From b4a82f0854033f8de17cb669882efc508f6558e7 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 31 Aug 2021 12:54:01 +0200 Subject: [PATCH] Introduce TupleType for modelling structural types --- .../org/hibernate/userguide/hql/HQLTest.java | 3 - .../metamodel/model/domain/TupleType.java | 27 ++++++ .../model/domain/internal/ArrayTupleType.java | 86 +++++++++++++++++ .../domain/internal/MappingMetamodelImpl.java | 22 +++++ .../TupleMappingModelExpressable.java | 80 ++++++++++++++++ .../hql/internal/SemanticQueryBuilder.java | 6 +- .../query/sqm/tree/expression/SqmTuple.java | 41 ++------ .../java/ComponentArrayComparator.java | 35 +++++++ .../java/ComponentArrayMutabilityPlan.java | 58 ++++++++++++ .../java/ObjectArrayTypeDescriptor.java | 94 +++++++++++++++++++ .../hibernate/type/spi/TypeConfiguration.java | 36 +++++++ 11 files changed, 451 insertions(+), 37 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/TupleType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressable.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ComponentArrayComparator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ComponentArrayMutabilityPlan.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ObjectArrayTypeDescriptor.java diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java index 120c866e75..fdf1b37582 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java @@ -43,9 +43,7 @@ import org.hibernate.userguide.model.Phone; import org.hibernate.userguide.model.PhoneType; import org.hibernate.userguide.model.WireTransferPayment; -import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialect; -import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.junit.Before; import org.junit.Test; @@ -2285,7 +2283,6 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase { @Test - @RequiresDialectFeature(DialectChecks.SupportRowValueConstructorSyntaxInInList.class) public void test_hql_in_predicate_example_6() { doInJPA( this::entityManagerFactory, entityManager -> { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/TupleType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/TupleType.java new file mode 100644 index 0000000000..7d7cf593b0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/TupleType.java @@ -0,0 +1,27 @@ +/* + * 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.metamodel.model.domain; + +import java.util.List; + +import org.hibernate.query.sqm.SqmExpressable; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * Describes any structural type without a direct java type representation. + * + * @author Christian Beikov + */ +public interface TupleType extends SqmExpressable { + + int componentCount(); + String getComponentName(int index); + List getComponentNames(); + + SqmExpressable get(int index); + SqmExpressable get(String componentName); +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java new file mode 100644 index 0000000000..83056f0745 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java @@ -0,0 +1,86 @@ +/* + * 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.metamodel.model.domain.internal; + +import java.util.Arrays; +import java.util.List; + +import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; +import org.hibernate.metamodel.model.domain.AllowableParameterType; +import org.hibernate.metamodel.model.domain.TupleType; +import org.hibernate.query.sqm.SqmExpressable; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.java.ObjectArrayTypeDescriptor; + +/** + * @author Christian Beikov + */ +public class ArrayTupleType implements TupleType, AllowableParameterType, AllowableFunctionReturnType, + MappingModelExpressable { + + private final ObjectArrayTypeDescriptor javaTypeDescriptor; + private final SqmExpressable[] components; + + public ArrayTupleType(SqmExpressable[] components) { + this.components = components; + this.javaTypeDescriptor = new ObjectArrayTypeDescriptor( getTypeDescriptors( components ) ); + } + + private static JavaTypeDescriptor[] getTypeDescriptors(SqmExpressable[] components) { + final JavaTypeDescriptor[] typeDescriptors = new JavaTypeDescriptor[components.length]; + for ( int i = 0; i < components.length; i++ ) { + typeDescriptors[i] = components[i].getExpressableJavaTypeDescriptor(); + } + return typeDescriptors; + } + + @Override + public int componentCount() { + return components.length; + } + + @Override + public String getComponentName(int index) { + throw new UnsupportedOperationException( "Array tuple has no component names" ); + } + + @Override + public List getComponentNames() { + throw new UnsupportedOperationException( "Array tuple has no component names" ); + } + + @Override + public SqmExpressable get(int index) { + return components[index]; + } + + @Override + public SqmExpressable get(String componentName) { + throw new UnsupportedOperationException( "Array tuple has no component names" ); + } + + @Override + public JavaTypeDescriptor getExpressableJavaTypeDescriptor() { + return javaTypeDescriptor; + } + + @Override + public PersistenceType getPersistenceType() { + return PersistenceType.EMBEDDABLE; + } + + @Override + public Class getJavaType() { + return getExpressableJavaTypeDescriptor().getJavaTypeClass(); + } + + @Override + public String toString() { + return "ArrayTupleType" + Arrays.toString( components ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java index ae5c69d6f6..e15e06b354 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java @@ -55,6 +55,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.metamodel.model.domain.TupleType; import org.hibernate.metamodel.spi.EntityRepresentationStrategy; import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; @@ -151,6 +152,7 @@ public class MappingMetamodelImpl implements MappingMetamodel, MetamodelImplemen private final TypeConfiguration typeConfiguration; private final Map implementorsCache = new ConcurrentHashMap<>(); + private final Map, MappingModelExpressable> tupleTypeCache = new ConcurrentHashMap<>(); public MappingMetamodelImpl(SessionFactoryImplementor sessionFactory, TypeConfiguration typeConfiguration) { this.sessionFactory = sessionFactory; @@ -751,6 +753,26 @@ public class MappingMetamodelImpl implements MappingMetamodel, MetamodelImplemen return getEntityDescriptor( ( (EntityDomainType) sqmExpressable ).getHibernateEntityName() ); } + if ( sqmExpressable instanceof TupleType ) { + final MappingModelExpressable mappingModelExpressable = tupleTypeCache.get( sqmExpressable ); + if ( mappingModelExpressable != null ) { + return mappingModelExpressable; + } + final TupleType tupleType = (TupleType) sqmExpressable; + final MappingModelExpressable[] components = new MappingModelExpressable[tupleType.componentCount()]; + for ( int i = 0; i < components.length; i++ ) { + components[i] = resolveMappingExpressable( tupleType.get( i ), tableGroupLocator ); + } + final MappingModelExpressable createdMappingModelExpressable = new TupleMappingModelExpressable( components ); + final MappingModelExpressable existingMappingModelExpressable = tupleTypeCache.putIfAbsent( + tupleType, + createdMappingModelExpressable + ); + return existingMappingModelExpressable == null + ? createdMappingModelExpressable + : existingMappingModelExpressable; + } + throw new UnsupportedOperationException( "Cannot determine proper mapping model expressable for " + sqmExpressable ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressable.java new file mode 100644 index 0000000000..1823407fe6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressable.java @@ -0,0 +1,80 @@ +/* + * 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.metamodel.model.domain.internal; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.mapping.IndexedConsumer; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.sql.ast.Clause; + +/** + * @author Christian Beikov + */ +public class TupleMappingModelExpressable implements MappingModelExpressable { + + private final MappingModelExpressable[] components; + + public TupleMappingModelExpressable(MappingModelExpressable[] components) { + this.components = (MappingModelExpressable[]) components; + } + + @Override + public int forEachJdbcType(int offset, IndexedConsumer action) { + int span = 0; + for ( int i = 0; i < components.length; i++ ) { + span += components[i].forEachJdbcType( offset + span, action ); + } + return span; + } + + @Override + public Object disassemble(Object value, SharedSessionContractImplementor session) { + final Object[] disassembled = new Object[components.length]; + final Object[] array = (Object[]) value; + for ( int i = 0; i < components.length; i++ ) { + disassembled[i] = components[i].disassemble( array[i], session ); + } + return disassembled; + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + Clause clause, + int offset, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + final Object[] values = (Object[]) value; + int span = 0; + for ( int i = 0; i < components.length; i++ ) { + span += components[i].forEachDisassembledJdbcValue( values[i], clause, span + offset, valuesConsumer, session ); + } + return span; + } + + @Override + public int forEachJdbcValue( + Object value, + Clause clause, + int offset, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + final Object[] values = (Object[]) value; + int span = 0; + for ( int i = 0; i < components.length; i++ ) { + span += components[i].forEachDisassembledJdbcValue( + components[i].disassemble( values[i], session ), + clause, + span + offset, + valuesConsumer, + session + ); + } + return span; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index b511b37adc..08638590ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -2025,7 +2025,9 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final HqlParser.ExplicitTupleInListContext tupleExpressionListContext = (HqlParser.ExplicitTupleInListContext) inListContext; final int size = tupleExpressionListContext.getChildCount(); final int estimatedSize = size >> 1; - final boolean isEnum = testExpression.getJavaType().isEnum(); + final Class testExpressionJavaType = testExpression.getJavaType(); + final boolean isEnum = testExpressionJavaType != null && testExpressionJavaType.isEnum(); + // Multi-valued bindings are only allowed if there is a single list item, hence size 3 (LP, RP and param) parameterDeclarationContextStack.push( () -> size == 3 ); try { final List> listExpressions = new ArrayList<>( estimatedSize ); @@ -2039,7 +2041,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem resolveEnumShorthandLiteral( expressionContext, possibleEnumValues, - testExpression.getJavaType() + testExpressionJavaType ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java index eb8900949a..4dc3a304fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java @@ -16,7 +16,6 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressable; import org.hibernate.query.sqm.tree.select.SqmJpaCompoundSelection; -import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * Models a tuple of values, generally defined as a series of values @@ -38,45 +37,28 @@ public class SqmTuple } public SqmTuple(NodeBuilder nodeBuilder, SqmExpressable type, SqmExpression... groupedExpressions) { - this( Arrays.asList( groupedExpressions ), nodeBuilder ); - applyInferableType( type ); + this( Arrays.asList( groupedExpressions ), type, nodeBuilder ); } public SqmTuple(List> groupedExpressions, NodeBuilder nodeBuilder) { - super( null, nodeBuilder ); + this( groupedExpressions, null, nodeBuilder ); + } + + public SqmTuple(List> groupedExpressions, SqmExpressable type, NodeBuilder nodeBuilder) { + super( type, nodeBuilder ); if ( groupedExpressions.isEmpty() ) { throw new QueryException( "tuple grouping cannot be constructed over zero expressions" ); } this.groupedExpressions = groupedExpressions; - } - - public SqmTuple(List> groupedExpressions, SqmExpressable type, NodeBuilder nodeBuilder) { - this( groupedExpressions, nodeBuilder ); - applyInferableType( type ); + if ( type == null ) { + setExpressableType( nodeBuilder.getTypeConfiguration().resolveTupleType( groupedExpressions ) ); + } } public List> getGroupedExpressions() { return groupedExpressions; } - @Override - public SqmExpressable getNodeType() { - final SqmExpressable expressableType = super.getNodeType(); - if ( expressableType != null ) { - return expressableType; - } - - for ( SqmExpression groupedExpression : groupedExpressions ) { - //noinspection unchecked - final SqmExpressable groupedExpressionExpressableType = groupedExpression.getNodeType(); - if ( groupedExpressionExpressableType != null ) { - return groupedExpressionExpressableType; - } - } - - return null; - } - @Override public X accept(SemanticQueryWalker walker) { return walker.visitTuple( this ); @@ -98,11 +80,6 @@ public class SqmTuple return toString(); } - @Override - public JavaTypeDescriptor getJavaTypeDescriptor() { - return getNodeType().getExpressableJavaTypeDescriptor(); - } - @Override public boolean isCompoundSelection() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ComponentArrayComparator.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ComponentArrayComparator.java new file mode 100644 index 0000000000..9ffc88b0c2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ComponentArrayComparator.java @@ -0,0 +1,35 @@ +/* + * 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 . + */ +package org.hibernate.type.descriptor.java; + +import java.util.Comparator; + +/** + * Comparator for component arrays. + * + * @author Christian Beikov + */ +public class ComponentArrayComparator implements Comparator { + + private final JavaTypeDescriptor[] components; + + public ComponentArrayComparator(JavaTypeDescriptor[] components) { + this.components = components; + } + + @Override + public int compare(Object[] o1, Object[] o2) { + for ( int i = 0; i < components.length; i++ ) { + final int cmp = components[i].getComparator().compare( o1[i], o2[i] ); + if ( cmp != 0 ) { + return cmp; + } + } + + return 0; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ComponentArrayMutabilityPlan.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ComponentArrayMutabilityPlan.java new file mode 100644 index 0000000000..89763ccfc0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ComponentArrayMutabilityPlan.java @@ -0,0 +1,58 @@ +/* + * 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 . + */ +package org.hibernate.type.descriptor.java; + +import java.io.Serializable; +import java.util.Arrays; + +import org.hibernate.SharedSessionContract; + +/** + * Mutability plan for component based arrays. + * + * @author Christian Beikov + */ +public class ComponentArrayMutabilityPlan implements MutabilityPlan { + + private final JavaTypeDescriptor[] components; + + public ComponentArrayMutabilityPlan(JavaTypeDescriptor[] components) { + this.components = components; + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public Serializable disassemble(Object[] value, SharedSessionContract session) { + return (Serializable) deepCopy( value ); + } + + @Override + public Object[] assemble(Serializable cached, SharedSessionContract session) { + return deepCopy( (Object[]) cached ); + } + + @Override + public final Object[] deepCopy(Object[] value) { + if ( value == null ) { + return null; + } + if ( value.length != components.length ) { + throw new IllegalArgumentException( + "Value does not have the expected size " + components.length + ": " + Arrays.toString( value ) + ); + } + final Object[] copy = new Object[value.length]; + for ( int i = 0; i < components.length; i++ ) { + copy[i] = components[i].getMutabilityPlan().deepCopy( value[i] ); + } + return copy; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ObjectArrayTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ObjectArrayTypeDescriptor.java new file mode 100644 index 0000000000..5ea5d234e2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ObjectArrayTypeDescriptor.java @@ -0,0 +1,94 @@ +/* + * 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 . + */ +package org.hibernate.type.descriptor.java; + +import java.util.Comparator; + +import org.hibernate.type.descriptor.WrapperOptions; + +/** + * Descriptor for {@code Object[]} handling, usually used for tuples. + * + * @author Christian Beikov + */ +public class ObjectArrayTypeDescriptor extends AbstractClassTypeDescriptor { + + private final JavaTypeDescriptor[] components; + private final Comparator comparator; + + public ObjectArrayTypeDescriptor(JavaTypeDescriptor[] components) { + super( Object[].class, ImmutableMutabilityPlan.INSTANCE ); + this.components = (JavaTypeDescriptor[]) components; + this.comparator = new ComponentArrayComparator( this.components ); + } + + @Override + public String toString(Object[] value) { + final StringBuilder sb = new StringBuilder(); + sb.append( '(' ); + sb.append( components[0].toString( value[0] ) ); + for ( int i = 1; i < components.length; i++ ) { + sb.append( ", " ); + sb.append( components[i].toString( value[i] ) ); + } + sb.append( ')' ); + return sb.toString(); + } + + @Override + public boolean areEqual(Object[] one, Object[] another) { + if ( one == another ) { + return true; + } + if ( one != null && another != null && one.length == another.length ) { + for ( int i = 0; i < components.length; i++ ) { + if ( !components[i].areEqual( one[i], another[i] ) ) { + return false; + } + } + return true; + } + return false; + } + + @Override + public int extractHashCode(Object[] objects) { + int hashCode = 1; + for ( int i = 0; i < objects.length; i++ ) { + hashCode = 31 * hashCode + components[i].extractHashCode( objects[i] ); + } + return hashCode; + } + + @Override + public Comparator getComparator() { + return comparator; + } + + @SuppressWarnings({ "unchecked" }) + @Override + public X unwrap(Object[] value, Class type, WrapperOptions options) { + if ( value == null ) { + return null; + } + if ( Object[].class.isAssignableFrom( type ) ) { + return (X) value; + } + throw unknownUnwrap( type ); + } + + @Override + public Object[] wrap(X value, WrapperOptions options) { + if ( value == null ) { + return null; + } + if ( Object[].class.isInstance( value ) ) { + return (Object[]) value; + } + throw unknownWrap( value.getClass() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java index 3689f6b849..dea7a850b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java @@ -17,6 +17,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZonedDateTime; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -24,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import org.hibernate.HibernateException; @@ -42,13 +44,16 @@ import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.metamodel.model.domain.internal.ArrayTupleType; import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl; import org.hibernate.query.BinaryArithmeticOperator; import org.hibernate.query.internal.QueryHelper; import org.hibernate.query.sqm.SqmExpressable; +import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.BasicType; import org.hibernate.type.BasicTypeRegistry; +import org.hibernate.type.JavaObjectType; import org.hibernate.type.SingleColumnType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -466,6 +471,37 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + private final ConcurrentMap arrayTuples = new ConcurrentHashMap<>(); + + public SqmExpressable resolveTupleType(List> typedNodes) { + final SqmExpressable[] components = new SqmExpressable[typedNodes.size()]; + for ( int i = 0; i < typedNodes.size(); i++ ) { + final SqmExpressable sqmExpressable = typedNodes.get( i ).getNodeType(); + components[i] = sqmExpressable == null ? JavaObjectType.INSTANCE : sqmExpressable; + } + return arrayTuples.computeIfAbsent( + new ArrayCacheKey( components ), + key -> new ArrayTupleType( key.components ) + ); + } + + private static class ArrayCacheKey { + final SqmExpressable[] components; + + public ArrayCacheKey(SqmExpressable[] components) { + this.components = components; + } + + @Override + public boolean equals(Object o) { + return Arrays.equals( components, ((ArrayCacheKey) o).components ); + } + + @Override + public int hashCode() { + return Arrays.hashCode( components ); + } + } /** * @see QueryHelper#highestPrecedenceType2