From 233a3b176cd4cf328fe467518b29ea5cf6e99f83 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Sun, 22 Sep 2019 10:32:41 -0500 Subject: [PATCH] initial working "unqualified" entity join support in criteria --- .../spi/SessionFactoryDelegatingImpl.java | 4 +- .../engine/spi/SessionFactoryImplementor.java | 4 +- .../query/criteria/JpaEntityJoin.java | 17 + .../org/hibernate/query/criteria/JpaJoin.java | 2 +- .../query/criteria/JpaJoinedFrom.java | 18 ++ .../org/hibernate/query/criteria/JpaRoot.java | 4 + .../query/sqm/InterpretationException.java | 4 + .../sqm/internal/SqmMappingModelHelper.java | 14 + .../sqm/sql/BaseSqmToSqlAstConverter.java | 293 +++++++++++------- .../internal/SqmSelectToSqlAstConverter.java | 1 + .../query/sqm/tree/from/SqmEntityJoin.java | 18 +- .../query/sqm/tree/from/SqmRoot.java | 16 + .../sql/ast/tree/from/TableGroupJoin.java | 15 +- .../ast/tree/from/TableGroupJoinProducer.java | 2 + .../criteria/BasicCriteriaExecutionTests.java | 21 ++ 15 files changed, 307 insertions(+), 126 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/query/criteria/JpaEntityJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoinedFrom.java diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java index 853cef803f..e78b4f9532 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java @@ -49,7 +49,9 @@ import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.type.Type; @@ -409,7 +411,7 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor, } @Override - public CriteriaBuilder getCriteriaBuilder() { + public HibernateCriteriaBuilder getCriteriaBuilder() { return delegate.getCriteriaBuilder(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java index 133e6e00bd..a50c2e53cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.EntityGraph; -import javax.persistence.criteria.CriteriaBuilder; import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.EntityNameResolver; @@ -37,6 +36,7 @@ import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryParameterBindingTypeResolver; import org.hibernate.query.sqm.spi.SqmCreationContext; @@ -88,7 +88,7 @@ public interface SessionFactoryImplementor QueryEngine getQueryEngine(); @Override - CriteriaBuilder getCriteriaBuilder(); + HibernateCriteriaBuilder getCriteriaBuilder(); @Override SessionBuilderImplementor withOptions(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaEntityJoin.java new file mode 100644 index 0000000000..1338cfc1f7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaEntityJoin.java @@ -0,0 +1,17 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.criteria; + +import org.hibernate.metamodel.model.domain.EntityDomainType; + +/** + * @author Steve Ebersole + */ +public interface JpaEntityJoin extends JpaJoinedFrom { + @Override + EntityDomainType getModel(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoin.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoin.java index b25a45725e..9533dd6256 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoin.java @@ -19,7 +19,7 @@ import org.hibernate.metamodel.model.domain.PersistentAttribute; * * @author Steve Ebersole */ -public interface JpaJoin extends JpaFrom, Join { +public interface JpaJoin extends JpaJoinedFrom, Join { @Override PersistentAttribute getAttribute(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoinedFrom.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoinedFrom.java new file mode 100644 index 0000000000..a2dfa8a932 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoinedFrom.java @@ -0,0 +1,18 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.criteria; + +/** + * Exists within the hierarchy mainly to support "entity joins". + * + * @see JpaEntityJoin + * @see org.hibernate.query.sqm.tree.from.SqmEntityJoin + * + * @author Steve Ebersole + */ +public interface JpaJoinedFrom extends JpaFrom { +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaRoot.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaRoot.java index 1e878e659c..5d4198eb6e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaRoot.java @@ -18,4 +18,8 @@ public interface JpaRoot extends JpaFrom, Root { EntityDomainType getModel(); EntityDomainType getManagedType(); + + JpaEntityJoin join(Class entityJavaType); + + JpaEntityJoin join(EntityDomainType entity); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/InterpretationException.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/InterpretationException.java index d60b118a30..a1fb4ad706 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/InterpretationException.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/InterpretationException.java @@ -13,6 +13,10 @@ package org.hibernate.query.sqm; * @author Steve Ebersole */ public class InterpretationException extends RuntimeException { + public InterpretationException(String query) { + this( query, null ); + } + public InterpretationException(String query, Throwable cause) { super( "Error interpreting query [" + query + "]; this may indicate a semantic (user query) problem or a bug in the parser", diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmMappingModelHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmMappingModelHelper.java index cc65ae26a5..ff1f944a9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmMappingModelHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmMappingModelHelper.java @@ -10,6 +10,7 @@ import javax.persistence.metamodel.Bindable; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.Queryable; @@ -29,7 +30,9 @@ import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.SqmExpressable; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SqmTreeTransformationLogger; +import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqlAstCreationState; +import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; @@ -166,4 +169,15 @@ public class SqmMappingModelHelper { return tableGroup; } + + public static EntityMappingType resolveExplicitTreatTarget( + SqmPath sqmPath, + SqmToSqlAstConverter converter) { + if ( sqmPath instanceof SqmTreatedPath ) { + final SqmTreatedPath treatedPath = (SqmTreatedPath) sqmPath; + return resolveEntityPersister( treatedPath.getTreatTarget(), converter.getCreationContext().getSessionFactory() ); + } + + return null; + } } 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 9a2375ff15..021d280912 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 @@ -20,9 +20,9 @@ import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.LoadQueryInfluencers; 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.MappingModelExpressable; -import org.hibernate.metamodel.model.domain.EmbeddableDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.BinaryArithmeticOperator; @@ -31,6 +31,7 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.InterpretationException; import org.hibernate.query.sqm.SqmExpressable; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.function.SqmFunction; @@ -67,6 +68,7 @@ import org.hibernate.query.sqm.tree.from.SqmCrossJoin; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmFromClause; +import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; import org.hibernate.query.sqm.tree.predicate.SqmAndPredicate; @@ -407,14 +409,7 @@ public abstract class BaseSqmToSqlAstConverter currentClauseStack.push( Clause.FROM ); try { - sqmFromClause.visitRoots( - sqmRoot -> { - final TableGroup rootTableGroup = visitRootPath( sqmRoot ); - - currentQuerySpec().getFromClause().addRoot( rootTableGroup ); - getFromClauseIndex().register( sqmRoot, rootTableGroup ); - } - ); + sqmFromClause.visitRoots( this::consumeFromClauseRoot ); } finally { currentClauseStack.pop(); @@ -423,16 +418,11 @@ public abstract class BaseSqmToSqlAstConverter return null; } + @SuppressWarnings("WeakerAccess") + protected void consumeFromClauseRoot(SqmRoot sqmRoot) { + log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot ); - @Override - public TableGroup visitRootPath(SqmRoot sqmRoot) { - log.tracef( "Starting resolution of SqmRoot [%s] to TableGroup", sqmRoot ); - - final TableGroup resolvedTableGroup = fromClauseIndex.findTableGroup( sqmRoot.getNavigablePath() ); - if ( resolvedTableGroup != null ) { - log.tracef( "SqmRoot [%s] resolved to existing TableGroup [%s]", sqmRoot, resolvedTableGroup ); - return resolvedTableGroup; - } + assert ! fromClauseIndex.isResolved( sqmRoot ); final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() ); @@ -446,108 +436,180 @@ public abstract class BaseSqmToSqlAstConverter creationContext ); - - fromClauseIndex.register( sqmRoot, tableGroup ); - log.tracef( "Resolved SqmRoot [%s] to new TableGroup [%s]", sqmRoot, tableGroup ); - visitExplicitJoins( sqmRoot, tableGroup ); - visitImplicitJoins( sqmRoot, tableGroup ); + fromClauseIndex.register( sqmRoot, tableGroup ); + currentQuerySpec().getFromClause().addRoot( tableGroup ); - return tableGroup; + consumeExplicitJoins( sqmRoot, tableGroup ); + consumeImplicitJoins( sqmRoot, tableGroup ); } private EntityPersister resolveEntityPersister(EntityDomainType entityDomainType) { return creationContext.getDomainModel().getEntityDescriptor( entityDomainType.getHibernateEntityName() ); } - private void visitExplicitJoins(SqmFrom sqmFrom, TableGroup tableGroup) { + protected void consumeExplicitJoins(SqmFrom sqmFrom, TableGroup lhsTableGroup) { log.tracef( "Visiting explicit joins for `%s`", sqmFrom.getNavigablePath() ); sqmFrom.visitSqmJoins( - sqmJoin -> { - final TableGroupJoin tableGroupJoin = (TableGroupJoin) sqmJoin.accept( this ); - if ( tableGroupJoin != null ) { - tableGroup.addTableGroupJoin( tableGroupJoin ); - getFromClauseIndex().register( sqmFrom, tableGroup ); - } - } + sqmJoin -> consumeExplicitJoin( sqmJoin, lhsTableGroup ) ); } - private void visitImplicitJoins(SqmPath sqmPath, TableGroup tableGroup) { + @SuppressWarnings("WeakerAccess") + protected void consumeExplicitJoin(SqmJoin sqmJoin, TableGroup lhsTableGroup) { + if ( sqmJoin instanceof SqmAttributeJoin ) { + consumeAttributeJoin( ( (SqmAttributeJoin) sqmJoin ), lhsTableGroup ); + } + else if ( sqmJoin instanceof SqmCrossJoin ) { + consumeCrossJoin( ( (SqmCrossJoin) sqmJoin ), lhsTableGroup ); + } + else if ( sqmJoin instanceof SqmEntityJoin ) { + consumeEntityJoin( ( (SqmEntityJoin) sqmJoin ), lhsTableGroup ); + } + else { + throw new InterpretationException( "Could not resolve SqmJoin [" + sqmJoin.getNavigablePath() + "] to TableGroupJoin" ); + } + } + + private void consumeAttributeJoin(SqmAttributeJoin sqmJoin, TableGroup lhsTableGroup) { + assert fromClauseIndex.findTableGroup( sqmJoin.getNavigablePath() ) == null; + assert fromClauseIndex.findTableGroupJoin( sqmJoin.getNavigablePath() ) == null; + + final SqmPathSource pathSource = sqmJoin.getReferencedPathSource(); + + final AttributeMapping attributeMapping = (AttributeMapping) lhsTableGroup.getModelPart().findSubPart( + pathSource.getPathName(), + SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this ) + ); + + assert attributeMapping instanceof TableGroupJoinProducer; + final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) attributeMapping ).createTableGroupJoin( + sqmJoin.getNavigablePath(), + lhsTableGroup, + sqmJoin.getExplicitAlias(), + sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(), + determineLockMode( sqmJoin.getExplicitAlias() ), + sqlAliasBaseManager, getSqlExpressionResolver(), + creationContext + ); + + fromClauseIndex.register( sqmJoin, tableGroupJoin ); + + lhsTableGroup.addTableGroupJoin( tableGroupJoin ); + + // add any additional join restrictions + if ( sqmJoin.getJoinPredicate() != null ) { + tableGroupJoin.applyPredicate( + (Predicate) sqmJoin.getJoinPredicate().accept( this ) + ); + } + + consumeExplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); + consumeImplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); + } + + private void consumeCrossJoin(SqmCrossJoin sqmJoin, TableGroup lhsTableGroup) { + final EntityPersister entityDescriptor = resolveEntityPersister( sqmJoin.getReferencedPathSource() ); + + final TableGroup tableGroup = entityDescriptor.createRootTableGroup( + sqmJoin.getNavigablePath(), + sqmJoin.getExplicitAlias(), + JoinType.CROSS, + determineLockMode( sqmJoin.getExplicitAlias() ), + sqlAliasBaseManager, + getSqlExpressionResolver(), + getCreationContext() + ); + + final TableGroupJoin tableGroupJoin = new TableGroupJoin( + sqmJoin.getNavigablePath(), + JoinType.CROSS, + tableGroup + ); + + lhsTableGroup.addTableGroupJoin( tableGroupJoin ); + + fromClauseIndex.register( sqmJoin, tableGroup ); + + consumeExplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); + consumeImplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); + } + + private void consumeEntityJoin(SqmEntityJoin sqmJoin, TableGroup lhsTableGroup) { + final EntityPersister entityDescriptor = resolveEntityPersister( sqmJoin.getReferencedPathSource() ); + + final TableGroup tableGroup = entityDescriptor.createRootTableGroup( + sqmJoin.getNavigablePath(), + sqmJoin.getExplicitAlias(), + sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(), + determineLockMode( sqmJoin.getExplicitAlias() ), + sqlAliasBaseManager, + getSqlExpressionResolver(), + getCreationContext() + ); + fromClauseIndex.register( sqmJoin, tableGroup ); + + final TableGroupJoin tableGroupJoin = new TableGroupJoin( + sqmJoin.getNavigablePath(), + sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(), + tableGroup, + null + ); + lhsTableGroup.addTableGroupJoin( tableGroupJoin ); + + // add any additional join restrictions + if ( sqmJoin.getJoinPredicate() != null ) { + tableGroupJoin.applyPredicate( + (Predicate) sqmJoin.getJoinPredicate().accept( this ) + ); + } + + consumeExplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); + consumeImplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); + } + + private void consumeImplicitJoins(SqmPath sqmPath, TableGroup tableGroup) { log.tracef( "Visiting implicit joins for `%s`", sqmPath.getNavigablePath() ); sqmPath.visitImplicitJoinPaths( joinedPath -> { log.tracef( "Starting implicit join handling for `%s`", joinedPath.getNavigablePath() ); + + // todo (6.0) : implement } ); } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // SqmPath handling + // - Note that SqmFrom references defined in the FROM-clause are already + // handled during `#visitFromClause` + + @Override + public TableGroup visitRootPath(SqmRoot sqmRoot) { + final TableGroup resolved = fromClauseIndex.findTableGroup( sqmRoot.getNavigablePath() ); + if ( resolved != null ) { + log.tracef( "SqmRoot [%s] resolved to existing TableGroup [%s]", sqmRoot, resolved ); + return resolved; + } + + throw new InterpretationException( "SqmRoot not yet resolved to TableGroup" ); + } + @Override public TableGroupJoin visitQualifiedAttributeJoin(SqmAttributeJoin sqmJoin) { - final TableGroupJoin tableJoinJoin = fromClauseIndex.findTableGroupJoin( sqmJoin.getNavigablePath() ); - if ( tableJoinJoin != null ) { - return tableJoinJoin; + // todo (6.0) : have this resolve to TableGroup instead? + // - trying to remove tracking of TableGroupJoin in the x-refs + + final TableGroupJoin existing = fromClauseIndex.findTableGroupJoin( sqmJoin.getNavigablePath() ); + if ( existing != null ) { + log.tracef( "SqmAttributeJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); + return existing; } - final SqmPathSource pathSource = sqmJoin.getReferencedPathSource(); - - final TableGroup lhsTableGroup = fromClauseIndex.findTableGroup( sqmJoin.getLhs().getNavigablePath() ); - - if ( pathSource.getSqmPathType() instanceof EmbeddableDomainType ) { - // we need some special handling for embeddables... - - // Above we checked for a TableGroupJoin associated with the `sqmJoin` path - but for - // an embeddable, check for its LHS too - final TableGroupJoin lhsTableGroupJoin = fromClauseIndex.findTableGroupJoin( sqmJoin.getNavigablePath() ); - if ( lhsTableGroupJoin != null ) { - fromClauseIndex.register( sqmJoin, lhsTableGroupJoin ); - return lhsTableGroupJoin; - } - - // Next, although we don't want an actual TableGroup/TableGroupJoin created just for the - // embeddable, we do need to register a TableGroup against its NavigablePath. - // Specifically the TableGroup associated with the embeddable's LHS - fromClauseIndex.registerTableGroup( sqmJoin.getNavigablePath(), lhsTableGroup ); - - // we also still want to process its joins, adding them to the LHS TableGroup - sqmJoin.visitSqmJoins( - sqmJoinJoin -> { - final TableGroupJoin tableGroupJoin = (TableGroupJoin) sqmJoinJoin.accept( this ); - if ( tableGroupJoin != null ) { - lhsTableGroup.addTableGroupJoin( tableGroupJoin ); - } - } - ); - - // return null - there is no TableGroupJoin that needs to be added - return null; - } - - final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) pathSource ).createTableGroupJoin( - sqmJoin.getNavigablePath(), - lhsTableGroup, - sqmJoin.getExplicitAlias(), - sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(), - LockMode.READ, - sqlAliasBaseManager, - creationContext - ); - - fromClauseIndex.register( sqmJoin, tableGroupJoin ); - lhsTableGroup.addTableGroupJoin( tableGroupJoin ); - - // add any additional join restrictions - if ( sqmJoin.getJoinPredicate() != null ) { - currentQuerySpec().applyPredicate( - (Predicate) sqmJoin.getJoinPredicate().accept( this ) - ); - } - - - return tableGroupJoin; + throw new InterpretationException( "SqmAttributeJoin not yet resolved to TableGroup" ); } private QuerySpec currentQuerySpec() { @@ -557,35 +619,30 @@ public abstract class BaseSqmToSqlAstConverter @Override public TableGroupJoin visitCrossJoin(SqmCrossJoin sqmJoin) { - final EntityPersister entityDescriptor = resolveEntityPersister( sqmJoin.getReferencedPathSource() ); + // todo (6.0) : have this resolve to TableGroup instead? + // - trying to remove tracking of TableGroupJoin in the x-refs - final TableGroup tableGroup = entityDescriptor.createRootTableGroup( - sqmJoin.getNavigablePath(), - sqmJoin.getExplicitAlias(), - JoinType.INNER, - LockMode.NONE, - sqlAliasBaseManager, - getSqlExpressionResolver(), - getCreationContext() - ); + final TableGroupJoin existing = fromClauseIndex.findTableGroupJoin( sqmJoin.getNavigablePath() ); + if ( existing != null ) { + log.tracef( "SqmCrossJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); + return existing; + } - fromClauseIndex.register( sqmJoin, tableGroup ); - - sqmJoin.visitSqmJoins( - sqmJoinJoin -> { - final TableGroupJoin tableGroupJoin = (TableGroupJoin) sqmJoinJoin.accept( this ); - if ( tableGroupJoin != null ) { - tableGroup.addTableGroupJoin( tableGroupJoin ); - } - } - ); - - return new TableGroupJoin( sqmJoin.getNavigablePath(), JoinType.CROSS, tableGroup, null ); + throw new InterpretationException( "SqmCrossJoin not yet resolved to TableGroup" ); } @Override - public Object visitQualifiedEntityJoin(SqmEntityJoin joinedFromElement) { - throw new NotYetImplementedFor6Exception(); + public TableGroupJoin visitQualifiedEntityJoin(SqmEntityJoin sqmJoin) { + // todo (6.0) : have this resolve to TableGroup instead? + // - trying to remove tracking of TableGroupJoin in the x-refs + + final TableGroupJoin existing = fromClauseIndex.findTableGroupJoin( sqmJoin.getNavigablePath() ); + if ( existing != null ) { + log.tracef( "SqmEntityJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); + return existing; + } + + throw new InterpretationException( "SqmEntityJoin not yet resolved to TableGroup" ); } @@ -600,10 +657,6 @@ public abstract class BaseSqmToSqlAstConverter @Override public SqmPathInterpretation visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath sqmPath) { return EmbeddableValuedPathInterpretation.from( sqmPath, this, this ); -// final SqmPath lhs = sqmPath.getLhs(); -// assert lhs != null; -// -// return (SqmPathInterpretation) sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmSelectToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmSelectToSqlAstConverter.java index 9338cf1094..de539507b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmSelectToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmSelectToSqlAstConverter.java @@ -274,6 +274,7 @@ public class SqmSelectToSqlAstConverter JoinType.LEFT, LockMode.NONE, getSqlAliasBaseManager(), + getSqlExpressionResolver(), getCreationContext() ); lhs.addTableGroupJoin( tableGroupJoin ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java index 7a7cd27161..d9753f816d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java @@ -8,18 +8,21 @@ package org.hibernate.query.sqm.tree.from; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.PathException; +import org.hibernate.query.criteria.JpaEntityJoin; +import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.AbstractSqmJoin; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedEntityJoin; +import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; /** * @author Steve Ebersole */ -public class SqmEntityJoin extends AbstractSqmJoin implements SqmQualifiedJoin { +public class SqmEntityJoin extends AbstractSqmJoin implements SqmQualifiedJoin, JpaEntityJoin { private final SqmRoot sqmRoot; private SqmPredicate joinPredicate; @@ -48,6 +51,19 @@ public class SqmEntityJoin extends AbstractSqmJoin implements SqmQualifi return getRoot(); } + @Override + public SqmPath resolveIndexedAccess( + SqmExpression selector, + boolean isTerminal, + SqmCreationState creationState) { + return null; + } + + @Override + public EntityDomainType getModel() { + return (EntityDomainType) super.getModel(); + } + @Override public SqmPath getLhs() { // An entity-join has no LHS diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java index 2dae839eba..86ea99a709 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java @@ -12,10 +12,12 @@ import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.PathException; +import org.hibernate.query.criteria.JpaEntityJoin; import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; +import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; @@ -83,6 +85,20 @@ public class SqmRoot extends AbstractSqmFrom implements JpaRoot, Doma return getReferencedPathSource(); } + + @Override + public JpaEntityJoin join(Class entityJavaType) { + return join( nodeBuilder().getDomainModel().entity( entityJavaType ) ); + } + + @Override + public JpaEntityJoin join(EntityDomainType entity) { + final SqmEntityJoin join = new SqmEntityJoin<>( entity, null, SqmJoinType.CROSS, this ); + //noinspection unchecked + addSqmJoin( (SqmEntityJoin) join ); + return join; + } + @Override public EntityDomainType getModel() { return getReferencedPathSource(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoin.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoin.java index bdc4b8b1b6..7cfa7b189d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoin.java @@ -8,6 +8,7 @@ package org.hibernate.sql.ast.tree.from; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.JoinType; +import org.hibernate.sql.ast.spi.SqlAstTreeHelper; import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.predicate.Predicate; @@ -22,7 +23,8 @@ public class TableGroupJoin implements SqlAstNode, DomainResultProducer { private final NavigablePath navigablePath; private final JoinType joinType; private final TableGroup joinedGroup; - private final Predicate predicate; + + private Predicate predicate; public TableGroupJoin( NavigablePath navigablePath, @@ -35,6 +37,13 @@ public class TableGroupJoin implements SqlAstNode, DomainResultProducer { this.predicate = predicate; } + public TableGroupJoin( + NavigablePath navigablePath, + JoinType joinType, + TableGroup joinedGroup) { + this( navigablePath, joinType, joinedGroup, null ); + } + public JoinType getJoinType() { return joinType; } @@ -47,6 +56,10 @@ public class TableGroupJoin implements SqlAstNode, DomainResultProducer { return predicate; } + public void applyPredicate(Predicate predicate) { + this.predicate = SqlAstTreeHelper.combinePredicates( this.predicate, predicate ); + } + @Override public void accept(SqlAstWalker sqlTreeWalker) { sqlTreeWalker.visitTableGroupJoin( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java index 1d7936dc56..fbbf06a6af 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java @@ -8,6 +8,7 @@ package org.hibernate.sql.ast.tree.from; import org.hibernate.LockMode; import org.hibernate.query.NavigablePath; +import org.hibernate.query.sqm.sql.SqlExpressionResolver; import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; @@ -26,5 +27,6 @@ public interface TableGroupJoinProducer extends TableGroupProducer { JoinType joinType, LockMode lockMode, SqlAliasBaseGenerator aliasBaseGenerator, + SqlExpressionResolver sqlExpressionResolver, SqlAstCreationContext creationContext); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/BasicCriteriaExecutionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/BasicCriteriaExecutionTests.java index b06e5e916f..76df143e15 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/BasicCriteriaExecutionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/BasicCriteriaExecutionTests.java @@ -16,7 +16,12 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Root; +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.query.criteria.JpaCriteriaQuery; +import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.sql.exec.spi.JdbcParameter; @@ -137,6 +142,22 @@ public class BasicCriteriaExecutionTests extends BaseNonConfigCoreFunctionalTest ); } + @Test + public void testCriteriaEntityJoin() { + final HibernateCriteriaBuilder criteriaBuilder = sessionFactory().getCriteriaBuilder(); + + final JpaCriteriaQuery criteria = criteriaBuilder.createQuery(); + + final JpaRoot root = criteria.from( BasicEntity.class ); + root.join( BasicEntity.class ); + + criteria.select( root ); + + inSession( + session -> session.createQuery( criteria ).list() + ); + } + @Entity(name = "BasicEntity") public static class BasicEntity {