/*******************************************************************************
 * Copyright (c) 2010 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     05/19/2010-2.1 ailitchev - Bug 244124 - Add Nested FetchGroup 
 ******************************************************************************/
package org.eclipse.persistence.testing.tests.jpa.fetchgroups;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import junit.framework.TestSuite;

import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.indirection.IndirectList;
import org.eclipse.persistence.queries.FetchGroup;

import org.eclipse.persistence.testing.models.jpa.advanced.Employee;
import org.eclipse.persistence.testing.models.jpa.advanced.PhoneNumber;

import org.junit.Test;

/**
 * Simple tests to verify the functionality of single level FetchGroup usage
 * 
 * @author dclarke
 * @since EclipseLink 2.1
 */
public class SimpleFetchGroupTests extends BaseFetchGroupTests {

    public SimpleFetchGroupTests() {
        super();
    }

    public SimpleFetchGroupTests(String name) {
        super(name);
    }

    public static junit.framework.Test suite() {
        TestSuite suite = new TestSuite();
        suite.setName("SimpleFetchGroupTests");
        
        suite.addTest(new SimpleFetchGroupTests("testSetup"));
        suite.addTest(new SimpleFetchGroupTests("findNoFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("singleResultNoFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("resultListNoFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("singleResultEmptyFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("resultListEmptyFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("resultListPeriodFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("managerFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("managerFetchGroupWithJoinFetch"));
        suite.addTest(new SimpleFetchGroupTests("employeeNamesFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("joinFetchEmployeeAddressWithDynamicFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("joinFetchEmployeeAddressPhoneWithDynamicFetchGroup"));
        suite.addTest(new SimpleFetchGroupTests("verifyFetchedRelationshipAttributes"));
        suite.addTest(new SimpleFetchGroupTests("detachedByClosingEntityManagerObjectWithFetchGroup"));
        if (!isJPA10()) {
            suite.addTest(new SimpleFetchGroupTests("findEmptyFetchGroup"));
            suite.addTest(new SimpleFetchGroupTests("findEmptyFetchGroup_setUnfetchedSalary"));
            suite.addTest(new SimpleFetchGroupTests("verifyUnfetchedAttributes"));
            suite.addTest(new SimpleFetchGroupTests("explicitlyDetachedObjectWithFetchGroup"));
        }
        return suite;
    }
    
    @Test
    public void findNoFetchGroup() throws Exception {
        EntityManager em = createEntityManager();
        int minId = minimumEmployeeId(em);

        Employee emp = em.find(Employee.class, minId);

        assertNotNull(emp);

        // Check Basics
        assertFetchedAttribute(emp, "id");
        assertFetchedAttribute(emp, "version");
        assertFetchedAttribute(emp, "firstName");
        assertFetchedAttribute(emp, "lastName");
        assertFetchedAttribute(emp, "gender");
        assertFetchedAttribute(emp, "salary");
        assertFetchedAttribute(emp, "startTime");
        assertFetchedAttribute(emp, "endTime");
        if (emp.getPeriod() != null) {
            assertFetchedAttribute(emp.getPeriod(), "startDate");
            assertFetchedAttribute(emp.getPeriod(), "endDate");
        }

        // Check Relationships
        assertNotFetchedAttribute(emp, "address");
        assertNotFetchedAttribute(emp, "manager");
        assertNotFetchedAttribute(emp, "phoneNumbers");
        assertNotFetchedAttribute(emp, "projects");

        assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
    }

    @Test
    public void singleResultNoFetchGroup() throws Exception {
        EntityManager em = createEntityManager();

        Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
        query.setParameter("ID", minimumEmployeeId(em));

        Employee emp = (Employee) query.getSingleResult();

        assertNotNull(emp);

        // Check Basics
        assertFetchedAttribute(emp, "id");
        assertFetchedAttribute(emp, "version");
        assertFetchedAttribute(emp, "firstName");
        assertFetchedAttribute(emp, "lastName");
        assertFetchedAttribute(emp, "gender");
        assertFetchedAttribute(emp, "salary");
        assertFetchedAttribute(emp, "startTime");
        assertFetchedAttribute(emp, "endTime");
        if (emp.getPeriod() != null) {
            assertFetchedAttribute(emp.getPeriod(), "startDate");
            assertFetchedAttribute(emp.getPeriod(), "endDate");
        }

        // Check Relationships
        assertNotFetchedAttribute(emp, "address");
        assertNotFetchedAttribute(emp, "manager");
        assertNotFetchedAttribute(emp, "phoneNumbers");
        assertNotFetchedAttribute(emp, "projects");

        assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
    }

    public void resultListNoFetchGroup() throws Exception {
        EntityManager em = createEntityManager();

        Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
        query.setParameter("ID", minimumEmployeeId(em));

        List<Employee> emps = query.getResultList();

        assertNotNull(emps);
        assertEquals(1, emps.size());

        Employee emp = emps.get(0);

        // Check Basics
        assertFetchedAttribute(emp, "id");
        assertFetchedAttribute(emp, "version");
        assertFetchedAttribute(emp, "firstName");
        assertFetchedAttribute(emp, "lastName");
        assertFetchedAttribute(emp, "gender");
        assertFetchedAttribute(emp, "salary");
        assertFetchedAttribute(emp, "startTime");
        assertFetchedAttribute(emp, "endTime");
        if (emp.getPeriod() != null) {
            assertFetchedAttribute(emp.getPeriod(), "startDate");
            assertFetchedAttribute(emp.getPeriod(), "endDate");
        }

        // Check Relationships
        assertNotFetchedAttribute(emp, "address");
        assertNotFetchedAttribute(emp, "manager");
        assertNotFetchedAttribute(emp, "phoneNumbers");
        assertNotFetchedAttribute(emp, "projects");
    }

    @Test
    public void findEmptyFetchGroup() throws Exception {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            int minId = minimumEmployeeId(em);

            assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            Map<String, Object> properties = new HashMap<String, Object>();
            FetchGroup emptyFG = new FetchGroup();
            properties.put(QueryHints.FETCH_GROUP, emptyFG);

            Employee emp = em.find(Employee.class, minId, properties);

            assertNotNull(emp);
            assertFetched(emp, emptyFG);
            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            // Check Basics
            assertFetchedAttribute(emp, "id");
            assertFetchedAttribute(emp, "version");
            assertNotFetchedAttribute(emp, "firstName");
            assertNotFetchedAttribute(emp, "lastName");
            assertNotFetchedAttribute(emp, "gender");
            assertNotFetchedAttribute(emp, "salary");
            assertNotFetchedAttribute(emp, "startTime");
            assertNotFetchedAttribute(emp, "endTime");
            if (emp.getPeriod() != null) {
                assertFetchedAttribute(emp.getPeriod(), "startDate");
                assertFetchedAttribute(emp.getPeriod(), "endDate");
            }

            // Check Relationships
            assertNotFetchedAttribute(emp, "address");
            assertNotFetchedAttribute(emp, "manager");
            assertNotFetchedAttribute(emp, "phoneNumbers");
            assertNotFetchedAttribute(emp, "projects");

            emp.getSalary();

            assertFetchedAttribute(emp, "salary");

            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertNoFetchGroup(emp);

            emp.getAddress();

            assertEquals(4, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertNoFetchGroup(emp.getAddress());

            emp.getPhoneNumbers().size();

            assertEquals(5, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            for (PhoneNumber phone : emp.getPhoneNumbers()) {
                assertNoFetchGroup(phone);
            }

            if (emp.getManager() != null) {
                assertEquals(6, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            } else {
                // If manager_id field is null then getManager() does not trigger an sql.
                assertEquals(5, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            } 
        }finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    @Test
    public void findEmptyFetchGroup_setUnfetchedSalary() throws Exception {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            int minId = minimumEmployeeId(em);

            assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            Map<String, Object> properties = new HashMap<String, Object>();
            FetchGroup emptyFG = new FetchGroup();
            properties.put(QueryHints.FETCH_GROUP, emptyFG);

            Employee emp = em.find(Employee.class, minId, properties);

            assertNotNull(emp);
            assertFetched(emp, emptyFG);
            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            // Check Basics
            assertFetchedAttribute(emp, "id");
            assertFetchedAttribute(emp, "version");
            assertNotFetchedAttribute(emp, "firstName");
            assertNotFetchedAttribute(emp, "lastName");
            assertNotFetchedAttribute(emp, "gender");
            assertNotFetchedAttribute(emp, "salary");
            assertNotFetchedAttribute(emp, "startTime");
            assertNotFetchedAttribute(emp, "endTime");
            if (emp.getPeriod() != null) {
                assertFetchedAttribute(emp.getPeriod(), "startDate");
                assertFetchedAttribute(emp.getPeriod(), "endDate");
            }
            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            // Check Relationships
            assertNotFetchedAttribute(emp, "address");
            assertNotFetchedAttribute(emp, "manager");
            assertNotFetchedAttribute(emp, "phoneNumbers");
            assertNotFetchedAttribute(emp, "projects");

            emp.setSalary(1);

            assertFetchedAttribute(emp, "salary");

            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertNoFetchGroup(emp);

            emp.getAddress();

            assertEquals(4, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertNoFetchGroup(emp.getAddress());

            emp.getPhoneNumbers().size();

            assertEquals(5, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            for (PhoneNumber phone : emp.getPhoneNumbers()) {
                assertNoFetchGroup(phone);
            }
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    @Test
    public void singleResultEmptyFetchGroup() throws Exception {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
            query.setParameter("ID", minimumEmployeeId(em));
            FetchGroup emptyFG = new FetchGroup();
            query.setHint(QueryHints.FETCH_GROUP, emptyFG);

            Employee emp = (Employee) query.getSingleResult();

            assertNotNull(emp);
            assertFetched(emp, emptyFG);
            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            // Check Basics
            assertFetchedAttribute(emp, "id");
            assertFetchedAttribute(emp, "version");
            assertNotFetchedAttribute(emp, "firstName");
            assertNotFetchedAttribute(emp, "lastName");
            assertNotFetchedAttribute(emp, "gender");
            assertNotFetchedAttribute(emp, "salary");
            assertNotFetchedAttribute(emp, "startTime");
            assertNotFetchedAttribute(emp, "endTime");
            if (emp.getPeriod() != null) {
                assertFetchedAttribute(emp.getPeriod(), "startDate");
                assertFetchedAttribute(emp.getPeriod(), "endDate");
            }

            // Check Relationships
            assertNotFetchedAttribute(emp, "address");
            assertNotFetchedAttribute(emp, "manager");
            assertNotFetchedAttribute(emp, "phoneNumbers");
            assertNotFetchedAttribute(emp, "projects");

            emp.getSalary();

            assertFetchedAttribute(emp, "salary");

            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertNoFetchGroup(emp);

            emp.getAddress();

            assertEquals(4, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertNoFetchGroup(emp.getAddress());

            emp.getPhoneNumbers().size();

            assertEquals(5, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            for (PhoneNumber phone : emp.getPhoneNumbers()) {
                assertNoFetchGroup(phone);
            }
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    /**
     * 
     */
    @Test
    public void resultListEmptyFetchGroup() throws Exception {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
            query.setParameter("ID", minimumEmployeeId(em));
            FetchGroup emptyFG = new FetchGroup();
            query.setHint(QueryHints.FETCH_GROUP, emptyFG);

            List<Employee> emps = query.getResultList();

            assertNotNull(emps);
            assertEquals(1, emps.size());

            Employee emp = emps.get(0);

            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertFetched(emp, emptyFG);

            // Check Basics
            assertFetchedAttribute(emp, "id");
            assertFetchedAttribute(emp, "version");
            assertNotFetchedAttribute(emp, "firstName");
            assertNotFetchedAttribute(emp, "lastName");
            assertNotFetchedAttribute(emp, "gender");
            assertNotFetchedAttribute(emp, "salary");
            assertNotFetchedAttribute(emp, "startTime");
            assertNotFetchedAttribute(emp, "endTime");
            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            if (emp.getPeriod() != null) {
                assertFetchedAttribute(emp.getPeriod(), "startDate");
                assertFetchedAttribute(emp.getPeriod(), "endDate");
            }

            // Check Relationships
            assertNotFetchedAttribute(emp, "address");
            assertNotFetchedAttribute(emp, "manager");
            assertNotFetchedAttribute(emp, "phoneNumbers");
            assertNotFetchedAttribute(emp, "projects");

            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            emp.getSalary();

            assertFetchedAttribute(emp, "salary");
            assertNoFetchGroup(emp);
            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            assertNoFetchGroup(emp.getAddress());

            assertEquals(4, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            for (PhoneNumber phone : emp.getPhoneNumbers()) {
                assertNoFetchGroup(phone);
            }
            assertEquals(5, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    /**
     * 
     */
    @Test
    public void resultListPeriodFetchGroup() throws Exception {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
            query.setParameter("ID", minimumEmployeeId(em));
            FetchGroup fg = new FetchGroup();
            fg.addAttribute("period");
            query.setHint(QueryHints.FETCH_GROUP, fg);

            List<Employee> emps = query.getResultList();

            assertNotNull(emps);
            assertEquals(1, emps.size());

            Employee emp = emps.get(0);

            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertFetched(emp, fg);

            // Check Basics
            assertFetchedAttribute(emp, "id");
            assertFetchedAttribute(emp, "version");
            assertNotFetchedAttribute(emp, "firstName");
            assertNotFetchedAttribute(emp, "lastName");
            assertNotFetchedAttribute(emp, "gender");
            assertNotFetchedAttribute(emp, "salary");
            assertNotFetchedAttribute(emp, "startTime");
            assertNotFetchedAttribute(emp, "endTime");
            if (emp.getPeriod() != null) {
                assertFetchedAttribute(emp.getPeriod(), "startDate");
                assertFetchedAttribute(emp.getPeriod(), "endDate");
            }

            // Check Relationships
            assertNotFetchedAttribute(emp, "address");
            assertNotFetchedAttribute(emp, "manager");
            assertNotFetchedAttribute(emp, "phoneNumbers");
            assertNotFetchedAttribute(emp, "projects");

            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            emp.getSalary();

            assertFetchedAttribute(emp, "salary");
            assertNoFetchGroup(emp);

            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            assertNoFetchGroup(emp.getAddress());

            assertEquals(4, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            for (PhoneNumber phone : emp.getPhoneNumbers()) {
                assertNoFetchGroup(phone);
            }
            assertEquals(5, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    @Test
    public void managerFetchGroup() throws Exception {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            // Use q query since find will only use default fetch group
            //Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
            //query.setParameter("ID", minimumEmployeeId(em));
            
            // Complex where clause used to avoid triggering employees and their departments:
            //   Don't include employees who are managers themselves - otherwise if first selected as employee, then as e.manager the full read will be triggered;
            //   Don't include managers with departments - because there is no fetch group on e.manager its (non-null) department will trigger an extra sql
            Query query = em.createQuery("SELECT e FROM Employee e WHERE e.manager IS NOT NULL AND NOT EXISTS(SELECT e2 FROM Employee e2 WHERE e2.manager = e) AND e.manager.department IS NULL");

            FetchGroup managerFG = new FetchGroup();
            managerFG.addAttribute("manager");

            query.setHint(QueryHints.FETCH_GROUP, managerFG);

            assertNotNull(getFetchGroup(query));
            assertSame(managerFG, getFetchGroup(query));

            Employee emp = (Employee) query.getSingleResult();

            assertFetched(emp, managerFG);
            //int nSqlBeforeGetManager = getQuerySQLTracker(em).getTotalSQLSELECTCalls();
            // manager hasn't been instantiated yet
            assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            
            int nSqlToAdd = 0;
            if (emp.getManager() != null) {
                assertFetchedAttribute(emp, "manager");
                // additional sql to select the manager
                nSqlToAdd++;
            }
            assertEquals(1 + nSqlToAdd, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            
            // acuses instantioation of the whole object
            emp.getLastName();

            assertEquals(2 + nSqlToAdd, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertNoFetchGroup(emp);

            for (PhoneNumber phone : emp.getPhoneNumbers()) {
                assertNoFetchGroup(phone);
                phone.getAreaCode();
            }

            assertEquals(3 + nSqlToAdd, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    @Test
    public void managerFetchGroupWithJoinFetch() throws Exception {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            //int minId = minimumEmployeeId(em);
            //assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            // Use q query since find will only use default fetch group
            //Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
            //query.setParameter("ID", minId);

            // Complex where clause used to avoid triggering employees and their departments:
            //   Don't include employees who are managers themselves - otherwise if first selected as employee, then as e.manager the full read will be triggered;
            //   Don't include managers with departments - because there is no fetch group on e.manager its (non-null) department will trigger an extra sql
            Query query = em.createQuery("SELECT e FROM Employee e WHERE e.manager IS NOT NULL AND NOT EXISTS(SELECT e2 FROM Employee e2 WHERE e2.manager = e) AND e.manager.department IS NULL");
            FetchGroup managerFG = new FetchGroup();
            managerFG.addAttribute("manager");

            query.setHint(QueryHints.FETCH_GROUP, managerFG);
            query.setHint(QueryHints.LEFT_FETCH, "e.manager");

            assertNotNull(getFetchGroup(query));
            assertSame(managerFG, getFetchGroup(query));

            Employee emp = (Employee) query.getSingleResult();

            assertFetched(emp, managerFG);
            assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            
            // manager (if not null) is instantiated by the fetch group, before emp.getManager call.
            emp.getManager();
            assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            
            // instantiates the whole object
            emp.getLastName();

            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertNoFetchGroup(emp);

            for (PhoneNumber phone : emp.getPhoneNumbers()) {
                assertNoFetchGroup(phone);
                phone.getAreaCode();
            }

            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    @Test
    public void employeeNamesFetchGroup() throws Exception {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            int minId = minimumEmployeeId(em);
            assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            // Use q query since find will only use default fetch group
            Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
            query.setParameter("ID", minId);
            FetchGroup namesFG = new FetchGroup();
            namesFG.addAttribute("firstName");
            namesFG.addAttribute("lastName");

            query.setHint(QueryHints.FETCH_GROUP, namesFG);

            assertNotNull(getFetchGroup(query));
            assertSame(namesFG, getFetchGroup(query));

            Employee emp = (Employee) query.getSingleResult();

            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertFetched(emp, namesFG);

            emp.getId();
            emp.getFirstName();
            emp.getLastName();
            emp.getVersion();

            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertFetched(emp, namesFG);

            emp.getGender();
            emp.getSalary();

            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            assertNoFetchGroup(emp);

            for (PhoneNumber phone : emp.getPhoneNumbers()) {
                assertNoFetchGroup(phone);
                phone.getAreaCode();
            }
            assertEquals(4, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            if (emp.getManager() != null) {
                assertEquals(5, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
                assertNoFetchGroup(emp.getManager());
            } else {
                // If manager_id field is null then getManager() does not trigger an sql.
                assertEquals(4, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
            }
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    @Test
    public void joinFetchEmployeeAddressWithDynamicFetchGroup() {
        EntityManager em = createEntityManager();

        Query query = em.createQuery("SELECT e FROM Employee e JOIN FETCH e.address");

        FetchGroup fetchGroup = new FetchGroup("names");
        fetchGroup.addAttribute("firstName");
        fetchGroup.addAttribute("lastName");
        query.setHint(QueryHints.FETCH_GROUP, fetchGroup);

        List<Employee> emps = query.getResultList();

        assertNotNull(emps);
    }

    @Test
    public void joinFetchEmployeeAddressPhoneWithDynamicFetchGroup() {
        EntityManager em = createEntityManager();

        Query query = em.createQuery("SELECT e FROM Employee e JOIN FETCH e.address WHERE e.id IN (SELECT p.id FROM PhoneNumber p)");

        FetchGroup fetchGroup = new FetchGroup("names");
        fetchGroup.addAttribute("firstName");
        fetchGroup.addAttribute("lastName");
        query.setHint(QueryHints.FETCH_GROUP, fetchGroup);

        List<Employee> emps = query.getResultList();

        assertNotNull(emps);
    }

    @Test
    public void verifyUnfetchedAttributes() throws Exception {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);
            TypedQuery<Employee> q = em.createQuery("SELECT e FROM Employee e WHERE e.id IN (SELECT MIN(p.id) FROM PhoneNumber p)", Employee.class);
            FetchGroup fg = new FetchGroup("Employee.empty");
            q.setHint(QueryHints.FETCH_GROUP, fg);
            Employee emp = q.getSingleResult();

            assertNotNull(emp);
            assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            // This check using the mapping returns a default (empty) IndirectList
            /*OneToManyMapping phoneMapping = (OneToManyMapping) employeeDescriptor.getMappingForAttributeName("phoneNumbers");
            IndirectList phones = (IndirectList) phoneMapping.getAttributeValueFromObject(emp);
            assertNotNull(phones);
            assertTrue(phones.isInstantiated());
            assertEquals(0, phones.size());
            assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());*/

            IndirectList phonesIL = (IndirectList) emp.getPhoneNumbers();
            assertFalse(phonesIL.isInstantiated());
            assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());

            assertTrue(emp.getPhoneNumbers().size() > 0);
            assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    @Test
    public void verifyFetchedRelationshipAttributes() throws Exception {
        EntityManager em = createEntityManager();

        FetchGroup fg = new FetchGroup("Employee.relationships");
        fg.addAttribute("address");
        fg.addAttribute("phoneNumbers");
        fg.addAttribute("manager");
        fg.addAttribute("projects");

        Map<String, Object> hints = new HashMap<String, Object>();
        hints.put(QueryHints.FETCH_GROUP, fg);

        Employee emp = minimumEmployee(em, hints);

        assertNotNull(emp);

    }

    @Test
    public void detachedByClosingEntityManagerObjectWithFetchGroup() {
        EntityManager em = createEntityManager();

        FetchGroup fg = new FetchGroup();
        fg.addAttribute("firstName");
        fg.addAttribute("lastName");

        Map<String, Object> hints = new HashMap<String, Object>();
        hints.put(QueryHints.FETCH_GROUP, fg);

        Employee emp = minimumEmployee(em, hints);
        assertFetched(emp, fg);
        closeEntityManager(em);
        
        // trigger the fetch group
        emp.getSalary();
        assertNoFetchGroup(emp);
    }

    @Test
    public void explicitlyDetachedObjectWithFetchGroup() {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);
            FetchGroup fg = new FetchGroup();
            fg.addAttribute("firstName");
            fg.addAttribute("lastName");

            Map<String, Object> hints = new HashMap<String, Object>();
            hints.put(QueryHints.FETCH_GROUP, fg);

            Employee emp = minimumEmployee(em, hints);
            em.detach(emp);
            assertFetched(emp, fg);
            
            // trigger the fetch group
            emp.getSalary();
            assertNoFetchGroup(emp);
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }
}
