diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index c35d3c96f8..81c8fd64e5 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -372,6 +372,7 @@ predicate | expression (NOT)? BETWEEN expression AND expression # BetweenPredicate | expression (NOT)? LIKE expression (likeEscape)? # LikePredicate | expression comparisonOperator expression # ComparisonPredicate + | EXISTS expression # ExistsPredicate | MEMBER OF path # MemberOfPredicate | NOT predicate # NegatedPredicate | predicate AND predicate # AndPredicate @@ -657,14 +658,6 @@ sumFunction : SUM LEFT_PAREN DISTINCT? expression RIGHT_PAREN ; -everyFunction - : (EVERY|ALL) LEFT_PAREN DISTINCT? predicate RIGHT_PAREN - ; - -anyFunction - : (ANY|SOME) LEFT_PAREN DISTINCT? predicate RIGHT_PAREN - ; - minFunction : MIN LEFT_PAREN DISTINCT? expression RIGHT_PAREN ; @@ -677,6 +670,14 @@ countFunction : COUNT LEFT_PAREN DISTINCT? (expression | ASTERISK) RIGHT_PAREN ; +everyFunction + : (EVERY|ALL) LEFT_PAREN (predicate | subQuery) RIGHT_PAREN + ; + +anyFunction + : (ANY|SOME) LEFT_PAREN (predicate | subQuery) RIGHT_PAREN + ; + standardFunction : castFunction | extractFunction diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 9b3fdc6cde..4536549700 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -94,6 +94,7 @@ import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; +import org.hibernate.query.sqm.tree.expression.SqmAny; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -102,6 +103,7 @@ import org.hibernate.query.sqm.tree.expression.SqmCastTarget; import org.hibernate.query.sqm.tree.expression.SqmCollectionSize; import org.hibernate.query.sqm.tree.expression.SqmDistinct; import org.hibernate.query.sqm.tree.expression.SqmDurationUnit; +import org.hibernate.query.sqm.tree.expression.SqmEvery; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; import org.hibernate.query.sqm.tree.expression.SqmFormat; @@ -130,6 +132,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmAndPredicate; import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate; import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate; import org.hibernate.query.sqm.tree.predicate.SqmEmptinessPredicate; +import org.hibernate.query.sqm.tree.predicate.SqmExistsPredicate; import org.hibernate.query.sqm.tree.predicate.SqmGroupedPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInSubQueryPredicate; @@ -1286,6 +1289,12 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre throw new ParsingException( "Unexpected IN predicate type [" + ctx.getClass().getSimpleName() + "] : " + ctx.getText() ); } + @Override + public SqmPredicate visitExistsPredicate(HqlParser.ExistsPredicateContext ctx) { + final SqmExpression expression = (SqmExpression) ctx.expression().accept( this ); + return new SqmExistsPredicate( expression, creationContext.getNodeBuilder() ); + } + @Override @SuppressWarnings("unchecked") public Object visitEntityTypeExpression(HqlParser.EntityTypeExpressionContext ctx) { @@ -2886,10 +2895,12 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override public SqmExpression visitEveryFunction(HqlParser.EveryFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.predicate().accept( this ); - SqmTypedNode argument = ctx.DISTINCT() != null - ? new SqmDistinct<>(arg, getCreationContext().getNodeBuilder()) - : arg; + if ( ctx.subQuery()!=null ) { + SqmSubQuery subquery = (SqmSubQuery) ctx.subQuery().accept(this); + return new SqmEvery( subquery, subquery.getNodeType(), creationContext.getNodeBuilder() ); + } + + final SqmExpression argument = (SqmExpression) ctx.predicate().accept( this ); return getFunctionDescriptor("every").generateSqmExpression( argument, @@ -2902,10 +2913,12 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override public SqmExpression visitAnyFunction(HqlParser.AnyFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.predicate().accept( this ); - SqmTypedNode argument = ctx.DISTINCT() != null - ? new SqmDistinct<>(arg, getCreationContext().getNodeBuilder()) - : arg; + if ( ctx.subQuery()!=null ) { + SqmSubQuery subquery = (SqmSubQuery) ctx.subQuery().accept(this); + return new SqmAny( subquery, subquery.getNodeType(), creationContext.getNodeBuilder() ); + } + + final SqmExpression argument = (SqmExpression) ctx.predicate().accept( this ); return getFunctionDescriptor("any").generateSqmExpression( argument, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java index 38e2d27e5f..1568ac573f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java @@ -22,6 +22,7 @@ import org.hibernate.query.sqm.tree.domain.SqmMinElementPath; import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; +import org.hibernate.query.sqm.tree.expression.SqmAny; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -31,6 +32,7 @@ import org.hibernate.query.sqm.tree.expression.SqmCoalesce; import org.hibernate.query.sqm.tree.expression.SqmCollectionSize; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmDurationUnit; +import org.hibernate.query.sqm.tree.expression.SqmEvery; import org.hibernate.query.sqm.tree.expression.SqmFormat; import org.hibernate.query.sqm.tree.expression.SqmFunction; import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType; @@ -61,6 +63,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate; import org.hibernate.query.sqm.tree.predicate.SqmBooleanExpressionPredicate; import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate; import org.hibernate.query.sqm.tree.predicate.SqmEmptinessPredicate; +import org.hibernate.query.sqm.tree.predicate.SqmExistsPredicate; import org.hibernate.query.sqm.tree.predicate.SqmGroupedPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInSubQueryPredicate; @@ -187,6 +190,9 @@ public interface SemanticQueryWalker { T visitSearchedCaseExpression(SqmCaseSearched expression); + T visitAny(SqmAny sqmAny); + T visitEvery(SqmEvery sqmEvery); + T visitPositionalParameterExpression(SqmPositionalParameter expression); T visitNamedParameterExpression(SqmNamedParameter expression); @@ -254,6 +260,8 @@ public interface SemanticQueryWalker { T visitBooleanExpressionPredicate(SqmBooleanExpressionPredicate predicate); + T visitExistsPredicate(SqmExistsPredicate sqmExistsPredicate); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // sorting diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java index 710f6dbd2f..be95d2a519 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java @@ -28,6 +28,7 @@ import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; +import org.hibernate.query.sqm.tree.expression.SqmAny; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -38,6 +39,7 @@ import org.hibernate.query.sqm.tree.expression.SqmCollectionSize; import org.hibernate.query.sqm.tree.expression.SqmDistinct; import org.hibernate.query.sqm.tree.expression.SqmDurationUnit; import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral; +import org.hibernate.query.sqm.tree.expression.SqmEvery; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral; @@ -67,6 +69,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate; import org.hibernate.query.sqm.tree.predicate.SqmBooleanExpressionPredicate; import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate; import org.hibernate.query.sqm.tree.predicate.SqmEmptinessPredicate; +import org.hibernate.query.sqm.tree.predicate.SqmExistsPredicate; import org.hibernate.query.sqm.tree.predicate.SqmGroupedPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInSubQueryPredicate; @@ -811,6 +814,11 @@ public class SqmTreePrinter implements SemanticQueryWalker { return null; } + @Override + public Object visitExistsPredicate(SqmExistsPredicate sqmExistsPredicate) { + return null; + } + @Override public Object visitOrderByClause(SqmOrderByClause orderByClause) { return null; @@ -891,6 +899,16 @@ public class SqmTreePrinter implements SemanticQueryWalker { return null; } + @Override + public Object visitAny(SqmAny sqmAny) { + return null; + } + + @Override + public Object visitEvery(SqmEvery sqmEvery) { + return null; + } + @Override public Object visitDynamicInstantiation(SqmDynamicInstantiation sqmDynamicInstantiation) { processStanza( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java index 5e9783bad5..f023184d87 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java @@ -25,6 +25,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; +import org.hibernate.query.sqm.tree.expression.SqmAny; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -34,6 +35,7 @@ import org.hibernate.query.sqm.tree.expression.SqmCoalesce; import org.hibernate.query.sqm.tree.expression.SqmCollectionSize; import org.hibernate.query.sqm.tree.expression.SqmDurationUnit; import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral; +import org.hibernate.query.sqm.tree.expression.SqmEvery; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral; import org.hibernate.query.sqm.tree.expression.SqmFormat; @@ -63,6 +65,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate; import org.hibernate.query.sqm.tree.predicate.SqmBooleanExpressionPredicate; import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate; import org.hibernate.query.sqm.tree.predicate.SqmEmptinessPredicate; +import org.hibernate.query.sqm.tree.predicate.SqmExistsPredicate; import org.hibernate.query.sqm.tree.predicate.SqmGroupedPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInSubQueryPredicate; @@ -386,6 +389,12 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker sqmAny) { + return sqmAny; + } + + @Override + public Object visitEvery(SqmEvery sqmEvery) { + return sqmEvery; + } + @Override public Object visitSearchedCaseExpression(SqmCaseSearched expression) { return expression; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 3d39fdac66..76f8988d41 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -70,6 +70,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; +import org.hibernate.query.sqm.tree.expression.SqmAny; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -78,6 +79,7 @@ import org.hibernate.query.sqm.tree.expression.SqmCastTarget; import org.hibernate.query.sqm.tree.expression.SqmDistinct; import org.hibernate.query.sqm.tree.expression.SqmDurationUnit; import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral; +import org.hibernate.query.sqm.tree.expression.SqmEvery; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral; @@ -104,6 +106,7 @@ import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; import org.hibernate.query.sqm.tree.predicate.SqmAndPredicate; import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate; import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate; +import org.hibernate.query.sqm.tree.predicate.SqmExistsPredicate; import org.hibernate.query.sqm.tree.predicate.SqmGroupedPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate; import org.hibernate.query.sqm.tree.predicate.SqmInSubQueryPredicate; @@ -133,6 +136,7 @@ import org.hibernate.sql.ast.tree.cte.CteColumn; import org.hibernate.sql.ast.tree.cte.CteConsumer; import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.ast.tree.expression.Any; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; @@ -140,6 +144,7 @@ import org.hibernate.sql.ast.tree.expression.CastTarget; import org.hibernate.sql.ast.tree.expression.Distinct; import org.hibernate.sql.ast.tree.expression.Duration; import org.hibernate.sql.ast.tree.expression.DurationUnit; +import org.hibernate.sql.ast.tree.expression.Every; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.ExtractUnit; import org.hibernate.sql.ast.tree.expression.Format; @@ -153,6 +158,7 @@ import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; import org.hibernate.sql.ast.tree.predicate.GroupedPredicate; import org.hibernate.sql.ast.tree.predicate.InListPredicate; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; @@ -2046,6 +2052,22 @@ public abstract class BaseSqmToSqlAstConverter return result; } + @Override + public Object visitAny(SqmAny sqmAny) { + return new Any( + visitSubQueryExpression( sqmAny.getSubquery() ), + null //resolveMappingExpressable( sqmAny.getNodeType() ) + ); + } + + @Override + public Object visitEvery(SqmEvery sqmEvery) { + return new Every( + visitSubQueryExpression( sqmEvery.getSubquery() ), + null //resolveMappingExpressable( sqmEvery.getNodeType() ) + ); + } + @Override public Object visitEnumLiteral(SqmEnumLiteral sqmEnumLiteral) { return new QueryLiteral<>( @@ -2303,4 +2325,9 @@ public abstract class BaseSqmToSqlAstConverter predicate.isNegated() ); } + + @Override + public Object visitExistsPredicate(SqmExistsPredicate predicate) { + return new ExistsPredicate( (QuerySpec) predicate.getExpression().accept( this ) ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java new file mode 100644 index 0000000000..973e4ea6c3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.tree.expression; + +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmExpressable; +import org.hibernate.query.sqm.tree.select.SqmSubQuery; + +/** + * @author Gavin King + */ +public class SqmAny extends AbstractSqmExpression { + + private final SqmSubQuery subquery; + + public SqmAny(SqmSubQuery subquery, SqmExpressable type, NodeBuilder criteriaBuilder) { + super(type, criteriaBuilder); + this.subquery = subquery; + } + + public SqmSubQuery getSubquery() { + return subquery; + } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitAny( this ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java new file mode 100644 index 0000000000..4e784cee0b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.tree.expression; + +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmExpressable; +import org.hibernate.query.sqm.tree.select.SqmSubQuery; + +/** + * @author Gavin King + */ +public class SqmEvery extends AbstractSqmExpression { + + private final SqmSubQuery subquery; + + public SqmEvery(SqmSubQuery subquery, SqmExpressable type, NodeBuilder criteriaBuilder) { + super(type, criteriaBuilder); + this.subquery = subquery; + } + + public SqmSubQuery getSubquery() { + return subquery; + } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitEvery( this ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmExistsPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmExistsPredicate.java new file mode 100644 index 0000000000..70afca1a95 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmExistsPredicate.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.tree.predicate; + +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.expression.SqmExpression; + +/** + * @author Gavin King + */ +public class SqmExistsPredicate extends AbstractNegatableSqmPredicate { + private final SqmExpression expression; + + public SqmExistsPredicate( + SqmExpression expression, + NodeBuilder nodeBuilder) { + super( nodeBuilder ); + this.expression = expression; + + expression.applyInferableType( expression.getNodeType() ); + } + + public SqmExpression getExpression() { + return expression; + } + + @Override + public T accept(SemanticQueryWalker walker) { + return walker.visitExistsPredicate( this ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstWalker.java index f079cc0958..31813cfe27 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstWalker.java @@ -9,6 +9,7 @@ package org.hibernate.sql.ast; import org.hibernate.Incubating; import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.expression.Any; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; @@ -18,6 +19,7 @@ import org.hibernate.sql.ast.tree.expression.Distinct; import org.hibernate.sql.ast.tree.expression.Duration; import org.hibernate.sql.ast.tree.expression.DurationUnit; import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; +import org.hibernate.sql.ast.tree.expression.Every; import org.hibernate.sql.ast.tree.expression.ExtractUnit; import org.hibernate.sql.ast.tree.expression.Format; import org.hibernate.sql.ast.tree.expression.JdbcLiteral; @@ -36,6 +38,7 @@ import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; import org.hibernate.sql.ast.tree.predicate.FilterPredicate; import org.hibernate.sql.ast.tree.predicate.GroupedPredicate; import org.hibernate.sql.ast.tree.predicate.InListPredicate; @@ -98,6 +101,10 @@ public interface SqlAstWalker { void visitCaseSimpleExpression(CaseSimpleExpression caseSimpleExpression); + void visitAny(Any any); + + void visitEvery(Every every); + void visitSelfRenderingExpression(SelfRenderingExpression expression); void visitSqlSelectionExpression(SqlSelectionExpression expression); @@ -124,6 +131,8 @@ public interface SqlAstWalker { void visitInSubQueryPredicate(InSubQueryPredicate inSubQueryPredicate); + void visitExistsPredicate(ExistsPredicate existsPredicate); + void visitJunction(Junction junction); void visitLikePredicate(LikePredicate likePredicate); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlTreePrinter.java index 29d075027f..d08ca1412e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlTreePrinter.java @@ -15,6 +15,7 @@ import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.SqlAstTreeLogger; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.expression.Any; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; @@ -24,6 +25,7 @@ import org.hibernate.sql.ast.tree.expression.Distinct; import org.hibernate.sql.ast.tree.expression.Duration; import org.hibernate.sql.ast.tree.expression.DurationUnit; import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; +import org.hibernate.sql.ast.tree.expression.Every; import org.hibernate.sql.ast.tree.expression.ExtractUnit; import org.hibernate.sql.ast.tree.expression.Format; import org.hibernate.sql.ast.tree.expression.JdbcLiteral; @@ -43,6 +45,7 @@ import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; import org.hibernate.sql.ast.tree.predicate.FilterPredicate; import org.hibernate.sql.ast.tree.predicate.GroupedPredicate; import org.hibernate.sql.ast.tree.predicate.InListPredicate; @@ -460,7 +463,17 @@ public class SqlTreePrinter implements SqlAstWalker { throw new NotYetImplementedFor6Exception(); } -// @Override + @Override + public void visitAny(Any any) { + + } + + @Override + public void visitEvery(Every every) { + + } + + // @Override // public void visitCoalesceFunction(CoalesceFunction coalesceExpression) { // throw new NotYetImplementedFor6Exception(); // } @@ -549,6 +562,11 @@ public class SqlTreePrinter implements SqlAstWalker { ); } + @Override + public void visitExistsPredicate(ExistsPredicate existsPredicate) { + + } + @Override public void visitJunction(Junction junction) { logNode( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java index 926cc65ee1..3e5dd773b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java @@ -24,6 +24,7 @@ import org.hibernate.query.sqm.sql.internal.EmbeddableValuedPathInterpretation; import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.tree.expression.Any; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; @@ -33,6 +34,7 @@ import org.hibernate.sql.ast.tree.expression.Distinct; import org.hibernate.sql.ast.tree.expression.Duration; import org.hibernate.sql.ast.tree.expression.DurationUnit; import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; +import org.hibernate.sql.ast.tree.expression.Every; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.ExtractUnit; import org.hibernate.sql.ast.tree.expression.Format; @@ -54,6 +56,7 @@ import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.from.VirtualTableGroup; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; import org.hibernate.sql.ast.tree.predicate.FilterPredicate; import org.hibernate.sql.ast.tree.predicate.GroupedPredicate; import org.hibernate.sql.ast.tree.predicate.InListPredicate; @@ -888,8 +891,19 @@ public abstract class AbstractSqlAstWalker appendSql( " end" ); } + @Override + public void visitAny(Any any) { + appendSql( "some " ); + any.getSubquery().accept( this ); + } -// @Override + @Override + public void visitEvery(Every every) { + appendSql( "all " ); + every.getSubquery().accept( this ); + } + + // @Override // public void visitGenericParameter(GenericParameter parameter) { // visitJdbcParameterBinder( parameter.getParameterBinder() ); // @@ -1064,6 +1078,12 @@ public abstract class AbstractSqlAstWalker visitQuerySpec( inSubQueryPredicate.getSubQuery() ); } + @Override + public void visitExistsPredicate(ExistsPredicate existsPredicate) { + appendSql( "exists " ); + existsPredicate.getExpression().accept( this ); + } + @Override public void visitJunction(Junction junction) { if ( junction.isEmpty() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Any.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Any.java new file mode 100644 index 0000000000..4806218bd4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Any.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.ast.tree.expression; + +import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.tree.select.QuerySpec; + +/** + * @author Gavin King + */ +public class Any implements Expression { + + private QuerySpec subquery; + private MappingModelExpressable type; + + public Any(QuerySpec subquery, MappingModelExpressable type) { + this.subquery = subquery; + this.type = type; + } + + public QuerySpec getSubquery() { + return subquery; + } + + @Override + public MappingModelExpressable getExpressionType() { + return type; + } + + @Override + public void accept(SqlAstWalker walker) { + walker.visitAny( this ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Every.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Every.java new file mode 100644 index 0000000000..f14865b053 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Every.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.ast.tree.expression; + +import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.tree.select.QuerySpec; + +/** + * @author Gavin King + */ +public class Every implements Expression { + + private QuerySpec subquery; + private MappingModelExpressable type; + + public Every(QuerySpec subquery, MappingModelExpressable type) { + this.subquery = subquery; + this.type = type; + } + + public QuerySpec getSubquery() { + return subquery; + } + + @Override + public MappingModelExpressable getExpressionType() { + return type; + } + + @Override + public void accept(SqlAstWalker walker) { + walker.visitEvery( this ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java new file mode 100644 index 0000000000..74378a5c0b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.ast.tree.predicate; + +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.tree.expression.Expression; + +/** + * @author Gavin King + */ +public class ExistsPredicate implements Predicate { + + private Expression expression; + + public ExistsPredicate(Expression expression) { + this.expression = expression; + } + + public Expression getExpression() { + return expression; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public void accept(SqlAstWalker sqlTreeWalker) { + sqlTreeWalker.visitExistsPredicate( this ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SubqueryOperatorsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SubqueryOperatorsTest.java new file mode 100644 index 0000000000..b9ac22ceef --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SubqueryOperatorsTest.java @@ -0,0 +1,104 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.query.hql; + +import org.hibernate.testing.junit5.SessionFactoryBasedFunctionalTest; +import org.hibernate.testing.orm.domain.gambit.SimpleEntity; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Calendar; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Gavin King + */ +public class SubqueryOperatorsTest extends SessionFactoryBasedFunctionalTest { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + SimpleEntity.class, + }; + } + + @Test + public void testEvery() { + inTransaction( + session -> { + List results = session.createQuery( + "from SimpleEntity o where o.someString >= every (select someString from SimpleEntity)" ) + .list(); + assertThat( results.size(), is( 1 ) ); + } ); + } + + @Test + public void testAny() { + inTransaction( + session -> { + List results = session.createQuery( + "from SimpleEntity o where o.someString >= any (select someString from SimpleEntity)" ) + .list(); + assertThat( results.size(), is( 2 ) ); + } ); + } + + @Test + public void testExists() { + inTransaction( + session -> { + List results = session.createQuery( + "from SimpleEntity o where exists (select someString from SimpleEntity where someString>o.someString)" ) + .list(); + assertThat( results.size(), is( 1 ) ); + results = session.createQuery( + "from SimpleEntity o where not exists (select someString from SimpleEntity where someString>o.someString)" ) + .list(); + assertThat( results.size(), is( 1 ) ); + } ); + } + + @BeforeEach + public void setUp() { + inTransaction( + session -> { + SimpleEntity entity = new SimpleEntity( + 1, + Calendar.getInstance().getTime(), + null, + Integer.MAX_VALUE, + Long.MAX_VALUE, + "aaa" + ); + session.save( entity ); + + SimpleEntity second_entity = new SimpleEntity( + 2, + Calendar.getInstance().getTime(), + null, + Integer.MIN_VALUE, + Long.MAX_VALUE, + "zzz" + ); + session.save( second_entity ); + + } ); + } + + @AfterEach + public void tearDown() { + inTransaction( + session -> { + session.createQuery( "delete SimpleEntity" ).executeUpdate(); + } ); + } +}