You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jdo-commits@db.apache.org by mb...@apache.org on 2020/01/03 19:45:28 UTC

[db-jdo] 01/01: JDO-778: JDOQLTypedQuery support for correlated subqueries

This is an automated email from the ASF dual-hosted git repository.

mbo pushed a commit to branch JDO-778-2
in repository https://gitbox.apache.org/repos/asf/db-jdo.git

commit a3d385c548bf5aa63c9f0d9c3c8ca075c4fd85b7
Author: Michael Bouschen <mb...@apache.org>
AuthorDate: Fri Jan 3 20:44:53 2020 +0100

    JDO-778: JDOQLTypedQuery support for correlated subqueries
    
    - Removed the two subquery methods taking a ListExpression and taking a MapExpression as argument.
    - Added TCK test case testing a correlated subquery using a list relationship.
    - Added more test data for subquery test cases.
    - Corresponding spec changes
---
 api/src/main/java/javax/jdo/JDOQLTypedQuery.java   |  25 ---
 specification/OOO/Ch14-Query.odt                   | Bin 78557 -> 79033 bytes
 .../jdoql/subqueries/CorrelatedSubqueries.java     | 212 +++++++++++++++++----
 tck/src/main/resources/conf/jdo-signatures.txt     |   2 -
 .../tck/pc/company/companyForSubqueriesTests.xml   |  24 +++
 5 files changed, 202 insertions(+), 61 deletions(-)

diff --git a/api/src/main/java/javax/jdo/JDOQLTypedQuery.java b/api/src/main/java/javax/jdo/JDOQLTypedQuery.java
index 19381b9..d91f85e 100644
--- a/api/src/main/java/javax/jdo/JDOQLTypedQuery.java
+++ b/api/src/main/java/javax/jdo/JDOQLTypedQuery.java
@@ -360,31 +360,6 @@ public interface JDOQLTypedQuery<T> extends Serializable, Closeable {
     <E> JDOQLTypedSubquery<E> subquery(CollectionExpression<Collection<E>, E> candidateCollection, Class<E> candidate, String candidateAlias);
 
     /**
-     * Method to return a correlated subquery for use in this query.
-     * The candidate collection of the subquery is defined using a list relationship of the outer query.
-     * To obtain the expression for the subquery to link it back to this query, call "result(...)" on the subquery.
-     * @param candidateList Expression defining the candidate list for the subquery
-     * @param candidate Candidate for the subquery
-     * @param candidateAlias Alias for the candidate
-     * @return The subquery
-     * @param <E> Candidate type for subquery
-     */
-    <E> JDOQLTypedSubquery<E> subquery(ListExpression<List<E>, E> candidateList, Class<E> candidate, String candidateAlias);
-
-    /**
-     * Method to return a correlated subquery for use in this query.
-     * The candidate collection of the subquery is defined using a map relationship of the outer query.
-     * To obtain the expression for the subquery to link it back to this query, call "result(...)" on the subquery.
-     * @param candidateMap Expression defining the candidate map for the subquery
-     * @param candidate Candidate for the subquery
-     * @param candidateAlias Alias for the candidate
-     * @return The subquery
-     * @param <K> The key type of the map relationship
-     * @param <V> The value type the map relationship
-     */
-    <K, V> JDOQLTypedSubquery<Map.Entry<K, V>> subquery(MapExpression<Map<K, V>, K, V> candidateMap, Class<Map.Entry<K, V>> candidate, String candidateAlias);
-
-    /**
      * Method to set the named parameters on this query prior to execution.
      * All parameter values specified in this method will only be retained until the subsequent query execution.
      * @param namedParamMap The map of parameter values keyed by their names.
diff --git a/specification/OOO/Ch14-Query.odt b/specification/OOO/Ch14-Query.odt
index 0d065a1..4c35a64 100644
Binary files a/specification/OOO/Ch14-Query.odt and b/specification/OOO/Ch14-Query.odt differ
diff --git a/tck/src/main/java/org/apache/jdo/tck/query/jdoql/subqueries/CorrelatedSubqueries.java b/tck/src/main/java/org/apache/jdo/tck/query/jdoql/subqueries/CorrelatedSubqueries.java
index ee535fd..b33adc4 100644
--- a/tck/src/main/java/org/apache/jdo/tck/query/jdoql/subqueries/CorrelatedSubqueries.java
+++ b/tck/src/main/java/org/apache/jdo/tck/query/jdoql/subqueries/CorrelatedSubqueries.java
@@ -19,12 +19,19 @@ package org.apache.jdo.tck.query.jdoql.subqueries;
 
 import java.util.List;
 
-import javax.jdo.PersistenceManager;
+import javax.jdo.JDOQLTypedQuery;
+import javax.jdo.JDOQLTypedSubquery;
 import javax.jdo.Query;
+import javax.jdo.Transaction;
+import javax.jdo.query.NumericExpression;
 
 import org.apache.jdo.tck.JDO_Test;
 import org.apache.jdo.tck.pc.company.CompanyModelReader;
 import org.apache.jdo.tck.pc.company.Employee;
+import org.apache.jdo.tck.pc.company.FullTimeEmployee;
+import org.apache.jdo.tck.pc.company.MeetingRoom;
+import org.apache.jdo.tck.pc.company.QEmployee;
+import org.apache.jdo.tck.pc.company.QMeetingRoom;
 import org.apache.jdo.tck.util.BatchTestRunner;
 
 /**
@@ -44,7 +51,17 @@ public class CorrelatedSubqueries extends SubqueriesTest {
     /** */
     private static final String ASSERTION_FAILED = 
         "Assertion A14.6.2-56 (CorrelatedSubqueries) failed: ";
-    
+
+    // Select employees who work more than the average of their department employees.
+    private static final String SINGLE_STRING_QUERY_COLLECTION_SUBQUERY =
+            "SELECT FROM " + Employee.class.getName() +
+            " WHERE this.weeklyhours > (SELECT AVG(e.weeklyhours) FROM this.department.employees e)";
+
+    // Select employees who work in a department having a meeting room with the maximum roomid.
+    private static final String SINGLE_STRING_QUERY_LIST_SUBQUERY =
+            "SELECT FROM " + Employee.class.getName() +
+            " WHERE (SELECT max(r.roomid) FROM this.department.meetingRooms r) == 3";
+
     /**
      * The <code>main</code> is called when the class
      * is directly executed from the command line.
@@ -55,38 +72,165 @@ public class CorrelatedSubqueries extends SubqueriesTest {
     }
 
     /** */
-    public void testPositive() throws Exception {
-        PersistenceManager pm = getPM();
-
-        List expectedResult = getTransientCompanyModelInstancesAsList(
-            new String[]{"emp1", "emp2", "emp4", "emp6", "emp7", "emp10"});
-
-        // Select employees who work more than the average of 
-        // their department employees. 
-        String singleStringJDOQL = 
-            "SELECT FROM " + Employee.class.getName() + " WHERE this.weeklyhours > " + 
-            "(SELECT AVG(e.weeklyhours) FROM this.department.employees e)";
-
-        // API query
-        Query sub = pm.newQuery(Employee.class);
-        sub.setResult("avg(this.weeklyhours)");
-        Query apiQuery = pm.newQuery(Employee.class);
-        apiQuery.setFilter("this.weeklyhours> averageWeeklyhours");
-        apiQuery.addSubquery(sub, "double averageWeeklyhours", 
-                          "this.department.employees"); 
-        executeJDOQuery(ASSERTION_FAILED, apiQuery, singleStringJDOQL, 
-                        false, null, expectedResult, true);
-
-        // API query against memory model
-        List allEmployees = getAllEmployees(pm);
-        apiQuery.setCandidates(allEmployees);
-        executeJDOQuery(ASSERTION_FAILED, apiQuery, singleStringJDOQL, 
-                        false, null, expectedResult, true);
-
-        // single String JDOQL
-        Query singleStringQuery = pm.newQuery(singleStringJDOQL);
-        executeJDOQuery(ASSERTION_FAILED, singleStringQuery, singleStringJDOQL, 
-                        false, null, expectedResult, true);
+    public void testCollectionApiQuery() {
+        Transaction tx = pm.currentTransaction();
+        try {
+            tx.begin();
+            List expected = getTransientCompanyModelInstancesAsList(
+                    new String[]{"emp1", "emp2", "emp4", "emp6", "emp7", "emp10"});
+
+            // API query
+            try (Query apiQuery = pm.newQuery(Employee.class)) {
+                Query sub = pm.newQuery(Employee.class);
+                sub.setResult("avg(this.weeklyhours)");
+                apiQuery.setFilter("this.weeklyhours> averageWeeklyhours");
+                apiQuery.addSubquery(sub, "double averageWeeklyhours",
+                              "this.department.employees");
+                List<FullTimeEmployee> emps = apiQuery.executeList();
+                checkQueryResultWithoutOrder(ASSERTION_FAILED, SINGLE_STRING_QUERY_COLLECTION_SUBQUERY, emps, expected);
+
+                // API query against memory model
+                List allEmployees = (List)pm.newQuery(Employee.class).execute();
+                apiQuery.setCandidates(allEmployees);
+                emps = apiQuery.executeList();
+                checkQueryResultWithoutOrder(ASSERTION_FAILED, SINGLE_STRING_QUERY_COLLECTION_SUBQUERY, emps, expected);
+            } catch (Exception ex) {
+                fail(ASSERTION_FAILED, ex.getLocalizedMessage());
+            }
+            tx.commit();
+        } finally {
+            if (tx.isActive()) {
+                tx.rollback();
+            }
+        }
+    }
+
+    /** */
+    public void testCollectionSingleStringQuery() {
+        Transaction tx = pm.currentTransaction();
+        try {
+            tx.begin();
+            List expected = getTransientCompanyModelInstancesAsList(
+                    new String[]{"emp1", "emp2", "emp4", "emp6", "emp7", "emp10"});
+            // single String JDOQL
+            try (Query singleStringQuery = pm.newQuery(SINGLE_STRING_QUERY_COLLECTION_SUBQUERY)) {
+                List<FullTimeEmployee> emps = singleStringQuery.executeList();
+                checkQueryResultWithoutOrder(ASSERTION_FAILED, SINGLE_STRING_QUERY_COLLECTION_SUBQUERY, emps, expected);
+            } catch (Exception ex) {
+                fail(ASSERTION_FAILED, ex.getLocalizedMessage());
+            }
+            tx.commit();
+        } finally {
+            if (tx.isActive()) {
+                tx.rollback();
+            }
+        }
+    }
+
+    /** */
+    public void testCollectionJDOQLTypedQuery() {
+        Transaction tx = pm.currentTransaction();
+        try {
+            tx.begin();
+            List expected = getTransientCompanyModelInstancesAsList(
+                    new String[]{"emp1", "emp2", "emp4", "emp6", "emp7", "emp10"});
+            try (JDOQLTypedQuery<Employee> q = pm.newJDOQLTypedQuery(Employee.class)) {
+                QEmployee cand = QEmployee.candidate();
+                JDOQLTypedSubquery<Employee> subquery = q.subquery(cand.department.employees, Employee.class,"e");
+                QEmployee candsub = QEmployee.candidate("e");
+                q.filter(cand.weeklyhours.gt(subquery.selectUnique(candsub.weeklyhours.avg())));
+                List<Employee> emps = q.executeList();
+                checkQueryResultWithoutOrder(ASSERTION_FAILED, SINGLE_STRING_QUERY_COLLECTION_SUBQUERY, emps, expected);
+            } catch (Exception ex) {
+                fail(ASSERTION_FAILED, ex.getLocalizedMessage());
+            }
+            tx.commit();
+        } finally {
+            if (tx.isActive()) {
+                tx.rollback();
+            }
+        }
+    }
+
+    /** */
+    public void testListApiQuery() {
+        Transaction tx = pm.currentTransaction();
+        try {
+            tx.begin();
+            List expected = getTransientCompanyModelInstancesAsList(
+                    new String[]{"emp7", "emp8", "emp9", "emp10"});
+
+            // API query
+            try (Query apiQuery = pm.newQuery(Employee.class)) {
+                 Query sub = pm.newQuery(MeetingRoom.class);
+                sub.setResult("max(roomid)");
+                apiQuery.setFilter("maxNumber == 3");
+                apiQuery.addSubquery(sub, "long maxNumber", "this.department.meetingRooms");
+                List<FullTimeEmployee> emps = apiQuery.executeList();
+                checkQueryResultWithoutOrder(ASSERTION_FAILED, SINGLE_STRING_QUERY_LIST_SUBQUERY, emps, expected);
+
+                // API query against memory model
+                List allEmployees = (List)pm.newQuery(Employee.class).execute();
+                apiQuery.setCandidates(allEmployees);
+                emps = apiQuery.executeList();
+                checkQueryResultWithoutOrder(ASSERTION_FAILED, SINGLE_STRING_QUERY_LIST_SUBQUERY, emps, expected);
+            } catch (Exception ex) {
+                fail(ASSERTION_FAILED, ex.getLocalizedMessage());
+            }
+            tx.commit();
+        } finally {
+            if (tx.isActive()) {
+                tx.rollback();
+            }
+        }
+    }
+
+    /** */
+    public void testListSingleStringQuery() {
+        Transaction tx = pm.currentTransaction();
+        try {
+            tx.begin();
+            List expected = getTransientCompanyModelInstancesAsList(
+                    new String[]{"emp7", "emp8", "emp9", "emp10"});
+            // single String JDOQL
+            try (Query singleStringQuery = pm.newQuery(SINGLE_STRING_QUERY_LIST_SUBQUERY)) {
+                List<FullTimeEmployee> emps = singleStringQuery.executeList();
+                checkQueryResultWithoutOrder(ASSERTION_FAILED, SINGLE_STRING_QUERY_LIST_SUBQUERY, emps, expected);
+            } catch (Exception ex) {
+                fail(ASSERTION_FAILED, ex.getLocalizedMessage());
+            }
+            tx.commit();
+        } finally {
+            if (tx.isActive()) {
+                tx.rollback();
+            }
+        }
+    }
+
+    /** */
+    public void testListJDOQLTypedQuery() {
+        Transaction tx = pm.currentTransaction();
+        try {
+            tx.begin();
+            List expected = getTransientCompanyModelInstancesAsList(
+                    new String[]{"emp7", "emp8", "emp9", "emp10"});
+            try (JDOQLTypedQuery<Employee> q = pm.newJDOQLTypedQuery(Employee.class)) {
+                QEmployee cand = QEmployee.candidate();
+                JDOQLTypedSubquery<MeetingRoom> subquery =
+                        q.subquery(cand.department.meetingRooms, MeetingRoom.class,"r");
+                QMeetingRoom candsub = QMeetingRoom.candidate("r");
+                q.filter(subquery.selectUnique((NumericExpression)candsub.roomid.max()).eq(3l));
+                List<Employee> emps = q.executeList();
+                checkQueryResultWithoutOrder(ASSERTION_FAILED, SINGLE_STRING_QUERY_COLLECTION_SUBQUERY, emps, expected);
+            } catch (Exception ex) {
+                fail(ASSERTION_FAILED, ex.getLocalizedMessage());
+            }
+            tx.commit();
+        } finally {
+            if (tx.isActive()) {
+                tx.rollback();
+            }
+        }
     }
 
     /**
diff --git a/tck/src/main/resources/conf/jdo-signatures.txt b/tck/src/main/resources/conf/jdo-signatures.txt
index cf3399a..2c47436 100644
--- a/tck/src/main/resources/conf/jdo-signatures.txt
+++ b/tck/src/main/resources/conf/jdo-signatures.txt
@@ -1747,8 +1747,6 @@ public interface javax.jdo.JDOQLTypedQuery extends java.io.Serializable, java.io
     abstract javax.jdo.JDOQLTypedSubquery subquery(String candidateAlias);
     abstract javax.jdo.JDOQLTypedSubquery subquery(Class candidate, String candidateAlias);
     abstract javax.jdo.JDOQLTypedSubquery subquery(javax.jdo.query.CollectionExpression candidateCollection, Class candidate , String candidateAlias);
-    abstract javax.jdo.JDOQLTypedSubquery subquery(javax.jdo.query.ListExpression candidateList, Class candidate , String candidateAlias);
-    abstract javax.jdo.JDOQLTypedSubquery subquery(javax.jdo.query.MapExpression candidateMap, Class candidate , String candidateAlias);
     abstract javax.jdo.JDOQLTypedQuery setParameters(java.util.Map namedParamMap);
     abstract javax.jdo.JDOQLTypedQuery setParameter(javax.jdo.query.Expression paramExpr, Object value);
     abstract javax.jdo.JDOQLTypedQuery setParameter(String paramName, Object value);
diff --git a/tck/src/main/resources/testdata/org/apache/jdo/tck/pc/company/companyForSubqueriesTests.xml b/tck/src/main/resources/testdata/org/apache/jdo/tck/pc/company/companyForSubqueriesTests.xml
index ec710df..0fd109a 100644
--- a/tck/src/main/resources/testdata/org/apache/jdo/tck/pc/company/companyForSubqueriesTests.xml
+++ b/tck/src/main/resources/testdata/org/apache/jdo/tck/pc/company/companyForSubqueriesTests.xml
@@ -41,6 +41,19 @@
         </property>
     </bean>
 
+    <bean id="room1" factory-bean="companyFactory" factory-method="newMeetingRoom">
+        <constructor-arg index="0" type="long"><value>1</value></constructor-arg>
+        <constructor-arg index="1" type="java.lang.String"><value>Comfy Room</value></constructor-arg>
+    </bean>
+    <bean id="room2" factory-bean="companyFactory" factory-method="newMeetingRoom">
+        <constructor-arg index="0" type="long"><value>2</value></constructor-arg>
+        <constructor-arg index="1" type="java.lang.String"><value>Large Discussion Room</value></constructor-arg>
+    </bean>
+    <bean id="room3" factory-bean="companyFactory" factory-method="newMeetingRoom">
+        <constructor-arg index="0" type="long"><value>3</value></constructor-arg>
+        <constructor-arg index="1" type="java.lang.String"><value>Conference Room</value></constructor-arg>
+    </bean>
+
     <bean id="dept1" factory-bean="companyFactory" factory-method="newDepartment">
         <constructor-arg index="0" type="long"><value>1</value></constructor-arg>
         <constructor-arg index="1" type="java.lang.String" ><value>Development</value></constructor-arg>
@@ -55,6 +68,12 @@
                 <ref local="emp6"/>
             </set>
         </property>
+        <property name="meetingRooms">
+            <list>
+                <ref local="room1"/>
+                <ref local="room2"/>
+            </list>
+        </property>
     </bean>
 
     <bean id="dept2" factory-bean="companyFactory" factory-method="newDepartment">
@@ -69,6 +88,11 @@
                 <ref local="emp10"/>
             </set>
         </property>
+        <property name="meetingRooms">
+            <list>
+                <ref local="room3"/>
+            </list>
+        </property>
     </bean>
 
     <bean id="emp1" factory-bean="companyFactory" factory-method="newFullTimeEmployee">