Skip to content

Commit

Permalink
HHH-17355 Add array_to_string function
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Nov 6, 2023
1 parent d558df9 commit 79e3af5
Show file tree
Hide file tree
Showing 19 changed files with 358 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ The following functions deal with SQL array types, which are not supported on ev
| `array_replace()` | Creates array copy replacing a given element with another
| `array_trim()` | Creates array copy trimming the last _N_ elements
| `array_fill()` | Creates array filled with the same element _N_ times
| `array_to_string()` | String representation of non-null array elements
|===
===== `array()`
Expand Down Expand Up @@ -1429,6 +1430,19 @@ include::{array-example-dir-hql}/ArrayFillTest.java[tags=hql-array-fill-example]
----
====
===== `array_to_string()`
Concatenates the non-null array elements with a separator, as specified by the arguments.
Returns `null` if the first argument is `null`.
[[hql-array-to-string-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArrayToStringTest.java[tags=hql-array-to-string-example]
----
====
[[hql-user-defined-functions]]
==== Native and user-defined functions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_postgresql();
functionFactory.arrayToString_postgresql();

functionContributions.getFunctionRegistry().register(
"trunc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace_h2( getMaximumArraySize() );
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_h2();
functionFactory.arrayToString_h2( getMaximumArraySize() );
}
else {
// Use group_concat until 2.x as listagg was buggy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace_unnest();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_hsql();
functionFactory.arrayToString_hsql();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace_oracle();
functionFactory.arrayTrim_oracle();
functionFactory.arrayFill_oracle();
functionFactory.arrayToString_oracle();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_postgresql();
functionFactory.arrayToString_postgresql();

if ( getVersion().isSameOrAfter( 9, 4 ) ) {
functionFactory.makeDateTimeTimestamp();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_postgresql();
functionFactory.arrayToString_postgresql();

functionContributions.getFunctionRegistry().register(
"trunc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace_h2( getMaximumArraySize() );
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_h2();
functionFactory.arrayToString_h2( getMaximumArraySize() );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace_unnest();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_hsql();
functionFactory.arrayToString_hsql();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,29 @@ public void addAuxiliaryDatabaseObjects(
false
)
);
database.addAuxiliaryDatabaseObject(
new NamedAuxiliaryDatabaseObject(
arrayTypeName + "_to_string",
database.getDefaultNamespace(),
new String[]{
"create or replace function " + arrayTypeName + "_to_string(arr in " + arrayTypeName +
", sep in varchar2) return varchar2 deterministic is " +
"res varchar2(4000):=''; begin " +
"if arr is null or sep is null then return null; end if; " +
"for i in 1 .. arr.count loop " +
"if arr(i) is not null then " +
"if length(res)<>0 then res:=res||sep; end if; " +
"res:=res||arr(i); " +
"end if; " +
"end loop; " +
"return res; " +
"end;"
},
new String[] { "drop function " + arrayTypeName + "_to_string" },
emptySet(),
false
)
);
}

protected String createOrReplaceConcatFunction(String arrayTypeName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace_oracle();
functionFactory.arrayTrim_oracle();
functionFactory.arrayFill_oracle();
functionFactory.arrayToString_oracle();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.arrayReplace();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_postgresql();
functionFactory.arrayToString_postgresql();

functionFactory.makeDateTimeTimestamp();
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.hibernate.dialect.function.array.ArrayReplaceUnnestFunction;
import org.hibernate.dialect.function.array.ArraySetUnnestFunction;
import org.hibernate.dialect.function.array.ArraySliceUnnestFunction;
import org.hibernate.dialect.function.array.ArrayToStringFunction;
import org.hibernate.dialect.function.array.ArrayViaArgumentReturnTypeResolver;
import org.hibernate.dialect.function.array.ElementViaArrayArgumentReturnTypeResolver;
import org.hibernate.dialect.function.array.H2ArrayContainsFunction;
Expand All @@ -42,12 +43,14 @@
import org.hibernate.dialect.function.array.H2ArrayRemoveIndexFunction;
import org.hibernate.dialect.function.array.H2ArrayReplaceFunction;
import org.hibernate.dialect.function.array.H2ArraySetFunction;
import org.hibernate.dialect.function.array.H2ArrayToStringFunction;
import org.hibernate.dialect.function.array.HSQLArrayConstructorFunction;
import org.hibernate.dialect.function.array.HSQLArrayFillFunction;
import org.hibernate.dialect.function.array.HSQLArrayPositionFunction;
import org.hibernate.dialect.function.array.HSQLArrayPositionsFunction;
import org.hibernate.dialect.function.array.HSQLArrayRemoveFunction;
import org.hibernate.dialect.function.array.HSQLArraySetFunction;
import org.hibernate.dialect.function.array.HSQLArrayToStringFunction;
import org.hibernate.dialect.function.array.OracleArrayConcatElementFunction;
import org.hibernate.dialect.function.array.OracleArrayConcatFunction;
import org.hibernate.dialect.function.array.OracleArrayFillFunction;
Expand All @@ -61,6 +64,7 @@
import org.hibernate.dialect.function.array.OracleArrayReplaceFunction;
import org.hibernate.dialect.function.array.OracleArraySetFunction;
import org.hibernate.dialect.function.array.OracleArraySliceFunction;
import org.hibernate.dialect.function.array.OracleArrayToStringFunction;
import org.hibernate.dialect.function.array.OracleArrayTrimFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatElementFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatFunction;
Expand Down Expand Up @@ -3194,4 +3198,32 @@ public void arrayFill_postgresql() {
public void arrayFill_oracle() {
functionRegistry.register( "array_fill", new OracleArrayFillFunction() );
}

/**
* H2 array_to_string() function
*/
public void arrayToString_h2(int maximumArraySize) {
functionRegistry.register( "array_to_string", new H2ArrayToStringFunction( maximumArraySize, typeConfiguration ) );
}

/**
* HSQL array_to_string() function
*/
public void arrayToString_hsql() {
functionRegistry.register( "array_to_string", new HSQLArrayToStringFunction( typeConfiguration ) );
}

/**
* CockroachDB and PostgreSQL array_to_string() function
*/
public void arrayToString_postgresql() {
functionRegistry.register( "array_to_string", new ArrayToStringFunction( typeConfiguration ) );
}

/**
* Oracle array_to_string() function
*/
public void arrayToString_oracle() {
functionRegistry.register( "array_to_string", new OracleArrayToStringFunction( typeConfiguration ) );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function.array;

import java.util.List;

import org.hibernate.query.ReturnableType;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration;

import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;

/**
* @author Christian Beikov
*/
public class ArrayToStringFunction extends AbstractSqmSelfRenderingFunctionDescriptor {

public ArrayToStringFunction(TypeConfiguration typeConfiguration) {
super(
"array_to_string",
FunctionKind.NORMAL,
StandardArgumentsValidators.composite(
new ArgumentTypesValidator( null, ANY, STRING ),
ArrayArgumentValidator.DEFAULT_INSTANCE
),
StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
),
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, ANY, STRING )
);
}

@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
sqlAppender.appendSql( "array_to_string(" );
sqlAstArguments.get( 0 ).accept( walker );
sqlAppender.appendSql( ',' );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.appendSql( ')' );
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function.array;

import java.util.List;

import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.spi.TypeConfiguration;

/**
* H2 requires a very special emulation, because {@code unnest} is pretty much useless,
* due to https://github.com/h2database/h2database/issues/1815.
* This emulation uses {@code array_get}, {@code array_length} and {@code system_range} functions to roughly achieve the same.
*/
public class H2ArrayToStringFunction extends ArrayToStringFunction {

private final int maximumArraySize;

public H2ArrayToStringFunction(int maximumArraySize, TypeConfiguration typeConfiguration) {
super( typeConfiguration );
this.maximumArraySize = maximumArraySize;
}

@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "case when " );
arrayExpression.accept( walker );
sqlAppender.append( " is not null then coalesce((select listagg(array_get(" );
arrayExpression.accept( walker );
sqlAppender.append(",i.idx)," );
separatorExpression.accept( walker );
sqlAppender.append( ") within group (order by i.idx) from system_range(1,");
sqlAppender.append( Integer.toString( maximumArraySize ) );
sqlAppender.append( ") i(idx) where i.idx<=coalesce(cardinality(");
arrayExpression.accept( walker );
sqlAppender.append("),0)),'') end" );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function.array;

import java.util.List;

import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.spi.TypeConfiguration;

/**
* HSQLDB has a special syntax.
*/
public class HSQLArrayToStringFunction extends ArrayToStringFunction {

public HSQLArrayToStringFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration );
}

@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "case when " );
arrayExpression.accept( walker );
sqlAppender.append( " is not null then coalesce((select group_concat(t.val order by t.idx separator " );
// HSQLDB doesn't like non-literals as separator
walker.render( separatorExpression, SqlAstNodeRenderingMode.INLINE_PARAMETERS );
sqlAppender.append( ") from unnest(");
arrayExpression.accept( walker );
sqlAppender.append(") with ordinality t(val,idx)),'') end" );
}
}

0 comments on commit 79e3af5

Please sign in to comment.