BatchFetchType.IN causes IndexOutOfBoundsException in ForeignReferenceMapping.extractResultsFromBatchQuery (#1426)

Fixes #1148 (bugfix + unit test)

Signed-off-by: Radek Felcman <radek.felcman@oracle.com>
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/ForeignReferenceMapping.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/ForeignReferenceMapping.java
index 28525a6..7c91501 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/ForeignReferenceMapping.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/ForeignReferenceMapping.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020 IBM Corporation. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
@@ -576,6 +576,9 @@
                     // If only fetching a page, need to make sure the row we want is in the page.
                     startIndex = parentRows.indexOf(sourceRow);
                 }
+                if (startIndex == -1) {
+                    return null;
+                }
                 List foreignKeyValues = new ArrayList(size);
                 Set foreignKeys = new HashSet(size);
                 int index = 0;
diff --git a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/AdvancedJPAJunitTest.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/AdvancedJPAJunitTest.java
index 0a2c9e9..ff3c154 100644
--- a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/AdvancedJPAJunitTest.java
+++ b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/AdvancedJPAJunitTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 1998, 2018 IBM Corporation. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
@@ -50,6 +50,7 @@
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.FlushModeType;
 import jakarta.persistence.Query;
+import jakarta.persistence.TypedQuery;
 import jakarta.persistence.metamodel.Attribute;
 import jakarta.persistence.metamodel.Attribute.PersistentAttributeType;
 import jakarta.persistence.metamodel.Bindable.BindableType;
@@ -282,6 +283,7 @@
         suite.addTest(new AdvancedJPAJunitTest("testEmployeeToProjectWithBatchFetchTypeInReverseIteration"));
         suite.addTest(new AdvancedJPAJunitTest("testEmployeeToProjectWithBatchFetchTypeInCustomIteration"));
         suite.addTest(new AdvancedJPAJunitTest("testEmployeeToProjectWithBatchFetchTypeInRandomIteration"));
+        suite.addTest(new AdvancedJPAJunitTest("testEmployeeToProjectWithBatchFetchTypeWithSmallFetchSize"));
 
         if (!isJPA10()) {
             // These tests use JPA 2.0 entity manager API
@@ -3736,6 +3738,72 @@
     }
 
     /**
+     * Bug 1148 (GitHub)
+     * Test batch fetch with small fetch size
+     */
+    public void testEmployeeToProjectWithBatchFetchTypeWithSmallFetchSize() {
+        final String lastName = "testRandomEmployeeToProject";
+
+        // Set up
+        Set<Employee> employeesToRemove = new HashSet<>();
+        Set<Integer> employeeIds = new HashSet<>();
+        EntityManager em = createEntityManager();
+        for (int i = 0; i < 100; i++) {
+            beginTransaction(em);
+            Employee employee = new Employee();
+            employee.setLastName(lastName);
+            employeesToRemove.add(employee);
+            em.persist(employee);
+            employeeIds.add(employee.getId());
+            for (int j = 0; j < 20; j++) {
+                Project project = new Project();
+                employee.addProject(project);
+                em.persist(project);
+            }
+            commitTransaction(em);
+        }
+
+        JpaEntityManager jpaEntityManager = (JpaEntityManager) em.getDelegate();
+        jpaEntityManager.getUnitOfWork().getIdentityMapAccessor().initializeAllIdentityMaps();
+        try {
+            String jpql = "SELECT employee FROM Employee employee WHERE employee.id IN :ids";
+            TypedQuery<Employee> query = em.createQuery(jpql, Employee.class)
+                    .setParameter("ids", employeeIds)
+                    .setHint(QueryHints.BATCH_TYPE, BatchFetchType.IN)
+                    .setHint(QueryHints.BATCH, "employee.projects.properties")
+                    .setHint(QueryHints.BATCH_SIZE, 3);
+            List<Employee> employees = query.getResultList();
+
+            // Trigger the bug
+            Collections.shuffle(employees);
+
+            int count = 0;
+            try {
+                for (Employee employee : employees) {
+                    for (Project project : employee.getProjects()) {
+                        count++;
+                    }
+                }
+                Assert.assertEquals("Project objects received are not as many as expected", 2000, count);
+            } catch (ArrayIndexOutOfBoundsException x) {
+                Assert.fail(Helper.printStackTraceToString(x));
+            }
+        } finally {
+            // Clean up
+            beginTransaction(em);
+            for (Employee employee : employeesToRemove) {
+                employee = em.merge(employee);
+                for (Project project : employee.getProjects()) {
+                    em.remove(em.merge(project));
+                }
+                em.remove(employee);
+            }
+            commitTransaction(em);
+            closeEntityManager(em);
+        }
+    }
+
+    /**
      * Bug 502085 - @OneToMany with @OrderColumn contains a null element after specific scenario
      * 
      */