Skip to content

Commit 5b69d75

Browse files
committed
HHH-17355 Support double-pipe operator for array concatenation
1 parent c257be6 commit 5b69d75

File tree

3 files changed

+153
-9
lines changed

3 files changed

+153
-9
lines changed

documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ include::{array-example-dir-hql}/ArrayLengthTest.java[tags=hql-array-length-exam
11731173
====
11741174
11751175
[[hql-array-concat-functions]]
1176-
===== `array_concat()`
1176+
===== `array_concat()` or `||`
11771177
11781178
Concatenates arrays with each other in order. Returns `null` if one of the arguments is `null`.
11791179
@@ -1185,6 +1185,26 @@ include::{array-example-dir-hql}/ArrayConcatTest.java[tags=hql-array-concat-exam
11851185
----
11861186
====
11871187
1188+
Arrays can also be concatenated with the `||` (double-pipe) operator.
1189+
1190+
[[hql-array-concat-pipe-example]]
1191+
====
1192+
[source, JAVA, indent=0]
1193+
----
1194+
include::{array-example-dir-hql}/ArrayConcatTest.java[tags=hql-array-concat-pipe-example]
1195+
----
1196+
====
1197+
1198+
In addition, the `||` (double-pipe) operator also support concatenating single elements to arrays.
1199+
1200+
[[hql-array-concat-pipe-element-example]]
1201+
====
1202+
[source, JAVA, indent=0]
1203+
----
1204+
include::{array-example-dir-hql}/ArrayConcatTest.java[tags=hql-array-concat-pipe-element-example]
1205+
----
1206+
====
1207+
11881208
[[hql-array-prepend-functions]]
11891209
===== `array_prepend()`
11901210

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

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@
211211
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
212212
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
213213
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
214+
import org.hibernate.type.BasicPluralType;
214215
import org.hibernate.type.BasicType;
215216
import org.hibernate.type.descriptor.java.JavaType;
216217
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
@@ -2858,14 +2859,53 @@ public SqmExpression<?> visitConcatenationExpression(HqlParser.ConcatenationExpr
28582859
if ( ctx.getChildCount() != 3 ) {
28592860
throw new SyntaxException( "Expecting two operands to the '||' operator" );
28602861
}
2861-
return getFunctionDescriptor( "concat" ).generateSqmExpression(
2862-
asList(
2863-
(SqmExpression<?>) ctx.expression( 0 ).accept( this ),
2864-
(SqmExpression<?>) ctx.expression( 1 ).accept( this )
2865-
),
2866-
null,
2867-
creationContext.getQueryEngine()
2868-
);
2862+
final SqmExpression<?> lhs = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
2863+
final SqmExpression<?> rhs = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
2864+
final SqmExpressible<?> lhsExpressible = lhs.getExpressible();
2865+
final SqmExpressible<?> rhsExpressible = rhs.getExpressible();
2866+
if ( lhsExpressible != null && lhsExpressible.getSqmType() instanceof BasicPluralType<?, ?> ) {
2867+
if ( rhsExpressible == null || rhsExpressible.getSqmType() instanceof BasicPluralType<?, ?> ) {
2868+
// Both sides are array, so use array_concat
2869+
return getFunctionDescriptor( "array_concat" ).generateSqmExpression(
2870+
asList( lhs, rhs ),
2871+
null,
2872+
creationContext.getQueryEngine()
2873+
);
2874+
}
2875+
else {
2876+
// The RHS seems to be of the element type, so use array_append
2877+
return getFunctionDescriptor( "array_append" ).generateSqmExpression(
2878+
asList( lhs, rhs ),
2879+
null,
2880+
creationContext.getQueryEngine()
2881+
);
2882+
}
2883+
}
2884+
else if ( rhsExpressible != null && rhsExpressible.getSqmType() instanceof BasicPluralType<?, ?> ) {
2885+
if ( lhsExpressible == null ) {
2886+
// The RHS is an array and the LHS doesn't have a clear type, so use array_concat
2887+
return getFunctionDescriptor( "array_concat" ).generateSqmExpression(
2888+
asList( lhs, rhs ),
2889+
null,
2890+
creationContext.getQueryEngine()
2891+
);
2892+
}
2893+
else {
2894+
// The LHS seems to be of the element type, so use array_prepend
2895+
return getFunctionDescriptor( "array_prepend" ).generateSqmExpression(
2896+
asList( lhs, rhs ),
2897+
null,
2898+
creationContext.getQueryEngine()
2899+
);
2900+
}
2901+
}
2902+
else {
2903+
return getFunctionDescriptor( "concat" ).generateSqmExpression(
2904+
asList( lhs, rhs ),
2905+
null,
2906+
creationContext.getQueryEngine()
2907+
);
2908+
}
28692909
}
28702910

28712911
@Override

hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayConcatTest.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,88 @@ public void testConcatPrepend(SessionFactoryScope scope) {
8787
} );
8888
}
8989

90+
@Test
91+
public void testConcatPipe(SessionFactoryScope scope) {
92+
scope.inSession( em -> {
93+
//tag::hql-array-concat-pipe-example[]
94+
List<Tuple> results = em.createQuery( "select e.id, e.theArray || array('xyz') from EntityWithArrays e order by e.id", Tuple.class )
95+
.getResultList();
96+
//end::hql-array-concat-pipe-example[]
97+
assertEquals( 3, results.size() );
98+
assertEquals( 1L, results.get( 0 ).get( 0 ) );
99+
assertArrayEquals( new String[]{ "xyz" }, results.get( 0 ).get( 1, String[].class ) );
100+
assertEquals( 2L, results.get( 1 ).get( 0 ) );
101+
assertArrayEquals( new String[]{ "abc", null, "def", "xyz" }, results.get( 1 ).get( 1, String[].class ) );
102+
assertEquals( 3L, results.get( 2 ).get( 0 ) );
103+
assertNull( results.get( 2 ).get( 1, String[].class ) );
104+
} );
105+
}
106+
107+
@Test
108+
public void testConcatPipeTwoArrayPaths(SessionFactoryScope scope) {
109+
scope.inSession( em -> {
110+
em.createQuery( "select e.id, e.theArray || e.theArray from EntityWithArrays e order by e.id" ).getResultList();
111+
} );
112+
}
113+
114+
@Test
115+
public void testConcatPipeAppendLiteral(SessionFactoryScope scope) {
116+
scope.inSession( em -> {
117+
//tag::hql-array-concat-pipe-element-example[]
118+
em.createQuery( "select e.id, e.theArray || 'last' from EntityWithArrays e order by e.id" ).getResultList();
119+
//end::hql-array-concat-pipe-element-example[]
120+
} );
121+
}
122+
123+
@Test
124+
public void testConcatPipePrependLiteral(SessionFactoryScope scope) {
125+
scope.inSession( em -> {
126+
em.createQuery( "select e.id, 'first' || e.theArray from EntityWithArrays e order by e.id" ).getResultList();
127+
} );
128+
}
129+
130+
@Test
131+
public void testConcatPipeAppendNull(SessionFactoryScope scope) {
132+
scope.inSession( em -> {
133+
em.createQuery( "select e.id, e.theArray || null from EntityWithArrays e order by e.id" ).getResultList();
134+
} );
135+
}
136+
137+
@Test
138+
public void testConcatPipePrependNull(SessionFactoryScope scope) {
139+
scope.inSession( em -> {
140+
em.createQuery( "select e.id, null || e.theArray from EntityWithArrays e order by e.id" ).getResultList();
141+
} );
142+
}
143+
144+
@Test
145+
public void testConcatPipeAppendParameter(SessionFactoryScope scope) {
146+
scope.inSession( em -> {
147+
em.createQuery( "select e.id, e.theArray || :p from EntityWithArrays e order by e.id" )
148+
.setParameter( "p", null )
149+
.getResultList();
150+
em.createQuery( "select e.id, e.theArray || :p from EntityWithArrays e order by e.id" )
151+
.setParameter( "p", "last" )
152+
.getResultList();
153+
em.createQuery( "select e.id, e.theArray || :p from EntityWithArrays e order by e.id" )
154+
.setParameter( "p", new String[]{ "last" } )
155+
.getResultList();
156+
} );
157+
}
158+
159+
@Test
160+
public void testConcatPipePrependParameter(SessionFactoryScope scope) {
161+
scope.inSession( em -> {
162+
em.createQuery( "select e.id, :p || e.theArray from EntityWithArrays e order by e.id" )
163+
.setParameter( "p", null )
164+
.getResultList();
165+
em.createQuery( "select e.id, :p || e.theArray from EntityWithArrays e order by e.id" )
166+
.setParameter( "p", "first" )
167+
.getResultList();
168+
em.createQuery( "select e.id, :p || e.theArray from EntityWithArrays e order by e.id" )
169+
.setParameter( "p", new String[]{ "first" } )
170+
.getResultList();
171+
} );
172+
}
173+
90174
}

0 commit comments

Comments
 (0)