Skip to content

Commit 60f5427

Browse files
committed
HHH-17355 Add array_to_string function
1 parent cf3677c commit 60f5427

File tree

19 files changed

+356
-2
lines changed

19 files changed

+356
-2
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,7 @@ The following functions deal with SQL array types, which are not supported on ev
11381138
| `array_replace()` | Creates array copy replacing a given element with another
11391139
| `array_trim()` | Creates array copy trimming the last _N_ elements
11401140
| `array_fill()` | Creates array filled with the same element _N_ times
1141+
| `array_to_string()` | String representation of non-null array elements
11411142
|===
11421143
11431144
===== `array()`
@@ -1429,6 +1430,19 @@ include::{array-example-dir-hql}/ArrayFillTest.java[tags=hql-array-fill-example]
14291430
----
14301431
====
14311432
1433+
===== `array_to_string()`
1434+
1435+
Concatenates the non-null array elements with a separator, as specified by the arguments.
1436+
Returns `null` if the first argument is `null`.
1437+
1438+
[[hql-array-to-string-example]]
1439+
====
1440+
[source, JAVA, indent=0]
1441+
----
1442+
include::{array-example-dir-hql}/ArrayToStringTest.java[tags=hql-array-to-string-example]
1443+
----
1444+
====
1445+
14321446
[[hql-user-defined-functions]]
14331447
==== Native and user-defined functions
14341448

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
479479
functionFactory.arrayReplace();
480480
functionFactory.arrayTrim_trim_array();
481481
functionFactory.arrayFill_postgresql();
482+
functionFactory.arrayToString_postgresql();
482483

483484
functionContributions.getFunctionRegistry().register(
484485
"trunc",

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
388388
functionFactory.arrayReplace_h2( getMaximumArraySize() );
389389
functionFactory.arrayTrim_trim_array();
390390
functionFactory.arrayFill_h2();
391+
functionFactory.arrayToString_h2( getMaximumArraySize() );
391392
}
392393
else {
393394
// Use group_concat until 2.x as listagg was buggy

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
265265
functionFactory.arrayReplace_unnest();
266266
functionFactory.arrayTrim_trim_array();
267267
functionFactory.arrayFill_hsql();
268+
functionFactory.arrayToString_hsql();
268269
}
269270

270271
@Override

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
301301
functionFactory.arrayReplace_oracle();
302302
functionFactory.arrayTrim_oracle();
303303
functionFactory.arrayFill_oracle();
304+
functionFactory.arrayToString_oracle();
304305
}
305306

306307
@Override

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
599599
functionFactory.arrayReplace();
600600
functionFactory.arrayTrim_trim_array();
601601
functionFactory.arrayFill_postgresql();
602+
functionFactory.arrayToString_postgresql();
602603

603604
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
604605
functionFactory.makeDateTimeTimestamp();

hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
466466
functionFactory.arrayReplace();
467467
functionFactory.arrayTrim_trim_array();
468468
functionFactory.arrayFill_postgresql();
469+
functionFactory.arrayToString_postgresql();
469470

470471
functionContributions.getFunctionRegistry().register(
471472
"trunc",

hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
327327
functionFactory.arrayReplace_h2( getMaximumArraySize() );
328328
functionFactory.arrayTrim_trim_array();
329329
functionFactory.arrayFill_h2();
330+
functionFactory.arrayToString_h2( getMaximumArraySize() );
330331
}
331332

332333
/**

hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
205205
functionFactory.arrayReplace_unnest();
206206
functionFactory.arrayTrim_trim_array();
207207
functionFactory.arrayFill_hsql();
208+
functionFactory.arrayToString_hsql();
208209
}
209210

210211
@Override

hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,29 @@ public void addAuxiliaryDatabaseObjects(
541541
false
542542
)
543543
);
544+
database.addAuxiliaryDatabaseObject(
545+
new NamedAuxiliaryDatabaseObject(
546+
arrayTypeName + "_to_string",
547+
database.getDefaultNamespace(),
548+
new String[]{
549+
"create or replace function " + arrayTypeName + "_to_string(arr in " + arrayTypeName +
550+
", sep in varchar2) return varchar2 deterministic is " +
551+
"res varchar2(4000):=''; begin " +
552+
"if arr is null or sep is null then return null; end if; " +
553+
"for i in 1 .. arr.count loop " +
554+
"if arr(i) is not null then " +
555+
"if length(res)<>0 then res:=res||sep; end if; " +
556+
"res:=res||arr(i); " +
557+
"end if; " +
558+
"end loop; " +
559+
"return res; " +
560+
"end;"
561+
},
562+
new String[] { "drop function " + arrayTypeName + "_to_string" },
563+
emptySet(),
564+
false
565+
)
566+
);
544567
}
545568

546569
protected String createOrReplaceConcatFunction(String arrayTypeName) {

hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
330330
functionFactory.arrayReplace_oracle();
331331
functionFactory.arrayTrim_oracle();
332332
functionFactory.arrayFill_oracle();
333+
functionFactory.arrayToString_oracle();
333334
}
334335

335336
@Override

hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
647647
functionFactory.arrayReplace();
648648
functionFactory.arrayTrim_trim_array();
649649
functionFactory.arrayFill_postgresql();
650+
functionFactory.arrayToString_postgresql();
650651

651652
functionFactory.makeDateTimeTimestamp();
652653
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions

hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.hibernate.dialect.function.array.ArrayReplaceUnnestFunction;
3232
import org.hibernate.dialect.function.array.ArraySetUnnestFunction;
3333
import org.hibernate.dialect.function.array.ArraySliceUnnestFunction;
34+
import org.hibernate.dialect.function.array.ArrayToStringFunction;
3435
import org.hibernate.dialect.function.array.ArrayViaArgumentReturnTypeResolver;
3536
import org.hibernate.dialect.function.array.ElementViaArrayArgumentReturnTypeResolver;
3637
import org.hibernate.dialect.function.array.H2ArrayContainsFunction;
@@ -42,12 +43,14 @@
4243
import org.hibernate.dialect.function.array.H2ArrayRemoveIndexFunction;
4344
import org.hibernate.dialect.function.array.H2ArrayReplaceFunction;
4445
import org.hibernate.dialect.function.array.H2ArraySetFunction;
46+
import org.hibernate.dialect.function.array.H2ArrayToStringFunction;
4547
import org.hibernate.dialect.function.array.HSQLArrayConstructorFunction;
4648
import org.hibernate.dialect.function.array.HSQLArrayFillFunction;
4749
import org.hibernate.dialect.function.array.HSQLArrayPositionFunction;
4850
import org.hibernate.dialect.function.array.HSQLArrayPositionsFunction;
4951
import org.hibernate.dialect.function.array.HSQLArrayRemoveFunction;
5052
import org.hibernate.dialect.function.array.HSQLArraySetFunction;
53+
import org.hibernate.dialect.function.array.HSQLArrayToStringFunction;
5154
import org.hibernate.dialect.function.array.OracleArrayConcatElementFunction;
5255
import org.hibernate.dialect.function.array.OracleArrayConcatFunction;
5356
import org.hibernate.dialect.function.array.OracleArrayFillFunction;
@@ -61,6 +64,7 @@
6164
import org.hibernate.dialect.function.array.OracleArrayReplaceFunction;
6265
import org.hibernate.dialect.function.array.OracleArraySetFunction;
6366
import org.hibernate.dialect.function.array.OracleArraySliceFunction;
67+
import org.hibernate.dialect.function.array.OracleArrayToStringFunction;
6468
import org.hibernate.dialect.function.array.OracleArrayTrimFunction;
6569
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatElementFunction;
6670
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatFunction;
@@ -3194,4 +3198,32 @@ public void arrayFill_postgresql() {
31943198
public void arrayFill_oracle() {
31953199
functionRegistry.register( "array_fill", new OracleArrayFillFunction() );
31963200
}
3201+
3202+
/**
3203+
* H2 array_to_string() function
3204+
*/
3205+
public void arrayToString_h2(int maximumArraySize) {
3206+
functionRegistry.register( "array_to_string", new H2ArrayToStringFunction( maximumArraySize, typeConfiguration ) );
3207+
}
3208+
3209+
/**
3210+
* HSQL array_to_string() function
3211+
*/
3212+
public void arrayToString_hsql() {
3213+
functionRegistry.register( "array_to_string", new HSQLArrayToStringFunction( typeConfiguration ) );
3214+
}
3215+
3216+
/**
3217+
* CockroachDB and PostgreSQL array_to_string() function
3218+
*/
3219+
public void arrayToString_postgresql() {
3220+
functionRegistry.register( "array_to_string", new ArrayToStringFunction( typeConfiguration ) );
3221+
}
3222+
3223+
/**
3224+
* Oracle array_to_string() function
3225+
*/
3226+
public void arrayToString_oracle() {
3227+
functionRegistry.register( "array_to_string", new OracleArrayToStringFunction( typeConfiguration ) );
3228+
}
31973229
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.dialect.function.array;
8+
9+
import java.util.List;
10+
11+
import org.hibernate.query.ReturnableType;
12+
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
13+
import org.hibernate.query.sqm.function.FunctionKind;
14+
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
15+
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
16+
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
17+
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
18+
import org.hibernate.sql.ast.SqlAstTranslator;
19+
import org.hibernate.sql.ast.spi.SqlAppender;
20+
import org.hibernate.sql.ast.tree.SqlAstNode;
21+
import org.hibernate.type.StandardBasicTypes;
22+
import org.hibernate.type.spi.TypeConfiguration;
23+
24+
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY;
25+
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
26+
27+
/**
28+
* @author Christian Beikov
29+
*/
30+
public class ArrayToStringFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
31+
32+
public ArrayToStringFunction(TypeConfiguration typeConfiguration) {
33+
super(
34+
"array_to_string",
35+
FunctionKind.NORMAL,
36+
StandardArgumentsValidators.composite(
37+
new ArgumentTypesValidator( null, ANY, STRING ),
38+
ArrayArgumentValidator.DEFAULT_INSTANCE
39+
),
40+
StandardFunctionReturnTypeResolvers.invariant(
41+
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
42+
),
43+
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, ANY, STRING )
44+
);
45+
}
46+
47+
@Override
48+
public void render(
49+
SqlAppender sqlAppender,
50+
List<? extends SqlAstNode> sqlAstArguments,
51+
ReturnableType<?> returnType,
52+
SqlAstTranslator<?> walker) {
53+
sqlAppender.appendSql( "array_to_string(" );
54+
sqlAstArguments.get( 0 ).accept( walker );
55+
sqlAppender.appendSql( ',' );
56+
sqlAstArguments.get( 1 ).accept( walker );
57+
sqlAppender.appendSql( ')' );
58+
}
59+
60+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.dialect.function.array;
8+
9+
import java.util.List;
10+
11+
import org.hibernate.query.ReturnableType;
12+
import org.hibernate.sql.ast.SqlAstTranslator;
13+
import org.hibernate.sql.ast.spi.SqlAppender;
14+
import org.hibernate.sql.ast.tree.SqlAstNode;
15+
import org.hibernate.sql.ast.tree.expression.Expression;
16+
import org.hibernate.type.spi.TypeConfiguration;
17+
18+
/**
19+
* H2 requires a very special emulation, because {@code unnest} is pretty much useless,
20+
* due to https://github.com/h2database/h2database/issues/1815.
21+
* This emulation uses {@code array_get}, {@code array_length} and {@code system_range} functions to roughly achieve the same.
22+
*/
23+
public class H2ArrayToStringFunction extends ArrayToStringFunction {
24+
25+
private final int maximumArraySize;
26+
27+
public H2ArrayToStringFunction(int maximumArraySize, TypeConfiguration typeConfiguration) {
28+
super( typeConfiguration );
29+
this.maximumArraySize = maximumArraySize;
30+
}
31+
32+
@Override
33+
public void render(
34+
SqlAppender sqlAppender,
35+
List<? extends SqlAstNode> sqlAstArguments,
36+
ReturnableType<?> returnType,
37+
SqlAstTranslator<?> walker) {
38+
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
39+
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
40+
sqlAppender.append( "case when " );
41+
arrayExpression.accept( walker );
42+
sqlAppender.append( " is not null then coalesce((select listagg(array_get(" );
43+
arrayExpression.accept( walker );
44+
sqlAppender.append(",i.idx)," );
45+
separatorExpression.accept( walker );
46+
sqlAppender.append( ") within group (order by i.idx) from system_range(1,");
47+
sqlAppender.append( Integer.toString( maximumArraySize ) );
48+
sqlAppender.append( ") i(idx) where i.idx<=coalesce(cardinality(");
49+
arrayExpression.accept( walker );
50+
sqlAppender.append("),0)),'') end" );
51+
}
52+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.dialect.function.array;
8+
9+
import java.util.List;
10+
11+
import org.hibernate.query.ReturnableType;
12+
import org.hibernate.sql.ast.SqlAstTranslator;
13+
import org.hibernate.sql.ast.spi.SqlAppender;
14+
import org.hibernate.sql.ast.tree.SqlAstNode;
15+
import org.hibernate.sql.ast.tree.expression.Expression;
16+
import org.hibernate.type.spi.TypeConfiguration;
17+
18+
/**
19+
* HSQLDB has a special syntax.
20+
*/
21+
public class HSQLArrayToStringFunction extends ArrayToStringFunction {
22+
23+
public HSQLArrayToStringFunction(TypeConfiguration typeConfiguration) {
24+
super( typeConfiguration );
25+
}
26+
27+
@Override
28+
public void render(
29+
SqlAppender sqlAppender,
30+
List<? extends SqlAstNode> sqlAstArguments,
31+
ReturnableType<?> returnType,
32+
SqlAstTranslator<?> walker) {
33+
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
34+
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
35+
sqlAppender.append( "case when " );
36+
arrayExpression.accept( walker );
37+
sqlAppender.append( " is not null then coalesce((select group_concat(t.val order by t.idx separator " );
38+
separatorExpression.accept( walker );
39+
sqlAppender.append( ") from unnest(");
40+
arrayExpression.accept( walker );
41+
sqlAppender.append(") with ordinality t(val,idx)),'') end" );
42+
}
43+
}

0 commit comments

Comments
 (0)