From c3e1815486f216e078deb0d0b5898cf19beb1559 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 30 Oct 2023 18:05:18 +0100 Subject: [PATCH] HHH-17355 Add array_positions and array_positions_list functions --- .../chapters/query/hql/QueryLanguage.adoc | 16 +++ .../dialect/CockroachLegacyDialect.java | 1 + .../community/dialect/H2LegacyDialect.java | 2 + .../community/dialect/HSQLLegacyDialect.java | 1 + .../dialect/OracleLegacyDialect.java | 1 + .../dialect/PostgreSQLLegacyDialect.java | 1 + .../hibernate/dialect/CockroachDialect.java | 1 + .../java/org/hibernate/dialect/H2Dialect.java | 2 + .../org/hibernate/dialect/HSQLDialect.java | 1 + .../dialect/OracleArrayJdbcType.java | 26 +++++ .../org/hibernate/dialect/OracleDialect.java | 1 + .../hibernate/dialect/PostgreSQLDialect.java | 1 + .../function/CommonFunctionFactory.java | 71 ++++++++++++ .../array/AbstractArrayPositionsFunction.java | 52 +++++++++ .../array/ArrayRemoveIndexUnnestFunction.java | 2 +- .../array/ArrayReplaceUnnestFunction.java | 2 +- .../array/ArraySliceUnnestFunction.java | 6 +- .../array/H2ArrayPositionFunction.java | 52 +++++++++ .../array/H2ArrayPositionsFunction.java | 52 +++++++++ .../function/array/H2ArrayRemoveFunction.java | 2 +- .../array/H2ArrayRemoveIndexFunction.java | 2 +- .../array/H2ArrayReplaceFunction.java | 2 +- .../array/HSQLArrayPositionFunction.java | 2 +- .../array/HSQLArrayPositionsFunction.java | 43 +++++++ .../array/HSQLArrayRemoveFunction.java | 2 +- .../array/OracleArrayPositionsFunction.java | 39 +++++++ .../PostgreSQLArrayPositionsFunction.java | 41 +++++++ .../type/internal/ParameterizedTypeImpl.java | 97 ++++++++++++++++ .../hibernate/type/spi/TypeConfiguration.java | 16 ++- .../function/array/ArrayPositionTest.java | 3 - .../function/array/ArrayPositionsTest.java | 106 ++++++++++++++++++ 31 files changed, 632 insertions(+), 14 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayPositionsFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayPositionFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayPositionsFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionsFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayPositionsFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayPositionsFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/internal/ParameterizedTypeImpl.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionsTest.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 d8d091f413..44d72a1142 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -1120,6 +1120,8 @@ The following functions deal with SQL array types, which are not supported on ev | `array()` | Creates an array based on the passed arguments | `array_agg()` | Aggregates row values into an array | `array_position()` | Determines the position of an element in an array +| `array_positions()` | Determines all positions of an element in an array +| `array_positions_list()` | Like `array_positions`, but returns the result as `List` | `array_length()` | Determines the length of an array | `array_concat()` | Concatenates array with each other in order | `array_prepend()` | Prepends element to array @@ -1175,6 +1177,20 @@ include::{array-example-dir-hql}/ArrayPositionTest.java[tags=hql-array-position- ---- ==== +[[hql-array-positions-functions]] +===== `array_positions()` and `array_positions_list()` + +Returns an `int[]` of 1-based positions of matching elements in the array. Returns an empty array if the element is not found and `null` if the array is `null`. +To retrieve the result as `List`, use the `array_positions_list()` function. + +[[hql-array-positions-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayPositionsTest.java[tags=hql-array-positions-example] +---- +==== + [[hql-array-length-functions]] ===== `array_length()` 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 b832251488..03abee3ec2 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 @@ -464,6 +464,7 @@ public class CockroachLegacyDialect extends Dialect { functionFactory.array_postgresql(); functionFactory.arrayAggregate(); functionFactory.arrayPosition_postgresql(); + functionFactory.arrayPositions_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); functionFactory.arrayPrepend_postgresql(); 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 08115f72f0..2559cd9762 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 @@ -372,6 +372,8 @@ public class H2LegacyDialect extends Dialect { functionFactory.listagg( null ); functionFactory.array(); functionFactory.arrayAggregate(); + functionFactory.arrayPosition_h2( getMaximumArraySize() ); + functionFactory.arrayPositions_h2( getMaximumArraySize() ); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); functionFactory.arrayPrepend_operator(); 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 45b7b9276c..13091113a4 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 @@ -250,6 +250,7 @@ public class HSQLLegacyDialect extends Dialect { functionFactory.array_hsql(); functionFactory.arrayAggregate(); functionFactory.arrayPosition_hsql(); + functionFactory.arrayPositions_hsql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); functionFactory.arrayPrepend_operator(); 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 b5e0fd7f33..9a245ba37f 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 @@ -286,6 +286,7 @@ public class OracleLegacyDialect extends Dialect { functionFactory.array_oracle(); functionFactory.arrayAggregate_jsonArrayagg(); functionFactory.arrayPosition_oracle(); + functionFactory.arrayPositions_oracle(); functionFactory.arrayLength_oracle(); functionFactory.arrayConcat_oracle(); functionFactory.arrayPrepend_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 5eb28790da..7df39adcbc 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 @@ -584,6 +584,7 @@ public class PostgreSQLLegacyDialect extends Dialect { functionFactory.array_postgresql(); functionFactory.arrayAggregate(); functionFactory.arrayPosition_postgresql(); + functionFactory.arrayPositions_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); functionFactory.arrayPrepend_postgresql(); 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 ced93af8cb..6dc09d60f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -451,6 +451,7 @@ public class CockroachDialect extends Dialect { functionFactory.array_postgresql(); functionFactory.arrayAggregate(); functionFactory.arrayPosition_postgresql(); + functionFactory.arrayPositions_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); functionFactory.arrayPrepend_postgresql(); 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 371ea03302..34f077c6ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -311,6 +311,8 @@ public class H2Dialect extends Dialect { functionFactory.hypotheticalOrderedSetAggregates(); functionFactory.array(); functionFactory.arrayAggregate(); + functionFactory.arrayPosition_h2( getMaximumArraySize() ); + functionFactory.arrayPositions_h2( getMaximumArraySize() ); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); functionFactory.arrayPrepend_operator(); 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 f390436314..f856ac64a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -190,6 +190,7 @@ public class HSQLDialect extends Dialect { functionFactory.array_hsql(); functionFactory.arrayAggregate(); functionFactory.arrayPosition_hsql(); + functionFactory.arrayPositions_hsql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); functionFactory.arrayPrepend_operator(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java index 44dd0b5b92..10c2151fa6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java @@ -515,6 +515,32 @@ public class OracleArrayJdbcType extends ArrayJdbcType { false ) ); + database.addAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + arrayTypeName + "_positions", + database.getDefaultNamespace(), + new String[]{ + "create or replace function " + arrayTypeName + "_positions(arr in " + arrayTypeName + + ", elem in " + getRawTypeName( elementType ) + ") return sdo_ordinate_array deterministic is " + + "res sdo_ordinate_array:=sdo_ordinate_array(); begin " + + "if arr is null then return null; end if; " + + "if elem is null then " + + "for i in 1 .. arr.count loop " + + "if arr(i) is null then res.extend; res(res.last):=i; end if; " + + "end loop; " + + "else " + + "for i in 1 .. arr.count loop " + + "if arr(i)=elem then res.extend; res(res.last):=i; end if; " + + "end loop; " + + "end if; " + + "return res; " + + "end;" + }, + new String[] { "drop function " + arrayTypeName + "_positions" }, + emptySet(), + false + ) + ); } protected String createOrReplaceConcatFunction(String arrayTypeName) { 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 01c39a514c..fbfea8db96 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -315,6 +315,7 @@ public class OracleDialect extends Dialect { functionFactory.array_oracle(); functionFactory.arrayAggregate_jsonArrayagg(); functionFactory.arrayPosition_oracle(); + functionFactory.arrayPositions_oracle(); functionFactory.arrayLength_oracle(); functionFactory.arrayConcat_oracle(); functionFactory.arrayPrepend_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 270400a780..6aa5a3ee33 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -632,6 +632,7 @@ public class PostgreSQLDialect extends Dialect { functionFactory.array_postgresql(); functionFactory.arrayAggregate(); functionFactory.arrayPosition_postgresql(); + functionFactory.arrayPositions_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); functionFactory.arrayPrepend_postgresql(); 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 e0ea034794..77392095da 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 @@ -6,9 +6,12 @@ */ package org.hibernate.dialect.function; +import java.lang.reflect.Type; import java.util.Date; import java.util.Arrays; +import java.util.List; +import org.hibernate.annotations.common.reflection.java.generics.ParameterizedTypeImpl; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.dialect.Dialect; @@ -33,6 +36,8 @@ import org.hibernate.dialect.function.array.ElementViaArrayArgumentReturnTypeRes import org.hibernate.dialect.function.array.H2ArrayContainsFunction; import org.hibernate.dialect.function.array.H2ArrayFillFunction; import org.hibernate.dialect.function.array.H2ArrayOverlapsFunction; +import org.hibernate.dialect.function.array.H2ArrayPositionFunction; +import org.hibernate.dialect.function.array.H2ArrayPositionsFunction; import org.hibernate.dialect.function.array.H2ArrayRemoveFunction; import org.hibernate.dialect.function.array.H2ArrayRemoveIndexFunction; import org.hibernate.dialect.function.array.H2ArrayReplaceFunction; @@ -40,6 +45,7 @@ import org.hibernate.dialect.function.array.H2ArraySetFunction; import org.hibernate.dialect.function.array.HSQLArrayConstructorFunction; import org.hibernate.dialect.function.array.HSQLArrayFillFunction; import org.hibernate.dialect.function.array.HSQLArrayPositionFunction; +import org.hibernate.dialect.function.array.HSQLArrayPositionsFunction; import org.hibernate.dialect.function.array.HSQLArrayRemoveFunction; import org.hibernate.dialect.function.array.HSQLArraySetFunction; import org.hibernate.dialect.function.array.OracleArrayConcatElementFunction; @@ -49,6 +55,7 @@ import org.hibernate.dialect.function.array.OracleArrayOverlapsFunction; import org.hibernate.dialect.function.array.OracleArrayGetFunction; import org.hibernate.dialect.function.array.OracleArrayLengthFunction; import org.hibernate.dialect.function.array.OracleArrayPositionFunction; +import org.hibernate.dialect.function.array.OracleArrayPositionsFunction; import org.hibernate.dialect.function.array.OracleArrayRemoveFunction; import org.hibernate.dialect.function.array.OracleArrayRemoveIndexFunction; import org.hibernate.dialect.function.array.OracleArrayReplaceFunction; @@ -63,6 +70,7 @@ import org.hibernate.dialect.function.array.PostgreSQLArrayConstructorFunction; import org.hibernate.dialect.function.array.OracleArrayAggEmulation; import org.hibernate.dialect.function.array.OracleArrayConstructorFunction; import org.hibernate.dialect.function.array.OracleArrayContainsFunction; +import org.hibernate.dialect.function.array.PostgreSQLArrayPositionsFunction; import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; @@ -2738,6 +2746,13 @@ public class CommonFunctionFactory { functionRegistry.register( "array_position", new PostgreSQLArrayPositionFunction( typeConfiguration ) ); } + /** + * H2 array_position() function + */ + public void arrayPosition_h2(int maximumArraySize) { + functionRegistry.register( "array_position", new H2ArrayPositionFunction( maximumArraySize, typeConfiguration ) ); + } + /** * HSQL array_position() function */ @@ -2752,6 +2767,62 @@ public class CommonFunctionFactory { functionRegistry.register( "array_position", new OracleArrayPositionFunction( typeConfiguration ) ); } + /** + * CockroachDB and PostgreSQL array_positions() function + */ + public void arrayPositions_postgresql() { + functionRegistry.register( + "array_positions", + new PostgreSQLArrayPositionsFunction( false, typeConfiguration ) + ); + functionRegistry.register( + "array_positions_list", + new PostgreSQLArrayPositionsFunction( true, typeConfiguration ) + ); + } + + /** + * H2 array_positions() function + */ + public void arrayPositions_h2(int maximumArraySize) { + functionRegistry.register( + "array_positions", + new H2ArrayPositionsFunction( false, maximumArraySize, typeConfiguration ) + ); + functionRegistry.register( + "array_positions_list", + new H2ArrayPositionsFunction( true, maximumArraySize, typeConfiguration ) + ); + } + + /** + * HSQL array_positions() function + */ + public void arrayPositions_hsql() { + functionRegistry.register( + "array_positions", + new HSQLArrayPositionsFunction( false, typeConfiguration ) + ); + functionRegistry.register( + "array_positions_list", + new HSQLArrayPositionsFunction( true, typeConfiguration ) + ); + } + + /** + * Oracle array_positions() function + */ + public void arrayPositions_oracle() { + functionRegistry.register( + "array_positions", + new OracleArrayPositionsFunction( false, typeConfiguration ) + ); + functionRegistry.register( + "array_positions_list", + new OracleArrayPositionsFunction( true, typeConfiguration ) + ); + } + /** * H2, HSQLDB, CockroachDB and PostgreSQL array_length() function */ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayPositionsFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayPositionsFunction.java new file mode 100644 index 0000000000..1ce45a4636 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayPositionsFunction.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.dialect.function.array; + +import java.lang.reflect.Type; +import java.util.List; + +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; +import org.hibernate.query.sqm.produce.function.FunctionParameterType; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.type.internal.ParameterizedTypeImpl; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Encapsulates the validator, return type and argument type resolvers for the array_positions functions. + * Subclasses only have to implement the rendering. + */ +public abstract class AbstractArrayPositionsFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public AbstractArrayPositionsFunction(boolean list, TypeConfiguration typeConfiguration) { + super( + "array_positions" + ( list ? "_list" : "" ), + new ArgumentTypesValidator( + StandardArgumentsValidators.composite( + StandardArgumentsValidators.exactly( 2 ), + ArrayAndElementArgumentValidator.DEFAULT_INSTANCE + ), + FunctionParameterType.ANY, + FunctionParameterType.ANY + ), + StandardFunctionReturnTypeResolvers.invariant( + typeConfiguration.standardBasicTypeForJavaType( + list + ? new ParameterizedTypeImpl( List.class, new Type[]{ Integer.class }, null ) + : int[].class + ) + ), + ArrayAndElementArgumentTypeResolver.DEFAULT_INSTANCE + ); + } + + @Override + public String getArgumentListSignature() { + return "(ARRAY array, OBJECT element)"; + } +} 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 e6fcee19df..7fc6913c30 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 @@ -54,7 +54,7 @@ public class ArrayRemoveIndexUnnestFunction extends AbstractSqmSelfRenderingFunc final Expression indexExpression = (Expression) sqlAstArguments.get( 1 ); sqlAppender.append( "case when "); arrayExpression.accept( walker ); - sqlAppender.append( " is null then null else coalesce((select array_agg(t.val) from unnest(" ); + sqlAppender.append( " is not null then coalesce((select array_agg(t.val) from unnest(" ); arrayExpression.accept( walker ); sqlAppender.append( ") with ordinality t(val,idx) where t.idx is distinct from " ); indexExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayReplaceUnnestFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayReplaceUnnestFunction.java index 3a66a99ac3..df5397a4af 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayReplaceUnnestFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayReplaceUnnestFunction.java @@ -44,7 +44,7 @@ public class ArrayReplaceUnnestFunction extends AbstractSqmSelfRenderingFunction final Expression newExpression = (Expression) sqlAstArguments.get( 2 ); sqlAppender.append( "case when "); arrayExpression.accept( walker ); - sqlAppender.append( " is null then null else coalesce((select array_agg(case when t.val is not distinct from " ); + sqlAppender.append( " is not null then coalesce((select array_agg(case when t.val is not distinct from " ); oldExpression.accept( walker ); sqlAppender.append( " then " ); newExpression.accept( walker ); 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 98eee1cad9..d6e693d1e6 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 @@ -55,11 +55,11 @@ public class ArraySliceUnnestFunction extends AbstractSqmSelfRenderingFunctionDe final Expression endIndexExpression = (Expression) sqlAstArguments.get( 2 ); sqlAppender.append( "case when "); arrayExpression.accept( walker ); - sqlAppender.append( " is null or "); + sqlAppender.append( " is not null and "); startIndexExpression.accept( walker ); - sqlAppender.append( " is null or "); + sqlAppender.append( " is not null and "); endIndexExpression.accept( walker ); - sqlAppender.append( " is null then null else coalesce((select array_agg(t.val) from unnest(" ); + sqlAppender.append( " is not null then coalesce((select array_agg(t.val) from unnest(" ); arrayExpression.accept( walker ); sqlAppender.append( ") with ordinality t(val,idx) where t.idx between " ); startIndexExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayPositionFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayPositionFunction.java new file mode 100644 index 0000000000..ff0e07945f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayPositionFunction.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.query.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * H2 requires a very special emulation, because {@code unnest} is pretty much useless, + * due to https://github.com/h2database/h2database/issues/1815. + * This emulation uses {@code array_get}, {@code array_length} and {@code system_range} functions to roughly achieve the same. + */ +public class H2ArrayPositionFunction extends AbstractArrayPositionFunction { + + private final int maximumArraySize; + + public H2ArrayPositionFunction(int maximumArraySize, TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + this.maximumArraySize = maximumArraySize; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression elementExpression = (Expression) sqlAstArguments.get( 1 ); + sqlAppender.append( "case when "); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then coalesce((select i.idx from system_range(1," ); + sqlAppender.append( Integer.toString( maximumArraySize ) ); + sqlAppender.append( ") i(idx) where i.idx<=coalesce(cardinality("); + arrayExpression.accept( walker ); + sqlAppender.append("),0) and array_get("); + arrayExpression.accept( walker ); + sqlAppender.append( ",i.idx) is not distinct from " ); + elementExpression.accept( walker ); + sqlAppender.append( " order by i.idx fetch first 1 row only),0) end" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayPositionsFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayPositionsFunction.java new file mode 100644 index 0000000000..084680234b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayPositionsFunction.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.query.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * H2 requires a very special emulation, because {@code unnest} is pretty much useless, + * due to https://github.com/h2database/h2database/issues/1815. + * This emulation uses {@code array_get}, {@code array_length} and {@code system_range} functions to roughly achieve the same. + */ +public class H2ArrayPositionsFunction extends AbstractArrayPositionsFunction { + + private final int maximumArraySize; + + public H2ArrayPositionsFunction(boolean list, int maximumArraySize, TypeConfiguration typeConfiguration) { + super( list, typeConfiguration ); + this.maximumArraySize = maximumArraySize; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression elementExpression = (Expression) sqlAstArguments.get( 1 ); + sqlAppender.append( "case when "); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then coalesce((select array_agg(i.idx) from system_range(1," ); + sqlAppender.append( Integer.toString( maximumArraySize ) ); + sqlAppender.append( ") i(idx) where i.idx<=coalesce(cardinality("); + arrayExpression.accept( walker ); + sqlAppender.append("),0) and array_get("); + arrayExpression.accept( walker ); + sqlAppender.append( ",i.idx) is not distinct from " ); + elementExpression.accept( walker ); + sqlAppender.append( "),array[]) end" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveFunction.java index c936776a67..feda5a041d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveFunction.java @@ -37,7 +37,7 @@ public class H2ArrayRemoveFunction extends AbstractArrayRemoveFunction { final Expression elementExpression = (Expression) sqlAstArguments.get( 1 ); sqlAppender.append( "case when "); arrayExpression.accept( walker ); - sqlAppender.append( " is null then null else coalesce((select array_agg(array_get("); + sqlAppender.append( " is not null then coalesce((select array_agg(array_get("); arrayExpression.accept( walker ); sqlAppender.append(",i.idx)) from system_range(1," ); sqlAppender.append( Integer.toString( maximumArraySize ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveIndexFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveIndexFunction.java index 9d85e911a6..8b28f46cf0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveIndexFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveIndexFunction.java @@ -38,7 +38,7 @@ public class H2ArrayRemoveIndexFunction extends ArrayRemoveIndexUnnestFunction { final Expression indexExpression = (Expression) sqlAstArguments.get( 1 ); sqlAppender.append( "case when "); arrayExpression.accept( walker ); - sqlAppender.append( " is null then null else coalesce((select array_agg(array_get("); + sqlAppender.append( " is not null then coalesce((select array_agg(array_get("); arrayExpression.accept( walker ); sqlAppender.append(",i.idx)) from system_range(1," ); sqlAppender.append( Integer.toString( maximumArraySize ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayReplaceFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayReplaceFunction.java index 44f1c0a996..62d46a4824 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayReplaceFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayReplaceFunction.java @@ -38,7 +38,7 @@ public class H2ArrayReplaceFunction extends ArrayReplaceUnnestFunction { final Expression newExpression = (Expression) sqlAstArguments.get( 2 ); sqlAppender.append( "case when "); arrayExpression.accept( walker ); - sqlAppender.append( " is null then null else coalesce((select array_agg(case when array_get("); + sqlAppender.append( " is not null then coalesce((select array_agg(case when array_get("); arrayExpression.accept( walker ); sqlAppender.append(",i.idx) is not distinct from "); oldExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionFunction.java index ed38287f2c..e936a19ac0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionFunction.java @@ -42,6 +42,6 @@ public class HSQLArrayPositionFunction extends AbstractArrayPositionFunction { sqlAppender.append( " and t.idx>=" ); sqlAstArguments.get( 2 ).accept( walker ); } - sqlAppender.append( "),0) end" ); + sqlAppender.append( " order by t.idx fetch first 1 row only),0) end" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionsFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionsFunction.java new file mode 100644 index 0000000000..85149b6378 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionsFunction.java @@ -0,0 +1,43 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.query.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * HSQLDB has a special syntax. + */ +public class HSQLArrayPositionsFunction extends AbstractArrayPositionsFunction { + + public HSQLArrayPositionsFunction(boolean list, TypeConfiguration typeConfiguration) { + super( list, typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression elementExpression = (Expression) sqlAstArguments.get( 1 ); + sqlAppender.append( "case when " ); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then coalesce((select array_agg(t.idx) from unnest("); + arrayExpression.accept( walker ); + sqlAppender.append(") with ordinality t(val,idx) where t.val is not distinct from " ); + elementExpression.accept( walker ); + sqlAppender.append( "),cast(array[] as integer array)) end" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayRemoveFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayRemoveFunction.java index 32e8d70d30..3416b8abce 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayRemoveFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayRemoveFunction.java @@ -32,7 +32,7 @@ public class HSQLArrayRemoveFunction extends AbstractArrayRemoveFunction { final Expression elementExpression = (Expression) sqlAstArguments.get( 1 ); sqlAppender.append( "case when "); arrayExpression.accept( walker ); - sqlAppender.append( " is null then null else coalesce((select array_agg(t.val) from unnest(" ); + sqlAppender.append( " is not null then coalesce((select array_agg(t.val) from unnest(" ); arrayExpression.accept( walker ); sqlAppender.append( ") with ordinality t(val,idx) where t.val is distinct from " ); elementExpression.accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayPositionsFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayPositionsFunction.java new file mode 100644 index 0000000000..5f5b3042ee --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayPositionsFunction.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.query.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.type.spi.TypeConfiguration; + +public class OracleArrayPositionsFunction extends AbstractArrayPositionsFunction { + + public OracleArrayPositionsFunction(boolean list, TypeConfiguration typeConfiguration) { + super( list, typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker ); + sqlAppender.appendSql( arrayTypeName ); + sqlAppender.append( "_positions(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ',' ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( ")" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayPositionsFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayPositionsFunction.java new file mode 100644 index 0000000000..156dcb51af --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayPositionsFunction.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.query.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * PostgreSQL variant of the function. + */ +public class PostgreSQLArrayPositionsFunction extends AbstractArrayPositionsFunction { + + public PostgreSQLArrayPositionsFunction(boolean list, TypeConfiguration typeConfiguration) { + super( list, typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression elementExpression = (Expression) sqlAstArguments.get( 1 ); + sqlAppender.append( "array_positions(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ',' ); + elementExpression.accept( walker ); + sqlAppender.append( ')' ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/internal/ParameterizedTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/type/internal/ParameterizedTypeImpl.java new file mode 100644 index 0000000000..a21377a003 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/internal/ParameterizedTypeImpl.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 . + */ +package org.hibernate.type.internal; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Objects; +import java.util.StringJoiner; + +public class ParameterizedTypeImpl implements ParameterizedType { + + private final Type[] substTypeArgs; + private final Type rawType; + private final Type ownerType; + + public ParameterizedTypeImpl(Type rawType, Type[] substTypeArgs, Type ownerType) { + this.substTypeArgs = substTypeArgs; + this.rawType = rawType; + this.ownerType = ownerType; + } + + public Type[] getActualTypeArguments() { + return substTypeArgs; + } + + public Type getRawType() { + return rawType; + } + + public Type getOwnerType() { + return ownerType; + } + + @Override + public boolean equals(Object obj) { + if ( !( obj instanceof ParameterizedType ) ) { + return false; + } + ParameterizedType other = (ParameterizedType) obj; + return Objects.equals( getOwnerType(), other.getOwnerType() ) + && Objects.equals( getRawType(), other.getRawType() ) + && Arrays.equals( getActualTypeArguments(), other.getActualTypeArguments() ); + } + + @Override + public int hashCode() { + return Arrays.hashCode( getActualTypeArguments() ) + ^ Objects.hashCode( getOwnerType() ) + ^ Objects.hashCode( getRawType() ); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + if ( ownerType != null ) { + sb.append( ownerType.getTypeName() ); + + sb.append( "$" ); + + if ( ownerType instanceof ParameterizedType ) { + // Find simple name of nested type by removing the + // shared prefix with owner. + sb.append( + rawType.getTypeName().replace( + ( (ParameterizedType) ownerType ).getRawType().getTypeName() + "$", + "" + ) + ); + } + else if ( rawType instanceof Class ) { + sb.append( ( (Class) rawType ).getSimpleName() ); + } + else { + sb.append( rawType.getTypeName() ); + } + } + else { + sb.append( rawType.getTypeName() ); + } + + if ( substTypeArgs != null ) { + final StringJoiner sj = new StringJoiner( ", ", "<", ">" ); + sj.setEmptyValue( "" ); + for ( Type t : substTypeArgs ) { + sj.add( t.getTypeName() ); + } + sb.append( sj ); + } + + return sb.toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java index fe0f78295e..e5180d3446 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java @@ -37,7 +37,6 @@ import org.hibernate.Internal; import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; import org.hibernate.TimeZoneStorageStrategy; -import org.hibernate.annotations.common.reflection.java.generics.ParameterizedTypeImpl; import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.spi.BasicTypeRegistration; @@ -74,6 +73,7 @@ import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import org.hibernate.type.internal.BasicTypeImpl; +import org.hibernate.type.internal.ParameterizedTypeImpl; import jakarta.persistence.TemporalType; @@ -716,6 +716,20 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable { ); } + public BasicType standardBasicTypeForJavaType(Type javaType) { + if ( javaType == null ) { + return null; + } + + return standardBasicTypeForJavaType( + javaType, + javaTypeDescriptor -> new BasicTypeImpl<>( + javaTypeDescriptor, + javaTypeDescriptor.getRecommendedJdbcType( getCurrentBaseSqlTypeIndicators() ) + ) + ); + } + public BasicType standardBasicTypeForJavaType( Class javaType, Function, BasicType> creator) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionTest.java index f828c884e1..9f04ed5a6c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionTest.java @@ -9,7 +9,6 @@ package org.hibernate.orm.test.function.array; import java.util.List; import org.hibernate.cfg.AvailableSettings; -import org.hibernate.dialect.H2Dialect; import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DomainModel; @@ -18,7 +17,6 @@ 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.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,7 +33,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; // 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 = "")) -@SkipForDialect(dialectClass = H2Dialect.class, reason = "H2 does not have an array_position function") public class ArrayPositionTest { @BeforeEach diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionsTest.java new file mode 100644 index 0000000000..ada74b7d74 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionsTest.java @@ -0,0 +1,106 @@ +/* + * 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 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 ArrayPositionsTest { + + @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", "abc" } ) ); + em.persist( new EntityWithArrays( 3L, null ) ); + } ); + } + + @AfterEach + public void cleanup(SessionFactoryScope scope) { + scope.inTransaction( em -> { + em.createMutationQuery( "delete from EntityWithArrays" ).executeUpdate(); + } ); + } + + @Test + public void testPositions(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-positions-example[] + List results = em.createQuery( "select array_positions(e.theArray, 'abc') from EntityWithArrays e order by e.id", int[].class ) + .getResultList(); + //end::hql-array-positions-example[] + assertEquals( 3, results.size() ); + assertArrayEquals( new int[0], results.get( 0 ) ); + assertArrayEquals( new int[]{ 1, 4 }, results.get( 1 ) ); + assertNull( results.get( 2 ) ); + } ); + } + + @Test + public void testPositionsNotFound(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select array_positions(e.theArray, 'xyz') from EntityWithArrays e order by e.id", int[].class ) + .getResultList(); + assertEquals( 3, results.size() ); + assertArrayEquals( new int[0], results.get( 0 ) ); + assertArrayEquals( new int[0], results.get( 1 ) ); + assertNull( results.get( 2 ) ); + } ); + } + + @Test + public void testPositionsNull(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select array_positions(e.theArray, null) from EntityWithArrays e order by e.id", int[].class ) + .getResultList(); + assertEquals( 3, results.size() ); + assertArrayEquals( new int[0], results.get( 0 ) ); + assertArrayEquals( new int[]{ 2 }, results.get( 1 ) ); + assertNull( results.get( 2 ) ); + } ); + } + + @Test + public void testPositionsList(SessionFactoryScope scope) { + scope.inSession( em -> { + List> results = em.createQuery( "select array_positions_list(e.theArray, null) from EntityWithArrays e order by e.id" ) + .getResultList(); + assertEquals( 3, results.size() ); + assertEquals( List.of(), results.get( 0 ) ); + assertEquals( List.of( 2 ), results.get( 1 ) ); + assertNull( results.get( 2 ) ); + } ); + } + +}