diff --git a/design/working/circular-fetching.adoc b/design/working/circular-fetching.adoc index d1ede6723d..dd733a79a8 100644 --- a/design/working/circular-fetching.adoc +++ b/design/working/circular-fetching.adoc @@ -113,6 +113,8 @@ Given this model, we have the following mappings from modelPart to "identifying * `Order#lineItems#{element}` -> `lines.order_id` +Once we find a circularity we should build the `BiDirectionalFetch` reference pointing to the +Initializer for the "parent parent path". See `RowProcessingState#.resolveInitializer` @@ -123,7 +125,8 @@ Hibernate needs to handle circularity in a fetch-graph. E.g.: select o from Order o join fetch o.lineItems l - join fetch l.order + join fetch l.order o2 + join fetch o2.lineItems ``` Here, the join fetch of `l.order` is circular, meaning we do not want to render a join in the SQL for it @@ -131,4 +134,75 @@ because it is already part of the from-clause via `Order o`. Recognizing circularity needs to happen in a number of mapping scenarios and I believe the conditions vary depending on the type of mapping involved (one-to-one, many-to-one, many-to-many). Ideally we can find commonality -and handle these conditions uniformly. \ No newline at end of file +and handle these conditions uniformly. + + +== with embeddables + +``` +@Entity +@Table(name="root") +class RootEntity { + ... + + @Embedded + IntermediateComponent intermediateComponent; +} + +@Embeddable +class IntermediateComponent { + ... + + @OneToMany( mappedBy = "rootEntity" ) + Set leaves +} + +@Entity +@Table(name="leaf") +class LeafEntity { + ... + + @ManyToOne + @JoinColumn(name="root_id) + RootEntity rootEntity; +} +``` + +Given this model, we have the following mappings from modelPart to "identifying columns": + +* `RootEntity#intermediateComponent#leaves -> `leaf.root_id` +* `RootEntity#intermediateComponent#leaves -> `leaf.root_id` +* + +* `RootEntity#intermediateComponent#leaves#{element} +* `Order#lineItems#{element}` -> `lines.order_id` + + +class Order { + @OneToMany(mappedBy="order") + List lineItems; +} + + + + +``` +------------ +"orders" +------------ +id INTEGER +name VARCHAR + +------------ +"order_items" +------------ +orders_id +items_id + +------------ +"items" +------------ +id +qty + +``` \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java index 464032a716..2195edfb22 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java @@ -24,6 +24,7 @@ import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.loader.ast.spi.Loadable; import org.hibernate.loader.ast.spi.Loader; import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ModelPart; @@ -383,9 +384,20 @@ public class LoaderSelectBuilder { final List fetches = new ArrayList<>(); final Consumer processor = fetchable -> { - final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() ); + final NavigablePath fetchablePath; + final Fetchable fetchedFetchable; + if ( fetchable instanceof PluralAttributeMapping ) { + fetchablePath = fetchParent.getNavigablePath() + .append( fetchable.getFetchableName() ) + .append( CollectionPart.Nature.ELEMENT.getName() ); + fetchedFetchable = ( (PluralAttributeMapping) fetchable ).getElementDescriptor(); + } + else { + fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() ); + fetchedFetchable = fetchable; + } - final Fetch biDirectionalFetch = fetchable.resolveCircularFetch( + final Fetch biDirectionalFetch = fetchedFetchable.resolveCircularFetch( fetchablePath, fetchParent, creationState @@ -426,7 +438,7 @@ public class LoaderSelectBuilder { if ( ! (fetchable instanceof BasicValuedModelPart) ) { fetchDepth++; } - Fetch fetch = fetchable.generateFetch( + Fetch fetch = fetchedFetchable.generateFetch( fetchParent, fetchablePath, fetchTiming, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java index d6da85205d..34605b4a06 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java @@ -8,8 +8,6 @@ package org.hibernate.loader.ast.internal; import java.util.Collections; import java.util.List; -import java.util.function.BiFunction; - import javax.persistence.CacheRetrieveMode; import javax.persistence.CacheStoreMode; @@ -18,6 +16,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.graph.spi.AppliedGraph; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.Limit; import org.hibernate.query.NavigablePath; import org.hibernate.query.ResultListTransformer; @@ -136,6 +135,12 @@ public class LoaderSqlAstCreationState return this; } + @Override + public ModelPart resolveModelPart(NavigablePath navigablePath) { + // for now, let's assume that the navigable-path refers to TableGroup + return fromClauseAccess.findTableGroup( navigablePath ).getModelPart(); + } + @Override public SqlAstProcessingState getParentState() { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Association.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Association.java new file mode 100644 index 0000000000..d5a3fb3589 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Association.java @@ -0,0 +1,22 @@ +/* + * 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; + +import org.hibernate.sql.results.graph.Fetchable; + +/** + * @author Steve Ebersole + */ +public interface Association extends Fetchable { + ForeignKeyDescriptor getForeignKeyDescriptor(); + + /** + * The column expressions that identify this association. + * Mainly used in circularity detection + */ + String[] getIdentifyingColumnExpressions(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java index 9a0b5efc02..3bf7e710c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java @@ -37,8 +37,6 @@ public interface EntityValuedModelPart extends FetchableContainer { getEntityMappingType().visitSubParts( consumer, targetType ); } - String[] getIdentifyingColumnExpressions(); - @Override default DomainResult createDomainResult( NavigablePath navigablePath, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java index 2c90e46dac..36b8696f55 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java @@ -15,6 +15,7 @@ import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.type.ForeignKeyDirection; /** * @author Steve Ebersole @@ -22,6 +23,8 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; public interface ForeignKeyDescriptor extends VirtualModelPart { String PART_NAME = "{fk}"; + ForeignKeyDirection getDirection(); + DomainResult createDomainResult(NavigablePath collectionPath, TableGroup tableGroup, DomainResultCreationState creationState); Predicate generateJoinPredicate( 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 520fe43b42..1068b61350 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 @@ -33,7 +33,6 @@ import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; @@ -167,7 +166,7 @@ public class EmbeddedAttributeMapping public Fetch resolveCircularFetch( NavigablePath fetchablePath, FetchParent fetchParent, - SqlAstProcessingState creationState) { + DomainResultCreationState creationState) { // an embeddable can never be circular return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java index 232624f6da..bbd092bb39 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java @@ -6,21 +6,25 @@ */ package org.hibernate.metamodel.mapping.internal; +import java.util.ArrayList; +import java.util.List; + import org.hibernate.LockMode; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchTiming; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Value; +import org.hibernate.metamodel.mapping.Association; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.query.NavigablePath; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -35,13 +39,15 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * @author Steve Ebersole */ -public class EntityCollectionPart implements CollectionPart, EntityAssociationMapping, EntityValuedFetchable { +public class EntityCollectionPart + implements CollectionPart, EntityAssociationMapping, EntityValuedFetchable, Association { private final NavigableRole navigableRole; private final CollectionPersister collectionDescriptor; private final Nature nature; private final EntityMappingType entityMappingType; private ModelPart fkTargetModelPart; + private String[] identifyingColumns; @SuppressWarnings("WeakerAccess") public EntityCollectionPart( @@ -69,6 +75,14 @@ public class EntityCollectionPart implements CollectionPart, EntityAssociationMa else { fkTargetModelPart = entityMappingType.findSubPart( fkTargetModelPartName, null ); } + + final List identifyingColumnsList = new ArrayList<>(); + collectionDescriptor.getAttributeMapping().getKeyDescriptor().visitReferringColumns( + (containingTableExpression, columnExpression, jdbcMapping) -> { + identifyingColumnsList.add( containingTableExpression + "." + columnExpression ); + } + ); + this.identifyingColumns = identifyingColumnsList.toArray( new String[0] ); } @@ -121,7 +135,7 @@ public class EntityCollectionPart implements CollectionPart, EntityAssociationMa public Fetch resolveCircularFetch( NavigablePath fetchablePath, FetchParent fetchParent, - SqlAstProcessingState creationState) { + DomainResultCreationState creationState) { return null; } @@ -192,4 +206,15 @@ public class EntityCollectionPart implements CollectionPart, EntityAssociationMa public String toString() { return "EntityCollectionPart {" + navigableRole + "}"; } + + @Override + public ForeignKeyDescriptor getForeignKeyDescriptor() { + // todo (6.0) : this will not strictly work - we'd want a new ForeignKeyDescriptor that points the other direction + return collectionDescriptor.getAttributeMapping().getKeyDescriptor(); + } + + @Override + public String[] getIdentifyingColumnExpressions() { + return identifyingColumns; + } } 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 fc815b3424..9d314c9348 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 @@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping.internal; import java.io.Serializable; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.SortedMap; import java.util.SortedSet; import java.util.function.Consumer; @@ -221,7 +222,22 @@ public class MappingModelCreationHelper { String resultVariable, DomainResultCreationState creationState) { final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver(); - final TableReference rootTableReference = tableGroup.resolveTableReference( rootTable ); + final TableReference rootTableReference; + try { + rootTableReference = tableGroup.resolveTableReference( rootTable ); + } + catch (Exception e) { + throw new IllegalStateException( + String.format( + Locale.ROOT, + "Could not resolve table reference `%s` relative to TableGroup `%s` related with NavigablePath `%s`", + rootTable, + tableGroup, + navigablePath + ), + e + ); + } final Expression expression = expressionResolver.resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( rootTableReference, pkColumnName ), @@ -1282,8 +1298,7 @@ public class MappingModelCreationHelper { CascadeStyle cascadeStyle, MappingModelCreationProcess creationProcess) { ToOne value = (ToOne) bootProperty.getValue(); - final EntityPersister entityPersister = creationProcess.getEntityPersister( - value.getReferencedEntityName() ); + final EntityPersister entityPersister = creationProcess.getEntityPersister( value.getReferencedEntityName() ); final StateArrayContributorMetadataAccess stateArrayContributorMetadataAccess = getStateArrayContributorMetadataAccess( bootProperty, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java index a33f38a78d..c66f97a665 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java @@ -515,6 +515,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme lockMode, primaryTableReference, sqlAliasBase, + (tableExpression) -> entityPartDescriptor.getEntityMappingType().containsTableReference( tableExpression ), (tableExpression, tg) -> entityPartDescriptor.getEntityMappingType().createTableReferenceJoin( tableExpression, sqlAliasBase, @@ -585,6 +586,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme final Consumer tableGroupFinalizer; final BiFunction tableReferenceJoinCreator; + final java.util.function.Predicate tableReferenceJoinNameChecker; if ( elementDescriptor instanceof EntityCollectionPart || indexDescriptor instanceof EntityCollectionPart ) { final EntityCollectionPart entityPartDescriptor; if ( elementDescriptor instanceof EntityCollectionPart ) { @@ -601,6 +603,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme creationContext ); + tableReferenceJoinNameChecker = mappingType::containsTableReference; tableReferenceJoinCreator = (tableExpression, tableGroup) -> mappingType.createTableReferenceJoin( tableExpression, sqlAliasBase, @@ -634,6 +637,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme "element-collection cannot contain joins : " + collectionTableReference.getTableExpression() + " -> " + tableExpression ); }; + tableReferenceJoinNameChecker = s -> false; tableGroupFinalizer = null; } @@ -643,6 +647,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme lockMode, collectionTableReference, sqlAliasBase, + tableReferenceJoinNameChecker, tableReferenceJoinCreator, creationContext.getSessionFactory() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java index a21d5401e1..ecabe55866 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java @@ -37,11 +37,11 @@ import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.type.BasicType; import org.hibernate.type.ForeignKeyDirection; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -73,6 +73,11 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa this.jdbcMapping = jdbcMapping; } + @Override + public ForeignKeyDirection getDirection() { + return fKeyDirection; + } + @Override public DomainResult createDomainResult( NavigablePath collectionPath, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SingularAssociationAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SingularAssociationAttributeMapping.java index d370a458a0..32a30d9892 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SingularAssociationAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SingularAssociationAttributeMapping.java @@ -6,21 +6,23 @@ */ package org.hibernate.metamodel.mapping.internal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.hibernate.HibernateException; import org.hibernate.LockMode; -import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchTiming; -import org.hibernate.engine.internal.JoinHelper; import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.OneToOne; import org.hibernate.mapping.ToOne; +import org.hibernate.metamodel.mapping.Association; import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.entity.EntityPersister; @@ -32,7 +34,6 @@ import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAliasStemHelper; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.from.StandardTableGroup; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -44,18 +45,21 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.entity.EntityFetch; +import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; import org.hibernate.sql.results.graph.entity.internal.EntityFetchDelayedImpl; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; import org.hibernate.sql.results.internal.domain.BiDirectionalFetchImpl; +import org.hibernate.type.ForeignKeyDirection; /** * @author Steve Ebersole */ public class SingularAssociationAttributeMapping extends AbstractSingularAttributeMapping - implements EntityValuedFetchable, EntityAssociationMapping, TableGroupJoinProducer { + implements EntityValuedFetchable, EntityAssociationMapping, Association, TableGroupJoinProducer { public enum Cardinality { ONE_TO_ONE, @@ -75,6 +79,11 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu private final Cardinality cardinality; private ForeignKeyDescriptor foreignKeyDescriptor; + private String identifyingColumnsTableExpression; + private String inverseIdentifyingColumnsTableExpression; + private String[] identifyingColumns; + + public SingularAssociationAttributeMapping( @@ -127,6 +136,32 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu public void setForeignKeyDescriptor(ForeignKeyDescriptor foreignKeyDescriptor) { this.foreignKeyDescriptor = foreignKeyDescriptor; + + final String identifyingColumnsTableExpression; + final String inverseColumnsTableExpression; + final List identifyingColumnsList = new ArrayList<>(); + if ( foreignKeyDescriptor.getDirection() == ForeignKeyDirection.TO_PARENT ) { + identifyingColumnsTableExpression = foreignKeyDescriptor.getTargetTableExpression(); + inverseColumnsTableExpression = foreignKeyDescriptor.getReferringTableExpression(); + foreignKeyDescriptor.visitTargetColumns( + (containingTableExpression, columnExpression, jdbcMapping) -> { + identifyingColumnsList.add( containingTableExpression + "." + columnExpression ); + } + ); + } + else { + identifyingColumnsTableExpression = foreignKeyDescriptor.getReferringTableExpression(); + inverseColumnsTableExpression = foreignKeyDescriptor.getTargetTableExpression(); + foreignKeyDescriptor.visitReferringColumns( + (containingTableExpression, columnExpression, jdbcMapping) -> { + identifyingColumnsList.add( containingTableExpression + "." + columnExpression ); + } + ); + } + + this.identifyingColumns = identifyingColumnsList.toArray( new String[0] ); + this.identifyingColumnsTableExpression = identifyingColumnsTableExpression; + this.inverseIdentifyingColumnsTableExpression = inverseColumnsTableExpression; } public ForeignKeyDescriptor getForeignKeyDescriptor() { @@ -152,123 +187,70 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu return navigableRole; } + @Override + public String[] getIdentifyingColumnExpressions() { + return identifyingColumns; + } + @Override public Fetch resolveCircularFetch( NavigablePath fetchablePath, FetchParent fetchParent, - SqlAstProcessingState creationState) { - // given a typical Order/LineItem model and a query like: - // select o - // from Order o - // join fetch o.lineItems l - // join fetch l.order - // - // - note : Order has a collection of LineItems which is "mapped by" LineItem#order - // - // the join-fetch for `l.order` ought point back to `o`. - // - // `o` -> Order(o) - // `l` -> Order(o).lineItems(l).{element} - // `l.order` -> Order(o).lineItems(l).{element}.order - // - // both `Order(o)` and `Order(o).lineItems(l).order` have the same identifying columns, so we know - // they are circular. So how do we resolve the columns? ... - // - // see `org.hibernate.loader.JoinWalker.isDuplicateAssociation(java.lang.String, java.lang.String[], org.hibernate.type.AssociationType)` in - // previous versions of Hibernate - // - // For `l.order` we are in SingularAssociationAttributeMapping as the Fetchable, so we have access to the FK descriptor. - // For `o` (the potential circular target reference) we need to locate the - // - // - // where `owner` is the "owner" (in the mapped-by sense) of the association. In other words it is a - // bi-directional mapping. - // - // This call is trying to generate a fetch for the NavigablePath `Person(p).address`. - // What we need to determine is whether owner is the same as address's container. This might include - // multiple parent-paths which we need to walk up to find the container (an entity of collection) + DomainResultCreationState creationState) { + // NOTE - a circular fetch reference ultimately needs 2 pieces of information: + // 1) The NavigablePath that is circular (`fetchablePath`) + // 2) The NavigablePath to the entity-valued-reference that is the "other side" of the circularity + final ModelPart parentModelPart = fetchParent.getReferencedModePart(); - final NavigablePath pathToParent = fetchParent.getNavigablePath(); - final NavigablePath pathToParentParent = pathToParent.getParent(); - - // pathToParent : org.hibernate.orm.test.annotations.embedded.EmbeddedCircularFetchTests$RootEntity(r).intermediateComponent.leaves.{element} - // pathToParentParent : org.hibernate.orm.test.annotations.embedded.EmbeddedCircularFetchTests$RootEntity(r).intermediateComponent.leaves - - // attributeName : rootEntity - // referencedPropertyName : null - - if ( pathToParentParent == null ) { + if ( ! Fetchable.class.isInstance( parentModelPart ) ) { + // the `fetchParent` would have to be a Fetch as well for this to be circular... return null; } - final TableGroup parentParentTableGroup = creationState.getSqlAstCreationState() - .getFromClauseAccess() - .findTableGroup( pathToParentParent ); + final FetchParent associationParent = fetchParent.resolveContainingAssociationParent(); + assert associationParent.getReferencedModePart() instanceof Association; - parentParentTableGroup.getModelPart().findContainingEntityMapping() + final Association association = (Association) associationParent.getReferencedModePart(); - final ModelPartContainer parentParentPart = parentParentTableGroup.getModelPart(); - final ModelPart parentPart = parentParentPart.findSubPart( pathToParent.getLocalName(), null ); + if ( Arrays.equals( association.getIdentifyingColumnExpressions(), this.getIdentifyingColumnExpressions() ) ) { + // we need to determine the NavigablePath referring to the entity that the bi-dir + // fetch will "return" for its Assembler. so we walk "up" the FetchParent graph + // to find the "referenced entity" reference - if ( ! parentPart.equals( fetchParent.getReferencedModePart() ) ) { - throw new AssertionError( ); - } + final EntityResultGraphNode referencedEntityReference = resolveEntityGraphNode( fetchParent ); - - - final EntityMappingType containingEntityMapping = findContainingEntityMapping(); - - // find the key-columns for the `parentParentTableGroup` and see if they match the fk-target - switch ( cardinality ) { - case ONE_TO_ONE: - case LOGICAL_ONE_TO_ONE: { - if ( ! EntityValuedModelPart.class.isInstance( parentPart ) ) { - throw new IllegalStateException( - "Parent part [" + pathToParent + "] did not refer to a `EntityValuedModelPart` - " + parentPart - ); - } - final EntityValuedModelPart entityValuedParentPart = (EntityValuedModelPart) parentPart; - - throw new NotYetImplementedFor6Exception( getClass() ); - } - case MANY_TO_ONE: { - - } - default: { - throw new UnsupportedOperationException( "Unknown to-one singular attribute cardinality - " + cardinality.name() ); - } - } - if ( parentPart instanceof EntityCollectionPart ) { - final EntityCollectionPart entityCollectionPart = (EntityCollectionPart) parentPart; - final String mappedBy = entityCollectionPart.getMappedBy(); - if ( mappedBy.equals( getAttributeName() ) ) { - return new BiDirectionalFetchImpl( - FetchTiming.IMMEDIATE, - fetchablePath, - fetchParent, - this, - fetchParent.getNavigablePath().getParent() + if ( referencedEntityReference == null ) { + throw new HibernateException( + "Could not locate entity-valued reference for circular path `" + fetchablePath + "`" ); } + + return new BiDirectionalFetchImpl( + FetchTiming.IMMEDIATE, + fetchablePath, + fetchParent, + this, + referencedEntityReference.getNavigablePath() + ); } - else if ( parentPart instanceof EntityAssociationMapping ) { - final EntityAssociationMapping entitySubPart = (EntityAssociationMapping) parentPart; - final boolean condition1 = pathToParent.getLocalName().equals( referencedPropertyName ) - && entitySubPart.getFetchableName().equals( referencedPropertyName ); - final boolean condition2 = entitySubPart.getKeyTargetMatchPart() != null - && entitySubPart.getKeyTargetMatchPart().getPartName().equals( getAttributeName() ); + return null; + } - if ( condition1 || condition2 ) { - return new BiDirectionalFetchImpl( - FetchTiming.IMMEDIATE, - fetchablePath, - fetchParent, - this, - fetchParent.getNavigablePath().getParent() - ); + protected EntityResultGraphNode resolveEntityGraphNode(FetchParent fetchParent) { + FetchParent processingParent = fetchParent; + while ( processingParent != null ) { + if ( processingParent instanceof EntityResultGraphNode ) { + return (EntityResultGraphNode) processingParent; } + + if ( processingParent instanceof Fetch ) { + processingParent = ( (Fetch) processingParent ).getFetchParent(); + continue; + } + + processingParent = null; } return null; @@ -387,6 +369,7 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu lockMode, primaryTableReference, sqlAliasBase, + (tableExpression) -> getEntityMappingType().containsTableReference( tableExpression ), (tableExpression, tg) -> getEntityMappingType().createTableReferenceJoin( tableExpression, sqlAliasBase, @@ -398,24 +381,23 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu creationContext.getSessionFactory() ); + final TableReference lhsTableReference = lhs.resolveTableReference( identifyingColumnsTableExpression ); + final TableGroupJoin tableGroupJoin = new TableGroupJoin( navigablePath, sqlAstJoinType, tableGroup, - null + foreignKeyDescriptor.generateJoinPredicate( + lhsTableReference, + primaryTableReference, + sqlAstJoinType, + sqlExpressionResolver, + creationContext + ) ); lhs.addTableGroupJoin( tableGroupJoin ); - final Predicate predicate = foreignKeyDescriptor.generateJoinPredicate( - lhs, - tableGroup, - sqlAstJoinType, - sqlExpressionResolver, - creationContext - ); - tableGroupJoin.applyPredicate( predicate ); - return tableGroupJoin; } 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 1952d911c3..59b309253e 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 @@ -1194,6 +1194,21 @@ public abstract class AbstractEntityPersister return sqlAliasStem; } + @Override + public boolean containsTableReference(String tableExpression) { + if ( getRootTableName().equals( tableExpression ) ) { + return true; + } + + for ( int i = 0; i < getSubclassTableSpan(); i++ ) { + if ( getSubclassTableName( i ).equals( tableExpression ) ) { + return true; + } + } + + return false; + } + @Override public String getPartName() { return getEntityName(); @@ -1251,6 +1266,7 @@ public abstract class AbstractEntityPersister lockMode, primaryTableReference, sqlAliasBase, + (tableExpression) -> ArrayHelper.contains( rootTableKeyColumnNames, tableExpression ), (tableExpression, tableGroup) -> { for ( int i = 0; i < getSubclassTableSpan(); i++ ) { final String subclassTableName = getSubclassTableName( i ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java index f01f7b66b7..b1cf604b5c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.mutation.internal; +import java.util.Collections; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -40,6 +41,8 @@ import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.update.Assignable; import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.FetchParent; /** * Specialized BaseSqmToSqlAstConverter implementation used during conversion @@ -211,6 +214,11 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter imp return this; } + @Override + public List visitFetches(FetchParent fetchParent) { + return Collections.emptyList(); + } + public void visitSelectClause( SqmSelectClause sqmSelectClause, QuerySpec sqlQuerySpec, 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 506f1e663c..3c0321f7e0 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 @@ -7,7 +7,6 @@ package org.hibernate.query.sqm.sql; import java.util.ArrayList; -import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -15,25 +14,29 @@ import java.util.function.Consumer; import java.util.function.Supplier; import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.dialect.function.TimestampaddFunction; import org.hibernate.dialect.function.TimestampdiffFunction; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; -import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.BinaryArithmeticOperator; import org.hibernate.query.NavigablePath; import org.hibernate.query.SemanticException; import org.hibernate.query.TemporalUnit; +import org.hibernate.query.QueryLogger; import org.hibernate.query.UnaryArithmeticOperator; import org.hibernate.query.internal.QueryHelper; import org.hibernate.query.spi.QueryOptions; @@ -139,6 +142,7 @@ import org.hibernate.sql.ast.tree.expression.DurationUnit; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.ExtractUnit; import org.hibernate.sql.ast.tree.expression.Format; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.sql.ast.tree.expression.Star; import org.hibernate.sql.ast.tree.expression.TrimSpecification; @@ -162,10 +166,7 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SortSpecification; import org.hibernate.sql.exec.internal.JdbcParameterImpl; import org.hibernate.sql.exec.internal.JdbcParametersImpl; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.exec.spi.JdbcParameters; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.type.spi.TypeConfiguration; import org.jboss.logging.Logger; @@ -282,11 +283,6 @@ public abstract class BaseSqmToSqlAstConverter return queryOptions.getLockOptions().getEffectiveLockMode( identificationVariable ); } - @Override - public List visitFetches(FetchParent fetchParent) { - return Collections.emptyList(); - } - public QueryOptions getQueryOptions() { return queryOptions; } @@ -579,34 +575,131 @@ public abstract class BaseSqmToSqlAstConverter final SqmPathSource pathSource = sqmJoin.getReferencedPathSource(); - final AttributeMapping attributeMapping = (AttributeMapping) lhsTableGroup.getModelPart().findSubPart( - pathSource.getPathName(), - SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this ) - ); + final TableGroupJoin joinedTableGroupJoin; + final TableGroup joinedTableGroup; - assert attributeMapping instanceof TableGroupJoinProducer; - final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) attributeMapping ).createTableGroupJoin( - sqmJoin.getNavigablePath(), - lhsTableGroup, - sqmJoin.getExplicitAlias(), - sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(), - determineLockMode( sqmJoin.getExplicitAlias() ), - sqlAliasBaseManager, getSqlExpressionResolver(), - creationContext - ); + if ( pathSource instanceof PluralPersistentAttribute ) { + final ModelPart pluralPart = lhsTableGroup.getModelPart().findSubPart( + sqmJoin.getReferencedPathSource().getPathName(), + SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this ) + ); - lhsTableGroup.addTableGroupJoin( tableGroupJoin ); - fromClauseIndex.register( sqmJoin, tableGroupJoin.getJoinedGroup() ); + assert pluralPart instanceof PluralAttributeMapping; + + final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) pluralPart; + + final NavigablePath elementPath = sqmJoin.getNavigablePath().append( CollectionPart.Nature.ELEMENT.getName() ); + + joinedTableGroupJoin = pluralAttributeMapping.createTableGroupJoin( + elementPath, + lhsTableGroup, + sqmJoin.getExplicitAlias(), + sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(), + determineLockMode( sqmJoin.getExplicitAlias() ), + sqlAliasBaseManager, getSqlExpressionResolver(), + creationContext + ); + joinedTableGroup = joinedTableGroupJoin.getJoinedGroup(); + + lhsTableGroup.addTableGroupJoin( joinedTableGroupJoin ); + + fromClauseIndex.register( sqmJoin, joinedTableGroup ); + fromClauseIndex.registerTableGroup( elementPath, joinedTableGroup ); + } + else if ( pathSource instanceof EmbeddedSqmPathSource ) { + final ModelPart joinedPart = lhsTableGroup.getModelPart().findSubPart( + pathSource.getPathName(), + SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this ) + ); + + assert joinedPart instanceof TableGroupJoinProducer; + + final NavigablePath joinedPath; + final String explicitAlias = sqmJoin.getExplicitAlias(); + if ( explicitAlias == null ) { + joinedPath = sqmJoin.getNavigablePath(); + } + else { + joinedPath = sqmJoin.getNavigablePath().getParent().append( sqmJoin.getAttribute().getName() ); + } + joinedTableGroupJoin = ( (TableGroupJoinProducer) joinedPart ).createTableGroupJoin( + joinedPath, + lhsTableGroup, + sqmJoin.getExplicitAlias(), + sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(), + determineLockMode( sqmJoin.getExplicitAlias() ), + sqlAliasBaseManager, getSqlExpressionResolver(), + creationContext + ); + + joinedTableGroup = joinedTableGroupJoin.getJoinedGroup(); + + lhsTableGroup.addTableGroupJoin( joinedTableGroupJoin ); + + fromClauseIndex.register( sqmJoin, joinedTableGroup ); + } + else { + if ( lhsTableGroup.getModelPart() instanceof PluralAttributeMapping ) { + fromClauseIndex.register( sqmJoin, lhsTableGroup ); + + joinedTableGroupJoin = null; + joinedTableGroup = lhsTableGroup; + } + else { + final ModelPart joinedPart = lhsTableGroup.getModelPart().findSubPart( + pathSource.getPathName(), + SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this ) + ); + + if ( ! TableGroupJoinProducer.class.isInstance( joinedPart ) ) { + throw new HibernateException( "Expecting joined model part to implement TableGroupJoinProducer - " + joinedPart ); + } + + final NavigablePath joinedPath; + final String explicitAlias = sqmJoin.getExplicitAlias(); + if ( explicitAlias == null ) { + joinedPath = sqmJoin.getNavigablePath(); + } + else { + joinedPath = sqmJoin.getNavigablePath().getParent().append( sqmJoin.getAttribute().getName() ); + } + + joinedTableGroupJoin = ( (TableGroupJoinProducer) joinedPart ).createTableGroupJoin( + joinedPath, + lhsTableGroup, + sqmJoin.getExplicitAlias(), + sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(), + determineLockMode( sqmJoin.getExplicitAlias() ), + sqlAliasBaseManager, + getSqlExpressionResolver(), + creationContext + ); + + joinedTableGroup = joinedTableGroupJoin.getJoinedGroup(); + + lhsTableGroup.addTableGroupJoin( joinedTableGroupJoin ); + + fromClauseIndex.register( sqmJoin, joinedTableGroup ); + } + } // add any additional join restrictions if ( sqmJoin.getJoinPredicate() != null ) { - tableGroupJoin.applyPredicate( + if ( sqmJoin.isFetched() ) { + QueryLogger.QUERY_LOGGER.debugf( "Join fetch [" + sqmJoin.getNavigablePath() + "] is restricted" ); + } + + if ( joinedTableGroupJoin == null ) { + throw new IllegalStateException( ); + } + + joinedTableGroupJoin.applyPredicate( (Predicate) sqmJoin.getJoinPredicate().accept( this ) ); } - consumeExplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); - consumeImplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); + consumeExplicitJoins( sqmJoin, joinedTableGroup ); + consumeImplicitJoins( sqmJoin, joinedTableGroup ); } private void consumeCrossJoin(SqmCrossJoin sqmJoin, TableGroup lhsTableGroup) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java index 7358bee1f7..0b0ba281f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java @@ -26,6 +26,7 @@ import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.metamodel.model.domain.EntityDomainType; @@ -221,6 +222,12 @@ public class StandardSqmSelectTranslator return (DomainResultProducer) sqmSelection.getSelectableNode().accept( this ); } + @Override + public ModelPart resolveModelPart(NavigablePath navigablePath) { + // again, assume that the path refers to a TableGroup + return getFromClauseIndex().findTableGroup( navigablePath ).getModelPart(); + } + private int fetchDepth = 0; @Override @@ -236,7 +243,7 @@ public class StandardSqmSelectTranslator final Fetch biDirectionalFetch = fetchable.resolveCircularFetch( fetchablePath, fetchParent, - getSqlAstCreationState().getCurrentProcessingState() + StandardSqmSelectTranslator.this ); if ( biDirectionalFetch != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationState.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationState.java index 3095798aa6..2470c0be8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationState.java @@ -6,12 +6,7 @@ */ package org.hibernate.sql.ast.spi; -import java.util.List; - import org.hibernate.LockMode; -import org.hibernate.metamodel.mapping.ordering.OrderByFragment; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.FetchParent; /** * Access to stuff used while creating a SQL AST @@ -30,12 +25,4 @@ public interface SqlAstCreationState { SqlAliasBaseGenerator getSqlAliasBaseGenerator(); LockMode determineLockMode(String identificationVariable); - - /** - * Visit fetches for the given parent. - * - * We walk fetches via the SqlAstCreationContext because each "context" - * will define differently what should be fetched (HQL versus load) - */ - List visitFetches(FetchParent fetchParent); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java index 90f6433f20..f227b4a331 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.List; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Predicate; import org.hibernate.LockMode; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -22,6 +23,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBase; */ public class StandardTableGroup extends AbstractTableGroup { private final TableReference primaryTableReference; + private final Predicate tableReferenceJoinNameChecker; private final BiFunction tableReferenceJoinCreator; private List tableJoins; @@ -38,6 +40,14 @@ public class StandardTableGroup extends AbstractTableGroup { this.primaryTableReference = primaryTableReference; this.tableJoins = tableJoins; this.tableReferenceJoinCreator = null; + this.tableReferenceJoinNameChecker = s -> { + for ( int i = 0; i < tableJoins.size(); i++ ) { + if ( tableJoins.get( i ).getJoinedTableReference().getTableExpression().equals( s ) ) { + return true; + } + } + return false; + }; } public StandardTableGroup( @@ -46,11 +56,13 @@ public class StandardTableGroup extends AbstractTableGroup { LockMode lockMode, TableReference primaryTableReference, SqlAliasBase sqlAliasBase, + Predicate tableReferenceJoinNameChecker, BiFunction tableReferenceJoinCreator, SessionFactoryImplementor sessionFactory) { super( navigablePath, tableGroupProducer, lockMode, sqlAliasBase, sessionFactory ); this.primaryTableReference = primaryTableReference; this.tableJoins = null; + this.tableReferenceJoinNameChecker = tableReferenceJoinNameChecker; this.tableReferenceJoinCreator = tableReferenceJoinCreator; } @@ -88,24 +100,28 @@ public class StandardTableGroup extends AbstractTableGroup { return tableReference; } - if ( tableJoins != null ) { - for ( int i = 0; i < tableJoins.size(); i++ ) { - final TableReferenceJoin join = tableJoins.get( i ); - assert join != null; - if ( join.getJoinedTableReference().getTableExpression().equals( tableExpression ) ) { - return join.getJoinedTableReference(); + if ( tableReferenceJoinNameChecker.test( tableExpression ) ) { + if ( tableJoins != null ) { + for ( int i = 0; i < tableJoins.size(); i++ ) { + final TableReferenceJoin join = tableJoins.get( i ); + assert join != null; + if ( join.getJoinedTableReference().getTableExpression().equals( tableExpression ) ) { + return join.getJoinedTableReference(); + } } } + + return potentiallyCreateTableReference( tableExpression ); } - for ( TableGroupJoin tableGroupJoin : getTableGroupJoins() ) { - final TableReference primaryTableReference = tableGroupJoin.getJoinedGroup().getPrimaryTableReference(); - if ( primaryTableReference.getTableExpression().equals( tableExpression ) ) { - return primaryTableReference; - } - } +// for ( TableGroupJoin tableGroupJoin : getTableGroupJoins() ) { +// final TableReference primaryTableReference = tableGroupJoin.getJoinedGroup().getPrimaryTableReference(); +// if ( primaryTableReference.getTableExpression().equals( tableExpression ) ) { +// return primaryTableReference; +// } +// } - return potentiallyCreateTableReference( tableExpression ); + return null; } @SuppressWarnings("WeakerAccess") diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupProducer.java index 59662a4db7..d536f38d0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupProducer.java @@ -26,4 +26,8 @@ public interface TableGroupProducer extends ModelPartContainer { * @see SqlAliasBaseManager#createSqlAliasBase */ String getSqlAliasStem(); + + default boolean containsTableReference(String tableExpression) { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java index 780cc85347..6e14ca5268 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java @@ -9,6 +9,9 @@ package org.hibernate.sql.results.graph; import java.util.List; import org.hibernate.LockMode; +import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.jdbc.spi.JdbcValues; import org.hibernate.sql.ast.spi.SqlAliasBaseManager; import org.hibernate.sql.ast.spi.SqlAstCreationState; @@ -24,8 +27,21 @@ public interface DomainResultCreationState { } /** + * Resolve the ModelPart associated with a given NavigablePath. More specific ModelParts should be preferred - e.g. + * the SingularAssociationAttributeMapping rather than just the EntityTypeMapping for the associated type + */ + default ModelPart resolveModelPart(NavigablePath navigablePath) { + throw new NotYetImplementedFor6Exception( getClass() ); + } + + /** + * Visit fetches for the given parent. + * + * We walk fetches via the SqlAstCreationContext because each "context" + * will define differently what should be fetched (HQL versus load) + * * todo (6.0) : centralize the implementation of this - * most of the logic in the impls of this is identical. variations (arguments) include: + * most of the logic in the impls of this is identical. variations include: * 1) given a Fetchable, determine the FetchTiming and `selected`[1]. Tricky as functional * interface because of the "composite return". * 2) given a Fetchable, determine the LockMode - currently not handled very well here; should consult `#getLockOptions` @@ -47,8 +63,6 @@ public interface DomainResultCreationState { * todo (6.0) : wrt the "trickiness" of `selected[1]`, that may no longer be an issue given how TableGroups * are built/accessed. Comes down to how we'd know whether to join fetch or select fetch. Simply pass * along FetchStyle? - * - * */ List visitFetches(FetchParent fetchParent); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParent.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParent.java index 6a9a775af4..2a650f591d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParent.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParent.java @@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph; import java.util.List; +import org.hibernate.metamodel.mapping.Association; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.NavigablePath; @@ -27,6 +28,17 @@ public interface FetchParent extends DomainResultGraphNode { */ FetchableContainer getReferencedMappingType(); + default FetchParent resolveContainingAssociationParent() { + final ModelPart referencedModePart = getReferencedModePart(); + if ( referencedModePart instanceof Association ) { + return this; + } + if ( this instanceof Fetch ) { + ( (Fetch) this ).getFetchParent().resolveContainingAssociationParent(); + } + return null; + } + /** * Whereas {@link #getReferencedMappingContainer} and {@link #getReferencedMappingType} return the * referenced container type, this method returns the referenced part. diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/Fetchable.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/Fetchable.java index b52b1ade0b..35b638a735 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/Fetchable.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/Fetchable.java @@ -9,10 +9,8 @@ package org.hibernate.sql.results.graph; import org.hibernate.LockMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchTiming; -import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.NavigablePath; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; /** * @author Steve Ebersole @@ -27,16 +25,10 @@ public interface Fetchable extends ModelPart { // per Fetch generation is performance drain. Would be better to // simply pass these 2 pieces of information - /** - * For an association, this would return the foreign-key's "referring columns". Would target - * the columns defined by {@link EntityValuedModelPart#getIdentifyingColumnExpressions} - */ - String[] getIdentifyingColumnExpressions(); - default Fetch resolveCircularFetch( NavigablePath fetchablePath, FetchParent fetchParent, - SqlAstProcessingState creationState) { + DomainResultCreationState creationState) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/BiDirectionalFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/BiDirectionalFetchImpl.java index e62067419a..8f86233d39 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/BiDirectionalFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/BiDirectionalFetchImpl.java @@ -12,10 +12,11 @@ import org.hibernate.LockMode; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.Association; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; @@ -27,8 +28,6 @@ import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.Initializer; -import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; -import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; @@ -37,7 +36,7 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * @author Andrea Boriero */ -public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable { +public class BiDirectionalFetchImpl implements BiDirectionalFetch, Association { private final FetchTiming timing; private final NavigablePath navigablePath; private final Fetchable fetchable; @@ -144,6 +143,18 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable { throw new UnsupportedOperationException(); } + @Override + public ForeignKeyDescriptor getForeignKeyDescriptor() { + return ( (Association) fetchParent ).getForeignKeyDescriptor(); + } + + @Override + public String[] getIdentifyingColumnExpressions() { + // fetch parent really always needs to be an Association, so we simply cast here + // should maybe verify this in ctor + return ( (Association) fetchParent ).getIdentifyingColumnExpressions(); + } + @Override public Fetch generateFetch( FetchParent fetchParent, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embedded/EmbeddedCircularFetchTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embedded/EmbeddedCircularFetchTests.java index 9640eaeaa9..10e53ab08a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embedded/EmbeddedCircularFetchTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embedded/EmbeddedCircularFetchTests.java @@ -154,7 +154,10 @@ public class EmbeddedCircularFetchTests { session -> { session.getSessionFactory().getStatistics().clear(); final RootEntity result = session.createQuery( - "from RootEntity r join fetch r.intermediateComponent.leaves", +// "from RootEntity r join fetch r.intermediateComponent.leaves", + "from RootEntity r " + + "join fetch r.intermediateComponent.leaves l " + + "join fetch l.rootEntity", RootEntity.class ).uniqueResult(); assertTrue( Hibernate.isInitialized( result.getIntermediateComponent().getLeaves() ) );