From 9b53ca8559e420f0becf3e2c7e83ea9164e81480 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 4 Feb 2022 11:23:39 +0100 Subject: [PATCH] Implement support for using generically typed associations to re-enable inheritance/discriminator tests --- .../hibernate/cfg/ClassPropertyHolder.java | 83 +++++++++++- .../org/hibernate/id/ExportableColumn.java | 5 + .../main/java/org/hibernate/mapping/Any.java | 47 +++++++ .../java/org/hibernate/mapping/Array.java | 10 ++ .../main/java/org/hibernate/mapping/Bag.java | 9 ++ .../org/hibernate/mapping/BasicValue.java | 24 ++++ .../org/hibernate/mapping/Collection.java | 49 +++++++ .../java/org/hibernate/mapping/Component.java | 22 +++ .../mapping/DependantBasicValue.java | 12 ++ .../org/hibernate/mapping/DependantValue.java | 13 ++ .../org/hibernate/mapping/IdentifierBag.java | 9 ++ .../mapping/IdentifierCollection.java | 5 + .../hibernate/mapping/IndexedCollection.java | 5 + .../main/java/org/hibernate/mapping/List.java | 10 ++ .../java/org/hibernate/mapping/ManyToOne.java | 11 ++ .../main/java/org/hibernate/mapping/Map.java | 9 ++ .../java/org/hibernate/mapping/OneToMany.java | 13 ++ .../java/org/hibernate/mapping/OneToOne.java | 15 +++ .../org/hibernate/mapping/PrimitiveArray.java | 9 ++ .../java/org/hibernate/mapping/Property.java | 23 ++++ .../main/java/org/hibernate/mapping/Set.java | 9 ++ .../org/hibernate/mapping/SimpleValue.java | 27 ++++ .../java/org/hibernate/mapping/ToOne.java | 13 ++ .../java/org/hibernate/mapping/Value.java | 1 + .../metamodel/internal/AttributeFactory.java | 13 +- .../metamodel/internal/MetadataContext.java | 15 ++- .../metamodel/mapping/MappingModelHelper.java | 64 ++++++++- .../model/domain/AbstractManagedType.java | 43 +++++- .../domain/MappedSuperclassDomainType.java | 4 +- .../model/domain/internal/EntityTypeImpl.java | 15 ++- .../domain/internal/JpaMetamodelImpl.java | 18 +-- .../MappedSuperclassSqmPathSource.java | 80 +++++++++++ .../internal/MappedSuperclassTypeImpl.java | 93 ++++++++++++- .../entity/AbstractEntityPersister.java | 30 ++++- .../internal/QualifiedJoinPathConsumer.java | 7 +- .../hql/internal/SemanticQueryBuilder.java | 33 +++-- .../sqm/internal/SqmMappingModelHelper.java | 23 +++- .../JoinedInheritanceEagerTest.java | 11 +- .../MultiInheritanceImplicitDowncastTest.java | 126 ++++++++---------- .../joined/JoinedSubclassTest.java | 4 - 40 files changed, 887 insertions(+), 125 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassSqmPathSource.java diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java index 2a830c379d..5829628efa 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java @@ -13,19 +13,25 @@ import jakarta.persistence.Converts; import jakarta.persistence.JoinTable; import org.hibernate.AssertionFailure; +import org.hibernate.MappingException; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.annotations.EntityBinder; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.mapping.Collection; import org.hibernate.mapping.Component; +import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; +import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.Value; /** * @author Emmanuel Bernard @@ -234,7 +240,82 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { private void addPropertyToMappedSuperclass(Property prop, XClass declaringClass) { final Class type = getContext().getBootstrapContext().getReflectionManager().toClass( declaringClass ); MappedSuperclass superclass = getContext().getMetadataCollector().getMappedSuperclass( type ); - superclass.addDeclaredProperty( prop ); + if ( type.getTypeParameters().length == 0 ) { + superclass.addDeclaredProperty( prop ); + } + else { + // If the type has type parameters, we have to look up the XClass and actual property again + // because the given XClass has a TypeEnvironment based on the type variable assignments of a subclass + // and that might result in a wrong property type being used for a property which uses a type variable + final XClass actualDeclaringClass = getContext().getBootstrapContext().getReflectionManager().toXClass( type ); + for ( XProperty declaredProperty : actualDeclaringClass.getDeclaredProperties( prop.getPropertyAccessorName() ) ) { + if ( prop.getName().equals( declaredProperty.getName() ) ) { + final PropertyData inferredData = new PropertyInferredData( + actualDeclaringClass, + declaredProperty, + null, + getContext().getBootstrapContext().getReflectionManager() + ); + final Value originalValue = prop.getValue(); + if ( originalValue instanceof SimpleValue ) { + // Avoid copying when the property doesn't depend on a type variable + if ( inferredData.getTypeName().equals( ( (SimpleValue) originalValue ).getTypeName() ) ) { + superclass.addDeclaredProperty( prop ); + return; + } + } + // If the property depends on a type variable, we have to copy it and the Value + final Property actualProperty = prop.copy(); + actualProperty.setReturnedClassName( inferredData.getTypeName() ); + final Value value = actualProperty.getValue().copy(); + if ( value instanceof Collection ) { + final Collection collection = (Collection) value; + // The owner is a MappedSuperclass which is not a PersistentClass, so set it to null +// collection.setOwner( null ); + collection.setRole( type.getName() + "." + prop.getName() ); + // To copy the element and key values, we need to defer setting the type name until the CollectionBinder ran + getContext().getMetadataCollector().addSecondPass( + new SecondPass() { + @Override + public void doSecondPass(Map persistentClasses) throws MappingException { + final Collection initializedCollection = (Collection) originalValue; + final Value element = initializedCollection.getElement().copy(); + setTypeName( element, inferredData.getProperty().getElementClass().getName() ); + if ( initializedCollection instanceof IndexedCollection ) { + final Value index = ( (IndexedCollection) initializedCollection ).getIndex().copy(); + setTypeName( index, inferredData.getProperty().getMapKey().getName() ); + ( (IndexedCollection) collection ).setIndex( index ); + } + collection.setElement( element ); + } + } + ); + } + else { + setTypeName( value, inferredData.getTypeName() ); + } + actualProperty.setValue( value ); + superclass.addDeclaredProperty( actualProperty ); + break; + } + } + } + } + + private void setTypeName(Value value, String typeName) { + if ( value instanceof ToOne ) { + final ToOne toOne = (ToOne) value; + toOne.setReferencedEntityName( typeName ); + toOne.setTypeName( typeName ); + } + else if ( value instanceof Component ) { + final Component component = (Component) value; + component.setComponentClassName( typeName ); + component.setTypeName( typeName ); + } + else if ( value instanceof SimpleValue ) { + ( (SimpleValue) value ).setTypeName( typeName ); + } } private void addPropertyToJoin(Property prop, XClass declaringClass, Join join) { diff --git a/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java b/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java index f4a82593b7..6531d61ed1 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java @@ -63,6 +63,11 @@ public class ExportableColumn extends Column { this.database = database; } + @Override + public Value copy() { + return new ValueImpl( column, table, type, database ); + } + @Override public int getColumnSpan() { return 1; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java index 46271ff4ac..41b0331bd5 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java @@ -57,6 +57,30 @@ public class Any extends SimpleValue { } + public Any(Any original) { + super( original ); + + this.metaMapping = original.metaMapping == null ? null : original.metaMapping.copy(); + this.keyMapping = original.keyMapping == null ? null : (SimpleValue) original.keyMapping.copy(); + + // annotations + this.discriminatorDescriptor = original.discriminatorDescriptor == null + ? null + : original.discriminatorDescriptor.copy(); + this.keyDescriptor = original.keyDescriptor == null ? null : original.keyDescriptor.copy(); + + // common + this.metaValueToEntityNameMap = original.metaValueToEntityNameMap == null + ? null + : new HashMap<>(original.metaValueToEntityNameMap); + this.lazy = original.lazy; + } + + @Override + public Any copy() { + return new Any( this ); + } + public void addSelectable(Selectable selectable) { if ( selectable == null ) { return; @@ -281,6 +305,18 @@ public class Any extends SimpleValue { this.selectableConsumer = selectableConsumer; } + private MetaValue(MetaValue original) { + super( original ); + this.typeName = original.typeName; + this.columnName = original.columnName; + this.selectableConsumer = original.selectableConsumer; + } + + @Override + public MetaValue copy() { + return new MetaValue( this ); + } + @Override public Type getType() throws MappingException { return getMetadata().getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( typeName ); @@ -365,6 +401,17 @@ public class Any extends SimpleValue { this.selectableConsumer = selectableConsumer; } + private KeyValue(KeyValue original) { + super( original ); + this.typeName = original.typeName; + this.selectableConsumer = original.selectableConsumer; + } + + @Override + public KeyValue copy() { + return new KeyValue( this ); + } + @Override public Type getType() throws MappingException { return getMetadata().getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( typeName ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Array.java b/hibernate-core/src/main/java/org/hibernate/mapping/Array.java index bb01577e12..14673c086a 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Array.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Array.java @@ -35,6 +35,16 @@ public class Array extends List { super( customTypeBeanResolver, owner, buildingContext ); } + protected Array(Array original) { + super( original ); + this.elementClassName = original.elementClassName; + } + + @Override + public Array copy() { + return new Array( this ); + } + public Class getElementClass() throws MappingException { if ( elementClassName == null ) { final org.hibernate.type.Type elementType = getElement().getType(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java b/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java index 0e3d3b65f6..595dc0e2cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java @@ -34,6 +34,15 @@ public class Bag extends Collection { super( customTypeBeanResolver, owner, buildingContext ); } + private Bag(Collection original) { + super( original ); + } + + @Override + public Bag copy() { + return new Bag( this ); + } + public CollectionType getDefaultCollectionType() { return new BagType( getRole(), getReferencedPropertyName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index 6952b32118..7ca2749fbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -6,6 +6,7 @@ */ package org.hibernate.mapping; +import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.function.Consumer; @@ -109,6 +110,29 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol buildingContext.getMetadataCollector().registerValueMappingResolver( this::resolve ); } + public BasicValue(BasicValue original) { + super( original ); + this.typeConfiguration = original.typeConfiguration; + this.explicitTypeName = original.explicitTypeName; + this.explicitLocalTypeParams = original.explicitLocalTypeParams == null + ? null + : new HashMap(original.explicitLocalTypeParams); + this.explicitJavaTypeAccess = original.explicitJavaTypeAccess; + this.explicitJdbcTypeAccess = original.explicitJdbcTypeAccess; + this.explicitMutabilityPlanAccess = original.explicitMutabilityPlanAccess; + this.implicitJavaTypeAccess = original.implicitJavaTypeAccess; + this.enumerationStyle = original.enumerationStyle; + this.temporalPrecision = original.temporalPrecision; + this.timeZoneStorageType = original.timeZoneStorageType; + this.resolvedJavaType = original.resolvedJavaType; + this.ownerName = original.ownerName; + this.propertyName = original.propertyName; + } + + @Override + public BasicValue copy() { + return new BasicValue( this ); + } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Setters - in preparation of resolution diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java index 5ab0417996..fcddd6e7c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java @@ -121,6 +121,55 @@ public abstract class Collection implements Fetchable, Value, Filterable { this.buildingContext = buildingContext; } + protected Collection(Collection original) { + this.buildingContext = original.buildingContext; + this.owner = original.owner; + this.key = original.key == null ? null : (KeyValue) original.key.copy(); + this.element = original.element == null ? null : original.element.copy(); + this.collectionTable = original.collectionTable; + this.role = original.role; + this.lazy = original.lazy; + this.extraLazy = original.extraLazy; + this.inverse = original.inverse; + this.mutable = original.mutable; + this.subselectLoadable = original.subselectLoadable; + this.cacheConcurrencyStrategy = original.cacheConcurrencyStrategy; + this.cacheRegionName = original.cacheRegionName; + this.orderBy = original.orderBy; + this.where = original.where; + this.manyToManyWhere = original.manyToManyWhere; + this.manyToManyOrderBy = original.manyToManyOrderBy; + this.referencedPropertyName = original.referencedPropertyName; + this.mappedByProperty = original.mappedByProperty; + this.sorted = original.sorted; + this.comparator = original.comparator; + this.comparatorClassName = original.comparatorClassName; + this.orphanDelete = original.orphanDelete; + this.batchSize = original.batchSize; + this.fetchMode = original.fetchMode; + this.optimisticLocked = original.optimisticLocked; + this.typeName = original.typeName; + this.typeParameters = original.typeParameters == null ? null : new Properties(original.typeParameters); + this.customTypeBeanResolver = original.customTypeBeanResolver; + this.collectionPersisterClass = original.collectionPersisterClass; + this.filters.addAll( original.filters ); + this.manyToManyFilters.addAll( original.manyToManyFilters ); + this.synchronizedTables.addAll( original.synchronizedTables ); + this.customSQLInsert = original.customSQLInsert; + this.customInsertCallable = original.customInsertCallable; + this.insertCheckStyle = original.insertCheckStyle; + this.customSQLUpdate = original.customSQLUpdate; + this.customUpdateCallable = original.customUpdateCallable; + this.updateCheckStyle = original.updateCheckStyle; + this.customSQLDelete = original.customSQLDelete; + this.customDeleteCallable = original.customDeleteCallable; + this.deleteCheckStyle = original.deleteCheckStyle; + this.customSQLDeleteAll = original.customSQLDeleteAll; + this.customDeleteAllCallable = original.customDeleteAllCallable; + this.deleteAllCheckStyle = original.deleteAllCheckStyle; + this.loaderName = original.loaderName; + } + public MetadataBuildingContext getBuildingContext() { return buildingContext; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java index c5e04044f9..f010d5e86f 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -8,6 +8,7 @@ package org.hibernate.mapping; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -81,6 +82,27 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable metadata.getMetadataCollector().registerComponent( this ); } + private Component(Component original) { + super( original ); + this.properties.addAll( original.properties ); + this.originalPropertyOrder = original.originalPropertyOrder.clone(); + this.componentClassName = original.componentClassName; + this.embedded = original.embedded; + this.parentProperty = original.parentProperty; + this.owner = original.owner; + this.dynamic = original.dynamic; + this.metaAttributes = original.metaAttributes == null ? null : new HashMap(original.metaAttributes); + this.isKey = original.isKey; + this.roleName = original.roleName; + this.customInstantiator = original.customInstantiator; + this.type = original.type; + } + + @Override + public Component copy() { + return new Component( this ); + } + public int getPropertySpan() { return properties.size(); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/DependantBasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/DependantBasicValue.java index f0aa441514..16e3d8bd1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/DependantBasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/DependantBasicValue.java @@ -29,6 +29,18 @@ public class DependantBasicValue extends BasicValue { this.updateable = updateable; } + private DependantBasicValue(DependantBasicValue original) { + super( original ); + this.referencedValue = original.referencedValue.copy(); + this.nullable = original.nullable; + this.updateable = original.updateable; + } + + @Override + public DependantBasicValue copy() { + return new DependantBasicValue( this ); + } + @Override protected Resolution buildResolution() { return referencedValue.resolve(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java index b4275ee4e3..8b724920a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java @@ -28,6 +28,19 @@ public class DependantValue extends SimpleValue implements Resolvable, SortableV this.wrappedValue = prototype; } + private DependantValue(DependantValue original) { + super( original ); + this.wrappedValue = (KeyValue) original.wrappedValue.copy(); + this.nullable = original.nullable; + this.updateable = original.updateable; + this.sorted = original.sorted; + } + + @Override + public DependantValue copy() { + return new DependantValue( this ); + } + public KeyValue getWrappedValue() { return wrappedValue; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java index 6b3504a417..40adb5c093 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java @@ -34,6 +34,15 @@ public class IdentifierBag extends IdentifierCollection { super( customTypeBeanResolver, owner, buildingContext ); } + public IdentifierBag(IdentifierBag original) { + super( original ); + } + + @Override + public IdentifierBag copy() { + return new IdentifierBag( this ); + } + public CollectionType getDefaultCollectionType() { return new IdentifierBagType( getRole(), getReferencedPropertyName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java index 9343a95956..846f1dae3a 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java @@ -31,6 +31,11 @@ public abstract class IdentifierCollection extends Collection { super( customTypeBeanResolver, owner, buildingContext ); } + protected IdentifierCollection(IdentifierCollection original) { + super( original ); + this.identifier = (KeyValue) original.identifier.copy(); + } + public KeyValue getIdentifier() { return identifier; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java b/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java index 0a978f0df3..db462c5644 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java @@ -33,6 +33,11 @@ public abstract class IndexedCollection extends Collection { super( customTypeBeanResolver, owner, buildingContext ); } + protected IndexedCollection(IndexedCollection original) { + super( original ); + this.index = original.index == null ? null : original.index.copy(); + } + public Value getIndex() { return index; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/List.java b/hibernate-core/src/main/java/org/hibernate/mapping/List.java index 24207a2250..9fc0eb2bda 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/List.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/List.java @@ -38,6 +38,16 @@ public class List extends IndexedCollection { super( customTypeBeanResolver, owner, buildingContext ); } + protected List(List original) { + super( original ); + this.baseIndex = original.baseIndex; + } + + @Override + public List copy() { + return new List( this ); + } + public boolean isList() { return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java index c968084493..d129573954 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java @@ -28,6 +28,17 @@ public class ManyToOne extends ToOne { super( buildingContext, table ); } + private ManyToOne(ManyToOne original) { + super( original ); + this.ignoreNotFound = original.ignoreNotFound; + this.isLogicalOneToOne = original.isLogicalOneToOne; + } + + @Override + public ManyToOne copy() { + return new ManyToOne( this ); + } + public Type getType() throws MappingException { if ( resolvedType == null ) { resolvedType = MappingHelper.manyToOne( diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Map.java b/hibernate-core/src/main/java/org/hibernate/mapping/Map.java index c5f51d344b..0b65f332f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Map.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Map.java @@ -33,6 +33,15 @@ public class Map extends IndexedCollection { super( customTypeBeanResolver, owner, buildingContext ); } + private Map(Map original) { + super( original ); + } + + @Override + public Map copy() { + return new Map( this ); + } + public boolean isMap() { return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java index 8ae712ad24..509ab8e06b 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java @@ -36,6 +36,19 @@ public class OneToMany implements Value { this.referencingTable = owner == null ? null : owner.getTable(); } + private OneToMany(OneToMany original) { + this.buildingContext = original.buildingContext; + this.referencingTable = original.referencingTable; + this.referencedEntityName = original.referencedEntityName; + this.associatedClass = original.associatedClass; + this.ignoreNotFound = original.ignoreNotFound; + } + + @Override + public Value copy() { + return new OneToMany( this ); + } + public MetadataBuildingContext getBuildingContext() { return buildingContext; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java index 6dcc708dcd..a7d02606df 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java @@ -35,6 +35,21 @@ public class OneToOne extends ToOne { this.entityName = owner.getEntityName(); } + private OneToOne(OneToOne original) { + super( original ); + this.constrained = original.constrained; + this.foreignKeyType = original.foreignKeyType; + this.identifier = original.identifier == null ? null : (KeyValue) original.identifier.copy(); + this.propertyName = original.propertyName; + this.entityName = original.entityName; + this.mappedByProperty = original.mappedByProperty; + } + + @Override + public OneToOne copy() { + return new OneToOne( this ); + } + public String getPropertyName() { return propertyName; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java b/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java index 99ed642f77..e43e69cd20 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java @@ -24,6 +24,15 @@ public class PrimitiveArray extends Array { super( customTypeBeanResolver, owner, buildingContext ); } + private PrimitiveArray(PrimitiveArray original) { + super( original ); + } + + @Override + public Array copy() { + return new PrimitiveArray( this ); + } + public boolean isPrimitiveArray() { return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index 380a053e84..d30987f28a 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -426,4 +426,27 @@ public class Property implements Serializable, MetaAttributable { public void setReturnedClassName(String returnedClassName) { this.returnedClassName = returnedClassName; } + + public Property copy() { + final Property prop = new Property(); + prop.setName( getName() ); + prop.setValue( getValue() ); + prop.setCascade( getCascade() ); + prop.setUpdateable( isUpdateable() ); + prop.setInsertable( isInsertable() ); + prop.setSelectable( isSelectable() ); + prop.setOptimisticLocked( isOptimisticLocked() ); + prop.setValueGenerationStrategy( getValueGenerationStrategy() ); + prop.setPropertyAccessorName( getPropertyAccessorName() ); + prop.setLazy( isLazy() ); + prop.setLazyGroup( getLazyGroup() ); + prop.setOptional( isOptional() ); + prop.setMetaAttributes( getMetaAttributes() ); + prop.setPersistentClass( getPersistentClass() ); + prop.setNaturalIdentifier( isNaturalIdentifier() ); + prop.setLob( isLob() ); + prop.addCallbackDefinitions( getCallbackDefinitions() ); + prop.setReturnedClassName( getReturnedClassName() ); + return prop; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Set.java b/hibernate-core/src/main/java/org/hibernate/mapping/Set.java index 6219cab641..9b55e01191 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Set.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Set.java @@ -38,6 +38,15 @@ public class Set extends Collection { super( customTypeBeanResolver, persistentClass, buildingContext ); } + private Set(Collection original) { + super( original ); + } + + @Override + public Set copy() { + return new Set( this ); + } + public void validate(Mapping mapping) throws MappingException { super.validate( mapping ); //for backward compatibility, disable this: diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index 76043db6de..62888b44a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -108,6 +108,33 @@ public abstract class SimpleValue implements KeyValue { this.table = table; } + protected SimpleValue(SimpleValue original) { + this.buildingContext = original.buildingContext; + this.metadata = original.metadata; + this.columns.addAll( original.columns ); + this.insertability.addAll( original.insertability ); + this.updatability.addAll( original.updatability ); + this.typeName = original.typeName; + this.typeParameters = original.typeParameters == null ? null : new Properties( original.typeParameters ); + this.isVersion = original.isVersion; + this.isNationalized = original.isNationalized; + this.isLob = original.isLob; + this.identifierGeneratorProperties = original.identifierGeneratorProperties == null + ? null + : new Properties( original.identifierGeneratorProperties ); + this.identifierGeneratorStrategy = original.identifierGeneratorStrategy; + this.nullValue = original.nullValue; + this.table = original.table; + this.foreignKeyName = original.foreignKeyName; + this.foreignKeyDefinition = original.foreignKeyDefinition; + this.alternateUniqueKey = original.alternateUniqueKey; + this.cascadeDeleteEnabled = original.cascadeDeleteEnabled; + this.attributeConverterDescriptor = original.attributeConverterDescriptor; + this.type = original.type; + this.customIdGeneratorCreator = original.customIdGeneratorCreator; + this.identifierGenerator = original.identifierGenerator; + } + public MetadataBuildingContext getBuildingContext() { return buildingContext; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java index 17def9aa81..ed0836d370 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java @@ -34,6 +34,19 @@ public abstract class ToOne extends SimpleValue implements Fetchable, SortableVa super( buildingContext, table ); } + protected ToOne(ToOne original) { + super( original ); + this.fetchMode = original.fetchMode; + this.referencedPropertyName = original.referencedPropertyName; + this.referencedEntityName = original.referencedEntityName; + this.propertyName = original.propertyName; + this.lazy = original.lazy; + this.sorted = original.sorted; + this.unwrapProxy = original.unwrapProxy; + this.isUnwrapProxyImplicit = original.isUnwrapProxyImplicit; + this.referenceToPrimaryKey = original.referenceToPrimaryKey; + } + public FetchMode getFetchMode() { return fetchMode; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Value.java b/hibernate-core/src/main/java/org/hibernate/mapping/Value.java index 310a6d333d..c468b33de8 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Value.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Value.java @@ -100,4 +100,5 @@ public interface Value extends Serializable { boolean hasAnyUpdatableColumns(); ServiceRegistry getServiceRegistry(); + Value copy(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java index 6e45a6c700..5963921dd0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java @@ -40,6 +40,7 @@ import org.hibernate.metamodel.model.domain.SimpleDomainType; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; import org.hibernate.metamodel.model.domain.internal.AnyMappingDomainTypeImpl; import org.hibernate.metamodel.model.domain.internal.EmbeddableTypeImpl; +import org.hibernate.metamodel.model.domain.internal.EntityTypeImpl; import org.hibernate.metamodel.model.domain.internal.MapMember; import org.hibernate.metamodel.model.domain.internal.MappedSuperclassTypeImpl; import org.hibernate.metamodel.model.domain.internal.PluralAttributeBuilder; @@ -223,7 +224,17 @@ public class AttributeFactory { final org.hibernate.type.Type type = typeContext.getHibernateValue().getType(); if ( type instanceof EntityType ) { final EntityType entityType = (EntityType) type; - return context.locateEntityType( entityType.getAssociatedEntityName() ); + final IdentifiableDomainType domainType = context.locateIdentifiableType( entityType.getAssociatedEntityName() ); + if ( domainType == null ) { + // Due to the use of generics, it can happen that a mapped super class uses a type + // for an attribute that is not a managed type. Since this case is not specifically mentioned + // in the Jakarta Persistence spec, we handle this by returning a "dummy" entity type + final JavaType domainJavaType = context.getJavaTypeRegistry().resolveDescriptor( + typeContext.getJpaBindableType() + ); + return new EntityTypeImpl<>( domainJavaType, context.getJpaMetamodel() ); + } + return domainType; } assert type instanceof AnyType; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index d94ea39dc6..b9ca50027c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -83,7 +83,7 @@ public class MetadataContext { private final AttributeFactory attributeFactory = new AttributeFactory( this ); private final Map, EntityDomainType> entityTypes = new HashMap<>(); - private final Map> entityTypesByEntityName = new HashMap<>(); + private final Map> identifiableTypesByName = new HashMap<>(); private final Map> entityTypesByPersistentClass = new HashMap<>(); private final Map, EmbeddableDomainType> embeddables = new HashMap<>(); @@ -172,7 +172,7 @@ public class MetadataContext { entityTypes.put( entityType.getBindableJavaType(), entityType ); } - entityTypesByEntityName.put( persistentClass.getEntityName(), entityType ); + identifiableTypesByName.put( persistentClass.getEntityName(), entityType ); entityTypesByPersistentClass.put( persistentClass, entityType ); orderedMappings.add( persistentClass ); } @@ -202,6 +202,7 @@ public class MetadataContext { public void registerMappedSuperclassType( MappedSuperclass mappedSuperclass, MappedSuperclassDomainType mappedSuperclassType) { + identifiableTypesByName.put( mappedSuperclassType.getTypeName(), mappedSuperclassType ); mappedSuperclassByMappedSuperclassMapping.put( mappedSuperclass, mappedSuperclassType ); orderedMappings.add( mappedSuperclass ); mappedSuperClassTypeToPersistentClass.put( mappedSuperclassType, getEntityWorkedOn() ); @@ -242,12 +243,12 @@ public class MetadataContext { * @return The corresponding JPA {@link org.hibernate.type.EntityType}, or null. */ @SuppressWarnings("unchecked") - public EntityDomainType locateEntityType(String entityName) { - return (EntityDomainType) entityTypesByEntityName.get( entityName ); + public IdentifiableDomainType locateIdentifiableType(String entityName) { + return (IdentifiableDomainType) identifiableTypesByName.get( entityName ); } - public Map> getEntityTypesByEntityName() { - return Collections.unmodifiableMap( entityTypesByEntityName ); + public Map> getIdentifiableTypesByName() { + return Collections.unmodifiableMap( identifiableTypesByName ); } @SuppressWarnings("unchecked") @@ -452,7 +453,7 @@ public class MetadataContext { assert cidValue.isEmbedded(); AbstractIdentifiableType idType = (AbstractIdentifiableType) - entityTypesByEntityName.get( cidValue.getOwner().getEntityName() ); + identifiableTypesByName.get( cidValue.getOwner().getEntityName() ); //noinspection rawtypes Set idAttributes = idType.getIdClassAttributesSafely(); if ( idAttributes == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappingModelHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappingModelHelper.java index 6a90ab0a10..bdcd4aed04 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappingModelHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappingModelHelper.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.List; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.entity.UnionSubclassEntityPersister; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; @@ -22,6 +23,11 @@ import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnRefere * @author Steve Ebersole */ public class MappingModelHelper { + + private MappingModelHelper() { + // disallow direct instantiation + } + public static Expression buildColumnReferenceExpression( ModelPart modelPart, SqlExpressionResolver sqlExpressionResolver, @@ -99,8 +105,60 @@ public class MappingModelHelper { } } } - - private MappingModelHelper() { - // disallow direct instantiation + public static boolean isCompatibleModelPart(ModelPart attribute1, ModelPart attribute2) { + if ( attribute1 == attribute2 ) { + return true; + } + if ( attribute1.getClass() != attribute2.getClass() || attribute1.getJavaType() != attribute2.getJavaType() ) { + return false; + } + if ( attribute1 instanceof Association ) { + final Association association1 = (Association) attribute1; + final Association association2 = (Association) attribute2; + return association1.getForeignKeyDescriptor().getAssociationKey().equals( + association2.getForeignKeyDescriptor().getAssociationKey() + ); + } + else if ( attribute1 instanceof PluralAttributeMapping ) { + final PluralAttributeMapping plural1 = (PluralAttributeMapping) attribute1; + final PluralAttributeMapping plural2 = (PluralAttributeMapping) attribute2; + final CollectionPart element1 = plural1.getElementDescriptor(); + final CollectionPart element2 = plural2.getElementDescriptor(); + final CollectionPart index1 = plural1.getIndexDescriptor(); + final CollectionPart index2 = plural2.getIndexDescriptor(); + return plural1.getKeyDescriptor().getAssociationKey().equals( + plural2.getKeyDescriptor().getAssociationKey() + ) && ( index1 == null && index2 == null || isCompatibleModelPart( index1, index2 ) ) + && isCompatibleModelPart( element1, element2 ); + } + else if ( attribute1 instanceof EmbeddableValuedModelPart ) { + final EmbeddableValuedModelPart embedded1 = (EmbeddableValuedModelPart) attribute1; + final EmbeddableValuedModelPart embedded2 = (EmbeddableValuedModelPart) attribute2; + final List attrs1 = embedded1.getEmbeddableTypeDescriptor().getAttributeMappings(); + final List attrs2 = embedded2.getEmbeddableTypeDescriptor().getAttributeMappings(); + if ( attrs1.size() != attrs2.size() ) { + return false; + } + for ( int i = 0; i < attrs1.size(); i++ ) { + if ( !isCompatibleModelPart( attrs1.get( i ), attrs2.get( i ) ) ) { + return false; + } + } + return true; + } + else if ( attribute1 instanceof BasicValuedModelPart ) { + final BasicValuedModelPart basic1 = (BasicValuedModelPart) attribute1; + final BasicValuedModelPart basic2 = (BasicValuedModelPart) attribute2; + if ( !basic1.getSelectionExpression().equals( basic2.getSelectionExpression() ) ) { + return false; + } + if ( basic1.getContainingTableExpression().equals( basic2.getContainingTableExpression() ) ) { + return true; + } + // For union subclass mappings we also consider mappings compatible that just match the selection expression, + // because we match up columns of disjoint union subclass types by column name + return attribute1.findContainingEntityMapping().getEntityPersister() instanceof UnionSubclassEntityPersister; + } + return false; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java index 3f1f9d10ea..3c366bceaf 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java @@ -15,6 +15,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -31,8 +32,12 @@ import org.hibernate.graph.internal.SubGraphImpl; import org.hibernate.graph.spi.SubGraphImplementor; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.RepresentationMode; +import org.hibernate.metamodel.RuntimeMetamodels; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.MappingModelHelper; import org.hibernate.metamodel.model.domain.internal.AttributeContainer; import org.hibernate.metamodel.model.domain.internal.DomainModelHelper; +import org.hibernate.query.SemanticException; import org.hibernate.type.descriptor.java.JavaType; /** @@ -167,11 +172,45 @@ public abstract class AbstractManagedType for ( ManagedDomainType subType : subTypes ) { PersistentAttribute subTypeAttribute = subType.findSubTypesAttribute( name ); if ( subTypeAttribute != null ) { - return subTypeAttribute; + if ( attribute != null && !isCompatible( attribute, subTypeAttribute ) ) { + throw new IllegalArgumentException( + new SemanticException( + String.format( + Locale.ROOT, + "Could not resolve attribute '%s' of '%s' due to the attribute being declared in multiple sub types: ['%s', '%s']", + name, + getExpressibleJavaType().getJavaType().getTypeName(), + attribute.getDeclaringType().getExpressibleJavaType().getJavaType().getTypeName(), + subTypeAttribute.getDeclaringType().getExpressibleJavaType().getJavaType().getTypeName() + ) + ) + ); + } + attribute = subTypeAttribute; } } - return null; + return attribute; + } + + private boolean isCompatible(PersistentAttribute attribute1, PersistentAttribute attribute2) { + if ( attribute1 == attribute2 ) { + return true; + } + final RuntimeMetamodels runtimeMetamodels = jpaMetamodel().getTypeConfiguration() + .getSessionFactory() + .getRuntimeMetamodels(); + final EntityMappingType entity1 = runtimeMetamodels.getEntityMappingType( + attribute1.getDeclaringType().getTypeName() + ); + final EntityMappingType entity2 = runtimeMetamodels.getEntityMappingType( + attribute2.getDeclaringType().getTypeName() + ); + + return entity1 != null && entity2 != null && MappingModelHelper.isCompatibleModelPart( + entity1.findSubPart( attribute1.getName() ), + entity2.findSubPart( attribute2.getName() ) + ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/MappedSuperclassDomainType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/MappedSuperclassDomainType.java index ab8244efa6..567db25037 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/MappedSuperclassDomainType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/MappedSuperclassDomainType.java @@ -6,6 +6,8 @@ */ package org.hibernate.metamodel.model.domain; +import org.hibernate.query.sqm.SqmPathSource; + import jakarta.persistence.metamodel.MappedSuperclassType; /** @@ -13,5 +15,5 @@ import jakarta.persistence.metamodel.MappedSuperclassType; * * @author Steve Ebersole */ -public interface MappedSuperclassDomainType extends IdentifiableDomainType, MappedSuperclassType { +public interface MappedSuperclassDomainType extends IdentifiableDomainType, MappedSuperclassType, SqmPathSource { } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java index f8dfc47259..d1c20c964d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java @@ -82,6 +82,20 @@ public class EntityTypeImpl entityDescriptor ); } + public EntityTypeImpl(JavaType javaTypeDescriptor, JpaMetamodel jpaMetamodel) { + super( + javaTypeDescriptor.getJavaTypeClass().getName(), + javaTypeDescriptor, + null, + false, + false, + false, + jpaMetamodel + ); + + this.jpaEntityName = javaTypeDescriptor.getJavaTypeClass().getName(); + this.discriminatorPathSource = null; + } @Override public String getName() { @@ -131,7 +145,6 @@ public class EntityTypeImpl } if ( "id".equalsIgnoreCase( name ) || EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( name ) ) { - //noinspection unchecked final SingularPersistentAttribute idAttribute = findIdAttribute(); //noinspection RedundantIfStatement if ( idAttribute != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index a7f9c7d119..b591f1d239 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -490,16 +490,21 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { context.wrapUp(); - this.jpaEntityTypeMap.putAll( context.getEntityTypesByEntityName() ); + for ( Map.Entry> entry : context.getIdentifiableTypesByName().entrySet() ) { + if ( entry.getValue() instanceof EntityDomainType ) { + this.jpaEntityTypeMap.put( entry.getKey(), (EntityDomainType) entry.getValue() ); + } + } + this.jpaManagedTypeMap.putAll( context.getEntityTypeMap() ); this.jpaManagedTypeMap.putAll( context.getMappedSuperclassTypeMap() ); - this.jpaManagedTypes.addAll( context.getMappedSuperclassTypeMap().values() ); switch ( jpaMetaModelPopulationSetting ) { case IGNORE_UNSUPPORTED: this.jpaManagedTypes.addAll( context.getEntityTypeMap().values() ); + this.jpaManagedTypes.addAll( context.getMappedSuperclassTypeMap().values() ); break; case ENABLED: - this.jpaManagedTypes.addAll( context.getEntityTypesByEntityName().values() ); + this.jpaManagedTypes.addAll( context.getIdentifiableTypesByName().values() ); break; } @@ -562,11 +567,8 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { private static Stream> domainTypeStream(MetadataContext context) { return Stream.concat( - context.getEntityTypesByEntityName().values().stream(), - Stream.concat( - context.getMappedSuperclassTypeMap().values().stream(), - context.getEmbeddableTypeSet().stream() - ) + context.getIdentifiableTypesByName().values().stream(), + context.getEmbeddableTypeSet().stream() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassSqmPathSource.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassSqmPathSource.java new file mode 100644 index 0000000000..d060c66d4d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassSqmPathSource.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.metamodel.model.domain.MappedSuperclassDomainType; +import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.spi.NavigablePath; +import org.hibernate.query.sqm.SqmJoinable; +import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmJoinType; +import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; +import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; +import org.hibernate.query.sqm.tree.from.SqmFrom; + +/** + * @author Steve Ebersole + */ +public class MappedSuperclassSqmPathSource extends AbstractSqmPathSource implements SqmJoinable { + public MappedSuperclassSqmPathSource( + String localPathName, + MappedSuperclassDomainType domainType, + BindableType jpaBindableType) { + super( localPathName, domainType, jpaBindableType ); + } + + @Override + public MappedSuperclassDomainType getSqmPathType() { + //noinspection unchecked + return ( MappedSuperclassDomainType ) super.getSqmPathType(); + } + + @Override + public SqmPathSource findSubPathSource(String name) { + final MappedSuperclassDomainType sqmPathType = getSqmPathType(); + return sqmPathType.findSubPathSource( name ); + } + + @Override + public SqmPath createSqmPath(SqmPath lhs, SqmPathSource intermediatePathSource) { + final NavigablePath navigablePath; + if ( intermediatePathSource == null ) { + navigablePath = lhs.getNavigablePath().append( getPathName() ); + } + else { + navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() ); + } + return new SqmEntityValuedSimplePath<>( + navigablePath, + this, + lhs, + lhs.nodeBuilder() + ); + } + + @Override + public SqmPluralPartJoin createSqmJoin( + SqmFrom lhs, + SqmJoinType joinType, + String alias, + boolean fetched, + SqmCreationState creationState) { + return new SqmPluralPartJoin<>( + lhs, + this, + alias, + joinType, + creationState.getCreationContext().getNodeBuilder() + ); + } + + @Override + public String getName() { + return getPathName(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassTypeImpl.java index 5823bde9f7..9cbb144e08 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassTypeImpl.java @@ -7,23 +7,33 @@ package org.hibernate.metamodel.model.domain.internal; import org.hibernate.cfg.NotYetImplementedException; +import org.hibernate.graph.internal.SubGraphImpl; import org.hibernate.graph.spi.SubGraphImplementor; import org.hibernate.mapping.MappedSuperclass; +import org.hibernate.metamodel.UnsupportedMappingException; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.model.domain.AbstractIdentifiableType; +import org.hibernate.metamodel.model.domain.DomainType; +import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.IdentifiableDomainType; import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.model.domain.MappedSuperclassDomainType; +import org.hibernate.metamodel.model.domain.PersistentAttribute; +import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; +import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.type.descriptor.java.JavaType; /** * @author Emmanuel Bernard * @author Steve Ebersole */ -public class MappedSuperclassTypeImpl extends AbstractIdentifiableType implements MappedSuperclassDomainType { +public class MappedSuperclassTypeImpl extends AbstractIdentifiableType implements MappedSuperclassDomainType { public MappedSuperclassTypeImpl( - JavaType javaType, + JavaType javaType, MappedSuperclass mappedSuperclass, - IdentifiableDomainType superType, + IdentifiableDomainType superType, JpaMetamodel jpaMetamodel) { super( javaType.getJavaType().getTypeName(), @@ -36,18 +46,91 @@ public class MappedSuperclassTypeImpl extends AbstractIdentifiableType imp ); } + + @Override + public String getPathName() { + return getTypeName(); + } + + @Override + public MappedSuperclassDomainType getSqmPathType() { + return this; + } + + @Override + public SqmPathSource findSubPathSource(String name) { + final PersistentAttribute attribute = findAttribute( name ); + if ( attribute != null ) { + return (SqmPathSource) attribute; + } + + if ( "id".equalsIgnoreCase( name ) ) { + if ( hasIdClass() ) { + return getIdentifierDescriptor(); + } + } + + return null; + } + + @Override + public PersistentAttribute findAttribute(String name) { + final PersistentAttribute attribute = super.findAttribute( name ); + if ( attribute != null ) { + return attribute; + } + + if ( "id".equalsIgnoreCase( name ) || EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( name ) ) { + final SingularPersistentAttribute idAttribute = findIdAttribute(); + //noinspection RedundantIfStatement + if ( idAttribute != null ) { + return idAttribute; + } + } + + return null; + } + + @Override + public BindableType getBindableType() { + return BindableType.ENTITY_TYPE; + } + @Override public PersistenceType getPersistenceType() { return PersistenceType.MAPPED_SUPERCLASS; } @Override - public SubGraphImplementor makeSubGraph(Class subType) { - throw new NotYetImplementedException( ); + @SuppressWarnings("unchecked") + public SubGraphImplementor makeSubGraph(Class subType) { + if ( ! getBindableJavaType().isAssignableFrom( subType ) ) { + throw new IllegalArgumentException( + String.format( + "MappedSuperclass type [%s] cannot be treated as requested sub-type [%s]", + getTypeName(), + subType.getName() + ) + ); + } + + return new SubGraphImpl( this, true, jpaMetamodel() ); + } + + @Override + public SubGraphImplementor makeSubGraph() { + return makeSubGraph( getBindableJavaType() ); } @Override protected boolean isIdMappingRequired() { return false; } + + @Override + public SqmPath createSqmPath(SqmPath lhs, SqmPathSource intermediatePathSource) { + throw new UnsupportedMappingException( + "MappedSuperclassType cannot be used to create an SqmPath - that would be an SqmFrom which are created directly" + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index b9a8afdb2b..f3e6977df1 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -21,6 +21,7 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -164,6 +165,7 @@ import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.MappingModelHelper; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; @@ -202,6 +204,7 @@ import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.property.access.spi.Setter; import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.spi.NavigablePath; +import org.hibernate.query.SemanticException; import org.hibernate.query.named.NamedQueryMemento; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.sql.internal.SQLQueryParser; @@ -6337,13 +6340,36 @@ public abstract class AbstractEntityPersister } else { if ( subclassMappingTypes != null && !subclassMappingTypes.isEmpty() ) { + ModelPart attribute = null; for ( EntityMappingType subMappingType : subclassMappingTypes.values() ) { final ModelPart subDefinedAttribute = subMappingType.findSubTypesSubPart( name, treatTargetType ); - if ( subDefinedAttribute != null ) { - return subDefinedAttribute; + if ( attribute != null && !MappingModelHelper.isCompatibleModelPart( attribute, subDefinedAttribute ) ) { + throw new IllegalArgumentException( + new SemanticException( + String.format( + Locale.ROOT, + "Could not resolve attribute '%s' of '%s' due to the attribute being declared in multiple sub types: ['%s', '%s']", + name, + getJavaType().getJavaType().getTypeName(), + ( (AttributeMapping) attribute ).getDeclaringType() + .getJavaType() + .getJavaType() + .getTypeName(), + ( (AttributeMapping) subDefinedAttribute ).getDeclaringType() + .getJavaType() + .getJavaType() + .getTypeName() + ) + ) + ); + } + attribute = subDefinedAttribute; } } + if ( attribute != null ) { + return attribute; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java index d08cc6e629..d84d3312b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java @@ -245,7 +245,12 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { public void consumeTreat(String entityName, boolean isTerminal) { final EntityDomainType entityDomainType = creationState.getCreationContext().getJpaMetamodel() .entity( entityName ); - currentPath = currentPath.treatAs( entityDomainType, isTerminal ? alias : null ); + if ( isTerminal ) { + currentPath = currentPath.treatAs( entityDomainType, alias ); + } + else { + currentPath = currentPath.treatAs( entityDomainType ); + } creationState.getCurrentProcessingState().getPathRegistry().register( currentPath ); } 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 e18d2dcc97..c40236c9ff 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 @@ -4259,8 +4259,16 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public SqmPath visitTreatedNavigablePath(HqlParser.TreatedNavigablePathContext ctx) { final DotIdentifierConsumer consumer = dotIdentifierConsumerStack.getCurrent(); + final boolean madeNested; if ( consumer instanceof QualifiedJoinPathConsumer) { - ( (QualifiedJoinPathConsumer) consumer ).setNested( true ); + final QualifiedJoinPathConsumer qualifiedJoinPathConsumer = (QualifiedJoinPathConsumer) consumer; + madeNested = !qualifiedJoinPathConsumer.isNested(); + if ( madeNested ) { + qualifiedJoinPathConsumer.setNested( true ); + } + } + else { + madeNested = false; } consumeManagedTypeReference( (HqlParser.PathContext) ctx.getChild( 2 ) ); @@ -4272,18 +4280,27 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem SqmPath result = (SqmPath) consumer.getConsumedPart(); if ( hasContinuation ) { - dotIdentifierConsumerStack.push( - new BasicDotIdentifierConsumer( result, this ) { - @Override - protected void reset() { + if ( madeNested ) { + // Reset the nested state before consuming the terminal identifier + ( (QualifiedJoinPathConsumer) consumer ).setNested( false ); + } + final boolean addConsumer = !( consumer instanceof QualifiedJoinPathConsumer ); + if ( addConsumer ) { + dotIdentifierConsumerStack.push( + new BasicDotIdentifierConsumer( result, this ) { + @Override + protected void reset() { + } } - } - ); + ); + } try { result = consumeDomainPath( (HqlParser.SimplePathContext) ctx.getChild( 6 ).getChild( 1 ) ); } finally { - dotIdentifierConsumerStack.pop(); + if ( addConsumer ) { + dotIdentifierConsumerStack.pop(); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmMappingModelHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmMappingModelHelper.java index c92a376d1f..cb093508ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmMappingModelHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmMappingModelHelper.java @@ -20,11 +20,13 @@ import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.EmbeddableDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.metamodel.model.domain.MappedSuperclassDomainType; import org.hibernate.metamodel.model.domain.internal.AnyMappingSqmPathSource; import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource; import org.hibernate.metamodel.MappingMetamodel; +import org.hibernate.metamodel.model.domain.internal.MappedSuperclassSqmPathSource; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.sqm.SqmExpressible; @@ -75,7 +77,7 @@ public class SqmMappingModelHelper { DomainType valueDomainType, Bindable.BindableType jpaBindableType) { - if ( valueDomainType instanceof BasicDomainType ) { + if ( valueDomainType instanceof BasicDomainType ) { return new BasicSqmPathSource<>( name, (BasicDomainType) valueDomainType, @@ -83,7 +85,7 @@ public class SqmMappingModelHelper { ); } - if ( valueDomainType instanceof AnyMappingDomainType ) { + if ( valueDomainType instanceof AnyMappingDomainType ) { return new AnyMappingSqmPathSource<>( name, (AnyMappingDomainType) valueDomainType, @@ -91,7 +93,7 @@ public class SqmMappingModelHelper { ); } - if ( valueDomainType instanceof EmbeddableDomainType ) { + if ( valueDomainType instanceof EmbeddableDomainType ) { return new EmbeddedSqmPathSource<>( name, (EmbeddableDomainType) valueDomainType, @@ -99,7 +101,7 @@ public class SqmMappingModelHelper { ); } - if ( valueDomainType instanceof EntityDomainType ) { + if ( valueDomainType instanceof EntityDomainType ) { return new EntitySqmPathSource<>( name, (EntityDomainType) valueDomainType, @@ -107,6 +109,14 @@ public class SqmMappingModelHelper { ); } + if ( valueDomainType instanceof MappedSuperclassDomainType ) { + return new MappedSuperclassSqmPathSource<>( + name, + (MappedSuperclassDomainType) valueDomainType, + jpaBindableType + ); + } + throw new IllegalArgumentException( "Unrecognized value type Java-type [" + valueDomainType.getTypeName() + "] for plural attribute value" ); @@ -169,8 +179,9 @@ public class SqmMappingModelHelper { public static EntityMappingType resolveExplicitTreatTarget( SqmPath sqmPath, SqmToSqlAstConverter converter) { - if ( sqmPath instanceof SqmTreatedPath ) { - final SqmTreatedPath treatedPath = (SqmTreatedPath) sqmPath; + final SqmPath parentPath = sqmPath.getLhs(); + if ( parentPath instanceof SqmTreatedPath ) { + final SqmTreatedPath treatedPath = (SqmTreatedPath) parentPath; return resolveEntityPersister( treatedPath.getTreatTarget(), converter.getCreationContext().getSessionFactory() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/discriminator/JoinedInheritanceEagerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/discriminator/JoinedInheritanceEagerTest.java index 31a0d13b66..34205f219c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/discriminator/JoinedInheritanceEagerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/discriminator/JoinedInheritanceEagerTest.java @@ -39,19 +39,15 @@ import jakarta.persistence.InheritanceType; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; -import org.hibernate.query.sqm.InterpretationException; - -import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.JiraKey; -import org.hibernate.testing.orm.junit.NotImplementedYet; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.Assert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; /** @@ -109,7 +105,6 @@ public class JoinedInheritanceEagerTest { @Test @JiraKey("HHH-12375") - @NotImplementedYet( strict = false ) public void joinUnrelatedCollectionOnBaseType(SessionFactoryScope scope) { scope.inSession( session -> { @@ -119,8 +114,8 @@ public class JoinedInheritanceEagerTest { session.createQuery( "from BaseEntity b join b.attributes" ).list(); fail( "Expected a resolution exception for property 'attributes'!" ); } - catch (InterpretationException ex) { - assertTrue( ex.getMessage().contains( "could not resolve property: attributes " ) ); + catch (IllegalArgumentException ex) { + Assert.assertTrue( ex.getCause().getCause().getMessage().contains( "Could not resolve attribute 'attributes' ")); } finally { session.getTransaction().commit(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java index a2d6439771..ed850c50b5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java @@ -37,6 +37,14 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + import jakarta.persistence.AssociationOverride; import jakarta.persistence.AssociationOverrides; import jakarta.persistence.Basic; @@ -57,14 +65,6 @@ import jakarta.persistence.MappedSuperclass; import jakarta.persistence.OneToMany; import jakarta.persistence.OrderColumn; -import org.hibernate.NotYetImplementedFor6Exception; - -import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.NotImplementedYet; -import org.hibernate.testing.orm.junit.SessionFactory; -import org.hibernate.testing.orm.junit.SessionFactoryScope; -import org.junit.jupiter.api.Test; - /** * @author Christian Beikov */ @@ -79,68 +79,60 @@ import org.junit.jupiter.api.Test; MultiInheritanceImplicitDowncastTest.PolymorphicPropertySub2.class, MultiInheritanceImplicitDowncastTest.PolymorphicSub1.class, MultiInheritanceImplicitDowncastTest.PolymorphicSub2.class - } -) -@SessionFactory -@NotImplementedYet( strict = false ) + }) +@SessionFactory(statementInspectorClass = SQLStatementInspector.class) public class MultiInheritanceImplicitDowncastTest { - @Test - public void testQueryingSingle(SessionFactoryScope scope) { - scope.inTransaction( (s) -> { - final String base = "from PolymorphicPropertyBase p left join "; - s.createQuery( base + "p.base b left join b.relation1 " ).getResultList(); - s.createQuery( base + "p.base b left join b.relation2 " ).getResultList(); - s.createQuery( base + "p.baseEmbeddable.embeddedRelation1 b left join b.relation1" ).getResultList(); - s.createQuery( base + "p.baseEmbeddable.embeddedRelation2 b left join b.relation2" ).getResultList(); - s.createQuery( base + "p.baseEmbeddable.embeddedBase b left join b.relation1" ).getResultList(); - s.createQuery( base + "p.baseEmbeddable.embeddedBase b left join b.relation2" ).getResultList(); - } ); - } @Test - public void testQueryingMultiple(SessionFactoryScope scope) { - scope.inTransaction( (s) -> { - final String base = "from PolymorphicPropertyBase p left join "; - s.createQuery( base + "p.base b left join b.relation1 left join b.relation2" ).getResultList(); - s.createQuery( base + "p.base b left join b.relation2 left join b.relation1" ).getResultList(); - s.createQuery( base + "p.baseEmbeddable.embeddedBase b left join b.relation1 left join b.relation2" ).getResultList(); - s.createQuery( base + "p.baseEmbeddable.embeddedBase b left join b.relation2 left join b.relation1" ).getResultList(); - } ); + public void testIllegalBaseJoin(SessionFactoryScope scope) { + try { + scope.inSession( + s -> s.createQuery( "from PolymorphicPropertyBase p left join p.base b left join b.relation1" ) + ); + } + catch (IllegalArgumentException ex) { + Assertions.assertTrue( ex.getCause() + .getCause() + .getMessage() + .contains( "Could not resolve attribute 'base' " ) ); + } } @Test public void testMultiJoinAddition1(SessionFactoryScope scope) { - testMultiJoinAddition( "from PolymorphicPropertyBase p left join p.base b left join b.relation1", scope ); + testMultiJoinAddition( + scope, + "base_sub_1", + "select 1 from PolymorphicPropertyBase p left join treat(p as PolymorphicPropertySub1).base b left join b.relation1" + ); } @Test public void testMultiJoinAddition2(SessionFactoryScope scope) { - testMultiJoinAddition( "from PolymorphicPropertyBase p left join p.base b left join b.relation2", scope ); + testMultiJoinAddition( + scope, + "base_sub_2", + "select 1 from PolymorphicPropertyBase p left join treat(p as PolymorphicPropertySub2).base b left join b.relation2" + ); } - private void testMultiJoinAddition(String hql, SessionFactoryScope scope) { - throw new NotYetImplementedFor6Exception( getClass() ); -// final HQLQueryPlan plan = sessionFactory().getQueryInterpretationCache().getHQLQueryPlan( -// hql, -// false, -// Collections.EMPTY_MAP -// ); -// assertEquals( 1, plan.getTranslators().length ); -// final QueryTranslator translator = plan.getTranslators()[0]; -// final String generatedSql = translator.getSQLString(); -// -// int sub1JoinColumnIndex = generatedSql.indexOf( ".base_sub_1" ); -// assertNotEquals( -// "Generated SQL doesn't contain a join for 'base' with 'PolymorphicSub1' via 'base_sub_1':\n" + generatedSql, -// -1, -// sub1JoinColumnIndex -// ); -// int sub2JoinColumnIndex = generatedSql.indexOf( ".base_sub_2" ); -// assertNotEquals( -// "Generated SQL doesn't contain a join for 'base' with 'PolymorphicSub2' via 'base_sub_2':\n" + generatedSql, -// -1, -// sub2JoinColumnIndex -// ); + private void testMultiJoinAddition(SessionFactoryScope scope, String joinColumnBase, String hql) { + SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + scope.inTransaction( + s -> { + sqlStatementInterceptor.clear(); + s.createQuery( hql ).getResultList(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + final String generatedSql = sqlStatementInterceptor.getSqlQueries().get( 0 ); + + int sub1JoinColumnIndex = generatedSql.indexOf( "." + joinColumnBase ); + Assertions.assertNotEquals( + -1, + sub1JoinColumnIndex, + "Generated SQL doesn't contain a join for 'base' via '" + joinColumnBase + "':\n" + generatedSql + ); + } + ); } @MappedSuperclass @@ -466,13 +458,12 @@ public class MultiInheritanceImplicitDowncastTest { } @MappedSuperclass - public abstract static class PolymorphicPropertyMapBase extends - PolymorphicPropertyBase { + public abstract static class PolymorphicPropertyMapBase extends PolymorphicPropertyBase { private static final long serialVersionUID = 1L; private T base; - private E baseEmbeddable; + private Set bases; public PolymorphicPropertyMapBase() { } @@ -486,21 +477,22 @@ public class MultiInheritanceImplicitDowncastTest { this.base = base; } - @Embedded - public E getBaseEmbeddable() { - return baseEmbeddable; + @OneToMany + public Set getBases() { + return bases; } - public void setBaseEmbeddable(E baseEmbeddable) { - this.baseEmbeddable = baseEmbeddable; + public void setBases(Set bases) { + this.bases = bases; } + } @Entity(name = "PolymorphicPropertySub1") @AssociationOverrides({ @AssociationOverride(name = "base", joinColumns = @JoinColumn(name = "base_sub_1")) }) - public static class PolymorphicPropertySub1 extends PolymorphicPropertyMapBase { + public static class PolymorphicPropertySub1 extends PolymorphicPropertyMapBase { private static final long serialVersionUID = 1L; public PolymorphicPropertySub1() { @@ -511,7 +503,7 @@ public class MultiInheritanceImplicitDowncastTest { @AssociationOverrides({ @AssociationOverride(name = "base", joinColumns = @JoinColumn(name = "base_sub_2")) }) - public static class PolymorphicPropertySub2 extends PolymorphicPropertyMapBase { + public static class PolymorphicPropertySub2 extends PolymorphicPropertyMapBase { private static final long serialVersionUID = 1L; public PolymorphicPropertySub2() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/joined/JoinedSubclassTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/joined/JoinedSubclassTest.java index 4ee2d7638d..dc4491b106 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/joined/JoinedSubclassTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/joined/JoinedSubclassTest.java @@ -16,7 +16,6 @@ import org.hibernate.Hibernate; import org.hibernate.query.Query; import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.NotImplementedYet; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.AfterEach; @@ -47,7 +46,6 @@ public class JoinedSubclassTest { } @Test - @NotImplementedYet( strict = false ) public void testJoinedSubclass(SessionFactoryScope scope) { scope.inTransaction( s -> { @@ -76,8 +74,6 @@ public class JoinedSubclassTest { s.save( mark ); s.save( joe ); - assertEquals( s.createQuery( "from java.lang.Object" ).list().size(), 0 ); - assertEquals( s.createQuery( "from Person" ).list().size(), 3 ); assertEquals( s.createQuery( "from Person p where p.class = Customer" ).list().size(), 1 ); assertEquals( s.createQuery( "from Person p where p.class = Person" ).list().size(), 1 );