diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc index f6d7dd928e..a27e1fa766 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -1202,7 +1202,7 @@ The following functions deal with SQL array types, which are not supported on ev | `array_trim()` | Creates array copy trimming the last _N_ elements | `array_fill()` | Creates array filled with the same element _N_ times | `array_fill_list()` | Like `array_fill`, but returns the result as `List` -| `array_to_string()` | String representation of non-null array elements +| `array_to_string()` | String representation of array |=== [[hql-array-constructor-functions]] @@ -1546,9 +1546,11 @@ include::{array-example-dir-hql}/ArrayFillTest.java[tags=hql-array-fill-example] ==== [[hql-array-to-string-functions]] -===== `array_to_string()` +===== `array_to_string()` or `cast(array as String)` -Concatenates the non-null array elements with a separator, as specified by the arguments. +Concatenates the array elements with a separator, as specified by the arguments. +Null values are filtered, but the optional third argument can be specified to define a default value to use +when a `null` array element is encountered. Returns `null` if the first argument is `null`. [[hql-array-to-string-example]] @@ -1559,6 +1561,17 @@ include::{array-example-dir-hql}/ArrayToStringTest.java[tags=hql-array-to-string ---- ==== +Alternatively, it is also possible to use `cast(array as String)`, +which is a short version of `concat('[', array_to_string(array, ',', 'null'), ']')`. + +[[hql-array-to-string-hql-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayToStringTest.java[tags=hql-array-to-string-hql-example] +---- +==== + [[hql-user-defined-functions]] ==== Native and user-defined functions diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgresPlusLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgresPlusLegacyDialect.java index 4a6b2bfc11..6102bd8937 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgresPlusLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgresPlusLegacyDialect.java @@ -104,6 +104,11 @@ public class PostgresPlusLegacyDialect extends PostgreSQLLegacyDialect { return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType ); } + @Override + public boolean isEmptyStringTreatedAsNull() { + return true; + } + @Override public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException { statement.registerOutParameter( col, Types.REF ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java index 0e74a2426c..455cde2df4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java @@ -248,13 +248,16 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport "return res; " + "end;", "create or replace function " + arrayTypeName + "_to_string(arr in " + arrayTypeName + - ", sep in varchar2) return varchar2 deterministic is " + + ", sep in varchar2, nullVal 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); " + + "elsif nullVal is not null then " + + "if length(res)<>0 then res:=res||sep; end if; " + + "res:=res||nullVal; " + "end if; " + "end loop; " + "return res; " + diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java index e5963b3535..95e2c87b57 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java @@ -104,6 +104,11 @@ public class PostgresPlusDialect extends PostgreSQLDialect { return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType ); } + @Override + public boolean isEmptyStringTreatedAsNull() { + return true; + } + @Override public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException { statement.registerOutParameter( col, Types.REF ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastFunction.java index 60eddadd26..6b44d1a985 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastFunction.java @@ -10,10 +10,14 @@ import java.sql.Types; import java.util.List; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.query.ReturnableType; import org.hibernate.query.sqm.CastType; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression; +import org.hibernate.query.sqm.function.SqmFunctionDescriptor; +import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; @@ -23,6 +27,8 @@ import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.expression.CastTarget; import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.QueryLiteral; +import org.hibernate.type.BasicType; /** * ANSI SQL-inspired {@code cast()} function, where the target types @@ -71,9 +77,59 @@ public class CastFunction extends AbstractSqmSelfRenderingFunctionDescriptor { final JdbcMapping targetJdbcMapping = castTarget.getExpressionType().getSingleJdbcMapping(); final CastType targetType = getCastType( targetJdbcMapping ); - String cast = dialect.castPattern( sourceType, targetType ); + if ( sourceType == CastType.OTHER && targetType == CastType.STRING + && sourceMapping.getJdbcType().isArray() ) { + renderCastArrayToString( sqlAppender, arguments.get( 0 ), dialect, walker ); + } + else { + String cast = dialect.castPattern( sourceType, targetType ); - new PatternRenderer( cast ).render( sqlAppender, arguments, walker ); + new PatternRenderer( cast ).render( sqlAppender, arguments, walker ); + } + } + + public static void renderCastArrayToString( + SqlAppender sqlAppender, + SqlAstNode arrayArgument, + Dialect dialect, + SqlAstTranslator walker) { + final SessionFactoryImplementor sessionFactory = walker.getSessionFactory(); + final BasicType stringType = sessionFactory.getTypeConfiguration() + .getBasicTypeForJavaType( String.class ); + final SqmFunctionRegistry functionRegistry = sessionFactory.getQueryEngine() + .getSqmFunctionRegistry(); + final SqmFunctionDescriptor concatDescriptor = functionRegistry.findFunctionDescriptor( "concat" ); + final SqmFunctionDescriptor arrayToStringDescriptor = functionRegistry.findFunctionDescriptor( "array_to_string" ); + final boolean caseWhen = dialect.isEmptyStringTreatedAsNull(); + if ( caseWhen ) { + sqlAppender.append( "case when " ); + arrayArgument.accept( walker ); + sqlAppender.append( " is null then null else " ); + } + + ( (AbstractSqmSelfRenderingFunctionDescriptor) concatDescriptor ).render( + sqlAppender, + List.of( + new QueryLiteral<>( "[", stringType ), + new SelfRenderingFunctionSqlAstExpression( + "array_to_string", + ( (AbstractSqmSelfRenderingFunctionDescriptor) arrayToStringDescriptor ), + List.of( + arrayArgument, + new QueryLiteral<>( ",", stringType ), + new QueryLiteral<>( "null", stringType ) + ), + stringType, + stringType + ), + new QueryLiteral<>( "]", stringType ) + ), + stringType, + walker + ); + if ( caseWhen ) { + sqlAppender.append( " end" ); + } } private CastType getCastType(JdbcMapping sourceMapping) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastingConcatFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastingConcatFunction.java index d0bb1c1f2a..70868be545 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastingConcatFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastingConcatFunction.java @@ -28,6 +28,7 @@ import org.hibernate.type.SqlTypes; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.dialect.function.CastFunction.renderCastArrayToString; import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING; public class CastingConcatFunction extends AbstractSqmSelfRenderingFunctionDescriptor { @@ -97,12 +98,16 @@ public class CastingConcatFunction extends AbstractSqmSelfRenderingFunctionDescr private void renderAsString(SqlAppender sqlAppender, SqlAstTranslator translator, Expression expression) { final JdbcMapping sourceMapping = expression.getExpressionType().getSingleJdbcMapping(); + final CastType sourceType = sourceMapping.getCastType(); // No need to cast if we already have a string - if ( sourceMapping.getCastType() == CastType.STRING ) { + if ( sourceType == CastType.STRING ) { translator.render( expression, argumentRenderingMode ); } + else if ( sourceType == CastType.OTHER && sourceMapping.getJdbcType().isArray() ) { + renderCastArrayToString( sqlAppender, expression, dialect, translator ); + } else { - final String cast = dialect.castPattern( sourceMapping.getCastType(), CastType.STRING ); + final String cast = dialect.castPattern( sourceType, CastType.STRING ); new PatternRenderer( cast.replace( "?2", concatArgumentCastType ), argumentRenderingMode ) .render( sqlAppender, Collections.singletonList( expression ), translator ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CountFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CountFunction.java index 4df09c17a3..ea4bd70915 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CountFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CountFunction.java @@ -39,6 +39,8 @@ import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.dialect.function.CastFunction.renderCastArrayToString; + /** * @author Christian Beikov */ @@ -401,13 +403,18 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor { } else { final JdbcMapping sourceMapping = realArg.getExpressionType().getSingleJdbcMapping(); + final CastType sourceType = sourceMapping.getCastType(); // No need to cast if we already have a string - if ( sourceMapping.getCastType() == CastType.STRING ) { + if ( sourceType == CastType.STRING ) { translator.render( realArg, defaultArgumentRenderingMode ); return false; } + else if ( sourceType == CastType.OTHER && sourceMapping.getJdbcType().isArray() ) { + renderCastArrayToString( sqlAppender, realArg, dialect, translator ); + return false; + } else { - final String cast = dialect.castPattern( sourceMapping.getCastType(), CastType.STRING ); + final String cast = dialect.castPattern( sourceType, CastType.STRING ); new PatternRenderer( cast.replace( "?2", concatArgumentCastType ) ) .render( sqlAppender, Collections.singletonList( realArg ), translator ); return false; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentValidator.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentValidator.java index d8ca571af1..398bdf5feb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentValidator.java @@ -36,18 +36,20 @@ public class ArrayAndElementArgumentValidator extends ArrayArgumentValidator { TypeConfiguration typeConfiguration) { final BasicType expectedElementType = getElementType( arguments, functionName, typeConfiguration ); for ( int elementIndex : elementIndexes ) { - final SqmTypedNode elementArgument = arguments.get( elementIndex ); - final SqmExpressible elementType = elementArgument.getExpressible().getSqmType(); - if ( expectedElementType != null && elementType != null && expectedElementType != elementType ) { - throw new FunctionArgumentException( - String.format( - "Parameter %d of function '%s()' has type %s, but argument is of type '%s'", - elementIndex, - functionName, - expectedElementType.getJavaTypeDescriptor().getTypeName(), - elementType.getTypeName() - ) - ); + if ( elementIndex < arguments.size() ) { + final SqmTypedNode elementArgument = arguments.get( elementIndex ); + final SqmExpressible elementType = elementArgument.getExpressible().getSqmType(); + if ( expectedElementType != null && elementType != null && expectedElementType != elementType ) { + throw new FunctionArgumentException( + String.format( + "Parameter %d of function '%s()' has type %s, but argument is of type '%s'", + elementIndex, + functionName, + expectedElementType.getJavaTypeDescriptor().getTypeName(), + elementType.getTypeName() + ) + ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayToStringFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayToStringFunction.java index 594ed3b1e8..18e06e8809 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayToStringFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayToStringFunction.java @@ -34,13 +34,16 @@ public class ArrayToStringFunction extends AbstractSqmSelfRenderingFunctionDescr "array_to_string", FunctionKind.NORMAL, StandardArgumentsValidators.composite( - new ArgumentTypesValidator( null, ANY, STRING ), - ArrayArgumentValidator.DEFAULT_INSTANCE + new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 3 ), ANY, STRING, ANY ), + new ArrayAndElementArgumentValidator( 0, 2 ) ), StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING ) ), - StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, ANY, STRING ) + StandardFunctionArgumentTypeResolvers.composite( + new ArrayAndElementArgumentTypeResolver( 0, 2 ), + StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, ANY, STRING ) + ) ); } @@ -54,6 +57,10 @@ public class ArrayToStringFunction extends AbstractSqmSelfRenderingFunctionDescr sqlAstArguments.get( 0 ).accept( walker ); sqlAppender.appendSql( ',' ); sqlAstArguments.get( 1 ).accept( walker ); + if ( sqlAstArguments.size() > 2 ) { + sqlAppender.appendSql( ',' ); + sqlAstArguments.get( 2 ).accept( walker ); + } sqlAppender.appendSql( ')' ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayToStringFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayToStringFunction.java index 6eceb5c6d9..5e02c2a677 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayToStringFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayToStringFunction.java @@ -37,11 +37,22 @@ public class H2ArrayToStringFunction extends ArrayToStringFunction { SqlAstTranslator walker) { final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 ); + final Expression defaultExpression = sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null; sqlAppender.append( "case when " ); arrayExpression.accept( walker ); - sqlAppender.append( " is not null then coalesce((select listagg(array_get(" ); + sqlAppender.append( " is not null then coalesce((select listagg(" ); + if ( defaultExpression != null ) { + sqlAppender.append( "coalesce(" ); + } + sqlAppender.append( "array_get(" ); arrayExpression.accept( walker ); - sqlAppender.append(",i.idx)," ); + sqlAppender.append(",i.idx)" ); + if ( defaultExpression != null ) { + sqlAppender.append( "," ); + defaultExpression.accept( walker ); + sqlAppender.append( ")" ); + } + sqlAppender.append("," ); separatorExpression.accept( walker ); sqlAppender.append( ") within group (order by i.idx) from system_range(1,"); sqlAppender.append( Integer.toString( maximumArraySize ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayToStringFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayToStringFunction.java index 948421249b..2b0149f70d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayToStringFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayToStringFunction.java @@ -33,9 +33,20 @@ public class HSQLArrayToStringFunction extends ArrayToStringFunction { SqlAstTranslator walker) { final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 ); + final Expression defaultExpression = sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null; sqlAppender.append( "case when " ); arrayExpression.accept( walker ); - sqlAppender.append( " is not null then coalesce((select group_concat(t.val order by t.idx separator " ); + sqlAppender.append( " is not null then coalesce((select group_concat(" ); + if ( defaultExpression != null ) { + sqlAppender.append( "coalesce(" ); + } + sqlAppender.append( "t.val" ); + if ( defaultExpression != null ) { + sqlAppender.append( "," ); + defaultExpression.accept( walker ); + sqlAppender.append( ")" ); + } + sqlAppender.append( " order by t.idx separator " ); // HSQLDB doesn't like non-literals as separator walker.render( separatorExpression, SqlAstNodeRenderingMode.INLINE_PARAMETERS ); sqlAppender.append( ") from unnest("); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayToStringFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayToStringFunction.java index 97efaadef4..2681a885d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayToStringFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayToStringFunction.java @@ -46,6 +46,13 @@ public class OracleArrayToStringFunction extends ArrayToStringFunction { sqlAstArguments.get( 0 ).accept( walker ); sqlAppender.append( ',' ); sqlAstArguments.get( 1 ).accept( walker ); + if ( sqlAstArguments.size() > 2 ) { + sqlAppender.append( ',' ); + sqlAstArguments.get( 2 ).accept( walker ); + } + else { + sqlAppender.append( ",null" ); + } sqlAppender.append( ')' ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java index b57cba4e74..0a71511f37 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java @@ -293,6 +293,21 @@ public interface JdbcType extends Serializable { || isIntervalType( ddlTypeCode ); } + default boolean isArray() { + return isArray( getDdlTypeCode() ); + } + + static boolean isArray(int jdbcTypeCode) { + switch ( jdbcTypeCode ) { + case ARRAY: + case STRUCT_ARRAY: + case JSON_ARRAY: + case XML_ARRAY: + return true; + } + return false; + } + default CastType getCastType() { return getCastType( getDdlTypeCode() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringTest.java index 86f5273a69..aa28254da1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringTest.java @@ -26,6 +26,8 @@ import org.junit.jupiter.api.Test; import jakarta.persistence.Tuple; import org.assertj.core.api.Assertions; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.isOneOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -59,16 +61,29 @@ public class ArrayToStringTest { public void test(SessionFactoryScope scope) { scope.inSession( em -> { //tag::hql-array-to-string-example[] - List results = em.createQuery( "select array_to_string(e.theArray, ',') from EntityWithArrays e", String.class ) + List results = em.createQuery( "select array_to_string(e.theArray, ',') from EntityWithArrays e order by e.id", String.class ) .getResultList(); //end::hql-array-to-string-example[] assertEquals( 3, results.size() ); + // We expect an empty string, but Oracle returns NULL instead of empty strings Assertions.assertThat( results.get( 0 ) ).isNullOrEmpty(); assertEquals( "abc,def", results.get( 1 ) ); assertNull( results.get( 2 ) ); } ); } + @Test + public void testNullValue(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select array_to_string(e.theArray, ',', 'null') from EntityWithArrays e order by e.id", String.class ) + .getResultList(); + assertEquals( 3, results.size() ); + Assertions.assertThat( results.get( 0 ) ).isNullOrEmpty(); + assertEquals( "abc,null,def", results.get( 1 ) ); + assertNull( results.get( 2 ) ); + } ); + } + @Test public void testNodeBuilderArray(SessionFactoryScope scope) { scope.inSession( em -> { @@ -99,4 +114,30 @@ public class ArrayToStringTest { } ); } + @Test + public void testCast(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-to-string-hql-example[] + List results = em.createQuery( "select cast(e.theArray as String) from EntityWithArrays e order by e.id", String.class ) + .getResultList(); + //end::hql-array-to-string-hql-example[] + assertEquals( 3, results.size() ); + assertEquals( "[]", results.get( 0 ) ); + assertEquals( "[abc,null,def]", results.get( 1 ) ); + assertNull( results.get( 2 ) ); + } ); + } + + @Test + public void testStr(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select str(e.theArray) from EntityWithArrays e order by e.id", String.class ) + .getResultList(); + assertEquals( 3, results.size() ); + assertEquals( "[]", results.get( 0 ) ); + assertEquals( "[abc,null,def]", results.get( 1 ) ); + assertNull( results.get( 2 ) ); + } ); + } + }