Skip to content

Commit d8fcd1b

Browse files
committed
HHH-19240 Left-factor CONTAINS/INCLUDES/INTERSECTS to single BinaryExpressionPredicate rule
(cherry picked from commit 1eecda8)
1 parent bc30cdc commit d8fcd1b

File tree

2 files changed

+87
-73
lines changed

2 files changed

+87
-73
lines changed

hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -660,9 +660,7 @@ predicate
660660
| expression NOT? IN inList # InPredicate
661661
| expression NOT? BETWEEN expression AND expression # BetweenPredicate
662662
| expression NOT? (LIKE | ILIKE) expression likeEscape? # LikePredicate
663-
| expression NOT? CONTAINS expression # ContainsPredicate
664-
| expression NOT? INCLUDES expression # IncludesPredicate
665-
| expression NOT? INTERSECTS expression # IntersectsPredicate
663+
| expression NOT? (CONTAINS | INCLUDES | INTERSECTS) expression # BinaryExpressionPredicate
666664
| expression comparisonOperator expression # ComparisonPredicate
667665
| EXISTS collectionQuantifier LEFT_PAREN simplePath RIGHT_PAREN # ExistsCollectionPartPredicate
668666
| EXISTS expression # ExistsPredicate

hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java

Lines changed: 86 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Set;
3333

3434
import jakarta.persistence.criteria.Nulls;
35+
import org.antlr.v4.runtime.Token;
3536
import org.hibernate.AssertionFailure;
3637
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
3738
import org.hibernate.cfg.QuerySettings;
@@ -2460,6 +2461,91 @@ public SqmPredicate visitUnaryIsPredicate(HqlParser.UnaryIsPredicateContext ctx)
24602461
};
24612462
}
24622463

2464+
@Override
2465+
public SqmPredicate visitBinaryExpressionPredicate(HqlParser.BinaryExpressionPredicateContext ctx) {
2466+
final var firstSymbol = ((TerminalNode) ctx.getChild( 1 )).getSymbol();
2467+
final boolean negated;
2468+
final Token operationSymbol;
2469+
if ( firstSymbol.getType() == HqlParser.NOT ) {
2470+
negated = true;
2471+
operationSymbol = ((TerminalNode) ctx.getChild( 2 )).getSymbol();
2472+
}
2473+
else {
2474+
negated = false;
2475+
operationSymbol = firstSymbol;
2476+
}
2477+
final var expressions = ctx.expression();
2478+
final var lhsCtx = expressions.get( 0 );
2479+
final var rhsCtx = expressions.get( 1 );
2480+
return switch ( operationSymbol.getType() ) {
2481+
case HqlParser.CONTAINS -> {
2482+
final var lhs = (SqmExpression<?>) lhsCtx.accept( this );
2483+
final var rhs = (SqmExpression<?>) rhsCtx.accept( this );
2484+
final var lhsExpressible = lhs.getExpressible();
2485+
if ( lhsExpressible != null && !(lhsExpressible.getSqmType() instanceof BasicPluralType<?, ?>) ) {
2486+
throw new SemanticException(
2487+
"First operand for contains predicate must be a basic plural type expression, but found: " + lhsExpressible.getSqmType(),
2488+
query
2489+
);
2490+
}
2491+
final SelfRenderingSqmFunction<Boolean> contains = getFunctionDescriptor(
2492+
"array_contains" ).generateSqmExpression(
2493+
asList( lhs, rhs ),
2494+
null,
2495+
queryEngine()
2496+
);
2497+
yield new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() );
2498+
}
2499+
case HqlParser.INCLUDES -> {
2500+
final var lhs = (SqmExpression<?>) lhsCtx.accept( this );
2501+
final var rhs = (SqmExpression<?>) rhsCtx.accept( this );
2502+
final var lhsExpressible = lhs.getExpressible();
2503+
final var rhsExpressible = rhs.getExpressible();
2504+
if ( lhsExpressible != null && !( lhsExpressible.getSqmType() instanceof BasicPluralType<?, ?>) ) {
2505+
throw new SemanticException(
2506+
"First operand for includes predicate must be a basic plural type expression, but found: "
2507+
+ lhsExpressible.getSqmType(),
2508+
query
2509+
);
2510+
}
2511+
if ( rhsExpressible != null && !( rhsExpressible.getSqmType() instanceof BasicPluralType<?, ?>) ) {
2512+
throw new SemanticException(
2513+
"Second operand for includes predicate must be a basic plural type expression, but found: "
2514+
+ rhsExpressible.getSqmType(),
2515+
query
2516+
);
2517+
}
2518+
final SelfRenderingSqmFunction<Boolean> contains = getFunctionDescriptor( "array_includes" ).generateSqmExpression(
2519+
asList( lhs, rhs ),
2520+
null,
2521+
queryEngine()
2522+
);
2523+
yield new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() );
2524+
}
2525+
case HqlParser.INTERSECTS -> {
2526+
final var lhs = (SqmExpression<?>) lhsCtx.accept( this );
2527+
final var rhs = (SqmExpression<?>) rhsCtx.accept( this );
2528+
final var lhsExpressible = lhs.getExpressible();
2529+
if ( lhsExpressible != null && !( lhsExpressible.getSqmType() instanceof BasicPluralType<?, ?>) ) {
2530+
throw new SemanticException(
2531+
"First operand for intersects predicate must be a basic plural type expression, but found: "
2532+
+ lhsExpressible.getSqmType(),
2533+
query
2534+
);
2535+
}
2536+
final SelfRenderingSqmFunction<Boolean> contains =
2537+
getFunctionDescriptor( "array_intersects" )
2538+
.generateSqmExpression(
2539+
asList( lhs, rhs ),
2540+
null,
2541+
queryEngine()
2542+
);
2543+
yield new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() );
2544+
}
2545+
default -> throw new AssertionError( "Unknown binary expression predicate: " + operationSymbol );
2546+
};
2547+
}
2548+
24632549
@Override
24642550
public Object visitComparisonOperator(HqlParser.ComparisonOperatorContext ctx) {
24652551
final TerminalNode firstToken = (TerminalNode) ctx.getChild( 0 );
@@ -2614,26 +2700,6 @@ private String getPossibleEnumValue(HqlParser.ExpressionContext expressionContex
26142700
return null;
26152701
}
26162702

2617-
@Override
2618-
public SqmPredicate visitContainsPredicate(HqlParser.ContainsPredicateContext ctx) {
2619-
final boolean negated = ctx.NOT() != null;
2620-
final SqmExpression<?> lhs = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
2621-
final SqmExpression<?> rhs = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
2622-
final SqmExpressible<?> lhsExpressible = lhs.getExpressible();
2623-
if ( lhsExpressible != null && !( lhsExpressible.getSqmType() instanceof BasicPluralType<?, ?>) ) {
2624-
throw new SemanticException(
2625-
"First operand for contains predicate must be a basic plural type expression, but found: " + lhsExpressible.getSqmType(),
2626-
query
2627-
);
2628-
}
2629-
final SelfRenderingSqmFunction<Boolean> contains = getFunctionDescriptor( "array_contains" ).generateSqmExpression(
2630-
asList( lhs, rhs ),
2631-
null,
2632-
queryEngine()
2633-
);
2634-
return new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() );
2635-
}
2636-
26372703
@Override
26382704
public SqmExpression<?> visitJsonValueFunction(HqlParser.JsonValueFunctionContext ctx) {
26392705
checkJsonFunctionsEnabled( ctx );
@@ -3168,56 +3234,6 @@ private void checkXmlFunctionsEnabled(ParserRuleContext ctx) {
31683234
}
31693235
}
31703236

3171-
@Override
3172-
public SqmPredicate visitIncludesPredicate(HqlParser.IncludesPredicateContext ctx) {
3173-
final boolean negated = ctx.NOT() != null;
3174-
final SqmExpression<?> lhs = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
3175-
final SqmExpression<?> rhs = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
3176-
final SqmExpressible<?> lhsExpressible = lhs.getExpressible();
3177-
final SqmExpressible<?> rhsExpressible = rhs.getExpressible();
3178-
if ( lhsExpressible != null && !( lhsExpressible.getSqmType() instanceof BasicPluralType<?, ?>) ) {
3179-
throw new SemanticException(
3180-
"First operand for includes predicate must be a basic plural type expression, but found: "
3181-
+ lhsExpressible.getSqmType(),
3182-
query
3183-
);
3184-
}
3185-
if ( rhsExpressible != null && !( rhsExpressible.getSqmType() instanceof BasicPluralType<?, ?>) ) {
3186-
throw new SemanticException(
3187-
"Second operand for includes predicate must be a basic plural type expression, but found: "
3188-
+ rhsExpressible.getSqmType(),
3189-
query
3190-
);
3191-
}
3192-
final SelfRenderingSqmFunction<Boolean> contains = getFunctionDescriptor( "array_includes" ).generateSqmExpression(
3193-
asList( lhs, rhs ),
3194-
null,
3195-
queryEngine()
3196-
);
3197-
return new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() );
3198-
}
3199-
3200-
@Override
3201-
public SqmPredicate visitIntersectsPredicate(HqlParser.IntersectsPredicateContext ctx) {
3202-
final boolean negated = ctx.NOT() != null;
3203-
final SqmExpression<?> lhs = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
3204-
final SqmExpression<?> rhs = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
3205-
final SqmExpressible<?> lhsExpressible = lhs.getExpressible();
3206-
if ( lhsExpressible != null && !( lhsExpressible.getSqmType() instanceof BasicPluralType<?, ?>) ) {
3207-
throw new SemanticException(
3208-
"First operand for intersects predicate must be a basic plural type expression, but found: "
3209-
+ lhsExpressible.getSqmType(),
3210-
query
3211-
);
3212-
}
3213-
final SelfRenderingSqmFunction<Boolean> contains = getFunctionDescriptor( "array_intersects" ).generateSqmExpression(
3214-
asList( lhs, rhs ),
3215-
null,
3216-
queryEngine()
3217-
);
3218-
return new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() );
3219-
}
3220-
32213237
@Override
32223238
public SqmPredicate visitLikePredicate(HqlParser.LikePredicateContext ctx) {
32233239
final boolean negated = ctx.NOT() != null;

0 commit comments

Comments
 (0)