From 31e2423d8ac02852c00cbacf47fbcb2e0cb3a1eb Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 18 Oct 2019 12:46:22 -0500 Subject: [PATCH] initial work on support for discrim-inheritance; started design doc about SQM model, building and translation; initial work on `#load` support (strange error in BasicFormatterImpl as part of SqlStatementLogger) --- design/sqm.adoc | 29 +++ .../jdbc/internal/BasicFormatterImpl.java | 2 + .../SingleIdEntityLoaderStandardImpl.java | 18 +- .../org/hibernate/loader/spi/Loadable.java | 5 + .../mapping/EntityDiscriminatorMapping.java | 14 ++ .../metamodel/mapping/EntityMappingType.java | 4 + .../metamodel/mapping/VirtualModelPart.java | 16 ++ .../BasicValuedSingularAttributeMapping.java | 4 +- .../internal/EmbeddedAttributeMapping.java | 2 +- .../EntityDiscriminatorMappingImpl.java | 144 +++++++++++ .../internal/MappingModelCreationHelper.java | 4 +- .../entity/AbstractEntityPersister.java | 50 +++- .../entity/SingleTableEntityPersister.java | 72 ++++++ .../internal/PersisterFactoryImpl.java | 14 +- .../sqm/sql/BaseSqmToSqlAstConverter.java | 12 + .../BasicValuedPathInterpretation.java | 2 +- .../ast/tree/expression/ColumnReference.java | 2 +- .../ast/tree/from/RootTableGroupProducer.java | 5 + .../metamodel/mapping/InheritanceTests.java | 238 ++++++++++++++++++ .../src/test/resources/hibernate.properties | 2 - 20 files changed, 607 insertions(+), 32 deletions(-) create mode 100644 design/sqm.adoc create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityDiscriminatorMapping.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/VirtualModelPart.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityDiscriminatorMappingImpl.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/InheritanceTests.java diff --git a/design/sqm.adoc b/design/sqm.adoc new file mode 100644 index 0000000000..273fcb2028 --- /dev/null +++ b/design/sqm.adoc @@ -0,0 +1,29 @@ +== SQM + +The Semantic Query Model (SQM) is Hibernate's representation of an HQL or Criteria query's semantic (meaning). This +representation is modeled as an "abstract syntax tree" (AST) - meaning it is a structured tree of nodes where each node +represrents an atomic piece of the query. E.g. `SqmSelectClause` represents the query's select clause as you might +imagine. That `SqmSelectClause` is ultimately a collection of one or more `SqmSelection` references representing the +individual selections to be returned from the query (called the domain results). + +=== The Model + +This SQM model uses the Hibernate domain model, which is Hibernate's extension to the JPA model. This model contains no +relational mapping information, it simply describes the domain model in mostly Java terms although it does incorporate +"classifications" of the type system. E.g. it understand that `Customer` is an entity, but contains no information +about the tables it maps to nor its columns. + +See the `design/type-system-domain.adoc` design doc. For details about this domain model + + +=== Building an SQM + + +=== Interpreting an SQM + +Ultimately Hibernate needs to talk with the database to fulfill these query executions. This is currently a 2-step process. + +First we convert the SQM into a new AST called the SQL AST. This is an AST that is more "SQL-y". It's nodes are defined +in terms of Hibernate's mapping model which is the model that actually does understand the relational mapping. +See `design/type-system-mapping.adoc` for details about this model. Part of this conversion step is to resolving +domain model references to mapping model references... diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java index c4414a4e03..a6ec425edb 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java @@ -95,6 +95,8 @@ public class BasicFormatterImpl implements Formatter { String lcToken; public FormatProcess(String sql) { + assert sql != null : "SQL to format should not be null"; + tokens = new StringTokenizer( sql, "()+*/-=<>'`\"[]," + StringHelper.WHITESPACE, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderStandardImpl.java index 97ed8196a4..9c2213c35a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderStandardImpl.java @@ -6,6 +6,7 @@ */ package org.hibernate.loader.internal; +import java.io.Serializable; import java.util.EnumMap; import org.hibernate.LockMode; @@ -14,9 +15,11 @@ import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.loader.entity.BatchingEntityLoaderBuilder; import org.hibernate.loader.spi.InternalFetchProfile; import org.hibernate.loader.spi.SingleIdEntityLoader; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.sql.exec.spi.JdbcSelect; /** @@ -45,14 +48,21 @@ public class SingleIdEntityLoaderStandardImpl implements SingleIdEntityLoader @Override public T load(Object key, LockOptions lockOptions, SharedSessionContractImplementor session) { + // todo (6.0) : TEMPORARY - use the legacy loaders + + //noinspection unchecked + return (T) BatchingEntityLoaderBuilder.getBuilder( session.getFactory() ) + .buildLoader( (OuterJoinLoadable) entityDescriptor, -1, lockOptions.getLockMode(), session.getFactory(), session.getLoadQueryInfluencers() ) + .load( (Serializable) key, null, session, lockOptions ); + // todo (6.0) : see `org.hibernate.loader.internal.StandardSingleIdEntityLoader#load` in "upstream" 6.0 branch // - and integrate as much as possible with the `o.h.loader.plan` stuff leveraging the similarities // between the legacy LoadPlan stuff and DomainResult, Assembler, etc. - - final JdbcSelect jdbcSelect = resolveJdbcSelect( lockOptions, session ); - - throw new NotYetImplementedFor6Exception( getClass() ); +// +// final JdbcSelect jdbcSelect = resolveJdbcSelect( lockOptions, session ); +// +// throw new NotYetImplementedFor6Exception( getClass() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/Loadable.java b/hibernate-core/src/main/java/org/hibernate/loader/spi/Loadable.java index 237c9c9084..0b301b524d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/Loadable.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/spi/Loadable.java @@ -6,6 +6,9 @@ */ package org.hibernate.loader.spi; +import java.util.function.Consumer; +import java.util.function.Supplier; + import org.hibernate.LockMode; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -17,6 +20,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.tree.from.RootTableGroupProducer; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.predicate.Predicate; /** * Contract for things that can be loaded by a Loader. @@ -40,6 +44,7 @@ public interface Loadable extends ModelPart, RootTableGroupProducer { LockMode lockMode, SqlAliasBaseGenerator aliasBaseGenerator, SqlExpressionResolver sqlExpressionResolver, + Supplier> additionalPredicateCollectorAccess, SqlAstCreationContext creationContext) { throw new NotYetImplementedFor6Exception( getClass() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityDiscriminatorMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityDiscriminatorMapping.java new file mode 100644 index 0000000000..7084a662aa --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityDiscriminatorMapping.java @@ -0,0 +1,14 @@ +/* + * 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.mapping; + +/** + * @author Steve Ebersole + */ +public interface EntityDiscriminatorMapping extends VirtualModelPart, BasicValuedModelPart { + String ROLE_NAME = "{discriminator}"; +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java index c02ca71a7b..18622ea3ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java @@ -33,6 +33,10 @@ public interface EntityMappingType extends ManagedMappingType { EntityVersionMapping getVersionMapping(); + default EntityDiscriminatorMapping getDiscriminatorMapping() { + return null; + } + NaturalIdMapping getNaturalIdMapping(); @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/VirtualModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/VirtualModelPart.java new file mode 100644 index 0000000000..124e31ebe3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/VirtualModelPart.java @@ -0,0 +1,16 @@ +/* + * 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.mapping; + +/** + * Marker interface for parts of the application domain model that are virtual - do not + * actually exist in the model classes + * + * @author Steve Ebersole + */ +public interface VirtualModelPart extends ModelPart { +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java index 4a7c06e291..a72950ce2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java @@ -121,8 +121,8 @@ public class BasicValuedSingularAttributeMapping extends AbstractSingularAttribu getMappedColumnExpression() ), sqlAstProcessingState -> new ColumnReference( - getMappedColumnExpression(), tableReference.getIdentificationVariable(), + getMappedColumnExpression(), jdbcMapping, creationState.getSqlAstCreationState().getCreationContext().getSessionFactory() ) @@ -149,8 +149,8 @@ public class BasicValuedSingularAttributeMapping extends AbstractSingularAttribu getMappedColumnExpression() ), sqlAstProcessingState -> new ColumnReference( - getMappedColumnExpression(), tableReference.getIdentificationVariable(), + getMappedColumnExpression(), jdbcMapping, creationState.getSqlAstCreationState().getCreationContext().getSessionFactory() ) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java index 932d6e041c..bb82f8f401 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java @@ -189,8 +189,8 @@ public class EmbeddedAttributeMapping attrColumnExpr ), sqlAstProcessingState -> new ColumnReference( - attrColumnExpr, tableReference.getIdentificationVariable(), + attrColumnExpr, jdbcMapping, sqlAstCreationState.getCreationContext().getSessionFactory() ) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityDiscriminatorMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityDiscriminatorMappingImpl.java new file mode 100644 index 0000000000..84afa9d66c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityDiscriminatorMappingImpl.java @@ -0,0 +1,144 @@ +/* + * 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.mapping.internal; + +import org.hibernate.LockMode; +import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.NavigablePath; +import org.hibernate.query.sqm.sql.SqlAstCreationState; +import org.hibernate.query.sqm.sql.SqlExpressionResolver; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.results.internal.domain.basic.BasicFetch; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.Fetch; +import org.hibernate.sql.results.spi.FetchParent; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * @author Steve Ebersole + */ +public class EntityDiscriminatorMappingImpl implements EntityDiscriminatorMapping { + private final EntityPersister entityDescriptor; + + private final String tableExpression; + private final String mappedColumnExpression; + + private final BasicType mappingType; + + public EntityDiscriminatorMappingImpl( + EntityPersister entityDescriptor, + String tableExpression, + String mappedColumnExpression, + BasicType mappingType) { + this.entityDescriptor = entityDescriptor; + this.tableExpression = tableExpression; + this.mappedColumnExpression = mappedColumnExpression; + this.mappingType = mappingType; + } + + @Override + public String getContainingTableExpression() { + return tableExpression; + } + + @Override + public String getMappedColumnExpression() { + return mappedColumnExpression; + } + + @Override + public BasicValueConverter getConverter() { + return null; + } + + @Override + public String getFetchableName() { + return ROLE_NAME; + } + + @Override + public FetchStrategy getMappedFetchStrategy() { + return FetchStrategy.IMMEDIATE_JOIN; + } + + @Override + public Fetch generateFetch( + FetchParent fetchParent, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + LockMode lockMode, + String resultVariable, + DomainResultCreationState creationState) { + final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); + final TableGroup tableGroup = sqlAstCreationState.getFromClauseAccess().getTableGroup( + fetchParent.getNavigablePath() + ); + + assert tableGroup != null; + + final SqlSelection sqlSelection = resolveSqlSelection( tableGroup, creationState ); + + return new BasicFetch( + sqlSelection.getValuesArrayPosition(), + fetchParent, + fetchablePath, + this, + false, + getConverter(), + fetchTiming, + creationState + ); + } + private SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) { + final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver(); + + final TableReference tableReference = tableGroup.resolveTableReference( getContainingTableExpression() ); + + return expressionResolver.resolveSqlSelection( + expressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( + tableReference, + getMappedColumnExpression() + ), + sqlAstProcessingState -> new ColumnReference( + tableReference.getIdentificationVariable(), + getMappedColumnExpression(), + mappingType.getJdbcMapping(), + creationState.getSqlAstCreationState().getCreationContext().getSessionFactory() + ) + ), + getMappedTypeDescriptor().getMappedJavaTypeDescriptor(), + creationState.getSqlAstCreationState().getCreationContext().getDomainModel().getTypeConfiguration() + ); + } + + @Override + public JavaTypeDescriptor getJavaTypeDescriptor() { + return getMappedTypeDescriptor().getMappedJavaTypeDescriptor(); + } + + @Override + public MappingType getMappedTypeDescriptor() { + return mappingType; + } + + @Override + public JdbcMapping getJdbcMapping() { + return mappingType.getJdbcMapping(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java index ead6d27abc..5ec9b2c26d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java @@ -135,8 +135,8 @@ public class MappingModelCreationHelper { final Expression expression = expressionResolver.resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( rootTableReference, pkColumnName ), sqlAstProcessingState -> new ColumnReference( - pkColumnName, rootTableReference.getIdentificationVariable(), + pkColumnName, ( (BasicValuedMapping) entityPersister.getIdentifierType() ).getJdbcMapping(), creationProcess.getCreationContext().getSessionFactory() ) @@ -167,8 +167,8 @@ public class MappingModelCreationHelper { final Expression expression = expressionResolver.resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( rootTableReference, pkColumnName ), sqlAstProcessingState -> new ColumnReference( - pkColumnName, rootTable, + pkColumnName, ( (BasicValuedModelPart) entityPersister.getIdentifierType() ).getJdbcMapping(), creationProcess.getCreationContext().getSessionFactory() ) 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 57476ce83a..23f67ca53c 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 @@ -31,6 +31,7 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.function.Supplier; import org.hibernate.AssertionFailure; import org.hibernate.EntityMode; @@ -104,7 +105,6 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.FilterHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectations; import org.hibernate.jdbc.TooManyRowsAffectedException; @@ -134,6 +134,7 @@ import org.hibernate.metadata.ClassMetadata; import org.hibernate.metamodel.RepresentationMode; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMetadata; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityVersionMapping; @@ -144,6 +145,7 @@ import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.Queryable; import org.hibernate.metamodel.mapping.StateArrayContributorMapping; import org.hibernate.metamodel.mapping.StateArrayContributorMetadata; +import org.hibernate.metamodel.mapping.internal.EntityDiscriminatorMappingImpl; import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; @@ -179,6 +181,7 @@ import org.hibernate.sql.ast.spi.SqlAliasStemHelper; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.sql.ast.tree.from.StandardTableGroup; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; @@ -1180,6 +1183,7 @@ public abstract class AbstractEntityPersister LockMode lockMode, SqlAliasBaseGenerator aliasBaseGenerator, SqlExpressionResolver sqlExpressionResolver, + Supplier> additionalPredicateCollectorAccess, SqlAstCreationContext creationContext) { final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() ); @@ -1274,8 +1278,8 @@ public abstract class AbstractEntityPersister rootPkColumnName ), sqlAstProcessingState -> new ColumnReference( - rootPkColumnName, rootTableReference.getIdentificationVariable(), + rootPkColumnName, jdbcMapping, getFactory() ) @@ -1288,8 +1292,8 @@ public abstract class AbstractEntityPersister fkColumnName ), sqlAstProcessingState -> new ColumnReference( - fkColumnName, joinedTableReference.getIdentificationVariable(), + fkColumnName, jdbcMapping, getFactory() ) @@ -5404,18 +5408,22 @@ public abstract class AbstractEntityPersister return this; } else { - final String concreteEntityName = getEntityTuplizer().determineConcreteSubclassEntityName( - instance, - factory - ); - if ( concreteEntityName == null || getEntityName().equals( concreteEntityName ) ) { - // the contract of EntityTuplizer.determineConcreteSubclassEntityName says that returning null - // is an indication that the specified entity-name (this.getEntityName) should be used. + // todo (6.0) : this previously used `org.hibernate.tuple.entity.EntityTuplizer#determineConcreteSubclassEntityName` + // - we may need something similar here... + + if ( getRepresentationStrategy().getInstantiator().isInstance( instance, factory ) ) { return this; } - else { - return factory.getEntityPersister( concreteEntityName ); + + if ( hasSubclasses() ) { + for ( EntityMappingType sub : subclassMappingTypes.values() ) { + if ( sub.getEntityPersister().getRepresentationStrategy().getInstantiator().isInstance( instance, factory ) ) { + return sub.getEntityPersister(); + } + } } + + return this; } } @@ -6048,6 +6056,7 @@ public abstract class AbstractEntityPersister private EntityIdentifierMapping identifierMapping; private NaturalIdMapping naturalIdMapping; private EntityVersionMapping versionMapping; + private EntityDiscriminatorMapping discriminatorMapping; private SortedMap declaredAttributeMappings = new TreeMap<>(); private Collection attributeMappings; @@ -6088,6 +6097,18 @@ public abstract class AbstractEntityPersister ); } + if ( getDiscriminatorType() == null ) { + discriminatorMapping = null; + } + else { + discriminatorMapping = new EntityDiscriminatorMappingImpl( + this, + getRootTableName(), + getDiscriminatorColumnName(), + (BasicType) getDiscriminatorType() + ); + } + final EntityMetamodel currentEntityMetamodel = this.getEntityMetamodel(); int stateArrayPosition = superMappingType == null ? 0 : superMappingType.getNumberOfAttributeMappings(); @@ -6285,6 +6306,11 @@ public abstract class AbstractEntityPersister return versionMapping; } + @Override + public EntityDiscriminatorMapping getDiscriminatorMapping() { + return discriminatorMapping; + } + @Override public Collection getAttributeMappings() { if ( attributeMappings == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index 2ab666d8c2..db0816302e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -14,8 +14,11 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; import org.hibernate.HibernateException; +import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.boot.model.relational.Database; import org.hibernate.cache.spi.access.EntityDataAccess; @@ -37,10 +40,23 @@ import org.hibernate.mapping.Subclass; import org.hibernate.mapping.Table; import org.hibernate.mapping.Value; import org.hibernate.persister.spi.PersisterCreationContext; +import org.hibernate.query.ComparisonOperator; +import org.hibernate.query.NavigablePath; +import org.hibernate.query.sqm.sql.SqlExpressionResolver; import org.hibernate.sql.InFragment; import org.hibernate.sql.Insert; import org.hibernate.sql.SelectFragment; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.JoinType; +import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; +import org.hibernate.sql.ast.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.QueryLiteral; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.type.AssociationType; +import org.hibernate.type.BasicType; import org.hibernate.type.DiscriminatorType; import org.hibernate.type.Type; @@ -847,4 +863,60 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) { return new DynamicFilterAliasGenerator( qualifiedTableNames, rootAlias ); } + + @Override + public TableGroup createRootTableGroup( + NavigablePath navigablePath, + String explicitSourceAlias, + JoinType tableReferenceJoinType, + LockMode lockMode, + SqlAliasBaseGenerator aliasBaseGenerator, + SqlExpressionResolver sqlExpressionResolver, + Supplier> additionalPredicateCollectorAccess, + SqlAstCreationContext creationContext) { + final TableGroup tableGroup = super.createRootTableGroup( + navigablePath, + explicitSourceAlias, + tableReferenceJoinType, + lockMode, + aliasBaseGenerator, + sqlExpressionResolver, + additionalPredicateCollectorAccess, + creationContext + ); + + if ( needsDiscriminator() ) { + final Predicate discriminatorPredicate = createDiscriminatorPredicate( + tableGroup, + sqlExpressionResolver, + creationContext + ); + additionalPredicateCollectorAccess.get().accept( discriminatorPredicate ); + } + + return tableGroup; + } + + private Predicate createDiscriminatorPredicate( + TableGroup tableGroup, + SqlExpressionResolver sqlExpressionResolver, + SqlAstCreationContext creationContext) { + return new ComparisonPredicate( + sqlExpressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( tableGroup.getPrimaryTableReference(), getDiscriminatorColumnName() ), + sqlAstProcessingState -> new ColumnReference( + tableGroup.getPrimaryTableReference().getIdentificationVariable(), + getDiscriminatorColumnName(), + ( (BasicType) getDiscriminatorType() ).getJdbcMapping(), + getFactory() + ) + ), + ComparisonOperator.EQUAL, + new QueryLiteral<>( + getDiscriminatorValue(), + ( (BasicType) getDiscriminatorType() ), + Clause.WHERE + ) + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java index acf9cbb334..14b94ce8c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java @@ -37,6 +37,7 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi /** * The constructor signature for {@link EntityPersister} implementations */ + @SuppressWarnings({"WeakerAccess", "deprecation"}) public static final Class[] ENTITY_PERSISTER_CONSTRUCTOR_ARGS = new Class[] { PersistentClass.class, EntityDataAccess.class, @@ -47,6 +48,7 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi /** * The constructor signature for {@link CollectionPersister} implementations */ + @SuppressWarnings({"WeakerAccess", "deprecation"}) public static final Class[] COLLECTION_PERSISTER_CONSTRUCTOR_ARGS = new Class[] { Collection.class, CollectionDataAccess.class, @@ -66,7 +68,7 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi PersistentClass entityBinding, EntityDataAccess entityCacheAccessStrategy, NaturalIdDataAccess naturalIdCacheAccessStrategy, - PersisterCreationContext creationContext) throws HibernateException { + @SuppressWarnings("deprecation") PersisterCreationContext creationContext) throws HibernateException { // If the metadata for the entity specified an explicit persister class, use it... Class persisterClass = entityBinding.getEntityPersisterClass(); if ( persisterClass == null ) { @@ -83,13 +85,12 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi ); } - @SuppressWarnings( {"unchecked"}) private EntityPersister createEntityPersister( Class persisterClass, PersistentClass entityBinding, EntityDataAccess entityCacheAccessStrategy, NaturalIdDataAccess naturalIdCacheAccessStrategy, - PersisterCreationContext creationContext) { + @SuppressWarnings("deprecation") PersisterCreationContext creationContext) { try { final Constructor constructor = persisterClass.getConstructor( ENTITY_PERSISTER_CONSTRUCTOR_ARGS ); try { @@ -116,7 +117,7 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi throw new MappingException( "Could not instantiate persister " + persisterClass.getName(), e ); } } - catch (MappingException e) { + catch (HibernateException e) { throw e; } catch (Exception e) { @@ -129,7 +130,7 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi public CollectionPersister createCollectionPersister( Collection collectionBinding, CollectionDataAccess cacheAccessStrategy, - PersisterCreationContext creationContext) throws HibernateException { + @SuppressWarnings("deprecation") PersisterCreationContext creationContext) throws HibernateException { // If the metadata for the collection specified an explicit persister class, use it Class persisterClass = collectionBinding.getCollectionPersisterClass(); if ( persisterClass == null ) { @@ -140,12 +141,11 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi return createCollectionPersister( persisterClass, collectionBinding, cacheAccessStrategy, creationContext ); } - @SuppressWarnings( {"unchecked"}) private CollectionPersister createCollectionPersister( Class persisterClass, Collection collectionBinding, CollectionDataAccess cacheAccessStrategy, - PersisterCreationContext creationContext) { + @SuppressWarnings("deprecation") PersisterCreationContext creationContext) { try { Constructor constructor = persisterClass.getConstructor( COLLECTION_PERSISTER_CONSTRUCTOR_ARGS ); try { 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 9a1b1e11ef..93d19b86fb 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 @@ -96,6 +96,7 @@ import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.spi.SqlAstTreeHelper; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; @@ -278,6 +279,8 @@ public abstract class BaseSqmToSqlAstConverter public QuerySpec visitQuerySpec(SqmQuerySpec sqmQuerySpec) { final QuerySpec sqlQuerySpec = new QuerySpec( processingStateStack.isEmpty(), sqmQuerySpec.getFromClause().getNumberOfRoots() ); + additionalRestrictions = null; + processingStateStack.push( new SqlAstQuerySpecProcessingStateImpl( sqlQuerySpec, @@ -308,6 +311,10 @@ public abstract class BaseSqmToSqlAstConverter } } + if ( additionalRestrictions != null ) { + sqlQuerySpec.applyPredicate( additionalRestrictions ); + } + // todo : group-by // todo : having @@ -412,6 +419,8 @@ public abstract class BaseSqmToSqlAstConverter return null; } + Predicate additionalRestrictions; + @SuppressWarnings("WeakerAccess") protected void consumeFromClauseRoot(SqmRoot sqmRoot) { log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot ); @@ -427,6 +436,7 @@ public abstract class BaseSqmToSqlAstConverter LockMode.NONE, sqlAliasBaseManager, getSqlExpressionResolver(), + () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, predicate ), creationContext ); @@ -512,6 +522,7 @@ public abstract class BaseSqmToSqlAstConverter determineLockMode( sqmJoin.getExplicitAlias() ), sqlAliasBaseManager, getSqlExpressionResolver(), + () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, predicate ), getCreationContext() ); @@ -539,6 +550,7 @@ public abstract class BaseSqmToSqlAstConverter determineLockMode( sqmJoin.getExplicitAlias() ), sqlAliasBaseManager, getSqlExpressionResolver(), + () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, predicate ), getCreationContext() ); fromClauseIndex.register( sqmJoin, tableGroup ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java index 496ff6e80f..f2ec4d9655 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java @@ -52,8 +52,8 @@ public class BasicValuedPathInterpretation implements AssignableSqmPathInterp mapping.getMappedColumnExpression() ), sacs -> new ColumnReference( - mapping.getMappedColumnExpression(), tableReference.getIdentificationVariable(), + mapping.getMappedColumnExpression(), mapping.getJdbcMapping(), sqlAstCreationState.getCreationContext().getSessionFactory() ) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java index 4c63048ac2..4142db40f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java @@ -29,8 +29,8 @@ public class ColumnReference implements Expression { private final JdbcMapping jdbcMapping; public ColumnReference( - String columnExpression, String qualifier, + String columnExpression, JdbcMapping jdbcMapping, SessionFactoryImplementor sessionFactory) { this( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java index 8ce569742b..270d3baf51 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java @@ -6,6 +6,9 @@ */ package org.hibernate.sql.ast.tree.from; +import java.util.function.Consumer; +import java.util.function.Supplier; + import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.query.NavigablePath; @@ -13,6 +16,7 @@ import org.hibernate.query.sqm.sql.SqlExpressionResolver; import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.tree.predicate.Predicate; /** * Contract for things that can produce the {@link TableGroup} that is the root of a @@ -31,5 +35,6 @@ public interface RootTableGroupProducer extends TableGroupProducer, ModelPartCon LockMode lockMode, SqlAliasBaseGenerator aliasBaseGenerator, SqlExpressionResolver sqlExpressionResolver, + Supplier> additionalPredicateCollectorAccess, SqlAstCreationContext creationContext); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/InheritanceTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/InheritanceTests.java new file mode 100644 index 0000000000..febb46bf6f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/InheritanceTests.java @@ -0,0 +1,238 @@ +/* + * 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.orm.test.metamodel.mapping; + +import java.util.List; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +import org.hibernate.persister.entity.EntityPersister; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.hamcrest.CoreMatchers; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("WeakerAccess") +@DomainModel( + annotatedClasses = { + InheritanceTests.Customer.class, + InheritanceTests.DomesticCustomer.class, + InheritanceTests.ForeignCustomer.class + } +) +@ServiceRegistry +@SessionFactory +public class InheritanceTests { + @Test + public void basicTest(SessionFactoryScope scope) { + final EntityPersister customerDescriptor = scope.getSessionFactory() + .getMetamodel() + .findEntityDescriptor( Customer.class ); + final EntityPersister domesticCustomerDescriptor = scope.getSessionFactory() + .getMetamodel() + .findEntityDescriptor( DomesticCustomer.class ); + final EntityPersister foreignCustomerDescriptor = scope.getSessionFactory() + .getMetamodel() + .findEntityDescriptor( ForeignCustomer.class ); + + assert customerDescriptor.isTypeOrSuperType( customerDescriptor ); + assert ! customerDescriptor.isTypeOrSuperType( domesticCustomerDescriptor ); + assert ! customerDescriptor.isTypeOrSuperType( foreignCustomerDescriptor ); + + assert domesticCustomerDescriptor.isTypeOrSuperType( customerDescriptor ); + assert domesticCustomerDescriptor.isTypeOrSuperType( domesticCustomerDescriptor ); + assert ! domesticCustomerDescriptor.isTypeOrSuperType( foreignCustomerDescriptor ); + + assert foreignCustomerDescriptor.isTypeOrSuperType( customerDescriptor ); + assert ! foreignCustomerDescriptor.isTypeOrSuperType( domesticCustomerDescriptor ); + assert foreignCustomerDescriptor.isTypeOrSuperType( foreignCustomerDescriptor ); + } + + @BeforeEach + public void createTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.persist( new DomesticCustomer( 1, "domestic", "123" ) ); + session.persist( new ForeignCustomer( 2, "foreign", "987" ) ); + } + ); + } + + @AfterEach + public void cleanupTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "from DomesticCustomer", DomesticCustomer.class ).list().forEach( + cust -> session.delete( cust ) + ); + session.createQuery( "from ForeignCustomer", ForeignCustomer.class ).list().forEach( + cust -> session.delete( cust ) + ); + } + ); + } + + @Test + @FailureExpected + public void rootQueryExecutionTest(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + { + final List results = session.createQuery( + "select c from Customer c", + Customer.class + ).list(); + + assertThat( results.size(), is( 2 ) ); + + for ( Customer result : results ) { + if ( result.getId() == 1 ) { + assertThat( result, instanceOf( DomesticCustomer.class ) ); + assertThat( ( (DomesticCustomer) result ).getTaxId(), is( "123" ) ); + } + else { + assertThat( result.getId(), is( 2 ) ); + assertThat( ( (ForeignCustomer) result ).getVat(), is( "987" ) ); + } + } + + } + } + ); + } + + @Test + public void subclassQueryExecutionTest(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + { + final DomesticCustomer result = session.createQuery( + "select c from DomesticCustomer c", + DomesticCustomer.class + ).uniqueResult(); + + assertThat( result, notNullValue() ); + assertThat( result.getId(), is( 1 ) ); + assertThat( result.getName(), is( "domestic" ) ); + assertThat( result.getTaxId(), is( "123" ) ); + } + + { + final ForeignCustomer result = session.createQuery( + "select c from ForeignCustomer c", + ForeignCustomer.class + ).uniqueResult(); + + assertThat( result, notNullValue() ); + assertThat( result.getId(), is( 2 ) ); + assertThat( result.getName(), is( "foreign" ) ); + assertThat( result.getVat(), is( "987" ) ); + } + } + ); + } + + @Entity( name = "Customer" ) + @Inheritance( strategy = InheritanceType.SINGLE_TABLE ) + @Table( name = "customer" ) + @DiscriminatorColumn( name = "cust_type" ) + public static abstract class Customer { + private Integer id; + private String name; + + public Customer() { + } + + public Customer(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity( name = "DomesticCustomer" ) + @DiscriminatorValue( "dc" ) + public static class DomesticCustomer extends Customer { + private String taxId; + + public DomesticCustomer() { + } + + public DomesticCustomer(Integer id, String name, String taxId) { + super( id, name ); + this.taxId = taxId; + } + + public String getTaxId() { + return taxId; + } + + public void setTaxId(String taxId) { + this.taxId = taxId; + } + } + + @Entity( name = "ForeignCustomer" ) + @DiscriminatorValue( "fc" ) + public static class ForeignCustomer extends Customer { + private String vat; + + public ForeignCustomer() { + } + + public ForeignCustomer(Integer id, String name, String vat) { + super( id, name ); + this.vat = vat; + } + + public String getVat() { + return vat; + } + + public void setVat(String vat) { + this.vat = vat; + } + } + +} + diff --git a/hibernate-core/src/test/resources/hibernate.properties b/hibernate-core/src/test/resources/hibernate.properties index 6b5b27a5d4..df1e7e1fb6 100644 --- a/hibernate-core/src/test/resources/hibernate.properties +++ b/hibernate-core/src/test/resources/hibernate.properties @@ -25,5 +25,3 @@ javax.persistence.validation.mode=NONE hibernate.service.allow_crawling=false hibernate.session.events.log=true hibernate.hql.bulk_id_strategy.global_temporary.drop_tables=true - -hibernate.bytecode.use_reflection_optimizer=true \ No newline at end of file