HHH-17375 Support casting array to string and add optional third argument to array_to_string for null element handling
This commit is contained in:
parent
8ec90b8fb1
commit
64dd9e657c
|
@ -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_trim()` | Creates array copy trimming the last _N_ elements
|
||||||
| `array_fill()` | Creates array filled with the same element _N_ times
|
| `array_fill()` | Creates array filled with the same element _N_ times
|
||||||
| `array_fill_list()` | Like `array_fill`, but returns the result as `List<?>`
|
| `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]]
|
[[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]]
|
[[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`.
|
Returns `null` if the first argument is `null`.
|
||||||
|
|
||||||
[[hql-array-to-string-example]]
|
[[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]]
|
[[hql-user-defined-functions]]
|
||||||
==== Native and user-defined functions
|
==== Native and user-defined functions
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,11 @@ public class PostgresPlusLegacyDialect extends PostgreSQLLegacyDialect {
|
||||||
return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType );
|
return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmptyStringTreatedAsNull() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException {
|
public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException {
|
||||||
statement.registerOutParameter( col, Types.REF );
|
statement.registerOutParameter( col, Types.REF );
|
||||||
|
|
|
@ -248,13 +248,16 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport
|
||||||
"return res; " +
|
"return res; " +
|
||||||
"end;",
|
"end;",
|
||||||
"create or replace function " + arrayTypeName + "_to_string(arr in " + arrayTypeName +
|
"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 " +
|
"res varchar2(4000):=''; begin " +
|
||||||
"if arr is null or sep is null then return null; end if; " +
|
"if arr is null or sep is null then return null; end if; " +
|
||||||
"for i in 1 .. arr.count loop " +
|
"for i in 1 .. arr.count loop " +
|
||||||
"if arr(i) is not null then " +
|
"if arr(i) is not null then " +
|
||||||
"if length(res)<>0 then res:=res||sep; end if; " +
|
"if length(res)<>0 then res:=res||sep; end if; " +
|
||||||
"res:=res||arr(i); " +
|
"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 if; " +
|
||||||
"end loop; " +
|
"end loop; " +
|
||||||
"return res; " +
|
"return res; " +
|
||||||
|
|
|
@ -104,6 +104,11 @@ public class PostgresPlusDialect extends PostgreSQLDialect {
|
||||||
return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType );
|
return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmptyStringTreatedAsNull() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException {
|
public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException {
|
||||||
statement.registerOutParameter( col, Types.REF );
|
statement.registerOutParameter( col, Types.REF );
|
||||||
|
|
|
@ -10,10 +10,14 @@ import java.sql.Types;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||||
import org.hibernate.query.ReturnableType;
|
import org.hibernate.query.ReturnableType;
|
||||||
import org.hibernate.query.sqm.CastType;
|
import org.hibernate.query.sqm.CastType;
|
||||||
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
|
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.StandardArgumentsValidators;
|
||||||
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
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.SqlAstNode;
|
||||||
import org.hibernate.sql.ast.tree.expression.CastTarget;
|
import org.hibernate.sql.ast.tree.expression.CastTarget;
|
||||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
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
|
* 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 JdbcMapping targetJdbcMapping = castTarget.getExpressionType().getSingleJdbcMapping();
|
||||||
final CastType targetType = getCastType( targetJdbcMapping );
|
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) {
|
private CastType getCastType(JdbcMapping sourceMapping) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.hibernate.type.SqlTypes;
|
||||||
import org.hibernate.type.StandardBasicTypes;
|
import org.hibernate.type.StandardBasicTypes;
|
||||||
import org.hibernate.type.spi.TypeConfiguration;
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
import static org.hibernate.dialect.function.CastFunction.renderCastArrayToString;
|
||||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
|
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
|
||||||
|
|
||||||
public class CastingConcatFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
public class CastingConcatFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||||
|
@ -97,12 +98,16 @@ public class CastingConcatFunction extends AbstractSqmSelfRenderingFunctionDescr
|
||||||
|
|
||||||
private void renderAsString(SqlAppender sqlAppender, SqlAstTranslator<?> translator, Expression expression) {
|
private void renderAsString(SqlAppender sqlAppender, SqlAstTranslator<?> translator, Expression expression) {
|
||||||
final JdbcMapping sourceMapping = expression.getExpressionType().getSingleJdbcMapping();
|
final JdbcMapping sourceMapping = expression.getExpressionType().getSingleJdbcMapping();
|
||||||
|
final CastType sourceType = sourceMapping.getCastType();
|
||||||
// No need to cast if we already have a string
|
// No need to cast if we already have a string
|
||||||
if ( sourceMapping.getCastType() == CastType.STRING ) {
|
if ( sourceType == CastType.STRING ) {
|
||||||
translator.render( expression, argumentRenderingMode );
|
translator.render( expression, argumentRenderingMode );
|
||||||
}
|
}
|
||||||
|
else if ( sourceType == CastType.OTHER && sourceMapping.getJdbcType().isArray() ) {
|
||||||
|
renderCastArrayToString( sqlAppender, expression, dialect, translator );
|
||||||
|
}
|
||||||
else {
|
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 )
|
new PatternRenderer( cast.replace( "?2", concatArgumentCastType ), argumentRenderingMode )
|
||||||
.render( sqlAppender, Collections.singletonList( expression ), translator );
|
.render( sqlAppender, Collections.singletonList( expression ), translator );
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,8 @@ import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||||
import org.hibernate.type.StandardBasicTypes;
|
import org.hibernate.type.StandardBasicTypes;
|
||||||
import org.hibernate.type.spi.TypeConfiguration;
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
import static org.hibernate.dialect.function.CastFunction.renderCastArrayToString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Beikov
|
* @author Christian Beikov
|
||||||
*/
|
*/
|
||||||
|
@ -401,13 +403,18 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final JdbcMapping sourceMapping = realArg.getExpressionType().getSingleJdbcMapping();
|
final JdbcMapping sourceMapping = realArg.getExpressionType().getSingleJdbcMapping();
|
||||||
|
final CastType sourceType = sourceMapping.getCastType();
|
||||||
// No need to cast if we already have a string
|
// No need to cast if we already have a string
|
||||||
if ( sourceMapping.getCastType() == CastType.STRING ) {
|
if ( sourceType == CastType.STRING ) {
|
||||||
translator.render( realArg, defaultArgumentRenderingMode );
|
translator.render( realArg, defaultArgumentRenderingMode );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else if ( sourceType == CastType.OTHER && sourceMapping.getJdbcType().isArray() ) {
|
||||||
|
renderCastArrayToString( sqlAppender, realArg, dialect, translator );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
final String cast = dialect.castPattern( sourceMapping.getCastType(), CastType.STRING );
|
final String cast = dialect.castPattern( sourceType, CastType.STRING );
|
||||||
new PatternRenderer( cast.replace( "?2", concatArgumentCastType ) )
|
new PatternRenderer( cast.replace( "?2", concatArgumentCastType ) )
|
||||||
.render( sqlAppender, Collections.singletonList( realArg ), translator );
|
.render( sqlAppender, Collections.singletonList( realArg ), translator );
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -36,18 +36,20 @@ public class ArrayAndElementArgumentValidator extends ArrayArgumentValidator {
|
||||||
TypeConfiguration typeConfiguration) {
|
TypeConfiguration typeConfiguration) {
|
||||||
final BasicType<?> expectedElementType = getElementType( arguments, functionName, typeConfiguration );
|
final BasicType<?> expectedElementType = getElementType( arguments, functionName, typeConfiguration );
|
||||||
for ( int elementIndex : elementIndexes ) {
|
for ( int elementIndex : elementIndexes ) {
|
||||||
final SqmTypedNode<?> elementArgument = arguments.get( elementIndex );
|
if ( elementIndex < arguments.size() ) {
|
||||||
final SqmExpressible<?> elementType = elementArgument.getExpressible().getSqmType();
|
final SqmTypedNode<?> elementArgument = arguments.get( elementIndex );
|
||||||
if ( expectedElementType != null && elementType != null && expectedElementType != elementType ) {
|
final SqmExpressible<?> elementType = elementArgument.getExpressible().getSqmType();
|
||||||
throw new FunctionArgumentException(
|
if ( expectedElementType != null && elementType != null && expectedElementType != elementType ) {
|
||||||
String.format(
|
throw new FunctionArgumentException(
|
||||||
"Parameter %d of function '%s()' has type %s, but argument is of type '%s'",
|
String.format(
|
||||||
elementIndex,
|
"Parameter %d of function '%s()' has type %s, but argument is of type '%s'",
|
||||||
functionName,
|
elementIndex,
|
||||||
expectedElementType.getJavaTypeDescriptor().getTypeName(),
|
functionName,
|
||||||
elementType.getTypeName()
|
expectedElementType.getJavaTypeDescriptor().getTypeName(),
|
||||||
)
|
elementType.getTypeName()
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,16 @@ public class ArrayToStringFunction extends AbstractSqmSelfRenderingFunctionDescr
|
||||||
"array_to_string",
|
"array_to_string",
|
||||||
FunctionKind.NORMAL,
|
FunctionKind.NORMAL,
|
||||||
StandardArgumentsValidators.composite(
|
StandardArgumentsValidators.composite(
|
||||||
new ArgumentTypesValidator( null, ANY, STRING ),
|
new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 3 ), ANY, STRING, ANY ),
|
||||||
ArrayArgumentValidator.DEFAULT_INSTANCE
|
new ArrayAndElementArgumentValidator( 0, 2 )
|
||||||
),
|
),
|
||||||
StandardFunctionReturnTypeResolvers.invariant(
|
StandardFunctionReturnTypeResolvers.invariant(
|
||||||
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
|
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 );
|
sqlAstArguments.get( 0 ).accept( walker );
|
||||||
sqlAppender.appendSql( ',' );
|
sqlAppender.appendSql( ',' );
|
||||||
sqlAstArguments.get( 1 ).accept( walker );
|
sqlAstArguments.get( 1 ).accept( walker );
|
||||||
|
if ( sqlAstArguments.size() > 2 ) {
|
||||||
|
sqlAppender.appendSql( ',' );
|
||||||
|
sqlAstArguments.get( 2 ).accept( walker );
|
||||||
|
}
|
||||||
sqlAppender.appendSql( ')' );
|
sqlAppender.appendSql( ')' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,11 +37,22 @@ public class H2ArrayToStringFunction extends ArrayToStringFunction {
|
||||||
SqlAstTranslator<?> walker) {
|
SqlAstTranslator<?> walker) {
|
||||||
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
|
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
|
||||||
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
|
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
|
||||||
|
final Expression defaultExpression = sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null;
|
||||||
sqlAppender.append( "case when " );
|
sqlAppender.append( "case when " );
|
||||||
arrayExpression.accept( walker );
|
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 );
|
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 );
|
separatorExpression.accept( walker );
|
||||||
sqlAppender.append( ") within group (order by i.idx) from system_range(1,");
|
sqlAppender.append( ") within group (order by i.idx) from system_range(1,");
|
||||||
sqlAppender.append( Integer.toString( maximumArraySize ) );
|
sqlAppender.append( Integer.toString( maximumArraySize ) );
|
||||||
|
|
|
@ -33,9 +33,20 @@ public class HSQLArrayToStringFunction extends ArrayToStringFunction {
|
||||||
SqlAstTranslator<?> walker) {
|
SqlAstTranslator<?> walker) {
|
||||||
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
|
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
|
||||||
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
|
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
|
||||||
|
final Expression defaultExpression = sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null;
|
||||||
sqlAppender.append( "case when " );
|
sqlAppender.append( "case when " );
|
||||||
arrayExpression.accept( walker );
|
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
|
// HSQLDB doesn't like non-literals as separator
|
||||||
walker.render( separatorExpression, SqlAstNodeRenderingMode.INLINE_PARAMETERS );
|
walker.render( separatorExpression, SqlAstNodeRenderingMode.INLINE_PARAMETERS );
|
||||||
sqlAppender.append( ") from unnest(");
|
sqlAppender.append( ") from unnest(");
|
||||||
|
|
|
@ -46,6 +46,13 @@ public class OracleArrayToStringFunction extends ArrayToStringFunction {
|
||||||
sqlAstArguments.get( 0 ).accept( walker );
|
sqlAstArguments.get( 0 ).accept( walker );
|
||||||
sqlAppender.append( ',' );
|
sqlAppender.append( ',' );
|
||||||
sqlAstArguments.get( 1 ).accept( walker );
|
sqlAstArguments.get( 1 ).accept( walker );
|
||||||
|
if ( sqlAstArguments.size() > 2 ) {
|
||||||
|
sqlAppender.append( ',' );
|
||||||
|
sqlAstArguments.get( 2 ).accept( walker );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sqlAppender.append( ",null" );
|
||||||
|
}
|
||||||
sqlAppender.append( ')' );
|
sqlAppender.append( ')' );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,6 +293,21 @@ public interface JdbcType extends Serializable {
|
||||||
|| isIntervalType( ddlTypeCode );
|
|| 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() {
|
default CastType getCastType() {
|
||||||
return getCastType( getDdlTypeCode() );
|
return getCastType( getDdlTypeCode() );
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import org.junit.jupiter.api.Test;
|
||||||
import jakarta.persistence.Tuple;
|
import jakarta.persistence.Tuple;
|
||||||
import org.assertj.core.api.Assertions;
|
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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
@ -59,16 +61,29 @@ public class ArrayToStringTest {
|
||||||
public void test(SessionFactoryScope scope) {
|
public void test(SessionFactoryScope scope) {
|
||||||
scope.inSession( em -> {
|
scope.inSession( em -> {
|
||||||
//tag::hql-array-to-string-example[]
|
//tag::hql-array-to-string-example[]
|
||||||
List<String> results = em.createQuery( "select array_to_string(e.theArray, ',') from EntityWithArrays e", String.class )
|
List<String> results = em.createQuery( "select array_to_string(e.theArray, ',') from EntityWithArrays e order by e.id", String.class )
|
||||||
.getResultList();
|
.getResultList();
|
||||||
//end::hql-array-to-string-example[]
|
//end::hql-array-to-string-example[]
|
||||||
assertEquals( 3, results.size() );
|
assertEquals( 3, results.size() );
|
||||||
|
// We expect an empty string, but Oracle returns NULL instead of empty strings
|
||||||
Assertions.assertThat( results.get( 0 ) ).isNullOrEmpty();
|
Assertions.assertThat( results.get( 0 ) ).isNullOrEmpty();
|
||||||
assertEquals( "abc,def", results.get( 1 ) );
|
assertEquals( "abc,def", results.get( 1 ) );
|
||||||
assertNull( results.get( 2 ) );
|
assertNull( results.get( 2 ) );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNullValue(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
List<String> 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
|
@Test
|
||||||
public void testNodeBuilderArray(SessionFactoryScope scope) {
|
public void testNodeBuilderArray(SessionFactoryScope scope) {
|
||||||
scope.inSession( em -> {
|
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<String> 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<String> 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 ) );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue