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.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<JdbcMapping> 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();
}
}

View File

@ -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;
}

View File

@ -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<Object>, DomainResultProducer<Object>, JavaTypedExpressible<Object> {
implements Expression, DomainResultProducer<Object>, 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();

View File

@ -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 );

View File

@ -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(),

View File

@ -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<T,E>
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
public boolean equals(Object o) {
return o == this || o.getClass() == BasicArrayType.class

View File

@ -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<C extends Collection<E>, E>
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
public boolean equals(Object o) {
return o == this || o.getClass() == BasicCollectionType.class

View File

@ -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<T,S,E>
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
public BasicValueConverter<T, ?> getValueConverter() {
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
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() );
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -90,6 +90,10 @@ public class PrimitiveCharacterArrayJavaType extends AbstractClassJavaType<char[
if (value instanceof Reader) {
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() );
}

View File

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

View File

@ -463,6 +463,13 @@ public class BasicCollectionJavaType<C extends Collection<E>, 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() );
}

View File

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