HHH-17335 Add array_length function

This commit is contained in:
Christian Beikov 2023-10-20 15:59:37 +02:00
parent 7f10a48469
commit 36b7374ba8
15 changed files with 205 additions and 1 deletions

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -252,6 +252,7 @@ public class HSQLLegacyDialect extends Dialect {
functionFactory.arrayContains_hsql();
functionFactory.arrayContainsNull_hsql();
functionFactory.arrayPosition_hsql();
functionFactory.arrayLength_cardinality();
}
@Override

View File

@ -288,6 +288,7 @@ public class OracleLegacyDialect extends Dialect {
functionFactory.arrayContains_oracle();
functionFactory.arrayContainsNull_oracle();
functionFactory.arrayPosition_oracle();
functionFactory.arrayLength_oracle();
}
@Override

View File

@ -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();

View File

@ -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",

View File

@ -314,6 +314,7 @@ public class H2Dialect extends Dialect {
functionFactory.arrayAggregate();
functionFactory.arrayContains();
functionFactory.arrayContainsNull();
functionFactory.arrayLength_cardinality();
}
@Override

View File

@ -192,6 +192,7 @@ public class HSQLDialect extends Dialect {
functionFactory.arrayContains_hsql();
functionFactory.arrayContainsNull_hsql();
functionFactory.arrayPosition_hsql();
functionFactory.arrayLength_cardinality();
}
@Override

View File

@ -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
)

View File

@ -317,6 +317,7 @@ public class OracleDialect extends Dialect {
functionFactory.arrayContains_oracle();
functionFactory.arrayContainsNull_oracle();
functionFactory.arrayPosition_oracle();
functionFactory.arrayLength_oracle();
}
@Override

View File

@ -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

View File

@ -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 ) );
}
}

View File

@ -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<? extends SqlAstNode> 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)";
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<EntityWithArrays> 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<EntityWithArrays> 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<EntityWithArrays> 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() );
} );
}
}