From d5404fdd49b3de7f5bbe3a2d8031268c9b92b110 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 23 Oct 2023 17:38:36 +0200 Subject: [PATCH] HHH-17335 Add array_contains quantified functions --- .../chapters/query/hql/QueryLanguage.adoc | 46 +++++ .../dialect/CockroachLegacyDialect.java | 4 + .../community/dialect/H2LegacyDialect.java | 4 + .../community/dialect/HSQLLegacyDialect.java | 4 + .../dialect/OracleLegacyDialect.java | 4 + .../dialect/PostgreSQLLegacyDialect.java | 4 + .../hibernate/dialect/CockroachDialect.java | 4 + .../java/org/hibernate/dialect/H2Dialect.java | 4 + .../org/hibernate/dialect/HSQLDialect.java | 4 + .../dialect/OracleArrayJdbcType.java | 45 +++++ .../org/hibernate/dialect/OracleDialect.java | 4 + .../hibernate/dialect/PostgreSQLDialect.java | 4 + .../function/CommonFunctionFactory.java | 165 +++++++++++++++++- ...stractArrayContainsQuantifiedFunction.java | 37 ++++ .../function/array/ArrayConcatFunction.java | 2 +- ...rayContainsQuantifiedOperatorFunction.java | 47 +++++ ...ArrayContainsQuantifiedUnnestFunction.java | 64 +++++++ ...=> ArraysOfSameTypeArgumentValidator.java} | 10 +- .../H2ArrayContainsQuantifiedEmulation.java | 79 +++++++++ .../array/HSQLArrayPositionFunction.java | 12 +- .../array/OracleArrayContainsAllFunction.java | 43 +++++ .../array/OracleArrayContainsAnyFunction.java | 43 +++++ .../function/array/ArrayContainsAllTest.java | 109 ++++++++++++ .../function/array/ArrayContainsAnyTest.java | 110 ++++++++++++ .../function/array/ArrayContainsTest.java | 3 - .../test/function/array/ArrayLengthTest.java | 3 - .../function/array/ArrayPositionTest.java | 2 - 27 files changed, 840 insertions(+), 20 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayContainsQuantifiedFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsQuantifiedOperatorFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsQuantifiedUnnestFunction.java rename hibernate-core/src/main/java/org/hibernate/dialect/function/array/{ArrayConcatArgumentValidator.java => ArraysOfSameTypeArgumentValidator.java} (81%) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayContainsQuantifiedEmulation.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAllFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAnyFunction.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsAllTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsAnyTest.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 9ab942ac07..ed3e297ff3 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -1195,6 +1195,52 @@ include::{array-example-dir-hql}/ArrayConcatTest.java[tags=hql-array-concat-exam ---- ==== +[[hql-array-contains-quantified-functions]] +===== `array_contains_all()` and `array_contains_any()` + +Checks if the first array argument contains all or any of the elements of the second array argument. +Returns `null` if either of the arguments is `null`. The result of the functions +is undefined when the second array argument contains a `null`. + +[[hql-array-contains-all-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayContainsAllTest.java[tags=hql-array-contains-all-example] +---- +==== + +[[hql-array-contains-any-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayContainsAnyTest.java[tags=hql-array-contains-any-example] +---- +==== + +[[hql-array-contains-quantified-functions]] +===== `array_contains_all_nullable()` & `array_contains_any_nullable()` + +Checks if the first array argument contains all or any of the elements of the second array argument. +Returns `null` if either of the arguments is `null`. Contrary to `array_contains_all()` and `array_contains_any()`, +these functions use the `distinct from` operator to also support searching for `null` elements. + +[[hql-array-contains-all-nullable-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayContainsAllTest.java[tags=hql-array-contains-all-nullable-example] +---- +==== + +[[hql-array-contains-any-nullable-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayContainsAnyTest.java[tags=hql-array-contains-any-nullable-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 89c72fda94..581fe75159 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,10 @@ public class CockroachLegacyDialect extends Dialect { functionFactory.arrayPosition_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); + functionFactory.arrayContainsAll_operator(); + functionFactory.arrayContainsAny_operator(); + functionFactory.arrayContainsAllNullable_operator(); + functionFactory.arrayContainsAnyNullable_operator(); 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 3e04ca857d..4938cab6bc 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,10 @@ public class H2LegacyDialect extends Dialect { functionFactory.arrayContainsNull(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); + functionFactory.arrayContainsAll_h2(); + functionFactory.arrayContainsAny_h2(); + functionFactory.arrayContainsAllNullable_h2(); + functionFactory.arrayContainsAnyNullable_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 c0229feff2..717a697e8d 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,10 @@ public class HSQLLegacyDialect extends Dialect { functionFactory.arrayPosition_hsql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); + functionFactory.arrayContainsAll_hsql(); + functionFactory.arrayContainsAny_hsql(); + functionFactory.arrayContainsAllNullable_hsql(); + functionFactory.arrayContainsAnyNullable_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 12cfe8f085..c613f2012c 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,10 @@ public class OracleLegacyDialect extends Dialect { functionFactory.arrayPosition_oracle(); functionFactory.arrayLength_oracle(); functionFactory.arrayConcat_oracle(); + functionFactory.arrayContainsAll_oracle(); + functionFactory.arrayContainsAny_oracle(); + functionFactory.arrayContainsAllNullable_oracle(); + functionFactory.arrayContainsAnyNullable_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 04b4b01ec2..f4727319da 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,10 @@ public class PostgreSQLLegacyDialect extends Dialect { functionFactory.arrayPosition_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); + functionFactory.arrayContainsAll_operator(); + functionFactory.arrayContainsAny_operator(); + functionFactory.arrayContainsAllNullable_operator(); + functionFactory.arrayContainsAnyNullable_operator(); 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 24ac33c208..2c4e0d190e 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,10 @@ public class CockroachDialect extends Dialect { functionFactory.arrayPosition_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); + functionFactory.arrayContainsAll_operator(); + functionFactory.arrayContainsAny_operator(); + functionFactory.arrayContainsAllNullable_operator(); + functionFactory.arrayContainsAnyNullable_operator(); 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 ce5025d846..ff319c7be6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -316,6 +316,10 @@ public class H2Dialect extends Dialect { functionFactory.arrayContainsNull(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); + functionFactory.arrayContainsAll_h2(); + functionFactory.arrayContainsAny_h2(); + functionFactory.arrayContainsAllNullable_h2(); + functionFactory.arrayContainsAnyNullable_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 a6cac7e499..53bce6d839 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,10 @@ public class HSQLDialect extends Dialect { functionFactory.arrayPosition_hsql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_operator(); + functionFactory.arrayContainsAll_hsql(); + functionFactory.arrayContainsAny_hsql(); + functionFactory.arrayContainsAllNullable_hsql(); + functionFactory.arrayContainsAnyNullable_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 018bc7c75f..d15108bbb7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java @@ -282,6 +282,51 @@ public class OracleArrayJdbcType extends ArrayJdbcType { false ) ); + database.addAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + arrayTypeName + "_contains_all", + database.getDefaultNamespace(), + new String[]{ + "create or replace function " + arrayTypeName + "_contains_all(haystack in " + arrayTypeName + + ", needle in " + arrayTypeName + ", nullable in number) return number deterministic is found number(1,0); begin " + + "if haystack is null or needle is null then return null; end if; " + + "for i in 1 .. needle.count loop " + + "found := 0; " + + "for j in 1 .. haystack.count loop " + + "if nullable = 1 and needle(i) is null and haystack(j) is null or needle(i)=haystack(j) then found := 1; exit; end if; " + + "end loop; " + + "if found = 0 then return 0; end if;" + + "end loop; " + + "return 1; " + + "end;" + }, + new String[] { "drop function " + arrayTypeName + "_contains_all" }, + emptySet(), + false + ) + ); + database.addAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + arrayTypeName + "_contains_any", + database.getDefaultNamespace(), + new String[]{ + "create or replace function " + arrayTypeName + "_contains_any(haystack in " + arrayTypeName + + ", needle in " + arrayTypeName + ", nullable in number) return number deterministic is begin " + + "if haystack is null or needle is null then return null; end if; " + + "if needle.count = 0 then return 1; end if; " + + "for i in 1 .. needle.count loop " + + "for j in 1 .. haystack.count loop " + + "if nullable = 1 and needle(i) is null and haystack(j) is null or needle(i)=haystack(j) then return 1; end if; " + + "end loop; " + + "end loop; " + + "return 0; " + + "end;" + }, + new String[] { "drop function " + arrayTypeName + "_contains_any" }, + 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 f5baa69373..86ba09b62b 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,10 @@ public class OracleDialect extends Dialect { functionFactory.arrayPosition_oracle(); functionFactory.arrayLength_oracle(); functionFactory.arrayConcat_oracle(); + functionFactory.arrayContainsAll_oracle(); + functionFactory.arrayContainsAny_oracle(); + functionFactory.arrayContainsAllNullable_oracle(); + functionFactory.arrayContainsAnyNullable_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 a25f210c42..c729687ef9 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,10 @@ public class PostgreSQLDialect extends Dialect { functionFactory.arrayPosition_postgresql(); functionFactory.arrayLength_cardinality(); functionFactory.arrayConcat_postgresql(); + functionFactory.arrayContainsAll_operator(); + functionFactory.arrayContainsAny_operator(); + functionFactory.arrayContainsAllNullable_operator(); + functionFactory.arrayContainsAnyNullable_operator(); 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 e0b4382663..272c44cdef 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,12 +16,16 @@ 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.ArrayConcatArgumentValidator; import org.hibernate.dialect.function.array.ArrayConcatFunction; import org.hibernate.dialect.function.array.ArrayConstructorFunction; +import org.hibernate.dialect.function.array.ArrayContainsQuantifiedOperatorFunction; import org.hibernate.dialect.function.array.ArrayContainsOperatorFunction; +import org.hibernate.dialect.function.array.ArrayContainsQuantifiedUnnestFunction; +import org.hibernate.dialect.function.array.H2ArrayContainsQuantifiedEmulation; import org.hibernate.dialect.function.array.HSQLArrayPositionFunction; import org.hibernate.dialect.function.array.OracleArrayConcatFunction; +import org.hibernate.dialect.function.array.OracleArrayContainsAllFunction; +import org.hibernate.dialect.function.array.OracleArrayContainsAnyFunction; import org.hibernate.dialect.function.array.OracleArrayLengthFunction; import org.hibernate.dialect.function.array.OracleArrayPositionFunction; import org.hibernate.dialect.function.array.PostgreSQLArrayConcatFunction; @@ -2688,7 +2692,7 @@ public class CommonFunctionFactory { * CockroachDB and PostgreSQL array contains null emulation */ public void arrayContainsNull_hsql() { - functionRegistry.patternDescriptorBuilder( "array_contains_null", "position_array(null in ?1)>0" ) + functionRegistry.patternDescriptorBuilder( "array_contains_null", "exists(select 1 from unnest(?1) t(i) where t.i is null)" ) .setReturnTypeResolver( StandardFunctionReturnTypeResolvers.invariant( booleanType ) ) .setArgumentsValidator( StandardArgumentsValidators.composite( @@ -2700,6 +2704,163 @@ public class CommonFunctionFactory { .register(); } + /** + * H2 array_contains_all() function + */ + public void arrayContainsAll_h2() { + functionRegistry.register( + "array_contains_all", + new H2ArrayContainsQuantifiedEmulation( typeConfiguration, true, false ) + ); + } + + /** + * HSQL array_contains_all() function + */ + public void arrayContainsAll_hsql() { + functionRegistry.register( + "array_contains_all", + new ArrayContainsQuantifiedUnnestFunction( typeConfiguration, true, false ) + ); + } + + /** + * CockroachDB and PostgreSQL array contains all operator + */ + public void arrayContainsAll_operator() { + functionRegistry.register( + "array_contains_all", + new ArrayContainsQuantifiedOperatorFunction( typeConfiguration, true, false ) + ); + } + + /** + * Oracle array_contains_all() function + */ + public void arrayContainsAll_oracle() { + functionRegistry.register( + "array_contains_all", + new OracleArrayContainsAllFunction( typeConfiguration, false ) + ); + } + + /** + * H2 array_contains_any() function + */ + public void arrayContainsAny_h2() { + functionRegistry.register( + "array_contains_any", + new H2ArrayContainsQuantifiedEmulation( typeConfiguration, false, false ) + ); + } + + /** + * HSQL array_contains_any() function + */ + public void arrayContainsAny_hsql() { + functionRegistry.register( + "array_contains_any", + new ArrayContainsQuantifiedUnnestFunction( typeConfiguration, false, false ) + ); + } + + /** + * CockroachDB and PostgreSQL array contains any operator + */ + public void arrayContainsAny_operator() { + functionRegistry.register( "array_contains_any", new ArrayContainsQuantifiedOperatorFunction( typeConfiguration, false, false ) ); + } + + /** + * Oracle array_contains_any() function + */ + public void arrayContainsAny_oracle() { + functionRegistry.register( + "array_contains_any", + new OracleArrayContainsAnyFunction( typeConfiguration, false ) + ); + } + + /** + * H2 array_contains_all_nullable() function + */ + public void arrayContainsAllNullable_h2() { + functionRegistry.register( + "array_contains_all_nullable", + new H2ArrayContainsQuantifiedEmulation( typeConfiguration, true, true ) + ); + } + + /** + * HSQL array_contains_all_nullable() function + */ + public void arrayContainsAllNullable_hsql() { + functionRegistry.register( + "array_contains_all_nullable", + new ArrayContainsQuantifiedUnnestFunction( typeConfiguration, true, true ) + ); + } + + /** + * CockroachDB and PostgreSQL array contains all nullable operator + */ + public void arrayContainsAllNullable_operator() { + functionRegistry.register( + "array_contains_all_nullable", + new ArrayContainsQuantifiedOperatorFunction( typeConfiguration, true, true ) + ); + } + + /** + * Oracle array_contains_all_nullable() function + */ + public void arrayContainsAllNullable_oracle() { + functionRegistry.register( + "array_contains_all_nullable", + new OracleArrayContainsAllFunction( typeConfiguration, true ) + ); + } + + /** + * H2 array_contains_any_nullable() function + */ + public void arrayContainsAnyNullable_h2() { + functionRegistry.register( + "array_contains_any_nullable", + new H2ArrayContainsQuantifiedEmulation( typeConfiguration, false, true ) + ); + } + + /** + * HSQL array_contains_any_nullable() function + */ + public void arrayContainsAnyNullable_hsql() { + functionRegistry.register( + "array_contains_any_nullable", + new ArrayContainsQuantifiedUnnestFunction( typeConfiguration, false, true ) + ); + } + + /** + * CockroachDB and PostgreSQL array contains any nullable operator + */ + public void arrayContainsAnyNullable_operator() { + functionRegistry.register( + "array_contains_any_nullable", + new ArrayContainsQuantifiedOperatorFunction( typeConfiguration, false, true ) + ); + } + + /** + * Oracle array_contains_any_nullable() function + */ + public void arrayContainsAnyNullable_oracle() { + functionRegistry.register( + "array_contains_any_nullable", + new OracleArrayContainsAnyFunction( typeConfiguration, true ) + ); + } + /** * CockroachDB and PostgreSQL array_position() function */ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayContainsQuantifiedFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayContainsQuantifiedFunction.java new file mode 100644 index 0000000000..924ee815dd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayContainsQuantifiedFunction.java @@ -0,0 +1,37 @@ +/* + * 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; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Encapsulates the validator, return type and argument type resolvers for the quantified array_contains functions. + * Subclasses only have to implement the rendering. + */ +public abstract class AbstractArrayContainsQuantifiedFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public AbstractArrayContainsQuantifiedFunction(String functionName, TypeConfiguration typeConfiguration) { + super( + functionName, + StandardArgumentsValidators.composite( + StandardArgumentsValidators.exactly( 2 ), + ArraysOfSameTypeArgumentValidator.INSTANCE + ), + StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.standardBasicTypeForJavaType( Boolean.class ) ), + StandardFunctionArgumentTypeResolvers.ARGUMENT_OR_IMPLIED_RESULT_TYPE + ); + } + + @Override + public String getArgumentListSignature() { + return "(ARRAY haystackArray, ARRAY needleArray)"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatFunction.java index 392a9f2f28..0ee3c9f291 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatFunction.java @@ -30,7 +30,7 @@ public class ArrayConcatFunction extends AbstractSqmSelfRenderingFunctionDescrip "array_concat", StandardArgumentsValidators.composite( StandardArgumentsValidators.min( 2 ), - ArrayConcatArgumentValidator.INSTANCE + ArraysOfSameTypeArgumentValidator.INSTANCE ), StandardFunctionReturnTypeResolvers.useFirstNonNull(), StandardFunctionArgumentTypeResolvers.ARGUMENT_OR_IMPLIED_RESULT_TYPE diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsQuantifiedOperatorFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsQuantifiedOperatorFunction.java new file mode 100644 index 0000000000..ab9ea40f33 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsQuantifiedOperatorFunction.java @@ -0,0 +1,47 @@ +/* + * 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; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Array contains all function that uses the PostgreSQL {@code @>} operator. + */ +public class ArrayContainsQuantifiedOperatorFunction extends ArrayContainsQuantifiedUnnestFunction { + + public ArrayContainsQuantifiedOperatorFunction(TypeConfiguration typeConfiguration, boolean all, boolean nullable) { + super( typeConfiguration, all, nullable ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + if ( nullable ) { + super.render( sqlAppender, sqlAstArguments, walker ); + } + else { + final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression needleExpression = (Expression) sqlAstArguments.get( 1 ); + haystackExpression.accept( walker ); + if ( all ) { + sqlAppender.append( "@>" ); + } + else { + sqlAppender.append( "&&" ); + } + needleExpression.accept( walker ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsQuantifiedUnnestFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsQuantifiedUnnestFunction.java new file mode 100644 index 0000000000..26a712b8d2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayContainsQuantifiedUnnestFunction.java @@ -0,0 +1,64 @@ +/* + * 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; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Implement the contains all function by using {@code unnest}. + */ +public class ArrayContainsQuantifiedUnnestFunction extends AbstractArrayContainsQuantifiedFunction { + + protected final boolean all; + protected final boolean nullable; + + public ArrayContainsQuantifiedUnnestFunction(TypeConfiguration typeConfiguration, boolean all, boolean nullable) { + super( "array_contains_" + ( all ? "all" : "any" ), typeConfiguration ); + this.all = all; + this.nullable = nullable; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression needleExpression = (Expression) sqlAstArguments.get( 1 ); + sqlAppender.append( '(' ); + haystackExpression.accept( walker ); + sqlAppender.append( " is not null and " ); + needleExpression.accept( walker ); + sqlAppender.append( " is not null and " ); + if ( !nullable ) { + sqlAppender.append( "not exists(select 1 from unnest(" ); + needleExpression.accept( walker ); + sqlAppender.append( ") t(i) where t.i is null) and " ); + } + if ( all ) { + sqlAppender.append( "not " ); + } + sqlAppender.append( "exists(select * from unnest(" ); + needleExpression.accept( walker ); + sqlAppender.append( ")" ); + if ( all ) { + sqlAppender.append( " except " ); + } + else { + sqlAppender.append( " intersect " ); + } + sqlAppender.append( "select * from unnest(" ); + haystackExpression.accept( walker ); + sqlAppender.append( ")))" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatArgumentValidator.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArraysOfSameTypeArgumentValidator.java similarity index 81% rename from hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatArgumentValidator.java rename to hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArraysOfSameTypeArgumentValidator.java index 74161f507d..8dfa8c48b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayConcatArgumentValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArraysOfSameTypeArgumentValidator.java @@ -9,6 +9,7 @@ package org.hibernate.dialect.function.array; import java.util.List; import org.hibernate.metamodel.model.domain.DomainType; +import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.FunctionArgumentException; import org.hibernate.query.sqm.tree.SqmTypedNode; @@ -18,9 +19,9 @@ import org.hibernate.type.spi.TypeConfiguration; /** * A {@link ArgumentsValidator} that validates all arguments are of the same array type. */ -public class ArrayConcatArgumentValidator implements ArgumentsValidator { +public class ArraysOfSameTypeArgumentValidator implements ArgumentsValidator { - public static final ArgumentsValidator INSTANCE = new ArrayConcatArgumentValidator(); + public static final ArgumentsValidator INSTANCE = new ArraysOfSameTypeArgumentValidator(); @Override public void validate( @@ -29,8 +30,9 @@ public class ArrayConcatArgumentValidator implements ArgumentsValidator { TypeConfiguration typeConfiguration) { BasicPluralType arrayType = null; for ( int i = 0; i < arguments.size(); i++ ) { - final DomainType sqmType = arguments.get( i ).getExpressible().getSqmType(); - if ( sqmType != null ) { + final SqmExpressible expressible = arguments.get( i ).getExpressible(); + final DomainType sqmType; + if ( expressible != null && ( sqmType = expressible.getSqmType() ) != null ) { if ( arrayType == null ) { if ( !( sqmType instanceof BasicPluralType ) ) { throw new FunctionArgumentException( diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayContainsQuantifiedEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayContainsQuantifiedEmulation.java new file mode 100644 index 0000000000..8276cd3b5b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayContainsQuantifiedEmulation.java @@ -0,0 +1,79 @@ +/* + * 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; +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 H2ArrayContainsQuantifiedEmulation extends AbstractArrayContainsQuantifiedFunction { + + private final boolean all; + private final boolean nullable; + + public H2ArrayContainsQuantifiedEmulation(TypeConfiguration typeConfiguration, boolean all, boolean nullable) { + super( "array_contains_" + ( all ? "all" : "any" ), typeConfiguration ); + this.all = all; + this.nullable = nullable; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression needleExpression = (Expression) sqlAstArguments.get( 1 ); + sqlAppender.append( '(' ); + haystackExpression.accept( walker ); + sqlAppender.append( " is not null and " ); + needleExpression.accept( walker ); + sqlAppender.append( " is not null and " ); + if ( !nullable ) { + sqlAppender.append( "not array_contains(" ); + needleExpression.accept( walker ); + sqlAppender.append( ",null) and " ); + } + if ( all ) { + sqlAppender.append( "not " ); + } + sqlAppender.append( "exists(select array_get(" ); + needleExpression.accept( walker ); + sqlAppender.append( ",t.i) from system_range(1," ); + sqlAppender.append( Integer.toString( getMaximumArraySize() ) ); + sqlAppender.append( ") t(i) where array_length(" ); + needleExpression.accept( walker ); + sqlAppender.append( ")>=t.i" ); + if ( all ) { + sqlAppender.append( " except " ); + } + else { + sqlAppender.append( " intersect " ); + } + sqlAppender.append( "select array_get(" ); + haystackExpression.accept( walker ); + sqlAppender.append( ",t.i) from system_range(1," ); + sqlAppender.append( Integer.toString( getMaximumArraySize() ) ); + sqlAppender.append( ") t(i) where array_length(" ); + haystackExpression.accept( walker ); + sqlAppender.append( ")>=t.i))" ); + } + + protected int getMaximumArraySize() { + return 1000; + } + +} 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 e4f86a516e..1c56e399c4 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 @@ -30,14 +30,16 @@ public class HSQLArrayPositionFunction extends AbstractArrayPositionFunction { SqlAstTranslator walker) { final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); final Expression elementExpression = (Expression) sqlAstArguments.get( 1 ); - sqlAppender.append( "position_array(" ); - elementExpression.accept( walker ); - sqlAppender.append( " in " ); + sqlAppender.append( "case when " ); arrayExpression.accept( walker ); + sqlAppender.append( " is not null then coalesce((select t.idx from unnest("); + arrayExpression.accept( walker ); + sqlAppender.append(") with ordinality t(val,idx) where t.val is not distinct from " ); + elementExpression.accept( walker ); if ( sqlAstArguments.size() > 2 ) { - sqlAppender.append( " from " ); + sqlAppender.append( " and t.idx>=" ); sqlAstArguments.get( 2 ).accept( walker ); } - sqlAppender.append( ')' ); + sqlAppender.append( "),0) end" ); } } 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 new file mode 100644 index 0000000000..e1d952906d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAllFunction.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.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 OracleArrayContainsAllFunction extends AbstractArrayContainsQuantifiedFunction { + + private final boolean nullable; + + public OracleArrayContainsAllFunction(TypeConfiguration typeConfiguration, boolean nullable) { + super( "array_contains_all", typeConfiguration ); + this.nullable = nullable; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 ); + final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( haystackExpression.getExpressionType(), walker ); + sqlAppender.appendSql( arrayTypeName ); + sqlAppender.append( "_contains_all(" ); + haystackExpression.accept( walker ); + sqlAppender.append( ',' ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( ',' ); + sqlAppender.append( nullable ? "1" : "0" ); + sqlAppender.append( ")>0" ); + } + +} 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 new file mode 100644 index 0000000000..dd01379fdf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayContainsAnyFunction.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.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 OracleArrayContainsAnyFunction extends AbstractArrayContainsQuantifiedFunction { + + private final boolean nullable; + + public OracleArrayContainsAnyFunction(TypeConfiguration typeConfiguration, boolean nullable) { + super( "array_contains_any", typeConfiguration ); + this.nullable = nullable; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 ); + final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( haystackExpression.getExpressionType(), walker ); + sqlAppender.appendSql( arrayTypeName ); + sqlAppender.append( "_contains_any(" ); + haystackExpression.accept( walker ); + sqlAppender.append( ',' ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( ',' ); + sqlAppender.append( nullable ? "1" : "0" ); + sqlAppender.append( ")>0" ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsAllTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsAllTest.java new file mode 100644 index 0000000000..62cdef25f6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsAllTest.java @@ -0,0 +1,109 @@ +/* + * 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.dialect.HSQLDialect; + +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.hibernate.testing.orm.junit.SkipForDialect; +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.assertEquals; + +/** + * @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 ArrayContainsAllTest { + + @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 testContainsAll(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-contains-all-example[] + List results = em.createQuery( "from EntityWithArrays e where array_contains_all(e.theArray, array('abc', 'def'))", EntityWithArrays.class ) + .getResultList(); + //end::hql-array-contains-all-example[] + assertEquals( 1, results.size() ); + assertEquals( 2L, results.get( 0 ).getId() ); + } ); + } + + @Test + public void testDoesNotContain(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "from EntityWithArrays e where array_contains_all(e.theArray, array('xyz'))", EntityWithArrays.class ) + .getResultList(); + assertEquals( 0, results.size() ); + } ); + } + + @Test + public void testContainsPartly(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "from EntityWithArrays e where array_contains_all(e.theArray, array('abc','xyz'))", EntityWithArrays.class ) + .getResultList(); + assertEquals( 0, results.size() ); + } ); + } + + @Test + @SkipForDialect(dialectClass = HSQLDialect.class, reason = "Type inference isn't smart enough to figure out the type for the `null`") + public void testContainsNull(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "from EntityWithArrays e where array_contains_all_nullable(e.theArray, array(null))", EntityWithArrays.class ) + .getResultList(); + assertEquals( 1, results.size() ); + assertEquals( 2L, results.get( 0 ).getId() ); + } ); + } + + @Test + public void testContainsWithNull(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-contains-all-nullable-example[] + List results = em.createQuery( "from EntityWithArrays e where array_contains_all_nullable(e.theArray, array('abc',null))", EntityWithArrays.class ) + .getResultList(); + //end::hql-array-contains-all-nullable-example[] + assertEquals( 1, results.size() ); + assertEquals( 2L, results.get( 0 ).getId() ); + } ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsAnyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsAnyTest.java new file mode 100644 index 0000000000..189ba57d34 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsAnyTest.java @@ -0,0 +1,110 @@ +/* + * 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.dialect.HSQLDialect; + +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.hibernate.testing.orm.junit.SkipForDialect; +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.assertEquals; + +/** + * @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 ArrayContainsAnyTest { + + @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 testContainsAny(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-contains-any-example[] + List results = em.createQuery( "from EntityWithArrays e where array_contains_any(e.theArray, array('abc', 'def'))", EntityWithArrays.class ) + .getResultList(); + //end::hql-array-contains-any-example[] + assertEquals( 1, results.size() ); + assertEquals( 2L, results.get( 0 ).getId() ); + } ); + } + + @Test + public void testDoesNotContain(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "from EntityWithArrays e where array_contains_any(e.theArray, array('xyz'))", EntityWithArrays.class ) + .getResultList(); + assertEquals( 0, results.size() ); + } ); + } + + @Test + public void testContainsPartly(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "from EntityWithArrays e where array_contains_any(e.theArray, array('abc','xyz'))", EntityWithArrays.class ) + .getResultList(); + assertEquals( 1, results.size() ); + assertEquals( 2L, results.get( 0 ).getId() ); + } ); + } + + @Test + @SkipForDialect(dialectClass = HSQLDialect.class, reason = "Type inference isn't smart enough to figure out the type for the `null`") + public void testContainsNull(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "from EntityWithArrays e where array_contains_any_nullable(e.theArray, array(null))", EntityWithArrays.class ) + .getResultList(); + assertEquals( 1, results.size() ); + assertEquals( 2L, results.get( 0 ).getId() ); + } ); + } + + @Test + public void testContainsNullPartly(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-contains-any-nullable-example[] + List results = em.createQuery( "from EntityWithArrays e where array_contains_any_nullable(e.theArray, array('xyz',null))", EntityWithArrays.class ) + .getResultList(); + //end::hql-array-contains-any-nullable-example[] + assertEquals( 1, results.size() ); + assertEquals( 2L, results.get( 0 ).getId() ); + } ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsTest.java index feba04ab67..1e5a50874c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayContainsTest.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.HSQLDialect; 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; @@ -75,7 +73,6 @@ public class ArrayContainsTest { } @Test - @SkipForDialect(dialectClass = HSQLDialect.class, reason = "See https://sourceforge.net/p/hsqldb/bugs/1692/") public void testContainsNull(SessionFactoryScope scope) { scope.inSession( em -> { //tag::hql-array-contains-null-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayLengthTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayLengthTest.java index 9ffa1eae40..9be0e7c9ed 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayLengthTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayLengthTest.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.HSQLDialect; 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; @@ -76,7 +74,6 @@ public class ArrayLengthTest { } @Test - @SkipForDialect(dialectClass = HSQLDialect.class, reason = "See https://sourceforge.net/p/hsqldb/bugs/1692/") public void testLengthNull(SessionFactoryScope scope) { scope.inSession( em -> { List results = em.createQuery( "from EntityWithArrays e where array_length(e.theArray) is null", EntityWithArrays.class ) 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 8ba0bb5d52..f828c884e1 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 @@ -10,7 +10,6 @@ import java.util.List; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.H2Dialect; -import org.hibernate.dialect.HSQLDialect; import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DomainModel; @@ -77,7 +76,6 @@ public class ArrayPositionTest { } @Test - @SkipForDialect(dialectClass = HSQLDialect.class, reason = "See https://sourceforge.net/p/hsqldb/bugs/1692/") public void testPositionNull(SessionFactoryScope scope) { scope.inSession( em -> { List results = em.createQuery( "from EntityWithArrays e where array_position(e.theArray, null) = 2", EntityWithArrays.class )