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 3ac0efbf41..95fa170e1e 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -1121,6 +1121,7 @@ The following functions deal with SQL array types, which are not supported on ev | `array_contains()` | Whether an array contains an element | `array_contains_null()` | Whether an array contains a null | `array_position()` | Determines the position of an element in an array +| `array_length()` | Determines the length of an array |=== ===== `array()` @@ -1167,6 +1168,19 @@ include::{array-example-dir-hql}/ArrayPositionTest.java[tags=hql-array-position- ---- ==== +[[hql-array-length-functions]] +===== `array_length()` + +Returns size of the passed array. Returns `null` if the array is `null`. + +[[hql-array-length-example]] +==== +[source, JAVA, indent=0] +---- +include::{array-example-dir-hql}/ArrayLengthTest.java[tags=hql-array-length-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 13fb142ccd..b4e87c7eeb 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 @@ -466,6 +466,7 @@ public class CockroachLegacyDialect extends Dialect { functionFactory.arrayContains_operator(); functionFactory.arrayContainsNull_array_position(); functionFactory.arrayPosition_postgresql(); + functionFactory.arrayLength_cardinality(); 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 4559c7da06..e219d804a0 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 @@ -374,6 +374,7 @@ public class H2LegacyDialect extends Dialect { functionFactory.arrayAggregate(); functionFactory.arrayContains(); functionFactory.arrayContainsNull(); + functionFactory.arrayLength_cardinality(); } 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 b8383233f5..f99bb6705b 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 @@ -252,6 +252,7 @@ public class HSQLLegacyDialect extends Dialect { functionFactory.arrayContains_hsql(); functionFactory.arrayContainsNull_hsql(); functionFactory.arrayPosition_hsql(); + functionFactory.arrayLength_cardinality(); } @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 2b855f7ef4..e9aed4aa57 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 @@ -288,6 +288,7 @@ public class OracleLegacyDialect extends Dialect { functionFactory.arrayContains_oracle(); functionFactory.arrayContainsNull_oracle(); functionFactory.arrayPosition_oracle(); + functionFactory.arrayLength_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 a3353a0db0..2d3dbef4e0 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 @@ -586,6 +586,7 @@ public class PostgreSQLLegacyDialect extends Dialect { functionFactory.arrayContains_operator(); functionFactory.arrayContainsNull_array_position(); functionFactory.arrayPosition_postgresql(); + functionFactory.arrayLength_cardinality(); 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 78a6c9cf01..74f42b21db 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -453,6 +453,7 @@ public class CockroachDialect extends Dialect { functionFactory.arrayContains_operator(); functionFactory.arrayContainsNull_array_position(); functionFactory.arrayPosition_postgresql(); + functionFactory.arrayLength_cardinality(); 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 2ff621d30f..08d5754b47 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -314,6 +314,7 @@ public class H2Dialect extends Dialect { functionFactory.arrayAggregate(); functionFactory.arrayContains(); functionFactory.arrayContainsNull(); + functionFactory.arrayLength_cardinality(); } @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 2f6877db07..0f969be80a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -192,6 +192,7 @@ public class HSQLDialect extends Dialect { functionFactory.arrayContains_hsql(); functionFactory.arrayContainsNull_hsql(); functionFactory.arrayPosition_hsql(); + functionFactory.arrayLength_cardinality(); } @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 0667d0c41c..9d5bccd167 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java @@ -251,7 +251,23 @@ public class OracleArrayJdbcType extends ArrayJdbcType { "return 0; " + "end;" }, - new String[] { "drop function " + arrayTypeName + "_contains" }, + new String[] { "drop function " + arrayTypeName + "_position" }, + emptySet(), + false + ) + ); + database.addAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + arrayTypeName + "_length", + database.getDefaultNamespace(), + new String[]{ + "create or replace function " + arrayTypeName + "_length(arr in " + arrayTypeName + + ") return number deterministic is begin " + + "if arr is null then return null; end if; " + + "return arr.count; " + + "end;" + }, + new String[] { "drop function " + arrayTypeName + "_length" }, emptySet(), false ) 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 d759c7144f..1d8fc0469d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -317,6 +317,7 @@ public class OracleDialect extends Dialect { functionFactory.arrayContains_oracle(); functionFactory.arrayContainsNull_oracle(); functionFactory.arrayPosition_oracle(); + functionFactory.arrayLength_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 858fb616db..68b321794e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -634,6 +634,7 @@ public class PostgreSQLDialect extends Dialect { functionFactory.arrayContains_operator(); functionFactory.arrayContainsNull_array_position(); functionFactory.arrayPosition_postgresql(); + functionFactory.arrayLength_cardinality(); 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 4f742a62d5..706925ac60 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 @@ -19,6 +19,7 @@ import org.hibernate.dialect.function.array.ArrayArgumentValidator; import org.hibernate.dialect.function.array.ArrayConstructorFunction; import org.hibernate.dialect.function.array.ArrayContainsOperatorFunction; import org.hibernate.dialect.function.array.HSQLArrayPositionFunction; +import org.hibernate.dialect.function.array.OracleArrayLengthFunction; import org.hibernate.dialect.function.array.OracleArrayPositionFunction; import org.hibernate.dialect.function.array.PostgreSQLArrayPositionFunction; import org.hibernate.dialect.function.array.CastingArrayConstructorFunction; @@ -2715,4 +2716,27 @@ public class CommonFunctionFactory { public void arrayPosition_oracle() { functionRegistry.register( "array_position", new OracleArrayPositionFunction( typeConfiguration ) ); } + + /** + * H2, HSQLDB, CockroachDB and PostgreSQL array_length() function + */ + public void arrayLength_cardinality() { + functionRegistry.patternDescriptorBuilder( "array_length", "cardinality(?1)" ) + .setReturnTypeResolver( StandardFunctionReturnTypeResolvers.invariant( integerType ) ) + .setArgumentsValidator( + StandardArgumentsValidators.composite( + StandardArgumentsValidators.exactly( 1 ), + ArrayArgumentValidator.DEFAULT_INSTANCE + ) + ) + .setArgumentListSignature( "(ARRAY array)" ) + .register(); + } + + /** + * Oracle array_length() function + */ + public void arrayLength_oracle() { + functionRegistry.register( "array_length", new OracleArrayLengthFunction( typeConfiguration ) ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayLengthFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayLengthFunction.java new file mode 100644 index 0000000000..5e3ac2aad9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayLengthFunction.java @@ -0,0 +1,51 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +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 OracleArrayLengthFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public OracleArrayLengthFunction(TypeConfiguration typeConfiguration) { + super( + "array_length", + StandardArgumentsValidators.composite( + StandardArgumentsValidators.exactly( 1 ), + ArrayArgumentValidator.DEFAULT_INSTANCE + ), + StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.standardBasicTypeForJavaType( Integer.class ) ), + null + ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ); + sqlAppender.appendSql( arrayTypeName ); + sqlAppender.append( "_length(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ')' ); + } + + @Override + public String getArgumentListSignature() { + return "(ARRAY array)"; + } +} 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 new file mode 100644 index 0000000000..9ffa1eae40 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayLengthTest.java @@ -0,0 +1,89 @@ +/* + * 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 ArrayLengthTest { + + @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 testLengthZero(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-array-length-example[] + List results = em.createQuery( "from EntityWithArrays e where array_length(e.theArray) = 0", EntityWithArrays.class ) + .getResultList(); + //end::hql-array-length-example[] + assertEquals( 1, results.size() ); + assertEquals( 1L, results.get( 0 ).getId() ); + } ); + } + + @Test + public void testLengthThree(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "from EntityWithArrays e where array_length(e.theArray) = 3", EntityWithArrays.class ) + .getResultList(); + assertEquals( 1, results.size() ); + assertEquals( 2L, results.get( 0 ).getId() ); + } ); + } + + @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 ) + .getResultList(); + assertEquals( 1, results.size() ); + assertEquals( 3L, results.get( 0 ).getId() ); + } ); + } + +}