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); + } + }