HHH-17355 Support binding single element value for basic plural parameter types

This commit is contained in:
Christian Beikov 2023-11-07 15:01:14 +01:00
parent eebb305837
commit 6bbf58973e
20 changed files with 124 additions and 62 deletions

View File

@ -24,6 +24,7 @@ import org.hibernate.loader.ast.internal.SimpleNaturalIdLoader;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader; import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader; import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.MappingType;
@ -43,7 +44,8 @@ import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.supportsSqlAr
/** /**
* Single-attribute NaturalIdMapping implementation * 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 SingularAttributeMapping attribute;
private final SessionFactoryImplementor sessionFactory; private final SessionFactoryImplementor sessionFactory;
private final TypeConfiguration typeConfiguration; private final TypeConfiguration typeConfiguration;
@ -241,6 +243,11 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping implements
return attribute.getSingleJdbcMapping(); return attribute.getSingleJdbcMapping();
} }
@Override
public JdbcMapping getJdbcMapping() {
return attribute.getSingleJdbcMapping();
}
@Override @Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) { public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
return attribute.forEachJdbcType( offset, action ); return attribute.forEachJdbcType( offset, action );
@ -317,4 +324,9 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping implements
public boolean hasPartitionedSelectionMapping() { public boolean hasPartitionedSelectionMapping() {
return attribute.hasPartitionedSelectionMapping(); return attribute.hasPartitionedSelectionMapping();
} }
@Override
public MappingType getMappedType() {
return attribute.getMappedType();
}
} }

View File

@ -14,7 +14,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.BindableType; import org.hibernate.query.BindableType;
import org.hibernate.query.QueryArgumentException; import org.hibernate.query.QueryArgumentException;
import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
@ -142,13 +141,6 @@ public class QueryParameterBindingValidator {
return parameterDeclarationIsTemporal && bindIsTemporal; 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; return false;
} }

View File

@ -9,9 +9,11 @@ package org.hibernate.sql.ast.tree.expression;
import org.hibernate.cache.MutableCacheKeyBuilder; import org.hibernate.cache.MutableCacheKeyBuilder;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.IndexedConsumer; import org.hibernate.internal.util.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.DiscriminatorType; import org.hibernate.metamodel.mapping.DiscriminatorType;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable; import org.hibernate.persister.entity.Queryable;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
@ -27,7 +29,7 @@ import org.hibernate.type.descriptor.java.JavaTypedExpressible;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class EntityTypeLiteral public class EntityTypeLiteral
implements Expression, MappingModelExpressible<Object>, DomainResultProducer<Object>, JavaTypedExpressible<Object> { implements Expression, DomainResultProducer<Object>, BasicValuedMapping {
private final EntityPersister entityTypeDescriptor; private final EntityPersister entityTypeDescriptor;
private final DiscriminatorType<?> discriminatorType; private final DiscriminatorType<?> discriminatorType;
@ -41,13 +43,23 @@ public class EntityTypeLiteral
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MappingModelExpressible // BasicValuedMapping
@Override @Override
public MappingModelExpressible getExpressionType() { public MappingModelExpressible getExpressionType() {
return this; return this;
} }
@Override
public JdbcMapping getJdbcMapping() {
return discriminatorType;
}
@Override
public MappingType getMappedType() {
return discriminatorType;
}
@Override @Override
public int getJdbcTypeCount() { public int getJdbcTypeCount() {
return discriminatorType.getJdbcTypeCount(); return discriminatorType.getJdbcTypeCount();

View File

@ -13,8 +13,10 @@ import java.sql.SQLException;
import org.hibernate.cache.MutableCacheKeyBuilder; import org.hibernate.cache.MutableCacheKeyBuilder;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.IndexedConsumer; import org.hibernate.internal.util.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.metamodel.mapping.SqlExpressible;
import org.hibernate.metamodel.model.domain.internal.BasicTypeImpl; import org.hibernate.metamodel.model.domain.internal.BasicTypeImpl;
import org.hibernate.query.BindableType; import org.hibernate.query.BindableType;
@ -38,7 +40,7 @@ import org.hibernate.type.spi.TypeConfiguration;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public abstract class AbstractJdbcParameter public abstract class AbstractJdbcParameter
implements JdbcParameter, JdbcParameterBinder, MappingModelExpressible, SqlExpressible { implements JdbcParameter, JdbcParameterBinder, MappingModelExpressible, SqlExpressible, BasicValuedMapping {
private final JdbcMapping jdbcMapping; private final JdbcMapping jdbcMapping;
@ -56,6 +58,11 @@ public abstract class AbstractJdbcParameter
return jdbcMapping; return jdbcMapping;
} }
@Override
public MappingType getMappedType() {
return jdbcMapping;
}
@Override @Override
public void accept(SqlAstWalker sqlTreeWalker) { public void accept(SqlAstWalker sqlTreeWalker) {
sqlTreeWalker.visitParameter( this ); sqlTreeWalker.visitParameter( this );

View File

@ -11,6 +11,7 @@ import java.util.Collections;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.Bindable; import org.hibernate.metamodel.mapping.Bindable;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.internal.BindingTypeHelper; import org.hibernate.query.internal.BindingTypeHelper;
@ -74,8 +75,15 @@ public interface JdbcParameterBindings {
Bindable bindable, Bindable bindable,
JdbcParametersList jdbcParameters, JdbcParametersList jdbcParameters,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
final Object valueToBind;
if ( bindable instanceof BasicValuedMapping ) {
valueToBind = ( (BasicValuedMapping) bindable ).getJdbcMapping().getMappedJavaType().wrap( value, session );
}
else {
valueToBind = value;
}
return bindable.forEachJdbcValue( return bindable.forEachJdbcValue(
value, valueToBind,
offset, offset,
jdbcParameters, jdbcParameters,
session.getFactory().getTypeConfiguration(), session.getFactory().getTypeConfiguration(),

View File

@ -6,10 +6,8 @@
*/ */
package org.hibernate.type; package org.hibernate.type;
import java.lang.reflect.Array;
import java.util.Objects; import java.util.Objects;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -56,20 +54,6 @@ public class BasicArrayType<T,E>
return (BasicType<X>) this; return (BasicType<X>) 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return o == this || o.getClass() == BasicArrayType.class return o == this || o.getClass() == BasicArrayType.class

View File

@ -6,11 +6,9 @@
*/ */
package org.hibernate.type; package org.hibernate.type;
import java.lang.reflect.Array;
import java.util.Collection; import java.util.Collection;
import java.util.Objects; import java.util.Objects;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType; import org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -77,21 +75,6 @@ public class BasicCollectionType<C extends Collection<E>, E>
return (BasicType<X>) this; return (BasicType<X>) 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<C, E> javaType = (BasicCollectionJavaType<C, E>) getJavaTypeDescriptor();
final C collection = javaType.getSemantics().instantiateRaw( 1, null );
collection.add( (E) value );
return collection;
}
return value;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return o == this || o.getClass() == BasicCollectionType.class return o == this || o.getClass() == BasicCollectionType.class

View File

@ -6,10 +6,8 @@
*/ */
package org.hibernate.type; package org.hibernate.type;
import java.lang.reflect.Array;
import java.util.Objects; import java.util.Objects;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
@ -78,20 +76,6 @@ public class ConvertedBasicArrayType<T,S,E>
return (BasicType<X>) this; return (BasicType<X>) 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 @Override
public BasicValueConverter<T, ?> getValueConverter() { public BasicValueConverter<T, ?> getValueConverter() {
return converter; return converter;

View File

@ -320,6 +320,14 @@ public class ArrayJavaType<T> extends AbstractArrayJavaType<T[], T> {
// When the value is a BinaryStream, this is a deserialization request // When the value is a BinaryStream, this is a deserialization request
return fromBytes( ( (BinaryStream) value ).getBytes() ); 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() ); throw unknownWrap( value.getClass() );
} }

View File

@ -168,6 +168,10 @@ public class BooleanPrimitiveArrayJavaType extends AbstractArrayJavaType<boolean
} }
return wrapped; return wrapped;
} }
else if ( value instanceof Boolean ) {
// Support binding a single element as parameter value
return new boolean[]{ (boolean) value };
}
throw unknownWrap( value.getClass() ); throw unknownWrap( value.getClass() );
} }

View File

@ -168,6 +168,10 @@ public class DoublePrimitiveArrayJavaType extends AbstractArrayJavaType<double[]
} }
return wrapped; return wrapped;
} }
else if ( value instanceof Double ) {
// Support binding a single element as parameter value
return new double[]{ (double) value };
}
throw unknownWrap( value.getClass() ); throw unknownWrap( value.getClass() );
} }

View File

@ -168,6 +168,10 @@ public class FloatPrimitiveArrayJavaType extends AbstractArrayJavaType<float[],
} }
return wrapped; return wrapped;
} }
else if ( value instanceof Float ) {
// Support binding a single element as parameter value
return new float[]{ (float) value };
}
throw unknownWrap( value.getClass() ); throw unknownWrap( value.getClass() );
} }

View File

@ -168,6 +168,10 @@ public class IntegerPrimitiveArrayJavaType extends AbstractArrayJavaType<int[],
} }
return wrapped; return wrapped;
} }
else if ( value instanceof Integer ) {
// Support binding a single element as parameter value
return new int[]{ (int) value };
}
throw unknownWrap( value.getClass() ); throw unknownWrap( value.getClass() );
} }

View File

@ -168,6 +168,10 @@ public class LongPrimitiveArrayJavaType extends AbstractArrayJavaType<long[], Lo
} }
return wrapped; return wrapped;
} }
else if ( value instanceof Long ) {
// Support binding a single element as parameter value
return new long[]{ (long) value };
}
throw unknownWrap( value.getClass() ); throw unknownWrap( value.getClass() );
} }

View File

@ -136,6 +136,10 @@ public class PrimitiveByteArrayJavaType extends AbstractClassJavaType<byte[]>
throw new HibernateException( "Unable to access lob stream", e ); 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() ); throw unknownWrap( value.getClass() );
} }

View File

@ -90,6 +90,10 @@ public class PrimitiveCharacterArrayJavaType extends AbstractClassJavaType<char[
if (value instanceof Reader) { if (value instanceof Reader) {
return DataHelper.extractString( ( (Reader) value ) ).toCharArray(); return DataHelper.extractString( ( (Reader) value ) ).toCharArray();
} }
else if ( value instanceof Character ) {
// Support binding a single element as parameter value
return new char[]{ (char) value };
}
throw unknownWrap( value.getClass() ); throw unknownWrap( value.getClass() );
} }

View File

@ -168,6 +168,10 @@ public class ShortPrimitiveArrayJavaType extends AbstractArrayJavaType<short[],
} }
return wrapped; return wrapped;
} }
else if ( value instanceof Short ) {
// Support binding a single element as parameter value
return new short[]{ (short) value };
}
throw unknownWrap( value.getClass() ); throw unknownWrap( value.getClass() );
} }

View File

@ -463,6 +463,13 @@ public class BasicCollectionJavaType<C extends Collection<E>, E> extends Abstrac
} }
return wrapped; 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() ); throw unknownWrap( value.getClass() );
} }

View File

@ -70,8 +70,10 @@ public class ArrayConstructorTest {
@Test @Test
public void testNonExisting(SessionFactoryScope scope) { public void testNonExisting(SessionFactoryScope scope) {
scope.inSession( em -> { scope.inSession( em -> {
//tag::hql-array-example[]
List<EntityWithArrays> results = em.createQuery( "from EntityWithArrays e where e.theArray = array('abc')", EntityWithArrays.class ) List<EntityWithArrays> results = em.createQuery( "from EntityWithArrays e where e.theArray = array('abc')", EntityWithArrays.class )
.getResultList(); .getResultList();
//end::hql-array-example[]
assertEquals( 0, results.size() ); assertEquals( 0, results.size() );
} ); } );
} }

View File

@ -101,6 +101,35 @@ public class CriteriaToBigDecimalTest {
); );
} }
@Test
public void testToBigDecimal3(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object> query = criteriaBuilder.createQuery();
final Root<Person> 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") @Entity(name = "Person")
public static class Person { public static class Person {
@Id @Id
@ -109,6 +138,7 @@ public class CriteriaToBigDecimalTest {
private String name; private String name;
private Integer age; private Integer age;
private BigDecimal ageAsBigDecimal;
Person() { Person() {
} }
@ -117,6 +147,7 @@ public class CriteriaToBigDecimalTest {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.age = age; this.age = age;
this.ageAsBigDecimal = new BigDecimal( age );
} }
} }
} }