HHH-17375 Introduce contains predicate for easy array containment checking

This commit is contained in:
Christian Beikov 2024-05-07 13:41:43 +02:00
parent 64dd9e657c
commit 6c34a0a4a6
7 changed files with 83 additions and 0 deletions

View File

@ -1400,6 +1400,26 @@ include::{array-example-dir-hql}/ArrayContainsArrayTest.java[tags=hql-array-cont
----
====
Alternatively, it's also possible to check for containment with the `contains` predicate,
where the left hand side of the predicate is the array and the right hand side the value(s) to check.
This is syntax sugar that translates to the `array_contains` function.
[[hql-array-contains-hql-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArrayContainsTest.java[tags=hql-array-contains-hql-example]
----
====
[[hql-array-contains-array-hql-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArrayContainsArrayTest.java[tags=hql-array-contains-array-hql-example]
----
====
[[hql-array-overlaps-functions]]
===== `array_overlaps()` and `array_overlaps_nullable()`
@ -1831,6 +1851,20 @@ include::{example-dir-hql}/HQLTest.java[tags=hql-collection-expressions-in-examp
----
====
[[hql-contains-predicate]]
==== `contains`
The `contains` predicates evaluates to true if the value to its right is contained in the value to its left.
Currently, this predicate only works with an array typed expression on the left side.
[[hql-contains-predicate-bnf]]
[source, antlrv4, indent=0]
----
include::{extrasdir}/predicate_contains_bnf.txt[]
----
For further details, refer to the <<hql-array-contains-example,`array_contains` section>>.
[[hql-relational-comparisons-subqueries]]
==== Relational operators and subqueries

View File

@ -0,0 +1 @@
expression "NOT"? "CONTAINS" expression

View File

@ -161,6 +161,7 @@ COLLATE : [cC] [oO] [lL] [lL] [aA] [tT] [eE];
COLUMN : [cC] [oO] [lL] [uU] [mM] [nN];
CONFLICT : [cC] [oO] [nN] [fF] [lL] [iI] [cC] [tT];
CONSTRAINT : [cC] [oO] [nN] [sS] [tT] [rR] [aA] [iI] [nN] [tT];
CONTAINS : [cC] [oO] [nN] [tT] [aA] [iI] [nN] [sS];
COUNT : [cC] [oO] [uU] [nN] [tT];
CROSS : [cC] [rR] [oO] [sS] [sS];
CUBE : [cC] [uU] [bB] [eE];

View File

@ -673,6 +673,7 @@ predicate
| expression NOT? IN inList # InPredicate
| expression NOT? BETWEEN expression AND expression # BetweenPredicate
| expression NOT? (LIKE | ILIKE) expression likeEscape? # LikePredicate
| expression NOT? CONTAINS expression # ContainsPredicate
| expression comparisonOperator expression # ComparisonPredicate
| EXISTS collectionQuantifier LEFT_PAREN simplePath RIGHT_PAREN # ExistsCollectionPartPredicate
| EXISTS expression # ExistsPredicate
@ -1648,6 +1649,7 @@ rollup
| COLUMN
| CONFLICT
| CONSTRAINT
| CONTAINS
| COUNT
| CROSS
| CUBE

View File

@ -95,6 +95,7 @@ import org.hibernate.query.sqm.UnaryArithmeticOperator;
import org.hibernate.query.sqm.UnknownEntityException;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor;
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.internal.ParameterCollector;
import org.hibernate.query.sqm.internal.SqmCreationProcessingStateImpl;
@ -2640,6 +2641,26 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return null;
}
@Override
public SqmPredicate visitContainsPredicate(HqlParser.ContainsPredicateContext ctx) {
final boolean negated = ctx.NOT() != null;
final SqmExpression<?> lhs = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
final SqmExpression<?> rhs = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
final SqmExpressible<?> lhsExpressible = lhs.getExpressible();
if ( lhsExpressible != null && !( lhsExpressible.getSqmType() instanceof BasicPluralType<?, ?>) ) {
throw new SemanticException(
"First operand for contains predicate must be a basic plural type expression, but found: " + lhsExpressible.getSqmType(),
query
);
}
final SelfRenderingSqmFunction<Boolean> contains = getFunctionDescriptor( "array_contains" ).generateSqmExpression(
asList( lhs, rhs ),
null,
creationContext.getQueryEngine()
);
return new SqmBooleanExpressionPredicate( contains, negated, creationContext.getNodeBuilder() );
}
@Override
public SqmPredicate visitLikePredicate(HqlParser.LikePredicateContext ctx) {
final boolean negated = ctx.NOT() != null;

View File

@ -199,4 +199,16 @@ public class ArrayContainsArrayTest {
} );
}
@Test
public void testContainsArraySyntax(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-array-contains-array-hql-example[]
List<EntityWithArrays> results = em.createQuery( "from EntityWithArrays e where e.theArray contains ['abc', 'def']", EntityWithArrays.class )
.getResultList();
//end::hql-array-contains-array-hql-example[]
assertEquals( 1, results.size() );
assertEquals( 2L, results.get( 0 ).getId() );
} );
}
}

View File

@ -133,4 +133,16 @@ public class ArrayContainsTest {
} );
}
@Test
public void testContainsSyntax(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-array-contains-hql-example[]
List<EntityWithArrays> results = em.createQuery( "from EntityWithArrays e where e.theArray contains 'abc'", EntityWithArrays.class )
.getResultList();
//end::hql-array-contains-hql-example[]
assertEquals( 1, results.size() );
assertEquals( 2L, results.get( 0 ).getId() );
} );
}
}