HHH-17335 Add array_prepend and array_append functions

This commit is contained in:
Christian Beikov 2023-10-24 18:38:30 +02:00
parent 24fa18f954
commit ae3c88ab66
39 changed files with 588 additions and 36 deletions

View File

@ -1123,6 +1123,8 @@ The following functions deal with SQL array types, which are not supported on ev
| `array_position()` | Determines the position of an element in an array
| `array_length()` | Determines the length of an array
| `array_concat()` | Concatenates array with each other in order
| `array_prepend()` | Prepends element to array
| `array_append()` | Appends element to array
| `array_contains_all()` | Determines if one array holds all elements of another array
| `array_contains_all_nullable()` | Determines if one array holds all elements of another array, supporting null elements
| `array_contains_any()` | Determines if one array holds at least one element of another array
@ -1204,6 +1206,32 @@ include::{array-example-dir-hql}/ArrayConcatTest.java[tags=hql-array-concat-exam
----
====
[[hql-array-prepend-functions]]
===== `array_prepend()`
Prepends element to array. Returns `null` if the array argument is `null`.
[[hql-array-prepend-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArrayPrependTest.java[tags=hql-array-prepend-example]
----
====
[[hql-array-append-functions]]
===== `array_append()`
Appends element to array. Returns `null` if the array argument is `null`.
[[hql-array-append-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArrayAppendTest.java[tags=hql-array-append-example]
----
====
[[hql-array-contains-quantified-functions]]
===== `array_contains_all()` and `array_contains_any()`

View File

@ -468,6 +468,8 @@ public class CockroachLegacyDialect extends Dialect {
functionFactory.arrayPosition_postgresql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_postgresql();
functionFactory.arrayPrepend_postgresql();
functionFactory.arrayAppend_postgresql();
functionFactory.arrayContainsAll_operator();
functionFactory.arrayContainsAny_operator();
functionFactory.arrayContainsAllNullable_operator();

View File

@ -376,6 +376,8 @@ public class H2LegacyDialect extends Dialect {
functionFactory.arrayContainsNull();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_operator();
functionFactory.arrayPrepend_operator();
functionFactory.arrayAppend_operator();
functionFactory.arrayContainsAll_h2();
functionFactory.arrayContainsAny_h2();
functionFactory.arrayContainsAllNullable_h2();

View File

@ -254,6 +254,8 @@ public class HSQLLegacyDialect extends Dialect {
functionFactory.arrayPosition_hsql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_operator();
functionFactory.arrayPrepend_operator();
functionFactory.arrayAppend_operator();
functionFactory.arrayContainsAll_hsql();
functionFactory.arrayContainsAny_hsql();
functionFactory.arrayContainsAllNullable_hsql();

View File

@ -290,6 +290,8 @@ public class OracleLegacyDialect extends Dialect {
functionFactory.arrayPosition_oracle();
functionFactory.arrayLength_oracle();
functionFactory.arrayConcat_oracle();
functionFactory.arrayPrepend_oracle();
functionFactory.arrayAppend_oracle();
functionFactory.arrayContainsAll_oracle();
functionFactory.arrayContainsAny_oracle();
functionFactory.arrayContainsAllNullable_oracle();

View File

@ -588,6 +588,8 @@ public class PostgreSQLLegacyDialect extends Dialect {
functionFactory.arrayPosition_postgresql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_postgresql();
functionFactory.arrayPrepend_postgresql();
functionFactory.arrayAppend_postgresql();
functionFactory.arrayContainsAll_operator();
functionFactory.arrayContainsAny_operator();
functionFactory.arrayContainsAllNullable_operator();

View File

@ -455,6 +455,8 @@ public class CockroachDialect extends Dialect {
functionFactory.arrayPosition_postgresql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_postgresql();
functionFactory.arrayPrepend_postgresql();
functionFactory.arrayAppend_postgresql();
functionFactory.arrayContainsAll_operator();
functionFactory.arrayContainsAny_operator();
functionFactory.arrayContainsAllNullable_operator();

View File

@ -315,6 +315,8 @@ public class H2Dialect extends Dialect {
functionFactory.arrayContainsNull();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_operator();
functionFactory.arrayPrepend_operator();
functionFactory.arrayAppend_operator();
functionFactory.arrayContainsAll_h2();
functionFactory.arrayContainsAny_h2();
functionFactory.arrayContainsAllNullable_h2();

View File

@ -194,6 +194,8 @@ public class HSQLDialect extends Dialect {
functionFactory.arrayPosition_hsql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_operator();
functionFactory.arrayPrepend_operator();
functionFactory.arrayAppend_operator();
functionFactory.arrayContainsAll_hsql();
functionFactory.arrayContainsAny_hsql();
functionFactory.arrayContainsAllNullable_hsql();

View File

@ -46,7 +46,7 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
}
public static String getSqlType(CastTarget castTarget, SessionFactoryImplementor factory) {
final String sqlType = getSqlTypeName( castTarget, factory );
final String sqlType = getCastTypeName( castTarget, factory );
return getSqlType( castTarget, sqlType, factory.getJdbcServices().getDialect() );
}

View File

@ -319,6 +319,8 @@ public class OracleDialect extends Dialect {
functionFactory.arrayPosition_oracle();
functionFactory.arrayLength_oracle();
functionFactory.arrayConcat_oracle();
functionFactory.arrayPrepend_oracle();
functionFactory.arrayAppend_oracle();
functionFactory.arrayContainsAll_oracle();
functionFactory.arrayContainsAny_oracle();
functionFactory.arrayContainsAllNullable_oracle();

View File

@ -636,6 +636,8 @@ public class PostgreSQLDialect extends Dialect {
functionFactory.arrayPosition_postgresql();
functionFactory.arrayLength_cardinality();
functionFactory.arrayConcat_postgresql();
functionFactory.arrayPrepend_postgresql();
functionFactory.arrayAppend_postgresql();
functionFactory.arrayContainsAll_operator();
functionFactory.arrayContainsAny_operator();
functionFactory.arrayContainsAllNullable_operator();

View File

@ -16,6 +16,7 @@ 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.ArrayConcatElementFunction;
import org.hibernate.dialect.function.array.ArrayConcatFunction;
import org.hibernate.dialect.function.array.ArrayConstructorFunction;
import org.hibernate.dialect.function.array.ArrayContainsQuantifiedOperatorFunction;
@ -36,6 +37,7 @@ import org.hibernate.dialect.function.array.H2ArraySetFunction;
import org.hibernate.dialect.function.array.HSQLArrayPositionFunction;
import org.hibernate.dialect.function.array.HSQLArrayRemoveFunction;
import org.hibernate.dialect.function.array.HSQLArraySetFunction;
import org.hibernate.dialect.function.array.OracleArrayConcatElementFunction;
import org.hibernate.dialect.function.array.OracleArrayConcatFunction;
import org.hibernate.dialect.function.array.OracleArrayContainsAllFunction;
import org.hibernate.dialect.function.array.OracleArrayContainsAnyFunction;
@ -47,6 +49,7 @@ import org.hibernate.dialect.function.array.OracleArrayRemoveIndexFunction;
import org.hibernate.dialect.function.array.OracleArrayReplaceFunction;
import org.hibernate.dialect.function.array.OracleArraySetFunction;
import org.hibernate.dialect.function.array.OracleArraySliceFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatElementFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayConcatFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayPositionFunction;
import org.hibernate.dialect.function.array.CastingArrayConstructorFunction;
@ -2946,6 +2949,48 @@ public class CommonFunctionFactory {
functionRegistry.register( "array_concat", new OracleArrayConcatFunction() );
}
/**
* H2 and HSQLDB array_prepend() function
*/
public void arrayPrepend_operator() {
functionRegistry.register( "array_prepend", new ArrayConcatElementFunction( "", "||", "", true ) );
}
/**
* CockroachDB and PostgreSQL array_prepend() function
*/
public void arrayPrepend_postgresql() {
functionRegistry.register( "array_prepend", new PostgreSQLArrayConcatElementFunction( true ) );
}
/**
* Oracle array_prepend() function
*/
public void arrayPrepend_oracle() {
functionRegistry.register( "array_prepend", new OracleArrayConcatElementFunction( true ) );
}
/**
* H2 and HSQLDB array_append() function
*/
public void arrayAppend_operator() {
functionRegistry.register( "array_append", new ArrayConcatElementFunction( "", "||", "", false ) );
}
/**
* CockroachDB and PostgreSQL array_append() function
*/
public void arrayAppend_postgresql() {
functionRegistry.register( "array_append", new PostgreSQLArrayConcatElementFunction( false ) );
}
/**
* Oracle array_append() function
*/
public void arrayAppend_oracle() {
functionRegistry.register( "array_append", new OracleArrayConcatElementFunction( false ) );
}
/**
* H2 array_get() function via bracket syntax
*/

View File

@ -43,7 +43,7 @@ public class ArrayAndElementArgumentTypeResolver implements FunctionArgumentType
final SqmTypedNode<?> argument = function.getArguments().get( elementIndex );
final DomainType<?> sqmType = argument.getExpressible().getSqmType();
if ( sqmType instanceof ReturnableType<?> ) {
return ArrayTypeHelper.resolveArrayType(
return DdlTypeHelper.resolveArrayType(
sqmType,
converter.getCreationContext().getSessionFactory().getTypeConfiguration()
);

View File

@ -0,0 +1,73 @@
/*
* 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.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
/**
* Concatenation function for array and an element.
*/
public class ArrayConcatElementFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
private final String prefix;
private final String separator;
private final String suffix;
protected final boolean prepend;
public ArrayConcatElementFunction(String prefix, String separator, String suffix, boolean prepend) {
super(
"array_" + ( prepend ? "prepend" : "append" ),
StandardArgumentsValidators.composite(
StandardArgumentsValidators.exactly( 2 ),
prepend ? new ArrayAndElementArgumentValidator( 1, 0 )
: ArrayAndElementArgumentValidator.DEFAULT_INSTANCE
),
prepend ? new ArrayViaArgumentReturnTypeResolver( 1 )
: ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE,
prepend ? new ArrayAndElementArgumentTypeResolver( 1, 0 )
: ArrayAndElementArgumentTypeResolver.DEFAULT_INSTANCE
);
this.prefix = prefix;
this.separator = separator;
this.suffix = suffix;
this.prepend = prepend;
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final SqlAstNode firstArgument = sqlAstArguments.get( 0 );
final SqlAstNode secondArgument = sqlAstArguments.get( 1 );
sqlAppender.append( prefix );
if ( prepend ) {
sqlAppender.append( "array[" );
firstArgument.accept( walker );
sqlAppender.append( ']' );
}
else {
firstArgument.accept( walker );
}
sqlAppender.append( separator );
if ( prepend ) {
secondArgument.accept( walker );
}
else {
sqlAppender.append( "array[" );
secondArgument.accept( walker );
sqlAppender.append( ']' );
}
sqlAppender.append( suffix );
}
}

View File

@ -48,7 +48,7 @@ public class ArrayContainsOperatorFunction extends AbstractSqmSelfRenderingFunct
sqlAppender.append( "cast(array[" );
elementExpression.accept( walker );
sqlAppender.append( "] as " );
sqlAppender.append( ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ) );
sqlAppender.append( DdlTypeHelper.getCastTypeName( arrayExpression.getExpressionType(), walker ) );
sqlAppender.append( ')' );
}
else {

View File

@ -59,7 +59,7 @@ public class ArrayRemoveIndexUnnestFunction extends AbstractSqmSelfRenderingFunc
sqlAppender.append( "),");
if ( castEmptyArrayLiteral ) {
sqlAppender.append( "cast(array[] as " );
sqlAppender.append( ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ) );
sqlAppender.append( DdlTypeHelper.getCastTypeName( arrayExpression.getExpressionType(), walker ) );
sqlAppender.append( ')' );
}
else {

View File

@ -66,7 +66,7 @@ public class ArraySliceUnnestFunction extends AbstractSqmSelfRenderingFunctionDe
sqlAppender.append( "),");
if ( castEmptyArrayLiteral ) {
sqlAppender.append( "cast(array[] as " );
sqlAppender.append( ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker ) );
sqlAppender.append( DdlTypeHelper.getCastTypeName( arrayExpression.getExpressionType(), walker ) );
sqlAppender.append( ')' );
}
else {

View File

@ -30,7 +30,7 @@ public class ArrayViaArgumentReturnTypeResolver implements FunctionReturnTypeRes
private final int arrayIndex;
private ArrayViaArgumentReturnTypeResolver(int arrayIndex) {
public ArrayViaArgumentReturnTypeResolver(int arrayIndex) {
this.arrayIndex = arrayIndex;
}

View File

@ -50,7 +50,7 @@ public class ArrayViaElementArgumentReturnTypeResolver implements FunctionReturn
for ( SqmTypedNode<?> argument : arguments ) {
final DomainType<?> sqmType = argument.getExpressible().getSqmType();
if ( sqmType instanceof ReturnableType<?> ) {
return ArrayTypeHelper.resolveArrayType( sqmType, typeConfiguration );
return DdlTypeHelper.resolveArrayType( sqmType, typeConfiguration );
}
}
return null;

View File

@ -22,7 +22,7 @@ import org.hibernate.type.descriptor.sql.DdlType;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
public class ArrayTypeHelper {
public class DdlTypeHelper {
@SuppressWarnings("unchecked")
public static BasicType<?> resolveArrayType(DomainType<?> elementType, TypeConfiguration typeConfiguration) {
@SuppressWarnings("unchecked") final BasicPluralJavaType<Object> arrayJavaType = (BasicPluralJavaType<Object>) typeConfiguration.getJavaTypeRegistry()
@ -39,18 +39,34 @@ public class ArrayTypeHelper {
);
}
public static String getArrayTypeName(JdbcMappingContainer arrayType, SqlAstTranslator<?> walker) {
if ( arrayType instanceof SqlTypedMapping ) {
return AbstractSqlAstTranslator.getSqlTypeName( (SqlTypedMapping) arrayType, walker.getSessionFactory() );
public static String getTypeName(JdbcMappingContainer type, SqlAstTranslator<?> walker) {
if ( type instanceof SqlTypedMapping ) {
return AbstractSqlAstTranslator.getSqlTypeName( (SqlTypedMapping) type, walker.getSessionFactory() );
}
else {
final BasicPluralType<?, ?> pluralType = (BasicPluralType<?, ?>) arrayType.getSingleJdbcMapping();
final BasicType<?> jdbcMapping = (BasicType<?>) type.getSingleJdbcMapping();
final TypeConfiguration typeConfiguration = walker.getSessionFactory().getTypeConfiguration();
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
final DdlType ddlType = ddlTypeRegistry.getDescriptor(
pluralType.getJdbcType().getDdlTypeCode()
jdbcMapping.getJdbcType().getDdlTypeCode()
);
return ddlType.getCastTypeName( Size.nil(), pluralType, ddlTypeRegistry );
return ddlType.getTypeName( Size.nil(), jdbcMapping, ddlTypeRegistry );
}
}
public static String getCastTypeName(JdbcMappingContainer type, SqlAstTranslator<?> walker) {
if ( type instanceof SqlTypedMapping ) {
return AbstractSqlAstTranslator.getCastTypeName( (SqlTypedMapping) type, walker.getSessionFactory() );
}
else {
final BasicType<?> jdbcMapping = (BasicType<?>) type.getSingleJdbcMapping();
final TypeConfiguration typeConfiguration = walker.getSessionFactory().getTypeConfiguration();
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
final DdlType ddlType = ddlTypeRegistry.getDescriptor(
jdbcMapping.getJdbcType().getDdlTypeCode()
);
return ddlType.getCastTypeName( Size.nil(), jdbcMapping, ddlTypeRegistry );
}
}
}

View File

@ -0,0 +1,60 @@
/*
* 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;
/**
* Oracle concatenation function for array and an element.
*/
public class OracleArrayConcatElementFunction extends ArrayConcatElementFunction {
public OracleArrayConcatElementFunction(boolean prepend) {
super( "(", ",", ")", prepend );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final Expression firstArgument = (Expression) sqlAstArguments.get( 0 );
final Expression secondArgument = (Expression) sqlAstArguments.get( 1 );
final String arrayTypeName = DdlTypeHelper.getTypeName(
prepend ? secondArgument.getExpressionType()
: firstArgument.getExpressionType(),
walker
);
sqlAppender.append( arrayTypeName );
sqlAppender.append( "_concat(" );
if ( prepend ) {
sqlAppender.append( arrayTypeName );
sqlAppender.append( '(' );
firstArgument.accept( walker );
sqlAppender.append( ')' );
}
else {
firstArgument.accept( walker );
}
sqlAppender.append( ',' );
if ( prepend ) {
secondArgument.accept( walker );
}
else {
sqlAppender.append( arrayTypeName );
sqlAppender.append( '(' );
secondArgument.accept( walker );
sqlAppender.append( ')' );
}
sqlAppender.append( ')' );
}
}

View File

@ -36,7 +36,7 @@ public class OracleArrayConcatFunction extends ArrayConcatFunction {
}
}
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( expressionType, walker );
final String arrayTypeName = DdlTypeHelper.getTypeName( expressionType, walker );
sqlAppender.append( arrayTypeName );
sqlAppender.append( "_concat" );
super.render( sqlAppender, sqlAstArguments, walker );

View File

@ -29,7 +29,7 @@ public class OracleArrayContainsAllFunction extends AbstractArrayContainsQuantif
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 );
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( haystackExpression.getExpressionType(), walker );
final String arrayTypeName = DdlTypeHelper.getTypeName( haystackExpression.getExpressionType(), walker );
sqlAppender.appendSql( arrayTypeName );
sqlAppender.append( "_contains_all(" );
haystackExpression.accept( walker );

View File

@ -29,7 +29,7 @@ public class OracleArrayContainsAnyFunction extends AbstractArrayContainsQuantif
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 );
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( haystackExpression.getExpressionType(), walker );
final String arrayTypeName = DdlTypeHelper.getTypeName( haystackExpression.getExpressionType(), walker );
sqlAppender.appendSql( arrayTypeName );
sqlAppender.append( "_contains_any(" );
haystackExpression.accept( walker );

View File

@ -37,7 +37,7 @@ public class OracleArrayContainsFunction extends AbstractSqmSelfRenderingFunctio
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker );
final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker );
sqlAppender.appendSql( arrayTypeName );
sqlAppender.append( "_position(" );
arrayExpression.accept( walker );

View File

@ -37,7 +37,7 @@ public class OracleArrayContainsNullFunction extends AbstractSqmSelfRenderingFun
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker );
final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker );
sqlAppender.appendSql( arrayTypeName );
sqlAppender.append( "_position(" );
arrayExpression.accept( walker );

View File

@ -26,7 +26,7 @@ public class OracleArrayGetFunction extends ArrayGetUnnestFunction {
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName(
final String arrayTypeName = DdlTypeHelper.getTypeName(
( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(),
walker
);

View File

@ -37,7 +37,7 @@ public class OracleArrayLengthFunction extends AbstractSqmSelfRenderingFunctionD
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker );
final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker );
sqlAppender.appendSql( arrayTypeName );
sqlAppender.append( "_length(" );
arrayExpression.accept( walker );

View File

@ -26,7 +26,7 @@ public class OracleArrayPositionFunction extends AbstractArrayPositionFunction {
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName( arrayExpression.getExpressionType(), walker );
final String arrayTypeName = DdlTypeHelper.getTypeName( arrayExpression.getExpressionType(), walker );
sqlAppender.appendSql( arrayTypeName );
sqlAppender.append( "_position(" );
arrayExpression.accept( walker );

View File

@ -26,7 +26,7 @@ public class OracleArrayRemoveFunction extends AbstractArrayRemoveFunction {
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName(
final String arrayTypeName = DdlTypeHelper.getTypeName(
( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(),
walker
);

View File

@ -27,7 +27,7 @@ public class OracleArrayRemoveIndexFunction extends ArrayRemoveIndexUnnestFuncti
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName(
final String arrayTypeName = DdlTypeHelper.getTypeName(
( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(),
walker
);

View File

@ -23,7 +23,7 @@ public class OracleArrayReplaceFunction extends ArrayReplaceUnnestFunction {
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName(
final String arrayTypeName = DdlTypeHelper.getTypeName(
( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(),
walker
);

View File

@ -26,7 +26,7 @@ public class OracleArraySetFunction extends ArraySetUnnestFunction {
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName(
final String arrayTypeName = DdlTypeHelper.getTypeName(
( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(),
walker
);

View File

@ -27,7 +27,7 @@ public class OracleArraySliceFunction extends ArraySliceUnnestFunction {
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final String arrayTypeName = ArrayTypeHelper.getArrayTypeName(
final String arrayTypeName = DdlTypeHelper.getTypeName(
( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(),
walker
);

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.dialect.function.array;
import java.util.List;
import org.hibernate.metamodel.mapping.JdbcMapping;
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.sql.ast.tree.expression.Literal;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BottomType;
/**
* PostgreSQL variant of the function to properly return {@code null} when the array argument is null.
*/
public class PostgreSQLArrayConcatElementFunction extends ArrayConcatElementFunction {
public PostgreSQLArrayConcatElementFunction(boolean prepend) {
super( "", "||", "", prepend );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
final Expression firstArgument = (Expression) sqlAstArguments.get( 0 );
final Expression secondArgument = (Expression) sqlAstArguments.get( 1 );
final Expression arrayArgument;
final Expression elementArgument;
if ( prepend ) {
elementArgument = firstArgument;
arrayArgument = secondArgument;
}
else {
arrayArgument = firstArgument;
elementArgument = secondArgument;
}
final String elementCastType;
if ( needsElementCasting( elementArgument ) ) {
final JdbcMapping elementType = elementArgument.getExpressionType().getSingleJdbcMapping();
if ( elementType instanceof BottomType ) {
elementCastType = DdlTypeHelper.getCastTypeName(
( (BasicPluralType<?, ?>) arrayArgument.getExpressionType().getSingleJdbcMapping() )
.getElementType(),
walker
);
}
else {
elementCastType = DdlTypeHelper.getCastTypeName( elementType, walker );
}
}
else {
elementCastType = null;
}
sqlAppender.append( "case when " );
arrayArgument.accept( walker );
sqlAppender.append( " is not null then " );
if ( prepend && elementCastType != null) {
sqlAppender.append( "cast(" );
firstArgument.accept( walker );
sqlAppender.append( " as " );
sqlAppender.append( elementCastType );
sqlAppender.append( ')' );
}
else {
firstArgument.accept( walker );
}
sqlAppender.append( "||" );
if ( !prepend && elementCastType != null) {
sqlAppender.append( "cast(" );
secondArgument.accept( walker );
sqlAppender.append( " as " );
sqlAppender.append( elementCastType );
sqlAppender.append( ')' );
}
else {
secondArgument.accept( walker );
}
sqlAppender.append( " end" );
}
private static boolean needsElementCasting(Expression elementExpression) {
// PostgreSQL needs casting of null and string literal expressions
return elementExpression instanceof Literal && (
elementExpression.getExpressionType().getSingleJdbcMapping().getJdbcType().isString()
|| ( (Literal) elementExpression ).getLiteralValue() == null
);
}
}

View File

@ -6263,7 +6263,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
@Override
public void visitCastTarget(CastTarget castTarget) {
appendSql( getSqlTypeName( castTarget, sessionFactory ) );
appendSql( getCastTypeName( castTarget, sessionFactory ) );
}
public static String getSqlTypeName(SqlTypedMapping castTarget, SessionFactoryImplementor factory) {
@ -6273,7 +6273,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
else {
final Size castTargetSize = castTarget.toSize();
final DdlTypeRegistry ddlTypeRegistry = factory.getTypeConfiguration().getDdlTypeRegistry();
final SqlExpressible expressionType = (SqlExpressible) castTarget.getJdbcMapping();
final BasicType<?> expressionType = (BasicType<?>) castTarget.getJdbcMapping();
if ( expressionType instanceof BasicPluralType<?, ?> ) {
final BasicPluralType<?, ?> containerType = (BasicPluralType<?, ?>) expressionType;
final BasicPluralJavaType<?> javaTypeDescriptor = (BasicPluralJavaType<?>) containerType.getJavaTypeDescriptor();
@ -6293,19 +6293,52 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
return arrayTypeName;
}
}
DdlType ddlType = ddlTypeRegistry
.getDescriptor( expressionType.getJdbcMapping().getJdbcType().getDdlTypeCode() );
DdlType ddlType = ddlTypeRegistry.getDescriptor( expressionType.getJdbcType().getDdlTypeCode() );
if ( ddlType == null ) {
// this may happen when selecting a null value like `SELECT null from ...`
// some dbs need the value to be cast so not knowing the real type we fall back to INTEGER
ddlType = ddlTypeRegistry.getDescriptor( SqlTypes.INTEGER );
}
return ddlType.getCastTypeName(
return ddlType.getTypeName( castTargetSize, expressionType, ddlTypeRegistry );
}
}
public static String getCastTypeName(SqlTypedMapping castTarget, SessionFactoryImplementor factory) {
if ( castTarget.getColumnDefinition() != null ) {
return castTarget.getColumnDefinition();
}
else {
final Size castTargetSize = castTarget.toSize();
final DdlTypeRegistry ddlTypeRegistry = factory.getTypeConfiguration().getDdlTypeRegistry();
final BasicType<?> expressionType = (BasicType<?>) castTarget.getJdbcMapping();
if ( expressionType instanceof BasicPluralType<?, ?> ) {
final BasicPluralType<?, ?> containerType = (BasicPluralType<?, ?>) expressionType;
final BasicPluralJavaType<?> javaTypeDescriptor = (BasicPluralJavaType<?>) containerType.getJavaTypeDescriptor();
final BasicType<?> elementType = containerType.getElementType();
final String elementTypeName = ddlTypeRegistry.getDescriptor( elementType.getJdbcType().getDdlTypeCode() )
.getCastTypeName(
castTargetSize,
expressionType,
elementType,
ddlTypeRegistry
);
final String arrayTypeName = factory.getJdbcServices().getDialect().getArrayTypeName(
javaTypeDescriptor.getElementJavaType().getJavaTypeClass().getSimpleName(),
elementTypeName,
null
);
if ( arrayTypeName != null ) {
return arrayTypeName;
}
}
DdlType ddlType = ddlTypeRegistry.getDescriptor( expressionType.getJdbcType().getDdlTypeCode() );
if ( ddlType == null ) {
// this may happen when selecting a null value like `SELECT null from ...`
// some dbs need the value to be cast so not knowing the real type we fall back to INTEGER
ddlType = ddlTypeRegistry.getDescriptor( SqlTypes.INTEGER );
}
return ddlType.getCastTypeName( castTargetSize, expressionType, ddlTypeRegistry );
}
}

View File

@ -0,0 +1,90 @@
/*
* 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 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.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 ArrayAppendTest {
@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 testAppend(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-array-append-example[]
List<Tuple> results = em.createQuery( "select e.id, array_append(e.theArray, 'xyz') from EntityWithArrays e order by e.id", Tuple.class )
.getResultList();
//end::hql-array-append-example[]
assertEquals( 3, results.size() );
assertEquals( 1L, results.get( 0 ).get( 0 ) );
assertArrayEquals( new String[]{ "xyz" }, results.get( 0 ).get( 1, String[].class ) );
assertEquals( 2L, results.get( 1 ).get( 0 ) );
assertArrayEquals( new String[]{ "abc", null, "def", "xyz" }, results.get( 1 ).get( 1, String[].class ) );
assertEquals( 3L, results.get( 2 ).get( 0 ) );
assertNull( results.get( 2 ).get( 1, String[].class ) );
} );
}
@Test
public void testAppendNull(SessionFactoryScope scope) {
scope.inSession( em -> {
List<Tuple> results = em.createQuery( "select e.id, array_append(e.theArray, null) from EntityWithArrays e order by e.id", Tuple.class )
.getResultList();
assertEquals( 3, results.size() );
assertEquals( 1L, results.get( 0 ).get( 0 ) );
assertArrayEquals( new String[]{ null }, results.get( 0 ).get( 1, String[].class ) );
assertEquals( 2L, results.get( 1 ).get( 0 ) );
assertArrayEquals( new String[]{ "abc", null, "def", null }, results.get( 1 ).get( 1, String[].class ) );
assertEquals( 3L, results.get( 2 ).get( 0 ) );
assertNull( results.get( 2 ).get( 1, String[].class ) );
} );
}
}

View File

@ -0,0 +1,90 @@
/*
* 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 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.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 ArrayPrependTest {
@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 testPrepend(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-array-prepend-example[]
List<Tuple> results = em.createQuery( "select e.id, array_prepend('xyz', e.theArray) from EntityWithArrays e order by e.id", Tuple.class )
.getResultList();
//end::hql-array-prepend-example[]
assertEquals( 3, results.size() );
assertEquals( 1L, results.get( 0 ).get( 0 ) );
assertArrayEquals( new String[]{ "xyz" }, results.get( 0 ).get( 1, String[].class ) );
assertEquals( 2L, results.get( 1 ).get( 0 ) );
assertArrayEquals( new String[]{ "xyz", "abc", null, "def" }, results.get( 1 ).get( 1, String[].class ) );
assertEquals( 3L, results.get( 2 ).get( 0 ) );
assertNull( results.get( 2 ).get( 1, String[].class ) );
} );
}
@Test
public void testPrependNull(SessionFactoryScope scope) {
scope.inSession( em -> {
List<Tuple> results = em.createQuery( "select e.id, array_prepend(null, e.theArray) from EntityWithArrays e order by e.id", Tuple.class )
.getResultList();
assertEquals( 3, results.size() );
assertEquals( 1L, results.get( 0 ).get( 0 ) );
assertArrayEquals( new String[]{ null }, results.get( 0 ).get( 1, String[].class ) );
assertEquals( 2L, results.get( 1 ).get( 0 ) );
assertArrayEquals( new String[]{ null, "abc", null, "def" }, results.get( 1 ).get( 1, String[].class ) );
assertEquals( 3L, results.get( 2 ).get( 0 ) );
assertNull( results.get( 2 ).get( 1, String[].class ) );
} );
}
}