continued work on circular fetch detection. still need to work through the cases involving embeddables "in between"
This commit is contained in:
parent
00b5a700eb
commit
3222b52ab2
|
@ -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.
|
||||
and handle these conditions uniformly.
|
||||
|
||||
|
||||
== with embeddables
|
||||
|
||||
```
|
||||
@Entity
|
||||
@Table(name="root")
|
||||
class RootEntity {
|
||||
...
|
||||
|
||||
@Embedded
|
||||
IntermediateComponent intermediateComponent;
|
||||
}
|
||||
|
||||
@Embeddable
|
||||
class IntermediateComponent {
|
||||
...
|
||||
|
||||
@OneToMany( mappedBy = "rootEntity" )
|
||||
Set<LeafEntity> 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<LineItem> lineItems;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
||||
------------
|
||||
"orders"
|
||||
------------
|
||||
id INTEGER
|
||||
name VARCHAR
|
||||
|
||||
------------
|
||||
"order_items"
|
||||
------------
|
||||
orders_id
|
||||
items_id
|
||||
|
||||
------------
|
||||
"items"
|
||||
------------
|
||||
id
|
||||
qty
|
||||
|
||||
```
|
|
@ -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<Fetch> fetches = new ArrayList<>();
|
||||
|
||||
final Consumer<Fetchable> 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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -37,8 +37,6 @@ public interface EntityValuedModelPart extends FetchableContainer {
|
|||
getEntityMappingType().visitSubParts( consumer, targetType );
|
||||
}
|
||||
|
||||
String[] getIdentifyingColumnExpressions();
|
||||
|
||||
@Override
|
||||
default <T> DomainResult<T> createDomainResult(
|
||||
NavigablePath navigablePath,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<TableGroup> tableGroupFinalizer;
|
||||
final BiFunction<String,TableGroup,TableReferenceJoin> tableReferenceJoinCreator;
|
||||
final java.util.function.Predicate<String> 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()
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<String> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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<Fetch> visitFetches(FetchParent fetchParent) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public void visitSelectClause(
|
||||
SqmSelectClause sqmSelectClause,
|
||||
QuerySpec sqlQuerySpec,
|
||||
|
|
|
@ -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<Fetch> 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) {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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<Fetch> visitFetches(FetchParent fetchParent);
|
||||
}
|
||||
|
|
|
@ -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<String> tableReferenceJoinNameChecker;
|
||||
private final BiFunction<String,TableGroup,TableReferenceJoin> tableReferenceJoinCreator;
|
||||
|
||||
private List<TableReferenceJoin> 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<String> tableReferenceJoinNameChecker,
|
||||
BiFunction<String,TableGroup,TableReferenceJoin> 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")
|
||||
|
|
|
@ -26,4 +26,8 @@ public interface TableGroupProducer extends ModelPartContainer {
|
|||
* @see SqlAliasBaseManager#createSqlAliasBase
|
||||
*/
|
||||
String getSqlAliasStem();
|
||||
|
||||
default boolean containsTableReference(String tableExpression) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Fetch> visitFetches(FetchParent fetchParent);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() ) );
|
||||
|
|
Loading…
Reference in New Issue