HHH-17355 Add array_positions and array_positions_list functions

This commit is contained in:
Christian Beikov 2023-10-30 18:05:18 +01:00
parent fe9289ba57
commit c3e1815486
31 changed files with 632 additions and 14 deletions

View File

@ -1120,6 +1120,8 @@ The following functions deal with SQL array types, which are not supported on ev
| `array()` | Creates an array based on the passed arguments
| `array_agg()` | Aggregates row values into an array
| `array_position()` | Determines the position of an element in an array
| `array_positions()` | Determines all positions of an element in an array
| `array_positions_list()` | Like `array_positions`, but returns the result as `List<Integer>`
| `array_length()` | Determines the length of an array
| `array_concat()` | Concatenates array with each other in order
| `array_prepend()` | Prepends element to array
@ -1175,6 +1177,20 @@ include::{array-example-dir-hql}/ArrayPositionTest.java[tags=hql-array-position-
----
====
[[hql-array-positions-functions]]
===== `array_positions()` and `array_positions_list()`
Returns an `int[]` of 1-based positions of matching elements in the array. Returns an empty array if the element is not found and `null` if the array is `null`.
To retrieve the result as `List<Integer>`, use the `array_positions_list()` function.
[[hql-array-positions-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArrayPositionsTest.java[tags=hql-array-positions-example]
----
====
[[hql-array-length-functions]]
===== `array_length()`

View File

@ -464,6 +464,7 @@ public class CockroachLegacyDialect extends Dialect {
functionFactory.array_postgresql();
functionFactory.arrayAggregate();
functionFactory.arrayPosition_postgresql();
functionFactory.arrayPositions_postgresql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_postgresql();
functionFactory.arrayPrepend_postgresql();

View File

@ -372,6 +372,8 @@ public class H2LegacyDialect extends Dialect {
functionFactory.listagg( null );
functionFactory.array();
functionFactory.arrayAggregate();
functionFactory.arrayPosition_h2( getMaximumArraySize() );
functionFactory.arrayPositions_h2( getMaximumArraySize() );
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_operator();
functionFactory.arrayPrepend_operator();

View File

@ -250,6 +250,7 @@ public class HSQLLegacyDialect extends Dialect {
functionFactory.array_hsql();
functionFactory.arrayAggregate();
functionFactory.arrayPosition_hsql();
functionFactory.arrayPositions_hsql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_operator();
functionFactory.arrayPrepend_operator();

View File

@ -286,6 +286,7 @@ public class OracleLegacyDialect extends Dialect {
functionFactory.array_oracle();
functionFactory.arrayAggregate_jsonArrayagg();
functionFactory.arrayPosition_oracle();
functionFactory.arrayPositions_oracle();
functionFactory.arrayLength_oracle();
functionFactory.arrayConcat_oracle();
functionFactory.arrayPrepend_oracle();

View File

@ -584,6 +584,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
functionFactory.array_postgresql();
functionFactory.arrayAggregate();
functionFactory.arrayPosition_postgresql();
functionFactory.arrayPositions_postgresql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_postgresql();
functionFactory.arrayPrepend_postgresql();

View File

@ -451,6 +451,7 @@ public class CockroachDialect extends Dialect {
functionFactory.array_postgresql();
functionFactory.arrayAggregate();
functionFactory.arrayPosition_postgresql();
functionFactory.arrayPositions_postgresql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_postgresql();
functionFactory.arrayPrepend_postgresql();

View File

@ -311,6 +311,8 @@ public class H2Dialect extends Dialect {
functionFactory.hypotheticalOrderedSetAggregates();
functionFactory.array();
functionFactory.arrayAggregate();
functionFactory.arrayPosition_h2( getMaximumArraySize() );
functionFactory.arrayPositions_h2( getMaximumArraySize() );
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_operator();
functionFactory.arrayPrepend_operator();

View File

@ -190,6 +190,7 @@ public class HSQLDialect extends Dialect {
functionFactory.array_hsql();
functionFactory.arrayAggregate();
functionFactory.arrayPosition_hsql();
functionFactory.arrayPositions_hsql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_operator();
functionFactory.arrayPrepend_operator();

View File

@ -515,6 +515,32 @@ public class OracleArrayJdbcType extends ArrayJdbcType {
false
)
);
database.addAuxiliaryDatabaseObject(
new NamedAuxiliaryDatabaseObject(
arrayTypeName + "_positions",
database.getDefaultNamespace(),
new String[]{
"create or replace function " + arrayTypeName + "_positions(arr in " + arrayTypeName +
", elem in " + getRawTypeName( elementType ) + ") return sdo_ordinate_array deterministic is " +
"res sdo_ordinate_array:=sdo_ordinate_array(); begin " +
"if arr is null then return null; end if; " +
"if elem is null then " +
"for i in 1 .. arr.count loop " +
"if arr(i) is null then res.extend; res(res.last):=i; end if; " +
"end loop; " +
"else " +
"for i in 1 .. arr.count loop " +
"if arr(i)=elem then res.extend; res(res.last):=i; end if; " +
"end loop; " +
"end if; " +
"return res; " +
"end;"
},
new String[] { "drop function " + arrayTypeName + "_positions" },
emptySet(),
false
)
);
}
protected String createOrReplaceConcatFunction(String arrayTypeName) {

View File

@ -315,6 +315,7 @@ public class OracleDialect extends Dialect {
functionFactory.array_oracle();
functionFactory.arrayAggregate_jsonArrayagg();
functionFactory.arrayPosition_oracle();
functionFactory.arrayPositions_oracle();
functionFactory.arrayLength_oracle();
functionFactory.arrayConcat_oracle();
functionFactory.arrayPrepend_oracle();

View File

@ -632,6 +632,7 @@ public class PostgreSQLDialect extends Dialect {
functionFactory.array_postgresql();
functionFactory.arrayAggregate();
functionFactory.arrayPosition_postgresql();
functionFactory.arrayPositions_postgresql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_postgresql();
functionFactory.arrayPrepend_postgresql();

View File

@ -6,9 +6,12 @@
*/
package org.hibernate.dialect.function;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Arrays;
import java.util.List;
import org.hibernate.annotations.common.reflection.java.generics.ParameterizedTypeImpl;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.dialect.Dialect;
@ -33,6 +36,8 @@ import org.hibernate.dialect.function.array.ElementViaArrayArgumentReturnTypeRes
import org.hibernate.dialect.function.array.H2ArrayContainsFunction;
import org.hibernate.dialect.function.array.H2ArrayFillFunction;
import org.hibernate.dialect.function.array.H2ArrayOverlapsFunction;
import org.hibernate.dialect.function.array.H2ArrayPositionFunction;
import org.hibernate.dialect.function.array.H2ArrayPositionsFunction;
import org.hibernate.dialect.function.array.H2ArrayRemoveFunction;
import org.hibernate.dialect.function.array.H2ArrayRemoveIndexFunction;
import org.hibernate.dialect.function.array.H2ArrayReplaceFunction;
@ -40,6 +45,7 @@ import org.hibernate.dialect.function.array.H2ArraySetFunction;
import org.hibernate.dialect.function.array.HSQLArrayConstructorFunction;
import org.hibernate.dialect.function.array.HSQLArrayFillFunction;
import org.hibernate.dialect.function.array.HSQLArrayPositionFunction;
import org.hibernate.dialect.function.array.HSQLArrayPositionsFunction;
import org.hibernate.dialect.function.array.HSQLArrayRemoveFunction;
import org.hibernate.dialect.function.array.HSQLArraySetFunction;
import org.hibernate.dialect.function.array.OracleArrayConcatElementFunction;
@ -49,6 +55,7 @@ import org.hibernate.dialect.function.array.OracleArrayOverlapsFunction;
import org.hibernate.dialect.function.array.OracleArrayGetFunction;
import org.hibernate.dialect.function.array.OracleArrayLengthFunction;
import org.hibernate.dialect.function.array.OracleArrayPositionFunction;
import org.hibernate.dialect.function.array.OracleArrayPositionsFunction;
import org.hibernate.dialect.function.array.OracleArrayRemoveFunction;
import org.hibernate.dialect.function.array.OracleArrayRemoveIndexFunction;
import org.hibernate.dialect.function.array.OracleArrayReplaceFunction;
@ -63,6 +70,7 @@ import org.hibernate.dialect.function.array.PostgreSQLArrayConstructorFunction;
import org.hibernate.dialect.function.array.OracleArrayAggEmulation;
import org.hibernate.dialect.function.array.OracleArrayConstructorFunction;
import org.hibernate.dialect.function.array.OracleArrayContainsFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayPositionsFunction;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
@ -2738,6 +2746,13 @@ public class CommonFunctionFactory {
functionRegistry.register( "array_position", new PostgreSQLArrayPositionFunction( typeConfiguration ) );
}
/**
* H2 array_position() function
*/
public void arrayPosition_h2(int maximumArraySize) {
functionRegistry.register( "array_position", new H2ArrayPositionFunction( maximumArraySize, typeConfiguration ) );
}
/**
* HSQL array_position() function
*/
@ -2752,6 +2767,62 @@ public class CommonFunctionFactory {
functionRegistry.register( "array_position", new OracleArrayPositionFunction( typeConfiguration ) );
}
/**
* CockroachDB and PostgreSQL array_positions() function
*/
public void arrayPositions_postgresql() {
functionRegistry.register(
"array_positions",
new PostgreSQLArrayPositionsFunction( false, typeConfiguration )
);
functionRegistry.register(
"array_positions_list",
new PostgreSQLArrayPositionsFunction( true, typeConfiguration )
);
}
/**
* H2 array_positions() function
*/
public void arrayPositions_h2(int maximumArraySize) {
functionRegistry.register(
"array_positions",
new H2ArrayPositionsFunction( false, maximumArraySize, typeConfiguration )
);
functionRegistry.register(
"array_positions_list",
new H2ArrayPositionsFunction( true, maximumArraySize, typeConfiguration )
);
}
/**
* HSQL array_positions() function
*/
public void arrayPositions_hsql() {
functionRegistry.register(
"array_positions",
new HSQLArrayPositionsFunction( false, typeConfiguration )
);
functionRegistry.register(
"array_positions_list",
new HSQLArrayPositionsFunction( true, typeConfiguration )
);
}
/**
* Oracle array_positions() function
*/
public void arrayPositions_oracle() {
functionRegistry.register(
"array_positions",
new OracleArrayPositionsFunction( false, typeConfiguration )
);
functionRegistry.register(
"array_positions_list",
new OracleArrayPositionsFunction( true, typeConfiguration )
);
}
/**
* H2, HSQLDB, CockroachDB and PostgreSQL array_length() function
*/

View File

@ -0,0 +1,52 @@
/*
* 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.lang.reflect.Type;
import java.util.List;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.type.internal.ParameterizedTypeImpl;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Encapsulates the validator, return type and argument type resolvers for the array_positions functions.
* Subclasses only have to implement the rendering.
*/
public abstract class AbstractArrayPositionsFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
public AbstractArrayPositionsFunction(boolean list, TypeConfiguration typeConfiguration) {
super(
"array_positions" + ( list ? "_list" : "" ),
new ArgumentTypesValidator(
StandardArgumentsValidators.composite(
StandardArgumentsValidators.exactly( 2 ),
ArrayAndElementArgumentValidator.DEFAULT_INSTANCE
),
FunctionParameterType.ANY,
FunctionParameterType.ANY
),
StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.standardBasicTypeForJavaType(
list
? new ParameterizedTypeImpl( List.class, new Type[]{ Integer.class }, null )
: int[].class
)
),
ArrayAndElementArgumentTypeResolver.DEFAULT_INSTANCE
);
}
@Override
public String getArgumentListSignature() {
return "(ARRAY array, OBJECT element)";
}
}

View File

@ -54,7 +54,7 @@ public class ArrayRemoveIndexUnnestFunction extends AbstractSqmSelfRenderingFunc
final Expression indexExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "case when ");
arrayExpression.accept( walker );
sqlAppender.append( " is null then null else coalesce((select array_agg(t.val) from unnest(" );
sqlAppender.append( " is not null then coalesce((select array_agg(t.val) from unnest(" );
arrayExpression.accept( walker );
sqlAppender.append( ") with ordinality t(val,idx) where t.idx is distinct from " );
indexExpression.accept( walker );

View File

@ -44,7 +44,7 @@ public class ArrayReplaceUnnestFunction extends AbstractSqmSelfRenderingFunction
final Expression newExpression = (Expression) sqlAstArguments.get( 2 );
sqlAppender.append( "case when ");
arrayExpression.accept( walker );
sqlAppender.append( " is null then null else coalesce((select array_agg(case when t.val is not distinct from " );
sqlAppender.append( " is not null then coalesce((select array_agg(case when t.val is not distinct from " );
oldExpression.accept( walker );
sqlAppender.append( " then " );
newExpression.accept( walker );

View File

@ -55,11 +55,11 @@ public class ArraySliceUnnestFunction extends AbstractSqmSelfRenderingFunctionDe
final Expression endIndexExpression = (Expression) sqlAstArguments.get( 2 );
sqlAppender.append( "case when ");
arrayExpression.accept( walker );
sqlAppender.append( " is null or ");
sqlAppender.append( " is not null and ");
startIndexExpression.accept( walker );
sqlAppender.append( " is null or ");
sqlAppender.append( " is not null and ");
endIndexExpression.accept( walker );
sqlAppender.append( " is null then null else coalesce((select array_agg(t.val) from unnest(" );
sqlAppender.append( " is not null then coalesce((select array_agg(t.val) from unnest(" );
arrayExpression.accept( walker );
sqlAppender.append( ") with ordinality t(val,idx) where t.idx between " );
startIndexExpression.accept( walker );

View File

@ -0,0 +1,52 @@
/*
* 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.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 H2ArrayPositionFunction extends AbstractArrayPositionFunction {
private final int maximumArraySize;
public H2ArrayPositionFunction(int maximumArraySize, TypeConfiguration typeConfiguration) {
super( typeConfiguration );
this.maximumArraySize = maximumArraySize;
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final Expression elementExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "case when ");
arrayExpression.accept( walker );
sqlAppender.append( " is not null then coalesce((select i.idx from system_range(1," );
sqlAppender.append( Integer.toString( maximumArraySize ) );
sqlAppender.append( ") i(idx) where i.idx<=coalesce(cardinality(");
arrayExpression.accept( walker );
sqlAppender.append("),0) and array_get(");
arrayExpression.accept( walker );
sqlAppender.append( ",i.idx) is not distinct from " );
elementExpression.accept( walker );
sqlAppender.append( " order by i.idx fetch first 1 row only),0) end" );
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.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 H2ArrayPositionsFunction extends AbstractArrayPositionsFunction {
private final int maximumArraySize;
public H2ArrayPositionsFunction(boolean list, int maximumArraySize, TypeConfiguration typeConfiguration) {
super( list, typeConfiguration );
this.maximumArraySize = maximumArraySize;
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final Expression elementExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "case when ");
arrayExpression.accept( walker );
sqlAppender.append( " is not null then coalesce((select array_agg(i.idx) from system_range(1," );
sqlAppender.append( Integer.toString( maximumArraySize ) );
sqlAppender.append( ") i(idx) where i.idx<=coalesce(cardinality(");
arrayExpression.accept( walker );
sqlAppender.append("),0) and array_get(");
arrayExpression.accept( walker );
sqlAppender.append( ",i.idx) is not distinct from " );
elementExpression.accept( walker );
sqlAppender.append( "),array[]) end" );
}
}

View File

@ -37,7 +37,7 @@ public class H2ArrayRemoveFunction extends AbstractArrayRemoveFunction {
final Expression elementExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "case when ");
arrayExpression.accept( walker );
sqlAppender.append( " is null then null else coalesce((select array_agg(array_get(");
sqlAppender.append( " is not null then coalesce((select array_agg(array_get(");
arrayExpression.accept( walker );
sqlAppender.append(",i.idx)) from system_range(1," );
sqlAppender.append( Integer.toString( maximumArraySize ) );

View File

@ -38,7 +38,7 @@ public class H2ArrayRemoveIndexFunction extends ArrayRemoveIndexUnnestFunction {
final Expression indexExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "case when ");
arrayExpression.accept( walker );
sqlAppender.append( " is null then null else coalesce((select array_agg(array_get(");
sqlAppender.append( " is not null then coalesce((select array_agg(array_get(");
arrayExpression.accept( walker );
sqlAppender.append(",i.idx)) from system_range(1," );
sqlAppender.append( Integer.toString( maximumArraySize ) );

View File

@ -38,7 +38,7 @@ public class H2ArrayReplaceFunction extends ArrayReplaceUnnestFunction {
final Expression newExpression = (Expression) sqlAstArguments.get( 2 );
sqlAppender.append( "case when ");
arrayExpression.accept( walker );
sqlAppender.append( " is null then null else coalesce((select array_agg(case when array_get(");
sqlAppender.append( " is not null then coalesce((select array_agg(case when array_get(");
arrayExpression.accept( walker );
sqlAppender.append(",i.idx) is not distinct from ");
oldExpression.accept( walker );

View File

@ -42,6 +42,6 @@ public class HSQLArrayPositionFunction extends AbstractArrayPositionFunction {
sqlAppender.append( " and t.idx>=" );
sqlAstArguments.get( 2 ).accept( walker );
}
sqlAppender.append( "),0) end" );
sqlAppender.append( " order by t.idx fetch first 1 row only),0) end" );
}
}

View File

@ -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.query.ReturnableType;
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;
/**
* HSQLDB has a special syntax.
*/
public class HSQLArrayPositionsFunction extends AbstractArrayPositionsFunction {
public HSQLArrayPositionsFunction(boolean list, TypeConfiguration typeConfiguration) {
super( list, typeConfiguration );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final Expression elementExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "case when " );
arrayExpression.accept( walker );
sqlAppender.append( " is not null then coalesce((select array_agg(t.idx) from unnest(");
arrayExpression.accept( walker );
sqlAppender.append(") with ordinality t(val,idx) where t.val is not distinct from " );
elementExpression.accept( walker );
sqlAppender.append( "),cast(array[] as integer array)) end" );
}
}

View File

@ -32,7 +32,7 @@ public class HSQLArrayRemoveFunction extends AbstractArrayRemoveFunction {
final Expression elementExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "case when ");
arrayExpression.accept( walker );
sqlAppender.append( " is null then null else coalesce((select array_agg(t.val) from unnest(" );
sqlAppender.append( " is not null then coalesce((select array_agg(t.val) from unnest(" );
arrayExpression.accept( walker );
sqlAppender.append( ") with ordinality t(val,idx) where t.val is distinct from " );
elementExpression.accept( walker );

View File

@ -0,0 +1,39 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function.array;
import java.util.List;
import org.hibernate.query.ReturnableType;
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 OracleArrayPositionsFunction extends AbstractArrayPositionsFunction {
public OracleArrayPositionsFunction(boolean list, TypeConfiguration typeConfiguration) {
super( list, typeConfiguration );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker );
sqlAppender.appendSql( arrayTypeName );
sqlAppender.append( "_positions(" );
arrayExpression.accept( walker );
sqlAppender.append( ',' );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.append( ")" );
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.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;
/**
* PostgreSQL variant of the function.
*/
public class PostgreSQLArrayPositionsFunction extends AbstractArrayPositionsFunction {
public PostgreSQLArrayPositionsFunction(boolean list, TypeConfiguration typeConfiguration) {
super( list, typeConfiguration );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final Expression elementExpression = (Expression) sqlAstArguments.get( 1 );
sqlAppender.append( "array_positions(" );
arrayExpression.accept( walker );
sqlAppender.append( ',' );
elementExpression.accept( walker );
sqlAppender.append( ')' );
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.type.internal;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Objects;
import java.util.StringJoiner;
public class ParameterizedTypeImpl implements ParameterizedType {
private final Type[] substTypeArgs;
private final Type rawType;
private final Type ownerType;
public ParameterizedTypeImpl(Type rawType, Type[] substTypeArgs, Type ownerType) {
this.substTypeArgs = substTypeArgs;
this.rawType = rawType;
this.ownerType = ownerType;
}
public Type[] getActualTypeArguments() {
return substTypeArgs;
}
public Type getRawType() {
return rawType;
}
public Type getOwnerType() {
return ownerType;
}
@Override
public boolean equals(Object obj) {
if ( !( obj instanceof ParameterizedType ) ) {
return false;
}
ParameterizedType other = (ParameterizedType) obj;
return Objects.equals( getOwnerType(), other.getOwnerType() )
&& Objects.equals( getRawType(), other.getRawType() )
&& Arrays.equals( getActualTypeArguments(), other.getActualTypeArguments() );
}
@Override
public int hashCode() {
return Arrays.hashCode( getActualTypeArguments() )
^ Objects.hashCode( getOwnerType() )
^ Objects.hashCode( getRawType() );
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
if ( ownerType != null ) {
sb.append( ownerType.getTypeName() );
sb.append( "$" );
if ( ownerType instanceof ParameterizedType ) {
// Find simple name of nested type by removing the
// shared prefix with owner.
sb.append(
rawType.getTypeName().replace(
( (ParameterizedType) ownerType ).getRawType().getTypeName() + "$",
""
)
);
}
else if ( rawType instanceof Class<?> ) {
sb.append( ( (Class<?>) rawType ).getSimpleName() );
}
else {
sb.append( rawType.getTypeName() );
}
}
else {
sb.append( rawType.getTypeName() );
}
if ( substTypeArgs != null ) {
final StringJoiner sj = new StringJoiner( ", ", "<", ">" );
sj.setEmptyValue( "" );
for ( Type t : substTypeArgs ) {
sj.add( t.getTypeName() );
}
sb.append( sj );
}
return sb.toString();
}
}

View File

@ -37,7 +37,6 @@ import org.hibernate.Internal;
import org.hibernate.SessionFactory;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.TimeZoneStorageStrategy;
import org.hibernate.annotations.common.reflection.java.generics.ParameterizedTypeImpl;
import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.spi.BasicTypeRegistration;
@ -74,6 +73,7 @@ import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.internal.BasicTypeImpl;
import org.hibernate.type.internal.ParameterizedTypeImpl;
import jakarta.persistence.TemporalType;
@ -716,6 +716,20 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
);
}
public BasicType<?> standardBasicTypeForJavaType(Type javaType) {
if ( javaType == null ) {
return null;
}
return standardBasicTypeForJavaType(
javaType,
javaTypeDescriptor -> new BasicTypeImpl<>(
javaTypeDescriptor,
javaTypeDescriptor.getRecommendedJdbcType( getCurrentBaseSqlTypeIndicators() )
)
);
}
public <J> BasicType<J> standardBasicTypeForJavaType(
Class<J> javaType,
Function<JavaType<J>, BasicType<J>> creator) {

View File

@ -9,7 +9,6 @@ package org.hibernate.orm.test.function.array;
import java.util.List;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.H2Dialect;
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;
@ -35,7 +33,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
// 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 = ""))
@SkipForDialect(dialectClass = H2Dialect.class, reason = "H2 does not have an array_position function")
public class ArrayPositionTest {
@BeforeEach

View File

@ -0,0 +1,106 @@
/*
* 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.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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = EntityWithArrays.class)
@SessionFactory
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsStructuralArrays.class)
// Make sure this stuff runs on a dedicated connection pool,
// otherwise we might run into ORA-21700: object does not exist or is marked for delete
// because the JDBC connection or database session caches something that should have been invalidated
@ServiceRegistry(settings = @Setting(name = AvailableSettings.CONNECTION_PROVIDER, value = ""))
public class ArrayPositionsTest {
@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", "abc" } ) );
em.persist( new EntityWithArrays( 3L, null ) );
} );
}
@AfterEach
public void cleanup(SessionFactoryScope scope) {
scope.inTransaction( em -> {
em.createMutationQuery( "delete from EntityWithArrays" ).executeUpdate();
} );
}
@Test
public void testPositions(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-array-positions-example[]
List<int[]> results = em.createQuery( "select array_positions(e.theArray, 'abc') from EntityWithArrays e order by e.id", int[].class )
.getResultList();
//end::hql-array-positions-example[]
assertEquals( 3, results.size() );
assertArrayEquals( new int[0], results.get( 0 ) );
assertArrayEquals( new int[]{ 1, 4 }, results.get( 1 ) );
assertNull( results.get( 2 ) );
} );
}
@Test
public void testPositionsNotFound(SessionFactoryScope scope) {
scope.inSession( em -> {
List<int[]> results = em.createQuery( "select array_positions(e.theArray, 'xyz') from EntityWithArrays e order by e.id", int[].class )
.getResultList();
assertEquals( 3, results.size() );
assertArrayEquals( new int[0], results.get( 0 ) );
assertArrayEquals( new int[0], results.get( 1 ) );
assertNull( results.get( 2 ) );
} );
}
@Test
public void testPositionsNull(SessionFactoryScope scope) {
scope.inSession( em -> {
List<int[]> results = em.createQuery( "select array_positions(e.theArray, null) from EntityWithArrays e order by e.id", int[].class )
.getResultList();
assertEquals( 3, results.size() );
assertArrayEquals( new int[0], results.get( 0 ) );
assertArrayEquals( new int[]{ 2 }, results.get( 1 ) );
assertNull( results.get( 2 ) );
} );
}
@Test
public void testPositionsList(SessionFactoryScope scope) {
scope.inSession( em -> {
List<List<Integer>> results = em.createQuery( "select array_positions_list(e.theArray, null) from EntityWithArrays e order by e.id" )
.getResultList();
assertEquals( 3, results.size() );
assertEquals( List.of(), results.get( 0 ) );
assertEquals( List.of( 2 ), results.get( 1 ) );
assertNull( results.get( 2 ) );
} );
}
}