HHH-17355 Add array_trim function
This commit is contained in:
parent
f2e6ad6741
commit
faf6345463
|
@ -1134,6 +1134,7 @@ The following functions deal with SQL array types, which are not supported on ev
|
||||||
| `array_remove_index()` | Creates array copy with the element at the given index removed
|
| `array_remove_index()` | Creates array copy with the element at the given index removed
|
||||||
| `array_slice()` | Creates a sub-array of the based on lower and upper index
|
| `array_slice()` | Creates a sub-array of the based on lower and upper index
|
||||||
| `array_replace()` | Creates array copy replacing a given element with another
|
| `array_replace()` | Creates array copy replacing a given element with another
|
||||||
|
| `array_trim()` | Creates array copy trimming the last _N_ elements
|
||||||
|===
|
|===
|
||||||
|
|
||||||
===== `array()`
|
===== `array()`
|
||||||
|
@ -1385,6 +1386,19 @@ include::{array-example-dir-hql}/ArrayReplaceTest.java[tags=hql-array-replace-ex
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
===== `array_trim()`
|
||||||
|
|
||||||
|
Returns an array copy without the last _N_ elements, specified by the second argument.
|
||||||
|
It is an error if any array has a length smaller than the second argument.
|
||||||
|
|
||||||
|
[[hql-array-trim-example]]
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{array-example-dir-hql}/ArrayTrimTest.java[tags=hql-array-trim-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
[[hql-user-defined-functions]]
|
[[hql-user-defined-functions]]
|
||||||
==== Native and user-defined functions
|
==== Native and user-defined functions
|
||||||
|
|
||||||
|
|
|
@ -476,6 +476,7 @@ public class CockroachLegacyDialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_unnest( true );
|
functionFactory.arrayRemoveIndex_unnest( true );
|
||||||
functionFactory.arraySlice_operator();
|
functionFactory.arraySlice_operator();
|
||||||
functionFactory.arrayReplace();
|
functionFactory.arrayReplace();
|
||||||
|
functionFactory.arrayTrim_trim_array();
|
||||||
|
|
||||||
functionContributions.getFunctionRegistry().register(
|
functionContributions.getFunctionRegistry().register(
|
||||||
"trunc",
|
"trunc",
|
||||||
|
|
|
@ -384,6 +384,7 @@ public class H2LegacyDialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_h2( getMaximumArraySize() );
|
functionFactory.arrayRemoveIndex_h2( getMaximumArraySize() );
|
||||||
functionFactory.arraySlice();
|
functionFactory.arraySlice();
|
||||||
functionFactory.arrayReplace_h2( getMaximumArraySize() );
|
functionFactory.arrayReplace_h2( getMaximumArraySize() );
|
||||||
|
functionFactory.arrayTrim_trim_array();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Use group_concat until 2.x as listagg was buggy
|
// Use group_concat until 2.x as listagg was buggy
|
||||||
|
|
|
@ -262,6 +262,7 @@ public class HSQLLegacyDialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_unnest( false );
|
functionFactory.arrayRemoveIndex_unnest( false );
|
||||||
functionFactory.arraySlice_unnest();
|
functionFactory.arraySlice_unnest();
|
||||||
functionFactory.arrayReplace_unnest();
|
functionFactory.arrayReplace_unnest();
|
||||||
|
functionFactory.arrayTrim_trim_array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -298,6 +298,7 @@ public class OracleLegacyDialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_oracle();
|
functionFactory.arrayRemoveIndex_oracle();
|
||||||
functionFactory.arraySlice_oracle();
|
functionFactory.arraySlice_oracle();
|
||||||
functionFactory.arrayReplace_oracle();
|
functionFactory.arrayReplace_oracle();
|
||||||
|
functionFactory.arrayTrim_oracle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -596,6 +596,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_unnest( true );
|
functionFactory.arrayRemoveIndex_unnest( true );
|
||||||
functionFactory.arraySlice_operator();
|
functionFactory.arraySlice_operator();
|
||||||
functionFactory.arrayReplace();
|
functionFactory.arrayReplace();
|
||||||
|
functionFactory.arrayTrim_trim_array();
|
||||||
|
|
||||||
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
||||||
functionFactory.makeDateTimeTimestamp();
|
functionFactory.makeDateTimeTimestamp();
|
||||||
|
|
|
@ -463,6 +463,7 @@ public class CockroachDialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_unnest( true );
|
functionFactory.arrayRemoveIndex_unnest( true );
|
||||||
functionFactory.arraySlice_operator();
|
functionFactory.arraySlice_operator();
|
||||||
functionFactory.arrayReplace();
|
functionFactory.arrayReplace();
|
||||||
|
functionFactory.arrayTrim_trim_array();
|
||||||
|
|
||||||
functionContributions.getFunctionRegistry().register(
|
functionContributions.getFunctionRegistry().register(
|
||||||
"trunc",
|
"trunc",
|
||||||
|
|
|
@ -323,6 +323,7 @@ public class H2Dialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_h2( getMaximumArraySize() );
|
functionFactory.arrayRemoveIndex_h2( getMaximumArraySize() );
|
||||||
functionFactory.arraySlice();
|
functionFactory.arraySlice();
|
||||||
functionFactory.arrayReplace_h2( getMaximumArraySize() );
|
functionFactory.arrayReplace_h2( getMaximumArraySize() );
|
||||||
|
functionFactory.arrayTrim_trim_array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -202,6 +202,7 @@ public class HSQLDialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_unnest( false );
|
functionFactory.arrayRemoveIndex_unnest( false );
|
||||||
functionFactory.arraySlice_unnest();
|
functionFactory.arraySlice_unnest();
|
||||||
functionFactory.arrayReplace_unnest();
|
functionFactory.arrayReplace_unnest();
|
||||||
|
functionFactory.arrayTrim_trim_array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -471,6 +471,28 @@ public class OracleArrayJdbcType extends ArrayJdbcType {
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
database.addAuxiliaryDatabaseObject(
|
||||||
|
new NamedAuxiliaryDatabaseObject(
|
||||||
|
arrayTypeName + "_trim",
|
||||||
|
database.getDefaultNamespace(),
|
||||||
|
new String[]{
|
||||||
|
"create or replace function " + arrayTypeName + "_trim(arr in " + arrayTypeName +
|
||||||
|
", elems number) return " + arrayTypeName + " deterministic is " +
|
||||||
|
"res " + arrayTypeName + ":=" + arrayTypeName + "(); begin " +
|
||||||
|
"if arr is null or elems is null then return null; end if; " +
|
||||||
|
"if arr.count < elems then raise_application_error (-20000, 'number of elements to trim must be between 0 and '||arr.count); end if;" +
|
||||||
|
"for i in 1 .. arr.count-elems loop " +
|
||||||
|
"res.extend; " +
|
||||||
|
"res(i) := arr(i); " +
|
||||||
|
"end loop; " +
|
||||||
|
"return res; " +
|
||||||
|
"end;"
|
||||||
|
},
|
||||||
|
new String[] { "drop function " + arrayTypeName + "_trim" },
|
||||||
|
emptySet(),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String createOrReplaceConcatFunction(String arrayTypeName) {
|
protected String createOrReplaceConcatFunction(String arrayTypeName) {
|
||||||
|
|
|
@ -327,6 +327,7 @@ public class OracleDialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_oracle();
|
functionFactory.arrayRemoveIndex_oracle();
|
||||||
functionFactory.arraySlice_oracle();
|
functionFactory.arraySlice_oracle();
|
||||||
functionFactory.arrayReplace_oracle();
|
functionFactory.arrayReplace_oracle();
|
||||||
|
functionFactory.arrayTrim_oracle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -644,6 +644,7 @@ public class PostgreSQLDialect extends Dialect {
|
||||||
functionFactory.arrayRemoveIndex_unnest( true );
|
functionFactory.arrayRemoveIndex_unnest( true );
|
||||||
functionFactory.arraySlice_operator();
|
functionFactory.arraySlice_operator();
|
||||||
functionFactory.arrayReplace();
|
functionFactory.arrayReplace();
|
||||||
|
functionFactory.arrayTrim_trim_array();
|
||||||
|
|
||||||
functionFactory.makeDateTimeTimestamp();
|
functionFactory.makeDateTimeTimestamp();
|
||||||
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
||||||
|
|
|
@ -51,6 +51,7 @@ import org.hibernate.dialect.function.array.OracleArrayRemoveIndexFunction;
|
||||||
import org.hibernate.dialect.function.array.OracleArrayReplaceFunction;
|
import org.hibernate.dialect.function.array.OracleArrayReplaceFunction;
|
||||||
import org.hibernate.dialect.function.array.OracleArraySetFunction;
|
import org.hibernate.dialect.function.array.OracleArraySetFunction;
|
||||||
import org.hibernate.dialect.function.array.OracleArraySliceFunction;
|
import org.hibernate.dialect.function.array.OracleArraySliceFunction;
|
||||||
|
import org.hibernate.dialect.function.array.OracleArrayTrimFunction;
|
||||||
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatElementFunction;
|
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatElementFunction;
|
||||||
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatFunction;
|
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatFunction;
|
||||||
import org.hibernate.dialect.function.array.PostgreSQLArrayPositionFunction;
|
import org.hibernate.dialect.function.array.PostgreSQLArrayPositionFunction;
|
||||||
|
@ -3061,4 +3062,33 @@ public class CommonFunctionFactory {
|
||||||
public void arrayReplace_oracle() {
|
public void arrayReplace_oracle() {
|
||||||
functionRegistry.register( "array_replace", new OracleArrayReplaceFunction() );
|
functionRegistry.register( "array_replace", new OracleArrayReplaceFunction() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* H2, HSQLDB, CockroachDB and PostgreSQL array_trim() function
|
||||||
|
*/
|
||||||
|
public void arrayTrim_trim_array() {
|
||||||
|
functionRegistry.patternAggregateDescriptorBuilder( "array_trim", "trim_array(?1,?2)" )
|
||||||
|
.setArgumentsValidator(
|
||||||
|
StandardArgumentsValidators.composite(
|
||||||
|
new ArgumentTypesValidator( null, ANY, INTEGER ),
|
||||||
|
ArrayArgumentValidator.DEFAULT_INSTANCE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setReturnTypeResolver( ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE )
|
||||||
|
.setArgumentTypeResolver(
|
||||||
|
StandardFunctionArgumentTypeResolvers.composite(
|
||||||
|
StandardFunctionArgumentTypeResolvers.invariant( ANY, INTEGER ),
|
||||||
|
StandardFunctionArgumentTypeResolvers.IMPLIED_RESULT_TYPE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setArgumentListSignature( "(ARRAY array, INTEGER elementsToRemove)" )
|
||||||
|
.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oracle array_trim() function
|
||||||
|
*/
|
||||||
|
public void arrayTrim_oracle() {
|
||||||
|
functionRegistry.register( "array_trim", new OracleArrayTrimFunction() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||||
|
*/
|
||||||
|
package org.hibernate.dialect.function.array;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
|
||||||
|
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||||
|
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||||
|
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||||
|
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 static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY;
|
||||||
|
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oracle array_trim function.
|
||||||
|
*/
|
||||||
|
public class OracleArrayTrimFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||||
|
|
||||||
|
public OracleArrayTrimFunction() {
|
||||||
|
super(
|
||||||
|
"array_trim",
|
||||||
|
StandardArgumentsValidators.composite(
|
||||||
|
new ArgumentTypesValidator( null, ANY, INTEGER ),
|
||||||
|
ArrayArgumentValidator.DEFAULT_INSTANCE
|
||||||
|
),
|
||||||
|
ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE,
|
||||||
|
StandardFunctionArgumentTypeResolvers.composite(
|
||||||
|
StandardFunctionArgumentTypeResolvers.invariant( ANY, INTEGER ),
|
||||||
|
StandardFunctionArgumentTypeResolvers.IMPLIED_RESULT_TYPE
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> sqlAstArguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
final String arrayTypeName = DdlTypeHelper.getTypeName(
|
||||||
|
( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(),
|
||||||
|
walker
|
||||||
|
);
|
||||||
|
sqlAppender.append( arrayTypeName );
|
||||||
|
sqlAppender.append( "_trim(" );
|
||||||
|
sqlAstArguments.get( 0 ).accept( walker );
|
||||||
|
sqlAppender.append( ',' );
|
||||||
|
sqlAstArguments.get( 1 ).accept( walker );
|
||||||
|
sqlAppender.append( ')' );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* 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.sql.SQLException;
|
||||||
|
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.PersistenceException;
|
||||||
|
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.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 ArrayTrimTest {
|
||||||
|
|
||||||
|
@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 testTrimOne(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
//tag::hql-array-trim-example[]
|
||||||
|
List<Tuple> results = em.createQuery( "select e.id, array_trim(e.theArray, 1) from EntityWithArrays e where e.id = 2", Tuple.class )
|
||||||
|
.getResultList();
|
||||||
|
//end::hql-array-trim-example[]
|
||||||
|
assertEquals( 1, results.size() );
|
||||||
|
assertEquals( 2L, results.get( 0 ).get( 0 ) );
|
||||||
|
assertArrayEquals( new String[] { "abc", null }, results.get( 0 ).get( 1, String[].class ) );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrimAll(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
List<Tuple> results = em.createQuery( "select e.id, array_trim(e.theArray, 3) from EntityWithArrays e where e.id = 2", Tuple.class )
|
||||||
|
.getResultList();
|
||||||
|
assertEquals( 1, results.size() );
|
||||||
|
assertEquals( 2L, results.get( 0 ).get( 0 ) );
|
||||||
|
assertArrayEquals( new String[0], results.get( 0 ).get( 1, String[].class ) );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrimOutOfRange(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
try {
|
||||||
|
em.createQuery( "select array_trim(e.theArray, 1) from EntityWithArrays e where e.id = 1" )
|
||||||
|
.getResultList();
|
||||||
|
fail( "Should fail because array is too small to trim!" );
|
||||||
|
}
|
||||||
|
catch (PersistenceException ex) {
|
||||||
|
assertInstanceOf( SQLException.class, ex.getCause() );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue