From 865365e6ec74c141a63856711bfa3ad991d18453 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 24 Oct 2023 12:06:00 +0200 Subject: [PATCH] HHH-17335 Add array_remove function --- .../chapters/query/hql/QueryLanguage.adoc | 14 +++ .../dialect/CockroachLegacyDialect.java | 1 + .../community/dialect/H2LegacyDialect.java | 1 + .../community/dialect/HSQLLegacyDialect.java | 1 + .../dialect/OracleLegacyDialect.java | 1 + .../dialect/PostgreSQLLegacyDialect.java | 1 + .../hibernate/dialect/CockroachDialect.java | 1 + .../java/org/hibernate/dialect/H2Dialect.java | 1 + .../org/hibernate/dialect/HSQLDialect.java | 1 + .../dialect/OracleArrayJdbcType.java | 26 +++++ .../org/hibernate/dialect/OracleDialect.java | 1 + .../hibernate/dialect/PostgreSQLDialect.java | 1 + .../function/CommonFunctionFactory.java | 43 ++++++- .../array/AbstractArrayRemoveFunction.java | 29 +++++ .../function/array/H2ArrayRemoveFunction.java | 49 ++++++++ .../array/HSQLArrayRemoveFunction.java | 39 +++++++ .../array/OracleArrayGetFunction.java | 14 +-- .../array/OracleArrayRemoveFunction.java | 40 +++++++ .../array/OracleArraySetFunction.java | 14 +-- .../test/function/array/ArrayRemoveTest.java | 105 ++++++++++++++++++ 20 files changed, 362 insertions(+), 21 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayRemoveFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayRemoveFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveFunction.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayRemoveTest.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 20f5e56854..be39bb7b0d 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -1129,6 +1129,7 @@ The following functions deal with SQL array types, which are not supported on ev | `array_contains_any_nullable()` | Determines if one array holds at least one element of another array, supporting null elements | `array_get()` | Accesses the element of an array by index | `array_set()` | Creates array copy with given element at given index +| `array_remove()` | Creates array copy with given element removed |=== ===== `array()` @@ -1274,6 +1275,19 @@ include::{array-example-dir-hql}/ArraySetTest.java[tags=hql-array-set-example] ---- ==== +[[hql-array-remove-functions]] +===== `array_remove()` + +Returns an array copy with the given element removed from the array. Allows removal of `null` elements. + +[[hql-array-remove-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayRemoveTest.java[tags=hql-array-remove-example] +---- +==== + [[hql-user-defined-functions]] ==== Native and user-defined functions 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 76a64b3c73..48b6143002 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 @@ -474,6 +474,7 @@ public class CockroachLegacyDialect extends Dialect { functionFactory.arrayContainsAnyNullable_operator(); functionFactory.arrayGet_bracket(); functionFactory.arraySet_unnest(); + functionFactory.arrayRemove_unnest(); functionContributions.getFunctionRegistry().register( "trunc", 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 42b26febb8..c59c99f36f 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 @@ -382,6 +382,7 @@ public class H2LegacyDialect extends Dialect { functionFactory.arrayContainsAnyNullable_h2(); functionFactory.arrayGet_h2(); functionFactory.arraySet_h2(); + functionFactory.arrayRemove_h2(); } else { // Use group_concat until 2.x as listagg was buggy 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 af12068d28..c6cc5aef3a 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 @@ -260,6 +260,7 @@ public class HSQLLegacyDialect extends Dialect { functionFactory.arrayContainsAnyNullable_hsql(); functionFactory.arrayGet_unnest(); functionFactory.arraySet_hsql(); + functionFactory.arrayRemove_hsql(); } @Override 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 de1434bd2d..864ed137e0 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 @@ -296,6 +296,7 @@ public class OracleLegacyDialect extends Dialect { functionFactory.arrayContainsAnyNullable_oracle(); functionFactory.arrayGet_oracle(); functionFactory.arraySet_oracle(); + functionFactory.arrayRemove_oracle(); } @Override 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 f7fb1f1417..eb375996e8 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 @@ -594,6 +594,7 @@ public class PostgreSQLLegacyDialect extends Dialect { functionFactory.arrayContainsAnyNullable_operator(); functionFactory.arrayGet_bracket(); functionFactory.arraySet_unnest(); + functionFactory.arrayRemove_unnest(); if ( getVersion().isSameOrAfter( 9, 4 ) ) { functionFactory.makeDateTimeTimestamp(); 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 00af107bce..54ea6453aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -461,6 +461,7 @@ public class CockroachDialect extends Dialect { functionFactory.arrayContainsAnyNullable_operator(); functionFactory.arrayGet_bracket(); functionFactory.arraySet_unnest(); + functionFactory.arrayRemove_unnest(); functionContributions.getFunctionRegistry().register( "trunc", 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 0e988396b2..78fbcb5489 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -322,6 +322,7 @@ public class H2Dialect extends Dialect { functionFactory.arrayContainsAnyNullable_h2(); functionFactory.arrayGet_h2(); functionFactory.arraySet_h2(); + functionFactory.arrayRemove_h2(); } @Override 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 d8969df3ab..3a6fd0cd85 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -200,6 +200,7 @@ public class HSQLDialect extends Dialect { functionFactory.arrayContainsAnyNullable_hsql(); functionFactory.arrayGet_unnest(); functionFactory.arraySet_hsql(); + functionFactory.arrayRemove_hsql(); } @Override 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 729f981a6e..9299e4a809 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java @@ -373,6 +373,32 @@ public class OracleArrayJdbcType extends ArrayJdbcType { false ) ); + database.addAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + arrayTypeName + "_remove", + database.getDefaultNamespace(), + new String[]{ + "create or replace function " + arrayTypeName + "_remove(arr in " + arrayTypeName + + ", elem in " + getRawTypeName( elementType ) + ") return " + arrayTypeName + " deterministic is " + + "res " + arrayTypeName + ":=" + arrayTypeName + "(); 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 not null then res.extend; res(res.last) := arr(i); end if; " + + "end loop; " + + "else " + + "for i in 1 .. arr.count loop " + + "if arr(i) is null or arr(i)<>elem then res.extend; res(res.last) := arr(i); end if; " + + "end loop; " + + "end if; " + + "return res; " + + "end;" + }, + new String[] { "drop function " + arrayTypeName + "_remove" }, + 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 5cf2bba02b..66d18dc2dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -325,6 +325,7 @@ public class OracleDialect extends Dialect { functionFactory.arrayContainsAnyNullable_oracle(); functionFactory.arrayGet_oracle(); functionFactory.arraySet_oracle(); + functionFactory.arrayRemove_oracle(); } @Override 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 04cdced830..3644078310 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -642,6 +642,7 @@ public class PostgreSQLDialect extends Dialect { functionFactory.arrayContainsAnyNullable_operator(); functionFactory.arrayGet_bracket(); functionFactory.arraySet_unnest(); + functionFactory.arrayRemove_unnest(); functionFactory.makeDateTimeTimestamp(); // Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions 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 4d6f617a3d..6658e3f742 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 @@ -23,10 +23,13 @@ import org.hibernate.dialect.function.array.ArrayContainsOperatorFunction; import org.hibernate.dialect.function.array.ArrayContainsQuantifiedUnnestFunction; import org.hibernate.dialect.function.array.ArrayGetUnnestFunction; import org.hibernate.dialect.function.array.ArraySetUnnestFunction; +import org.hibernate.dialect.function.array.ArrayViaArgumentReturnTypeResolver; import org.hibernate.dialect.function.array.ElementViaArrayArgumentReturnTypeResolver; import org.hibernate.dialect.function.array.H2ArrayContainsQuantifiedEmulation; +import org.hibernate.dialect.function.array.H2ArrayRemoveFunction; 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.OracleArrayConcatFunction; import org.hibernate.dialect.function.array.OracleArrayContainsAllFunction; @@ -34,6 +37,7 @@ import org.hibernate.dialect.function.array.OracleArrayContainsAnyFunction; 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.OracleArrayRemoveFunction; import org.hibernate.dialect.function.array.OracleArraySetFunction; import org.hibernate.dialect.function.array.PostgreSQLArrayConcatFunction; import org.hibernate.dialect.function.array.PostgreSQLArrayPositionFunction; @@ -2996,7 +3000,7 @@ public class CommonFunctionFactory { } /** - * HSQL, CockroachDB and PostgreSQL array_set() function + * CockroachDB and PostgreSQL array_set() function */ public void arraySet_unnest() { functionRegistry.register( "array_set", new ArraySetUnnestFunction() ); @@ -3008,4 +3012,41 @@ public class CommonFunctionFactory { public void arraySet_oracle() { functionRegistry.register( "array_set", new OracleArraySetFunction() ); } + + /** + * H2 array_remove() function + */ + public void arrayRemove_h2() { + functionRegistry.register( "array_remove", new H2ArrayRemoveFunction() ); + } + + /** + * HSQL array_remove() function + */ + public void arrayRemove_hsql() { + functionRegistry.register( "array_remove", new HSQLArrayRemoveFunction() ); + } + + /** + * CockroachDB and PostgreSQL array_remove() function + */ + public void arrayRemove_unnest() { + functionRegistry.namedDescriptorBuilder( "array_remove" ) + .setArgumentsValidator( + StandardArgumentsValidators.composite( + StandardArgumentsValidators.exactly( 2 ), + ArrayAndElementArgumentValidator.DEFAULT_INSTANCE + ) + ) + .setReturnTypeResolver( ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE ) + .setArgumentTypeResolver( ArrayAndElementArgumentTypeResolver.DEFAULT_INSTANCE ) + .register(); + } + + /** + * Oracle array_remove() function + */ + public void arrayRemove_oracle() { + functionRegistry.register( "array_remove", new OracleArrayRemoveFunction() ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayRemoveFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayRemoveFunction.java new file mode 100644 index 0000000000..0f8eed62f7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayRemoveFunction.java @@ -0,0 +1,29 @@ +/* + * 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 org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; + +/** + * Encapsulates the validator, return type and argument type resolvers for the array_remove functions. + * Subclasses only have to implement the rendering. + */ +public abstract class AbstractArrayRemoveFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public AbstractArrayRemoveFunction() { + super( + "array_remove", + StandardArgumentsValidators.composite( + StandardArgumentsValidators.exactly( 2 ), + ArrayAndElementArgumentValidator.DEFAULT_INSTANCE + ), + ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE, + ArrayAndElementArgumentTypeResolver.DEFAULT_INSTANCE + ); + } +} 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 new file mode 100644 index 0000000000..0250bb1db4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayRemoveFunction.java @@ -0,0 +1,49 @@ +/* + * 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; + +/** + * H2 array_remove function. + */ +public class H2ArrayRemoveFunction extends AbstractArrayRemoveFunction { + + public H2ArrayRemoveFunction() { + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + 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 null then null else coalesce((select array_agg(array_get("); + arrayExpression.accept( walker ); + sqlAppender.append(",i.idx)) from system_range(1," ); + sqlAppender.append( Integer.toString( getMaximumArraySize() ) ); + 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 distinct from " ); + elementExpression.accept( walker ); + sqlAppender.append( "),array[]) end" ); + } + + protected int getMaximumArraySize() { + return 1000; + } +} 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 new file mode 100644 index 0000000000..635772f9a2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayRemoveFunction.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.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; + +/** + * HSQLDB array_remove function. + */ +public class HSQLArrayRemoveFunction extends AbstractArrayRemoveFunction { + + public HSQLArrayRemoveFunction() { + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + 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 null then null else 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 ); + sqlAppender.append( "),array[]) end" ); + } +} 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 a6906d2509..0bad35d667 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 @@ -8,7 +8,6 @@ package org.hibernate.dialect.function.array; import java.util.List; -import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.tree.SqlAstNode; @@ -27,15 +26,10 @@ public class OracleArrayGetFunction extends ArrayGetUnnestFunction { SqlAppender sqlAppender, List sqlAstArguments, SqlAstTranslator walker) { - JdbcMappingContainer expressionType = null; - for ( SqlAstNode sqlAstArgument : sqlAstArguments ) { - expressionType = ( (Expression) sqlAstArgument ).getExpressionType(); - if ( expressionType != null ) { - break; - } - } - - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( expressionType, walker ); + final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( + ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), + walker + ); sqlAppender.append( arrayTypeName ); sqlAppender.append( "_get(" ); sqlAstArguments.get( 0 ).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 new file mode 100644 index 0000000000..16176dec2a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayRemoveFunction.java @@ -0,0 +1,40 @@ +/* + * 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 array_remove function. + */ +public class OracleArrayRemoveFunction extends AbstractArrayRemoveFunction { + + public OracleArrayRemoveFunction() { + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( + ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), + walker + ); + sqlAppender.append( arrayTypeName ); + sqlAppender.append( "_remove(" ); + sqlAstArguments.get( 0 ).accept( walker ); + sqlAppender.append( ',' ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( ')' ); + } +} 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 129327a85d..fae3b2e96c 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 @@ -8,7 +8,6 @@ package org.hibernate.dialect.function.array; import java.util.List; -import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.tree.SqlAstNode; @@ -27,15 +26,10 @@ public class OracleArraySetFunction extends ArraySetUnnestFunction { SqlAppender sqlAppender, List sqlAstArguments, SqlAstTranslator walker) { - JdbcMappingContainer expressionType = null; - for ( SqlAstNode sqlAstArgument : sqlAstArguments ) { - expressionType = ( (Expression) sqlAstArgument ).getExpressionType(); - if ( expressionType != null ) { - break; - } - } - - final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( expressionType, walker ); + final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( + ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), + walker + ); sqlAppender.append( arrayTypeName ); sqlAppender.append( "_set(" ); sqlAstArguments.get( 0 ).accept( walker ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayRemoveTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayRemoveTest.java new file mode 100644 index 0000000000..fa507824b1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayRemoveTest.java @@ -0,0 +1,105 @@ +/* + * 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 ArrayRemoveTest { + + @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 testRemove(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-remove-example[] + List results = em.createQuery( "select e.id, array_remove(e.theArray, 'abc') from EntityWithArrays e order by e.id", Tuple.class ) + .getResultList(); + //end::hql-array-remove-example[] + assertEquals( 3, results.size() ); + assertEquals( 1L, results.get( 0 ).get( 0 ) ); + assertArrayEquals( new String[] {}, results.get( 0 ).get( 1, String[].class ) ); + assertEquals( 2L, results.get( 1 ).get( 0 ) ); + assertArrayEquals( new String[] { 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 testRemoveNullElement(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select e.id, array_remove(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[] {}, results.get( 0 ).get( 1, String[].class ) ); + assertEquals( 2L, results.get( 1 ).get( 0 ) ); + assertArrayEquals( new String[] { "abc", "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 testRemoveNonExisting(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select e.id, array_remove(e.theArray, 'aaa') from EntityWithArrays e order by e.id", Tuple.class ) + .getResultList(); + assertEquals( 3, results.size() ); + assertEquals( 1L, results.get( 0 ).get( 0 ) ); + assertArrayEquals( new String[] {}, results.get( 0 ).get( 1, String[].class ) ); + assertEquals( 2L, results.get( 1 ).get( 0 ) ); + assertArrayEquals( new String[] { "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 ) ); + } ); + } + +}