From 6bbf58973e7f3cc556a50f7b737a311f9f710ff8 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 7 Nov 2023 15:01:14 +0100 Subject: [PATCH] HHH-17355 Support binding single element value for basic plural parameter types --- .../internal/SimpleNaturalIdMapping.java | 14 ++++++++- .../spi/QueryParameterBindingValidator.java | 8 ----- .../tree/expression/EntityTypeLiteral.java | 16 ++++++++-- .../exec/internal/AbstractJdbcParameter.java | 9 +++++- .../sql/exec/spi/JdbcParameterBindings.java | 10 +++++- .../org/hibernate/type/BasicArrayType.java | 16 ---------- .../hibernate/type/BasicCollectionType.java | 17 ---------- .../type/ConvertedBasicArrayType.java | 16 ---------- .../type/descriptor/java/ArrayJavaType.java | 8 +++++ .../java/BooleanPrimitiveArrayJavaType.java | 4 +++ .../java/DoublePrimitiveArrayJavaType.java | 4 +++ .../java/FloatPrimitiveArrayJavaType.java | 4 +++ .../java/IntegerPrimitiveArrayJavaType.java | 4 +++ .../java/LongPrimitiveArrayJavaType.java | 4 +++ .../java/PrimitiveByteArrayJavaType.java | 4 +++ .../java/PrimitiveCharacterArrayJavaType.java | 4 +++ .../java/ShortPrimitiveArrayJavaType.java | 4 +++ .../java/spi/BasicCollectionJavaType.java | 7 +++++ .../function/array/ArrayConstructorTest.java | 2 ++ .../compliance/CriteriaToBigDecimalTest.java | 31 +++++++++++++++++++ 20 files changed, 124 insertions(+), 62 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java index e363f68ca2..50bd0e9d8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java @@ -24,6 +24,7 @@ import org.hibernate.loader.ast.internal.SimpleNaturalIdLoader; import org.hibernate.loader.ast.spi.MultiNaturalIdLoader; import org.hibernate.loader.ast.spi.NaturalIdLoader; import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; @@ -43,7 +44,8 @@ import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.supportsSqlAr /** * Single-attribute NaturalIdMapping implementation */ -public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping implements JavaType.CoercionContext { +public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping implements JavaType.CoercionContext, + BasicValuedMapping { private final SingularAttributeMapping attribute; private final SessionFactoryImplementor sessionFactory; private final TypeConfiguration typeConfiguration; @@ -241,6 +243,11 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping implements return attribute.getSingleJdbcMapping(); } + @Override + public JdbcMapping getJdbcMapping() { + return attribute.getSingleJdbcMapping(); + } + @Override public int forEachJdbcType(int offset, IndexedConsumer action) { return attribute.forEachJdbcType( offset, action ); @@ -317,4 +324,9 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping implements public boolean hasPartitionedSelectionMapping() { return attribute.hasPartitionedSelectionMapping(); } + + @Override + public MappingType getMappedType() { + return attribute.getMappedType(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java index 22dfc4924f..d2676b278e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java @@ -14,7 +14,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.BindableType; import org.hibernate.query.QueryArgumentException; import org.hibernate.query.sqm.SqmExpressible; -import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; import jakarta.persistence.TemporalType; @@ -142,13 +141,6 @@ public class QueryParameterBindingValidator { return parameterDeclarationIsTemporal && bindIsTemporal; } - // Allow binding a single element for a basic plural parameter type - else if ( expectedJavaType instanceof BasicPluralJavaType ) { - final JavaType elementJavaType = ( (BasicPluralJavaType) expectedJavaType ).getElementJavaType(); - if ( elementJavaType.isInstance( value ) ) { - return true; - } - } return false; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/EntityTypeLiteral.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/EntityTypeLiteral.java index b99ec95206..ac7ef868c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/EntityTypeLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/EntityTypeLiteral.java @@ -9,9 +9,11 @@ package org.hibernate.sql.ast.tree.expression; import org.hibernate.cache.MutableCacheKeyBuilder; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.IndexedConsumer; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.DiscriminatorType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Queryable; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; @@ -27,7 +29,7 @@ import org.hibernate.type.descriptor.java.JavaTypedExpressible; * @author Steve Ebersole */ public class EntityTypeLiteral - implements Expression, MappingModelExpressible, DomainResultProducer, JavaTypedExpressible { + implements Expression, DomainResultProducer, BasicValuedMapping { private final EntityPersister entityTypeDescriptor; private final DiscriminatorType discriminatorType; @@ -41,13 +43,23 @@ public class EntityTypeLiteral } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // MappingModelExpressible + // BasicValuedMapping @Override public MappingModelExpressible getExpressionType() { return this; } + @Override + public JdbcMapping getJdbcMapping() { + return discriminatorType; + } + + @Override + public MappingType getMappedType() { + return discriminatorType; + } + @Override public int getJdbcTypeCount() { return discriminatorType.getJdbcTypeCount(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java index a08bde3567..902699e064 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java @@ -13,8 +13,10 @@ import java.sql.SQLException; import org.hibernate.cache.MutableCacheKeyBuilder; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.IndexedConsumer; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.metamodel.model.domain.internal.BasicTypeImpl; import org.hibernate.query.BindableType; @@ -38,7 +40,7 @@ import org.hibernate.type.spi.TypeConfiguration; * @author Steve Ebersole */ public abstract class AbstractJdbcParameter - implements JdbcParameter, JdbcParameterBinder, MappingModelExpressible, SqlExpressible { + implements JdbcParameter, JdbcParameterBinder, MappingModelExpressible, SqlExpressible, BasicValuedMapping { private final JdbcMapping jdbcMapping; @@ -56,6 +58,11 @@ public abstract class AbstractJdbcParameter return jdbcMapping; } + @Override + public MappingType getMappedType() { + return jdbcMapping; + } + @Override public void accept(SqlAstWalker sqlTreeWalker) { sqlTreeWalker.visitParameter( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java index 2a54412cbd..09da97372e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.function.BiConsumer; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.Bindable; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.query.internal.BindingTypeHelper; @@ -74,8 +75,15 @@ public interface JdbcParameterBindings { Bindable bindable, JdbcParametersList jdbcParameters, SharedSessionContractImplementor session) { + final Object valueToBind; + if ( bindable instanceof BasicValuedMapping ) { + valueToBind = ( (BasicValuedMapping) bindable ).getJdbcMapping().getMappedJavaType().wrap( value, session ); + } + else { + valueToBind = value; + } return bindable.forEachJdbcValue( - value, + valueToBind, offset, jdbcParameters, session.getFactory().getTypeConfiguration(), diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java b/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java index 1740972338..a2bf4c36a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java @@ -6,10 +6,8 @@ */ package org.hibernate.type; -import java.lang.reflect.Array; import java.util.Objects; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; @@ -56,20 +54,6 @@ public class BasicArrayType return (BasicType) this; } - @Override - public Object disassemble(Object value, SharedSessionContractImplementor session) { - if ( value == null ) { - return null; - } - if ( baseDescriptor.isInstance( (E) value ) ) { - // Support binding a single element as parameter value - final Object array = Array.newInstance( baseDescriptor.getJavaType(), 1 ); - Array.set( array, 0, value ); - return array; - } - return value; - } - @Override public boolean equals(Object o) { return o == this || o.getClass() == BasicArrayType.class diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java index 1dbae995fe..bd40f9e17f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java @@ -6,11 +6,9 @@ */ package org.hibernate.type; -import java.lang.reflect.Array; import java.util.Collection; import java.util.Objects; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; @@ -77,21 +75,6 @@ public class BasicCollectionType, E> return (BasicType) this; } - @Override - public Object disassemble(Object value, SharedSessionContractImplementor session) { - if ( value == null ) { - return null; - } - if ( baseDescriptor.isInstance( (E) value ) ) { - // Support binding a single element as parameter value - final BasicCollectionJavaType javaType = (BasicCollectionJavaType) getJavaTypeDescriptor(); - final C collection = javaType.getSemantics().instantiateRaw( 1, null ); - collection.add( (E) value ); - return collection; - } - return value; - } - @Override public boolean equals(Object o) { return o == this || o.getClass() == BasicCollectionType.class diff --git a/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java b/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java index 30abf47abe..d592694b37 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java @@ -6,10 +6,8 @@ */ package org.hibernate.type; -import java.lang.reflect.Array; import java.util.Objects; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; @@ -78,20 +76,6 @@ public class ConvertedBasicArrayType return (BasicType) this; } - @Override - public Object disassemble(Object value, SharedSessionContractImplementor session) { - if ( value == null ) { - return null; - } - if ( baseDescriptor.isInstance( (E) value ) ) { - // Support binding a single element as parameter value - final Object array = Array.newInstance( baseDescriptor.getJavaType(), 1 ); - Array.set( array, 0, value ); - return array; - } - return value; - } - @Override public BasicValueConverter getValueConverter() { return converter; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java index cc65905042..be642b5178 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java @@ -320,6 +320,14 @@ public class ArrayJavaType extends AbstractArrayJavaType { // When the value is a BinaryStream, this is a deserialization request return fromBytes( ( (BinaryStream) value ).getBytes() ); } + else if ( getElementJavaType().isInstance( value ) ) { + // Support binding a single element as parameter value + //noinspection unchecked + final T[] wrapped = (T[]) java.lang.reflect.Array.newInstance( getElementJavaType().getJavaTypeClass(), 1 ); + //noinspection unchecked + wrapped[0] = (T) value; + return wrapped; + } throw unknownWrap( value.getClass() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java index 5bd30400df..e18522b0c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java @@ -168,6 +168,10 @@ public class BooleanPrimitiveArrayJavaType extends AbstractArrayJavaType throw new HibernateException( "Unable to access lob stream", e ); } } + else if ( value instanceof Byte ) { + // Support binding a single element as parameter value + return new byte[]{ (byte) value }; + } throw unknownWrap( value.getClass() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java index 2f09bdb59c..46e6339c6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java @@ -90,6 +90,10 @@ public class PrimitiveCharacterArrayJavaType extends AbstractClassJavaType, E> extends Abstrac } return wrapped; } + else if ( getElementJavaType().isInstance( value ) ) { + // Support binding a single element as parameter value + final C wrapped = semantics.instantiateRaw( 1, null ); + //noinspection unchecked + wrapped.add( (E) value ); + return wrapped; + } throw unknownWrap( value.getClass() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayConstructorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayConstructorTest.java index 05087d213d..2e85a8e1ca 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayConstructorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayConstructorTest.java @@ -70,8 +70,10 @@ public class ArrayConstructorTest { @Test public void testNonExisting(SessionFactoryScope scope) { scope.inSession( em -> { + //tag::hql-array-example[] List results = em.createQuery( "from EntityWithArrays e where e.theArray = array('abc')", EntityWithArrays.class ) .getResultList(); + //end::hql-array-example[] assertEquals( 0, results.size() ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaToBigDecimalTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaToBigDecimalTest.java index 43985ce31f..29bddacec1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaToBigDecimalTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaToBigDecimalTest.java @@ -101,6 +101,35 @@ public class CriteriaToBigDecimalTest { ); } + @Test + public void testToBigDecimal3(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = criteriaBuilder.createQuery(); + final Root person = query.from( Person.class ); + query.select( criteriaBuilder.sum( person.get( "ageAsBigDecimal" ), 1 ) ); + + BigDecimal result = (BigDecimal) entityManager.createQuery( query ).getSingleResult(); + + assertEquals( new BigDecimal( "21" ), result.stripTrailingZeros() ); + } + ); + } + + @Test + public void testToBigDecimal4(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + BigDecimal result = (BigDecimal) entityManager.createQuery( "select p.ageAsBigDecimal + :val from Person p" ) + .setParameter( "val", 1 ) + .getSingleResult(); + + assertEquals( new BigDecimal( "21" ), result.stripTrailingZeros() ); + } + ); + } + @Entity(name = "Person") public static class Person { @Id @@ -109,6 +138,7 @@ public class CriteriaToBigDecimalTest { private String name; private Integer age; + private BigDecimal ageAsBigDecimal; Person() { } @@ -117,6 +147,7 @@ public class CriteriaToBigDecimalTest { this.id = id; this.name = name; this.age = age; + this.ageAsBigDecimal = new BigDecimal( age ); } } } \ No newline at end of file