HHH-17355 Add array_fill function

This commit is contained in:
Christian Beikov 2023-10-30 14:38:27 +01:00
parent 6d392f5e20
commit 1a5184e89b
37 changed files with 521 additions and 79 deletions

View File

@ -1135,6 +1135,7 @@ The following functions deal with SQL array types, which are not supported on ev
| `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_trim()` | Creates array copy trimming the last _N_ elements
| `array_fill()` | Creates array filled with the same element _N_ times
|===
===== `array()`
@ -1399,6 +1400,19 @@ include::{array-example-dir-hql}/ArrayTrimTest.java[tags=hql-array-trim-example]
----
====
===== `array_fill()`
Creates an array filled with the same element _N_ times as specified by the arguments.
It is an error to supply an array length smaller than 0.
[[hql-array-fill-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArrayFillTest.java[tags=hql-array-fill-example]
----
====
[[hql-user-defined-functions]]
==== Native and user-defined functions

View File

@ -477,6 +477,7 @@ public class CockroachLegacyDialect extends Dialect {
functionFactory.arraySlice_operator();
functionFactory.arrayReplace();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_postgresql();
functionContributions.getFunctionRegistry().register(
"trunc",

View File

@ -385,6 +385,7 @@ public class H2LegacyDialect extends Dialect {
functionFactory.arraySlice();
functionFactory.arrayReplace_h2( getMaximumArraySize() );
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_h2();
}
else {
// Use group_concat until 2.x as listagg was buggy

View File

@ -263,6 +263,7 @@ public class HSQLLegacyDialect extends Dialect {
functionFactory.arraySlice_unnest();
functionFactory.arrayReplace_unnest();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_hsql();
}
@Override

View File

@ -299,6 +299,7 @@ public class OracleLegacyDialect extends Dialect {
functionFactory.arraySlice_oracle();
functionFactory.arrayReplace_oracle();
functionFactory.arrayTrim_oracle();
functionFactory.arrayFill_oracle();
}
@Override

View File

@ -597,6 +597,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
functionFactory.arraySlice_operator();
functionFactory.arrayReplace();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_postgresql();
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
functionFactory.makeDateTimeTimestamp();

View File

@ -464,6 +464,7 @@ public class CockroachDialect extends Dialect {
functionFactory.arraySlice_operator();
functionFactory.arrayReplace();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_postgresql();
functionContributions.getFunctionRegistry().register(
"trunc",

View File

@ -324,6 +324,7 @@ public class H2Dialect extends Dialect {
functionFactory.arraySlice();
functionFactory.arrayReplace_h2( getMaximumArraySize() );
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_h2();
}
/**

View File

@ -203,6 +203,7 @@ public class HSQLDialect extends Dialect {
functionFactory.arraySlice_unnest();
functionFactory.arrayReplace_unnest();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_hsql();
}
@Override

View File

@ -493,6 +493,28 @@ public class OracleArrayJdbcType extends ArrayJdbcType {
false
)
);
database.addAuxiliaryDatabaseObject(
new NamedAuxiliaryDatabaseObject(
arrayTypeName + "_fill",
database.getDefaultNamespace(),
new String[]{
"create or replace function " + arrayTypeName + "_fill(elem in " + getRawTypeName( elementType ) +
", elems number) return " + arrayTypeName + " deterministic is " +
"res " + arrayTypeName + ":=" + arrayTypeName + "(); begin " +
"if elems is null then return null; end if; " +
"if elems<0 then raise_application_error (-20000, 'number of elements must be greater than or equal to 0'); end if;" +
"for i in 1 .. elems loop " +
"res.extend; " +
"res(i) := elem; " +
"end loop; " +
"return res; " +
"end;"
},
new String[] { "drop function " + arrayTypeName + "_fill" },
emptySet(),
false
)
);
}
protected String createOrReplaceConcatFunction(String arrayTypeName) {

View File

@ -328,6 +328,7 @@ public class OracleDialect extends Dialect {
functionFactory.arraySlice_oracle();
functionFactory.arrayReplace_oracle();
functionFactory.arrayTrim_oracle();
functionFactory.arrayFill_oracle();
}
@Override

View File

@ -645,6 +645,7 @@ public class PostgreSQLDialect extends Dialect {
functionFactory.arraySlice_operator();
functionFactory.arrayReplace();
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_postgresql();
functionFactory.makeDateTimeTimestamp();
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions

View File

@ -31,17 +31,20 @@ import org.hibernate.dialect.function.array.ArraySliceUnnestFunction;
import org.hibernate.dialect.function.array.ArrayViaArgumentReturnTypeResolver;
import org.hibernate.dialect.function.array.ElementViaArrayArgumentReturnTypeResolver;
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.H2ArrayRemoveFunction;
import org.hibernate.dialect.function.array.H2ArrayRemoveIndexFunction;
import org.hibernate.dialect.function.array.H2ArrayReplaceFunction;
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.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.OracleArrayFillFunction;
import org.hibernate.dialect.function.array.OracleArrayOverlapsFunction;
import org.hibernate.dialect.function.array.OracleArrayGetFunction;
import org.hibernate.dialect.function.array.OracleArrayLengthFunction;
@ -54,6 +57,7 @@ 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.PostgreSQLArrayConcatFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayFillFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayPositionFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayConstructorFunction;
import org.hibernate.dialect.function.array.OracleArrayAggEmulation;
@ -3091,4 +3095,32 @@ public class CommonFunctionFactory {
public void arrayTrim_oracle() {
functionRegistry.register( "array_trim", new OracleArrayTrimFunction() );
}
/**
* H2 array_fill() function
*/
public void arrayFill_h2() {
functionRegistry.register( "array_fill", new H2ArrayFillFunction() );
}
/**
* HSQLDB array_fill() function
*/
public void arrayFill_hsql() {
functionRegistry.register( "array_fill", new HSQLArrayFillFunction() );
}
/**
* PostgreSQL array_fill() function
*/
public void arrayFill_postgresql() {
functionRegistry.register( "array_fill", new PostgreSQLArrayFillFunction() );
}
/**
* Oracle array_fill() function
*/
public void arrayFill_oracle() {
functionRegistry.register( "array_fill", new OracleArrayFillFunction() );
}
}

View File

@ -0,0 +1,63 @@
/*
* 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 org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.type.BasicPluralType;
/**
* Encapsulates the validator, return type and argument type resolvers for the array_contains function.
* Subclasses only have to implement the rendering.
*/
public abstract class AbstractArrayFillFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
public AbstractArrayFillFunction() {
super(
"array_fill",
new ArgumentTypesValidator( null, FunctionParameterType.NO_UNTYPED, FunctionParameterType.INTEGER ),
ArrayViaElementArgumentReturnTypeResolver.DEFAULT_INSTANCE,
ArrayFillArgumentsValidator.INSTANCE
);
}
@Override
public String getArgumentListSignature() {
return "(OBJECT element, INTEGER elementCount)";
}
private static class ArrayFillArgumentsValidator implements FunctionArgumentTypeResolver {
public static final FunctionArgumentTypeResolver INSTANCE = new ArrayFillArgumentsValidator();
private ArrayFillArgumentsValidator() {
}
@Override
public MappingModelExpressible<?> resolveFunctionArgumentType(
SqmFunction<?> function,
int argumentIndex,
SqmToSqlAstConverter converter) {
if ( argumentIndex == 0 ) {
final MappingModelExpressible<?> impliedReturnType = converter.resolveFunctionImpliedReturnType();
return impliedReturnType instanceof BasicPluralType<?, ?>
? ( (BasicPluralType<?, ?>) impliedReturnType ).getElementType()
: null;
}
else {
return converter.getCreationContext().getSessionFactory().getTypeConfiguration().getBasicTypeRegistry()
.getRegisteredType( Integer.class );
}
}
}
}

View File

@ -39,7 +39,7 @@ public class ArrayAggFunction extends AbstractSqmSelfRenderingFunctionDescriptor
FUNCTION_NAME,
FunctionKind.ORDERED_SET_AGGREGATE,
StandardArgumentsValidators.exactly( 1 ),
ArrayViaElementArgumentReturnTypeResolver.INSTANCE,
ArrayViaElementArgumentReturnTypeResolver.VARARGS_INSTANCE,
StandardFunctionArgumentTypeResolvers.NULL
);
this.functionName = functionName;

View File

@ -33,7 +33,7 @@ public class ArrayConstructorFunction extends AbstractSqmSelfRenderingFunctionDe
super(
"array",
ArrayConstructorArgumentsValidator.INSTANCE,
ArrayViaElementArgumentReturnTypeResolver.INSTANCE,
ArrayViaElementArgumentReturnTypeResolver.VARARGS_INSTANCE,
StandardFunctionArgumentTypeResolvers.NULL
);
this.withKeyword = withKeyword;

View File

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

View File

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

View File

@ -24,9 +24,13 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public class ArrayViaElementArgumentReturnTypeResolver implements FunctionReturnTypeResolver {
public static final FunctionReturnTypeResolver INSTANCE = new ArrayViaElementArgumentReturnTypeResolver();
public static final FunctionReturnTypeResolver DEFAULT_INSTANCE = new ArrayViaElementArgumentReturnTypeResolver( 0 );
public static final FunctionReturnTypeResolver VARARGS_INSTANCE = new ArrayViaElementArgumentReturnTypeResolver( -1 );
private ArrayViaElementArgumentReturnTypeResolver() {
private final int elementIndex;
private ArrayViaElementArgumentReturnTypeResolver(int elementIndex) {
this.elementIndex = elementIndex;
}
@Override
@ -47,8 +51,16 @@ public class ArrayViaElementArgumentReturnTypeResolver implements FunctionReturn
if ( impliedType != null ) {
return impliedType;
}
for ( SqmTypedNode<?> argument : arguments ) {
final DomainType<?> sqmType = argument.getExpressible().getSqmType();
if ( elementIndex == -1 ) {
for ( SqmTypedNode<?> argument : arguments ) {
final DomainType<?> sqmType = argument.getExpressible().getSqmType();
if ( sqmType instanceof ReturnableType<?> ) {
return DdlTypeHelper.resolveArrayType( sqmType, typeConfiguration );
}
}
}
else {
final DomainType<?> sqmType = arguments.get( elementIndex ).getExpressible().getSqmType();
if ( sqmType instanceof ReturnableType<?> ) {
return DdlTypeHelper.resolveArrayType( sqmType, typeConfiguration );
}

View File

@ -40,21 +40,44 @@ public class DdlTypeHelper {
);
}
public static String getTypeName(BasicType<?> type, SqlAstTranslator<?> walker) {
return getTypeName( (JdbcMappingContainer) type, walker );
}
public static String getTypeName(JdbcMappingContainer type, SqlAstTranslator<?> walker) {
if ( type instanceof SqlTypedMapping ) {
return AbstractSqlAstTranslator.getSqlTypeName( (SqlTypedMapping) type, walker.getSessionFactory() );
}
else {
final BasicType<?> jdbcMapping = (BasicType<?>) type.getSingleJdbcMapping();
final BasicType<?> basicType = (BasicType<?>) type.getSingleJdbcMapping();
final TypeConfiguration typeConfiguration = walker.getSessionFactory().getTypeConfiguration();
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
final DdlType ddlType = ddlTypeRegistry.getDescriptor(
jdbcMapping.getJdbcType().getDdlTypeCode()
basicType.getJdbcType().getDdlTypeCode()
);
return ddlType.getTypeName( Size.nil(), jdbcMapping, ddlTypeRegistry );
return ddlType.getTypeName( Size.nil(), basicType, ddlTypeRegistry );
}
}
public static String getTypeName(ReturnableType<?> type, SqlAstTranslator<?> walker) {
if ( type instanceof SqlTypedMapping ) {
return AbstractSqlAstTranslator.getSqlTypeName( (SqlTypedMapping) type, walker.getSessionFactory() );
}
else {
final BasicType<?> basicType = (BasicType<?>) ( (JdbcMappingContainer) type ).getSingleJdbcMapping();
final TypeConfiguration typeConfiguration = walker.getSessionFactory().getTypeConfiguration();
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
final DdlType ddlType = ddlTypeRegistry.getDescriptor(
basicType.getJdbcType().getDdlTypeCode()
);
return ddlType.getTypeName( Size.nil(), basicType, ddlTypeRegistry );
}
}
public static String getCastTypeName(BasicType<?> type, SqlAstTranslator<?> walker) {
return getCastTypeName( (JdbcMappingContainer) type, walker );
}
public static String getCastTypeName(JdbcMappingContainer type, SqlAstTranslator<?> walker) {
if ( type instanceof SqlTypedMapping ) {
return AbstractSqlAstTranslator.getCastTypeName( (SqlTypedMapping) type, walker.getSessionFactory() );
@ -70,4 +93,19 @@ public class DdlTypeHelper {
}
}
public static String getCastTypeName(ReturnableType<?> type, SqlAstTranslator<?> walker) {
if ( type instanceof SqlTypedMapping ) {
return AbstractSqlAstTranslator.getCastTypeName( (SqlTypedMapping) type, walker.getSessionFactory() );
}
else {
final BasicType<?> basicType = (BasicType<?>) ( (JdbcMappingContainer) type ).getSingleJdbcMapping();
final TypeConfiguration typeConfiguration = walker.getSessionFactory().getTypeConfiguration();
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
final DdlType ddlType = ddlTypeRegistry.getDescriptor(
basicType.getJdbcType().getDdlTypeCode()
);
return ddlType.getCastTypeName( Size.nil(), basicType, ddlTypeRegistry );
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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;
/**
* Implement the array fill function by using {@code system_range}.
*/
public class H2ArrayFillFunction extends AbstractArrayFillFunction {
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
sqlAppender.append( "coalesce((select array_agg(" );
sqlAstArguments.get( 0 ).accept( walker );
sqlAppender.append( ") from system_range(1," );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.append( ")),array[])" );
}
}

View File

@ -32,12 +32,7 @@ public class HSQLArrayConstructorFunction extends ArrayConstructorFunction {
SqlAstTranslator<?> walker) {
final String castTypeName;
if ( returnType != null && hasOnlyBottomArguments( arguments ) ) {
if ( returnType instanceof SqlTypedMapping ) {
castTypeName = AbstractSqlAstTranslator.getCastTypeName( (SqlTypedMapping) returnType, walker.getSessionFactory() );
}
else {
castTypeName = DdlTypeHelper.getCastTypeName( (JdbcMappingContainer) returnType, walker );
}
castTypeName = DdlTypeHelper.getCastTypeName( returnType, walker );
sqlAppender.append( "cast(" );
}
else {

View File

@ -0,0 +1,36 @@
/*
* 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.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
/**
* Implement the array fill function by using {@code sequence_array}.
*/
public class HSQLArrayFillFunction extends AbstractArrayFillFunction {
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
sqlAppender.append( "coalesce(case when " );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.append( "<>0 then (select array_agg(" );
walker.render( sqlAstArguments.get( 0 ), SqlAstNodeRenderingMode.NO_UNTYPED );
sqlAppender.append( ") from unnest(sequence_array(1," );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.append( ",1))) end,array[])" );
}
}

View File

@ -0,0 +1,36 @@
/*
* 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;
/**
* Oracle array_fill function.
*/
public class OracleArrayFillFunction extends AbstractArrayFillFunction {
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
final String arrayTypeName = DdlTypeHelper.getTypeName( returnType, walker );
sqlAppender.append( arrayTypeName );
sqlAppender.append( "_fill(" );
sqlAstArguments.get( 0 ).accept( walker );
sqlAppender.append( ',' );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.append( ')' );
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.sql.ast.tree.expression.Literal;
/**
* Implement the array get function by using {@code unnest}.
*/
public class PostgreSQLArrayFillFunction extends AbstractArrayFillFunction {
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
sqlAppender.append( "array_fill(" );
final String elementCastType;
final Expression elementExpression = (Expression) sqlAstArguments.get( 0 );
if ( needsElementCasting( elementExpression ) ) {
elementCastType = DdlTypeHelper.getCastTypeName( elementExpression.getExpressionType(), walker );
sqlAppender.append( "cast(" );
}
else {
elementCastType = null;
}
sqlAstArguments.get( 0 ).accept( walker );
if ( elementCastType != null ) {
sqlAppender.append( " as " );
sqlAppender.append( elementCastType );
sqlAppender.append( ')' );
}
sqlAppender.append( ",array[" );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.append( "])" );
}
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

@ -28,23 +28,23 @@ import org.hibernate.type.spi.TypeConfiguration;
public class ResultSetMappingSqlSelection implements SqlSelection, Expression, SqlExpressionAccess {
private final int valuesArrayPosition;
private final BasicValuedMapping valueMapping;
private final JdbcMapping jdbcMapping;
private final ValueExtractor valueExtractor;
public ResultSetMappingSqlSelection(int valuesArrayPosition, BasicValuedMapping valueMapping) {
this.valuesArrayPosition = valuesArrayPosition;
this.valueMapping = valueMapping;
this.jdbcMapping = valueMapping.getJdbcMapping();
this.valueExtractor = valueMapping.getJdbcMapping().getJdbcValueExtractor();
}
public ResultSetMappingSqlSelection(int valuesArrayPosition, JdbcMapping jdbcMapping) {
this.valuesArrayPosition = valuesArrayPosition;
this.jdbcMapping = jdbcMapping;
this.valueMapping = null;
this.valueExtractor = jdbcMapping.getJdbcValueExtractor();
}
@Override
public ValueExtractor getJdbcValueExtractor() {
return jdbcMapping.getJdbcValueExtractor();
return valueExtractor;
}
@Override

View File

@ -852,7 +852,6 @@ public class CteInsertHandler implements InsertHandler {
final CteColumn idCteColumn = cteColumns.get( 0 );
querySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new ColumnReference(
"t",
idCteColumn.getColumnExpression(),
@ -867,7 +866,6 @@ public class CteInsertHandler implements InsertHandler {
final CteColumn cteColumn = cteColumns.get( j );
querySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new ColumnReference(
"e",
cteColumn.getColumnExpression(),
@ -906,13 +904,11 @@ public class CteInsertHandler implements InsertHandler {
);
finalResultQuery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
idColumnReference
)
);
finalResultQuery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
SqmInsertStrategyHelper.createRowNumberingExpression(
querySpec,
sessionFactory
@ -958,7 +954,6 @@ public class CteInsertHandler implements InsertHandler {
);
insertSelectSpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new ColumnReference(
"e",
rootKeyColumns[j],
@ -991,7 +986,6 @@ public class CteInsertHandler implements InsertHandler {
final ColumnReference columnReference = assignmentReferences.get( j );
insertSelectSpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new ColumnReference(
"e",
entry.getKey().get( j ).getColumnExpression(),

View File

@ -460,7 +460,7 @@ public class InlineUpdateHandler implements UpdateHandler {
)
);
querySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl( 0, valuesColumnReference )
new SqlSelectionImpl( valuesColumnReference )
);
}
);
@ -492,7 +492,7 @@ public class InlineUpdateHandler implements UpdateHandler {
)
);
querySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl( 0, valuesColumnReference )
new SqlSelectionImpl( valuesColumnReference )
);
}
final ValuesTableGroup valuesTableGroup = new ValuesTableGroup(

View File

@ -55,11 +55,7 @@ public final class ExecuteWithoutIdTableHelper {
rootTableReference,
selection
);
final SqlSelection sqlSelection = new SqlSelectionImpl(
// irrelevant
0,
columnReference
);
final SqlSelection sqlSelection = new SqlSelectionImpl( columnReference );
matchingIdSelect.getSelectClause().addSqlSelection( sqlSelection );
}
);

View File

@ -340,7 +340,6 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio
for ( ColumnReference columnReference : assignable.getColumnReferences() ) {
querySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new ColumnReference(
updatingTableReference.getIdentificationVariable(),
columnReference.getColumnExpression(),
@ -528,7 +527,6 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio
);
querySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new ColumnReference(
updatingTableReference.getIdentificationVariable(),
idColumnReference.getColumnExpression(),
@ -688,7 +686,6 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio
for ( ColumnReference columnReference : assignment.getAssignable().getColumnReferences() ) {
querySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new ColumnReference(
updatingTableReference.getIdentificationVariable(),
columnReference.getColumnExpression(),
@ -732,7 +729,6 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio
);
querySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new ColumnReference(
updatingTableReference.getIdentificationVariable(),
identifierMapping

View File

@ -19,6 +19,7 @@ import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmCollation;
import org.hibernate.query.sqm.tree.expression.SqmDurationUnit;
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -119,6 +120,17 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
throwError(type, Object.class, functionName, count);
}
break;
case NO_UNTYPED:
if ( argument instanceof SqmLiteralNull<?> ) {
throw new FunctionArgumentException(
String.format(
"Parameter %d of function '%s()' does not permit untyped expressions like null literals. Please cast the expression to a type",
count,
functionName
)
);
}
break;
}
}
else {

View File

@ -87,5 +87,9 @@ public enum FunctionParameterType {
* Indicates that the argument should be a spatial type
* @see org.hibernate.type.SqlTypes#isSpatialType(int)
*/
SPATIAL
SPATIAL,
/**
* Indicates a parameter that accepts any type, except untyped expressions like {@code null} literals
*/
NO_UNTYPED
}

View File

@ -1498,14 +1498,14 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
if ( versionExpression != null ) {
if ( versionSelection == null ) {
// The position is irrelevant as this is only needed for insert
versionSelection = new SqlSelectionImpl( 0, versionExpression );
versionSelection = new SqlSelectionImpl( versionExpression );
}
selectClause.addSqlSelection( versionSelection );
}
if ( discriminatorExpression != null ) {
if ( discriminatorSelection == null ) {
// The position is irrelevant as this is only needed for insert
discriminatorSelection = new SqlSelectionImpl( 0, discriminatorExpression );
discriminatorSelection = new SqlSelectionImpl( discriminatorExpression );
}
selectClause.addSqlSelection( discriminatorSelection );
}
@ -1525,7 +1525,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return false;
}
identifierSelection = new SqlSelectionImpl(
0,
SqmInsertStrategyHelper.createRowNumberingExpression( querySpec, sessionFactory )
);
selectClause.addSqlSelection( identifierSelection );
@ -1538,7 +1537,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
// The position is irrelevant as this is only needed for insert
identifierSelection = new SqlSelectionImpl(
0,
new SelfRenderingSqlFragmentExpression( fragment )
);
}
@ -4517,7 +4515,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
integerType,
integerType
);
subQuerySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, expression ) );
subQuerySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( expression ) );
subQuerySpec.applyPredicate(
pluralAttributeMapping.getKeyDescriptor().generateJoinPredicate(
@ -4711,7 +4709,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
).getJdbcMapping(),
modelPart
);
subQuerySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, expression ) );
subQuerySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( expression ) );
NavigablePath parent = pluralPartPath.getPluralDomainPath().getNavigablePath().getParent();
subQuerySpec.applyPredicate(
@ -4885,10 +4883,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
subQuerySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
expression
)
new SqlSelectionImpl( expression )
);
resultColumnReferences.add(
new ColumnReference(
@ -7576,9 +7571,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
assert jdbcTypeCount > 0;
final JdbcLiteral<Integer> jdbcLiteral = new JdbcLiteral<>( 1, basicType( Integer.class ) );
subQuerySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl( 0, jdbcLiteral )
);
subQuerySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( jdbcLiteral ) );
return new ExistsPredicate( subQuerySpec, !predicate.isNegated(), getBooleanType() );
}

View File

@ -5772,12 +5772,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
if ( shouldEmulateLateralWithIntersect( statement.getQueryPart() ) ) {
final QuerySpec lhsReferencesQuery = new QuerySpec( false );
for ( ColumnReference columnReference : columnReferences ) {
lhsReferencesQuery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
columnReference
)
);
lhsReferencesQuery.getSelectClause().addSqlSelection( new SqlSelectionImpl( columnReference ) );
}
final List<QueryPart> queryParts = new ArrayList<>( 2 );
queryParts.add( lhsReferencesQuery );
@ -5822,10 +5817,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
final QuerySpec existsQuery = new QuerySpec( false, 1 );
existsQuery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new QueryLiteral<>( 1, getIntegerType() )
)
new SqlSelectionImpl( new QueryLiteral<>( 1, getIntegerType() ) )
);
existsQuery.getFromClause().addRoot( subTableGroup );
existsQuery.applyPredicate(
@ -5874,10 +5866,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
existsQuery.setGroupByClauseExpressions( querySpec.getGroupByClauseExpressions() );
existsQuery.setHavingClauseRestrictions( querySpec.getHavingClauseRestrictions() );
existsQuery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new QueryLiteral<>( 1, getIntegerType() )
)
new SqlSelectionImpl( new QueryLiteral<>( 1, getIntegerType() ) )
);
existsQuery.applyPredicate(
new ComparisonPredicate(
@ -5911,7 +5900,6 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
countQuery.setHavingClauseRestrictions( querySpec.getHavingClauseRestrictions() );
countQuery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
0,
new SelfRenderingAggregateFunctionSqlAstExpression(
"count",
(sqlAppender, sqlAstArguments, returnType, walker) -> sqlAppender.append( "count(*)" ),

View File

@ -26,7 +26,7 @@ public class ResolvedSqlSelection extends SqlSelectionImpl {
int valuesArrayPosition,
Expression sqlExpression,
BasicType<Object> resolvedType) {
super( valuesArrayPosition + 1, valuesArrayPosition, null, sqlExpression, false );
super( valuesArrayPosition + 1, valuesArrayPosition, sqlExpression, null, false, resolvedType.getJdbcValueExtractor() );
this.resolvedType = resolvedType;
}
@ -35,15 +35,10 @@ public class ResolvedSqlSelection extends SqlSelectionImpl {
int valuesArrayPosition,
Expression sqlExpression,
BasicType<Object> resolvedType) {
super( jdbcPosition, valuesArrayPosition, null, sqlExpression, false );
super( jdbcPosition, valuesArrayPosition, sqlExpression, null, false, resolvedType.getJdbcValueExtractor() );
this.resolvedType = resolvedType;
}
@Override
public ValueExtractor getJdbcValueExtractor() {
return resolvedType.getJdbcMapping().getJdbcValueExtractor();
}
@Override
public JdbcMappingContainer getExpressionType() {
return resolvedType;

View File

@ -47,6 +47,7 @@ public class SqlSelectionImpl implements SqlSelection, SqlExpressionAccess {
private final Expression sqlExpression;
private final JavaType<?> jdbcJavaType;
private final boolean virtual;
private transient ValueExtractor valueExtractor;
public SqlSelectionImpl(Expression sqlExpression) {
this( 0, -1, null, sqlExpression, false );
@ -66,11 +67,39 @@ public class SqlSelectionImpl implements SqlSelection, SqlExpressionAccess {
JavaType<?> jdbcJavaType,
Expression sqlExpression,
boolean virtual) {
this(
jdbcPosition,
valuesArrayPosition,
sqlExpression,
jdbcJavaType,
virtual,
null
);
}
protected SqlSelectionImpl(
int jdbcPosition,
int valuesArrayPosition,
Expression sqlExpression,
JavaType<?> jdbcJavaType,
boolean virtual,
ValueExtractor valueExtractor) {
this.jdbcPosition = jdbcPosition;
this.valuesArrayPosition = valuesArrayPosition;
this.jdbcJavaType = jdbcJavaType;
this.sqlExpression = sqlExpression;
this.jdbcJavaType = jdbcJavaType;
this.virtual = virtual;
this.valueExtractor = valueExtractor;
}
private static ValueExtractor determineValueExtractor(Expression sqlExpression, JavaType<?> jdbcJavaType) {
final JdbcMapping jdbcMapping = sqlExpression.getExpressionType().getSingleJdbcMapping();
if ( jdbcJavaType == null || jdbcMapping.getMappedJavaType() == jdbcJavaType ) {
return jdbcMapping.getJdbcValueExtractor();
}
else {
return jdbcMapping.getJdbcType().getExtractor( jdbcJavaType );
}
}
@Override
@ -80,11 +109,11 @@ public class SqlSelectionImpl implements SqlSelection, SqlExpressionAccess {
@Override
public ValueExtractor getJdbcValueExtractor() {
final JdbcMapping jdbcMapping = sqlExpression.getExpressionType().getSingleJdbcMapping();
if ( jdbcJavaType == null || jdbcMapping.getMappedJavaType() == jdbcJavaType ) {
return jdbcMapping.getJdbcValueExtractor();
ValueExtractor extractor = valueExtractor;
if ( extractor == null ) {
valueExtractor = extractor = determineValueExtractor( sqlExpression, jdbcJavaType );
}
return jdbcMapping.getJdbcType().getExtractor( jdbcJavaType );
return extractor;
}
@Override

View File

@ -0,0 +1,87 @@
/*
* 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;
/**
* @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 ArrayFillTest {
@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 testFill(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-array-fill-example[]
List<String[]> results = em.createQuery( "select array_fill('aaa', 2)", String[].class )
.getResultList();
//end::hql-array-fill-example[]
assertEquals( 1, results.size() );
assertArrayEquals( new String[] { "aaa", "aaa" }, results.get( 0 ) );
} );
}
@Test
public void testFillEmpty(SessionFactoryScope scope) {
scope.inSession( em -> {
List<String[]> results = em.createQuery( "select array_fill('aaa', 0)", String[].class )
.getResultList();
assertEquals( 1, results.size() );
assertArrayEquals( new String[0], results.get( 0 ) );
} );
}
@Test
public void testFillNull(SessionFactoryScope scope) {
scope.inSession( em -> {
List<String[]> results = em.createQuery( "select array_fill(cast(null as String), 1)", String[].class )
.getResultList();
assertEquals( 1, results.size() );
assertArrayEquals( new String[]{ null }, results.get( 0 ) );
} );
}
}