From 83ea2e8f42ff6e5e4c17c4b5f434a5f72dbb3ec3 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 4 Jul 2022 11:14:00 +0200 Subject: [PATCH] HHH-15323 @AnyDiscriminator Unable to filter a polymorphic relationship in a query --- .../internal/DiscriminatedCollectionPart.java | 9 + .../internal/AnyDiscriminatorSqmPath.java | 70 ++++++++ .../AnyDiscriminatorSqmPathSource.java | 61 +++++++ .../internal/AnyMappingDomainTypeImpl.java | 3 +- .../internal/AnyMappingSqmPathSource.java | 5 + .../hql/internal/SemanticQueryBuilder.java | 36 +++- .../query/sqm/SemanticQueryWalker.java | 6 + .../query/sqm/internal/SqmTreePrinter.java | 12 ++ .../sqm/spi/BaseSemanticQueryWalker.java | 12 ++ .../sqm/sql/BaseSqmToSqlAstConverter.java | 59 +++++++ ...atedAssociationTypePathInterpretation.java | 95 ++++++++++ .../expression/SqmAnyDiscriminatorValue.java | 92 ++++++++++ .../orm/test/any/annotations/AnyTest.java | 162 ++++++++++++++---- .../test/any/annotations/EmbeddedAnyTest.java | 26 +++ .../orm/test/any/annotations/Property.java | 6 +- .../test/any/annotations/PropertyHolder.java | 52 ++++++ 16 files changed, 661 insertions(+), 45 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPath.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPathSource.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/DiscriminatedAssociationTypePathInterpretation.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAnyDiscriminatorValue.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/PropertyHolder.java diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java index ebfdd067b7..fc4149e313 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java @@ -20,6 +20,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.spi.NavigablePath; @@ -98,6 +99,14 @@ public class DiscriminatedCollectionPart implements DiscriminatedAssociationMode return discriminatorMapping.resolveDiscriminatorValueToEntityMapping( entityMappingType ); } + @Override + public int forEachSelectable(int offset, SelectableConsumer consumer) { + discriminatorMapping.getDiscriminatorPart().forEachSelectable( offset, consumer ); + discriminatorMapping.getKeyPart().forEachSelectable( offset + 1, consumer ); + + return 2; + } + @Override public String getFetchableName() { return nature.getName(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPath.java new file mode 100644 index 0000000000..40d675cbcb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPath.java @@ -0,0 +1,70 @@ +/* + * 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.UnsupportedMappingException; +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.query.PathException; +import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; +import org.hibernate.query.sqm.tree.domain.AbstractSqmPath; +import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; +import org.hibernate.spi.NavigablePath; + +public class AnyDiscriminatorSqmPath extends AbstractSqmPath { + + protected AnyDiscriminatorSqmPath( + NavigablePath navigablePath, + SqmPathSource referencedPathSource, + SqmPath lhs, + NodeBuilder nodeBuilder) { + super( navigablePath, referencedPathSource, lhs, nodeBuilder ); + } + + @Override + public AnyDiscriminatorSqmPath copy(SqmCopyContext context) { + final AnyDiscriminatorSqmPath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + return context.registerCopy( + this, + (AnyDiscriminatorSqmPath) getLhs().copy( context ).type() + ); + } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitAnyDiscriminatorTypeExpression( this ) ; + } + + @Override + public void appendHqlString(StringBuilder sb) { + sb.append( "type(" ); + getLhs().appendHqlString( sb ); + sb.append( ')' ); + } + + @Override + public SqmPath resolvePathPart(String name, boolean isTerminal, SqmCreationState creationState) { + throw new IllegalStateException( "Discriminator cannot be de-referenced" ); + } + + @Override + public SqmTreatedPath treatAs(Class treatJavaType) throws PathException { + throw new UnsupportedMappingException( "Cannot apply TREAT operator to discriminator path" ); + } + + @Override + public SqmTreatedPath treatAs(EntityDomainType treatTarget) throws PathException { + throw new UnsupportedMappingException( "Cannot apply TREAT operator to discriminator path" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPathSource.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPathSource.java new file mode 100644 index 0000000000..81042231e2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPathSource.java @@ -0,0 +1,61 @@ +/* + * 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.AnyMappingDomainType; +import org.hibernate.metamodel.model.domain.DomainType; +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.metamodel.model.domain.SimpleDomainType; +import org.hibernate.query.BindableType; +import org.hibernate.query.ReturnableType; +import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.spi.NavigablePath; + +/** + * SqmPathSource implementation for {@link org.hibernate.annotations.AnyDiscriminator} + * + */ +public class AnyDiscriminatorSqmPathSource extends AbstractSqmPathSource + implements BindableType, ReturnableType { + + + public AnyDiscriminatorSqmPathSource( + String localPathName, + SimpleDomainType domainType, + BindableType jpaBindableType) { + super( localPathName, domainType, jpaBindableType ); + } + + @Override + public SqmPath createSqmPath(SqmPath lhs, SqmPathSource intermediatePathSource) { + final NavigablePath navigablePath; + if ( intermediatePathSource == null ) { + navigablePath = lhs.getNavigablePath(); + } + else { + navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ); + } + return new AnyDiscriminatorSqmPath( navigablePath, this, lhs, lhs.nodeBuilder() ); + } + + @Override + public SqmPathSource findSubPathSource(String name) { + throw new IllegalStateException( "Entity discriminator cannot be de-referenced" ); + } + + @Override + public PersistenceType getPersistenceType() { + return PersistenceType.BASIC; + } + + @Override + public Class getJavaType() { + return getExpressibleJavaType().getJavaTypeClass(); + } + +} 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 59810744ff..5852f9f588 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 @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.AnyMappingDomainType; import org.hibernate.metamodel.model.domain.SimpleDomainType; import org.hibernate.type.AnyType; import org.hibernate.type.BasicType; +import org.hibernate.type.MetaType; import org.hibernate.type.descriptor.java.JavaType; /** @@ -41,7 +42,7 @@ public class AnyMappingDomainTypeImpl implements AnyMappingDomainType { @Override public SimpleDomainType getDiscriminatorType() { - return (BasicType) anyType.getDiscriminatorType(); + return (SimpleDomainType) ((MetaType) anyType.getDiscriminatorType()).getBaseType(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingSqmPathSource.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingSqmPathSource.java index 6291fe1654..8dea6b75f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingSqmPathSource.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingSqmPathSource.java @@ -22,6 +22,7 @@ import static jakarta.persistence.metamodel.Bindable.BindableType.SINGULAR_ATTRI */ public class AnyMappingSqmPathSource extends AbstractSqmPathSource implements BindableType { private final SqmPathSource keyPathSource; + private final AnyDiscriminatorSqmPathSource discriminatorPathSource; public AnyMappingSqmPathSource( String localPathName, @@ -29,6 +30,7 @@ public class AnyMappingSqmPathSource extends AbstractSqmPathSource impleme BindableType jpaBindableType) { super( localPathName, domainType, jpaBindableType ); keyPathSource = new BasicSqmPathSource<>( "id", (BasicDomainType) domainType.getKeyType(), SINGULAR_ATTRIBUTE ); + discriminatorPathSource = new AnyDiscriminatorSqmPathSource<>( localPathName, domainType.getDiscriminatorType(), jpaBindableType ); } @Override @SuppressWarnings("unchecked") @@ -41,6 +43,9 @@ public class AnyMappingSqmPathSource extends AbstractSqmPathSource impleme if ( "id".equals( name ) ) { return keyPathSource; } + else if("{discriminator}".equals( name )) { + return discriminatorPathSource; + } throw new UnsupportedMappingException( "De-referencing parts of an ANY mapping, other than the key, is not supported" ); } 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 d2097ad825..c7aa0d65ec 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 @@ -56,6 +56,7 @@ import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.metamodel.model.domain.PersistentAttribute; import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; +import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPath; import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource; import org.hibernate.query.PathException; import org.hibernate.query.ReturnableType; @@ -118,6 +119,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmAny; +import org.hibernate.query.sqm.tree.expression.SqmAnyDiscriminatorValue; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -133,6 +135,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; import org.hibernate.query.sqm.tree.expression.SqmFormat; import org.hibernate.query.sqm.tree.expression.SqmFunction; import org.hibernate.query.sqm.tree.expression.SqmLiteral; +import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType; import org.hibernate.query.sqm.tree.expression.SqmLiteralNull; import org.hibernate.query.sqm.tree.expression.SqmNamedParameter; import org.hibernate.query.sqm.tree.expression.SqmOver; @@ -2101,8 +2104,21 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); break; } - left = (SqmExpression) leftExpressionContext.accept( this ); - right = (SqmExpression) rightExpressionContext.accept( this ); + final SqmExpression l = (SqmExpression) leftExpressionContext.accept( this ); + final SqmExpression r = (SqmExpression) rightExpressionContext.accept( this ); + if ( l instanceof AnyDiscriminatorSqmPath && r instanceof SqmLiteralEntityType ) { + left = l; + right = createDiscriminatorValue( (AnyDiscriminatorSqmPath) left, rightExpressionContext ); + } + else if ( r instanceof AnyDiscriminatorSqmPath && l instanceof SqmLiteralEntityType ) { + left = createDiscriminatorValue( (AnyDiscriminatorSqmPath) r, leftExpressionContext ); + right = r; + } + else { + left = l; + right = r; + } + // This is something that we used to support before 6 which is also used in our testsuite if ( left instanceof SqmLiteralNull ) { return new SqmNullnessPredicate( @@ -2136,6 +2152,20 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } + private SqmExpression createDiscriminatorValue(AnyDiscriminatorSqmPath anyDiscriminatorTypeSqmPath, HqlParser.ExpressionContext valueExpressionContext) { + final SqmPath discriminatorSqmPath = anyDiscriminatorTypeSqmPath.getLhs(); + final EntityDomainType entityWithDiscriminator = creationContext.getJpaMetamodel() + .entity( discriminatorSqmPath.findRoot().getNavigablePath().getLocalName() ); + final EntityDomainType entityDiscriminatorValue = creationContext.getJpaMetamodel() + .resolveHqlEntityReference( valueExpressionContext.getText() ); + return new SqmAnyDiscriminatorValue<>( + entityWithDiscriminator, + discriminatorSqmPath.getNodeType().getPathName(), + entityDiscriminatorValue, + creationContext.getNodeBuilder() + ); + } + private SqmExpression resolveEnumShorthandLiteral(HqlParser.ExpressionContext expressionContext, Map, Enum> possibleEnumValues, Class enumType) { final Enum enumValue; if ( possibleEnumValues != null && ( enumValue = possibleEnumValues.get( enumType ) ) != null ) { @@ -3335,7 +3365,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem //noinspection unchecked return new SqmLiteral<>( value, - (SqmExpressible) type, + type, creationContext.getNodeBuilder() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java index 27c0ee9b7e..05e7b43d21 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm; import java.util.List; +import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPath; import org.hibernate.query.sqm.sql.internal.SelfInterpretingSqmPath; import org.hibernate.query.sqm.tree.cte.SqmCteContainer; import org.hibernate.query.sqm.tree.cte.SqmCteStatement; @@ -29,6 +30,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAny; +import org.hibernate.query.sqm.tree.expression.SqmAnyDiscriminatorValue; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -227,6 +229,10 @@ public interface SemanticQueryWalker { T visitEntityTypeLiteralExpression(SqmLiteralEntityType expression); + T visitAnyDiscriminatorTypeExpression(AnyDiscriminatorSqmPath expression); + + T visitAnyDiscriminatorTypeValueExpression(SqmAnyDiscriminatorValue expression); + T visitParameterizedEntityTypeExpression(SqmParameterizedEntityType expression); T visitUnaryOperationExpression(SqmUnaryOperation expression); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java index 503da82338..a646082b32 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.internal; import java.util.List; import java.util.Locale; +import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPath; import org.hibernate.query.QueryLogging; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.sql.internal.SelfInterpretingSqmPath; @@ -33,6 +34,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAny; +import org.hibernate.query.sqm.tree.expression.SqmAnyDiscriminatorValue; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -1044,6 +1046,16 @@ public class SqmTreePrinter implements SemanticQueryWalker { return null; } + @Override + public Object visitAnyDiscriminatorTypeExpression(AnyDiscriminatorSqmPath expression) { + return null; + } + + @Override + public Object visitAnyDiscriminatorTypeValueExpression(SqmAnyDiscriminatorValue expression) { + return null; + } + @Override public Object visitDynamicInstantiation(SqmDynamicInstantiation sqmDynamicInstantiation) { processStanza( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java index 1f04097d42..31d01d288a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.spi; import java.util.List; +import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPath; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.sql.internal.SelfInterpretingSqmPath; import org.hibernate.query.sqm.tree.SqmStatement; @@ -34,6 +35,7 @@ import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAggregateFunction; import org.hibernate.query.sqm.tree.expression.SqmAny; +import org.hibernate.query.sqm.tree.expression.SqmAnyDiscriminatorValue; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -578,6 +580,16 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker expression) { expression.getDiscriminatorSource().accept( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 929dbdc074..0d3edc093d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -63,6 +63,7 @@ import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.Bindable; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.ConvertibleModelPart; +import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityAssociationMapping; @@ -83,6 +84,7 @@ import org.hibernate.metamodel.mapping.SelectableMappings; import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.metamodel.mapping.SqlTypedMapping; import org.hibernate.metamodel.mapping.ValueMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.metamodel.mapping.internal.SqlTypedMappingImpl; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; @@ -96,6 +98,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; +import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPath; import org.hibernate.query.derived.AnonymousTupleTableGroupProducer; import org.hibernate.query.derived.AnonymousTupleType; import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource; @@ -138,6 +141,7 @@ import org.hibernate.query.sqm.produce.function.internal.PatternRenderer; import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker; import org.hibernate.query.sqm.sql.internal.BasicValuedPathInterpretation; import org.hibernate.query.sqm.sql.internal.DiscriminatedAssociationPathInterpretation; +import org.hibernate.query.sqm.sql.internal.DiscriminatedAssociationTypePathInterpretation; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.query.sqm.sql.internal.EmbeddableValuedPathInterpretation; import org.hibernate.query.sqm.sql.internal.EntityValuedPathInterpretation; @@ -182,6 +186,7 @@ import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmAny; +import org.hibernate.query.sqm.tree.expression.SqmAnyDiscriminatorValue; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmByUnit; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; @@ -6227,6 +6232,60 @@ public abstract class BaseSqmToSqlAstConverter extends Base return new EntityTypeLiteral( mappingDescriptor ); } + @Override + public Expression visitAnyDiscriminatorTypeExpression(AnyDiscriminatorSqmPath expression) { + return DiscriminatedAssociationTypePathInterpretation.from( expression, this ); + } + + @Override + public Expression visitAnyDiscriminatorTypeValueExpression(SqmAnyDiscriminatorValue expression) { + final EntityPersister mappingDescriptor = creationContext.getSessionFactory() + .getRuntimeMetamodels() + .getMappingMetamodel() + .getEntityDescriptor( ((EntityDomainType)expression.getNodeType()).getHibernateEntityName() ); + for ( AttributeMapping attributeMapping : mappingDescriptor.getAttributeMappings() ) { + if ( attributeMapping instanceof EmbeddedAttributeMapping ) { + final ModelPart subPart = ( (EmbeddedAttributeMapping) attributeMapping ).findSubPart( + expression.getPathName(), + null + ); + if ( subPart != null ) { + return buildQueryLiteral( expression, subPart ); + } + } + else if ( attributeMapping.getAttributeName().equals( expression.getPathName() ) ) { + return buildQueryLiteral( expression, attributeMapping ); + } + } + throw new HibernateException(String.format( + Locale.ROOT, + "Could not locate attribute mapping : %s -> %s", + mappingDescriptor.getEntityName(), + expression.getPathName() + )); + } + + private QueryLiteral buildQueryLiteral( + SqmAnyDiscriminatorValue expression, + ModelPart attributeMapping) { + final DiscriminatedAssociationModelPart discriminatedAssociationModelPart; + if ( attributeMapping instanceof PluralAttributeMapping ) { + discriminatedAssociationModelPart = (DiscriminatedAssociationModelPart) ( (PluralAttributeMapping) attributeMapping ).getElementDescriptor(); + } + else { + assert attributeMapping instanceof DiscriminatedAssociationModelPart; + discriminatedAssociationModelPart = (DiscriminatedAssociationModelPart) attributeMapping; + } + final Object value = discriminatedAssociationModelPart.resolveDiscriminatorForEntityType( + creationContext.getMappingMetamodel() + .findEntityDescriptor( expression.getEntityValue().getHibernateEntityName() ) ); + + return new QueryLiteral<>( + value, + discriminatedAssociationModelPart.getDiscriminatorPart() + ); + } + @Override public Expression visitParameterizedEntityTypeExpression(SqmParameterizedEntityType sqmExpression) { assert resolveInferredType() instanceof EntityDiscriminatorMapping; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/DiscriminatedAssociationTypePathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/DiscriminatedAssociationTypePathInterpretation.java new file mode 100644 index 0000000000..efc85abb48 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/DiscriminatedAssociationTypePathInterpretation.java @@ -0,0 +1,95 @@ +/* + * 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.query.sqm.sql.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.AnyDiscriminatorPart; +import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPath; +import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; +import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableReference; + +public class DiscriminatedAssociationTypePathInterpretation extends AbstractSqmPathInterpretation { + + private final Expression expression; + + public static DiscriminatedAssociationTypePathInterpretation from( + AnyDiscriminatorSqmPath sqmPath, + SqmToSqlAstConverter converter) { + final SqmPath lhs = sqmPath.getLhs(); + final TableGroup tableGroup = converter.getFromClauseAccess().findTableGroup( lhs.getLhs().getNavigablePath() ); + final ModelPart subPart = tableGroup.getModelPart().findSubPart( + lhs.getNavigablePath().getLocalName(), + null + ); + + final DiscriminatedAssociationModelPart mapping; + if ( subPart instanceof PluralAttributeMapping ) { + PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) subPart; + mapping = (DiscriminatedAssociationModelPart) pluralAttributeMapping.getElementDescriptor(); + } + else { + mapping = (DiscriminatedAssociationModelPart) subPart; + } + + final List tupleExpressions = new ArrayList<>(); + + mapping.forEachSelectable( + (selectionIndex, selectableMapping) -> { + if ( selectableMapping instanceof AnyDiscriminatorPart ) { + final TableReference tableReference = tableGroup.getPrimaryTableReference(); + final Expression expression = converter.getSqlExpressionResolver().resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( + tableReference, + selectableMapping.getSelectionExpression() + ), + processingState -> new ColumnReference( + tableReference, + selectableMapping, + converter.getCreationContext().getSessionFactory() + ) + ); + tupleExpressions.add( expression ); + } + } + ); + + assert tupleExpressions.size() == 1; + + return new DiscriminatedAssociationTypePathInterpretation( + sqmPath.getNavigablePath(), + mapping, + tableGroup, + tupleExpressions.get( 0 ) + ); + } + + public DiscriminatedAssociationTypePathInterpretation( + NavigablePath navigablePath, + ModelPart mapping, + TableGroup tableGroup, + Expression expression) { + super( navigablePath, mapping, tableGroup ); + this.expression = expression; + } + + @Override + public void accept(SqlAstWalker sqlTreeWalker) { + expression.accept( sqlTreeWalker ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAnyDiscriminatorValue.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAnyDiscriminatorValue.java new file mode 100644 index 0000000000..893f0ea602 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAnyDiscriminatorValue.java @@ -0,0 +1,92 @@ +/* + * 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.query.sqm.tree.expression; + +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.query.hql.HqlInterpretationException; +import org.hibernate.query.hql.spi.SemanticPathPart; +import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; +import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.select.SqmSelectableNode; + +public class SqmAnyDiscriminatorValue extends AbstractSqmExpression + implements SqmSelectableNode, SemanticPathPart { + + private final EntityDomainType value; + private final String pathName; + + public SqmAnyDiscriminatorValue( + EntityDomainType entityWithDiscriminator, + String pathName, + EntityDomainType entityValue, + NodeBuilder nodeBuilder) { + super( entityWithDiscriminator, nodeBuilder ); + this.value = entityValue; + this.pathName = pathName; + } + + @Override + public SqmAnyDiscriminatorValue copy(SqmCopyContext context) { + final SqmAnyDiscriminatorValue existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmAnyDiscriminatorValue expression = context.registerCopy( + this, + new SqmAnyDiscriminatorValue<>( + (EntityDomainType) getNodeType(), + pathName, + value, + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitAnyDiscriminatorTypeValueExpression( this ); + } + + public EntityDomainType getEntityValue() { + return value; + } + + public String getPathName() { + return pathName; + } + + @Override + public String asLoggableText() { + return getEntityValue().getName(); + } + + @Override + public SemanticPathPart resolvePathPart( + String name, + boolean isTerminal, + SqmCreationState creationState) { + throw new HqlInterpretationException( "Cannot dereference an entity name" ); + } + + @Override + public SqmPath resolveIndexedAccess( + SqmExpression selector, + boolean isTerminal, + SqmCreationState creationState) { + throw new HqlInterpretationException( "Cannot dereference an entity name" ); + } + + @Override + public void appendHqlString(StringBuilder sb) { + sb.append( getEntityValue().getName() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/AnyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/AnyTest.java index 6ee18abcd7..3f34f50707 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/AnyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/AnyTest.java @@ -6,9 +6,12 @@ */ package org.hibernate.orm.test.any.annotations; +import java.util.List; + import org.hibernate.LazyInitializationException; import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -16,6 +19,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -31,6 +35,7 @@ import static org.junit.Assert.fail; LazyPropertySet.class, PropertyMap.class, PropertyList.class, + PropertyHolder.class, CharProperty.class } ) @@ -41,48 +46,68 @@ public class AnyTest { scope.inTransaction( session -> { { - final PropertySet set1 = new PropertySet("string"); - final Property property = new StringProperty("name", "Alex"); - set1.setSomeProperty(property); - set1.addGeneralProperty(property); - session.save(set1); + final PropertySet set1 = new PropertySet( "string" ); + final Property property = new StringProperty( "name", "Alex" ); + set1.setSomeProperty( property ); + set1.addGeneralProperty( property ); + session.persist( set1 ); - final PropertySet set2 = new PropertySet("integer"); - final Property property2 = new IntegerProperty("age", 33); - set2.setSomeProperty(property2); - set2.addGeneralProperty(property2); - session.save(set2); + final PropertySet set2 = new PropertySet( "integer" ); + final Property property2 = new IntegerProperty( "age", 33 ); + set2.setSomeProperty( property2 ); + set2.addGeneralProperty( property2 ); + session.persist( set2 ); } { - final PropertyMap map = new PropertyMap("sample"); - map.getProperties().put("name", new StringProperty("name", "Alex")); - map.getProperties().put("age", new IntegerProperty("age", 33)); - session.save(map); + final PropertyMap map = new PropertyMap( "sample" ); + map.getProperties().put( "name", new StringProperty( "name", "Alex" ) ); + map.getProperties().put( "age", new IntegerProperty( "age", 33 ) ); + session.persist( map ); } { - final PropertyList list = new PropertyList("sample"); - final StringProperty stringProperty = new StringProperty("name", "Alex"); - final IntegerProperty integerProperty = new IntegerProperty("age", 33); - final LongProperty longProperty = new LongProperty("distance", 121L); - final CharProperty charProp = new CharProperty("Est", 'E'); + StringProperty nameProperty = new StringProperty( "name", "John Doe" ); + session.persist( nameProperty ); - list.setSomeProperty(longProperty); + PropertyHolder namePropertyHolder = new PropertyHolder(); + namePropertyHolder.setId( 1 ); + namePropertyHolder.setProperty( nameProperty ); - list.addGeneralProperty(stringProperty); - list.addGeneralProperty(integerProperty); - list.addGeneralProperty(longProperty); - list.addGeneralProperty(charProp); + session.persist( namePropertyHolder ); - session.save(list); + final IntegerProperty ageProperty = new IntegerProperty( "age", 23 ); + session.persist( ageProperty ); + + PropertyHolder agePropertyHolder = new PropertyHolder(); + agePropertyHolder.setId( 2 ); + agePropertyHolder.setProperty( ageProperty ); + + session.persist( agePropertyHolder ); + } + + { + final PropertyList list = new PropertyList( "sample" ); + final StringProperty stringProperty = new StringProperty( "name", "Alex" ); + final IntegerProperty integerProperty = new IntegerProperty( "age", 33 ); + final LongProperty longProperty = new LongProperty( "distance", 121L ); + final CharProperty charProp = new CharProperty( "Est", 'E' ); + + list.setSomeProperty( longProperty ); + + list.addGeneralProperty( stringProperty ); + list.addGeneralProperty( integerProperty ); + list.addGeneralProperty( longProperty ); + list.addGeneralProperty( charProp ); + + session.persist( list ); } { final LazyPropertySet set = new LazyPropertySet( "string" ); final Property property = new StringProperty( "name", "Alex" ); set.setSomeProperty( property ); - session.save( set ); + session.persist( set ); } } ); @@ -92,14 +117,66 @@ public class AnyTest { public void dropTestData(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery( "delete StringProperty" ).executeUpdate(); - session.createQuery( "delete IntegerProperty" ).executeUpdate(); - session.createQuery( "delete LongProperty" ).executeUpdate(); - session.createQuery( "delete CharProperty" ).executeUpdate(); + session.createMutationQuery( "delete StringProperty" ).executeUpdate(); + session.createMutationQuery( "delete IntegerProperty" ).executeUpdate(); + session.createMutationQuery( "delete LongProperty" ).executeUpdate(); + session.createMutationQuery( "delete CharProperty" ).executeUpdate(); - session.createQuery( "delete PropertyList" ).executeUpdate(); - session.createQuery( "delete PropertyMap" ).executeUpdate(); - session.createQuery( "delete PropertySet" ).executeUpdate(); + session.createMutationQuery( "delete PropertyHolder" ).executeUpdate(); + session.createMutationQuery( "delete PropertyList" ).executeUpdate(); + session.createMutationQuery( "delete PropertyMap" ).executeUpdate(); + session.createMutationQuery( "delete PropertySet" ).executeUpdate(); + session.createMutationQuery( "delete LazyPropertySet" ).executeUpdate(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-15323") + public void testHqlCollectionTypeQuery(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List propertySets = session.createQuery( + "select p from PropertySet p where type(p.generalProperties) = IntegerProperty ", + PropertySet.class ).list(); + assertEquals( 1, propertySets.size() ); + + PropertySet propertySet = propertySets.get( 0 ); + assertEquals( 1, propertySet.getGeneralProperties().size() ); + + assertEquals( "age", propertySet.getGeneralProperties().get( 0 ).getName() ); + + propertySets = session.createQuery( + "select p from PropertySet p where type(p.generalProperties) = StringProperty ", + PropertySet.class ).list(); + assertEquals( 1, propertySets.size() ); + + propertySet = propertySets.get( 0 ); + assertEquals( 1, propertySet.getGeneralProperties().size() ); + + assertEquals( "name", propertySet.getGeneralProperties().get( 0 ).getName() ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-15323") + public void testHqlTypeQuery(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List propertyHolders = session.createQuery( + "select p from PropertyHolder p where type(p.property) = IntegerProperty ", + PropertyHolder.class ).list(); + assertEquals( 1, propertyHolders.size() ); + + assertEquals( "age", propertyHolders.get( 0 ).getProperty().getName() ); + + propertyHolders = session.createQuery( + "select p from PropertyHolder p where type(p.property) = StringProperty ", + PropertyHolder.class ).list(); + assertEquals( 1, propertyHolders.size() ); + + assertEquals( "name", propertyHolders.get( 0 ).getProperty().getName() ); } ); } @@ -108,7 +185,10 @@ public class AnyTest { public void testDefaultAnyAssociation(SessionFactoryScope scope) { scope.inTransaction( session -> { - final QueryImplementor query = session.createQuery("select s from PropertySet s where name = :name", PropertySet.class); + final QueryImplementor query = session.createQuery( + "select s from PropertySet s where name = :name", + PropertySet.class + ); { final PropertySet result = query.setParameter( "name", "string" ).uniqueResult(); @@ -192,8 +272,11 @@ public class AnyTest { public void testFetchEager(SessionFactoryScope scope) { final PropertySet result = scope.fromTransaction( session -> { - final PropertySet localResult = session.createQuery("select s from PropertySet s where name = :name", PropertySet.class) - .setParameter("name", "string") + final PropertySet localResult = session.createQuery( + "select s from PropertySet s where name = :name", + PropertySet.class + ) + .setParameter( "name", "string" ) .getSingleResult(); assertNotNull( localResult ); assertNotNull( localResult.getSomeProperty() ); @@ -210,8 +293,11 @@ public class AnyTest { public void testFetchLazy(SessionFactoryScope scope) { final LazyPropertySet result = scope.fromTransaction( session -> { - final LazyPropertySet localResult = session.createQuery("select s from LazyPropertySet s where name = :name", LazyPropertySet.class) - .setParameter("name", "string") + final LazyPropertySet localResult = session.createQuery( + "select s from LazyPropertySet s where name = :name", + LazyPropertySet.class + ) + .setParameter( "name", "string" ) .getSingleResult(); assertNotNull( localResult ); assertNotNull( localResult.getSomeProperty() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/EmbeddedAnyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/EmbeddedAnyTest.java index 3b2f50ae79..4061dbe0e2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/EmbeddedAnyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/EmbeddedAnyTest.java @@ -6,11 +6,14 @@ */ package org.hibernate.orm.test.any.annotations; +import java.util.List; + import org.hibernate.annotations.Any; import org.hibernate.annotations.AnyDiscriminator; import org.hibernate.annotations.AnyDiscriminatorValue; import org.hibernate.annotations.AnyKeyJavaClass; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -33,6 +36,7 @@ import static org.junit.Assert.assertTrue; @DomainModel( annotatedClasses = { EmbeddedAnyTest.Foo.class, EmbeddedAnyTest.Bar1.class, EmbeddedAnyTest.Bar2.class } ) @SessionFactory public class EmbeddedAnyTest { + @BeforeEach public void createTestData(SessionFactoryScope scope) { scope.inTransaction( @@ -78,6 +82,28 @@ public class EmbeddedAnyTest { ); } + @Test + @TestForIssue( jiraKey = "HHH-15323") + public void testEmbeddedTypeHql(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final List foos = session.createQuery( "from Foo f where type(f.fooEmbedded.bar) = Bar1", Foo.class ) + .list(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-15323") + public void testEmbeddedTypeHql2(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final List foos = session.createQuery( "from Foo f where Bar1 = type(f.fooEmbedded.bar)", Foo.class ) + .list(); + } + ); + } + @Entity(name = "Foo") public static class Foo { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/Property.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/Property.java index 2e1c42523b..6881d270f9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/Property.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/Property.java @@ -6,9 +6,9 @@ */ package org.hibernate.orm.test.any.annotations; - public interface Property { - public String getName(); - public String asString(); + String getName(); + + String asString(); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/PropertyHolder.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/PropertyHolder.java new file mode 100644 index 0000000000..c9895adb95 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/annotations/PropertyHolder.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.orm.test.any.annotations; + +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyDiscriminator; +import org.hibernate.annotations.AnyDiscriminatorValue; +import org.hibernate.annotations.AnyKeyJavaClass; + +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Table; + +@Entity +@Table(name = "property_holder") +public class PropertyHolder { + + @Id + private Integer id; + + @Any + @AnyDiscriminator(DiscriminatorType.STRING) + @AnyDiscriminatorValue(discriminator = "S", entity = StringProperty.class) + @AnyDiscriminatorValue(discriminator = "I", entity = IntegerProperty.class) + @AnyKeyJavaClass(Integer.class) + @Column(name = "property_type") + @JoinColumn(name = "property_id") + private Property property; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Property getProperty() { + return property; + } + + public void setProperty(Property property) { + this.property = property; + } +}