From d647599fe97a0ed1951fcc9d6cf8a21226d9a2e7 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 15 Oct 2024 18:02:06 -0500 Subject: [PATCH] HHH-18733 - Add a prepareMappingModel phase to collection persisters Co-authored-by: Christian Beikov --- .../main/java/org/hibernate/mapping/Any.java | 9 + .../org/hibernate/mapping/MappingHelper.java | 18 +- .../DefaultDiscriminatorConverter.java | 88 ------- .../mapping/MappedDiscriminatorConverter.java | 151 ------------ .../internal/AnyDiscriminatorPart.java | 73 ++++-- .../DiscriminatedAssociationMapping.java | 1 + .../ExplicitDiscriminatorConverter.java | 130 +++++++++++ .../ImplicitDiscriminatorConverter.java | 109 +++++++++ .../internal/InFlightCollectionMapping.java | 24 ++ .../internal/InFlightEntityMappingType.java | 2 + .../internal/MappingModelCreationProcess.java | 12 + .../internal/MixedDiscriminatorConverter.java | 133 +++++++++++ .../internal/AnyMappingDomainTypeImpl.java | 18 +- .../domain/internal/MappingMetamodelImpl.java | 2 +- .../AbstractCollectionPersister.java | 217 ++++++++++++------ .../entity/AbstractEntityPersister.java | 13 +- .../persister/entity/EntityPersister.java | 13 ++ .../type/AnyDiscriminatorValueStrategy.java | 55 +++++ .../main/java/org/hibernate/type/AnyType.java | 25 +- .../java/org/hibernate/type/MetaType.java | 72 ++++-- .../GoofyPersisterClassProvider.java | 6 + .../PersisterClassProviderTest.java | 6 + .../orm/test/legacy/CustomPersister.java | 5 + 23 files changed, 803 insertions(+), 379 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DefaultDiscriminatorConverter.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappedDiscriminatorConverter.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitDiscriminatorConverter.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ImplicitDiscriminatorConverter.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InFlightCollectionMapping.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MixedDiscriminatorConverter.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/AnyDiscriminatorValueStrategy.java 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 29a570a21e..c7eb58d919 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java @@ -12,6 +12,7 @@ import java.util.function.Consumer; import org.hibernate.MappingException; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.type.AnyDiscriminatorValueStrategy; import org.hibernate.type.AnyType; import org.hibernate.type.Type; import org.hibernate.type.MappingContext; @@ -301,9 +302,13 @@ public class Any extends SimpleValue { } } + /** + * The discriminator {@linkplain Value} + */ public static class MetaValue extends SimpleValue { private String typeName; private String columnName; + private AnyDiscriminatorValueStrategy valueStrategy; private final Consumer selectableConsumer; @@ -396,6 +401,10 @@ public class Any extends SimpleValue { return columnName != null && getType().getColumnSpan( mappingContext ) == 1; } + + public AnyDiscriminatorValueStrategy getValueStrategy() { + return valueStrategy; + } } public static class KeyValue extends SimpleValue { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java b/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java index db33069b10..77e5a0364f 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java @@ -22,6 +22,7 @@ import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; import org.hibernate.resource.beans.spi.ManagedBean; import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.resource.beans.spi.ProvidedInstanceManagedBeanImpl; +import org.hibernate.type.AnyDiscriminatorValueStrategy; import org.hibernate.type.AnyType; import org.hibernate.type.CollectionType; import org.hibernate.type.CustomCollectionType; @@ -123,15 +124,22 @@ public final class MappingHelper { } public static AnyType anyMapping( - Type metaType, + Type discriminatorType, Type identifierType, - Map metaValueToEntityNameMap, + Map explicitValeMappings, boolean lazy, MetadataBuildingContext buildingContext) { - if ( metaValueToEntityNameMap != null ) { - metaType = new MetaType( metaValueToEntityNameMap, metaType ); - } + return anyMapping( discriminatorType, identifierType, AnyDiscriminatorValueStrategy.AUTO, explicitValeMappings, lazy, buildingContext ); + } + public static AnyType anyMapping( + Type discriminatorType, + Type identifierType, + AnyDiscriminatorValueStrategy discriminatorValueStrategy, + Map explicitValeMappings, + boolean lazy, + MetadataBuildingContext buildingContext) { + final MetaType metaType = new MetaType( discriminatorType, discriminatorValueStrategy, explicitValeMappings ); return new AnyType( buildingContext.getBootstrapContext().getTypeConfiguration(), metaType, identifierType, lazy ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DefaultDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DefaultDiscriminatorConverter.java deleted file mode 100644 index b55f5a2d44..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DefaultDiscriminatorConverter.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.metamodel.mapping; - -import org.hibernate.AssertionFailure; -import org.hibernate.HibernateException; -import org.hibernate.metamodel.mapping.internal.DiscriminatorValueDetailsImpl; -import org.hibernate.metamodel.model.domain.NavigableRole; -import org.hibernate.metamodel.spi.MappingMetamodelImplementor; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.BasicType; -import org.hibernate.type.descriptor.java.JavaType; - -import java.util.function.Consumer; -import java.util.function.Function; - -/** - * Implements the default discriminator assignment strategy defined by JPA, - * that is, the discriminator value is the JPA (unqualified) entity name. - * This strategy is used when no explicit discriminator mapping is specified. - * - * @author Gavin King - */ -public class DefaultDiscriminatorConverter extends DiscriminatorConverter { - - public static DefaultDiscriminatorConverter fromMappingMetamodel( - NavigableRole role, - JavaType domainJavaType, - BasicType underlyingJdbcMapping, - MappingMetamodelImplementor mappingMetamodel) { - return new DefaultDiscriminatorConverter<>( - role, - domainJavaType, - underlyingJdbcMapping.getJavaTypeDescriptor(), - mappingMetamodel - ); - } - - private final MappingMetamodelImplementor mappingMetamodel; - - public DefaultDiscriminatorConverter( - NavigableRole discriminatorRole, - JavaType domainJavaType, - JavaType relationalJavaType, - MappingMetamodelImplementor mappingMetamodel) { - super( discriminatorRole.getFullPath(), domainJavaType, relationalJavaType ); - this.mappingMetamodel = mappingMetamodel; - } - - @Override - public DiscriminatorValueDetails getDetailsForRelationalForm(R relationalForm) { - return getDetailsForDiscriminatorValue( relationalForm ); - } - - @Override - public DiscriminatorValueDetails getDetailsForEntityName(String entityName) { - EntityPersister persister = mappingMetamodel.findEntityDescriptor( entityName ); - if ( persister!= null ) { - return new DiscriminatorValueDetailsImpl( entityName, persister ); - } - - throw new AssertionFailure( "Unrecognized entity name: " + entityName ); - } - - @Override - public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object value) { - if ( value instanceof String ) { - String entityName = mappingMetamodel.getImportedName( (String) value ); - EntityPersister persister = mappingMetamodel.findEntityDescriptor( entityName ); - if ( persister!= null ) { - return new DiscriminatorValueDetailsImpl( entityName, persister ); - } - } - - throw new HibernateException( "Unrecognized discriminator value: " + value ); - } - - @Override - public void forEachValueDetail(Consumer consumer) { - } - - @Override - public X fromValueDetails(Function handler) { - return null; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappedDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappedDiscriminatorConverter.java deleted file mode 100644 index c6fffadb5d..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappedDiscriminatorConverter.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.metamodel.mapping; - -import org.hibernate.AssertionFailure; -import org.hibernate.HibernateException; -import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.metamodel.mapping.internal.DiscriminatorValueDetailsImpl; -import org.hibernate.metamodel.model.domain.NavigableRole; -import org.hibernate.metamodel.spi.MappingMetamodelImplementor; -import org.hibernate.type.BasicType; -import org.hibernate.type.descriptor.java.CharacterJavaType; -import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.java.StringJavaType; - -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; - -import static org.hibernate.persister.entity.DiscriminatorHelper.NOT_NULL_DISCRIMINATOR; -import static org.hibernate.persister.entity.DiscriminatorHelper.NULL_DISCRIMINATOR; - -/** - * Conversion of discriminator values between the entity name/Class domain form and - * its generally CHARACTER or INTEGER based relational form - * - * @param The domain type - either
    - *
  • - * the {@linkplain EntityMappingType#getMappedJavaType() entity Class} for unnamed entities - *
  • - *
  • - * the {@linkplain EntityMappingType#getEntityName() entity name} for named entities - *
  • - *
- * @param The Java type of the relational form of the discriminator - * - * @author Steve Ebersole - */ -public class MappedDiscriminatorConverter extends DiscriminatorConverter { - - public static MappedDiscriminatorConverter fromValueMappings( - NavigableRole role, - JavaType domainJavaType, - BasicType underlyingJdbcMapping, - Map valueMappings, - MappingMetamodelImplementor mappingMetamodel) { - final List valueDetailsList = CollectionHelper.arrayList( valueMappings.size() ); - valueMappings.forEach( (value, entityName) -> { - final DiscriminatorValueDetails valueDetails = new DiscriminatorValueDetailsImpl( - value, - mappingMetamodel.getEntityDescriptor( entityName ) - ); - valueDetailsList.add( valueDetails ); - } ); - - return new MappedDiscriminatorConverter<>( - role, - domainJavaType, - underlyingJdbcMapping.getJavaTypeDescriptor(), - valueDetailsList - ); - } - - private final Map discriminatorValueToEntityNameMap; - private final Map entityNameToDiscriminatorValueMap; - - public MappedDiscriminatorConverter( - NavigableRole discriminatorRole, - JavaType domainJavaType, - JavaType relationalJavaType, - List valueMappings) { - super( discriminatorRole.getFullPath(), domainJavaType, relationalJavaType ); - - this.discriminatorValueToEntityNameMap = CollectionHelper.concurrentMap( valueMappings.size() ); - this.entityNameToDiscriminatorValueMap = CollectionHelper.concurrentMap( valueMappings.size() ); - valueMappings.forEach( (valueDetails) -> { - discriminatorValueToEntityNameMap.put( valueDetails.getValue(), valueDetails ); - entityNameToDiscriminatorValueMap.put( valueDetails.getIndicatedEntityName(), valueDetails ); - } ); - } - - @Override - public DiscriminatorValueDetails getDetailsForRelationalForm(R relationalForm) { - return getDetailsForDiscriminatorValue( relationalForm ); - } - - @Override - public DiscriminatorValueDetails getDetailsForEntityName(String entityName) { - DiscriminatorValueDetails valueDetails = entityNameToDiscriminatorValueMap.get( entityName ); - if ( valueDetails!= null) { - return valueDetails; - } - - throw new AssertionFailure( "Unrecognized entity name: " + entityName ); - } - - @Override - public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object value) { - if ( value == null ) { - return discriminatorValueToEntityNameMap.get( NULL_DISCRIMINATOR ); - } - - final DiscriminatorValueDetails valueMatch = discriminatorValueToEntityNameMap.get( value ); - if ( valueMatch != null ) { - return valueMatch; - } - - final DiscriminatorValueDetails notNullMatch = discriminatorValueToEntityNameMap.get( NOT_NULL_DISCRIMINATOR ); - if ( notNullMatch != null ) { - return notNullMatch; - } - - if ( value.getClass().isEnum() ) { - final Object enumValue; - if ( getRelationalJavaType() instanceof StringJavaType ) { - enumValue = ( (Enum) value ).name(); - } - else if ( getRelationalJavaType() instanceof CharacterJavaType ) { - enumValue = ( (Enum) value ).name().charAt( 0 ); - } - else { - enumValue = ( (Enum) value ).ordinal(); - } - final DiscriminatorValueDetails enumMatch = discriminatorValueToEntityNameMap.get( enumValue ); - if ( enumMatch != null ) { - return enumMatch; - } - } - - throw new HibernateException( "Unrecognized discriminator value: " + value ); - } - - @Override - public void forEachValueDetail(Consumer consumer) { - discriminatorValueToEntityNameMap.forEach( (value, detail) -> consumer.accept( detail ) ); - } - - @Override - public X fromValueDetails(Function handler) { - for ( DiscriminatorValueDetails detail : discriminatorValueToEntityNameMap.values() ) { - final X result = handler.apply( detail ); - if ( result != null ) { - return result; - } - } - return null; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java index ecfdf7a846..3556bbf835 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java @@ -4,23 +4,19 @@ */ package org.hibernate.metamodel.mapping.internal; -import java.util.Map; -import java.util.function.BiConsumer; - +import org.hibernate.AssertionFailure; import org.hibernate.cache.MutableCacheKeyBuilder; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.IndexedConsumer; -import org.hibernate.metamodel.mapping.DefaultDiscriminatorConverter; import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart; import org.hibernate.metamodel.mapping.DiscriminatorConverter; import org.hibernate.metamodel.mapping.DiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; -import org.hibernate.metamodel.mapping.MappedDiscriminatorConverter; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.model.domain.NavigableRole; @@ -39,10 +35,14 @@ import org.hibernate.sql.results.graph.FetchOptions; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.type.AnyDiscriminatorValueStrategy; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.ClassJavaType; import org.hibernate.type.descriptor.java.JavaType; +import java.util.Map; +import java.util.function.BiConsumer; + /** * Acts as a ModelPart for the discriminator portion of an any-valued mapping * @@ -84,6 +84,7 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions boolean partitioned, BasicType underlyingJdbcMapping, Map valueToEntityNameMap, + AnyDiscriminatorValueStrategy valueStrategy, MappingMetamodelImplementor mappingMetamodel) { this.navigableRole = partRole; this.declaringType = declaringType; @@ -100,20 +101,54 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions this.partitioned = partitioned; this.underlyingJdbcMapping = underlyingJdbcMapping; - this.valueConverter = valueToEntityNameMap.isEmpty() - ? DefaultDiscriminatorConverter.fromMappingMetamodel( - partRole, - ClassJavaType.INSTANCE, - underlyingJdbcMapping, - mappingMetamodel - ) - : MappedDiscriminatorConverter.fromValueMappings( - partRole, - ClassJavaType.INSTANCE, - underlyingJdbcMapping, - valueToEntityNameMap, - mappingMetamodel - ); + this.valueConverter = determineDiscriminatorConverter( + partRole, + underlyingJdbcMapping, + valueToEntityNameMap, + valueStrategy, + mappingMetamodel + ); + } + + public static DiscriminatorConverter determineDiscriminatorConverter( + NavigableRole partRole, + BasicType underlyingJdbcMapping, + Map valueToEntityNameMap, + AnyDiscriminatorValueStrategy valueStrategy, + MappingMetamodelImplementor mappingMetamodel) { + if ( valueStrategy == AnyDiscriminatorValueStrategy.AUTO ) { + if ( valueToEntityNameMap == null || valueToEntityNameMap.isEmpty() ) { + valueStrategy = AnyDiscriminatorValueStrategy.IMPLICIT; + } + else { + valueStrategy = AnyDiscriminatorValueStrategy.EXPLICIT; + } + } + + return switch ( valueStrategy ) { + case AUTO -> throw new AssertionFailure( "Not expecting AUTO" ); + case MIXED -> new MixedDiscriminatorConverter<>( + partRole, + ClassJavaType.INSTANCE, + underlyingJdbcMapping.getJavaTypeDescriptor(), + valueToEntityNameMap, + mappingMetamodel + ); + case EXPLICIT -> new ExplicitDiscriminatorConverter<>( + partRole, + ClassJavaType.INSTANCE, + underlyingJdbcMapping.getJavaTypeDescriptor(), + valueToEntityNameMap, + mappingMetamodel + ); + case IMPLICIT -> new ImplicitDiscriminatorConverter<>( + partRole, + ClassJavaType.INSTANCE, + underlyingJdbcMapping.getJavaTypeDescriptor(), + valueToEntityNameMap, + mappingMetamodel + ); + }; } public DiscriminatorConverter getValueConverter() { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java index ae3350c117..1b788e0db9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java @@ -89,6 +89,7 @@ public class DiscriminatedAssociationMapping implements MappingType, FetchOption bootValueMapping.isPartitionKey(), (BasicType) metaType.getBaseType(), metaType.getDiscriminatorValuesToEntityNameMap(), + metaType.getValueStrategy(), creationProcess.getCreationContext().getSessionFactory().getMappingMetamodel() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitDiscriminatorConverter.java new file mode 100644 index 0000000000..e3dc1b213f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitDiscriminatorConverter.java @@ -0,0 +1,130 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.metamodel.mapping.internal; + +import org.hibernate.HibernateException; +import org.hibernate.MappingException; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.mapping.DiscriminatorConverter; +import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.descriptor.java.CharacterJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.StringJavaType; + +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import static java.util.Locale.ROOT; +import static org.hibernate.persister.entity.DiscriminatorHelper.NOT_NULL_DISCRIMINATOR; +import static org.hibernate.persister.entity.DiscriminatorHelper.NULL_DISCRIMINATOR; + +/** + * @author Steve Ebersole + */ +public class ExplicitDiscriminatorConverter extends DiscriminatorConverter { + private final NavigableRole discriminatorRole; + private final Map detailsByValue; + private final Map detailsByEntityName; + + public ExplicitDiscriminatorConverter( + NavigableRole discriminatorRole, + JavaType domainJavaType, + JavaType relationalJavaType, + Map explicitValueMappings, + MappingMetamodelImplementor mappingMetamodel) { + super( discriminatorRole.getFullPath(), domainJavaType, relationalJavaType ); + this.discriminatorRole = discriminatorRole; + + if ( CollectionHelper.isEmpty( explicitValueMappings ) ) { + throw new MappingException( String.format( + ROOT, + "No explicit ANY discriminator mappings (%s)", + discriminatorRole.getFullPath() + ) ); + } + + this.detailsByValue = CollectionHelper.concurrentMap( explicitValueMappings.size() ); + this.detailsByEntityName = CollectionHelper.concurrentMap( explicitValueMappings.size() ); + + explicitValueMappings.forEach( (value, entityName) -> { + final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( entityName ); + final DiscriminatorValueDetails details = new DiscriminatorValueDetailsImpl( value, entityDescriptor ); + detailsByValue.put( value, details ); + detailsByEntityName.put( entityDescriptor.getEntityName(), details ); + } ); + } + + @Override + public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object relationalForm) { + if ( relationalForm == null ) { + return detailsByValue.get( NULL_DISCRIMINATOR ); + } + + final DiscriminatorValueDetails existing = detailsByValue.get( relationalForm ); + if ( existing != null ) { + // an explicit or previously-resolved mapping + return existing; + } + + final DiscriminatorValueDetails notNullMatch = detailsByValue.get( NOT_NULL_DISCRIMINATOR ); + if ( notNullMatch != null ) { + return notNullMatch; + } + + if ( relationalForm.getClass().isEnum() ) { + final Object enumValue; + if ( getRelationalJavaType() instanceof StringJavaType ) { + enumValue = ( (Enum) relationalForm ).name(); + } + else if ( getRelationalJavaType() instanceof CharacterJavaType ) { + enumValue = ( (Enum) relationalForm ).name().charAt( 0 ); + } + else { + enumValue = ( (Enum) relationalForm ).ordinal(); + } + final DiscriminatorValueDetails enumMatch = detailsByValue.get( enumValue ); + if ( enumMatch != null ) { + return enumMatch; + } + } + + throw new HibernateException( String.format( + ROOT, + "Unknown discriminator value (%s) : %s", + discriminatorRole, + relationalForm + ) ); + } + + @Override + public DiscriminatorValueDetails getDetailsForEntityName(String entityName) { + final DiscriminatorValueDetails valueDetails = detailsByEntityName.get( entityName ); + if ( valueDetails != null) { + return valueDetails; + } + throw new HibernateException( "Unknown entity name (" + discriminatorRole + ") : " + entityName ); + } + + + @Override + public void forEachValueDetail(Consumer consumer) { + detailsByEntityName.forEach( (value, detail) -> consumer.accept( detail ) ); + } + + @Override + public X fromValueDetails(Function handler) { + for ( DiscriminatorValueDetails detail : detailsByEntityName.values() ) { + final X result = handler.apply( detail ); + if ( result != null ) { + return result; + } + } + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ImplicitDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ImplicitDiscriminatorConverter.java new file mode 100644 index 0000000000..90f7693982 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ImplicitDiscriminatorConverter.java @@ -0,0 +1,109 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.metamodel.mapping.internal; + +import org.hibernate.HibernateException; +import org.hibernate.MappingException; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.mapping.DiscriminatorConverter; +import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.descriptor.java.JavaType; + +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import static java.util.Locale.ROOT; + +/** + * @author Steve Ebersole + */ +public class ImplicitDiscriminatorConverter extends DiscriminatorConverter { + private final NavigableRole discriminatorRole; + private final MappingMetamodelImplementor mappingMetamodel; + private final Map detailsByValue; + private final Map detailsByEntityName; + + public ImplicitDiscriminatorConverter( + NavigableRole discriminatorRole, + JavaType domainJavaType, + JavaType relationalJavaType, + Map explicitValueMappings, + MappingMetamodelImplementor mappingMetamodel) { + super( discriminatorRole.getFullPath(), domainJavaType, relationalJavaType ); + this.discriminatorRole = discriminatorRole; + this.mappingMetamodel = mappingMetamodel; + + if ( CollectionHelper.isNotEmpty( explicitValueMappings ) ) { + throw new MappingException( String.format( + ROOT, + "Encountered explicit ANY discriminator mappings (%s)", + discriminatorRole.getFullPath() + ) ); + } + + this.detailsByValue = CollectionHelper.concurrentMap( 8 ); + this.detailsByEntityName = CollectionHelper.concurrentMap( 8 ); + } + + @Override + public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object value) { + if ( value instanceof String incoming ) { + final DiscriminatorValueDetails existingDetails = detailsByValue.get( incoming ); + if ( existingDetails != null ) { + return existingDetails; + } + final String entityName = mappingMetamodel.getImportedName( incoming ); + final EntityPersister persister = mappingMetamodel.findEntityDescriptor( entityName ); + if ( persister != null ) { + assert persister.getImportedName().equals( incoming ); + return register( incoming, persister ); + } + } + throw new HibernateException( String.format( + ROOT, + "Unrecognized discriminator value (%s): %s", + discriminatorRole.getFullPath(), + value + ) ); + } + + private DiscriminatorValueDetails register(Object value, EntityPersister entityDescriptor) { + final DiscriminatorValueDetails details = new DiscriminatorValueDetailsImpl( value, entityDescriptor ); + detailsByValue.put( value, details ); + detailsByEntityName.put( entityDescriptor.getImportedName(), details ); + return details; + } + + @Override + public DiscriminatorValueDetails getDetailsForEntityName(String entityName) { + final DiscriminatorValueDetails existingDetails = detailsByEntityName.get( entityName ); + if ( existingDetails != null ) { + return existingDetails; + } + final EntityPersister persister = mappingMetamodel.findEntityDescriptor( entityName ); + if ( persister!= null ) { + return register( persister.getImportedName(), persister ); + } + throw new HibernateException( String.format( + ROOT, + "Unrecognized entity name (%s): %s", + discriminatorRole.getFullPath(), + entityName + ) ); + } + + @Override + public void forEachValueDetail(Consumer consumer) { + } + + @Override + public X fromValueDetails(Function handler) { + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InFlightCollectionMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InFlightCollectionMapping.java new file mode 100644 index 0000000000..611161a37f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InFlightCollectionMapping.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.metamodel.mapping.internal; + +/** + * Defines the ability to perform post-creation processing for collection mappings. + * + * @apiNote Called after all {@linkplain InFlightEntityMappingType} processing has + * occurred, allowing access to the runtime mapping model of the entities. + * + * @author Steve Ebersole + */ +public interface InFlightCollectionMapping { + /** + * After all hierarchy types have been linked, this method is called to allow the + * mapping model to be prepared which generally includes creating attribute mapping + * descriptors, identifier mapping descriptor, etc. + */ + default void prepareMappingModel(MappingModelCreationProcess creationProcess) { + // by default do nothing - support for legacy impls + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InFlightEntityMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InFlightEntityMappingType.java index 16eb5741cf..7e704ff95b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InFlightEntityMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InFlightEntityMappingType.java @@ -7,6 +7,8 @@ package org.hibernate.metamodel.mapping.internal; import org.hibernate.metamodel.mapping.EntityMappingType; /** + * Defines the ability to perform post-creation processing for entity mappings. + * * @author Steve Ebersole */ public interface InFlightEntityMappingType extends EntityMappingType { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java index f3367ead9a..aa466dde22 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java @@ -16,6 +16,7 @@ import org.hibernate.metamodel.mapping.NonTransientException; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.internal.EntityPersisterConcurrentMap; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.sqm.function.SqmFunctionRegistry; @@ -32,15 +33,18 @@ public class MappingModelCreationProcess { */ public static void process( EntityPersisterConcurrentMap entityPersisterMap, + Map collectionPersisterMap, RuntimeModelCreationContext creationContext) { final MappingModelCreationProcess process = new MappingModelCreationProcess( entityPersisterMap, + collectionPersisterMap, creationContext ); process.execute(); } private final EntityPersisterConcurrentMap entityPersisterMap; + private final Map collectionPersisterMap; private final RuntimeModelCreationContext creationContext; private String currentlyProcessingRole; @@ -48,8 +52,10 @@ public class MappingModelCreationProcess { private MappingModelCreationProcess( EntityPersisterConcurrentMap entityPersisterMap, + Map collectionPersisterMap, RuntimeModelCreationContext creationContext) { this.entityPersisterMap = entityPersisterMap; + this.collectionPersisterMap = collectionPersisterMap; this.creationContext = creationContext; } @@ -83,6 +89,12 @@ public class MappingModelCreationProcess { } } + for ( CollectionPersister collectionPersister : collectionPersisterMap.values() ) { + if ( collectionPersister instanceof InFlightCollectionMapping ) { + ((InFlightCollectionMapping) collectionPersister).prepareMappingModel( this ); + } + } + executePostInitCallbacks(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MixedDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MixedDiscriminatorConverter.java new file mode 100644 index 0000000000..5c1188a65f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MixedDiscriminatorConverter.java @@ -0,0 +1,133 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.metamodel.mapping.internal; + +import org.hibernate.HibernateException; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.mapping.DiscriminatorConverter; +import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.descriptor.java.CharacterJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.StringJavaType; + +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.hibernate.persister.entity.DiscriminatorHelper.NOT_NULL_DISCRIMINATOR; +import static org.hibernate.persister.entity.DiscriminatorHelper.NULL_DISCRIMINATOR; + +/** + * DiscriminatorConverter for use with {@linkplain org.hibernate.type.AnyDiscriminatorValueStrategy#MIXED} + * + * @author Steve Ebersole + */ +public class MixedDiscriminatorConverter extends DiscriminatorConverter { + private final NavigableRole discriminatorRole; + private final Map detailsByValue; + private final Map detailsByEntityName; + private final MappingMetamodelImplementor mappingMetamodel; + + public MixedDiscriminatorConverter( + NavigableRole discriminatorRole, + JavaType domainJavaType, + JavaType relationalJavaType, + Map explicitValueMappings, + MappingMetamodelImplementor mappingMetamodel) { + super( discriminatorRole.getFullPath(), domainJavaType, relationalJavaType ); + this.discriminatorRole = discriminatorRole; + this.mappingMetamodel = mappingMetamodel; + + this.detailsByValue = CollectionHelper.concurrentMap( explicitValueMappings.size() ); + this.detailsByEntityName = CollectionHelper.concurrentMap( explicitValueMappings.size() ); + explicitValueMappings.forEach( (value,entityName) -> { + String importedEntityName = mappingMetamodel.getImportedName( entityName ); + final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( importedEntityName ); + register( value, entityDescriptor ); + } ); + } + + private DiscriminatorValueDetails register(Object value, EntityPersister entityDescriptor) { + final DiscriminatorValueDetails details = new DiscriminatorValueDetailsImpl( value, entityDescriptor ); + detailsByValue.put( value, details ); + detailsByEntityName.put( entityDescriptor.getEntityName(), details ); + return details; + } + + @Override + public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object relationalForm) { + if ( relationalForm == null ) { + return detailsByValue.get( NULL_DISCRIMINATOR ); + } + + final DiscriminatorValueDetails existing = detailsByValue.get( relationalForm ); + if ( existing != null ) { + // an explicit or previously-resolved mapping + return existing; + } + + final DiscriminatorValueDetails notNullMatch = detailsByValue.get( NOT_NULL_DISCRIMINATOR ); + if ( notNullMatch != null ) { + return notNullMatch; + } + + if ( relationalForm.getClass().isEnum() ) { + final Object enumValue; + if ( getRelationalJavaType() instanceof StringJavaType ) { + enumValue = ( (Enum) relationalForm ).name(); + } + else if ( getRelationalJavaType() instanceof CharacterJavaType ) { + enumValue = ( (Enum) relationalForm ).name().charAt( 0 ); + } + else { + enumValue = ( (Enum) relationalForm ).ordinal(); + } + final DiscriminatorValueDetails enumMatch = detailsByValue.get( enumValue ); + if ( enumMatch != null ) { + return enumMatch; + } + } + + if ( relationalForm instanceof String assumedEntityName ) { + // Assume the relational form is the entity name + final EntityPersister persister = mappingMetamodel.findEntityDescriptor( assumedEntityName ); + if ( persister != null ) { + return register( assumedEntityName, persister ); + } + } + + throw new HibernateException( "Cannot interpret discriminator value (" + discriminatorRole + ") : " + relationalForm ); + } + + @Override + public DiscriminatorValueDetails getDetailsForEntityName(String entityName) { + final DiscriminatorValueDetails existing = detailsByEntityName.get( entityName ); + if ( existing != null) { + return existing; + } + + final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( entityName ); + return register( entityName, entityDescriptor ); + } + + @Override + public void forEachValueDetail(Consumer consumer) { + detailsByEntityName.forEach( (value, detail) -> consumer.accept( detail ) ); + } + + @Override + public X fromValueDetails(Function handler) { + for ( DiscriminatorValueDetails detail : detailsByEntityName.values() ) { + final X result = handler.apply( detail ); + if ( result != null ) { + return result; + } + } + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java index 75f7fd9b5e..417236612f 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java @@ -6,8 +6,6 @@ package org.hibernate.metamodel.model.domain.internal; import org.hibernate.mapping.Any; import org.hibernate.mapping.Column; -import org.hibernate.metamodel.mapping.DefaultDiscriminatorConverter; -import org.hibernate.metamodel.mapping.MappedDiscriminatorConverter; import org.hibernate.metamodel.model.domain.AnyMappingDomainType; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.SimpleDomainType; @@ -15,12 +13,13 @@ import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.type.AnyType; import org.hibernate.type.BasicType; import org.hibernate.type.MetaType; -import org.hibernate.type.descriptor.java.ClassJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.internal.ConvertedBasicTypeImpl; import java.util.List; +import static org.hibernate.metamodel.mapping.internal.AnyDiscriminatorPart.determineDiscriminatorConverter; + /** * @author Steve Ebersole */ @@ -42,21 +41,14 @@ public class AnyMappingDomainTypeImpl implements AnyMappingDomainType { final BasicType discriminatorBaseType = (BasicType) discriminatorType.getBaseType(); final NavigableRole navigableRole = resolveNavigableRole( bootAnyMapping ); - anyDiscriminatorType = new ConvertedBasicTypeImpl<>( + anyDiscriminatorType = new ConvertedBasicTypeImpl( navigableRole.getFullPath(), discriminatorBaseType.getJdbcType(), - bootAnyMapping.getMetaValues().isEmpty() - ? DefaultDiscriminatorConverter.fromMappingMetamodel( + determineDiscriminatorConverter( navigableRole, - ClassJavaType.INSTANCE, - discriminatorBaseType, - mappingMetamodel - ) - : MappedDiscriminatorConverter.fromValueMappings( - navigableRole, - ClassJavaType.INSTANCE, discriminatorBaseType, bootAnyMapping.getMetaValues(), + discriminatorType.getValueStrategy(), mappingMetamodel ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java index 6249b66859..f0b785a03b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java @@ -195,7 +195,7 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // after *all* persisters and named queries are registered - MappingModelCreationProcess.process( entityPersisterMap, context ); + MappingModelCreationProcess.process( entityPersisterMap, collectionPersisterMap, context ); for ( EntityPersister persister : entityPersisterMap.values() ) { persister.postInstantiate(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index c9980b003d..0fdeed82a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -4,18 +4,8 @@ */ package org.hibernate.persister.collection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - +import jakarta.persistence.metamodel.PluralAttribute; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.AssertionFailure; import org.hibernate.Filter; import org.hibernate.HibernateException; @@ -60,7 +50,6 @@ import org.hibernate.loader.ast.internal.LoaderSqlAstCreationState; import org.hibernate.loader.ast.spi.BatchLoaderFactory; import org.hibernate.loader.ast.spi.CollectionLoader; import org.hibernate.mapping.Any; -import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; import org.hibernate.mapping.Formula; @@ -72,15 +61,21 @@ import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Table; import org.hibernate.mapping.Value; import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.DiscriminatorConverter; +import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedCollectionPart; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; +import org.hibernate.metamodel.mapping.internal.InFlightCollectionMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; @@ -129,15 +124,25 @@ import org.hibernate.sql.model.jdbc.JdbcMutationOperation; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.type.AnyType; -import org.hibernate.type.BasicType; import org.hibernate.type.CollectionType; import org.hibernate.type.ComponentType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; -import org.hibernate.type.MetaType; import org.hibernate.type.Type; -import org.checkerframework.checker.nullness.qual.Nullable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.function.Consumer; import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty; import static org.hibernate.internal.util.collections.CollectionHelper.arrayList; @@ -154,7 +159,7 @@ import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER */ @Internal public abstract class AbstractCollectionPersister - implements CollectionPersister, CollectionMutationTarget, PluralAttributeMappingImpl.Aware, FetchProfileAffectee, Joinable { + implements CollectionPersister, InFlightCollectionMapping, CollectionMutationTarget, PluralAttributeMappingImpl.Aware, FetchProfileAffectee, Joinable { private final NavigableRole navigableRole; private final CollectionSemantics collectionSemantics; @@ -164,13 +169,13 @@ public abstract class AbstractCollectionPersister protected final String qualifiedTableName; private final CollectionTableMapping tableMapping; - private final String sqlSelectSizeString; - private final String sqlDetectRowByIndexString; - private final String sqlDetectRowByElementString; + private String sqlSelectSizeString; + private String sqlDetectRowByIndexString; + private String sqlDetectRowByElementString; - protected final boolean hasWhere; - protected final String sqlWhereString; - private final String sqlWhereStringTemplate; + protected boolean hasWhere; + protected String sqlWhereString; + private String sqlWhereStringTemplate; private final boolean hasOrder; private final boolean hasManyToManyOrder; @@ -244,10 +249,14 @@ public abstract class AbstractCollectionPersister private PluralAttributeMapping attributeMapping; private volatile Set affectingFetchProfiles; + private Collection collectionBootDescriptor; + public AbstractCollectionPersister( Collection collectionBootDescriptor, @Nullable CollectionDataAccess cacheAccessStrategy, RuntimeModelCreationContext creationContext) throws MappingException, CacheException { + this.collectionBootDescriptor = collectionBootDescriptor; + this.factory = creationContext.getSessionFactory(); this.collectionSemantics = creationContext.getBootstrapContext() .getMetadataBuildingOptions() @@ -295,49 +304,6 @@ public abstract class AbstractCollectionPersister spaces[i] = tables.next(); } - String where = collectionBootDescriptor.getWhere(); - /* - * Add the predicate on the role in the WHERE clause before creating the SQL queries. - */ - if ( mappedByProperty != null && elementType instanceof EntityType ) { - final String entityName = ( (EntityType) elementType ).getAssociatedEntityName(); - final PersistentClass persistentClass = creationContext.getBootModel().getEntityBinding( entityName ); - final Property property = persistentClass.getRecursiveProperty( mappedByProperty ); - final Value propertyValue = property.getValue(); - if ( propertyValue instanceof Any ) { - final Any any = (Any) propertyValue; - final BasicValue discriminatorDescriptor = any.getDiscriminatorDescriptor(); - final AnyType anyType = any.getType(); - final MetaType metaType = (MetaType) anyType.getDiscriminatorType(); - final Object discriminatorValue = metaType.getEntityNameToDiscriminatorValueMap().get( ownerPersister.getEntityName() ); - final BasicType discriminatorBaseType = (BasicType) metaType.getBaseType(); - final String discriminatorLiteral = discriminatorBaseType.getJdbcLiteralFormatter().toJdbcLiteral( - discriminatorValue, - creationContext.getDialect(), - creationContext.getSessionFactory().getWrapperOptions() - ); - where = getNonEmptyOrConjunctionIfBothNonEmpty( - where, - discriminatorDescriptor.getColumn().getText() + "=" + discriminatorLiteral - ); - } - } - - if ( StringHelper.isNotEmpty( where ) ) { - hasWhere = true; - sqlWhereString = "(" + where + ")"; - sqlWhereStringTemplate = Template.renderWhereStringTemplate( - sqlWhereString, - dialect, - creationContext.getTypeConfiguration() - ); - } - else { - hasWhere = false; - sqlWhereString = null; - sqlWhereStringTemplate = null; - } - hasOrphanDelete = collectionBootDescriptor.hasOrphanDelete(); batchSize = collectionBootDescriptor.getBatchSize() < 0 @@ -512,12 +478,6 @@ public abstract class AbstractCollectionPersister identifierGenerator = null; } - // GENERATE THE SQL: - - sqlSelectSizeString = generateSelectSizeString( collectionBootDescriptor.isIndexed() && !collectionBootDescriptor.isMap() ); - sqlDetectRowByIndexString = generateDetectRowByIndexString(); - sqlDetectRowByElementString = generateDetectRowByElementString(); - isLazy = collectionBootDescriptor.isLazy(); isExtraLazy = collectionBootDescriptor.isExtraLazy(); @@ -592,6 +552,119 @@ public abstract class AbstractCollectionPersister && creationContext.getDialect().supportsCascadeDelete(); } + @Override + public void prepareMappingModel(MappingModelCreationProcess creationProcess) { + if ( mappedByProperty != null && elementType instanceof EntityType ) { + final String entityName = ((EntityType) elementType).getAssociatedEntityName(); + + final PersistentClass persistentClass = creationProcess.getCreationContext().getBootModel().getEntityBinding( entityName ); + final Property property = persistentClass.getRecursiveProperty( mappedByProperty ); + final Value propertyValue = property.getValue(); + + if ( propertyValue instanceof Any ) { + // we want to delay processing of where-fragment, and therefore affected SQL, until + // all model parts are ready so that we can access details about the ANY discriminator + creationProcess.registerInitializationCallback( + "Where-fragment handling for ANY inverse mapping : " + navigableRole, + () -> { + final EntityPersister entityPersister = creationProcess.getEntityPersister( entityName ); + delayedWhereFragmentProcessing( entityPersister, mappedByProperty, collectionBootDescriptor, creationProcess ); + buildStaticWhereFragmentSensitiveSql(); + collectionBootDescriptor = null; + return true; + } + ); + return; + } + } + + if ( StringHelper.isNotEmpty( collectionBootDescriptor.getWhere() ) ) { + hasWhere = true; + sqlWhereString = "(" + collectionBootDescriptor.getWhere() + ")"; + sqlWhereStringTemplate = Template.renderWhereStringTemplate( + sqlWhereString, + dialect, + creationProcess.getCreationContext().getTypeConfiguration() + ); + } + buildStaticWhereFragmentSensitiveSql(); + collectionBootDescriptor = null; + } + + private void delayedWhereFragmentProcessing( + EntityPersister entityPersister, + String mappedByProperty, + Collection collectionBootDescriptor, + MappingModelCreationProcess creationProcess) { + String where = collectionBootDescriptor.getWhere(); + + final AttributeMapping mappedByAttribute = resolveMappedBy( entityPersister, mappedByProperty, creationProcess ); + if ( mappedByAttribute instanceof DiscriminatedAssociationAttributeMapping ) { + final DiscriminatedAssociationAttributeMapping anyMapping = (DiscriminatedAssociationAttributeMapping) mappedByAttribute; + final DiscriminatorConverter valueConverter = anyMapping.getDiscriminatorMapping().getValueConverter(); + final DiscriminatorValueDetails discriminatorValueDetails = valueConverter.getDetailsForEntityName( ownerPersister.getEntityName() ); + //noinspection unchecked + final String discriminatorLiteral = anyMapping.getDiscriminatorMapping().getUnderlyingJdbcMapping().getJdbcLiteralFormatter().toJdbcLiteral( + discriminatorValueDetails.getValue(), + creationProcess.getCreationContext().getDialect(), + creationProcess.getCreationContext().getSessionFactory().getWrapperOptions() + ); + where = getNonEmptyOrConjunctionIfBothNonEmpty( + where, + anyMapping.getDiscriminatorMapping().getSelectableName() + "=" + discriminatorLiteral + ); + } + + if ( StringHelper.isNotEmpty( where ) ) { + hasWhere = true; + sqlWhereString = "(" + where + ")"; + sqlWhereStringTemplate = Template.renderWhereStringTemplate( + sqlWhereString, + dialect, + creationProcess.getCreationContext().getTypeConfiguration() + ); + } + } + + private void buildStaticWhereFragmentSensitiveSql() { + sqlSelectSizeString = generateSelectSizeString( hasIndex() && !isMap() ); + sqlDetectRowByIndexString = generateDetectRowByIndexString(); + sqlDetectRowByElementString = generateDetectRowByElementString(); + } + + private boolean isMap() { + return collectionSemantics.getCollectionClassification().toJpaClassification() == PluralAttribute.CollectionType.MAP; + } + + private static AttributeMapping resolveMappedBy(EntityPersister entityPersister, String mappedByProperty, MappingModelCreationProcess creationProcess) { + final StringTokenizer propertyPathParts = new StringTokenizer( mappedByProperty, ".", false ); + assert propertyPathParts.countTokens() > 0; + + if ( propertyPathParts.countTokens() == 1 ) { + return entityPersister.findAttributeMapping( propertyPathParts.nextToken() ); + } + + ManagedMappingType source = entityPersister; + while ( propertyPathParts.hasMoreTokens() ) { + final String partName = propertyPathParts.nextToken(); + final AttributeMapping namedPart = source.findAttributeMapping( partName ); + if ( !propertyPathParts.hasMoreTokens() ) { + return namedPart; + } + + source = (ManagedMappingType) namedPart.getPartMappingType(); + } + + throw new MappingException( + String.format( + Locale.ROOT, + "Unable to resolve mapped-by path : (%s) %s", + entityPersister.getEntityName(), + mappedByProperty + ) + ); + } + private BeforeExecutionGenerator createGenerator(RuntimeModelCreationContext context, IdentifierCollection collection) { final Generator generator = collection.getIdentifier() 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 145fec1c24..67070b1325 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 @@ -165,7 +165,6 @@ 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.MappedDiscriminatorConverter; import org.hibernate.metamodel.mapping.MappingModelHelper; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NaturalIdMapping; @@ -185,6 +184,7 @@ import org.hibernate.loader.ast.internal.EntityConcreteTypeLoader; import org.hibernate.metamodel.mapping.internal.EntityRowIdMappingImpl; import org.hibernate.metamodel.mapping.internal.EntityVersionMappingImpl; import org.hibernate.metamodel.mapping.internal.ExplicitColumnDiscriminatorMappingImpl; +import org.hibernate.metamodel.mapping.internal.ExplicitDiscriminatorConverter; import org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor; import org.hibernate.metamodel.mapping.internal.ImmutableAttributeMappingList; import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType; @@ -337,6 +337,7 @@ public abstract class AbstractEntityPersister private final EntityEntryFactory entityEntryFactory; private final String sqlAliasStem; + private final String jpaEntityName; private SingleIdEntityLoader singleIdLoader; private MultiIdEntityLoader multiIdLoader; @@ -473,6 +474,7 @@ public abstract class AbstractEntityPersister final EntityDataAccess cacheAccessStrategy, final NaturalIdDataAccess naturalIdRegionAccessStrategy, final RuntimeModelCreationContext creationContext) throws HibernateException { + this.jpaEntityName = persistentClass.getJpaEntityName(); //set it here, but don't call it, since it's still uninitialized! factory = creationContext.getSessionFactory(); @@ -2279,10 +2281,10 @@ public abstract class AbstractEntityPersister } //noinspection rawtypes - final DiscriminatorConverter converter = MappedDiscriminatorConverter.fromValueMappings( + final DiscriminatorConverter converter = new ExplicitDiscriminatorConverter( getNavigableRole().append( EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME ), domainJavaType, - underlingJdbcMapping, + underlingJdbcMapping.getRelationalJavaType(), getSubclassByDiscriminatorValue(), factory.getMappingMetamodel() ); @@ -3776,6 +3778,11 @@ public abstract class AbstractEntityPersister return entityMetamodel.getName(); } + @Override + public @Nullable String getJpaEntityName() { + return jpaEntityName; + } + @Override public boolean isInherited() { return entityMetamodel.isInherited(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index 8e3576039f..809d38e08f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; +import jakarta.persistence.Entity; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.HibernateException; import org.hibernate.Incubating; import org.hibernate.LockMode; @@ -37,6 +39,7 @@ import org.hibernate.generator.values.GeneratedValues; import org.hibernate.id.IdentifierGenerator; import org.hibernate.internal.FilterAliasGenerator; import org.hibernate.internal.TableGroupFilterAliasGenerator; +import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.ast.spi.MultiNaturalIdLoader; import org.hibernate.loader.ast.spi.NaturalIdLoader; @@ -178,6 +181,16 @@ public interface EntityPersister extends EntityMappingType, EntityMutationTarget */ String getEntityName(); + /** + * The {@linkplain Entity#name() JPA entity name}, if one, associated with the entity. + */ + @Nullable + String getJpaEntityName(); + + default String getImportedName() { + return getJpaEntityName() != null ? getJpaEntityName() : StringHelper.unqualifyEntityName( getEntityName() ); + } + /** * The strategy to use for SQM mutation statements where the target entity * has multiple tables. Returns {@code null} to indicate that the entity diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyDiscriminatorValueStrategy.java b/hibernate-core/src/main/java/org/hibernate/type/AnyDiscriminatorValueStrategy.java new file mode 100644 index 0000000000..1c2b982a0a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/AnyDiscriminatorValueStrategy.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type; + +import org.hibernate.annotations.AnyDiscriminatorValue; + +/** + * Describes how to deal with discriminator values in regard to + * a {@linkplain org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart ANY mapping} + * + * @see AnyDiscriminatorValue + * + * @author Steve Ebersole + */ +public enum AnyDiscriminatorValueStrategy { + /** + * Pick between {@link #IMPLICIT} and {@link #EXPLICIT} based on + * presence or not of {@link AnyDiscriminatorValue}. The default + * (and legacy) behavior. + */ + AUTO, + + /** + * Expect explicit, complete mapping of discriminator values using + * one-or-more {@link AnyDiscriminatorValue}. + * + * @implNote With this option, it is considered an error if, at runtime, + * we encounter an entity type not explicitly mapped with a + * {@link AnyDiscriminatorValue}. + */ + EXPLICIT, + + /** + * Expect no {@link AnyDiscriminatorValue}. The entity name of the associated + * entity is used as the discriminator value. + * + * @implNote With this option, it is considered illegal to specify + * any {@link AnyDiscriminatorValue} mappings. + */ + IMPLICIT, + + /** + * Allows a combination of {@linkplain #EXPLICIT explicit} and {@linkplain #IMPLICIT implicit} + * discriminator value mappings. If an entity is mapped using an explicit + * {@link AnyDiscriminatorValue} mapping, the associated discriminator value is used. + * Otherwise, the entity name is used. + * + * @implNote This option is roughly the same as {@link #EXPLICIT} except + * that an implicit mapping using the entity name is used when a matching + * explicit {@link AnyDiscriminatorValue} mapping is not found. + */ + MIXED +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java index 86c695d585..cee22ced08 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java @@ -43,16 +43,37 @@ import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; public class AnyType extends AbstractType implements CompositeType, AssociationType { private final TypeConfiguration typeConfiguration; private final Type identifierType; - private final Type discriminatorType; + private final MetaType discriminatorType; private final boolean eager; - public AnyType(TypeConfiguration typeConfiguration, Type discriminatorType, Type identifierType, boolean lazy) { + public AnyType(TypeConfiguration typeConfiguration, MetaType discriminatorType, Type identifierType, boolean lazy) { this.typeConfiguration = typeConfiguration; this.discriminatorType = discriminatorType; this.identifierType = identifierType; this.eager = !lazy; } + /** + * @deprecated Use {@linkplain AnyType#AnyType(TypeConfiguration, MetaType, Type, boolean)} instead + */ + @Deprecated + public AnyType(TypeConfiguration typeConfiguration, Type discriminatorType, Type identifierType, boolean lazy) { + this( + typeConfiguration, + wrapDiscriminatorType( discriminatorType ), + identifierType, + lazy + ); + } + + private static MetaType wrapDiscriminatorType(Type discriminatorType) { + if ( discriminatorType instanceof MetaType metaType ) { + return metaType; + } + + return new MetaType( discriminatorType, AnyDiscriminatorValueStrategy.AUTO, null ); + } + public Type getIdentifierType() { return identifierType; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/MetaType.java b/hibernate-core/src/main/java/org/hibernate/type/MetaType.java index c7d2ebfc19..45b0095a27 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/MetaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/MetaType.java @@ -4,47 +4,68 @@ */ package org.hibernate.type; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; - import org.hibernate.HibernateException; +import org.hibernate.Internal; import org.hibernate.MappingException; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; -import org.hibernate.metamodel.mapping.DiscriminatorConverter; -import org.hibernate.persister.entity.DiscriminatorMetadata; -import org.hibernate.persister.entity.DiscriminatorType; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; /** * @author Gavin King - * - * @deprecated The functionality of MetaType, {@link DiscriminatorType} and {@link DiscriminatorMetadata} have been - * consolidated into {@link EntityDiscriminatorMapping} and {@link DiscriminatorConverter} */ -@Deprecated( since = "6.2", forRemoval = true ) +@Internal public class MetaType extends AbstractType { public static final String[] REGISTRATION_KEYS = ArrayHelper.EMPTY_STRING_ARRAY; - private final Type baseType; + private final Type valueType; + private final AnyDiscriminatorValueStrategy valueStrategy; private final Map discriminatorValuesToEntityNameMap; private final Map entityNameToDiscriminatorValueMap; - public MetaType(Map discriminatorValuesToEntityNameMap, Type baseType) { - this.baseType = baseType; - this.discriminatorValuesToEntityNameMap = discriminatorValuesToEntityNameMap; - this.entityNameToDiscriminatorValueMap = new HashMap<>(); - for ( Map.Entry entry : discriminatorValuesToEntityNameMap.entrySet() ) { - entityNameToDiscriminatorValueMap.put( entry.getValue(), entry.getKey() ); + public MetaType( + Type valueType, + AnyDiscriminatorValueStrategy valueStrategy, + Map explicitValueMappings) { + this.valueType = valueType; + + if ( explicitValueMappings == null || explicitValueMappings.isEmpty() ) { + if ( valueStrategy == AnyDiscriminatorValueStrategy.AUTO ) { + valueStrategy = AnyDiscriminatorValueStrategy.IMPLICIT; + } + this.discriminatorValuesToEntityNameMap = new HashMap<>(); + this.entityNameToDiscriminatorValueMap = new HashMap<>(); } + else { + if ( valueStrategy == AnyDiscriminatorValueStrategy.AUTO ) { + valueStrategy = AnyDiscriminatorValueStrategy.EXPLICIT; + } + this.discriminatorValuesToEntityNameMap = explicitValueMappings; + this.entityNameToDiscriminatorValueMap = new HashMap<>(); + for ( Map.Entry entry : discriminatorValuesToEntityNameMap.entrySet() ) { + entityNameToDiscriminatorValueMap.put( entry.getValue(), entry.getKey() ); + } + } + + this.valueStrategy = valueStrategy; + } + + public MetaType(Map discriminatorValuesToEntityNameMap, Type baseType) { + this( baseType, AnyDiscriminatorValueStrategy.AUTO, discriminatorValuesToEntityNameMap ); } public Type getBaseType() { - return baseType; + return valueType; + } + + public AnyDiscriminatorValueStrategy getValueStrategy() { + return valueStrategy; } public String[] getRegistrationKeys() { @@ -60,12 +81,12 @@ public class MetaType extends AbstractType { } public int[] getSqlTypeCodes(MappingContext mappingContext) throws MappingException { - return baseType.getSqlTypeCodes( mappingContext ); + return valueType.getSqlTypeCodes( mappingContext ); } @Override public int getColumnSpan(MappingContext mapping) throws MappingException { - return baseType.getColumnSpan(mapping); + return valueType.getColumnSpan(mapping); } @Override @@ -84,7 +105,8 @@ public class MetaType extends AbstractType { Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { - baseType.nullSafeSet(st, value==null ? null : entityNameToDiscriminatorValueMap.get(value), index, session); + throw new UnsupportedOperationException(); +// baseType.nullSafeSet(st, value==null ? null : entityNameToDiscriminatorValueMap.get(value), index, session); } @Override @@ -122,7 +144,7 @@ public class MetaType extends AbstractType { @Override public String getName() { - return baseType.getName(); //TODO! + return valueType.getName(); //TODO! } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java index 3246d2b215..d61f830c6c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java @@ -13,6 +13,7 @@ import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.Filter; import org.hibernate.HibernateException; import org.hibernate.LockMode; @@ -142,6 +143,11 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver { return null; } + @Override + public @Nullable String getJpaEntityName() { + return null; + } + @Override public TableDetails getMappedTableDetails() { throw new UnsupportedOperationException(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java index 8ec636cef3..f57a7f2dde 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; @@ -172,6 +173,11 @@ public class PersisterClassProviderTest { return null; } + @Override + public @Nullable String getJpaEntityName() { + return null; + } + @Override public TableDetails getMappedTableDetails() { throw new UnsupportedOperationException(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java index 9309970bae..f783dca8c1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java @@ -134,6 +134,11 @@ public class CustomPersister implements EntityPersister { return Custom.class.getName(); } + @Override + public @Nullable String getJpaEntityName() { + return Custom.class.getSimpleName(); + } + @Override public TableDetails getMappedTableDetails() { throw new UnsupportedOperationException();