From ae3c88ab66b15fa14b31d9dc144bc257854d8b4f Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 24 Oct 2023 18:38:30 +0200 Subject: [PATCH] HHH-17335 Add array_prepend and array_append functions --- .../chapters/query/hql/QueryLanguage.adoc | 28 ++++++ .../dialect/CockroachLegacyDialect.java | 2 + .../community/dialect/H2LegacyDialect.java | 2 + .../community/dialect/HSQLLegacyDialect.java | 2 + .../dialect/OracleLegacyDialect.java | 2 + .../dialect/PostgreSQLLegacyDialect.java | 2 + .../hibernate/dialect/CockroachDialect.java | 2 + .../java/org/hibernate/dialect/H2Dialect.java | 2 + .../org/hibernate/dialect/HSQLDialect.java | 2 + .../dialect/MySQLSqlAstTranslator.java | 2 +- .../org/hibernate/dialect/OracleDialect.java | 2 + .../hibernate/dialect/PostgreSQLDialect.java | 2 + .../function/CommonFunctionFactory.java | 45 +++++++++ .../ArrayAndElementArgumentTypeResolver.java | 2 +- .../array/ArrayConcatElementFunction.java | 73 ++++++++++++++ .../array/ArrayContainsOperatorFunction.java | 2 +- .../array/ArrayRemoveIndexUnnestFunction.java | 2 +- .../array/ArraySliceUnnestFunction.java | 2 +- .../ArrayViaArgumentReturnTypeResolver.java | 2 +- ...yViaElementArgumentReturnTypeResolver.java | 2 +- ...rrayTypeHelper.java => DdlTypeHelper.java} | 30 ++++-- .../OracleArrayConcatElementFunction.java | 60 ++++++++++++ .../array/OracleArrayConcatFunction.java | 2 +- .../array/OracleArrayContainsAllFunction.java | 2 +- .../array/OracleArrayContainsAnyFunction.java | 2 +- .../array/OracleArrayContainsFunction.java | 2 +- .../OracleArrayContainsNullFunction.java | 2 +- .../array/OracleArrayGetFunction.java | 2 +- .../array/OracleArrayLengthFunction.java | 2 +- .../array/OracleArrayPositionFunction.java | 2 +- .../array/OracleArrayRemoveFunction.java | 2 +- .../array/OracleArrayRemoveIndexFunction.java | 2 +- .../array/OracleArrayReplaceFunction.java | 2 +- .../array/OracleArraySetFunction.java | 2 +- .../array/OracleArraySliceFunction.java | 2 +- .../PostgreSQLArrayConcatElementFunction.java | 97 +++++++++++++++++++ .../sql/ast/spi/AbstractSqlAstTranslator.java | 51 ++++++++-- .../test/function/array/ArrayAppendTest.java | 90 +++++++++++++++++ .../test/function/array/ArrayPrependTest.java | 90 +++++++++++++++++ 39 files changed, 588 insertions(+), 36 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatElementFunction.java rename hibernate-core/src/main/java/org/hibernate/dialect/function/array/{ArrayTypeHelper.java => DdlTypeHelper.java} (63%) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayConcatElementFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayConcatElementFunction.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAppendTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPrependTest.java 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 5867324f8b..2c74e33238 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -1123,6 +1123,8 @@ The following functions deal with SQL array types, which are not supported on ev | `array_position()` | Determines the position of an element in an array | `array_length()` | Determines the length of an array | `array_concat()` | Concatenates array with each other in order +| `array_prepend()` | Prepends element to array +| `array_append()` | Appends element to array | `array_contains_all()` | Determines if one array holds all elements of another array | `array_contains_all_nullable()` | Determines if one array holds all elements of another array, supporting null elements | `array_contains_any()` | Determines if one array holds at least one element of another array @@ -1204,6 +1206,32 @@ include::{array-example-dir-hql}/ArrayConcatTest.java[tags=hql-array-concat-exam ---- ==== +[[hql-array-prepend-functions]] +===== `array_prepend()` + +Prepends element to array. Returns `null` if the array argument is `null`. + +[[hql-array-prepend-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayPrependTest.java[tags=hql-array-prepend-example] +---- +==== + +[[hql-array-append-functions]] +===== `array_append()` + +Appends element to array. Returns `null` if the array argument is `null`. + +[[hql-array-append-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayAppendTest.java[tags=hql-array-append-example] +---- +==== + [[hql-array-contains-quantified-functions]] ===== `array_contains_all()` and `array_contains_any()` diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java index 769d9edfcb..2b8037cf26 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java @@ -468,6 +468,8 @@ public class CockroachLegacyDialect extends Dialect { functionFactory.arrayPosition_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); + functionFactory.arrayPrepend_postgresql(); + functionFactory.arrayAppend_postgresql(); functionFactory.arrayContainsAll_operator(); functionFactory.arrayContainsAny_operator(); functionFactory.arrayContainsAllNullable_operator(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java index 88345660f6..f79acf6741 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java @@ -376,6 +376,8 @@ public class H2LegacyDialect extends Dialect { functionFactory.arrayContainsNull(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); + functionFactory.arrayPrepend_operator(); + functionFactory.arrayAppend_operator(); functionFactory.arrayContainsAll_h2(); functionFactory.arrayContainsAny_h2(); functionFactory.arrayContainsAllNullable_h2(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java index da08003540..459a248c18 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java @@ -254,6 +254,8 @@ public class HSQLLegacyDialect extends Dialect { functionFactory.arrayPosition_hsql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); + functionFactory.arrayPrepend_operator(); + functionFactory.arrayAppend_operator(); functionFactory.arrayContainsAll_hsql(); functionFactory.arrayContainsAny_hsql(); functionFactory.arrayContainsAllNullable_hsql(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index 6beec5e443..a30d9e2c8b 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -290,6 +290,8 @@ public class OracleLegacyDialect extends Dialect { functionFactory.arrayPosition_oracle(); functionFactory.arrayLength_oracle(); functionFactory.arrayConcat_oracle(); + functionFactory.arrayPrepend_oracle(); + functionFactory.arrayAppend_oracle(); functionFactory.arrayContainsAll_oracle(); functionFactory.arrayContainsAny_oracle(); functionFactory.arrayContainsAllNullable_oracle(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java index 907f0dc1e1..cb75f6e024 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java @@ -588,6 +588,8 @@ public class PostgreSQLLegacyDialect extends Dialect { functionFactory.arrayPosition_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); + functionFactory.arrayPrepend_postgresql(); + functionFactory.arrayAppend_postgresql(); functionFactory.arrayContainsAll_operator(); functionFactory.arrayContainsAny_operator(); functionFactory.arrayContainsAllNullable_operator(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java index 3f869e2525..ba5128e79c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -455,6 +455,8 @@ public class CockroachDialect extends Dialect { functionFactory.arrayPosition_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); + functionFactory.arrayPrepend_postgresql(); + functionFactory.arrayAppend_postgresql(); functionFactory.arrayContainsAll_operator(); functionFactory.arrayContainsAny_operator(); functionFactory.arrayContainsAllNullable_operator(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index 199a7dc2a9..45dd2754a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -315,6 +315,8 @@ public class H2Dialect extends Dialect { functionFactory.arrayContainsNull(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); + functionFactory.arrayPrepend_operator(); + functionFactory.arrayAppend_operator(); functionFactory.arrayContainsAll_h2(); functionFactory.arrayContainsAny_h2(); functionFactory.arrayContainsAllNullable_h2(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index e7443a8d09..4ec12e93e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -194,6 +194,8 @@ public class HSQLDialect extends Dialect { functionFactory.arrayPosition_hsql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); + functionFactory.arrayPrepend_operator(); + functionFactory.arrayAppend_operator(); functionFactory.arrayContainsAll_hsql(); functionFactory.arrayContainsAny_hsql(); functionFactory.arrayContainsAllNullable_hsql(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java index 29ddc55641..f4144580d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java @@ -46,7 +46,7 @@ public class MySQLSqlAstTranslator extends AbstractSqlA } public static String getSqlType(CastTarget castTarget, SessionFactoryImplementor factory) { - final String sqlType = getSqlTypeName( castTarget, factory ); + final String sqlType = getCastTypeName( castTarget, factory ); return getSqlType( castTarget, sqlType, factory.getJdbcServices().getDialect() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 7ba6083618..44fd062bf4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -319,6 +319,8 @@ public class OracleDialect extends Dialect { functionFactory.arrayPosition_oracle(); functionFactory.arrayLength_oracle(); functionFactory.arrayConcat_oracle(); + functionFactory.arrayPrepend_oracle(); + functionFactory.arrayAppend_oracle(); functionFactory.arrayContainsAll_oracle(); functionFactory.arrayContainsAny_oracle(); functionFactory.arrayContainsAllNullable_oracle(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 8ff97a517c..aea621ba28 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -636,6 +636,8 @@ public class PostgreSQLDialect extends Dialect { functionFactory.arrayPosition_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); + functionFactory.arrayPrepend_postgresql(); + functionFactory.arrayAppend_postgresql(); functionFactory.arrayContainsAll_operator(); functionFactory.arrayContainsAny_operator(); functionFactory.arrayContainsAllNullable_operator(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java index cf7de5f2f5..c44ce4da5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java @@ -16,6 +16,7 @@ import org.hibernate.dialect.function.array.ArrayAggFunction; import org.hibernate.dialect.function.array.ArrayAndElementArgumentTypeResolver; import org.hibernate.dialect.function.array.ArrayAndElementArgumentValidator; import org.hibernate.dialect.function.array.ArrayArgumentValidator; +import org.hibernate.dialect.function.array.ArrayConcatElementFunction; import org.hibernate.dialect.function.array.ArrayConcatFunction; import org.hibernate.dialect.function.array.ArrayConstructorFunction; import org.hibernate.dialect.function.array.ArrayContainsQuantifiedOperatorFunction; @@ -36,6 +37,7 @@ import org.hibernate.dialect.function.array.H2ArraySetFunction; import org.hibernate.dialect.function.array.HSQLArrayPositionFunction; import org.hibernate.dialect.function.array.HSQLArrayRemoveFunction; import org.hibernate.dialect.function.array.HSQLArraySetFunction; +import org.hibernate.dialect.function.array.OracleArrayConcatElementFunction; import org.hibernate.dialect.function.array.OracleArrayConcatFunction; import org.hibernate.dialect.function.array.OracleArrayContainsAllFunction; import org.hibernate.dialect.function.array.OracleArrayContainsAnyFunction; @@ -47,6 +49,7 @@ import org.hibernate.dialect.function.array.OracleArrayRemoveIndexFunction; 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.PostgreSQLArrayConcatElementFunction; import org.hibernate.dialect.function.array.PostgreSQLArrayConcatFunction; import org.hibernate.dialect.function.array.PostgreSQLArrayPositionFunction; import org.hibernate.dialect.function.array.CastingArrayConstructorFunction; @@ -2946,6 +2949,48 @@ public class CommonFunctionFactory { functionRegistry.register( "array_concat", new OracleArrayConcatFunction() ); } + /** + * H2 and HSQLDB array_prepend() function + */ + public void arrayPrepend_operator() { + functionRegistry.register( "array_prepend", new ArrayConcatElementFunction( "", "||", "", true ) ); + } + + /** + * CockroachDB and PostgreSQL array_prepend() function + */ + public void arrayPrepend_postgresql() { + functionRegistry.register( "array_prepend", new PostgreSQLArrayConcatElementFunction( true ) ); + } + + /** + * Oracle array_prepend() function + */ + public void arrayPrepend_oracle() { + functionRegistry.register( "array_prepend", new OracleArrayConcatElementFunction( true ) ); + } + + /** + * H2 and HSQLDB array_append() function + */ + public void arrayAppend_operator() { + functionRegistry.register( "array_append", new ArrayConcatElementFunction( "", "||", "", false ) ); + } + + /** + * CockroachDB and PostgreSQL array_append() function + */ + public void arrayAppend_postgresql() { + functionRegistry.register( "array_append", new PostgreSQLArrayConcatElementFunction( false ) ); + } + + /** + * Oracle array_append() function + */ + public void arrayAppend_oracle() { + functionRegistry.register( "array_append", new OracleArrayConcatElementFunction( false ) ); + } + /** * H2 array_get() function via bracket syntax */ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentTypeResolver.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentTypeResolver.java index d74e77177b..8dffe03c4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentTypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentTypeResolver.java @@ -43,7 +43,7 @@ public class ArrayAndElementArgumentTypeResolver implements FunctionArgumentType final SqmTypedNode argument = function.getArguments().get( elementIndex ); final DomainType sqmType = argument.getExpressible().getSqmType(); if ( sqmType instanceof ReturnableType ) { - return ArrayTypeHelper.resolveArrayType( + return DdlTypeHelper.resolveArrayType( sqmType, converter.getCreationContext().getSessionFactory().getTypeConfiguration() ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatElementFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatElementFunction.java new file mode 100644 index 0000000000..bd08bacf8e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatElementFunction.java @@ -0,0 +1,73 @@ +/* + * 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.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; + +/** + * Concatenation function for array and an element. + */ +public class ArrayConcatElementFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + private final String prefix; + private final String separator; + private final String suffix; + protected final boolean prepend; + + public ArrayConcatElementFunction(String prefix, String separator, String suffix, boolean prepend) { + super( + "array_" + ( prepend ? "prepend" : "append" ), + StandardArgumentsValidators.composite( + StandardArgumentsValidators.exactly( 2 ), + prepend ? new ArrayAndElementArgumentValidator( 1, 0 ) + : ArrayAndElementArgumentValidator.DEFAULT_INSTANCE + ), + prepend ? new ArrayViaArgumentReturnTypeResolver( 1 ) + : ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE, + prepend ? new ArrayAndElementArgumentTypeResolver( 1, 0 ) + : ArrayAndElementArgumentTypeResolver.DEFAULT_INSTANCE + ); + this.prefix = prefix; + this.separator = separator; + this.suffix = suffix; + this.prepend = prepend; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final SqlAstNode firstArgument = sqlAstArguments.get( 0 ); + final SqlAstNode secondArgument = sqlAstArguments.get( 1 ); + sqlAppender.append( prefix ); + if ( prepend ) { + sqlAppender.append( "array[" ); + firstArgument.accept( walker ); + sqlAppender.append( ']' ); + } + else { + firstArgument.accept( walker ); + } + sqlAppender.append( separator ); + if ( prepend ) { + secondArgument.accept( walker ); + } + else { + sqlAppender.append( "array[" ); + secondArgument.accept( walker ); + sqlAppender.append( ']' ); + } + sqlAppender.append( suffix ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsOperatorFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsOperatorFunction.java index 3021c70179..3b792fd51f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsOperatorFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsOperatorFunction.java @@ -48,7 +48,7 @@ public class ArrayContainsOperatorFunction extends AbstractSqmSelfRenderingFunct sqlAppender.append( "cast(array[" ); elementExpression.accept( walker ); sqlAppender.append( "] as " ); - sqlAppender.append( ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ) ); + sqlAppender.append( DdlTypeHelper.getCastTypeName( arrayExpression.getExpressionType(), walker ) ); sqlAppender.append( ')' ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayRemoveIndexUnnestFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayRemoveIndexUnnestFunction.java index 9c0f913232..410601906b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayRemoveIndexUnnestFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayRemoveIndexUnnestFunction.java @@ -59,7 +59,7 @@ public class ArrayRemoveIndexUnnestFunction extends AbstractSqmSelfRenderingFunc sqlAppender.append( "),"); if ( castEmptyArrayLiteral ) { sqlAppender.append( "cast(array[] as " ); - sqlAppender.append( ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ) ); + sqlAppender.append( DdlTypeHelper.getCastTypeName( arrayExpression.getExpressionType(), walker ) ); sqlAppender.append( ')' ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArraySliceUnnestFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArraySliceUnnestFunction.java index 46e8dc23ff..a2faabf410 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArraySliceUnnestFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArraySliceUnnestFunction.java @@ -66,7 +66,7 @@ public class ArraySliceUnnestFunction extends AbstractSqmSelfRenderingFunctionDe sqlAppender.append( "),"); if ( castEmptyArrayLiteral ) { sqlAppender.append( "cast(array[] as " ); - sqlAppender.append( ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ) ); + sqlAppender.append( DdlTypeHelper.getCastTypeName( arrayExpression.getExpressionType(), walker ) ); sqlAppender.append( ')' ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayViaArgumentReturnTypeResolver.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayViaArgumentReturnTypeResolver.java index 16ec36be9f..91361e58e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayViaArgumentReturnTypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayViaArgumentReturnTypeResolver.java @@ -30,7 +30,7 @@ public class ArrayViaArgumentReturnTypeResolver implements FunctionReturnTypeRes private final int arrayIndex; - private ArrayViaArgumentReturnTypeResolver(int arrayIndex) { + public ArrayViaArgumentReturnTypeResolver(int arrayIndex) { this.arrayIndex = arrayIndex; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayViaElementArgumentReturnTypeResolver.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayViaElementArgumentReturnTypeResolver.java index b26029aacd..87822ada51 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayViaElementArgumentReturnTypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayViaElementArgumentReturnTypeResolver.java @@ -50,7 +50,7 @@ public class ArrayViaElementArgumentReturnTypeResolver implements FunctionReturn for ( SqmTypedNode argument : arguments ) { final DomainType sqmType = argument.getExpressible().getSqmType(); if ( sqmType instanceof ReturnableType ) { - return ArrayTypeHelper.resolveArrayType( sqmType, typeConfiguration ); + return DdlTypeHelper.resolveArrayType( sqmType, typeConfiguration ); } } return null; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayTypeHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/DdlTypeHelper.java similarity index 63% rename from hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayTypeHelper.java rename to hibernate-core/src/main/java/org/hibernate/dialect/function/array/DdlTypeHelper.java index adc509786c..4f35d41a79 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayTypeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/DdlTypeHelper.java @@ -22,7 +22,7 @@ import org.hibernate.type.descriptor.sql.DdlType; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; -public class ArrayTypeHelper { +public class DdlTypeHelper { @SuppressWarnings("unchecked") public static BasicType resolveArrayType(DomainType elementType, TypeConfiguration typeConfiguration) { @SuppressWarnings("unchecked") final BasicPluralJavaType arrayJavaType = (BasicPluralJavaType) typeConfiguration.getJavaTypeRegistry() @@ -39,18 +39,34 @@ public class ArrayTypeHelper { ); } - public static String getArrayTypeName(JdbcMappingContainer arrayType, SqlAstTranslator walker) { - if ( arrayType instanceof SqlTypedMapping ) { - return AbstractSqlAstTranslator.getSqlTypeName( (SqlTypedMapping) arrayType, walker.getSessionFactory() ); + public static String getTypeName(JdbcMappingContainer type, SqlAstTranslator walker) { + if ( type instanceof SqlTypedMapping ) { + return AbstractSqlAstTranslator.getSqlTypeName( (SqlTypedMapping) type, walker.getSessionFactory() ); } else { - final BasicPluralType pluralType = (BasicPluralType) arrayType.getSingleJdbcMapping(); + final BasicType jdbcMapping = (BasicType) type.getSingleJdbcMapping(); final TypeConfiguration typeConfiguration = walker.getSessionFactory().getTypeConfiguration(); final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); final DdlType ddlType = ddlTypeRegistry.getDescriptor( - pluralType.getJdbcType().getDdlTypeCode() + jdbcMapping.getJdbcType().getDdlTypeCode() ); - return ddlType.getCastTypeName( Size.nil(), pluralType, ddlTypeRegistry ); + return ddlType.getTypeName( Size.nil(), jdbcMapping, ddlTypeRegistry ); } } + + public static String getCastTypeName(JdbcMappingContainer type, SqlAstTranslator walker) { + if ( type instanceof SqlTypedMapping ) { + return AbstractSqlAstTranslator.getCastTypeName( (SqlTypedMapping) type, walker.getSessionFactory() ); + } + else { + final BasicType jdbcMapping = (BasicType) type.getSingleJdbcMapping(); + final TypeConfiguration typeConfiguration = walker.getSessionFactory().getTypeConfiguration(); + final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); + final DdlType ddlType = ddlTypeRegistry.getDescriptor( + jdbcMapping.getJdbcType().getDdlTypeCode() + ); + return ddlType.getCastTypeName( Size.nil(), jdbcMapping, ddlTypeRegistry ); + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayConcatElementFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayConcatElementFunction.java new file mode 100644 index 0000000000..bb000737ea --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayConcatElementFunction.java @@ -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.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; + +/** + * Oracle concatenation function for array and an element. + */ +public class OracleArrayConcatElementFunction extends ArrayConcatElementFunction { + + public OracleArrayConcatElementFunction(boolean prepend) { + super( "(", ",", ")", prepend ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final Expression firstArgument = (Expression) sqlAstArguments.get( 0 ); + final Expression secondArgument = (Expression) sqlAstArguments.get( 1 ); + final String arrayTypeName = DdlTypeHelper.getTypeName( + prepend ? secondArgument.getExpressionType() + : firstArgument.getExpressionType(), + walker + ); + sqlAppender.append( arrayTypeName ); + sqlAppender.append( "_concat(" ); + if ( prepend ) { + sqlAppender.append( arrayTypeName ); + sqlAppender.append( '(' ); + firstArgument.accept( walker ); + sqlAppender.append( ')' ); + } + else { + firstArgument.accept( walker ); + } + sqlAppender.append( ',' ); + if ( prepend ) { + secondArgument.accept( walker ); + } + else { + sqlAppender.append( arrayTypeName ); + sqlAppender.append( '(' ); + secondArgument.accept( walker ); + sqlAppender.append( ')' ); + } + sqlAppender.append( ')' ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayConcatFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayConcatFunction.java index e1ac2d7796..8e0f978592 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayConcatFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayConcatFunction.java @@ -36,7 +36,7 @@ public class OracleArrayConcatFunction extends ArrayConcatFunction { } } - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( expressionType, walker ); + final String arrayTypeName = DdlTypeHelper.getTypeName( expressionType, walker ); sqlAppender.append( arrayTypeName ); sqlAppender.append( "_concat" ); super.render( sqlAppender, sqlAstArguments, walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAllFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAllFunction.java index e1d952906d..05f5f66a5c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAllFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAllFunction.java @@ -29,7 +29,7 @@ public class OracleArrayContainsAllFunction extends AbstractArrayContainsQuantif List sqlAstArguments, SqlAstTranslator walker) { final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 ); - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( haystackExpression.getExpressionType(), walker ); + final String arrayTypeName = DdlTypeHelper.getTypeName( haystackExpression.getExpressionType(), walker ); sqlAppender.appendSql( arrayTypeName ); sqlAppender.append( "_contains_all(" ); haystackExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAnyFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAnyFunction.java index dd01379fdf..d909d3bdd1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAnyFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAnyFunction.java @@ -29,7 +29,7 @@ public class OracleArrayContainsAnyFunction extends AbstractArrayContainsQuantif List sqlAstArguments, SqlAstTranslator walker) { final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 ); - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( haystackExpression.getExpressionType(), walker ); + final String arrayTypeName = DdlTypeHelper.getTypeName( haystackExpression.getExpressionType(), walker ); sqlAppender.appendSql( arrayTypeName ); sqlAppender.append( "_contains_any(" ); haystackExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsFunction.java index d23000bebf..3d64c91dbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsFunction.java @@ -37,7 +37,7 @@ public class OracleArrayContainsFunction extends AbstractSqmSelfRenderingFunctio List sqlAstArguments, SqlAstTranslator walker) { final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ); + final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker ); sqlAppender.appendSql( arrayTypeName ); sqlAppender.append( "_position(" ); arrayExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsNullFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsNullFunction.java index a8e76c94bf..d2c6f454ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsNullFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsNullFunction.java @@ -37,7 +37,7 @@ public class OracleArrayContainsNullFunction extends AbstractSqmSelfRenderingFun List sqlAstArguments, SqlAstTranslator walker) { final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ); + final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker ); sqlAppender.appendSql( arrayTypeName ); sqlAppender.append( "_position(" ); arrayExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayGetFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayGetFunction.java index 0bad35d667..8ccc35bd03 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayGetFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayGetFunction.java @@ -26,7 +26,7 @@ public class OracleArrayGetFunction extends ArrayGetUnnestFunction { SqlAppender sqlAppender, List sqlAstArguments, SqlAstTranslator walker) { - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( + final String arrayTypeName = DdlTypeHelper.getTypeName( ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayLengthFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayLengthFunction.java index 5e3ac2aad9..11b1c8cca5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayLengthFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayLengthFunction.java @@ -37,7 +37,7 @@ public class OracleArrayLengthFunction extends AbstractSqmSelfRenderingFunctionD List sqlAstArguments, SqlAstTranslator walker) { final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ); + final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker ); sqlAppender.appendSql( arrayTypeName ); sqlAppender.append( "_length(" ); arrayExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayPositionFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayPositionFunction.java index eff684b683..6ad4fdac80 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayPositionFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayPositionFunction.java @@ -26,7 +26,7 @@ public class OracleArrayPositionFunction extends AbstractArrayPositionFunction { List sqlAstArguments, SqlAstTranslator walker) { final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ); + final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker ); sqlAppender.appendSql( arrayTypeName ); sqlAppender.append( "_position(" ); arrayExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveFunction.java index 16176dec2a..b2241c10db 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveFunction.java @@ -26,7 +26,7 @@ public class OracleArrayRemoveFunction extends AbstractArrayRemoveFunction { SqlAppender sqlAppender, List sqlAstArguments, SqlAstTranslator walker) { - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( + final String arrayTypeName = DdlTypeHelper.getTypeName( ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveIndexFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveIndexFunction.java index 7f80a421bd..682f783006 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveIndexFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveIndexFunction.java @@ -27,7 +27,7 @@ public class OracleArrayRemoveIndexFunction extends ArrayRemoveIndexUnnestFuncti SqlAppender sqlAppender, List sqlAstArguments, SqlAstTranslator walker) { - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( + final String arrayTypeName = DdlTypeHelper.getTypeName( ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayReplaceFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayReplaceFunction.java index 18a952a3d3..6f7acd11b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayReplaceFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayReplaceFunction.java @@ -23,7 +23,7 @@ public class OracleArrayReplaceFunction extends ArrayReplaceUnnestFunction { SqlAppender sqlAppender, List sqlAstArguments, SqlAstTranslator walker) { - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( + final String arrayTypeName = DdlTypeHelper.getTypeName( ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySetFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySetFunction.java index fae3b2e96c..c7041ee0a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySetFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySetFunction.java @@ -26,7 +26,7 @@ public class OracleArraySetFunction extends ArraySetUnnestFunction { SqlAppender sqlAppender, List sqlAstArguments, SqlAstTranslator walker) { - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( + final String arrayTypeName = DdlTypeHelper.getTypeName( ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySliceFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySliceFunction.java index a74de8ddc9..0600794a8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySliceFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySliceFunction.java @@ -27,7 +27,7 @@ public class OracleArraySliceFunction extends ArraySliceUnnestFunction { SqlAppender sqlAppender, List sqlAstArguments, SqlAstTranslator walker) { - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( + final String arrayTypeName = DdlTypeHelper.getTypeName( ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayConcatElementFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayConcatElementFunction.java new file mode 100644 index 0000000000..1997f14ce7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayConcatElementFunction.java @@ -0,0 +1,97 @@ +/* + * 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.metamodel.mapping.JdbcMapping; +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.sql.ast.tree.expression.Literal; +import org.hibernate.type.BasicPluralType; +import org.hibernate.type.BottomType; + +/** + * PostgreSQL variant of the function to properly return {@code null} when the array argument is null. + */ +public class PostgreSQLArrayConcatElementFunction extends ArrayConcatElementFunction { + + public PostgreSQLArrayConcatElementFunction(boolean prepend) { + super( "", "||", "", prepend ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final Expression firstArgument = (Expression) sqlAstArguments.get( 0 ); + final Expression secondArgument = (Expression) sqlAstArguments.get( 1 ); + final Expression arrayArgument; + final Expression elementArgument; + if ( prepend ) { + elementArgument = firstArgument; + arrayArgument = secondArgument; + } + else { + arrayArgument = firstArgument; + elementArgument = secondArgument; + } + final String elementCastType; + if ( needsElementCasting( elementArgument ) ) { + final JdbcMapping elementType = elementArgument.getExpressionType().getSingleJdbcMapping(); + if ( elementType instanceof BottomType ) { + elementCastType = DdlTypeHelper.getCastTypeName( + ( (BasicPluralType) arrayArgument.getExpressionType().getSingleJdbcMapping() ) + .getElementType(), + walker + ); + } + else { + elementCastType = DdlTypeHelper.getCastTypeName( elementType, walker ); + } + } + else { + elementCastType = null; + } + sqlAppender.append( "case when " ); + arrayArgument.accept( walker ); + sqlAppender.append( " is not null then " ); + if ( prepend && elementCastType != null) { + sqlAppender.append( "cast(" ); + firstArgument.accept( walker ); + sqlAppender.append( " as " ); + sqlAppender.append( elementCastType ); + sqlAppender.append( ')' ); + } + else { + firstArgument.accept( walker ); + } + sqlAppender.append( "||" ); + if ( !prepend && elementCastType != null) { + sqlAppender.append( "cast(" ); + secondArgument.accept( walker ); + sqlAppender.append( " as " ); + sqlAppender.append( elementCastType ); + sqlAppender.append( ')' ); + } + else { + secondArgument.accept( walker ); + } + sqlAppender.append( " end" ); + } + + private static boolean needsElementCasting(Expression elementExpression) { + // PostgreSQL needs casting of null and string literal expressions + return elementExpression instanceof Literal && ( + elementExpression.getExpressionType().getSingleJdbcMapping().getJdbcType().isString() + || ( (Literal) elementExpression ).getLiteralValue() == null + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 8f03e0e812..bb80c75f26 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -6263,7 +6263,7 @@ public abstract class AbstractSqlAstTranslator implemen @Override public void visitCastTarget(CastTarget castTarget) { - appendSql( getSqlTypeName( castTarget, sessionFactory ) ); + appendSql( getCastTypeName( castTarget, sessionFactory ) ); } public static String getSqlTypeName(SqlTypedMapping castTarget, SessionFactoryImplementor factory) { @@ -6273,7 +6273,7 @@ public abstract class AbstractSqlAstTranslator implemen else { final Size castTargetSize = castTarget.toSize(); final DdlTypeRegistry ddlTypeRegistry = factory.getTypeConfiguration().getDdlTypeRegistry(); - final SqlExpressible expressionType = (SqlExpressible) castTarget.getJdbcMapping(); + final BasicType expressionType = (BasicType) castTarget.getJdbcMapping(); if ( expressionType instanceof BasicPluralType ) { final BasicPluralType containerType = (BasicPluralType) expressionType; final BasicPluralJavaType javaTypeDescriptor = (BasicPluralJavaType) containerType.getJavaTypeDescriptor(); @@ -6293,19 +6293,52 @@ public abstract class AbstractSqlAstTranslator implemen return arrayTypeName; } } - DdlType ddlType = ddlTypeRegistry - .getDescriptor( expressionType.getJdbcMapping().getJdbcType().getDdlTypeCode() ); + DdlType ddlType = ddlTypeRegistry.getDescriptor( expressionType.getJdbcType().getDdlTypeCode() ); if ( ddlType == null ) { // this may happen when selecting a null value like `SELECT null from ...` // some dbs need the value to be cast so not knowing the real type we fall back to INTEGER ddlType = ddlTypeRegistry.getDescriptor( SqlTypes.INTEGER ); } - return ddlType.getCastTypeName( - castTargetSize, - expressionType, - ddlTypeRegistry - ); + return ddlType.getTypeName( castTargetSize, expressionType, ddlTypeRegistry ); + } + } + + public static String getCastTypeName(SqlTypedMapping castTarget, SessionFactoryImplementor factory) { + if ( castTarget.getColumnDefinition() != null ) { + return castTarget.getColumnDefinition(); + } + else { + final Size castTargetSize = castTarget.toSize(); + final DdlTypeRegistry ddlTypeRegistry = factory.getTypeConfiguration().getDdlTypeRegistry(); + final BasicType expressionType = (BasicType) castTarget.getJdbcMapping(); + if ( expressionType instanceof BasicPluralType ) { + final BasicPluralType containerType = (BasicPluralType) expressionType; + final BasicPluralJavaType javaTypeDescriptor = (BasicPluralJavaType) containerType.getJavaTypeDescriptor(); + final BasicType elementType = containerType.getElementType(); + final String elementTypeName = ddlTypeRegistry.getDescriptor( elementType.getJdbcType().getDdlTypeCode() ) + .getCastTypeName( + castTargetSize, + elementType, + ddlTypeRegistry + ); + final String arrayTypeName = factory.getJdbcServices().getDialect().getArrayTypeName( + javaTypeDescriptor.getElementJavaType().getJavaTypeClass().getSimpleName(), + elementTypeName, + null + ); + if ( arrayTypeName != null ) { + return arrayTypeName; + } + } + DdlType ddlType = ddlTypeRegistry.getDescriptor( expressionType.getJdbcType().getDdlTypeCode() ); + if ( ddlType == null ) { + // this may happen when selecting a null value like `SELECT null from ...` + // some dbs need the value to be cast so not knowing the real type we fall back to INTEGER + ddlType = ddlTypeRegistry.getDescriptor( SqlTypes.INTEGER ); + } + + return ddlType.getCastTypeName( castTargetSize, expressionType, ddlTypeRegistry ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAppendTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAppendTest.java new file mode 100644 index 0000000000..c9999dce4d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAppendTest.java @@ -0,0 +1,90 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.function.array; + +import java.util.List; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Tuple; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * @author Christian Beikov + */ +@DomainModel(annotatedClasses = EntityWithArrays.class) +@SessionFactory +@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsStructuralArrays.class) +// Make sure this stuff runs on a dedicated connection pool, +// otherwise we might run into ORA-21700: object does not exist or is marked for delete +// because the JDBC connection or database session caches something that should have been invalidated +@ServiceRegistry(settings = @Setting(name = AvailableSettings.CONNECTION_PROVIDER, value = "")) +public class ArrayAppendTest { + + @BeforeEach + public void prepareData(SessionFactoryScope scope) { + scope.inTransaction( em -> { + em.persist( new EntityWithArrays( 1L, new String[]{} ) ); + em.persist( new EntityWithArrays( 2L, new String[]{ "abc", null, "def" } ) ); + em.persist( new EntityWithArrays( 3L, null ) ); + } ); + } + + @AfterEach + public void cleanup(SessionFactoryScope scope) { + scope.inTransaction( em -> { + em.createMutationQuery( "delete from EntityWithArrays" ).executeUpdate(); + } ); + } + + @Test + public void testAppend(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-append-example[] + List results = em.createQuery( "select e.id, array_append(e.theArray, 'xyz') from EntityWithArrays e order by e.id", Tuple.class ) + .getResultList(); + //end::hql-array-append-example[] + assertEquals( 3, results.size() ); + assertEquals( 1L, results.get( 0 ).get( 0 ) ); + assertArrayEquals( new String[]{ "xyz" }, results.get( 0 ).get( 1, String[].class ) ); + assertEquals( 2L, results.get( 1 ).get( 0 ) ); + assertArrayEquals( new String[]{ "abc", null, "def", "xyz" }, results.get( 1 ).get( 1, String[].class ) ); + assertEquals( 3L, results.get( 2 ).get( 0 ) ); + assertNull( results.get( 2 ).get( 1, String[].class ) ); + } ); + } + + @Test + public void testAppendNull(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select e.id, array_append(e.theArray, null) from EntityWithArrays e order by e.id", Tuple.class ) + .getResultList(); + assertEquals( 3, results.size() ); + assertEquals( 1L, results.get( 0 ).get( 0 ) ); + assertArrayEquals( new String[]{ null }, results.get( 0 ).get( 1, String[].class ) ); + assertEquals( 2L, results.get( 1 ).get( 0 ) ); + assertArrayEquals( new String[]{ "abc", null, "def", null }, results.get( 1 ).get( 1, String[].class ) ); + assertEquals( 3L, results.get( 2 ).get( 0 ) ); + assertNull( results.get( 2 ).get( 1, String[].class ) ); + } ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPrependTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPrependTest.java new file mode 100644 index 0000000000..d4e5205744 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPrependTest.java @@ -0,0 +1,90 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.function.array; + +import java.util.List; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Tuple; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * @author Christian Beikov + */ +@DomainModel(annotatedClasses = EntityWithArrays.class) +@SessionFactory +@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsStructuralArrays.class) +// Make sure this stuff runs on a dedicated connection pool, +// otherwise we might run into ORA-21700: object does not exist or is marked for delete +// because the JDBC connection or database session caches something that should have been invalidated +@ServiceRegistry(settings = @Setting(name = AvailableSettings.CONNECTION_PROVIDER, value = "")) +public class ArrayPrependTest { + + @BeforeEach + public void prepareData(SessionFactoryScope scope) { + scope.inTransaction( em -> { + em.persist( new EntityWithArrays( 1L, new String[]{} ) ); + em.persist( new EntityWithArrays( 2L, new String[]{ "abc", null, "def" } ) ); + em.persist( new EntityWithArrays( 3L, null ) ); + } ); + } + + @AfterEach + public void cleanup(SessionFactoryScope scope) { + scope.inTransaction( em -> { + em.createMutationQuery( "delete from EntityWithArrays" ).executeUpdate(); + } ); + } + + @Test + public void testPrepend(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-prepend-example[] + List results = em.createQuery( "select e.id, array_prepend('xyz', e.theArray) from EntityWithArrays e order by e.id", Tuple.class ) + .getResultList(); + //end::hql-array-prepend-example[] + assertEquals( 3, results.size() ); + assertEquals( 1L, results.get( 0 ).get( 0 ) ); + assertArrayEquals( new String[]{ "xyz" }, results.get( 0 ).get( 1, String[].class ) ); + assertEquals( 2L, results.get( 1 ).get( 0 ) ); + assertArrayEquals( new String[]{ "xyz", "abc", null, "def" }, results.get( 1 ).get( 1, String[].class ) ); + assertEquals( 3L, results.get( 2 ).get( 0 ) ); + assertNull( results.get( 2 ).get( 1, String[].class ) ); + } ); + } + + @Test + public void testPrependNull(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select e.id, array_prepend(null, e.theArray) from EntityWithArrays e order by e.id", Tuple.class ) + .getResultList(); + assertEquals( 3, results.size() ); + assertEquals( 1L, results.get( 0 ).get( 0 ) ); + assertArrayEquals( new String[]{ null }, results.get( 0 ).get( 1, String[].class ) ); + assertEquals( 2L, results.get( 1 ).get( 0 ) ); + assertArrayEquals( new String[]{ null, "abc", null, "def" }, results.get( 1 ).get( 1, String[].class ) ); + assertEquals( 3L, results.get( 2 ).get( 0 ) ); + assertNull( results.get( 2 ).get( 1, String[].class ) ); + } ); + } + +}