Introduce TupleType for modelling structural types

This commit is contained in:
Christian Beikov 2021-08-31 12:54:01 +02:00
parent 1a3629a571
commit b4a82f0854
11 changed files with 451 additions and 37 deletions

View File

@ -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 -> {

View File

@ -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<J> extends SqmExpressable<J> {
int componentCount();
String getComponentName(int index);
List<String> getComponentNames();
SqmExpressable<?> get(int index);
SqmExpressable<?> get(String componentName);
}

View File

@ -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<Object[]>, AllowableParameterType<Object[]>, AllowableFunctionReturnType<Object[]>,
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<String> 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<Object[]> getExpressableJavaTypeDescriptor() {
return javaTypeDescriptor;
}
@Override
public PersistenceType getPersistenceType() {
return PersistenceType.EMBEDDABLE;
}
@Override
public Class<Object[]> getJavaType() {
return getExpressableJavaTypeDescriptor().getJavaTypeClass();
}
@Override
public String toString() {
return "ArrayTupleType" + Arrays.toString( components );
}
}

View File

@ -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<String, String[]> implementorsCache = new ConcurrentHashMap<>();
private final Map<TupleType<?>, 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 );
}

View File

@ -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<Object>[] components;
public TupleMappingModelExpressable(MappingModelExpressable<?>[] components) {
this.components = (MappingModelExpressable<Object>[]) components;
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> 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;
}
}

View File

@ -2025,7 +2025,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> 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<SqmExpression<?>> listExpressions = new ArrayList<>( estimatedSize );
@ -2039,7 +2041,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
resolveEnumShorthandLiteral(
expressionContext,
possibleEnumValues,
testExpression.getJavaType()
testExpressionJavaType
)
);
}

View File

@ -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<T>
}
public SqmTuple(NodeBuilder nodeBuilder, SqmExpressable<T> type, SqmExpression<?>... groupedExpressions) {
this( Arrays.asList( groupedExpressions ), nodeBuilder );
applyInferableType( type );
this( Arrays.asList( groupedExpressions ), type, nodeBuilder );
}
public SqmTuple(List<SqmExpression<?>> groupedExpressions, NodeBuilder nodeBuilder) {
super( null, nodeBuilder );
this( groupedExpressions, null, nodeBuilder );
}
public SqmTuple(List<SqmExpression<?>> groupedExpressions, SqmExpressable<T> 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<SqmExpression<?>> groupedExpressions, SqmExpressable<T> type, NodeBuilder nodeBuilder) {
this( groupedExpressions, nodeBuilder );
applyInferableType( type );
if ( type == null ) {
setExpressableType( nodeBuilder.getTypeConfiguration().resolveTupleType( groupedExpressions ) );
}
}
public List<SqmExpression<?>> getGroupedExpressions() {
return groupedExpressions;
}
@Override
public SqmExpressable<T> getNodeType() {
final SqmExpressable<T> expressableType = super.getNodeType();
if ( expressableType != null ) {
return expressableType;
}
for ( SqmExpression groupedExpression : groupedExpressions ) {
//noinspection unchecked
final SqmExpressable<T> groupedExpressionExpressableType = groupedExpression.getNodeType();
if ( groupedExpressionExpressableType != null ) {
return groupedExpressionExpressableType;
}
}
return null;
}
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitTuple( this );
@ -98,11 +80,6 @@ public class SqmTuple<T>
return toString();
}
@Override
public JavaTypeDescriptor<T> getJavaTypeDescriptor() {
return getNodeType().getExpressableJavaTypeDescriptor();
}
@Override
public boolean isCompoundSelection() {
return true;

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.util.Comparator;
/**
* Comparator for component arrays.
*
* @author Christian Beikov
*/
public class ComponentArrayComparator implements Comparator<Object[]> {
private final JavaTypeDescriptor<Object>[] components;
public ComponentArrayComparator(JavaTypeDescriptor<Object>[] 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Object[]> {
private final JavaTypeDescriptor<Object>[] components;
public ComponentArrayMutabilityPlan(JavaTypeDescriptor<Object>[] 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Object[]> {
private final JavaTypeDescriptor<Object>[] components;
private final Comparator<Object[]> comparator;
public ObjectArrayTypeDescriptor(JavaTypeDescriptor<?>[] components) {
super( Object[].class, ImmutableMutabilityPlan.INSTANCE );
this.components = (JavaTypeDescriptor<Object>[]) 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<Object[]> getComparator() {
return comparator;
}
@SuppressWarnings({ "unchecked" })
@Override
public <X> X unwrap(Object[] value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( Object[].class.isAssignableFrom( type ) ) {
return (X) value;
}
throw unknownUnwrap( type );
}
@Override
public <X> Object[] wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( Object[].class.isInstance( value ) ) {
return (Object[]) value;
}
throw unknownWrap( value.getClass() );
}
}

View File

@ -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<ArrayCacheKey, ArrayTupleType> arrayTuples = new ConcurrentHashMap<>();
public SqmExpressable<?> resolveTupleType(List<? extends SqmTypedNode<?>> 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