HHH-17355 Support double-pipe operator for array concatenation

This commit is contained in:
Christian Beikov 2023-10-26 18:15:48 +02:00
parent c257be699a
commit 5b69d751f5
3 changed files with 153 additions and 9 deletions

View File

@ -1173,7 +1173,7 @@ include::{array-example-dir-hql}/ArrayLengthTest.java[tags=hql-array-length-exam
====
[[hql-array-concat-functions]]
===== `array_concat()`
===== `array_concat()` or `||`
Concatenates arrays with each other in order. Returns `null` if one of the arguments is `null`.
@ -1185,6 +1185,26 @@ include::{array-example-dir-hql}/ArrayConcatTest.java[tags=hql-array-concat-exam
----
====
Arrays can also be concatenated with the `||` (double-pipe) operator.
[[hql-array-concat-pipe-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArrayConcatTest.java[tags=hql-array-concat-pipe-example]
----
====
In addition, the `||` (double-pipe) operator also support concatenating single elements to arrays.
[[hql-array-concat-pipe-element-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArrayConcatTest.java[tags=hql-array-concat-pipe-element-example]
----
====
[[hql-array-prepend-functions]]
===== `array_prepend()`

View File

@ -211,6 +211,7 @@ import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
@ -2858,14 +2859,53 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
if ( ctx.getChildCount() != 3 ) {
throw new SyntaxException( "Expecting two operands to the '||' operator" );
}
return getFunctionDescriptor( "concat" ).generateSqmExpression(
asList(
(SqmExpression<?>) ctx.expression( 0 ).accept( this ),
(SqmExpression<?>) ctx.expression( 1 ).accept( this )
),
null,
creationContext.getQueryEngine()
);
final SqmExpression<?> lhs = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
final SqmExpression<?> rhs = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
final SqmExpressible<?> lhsExpressible = lhs.getExpressible();
final SqmExpressible<?> rhsExpressible = rhs.getExpressible();
if ( lhsExpressible != null && lhsExpressible.getSqmType() instanceof BasicPluralType<?, ?> ) {
if ( rhsExpressible == null || rhsExpressible.getSqmType() instanceof BasicPluralType<?, ?> ) {
// Both sides are array, so use array_concat
return getFunctionDescriptor( "array_concat" ).generateSqmExpression(
asList( lhs, rhs ),
null,
creationContext.getQueryEngine()
);
}
else {
// The RHS seems to be of the element type, so use array_append
return getFunctionDescriptor( "array_append" ).generateSqmExpression(
asList( lhs, rhs ),
null,
creationContext.getQueryEngine()
);
}
}
else if ( rhsExpressible != null && rhsExpressible.getSqmType() instanceof BasicPluralType<?, ?> ) {
if ( lhsExpressible == null ) {
// The RHS is an array and the LHS doesn't have a clear type, so use array_concat
return getFunctionDescriptor( "array_concat" ).generateSqmExpression(
asList( lhs, rhs ),
null,
creationContext.getQueryEngine()
);
}
else {
// The LHS seems to be of the element type, so use array_prepend
return getFunctionDescriptor( "array_prepend" ).generateSqmExpression(
asList( lhs, rhs ),
null,
creationContext.getQueryEngine()
);
}
}
else {
return getFunctionDescriptor( "concat" ).generateSqmExpression(
asList( lhs, rhs ),
null,
creationContext.getQueryEngine()
);
}
}
@Override

View File

@ -87,4 +87,88 @@ public class ArrayConcatTest {
} );
}
@Test
public void testConcatPipe(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-array-concat-pipe-example[]
List<Tuple> results = em.createQuery( "select e.id, e.theArray || array('xyz') from EntityWithArrays e order by e.id", Tuple.class )
.getResultList();
//end::hql-array-concat-pipe-example[]
assertEquals( 3, results.size() );
assertEquals( 1L, results.get( 0 ).get( 0 ) );
assertArrayEquals( new String[]{ "xyz" }, results.get( 0 ).get( 1, String[].class ) );
assertEquals( 2L, results.get( 1 ).get( 0 ) );
assertArrayEquals( new String[]{ "abc", null, "def", "xyz" }, results.get( 1 ).get( 1, String[].class ) );
assertEquals( 3L, results.get( 2 ).get( 0 ) );
assertNull( results.get( 2 ).get( 1, String[].class ) );
} );
}
@Test
public void testConcatPipeTwoArrayPaths(SessionFactoryScope scope) {
scope.inSession( em -> {
em.createQuery( "select e.id, e.theArray || e.theArray from EntityWithArrays e order by e.id" ).getResultList();
} );
}
@Test
public void testConcatPipeAppendLiteral(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-array-concat-pipe-element-example[]
em.createQuery( "select e.id, e.theArray || 'last' from EntityWithArrays e order by e.id" ).getResultList();
//end::hql-array-concat-pipe-element-example[]
} );
}
@Test
public void testConcatPipePrependLiteral(SessionFactoryScope scope) {
scope.inSession( em -> {
em.createQuery( "select e.id, 'first' || e.theArray from EntityWithArrays e order by e.id" ).getResultList();
} );
}
@Test
public void testConcatPipeAppendNull(SessionFactoryScope scope) {
scope.inSession( em -> {
em.createQuery( "select e.id, e.theArray || null from EntityWithArrays e order by e.id" ).getResultList();
} );
}
@Test
public void testConcatPipePrependNull(SessionFactoryScope scope) {
scope.inSession( em -> {
em.createQuery( "select e.id, null || e.theArray from EntityWithArrays e order by e.id" ).getResultList();
} );
}
@Test
public void testConcatPipeAppendParameter(SessionFactoryScope scope) {
scope.inSession( em -> {
em.createQuery( "select e.id, e.theArray || :p from EntityWithArrays e order by e.id" )
.setParameter( "p", null )
.getResultList();
em.createQuery( "select e.id, e.theArray || :p from EntityWithArrays e order by e.id" )
.setParameter( "p", "last" )
.getResultList();
em.createQuery( "select e.id, e.theArray || :p from EntityWithArrays e order by e.id" )
.setParameter( "p", new String[]{ "last" } )
.getResultList();
} );
}
@Test
public void testConcatPipePrependParameter(SessionFactoryScope scope) {
scope.inSession( em -> {
em.createQuery( "select e.id, :p || e.theArray from EntityWithArrays e order by e.id" )
.setParameter( "p", null )
.getResultList();
em.createQuery( "select e.id, :p || e.theArray from EntityWithArrays e order by e.id" )
.setParameter( "p", "first" )
.getResultList();
em.createQuery( "select e.id, :p || e.theArray from EntityWithArrays e order by e.id" )
.setParameter( "p", new String[]{ "first" } )
.getResultList();
} );
}
}