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

ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 9b04dba4ffbfe4b4131c6e1864ca41c26050024d
Author: Nikita Timofeev <stari...@gmail.com>
AuthorDate: Mon Sep 5 10:19:09 2022 +0300

    CAY-2715 Support ANY and ALL expressions for subqueries
---
 RELEASE-NOTES.txt                                  |  1 +
 .../translator/select/QualifierTranslator.java     |  4 +
 .../java/org/apache/cayenne/exp/Expression.java    | 10 +++
 .../org/apache/cayenne/exp/ExpressionFactory.java  | 16 ++++
 .../java/org/apache/cayenne/exp/parser/ASTAll.java | 58 ++++++++++++++
 .../java/org/apache/cayenne/exp/parser/ASTAny.java | 58 ++++++++++++++
 .../apache/cayenne/exp/property/BaseProperty.java  | 12 ++-
 .../cayenne/exp/property/ComparableProperty.java   | 88 ++++++++++++++++++++++
 .../cayenne/exp/property/StringProperty.java       |  3 +-
 .../cayenne/query/ObjectSelect_SubqueryIT.java     |  9 +++
 10 files changed, 257 insertions(+), 2 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index ee8f8b6f1..3f9b9bbee 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -15,6 +15,7 @@ Changes/New Features:
 
 CAY-2378 Switch usage of SelectQuery to ObjectSelect internally
 CAY-2498 Rename packages to prevent crossing package names
+CAY-2715 Support ANY and ALL expressions for subqueries
 CAY-2737 Cayenne 4.3: cleanup deprecated code
 CAY-2741 Cleanup TransactionDescriptor and deprecate 
DefaultTransactionDescriptor
 CAY-2742 Switch minimum required Java version to 11
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
index 765b14969..200b70f34 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
@@ -199,6 +199,10 @@ class QualifierTranslator implements TraversalHandler {
                 return new FunctionNode("EXISTS", null, false);
             case NOT_EXISTS:
                 return new FunctionNode("NOT EXISTS", null, false);
+            case ALL:
+                return new FunctionNode("ALL", null, false);
+            case ANY:
+                return new FunctionNode("ANY", null, false);
 
             case SUBQUERY:
                 ASTSubquery subquery = (ASTSubquery)node;
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
index 05debf51d..4ab98f258 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
@@ -194,6 +194,16 @@ public abstract class Expression implements Serializable, 
XMLSerializable {
         */
        public static final int CUSTOM_OP = 53;
 
+       /**
+        * @since 4.3
+        */
+       public static final int ALL = 54;
+
+       /**
+        * @since 4.3
+        */
+       public static final int ANY = 55;
+
        protected int type;
 
        /**
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index 094e352e4..9fe545561 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -21,7 +21,9 @@ package org.apache.cayenne.exp;
 
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.exp.parser.ASTAdd;
+import org.apache.cayenne.exp.parser.ASTAll;
 import org.apache.cayenne.exp.parser.ASTAnd;
+import org.apache.cayenne.exp.parser.ASTAny;
 import org.apache.cayenne.exp.parser.ASTBetween;
 import org.apache.cayenne.exp.parser.ASTBitwiseAnd;
 import org.apache.cayenne.exp.parser.ASTBitwiseLeftShift;
@@ -1468,4 +1470,18 @@ public class ExpressionFactory {
                }
                return new ASTNotIn((SimpleNode)exp, new ASTSubquery(subQuery));
        }
+
+       /**
+        * @since 4.3
+        */
+       public static Expression all(ColumnSelect<?> subquery) {
+               return new ASTAll(new ASTSubquery(subquery));
+       }
+
+       /**
+        * @since 4.3
+        */
+       public static Expression any(ColumnSelect<?> subquery) {
+               return new ASTAny(new ASTSubquery(subquery));
+       }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAll.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAll.java
new file mode 100644
index 000000000..631c146a6
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAll.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.parser;
+
+import org.apache.cayenne.exp.Expression;
+
+public class ASTAll extends ConditionNode {
+    public ASTAll(ASTSubquery subquery) {
+        super(0);
+        jjtAddChild(subquery, 0);
+    }
+
+    ASTAll(int id) {
+        super(id);
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTAll(id);
+    }
+
+    @Override
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) 
throws Exception {
+        return null;
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        return "ALL";
+    }
+
+    @Override
+    public int getType() {
+        return Expression.ALL;
+    }
+}
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAny.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAny.java
new file mode 100644
index 000000000..0a1fabfd6
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAny.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.parser;
+
+import org.apache.cayenne.exp.Expression;
+
+public class ASTAny extends ConditionNode {
+    public ASTAny(ASTSubquery subquery) {
+        super(0);
+        jjtAddChild(subquery, 0);
+    }
+
+    ASTAny(int id) {
+        super(id);
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTAny(id);
+    }
+
+    @Override
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) 
throws Exception {
+        return null;
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        return "ANY";
+    }
+
+    @Override
+    public int getType() {
+        return Expression.ANY;
+    }
+}
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
index 478de4784..77855d19c 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
@@ -401,7 +401,17 @@ public class BaseProperty<E> implements Property<E> {
     }
 
     /**
-     * @return property that will be translated relative to parent query
+     * This operator allows to access properties of the enclosing query from 
the subquery.
+     * It allows multiple nesting levels to access a corresponding query in 
case of multiple levels of subqueries.
+     *
+     * Example: <pre>{@code
+     * ObjectSelect.query(Artist.class)
+     *                 
.where(ExpressionFactory.notExists(ObjectSelect.query(Painting.class)
+     *                         
.where(Painting.TO_ARTIST.eq(Artist.ARTIST_ID_PK_PROPERTY.enclosing()))))
+     * }
+     * </pre>
+     *
+     * @return property that will be translated relative to a parent query
      */
     public BaseProperty<E> enclosing() {
         return 
PropertyFactory.createBase(ExpressionFactory.enclosingObjectExp(getExpression()),
 getType());
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ComparableProperty.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ComparableProperty.java
index bad68a361..e1c87c5d3 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ComparableProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ComparableProperty.java
@@ -19,9 +19,11 @@
 
 package org.apache.cayenne.exp.property;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.FunctionExpressionFactory;
+import org.apache.cayenne.query.ColumnSelect;
 
 /**
  * Interface (or "Trait") that provides basic functionality for comparable 
properties.
@@ -122,4 +124,90 @@ public interface ComparableProperty<E> extends Property<E> 
{
     default BaseProperty<E> min() {
         return 
PropertyFactory.createBase(FunctionExpressionFactory.minExp(getExpression()), 
getType());
     }
+
+    /**
+     * @param subquery to use, must be a single column query.
+     * @return {@link Expression} that translates to a "< ALL (subquery)" SQL
+     * @since 4.3
+     */
+    default Expression ltAll(ColumnSelect<E> subquery) {
+        assertValidateSubqueryForComparison(subquery);
+        return ExpressionFactory.lessExp(getExpression(), 
ExpressionFactory.all(subquery));
+    }
+
+    /**
+     * @param subquery to use, must be a single column query.
+     * @return {@link Expression} that translates to a "<= ALL (subquery)" SQL
+     * @since 4.3
+     */
+    default Expression lteAll(ColumnSelect<E> subquery) {
+        assertValidateSubqueryForComparison(subquery);
+        return ExpressionFactory.lessOrEqualExp(getExpression(), 
ExpressionFactory.all(subquery));
+    }
+
+    /**
+     * @param subquery to use, must be a single column query.
+     * @return {@link Expression} that translates to a "> ALL (subquery)" SQL
+     * @since 4.3
+     */
+    default Expression gtAll(ColumnSelect<E> subquery) {
+        assertValidateSubqueryForComparison(subquery);
+        return ExpressionFactory.greaterExp(getExpression(), 
ExpressionFactory.all(subquery));
+    }
+
+    /**
+     * @param subquery to use, must be a single column query.
+     * @return {@link Expression} that translates to a ">= ALL (subquery)" SQL
+     * @since 4.3
+     */
+    default Expression gteAll(ColumnSelect<E> subquery) {
+        assertValidateSubqueryForComparison(subquery);
+        return ExpressionFactory.greaterOrEqualExp(getExpression(), 
ExpressionFactory.all(subquery));
+    }
+
+    /**
+     * @param subquery to use, must be a single column query.
+     * @return {@link Expression} that translates to a "< ANY (subquery)" SQL
+     * @since 4.3
+     */
+    default Expression ltAny(ColumnSelect<E> subquery) {
+        assertValidateSubqueryForComparison(subquery);
+        return ExpressionFactory.lessExp(getExpression(), 
ExpressionFactory.any(subquery));
+    }
+
+    /**
+     * @param subquery to use, must be a single column query.
+     * @return {@link Expression} that translates to a "<= ANY (subquery)" SQL
+     * @since 4.3
+     */
+    default Expression lteAny(ColumnSelect<E> subquery) {
+        assertValidateSubqueryForComparison(subquery);
+        return ExpressionFactory.lessOrEqualExp(getExpression(), 
ExpressionFactory.any(subquery));
+    }
+
+    /**
+     * @param subquery to use, must be a single column query.
+     * @return {@link Expression} that translates to a "> ANY (subquery)" SQL
+     * @since 4.3
+     */
+    default Expression gtAny(ColumnSelect<E> subquery) {
+        assertValidateSubqueryForComparison(subquery);
+        return ExpressionFactory.greaterExp(getExpression(), 
ExpressionFactory.any(subquery));
+    }
+
+    /**
+     * @param subquery to use, must be a single column query.
+     * @return {@link Expression} that translates to a ">= ANY (subquery)" SQL
+     * @since 4.3
+     */
+    default Expression gteAny(ColumnSelect<E> subquery) {
+        assertValidateSubqueryForComparison(subquery);
+        return ExpressionFactory.greaterOrEqualExp(getExpression(), 
ExpressionFactory.any(subquery));
+    }
+
+    private static <E> void 
assertValidateSubqueryForComparison(ColumnSelect<E> subquery) {
+        if(subquery.getColumns().size() != 1) {
+            throw new CayenneRuntimeException("Only single-column query could 
be used in the comparison.");
+        }
+    }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
index ee04eba7f..eaf010495 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
@@ -303,8 +303,9 @@ public class StringProperty<E extends CharSequence> extends 
BaseProperty<E> impl
     }
 
     /**
-     * @return property that will be translated relative to parent query
+     * @inheritDoc
      */
+    @Override
     public StringProperty<E> enclosing() {
         return 
PropertyFactory.createString(ExpressionFactory.enclosingObjectExp(getExpression()),
 getType());
     }
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
index 5741dee42..8635d7169 100644
--- 
a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
@@ -170,4 +170,13 @@ public class ObjectSelect_SubqueryIT extends ServerCase {
 
         assertEquals(16L, count);
     }
+
+    @Test
+    public void selectQuery_ltAll() {
+        long count = ObjectSelect.query(Artist.class)
+                
.where(Artist.DATE_OF_BIRTH.year().ltAll(ObjectSelect.columnQuery(Artist.class, 
Artist.DATE_OF_BIRTH.year())))
+                .selectCount(context);
+        assertEquals(0L, count);
+    }
+
 }

Reply via email to