From 335ed198213af26b294cf92b79e3f19c5d870433 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Sun, 20 Feb 2022 21:06:25 +0100 Subject: [PATCH] Various fixes * Fix parsing soft-keywords as naked identifiers * Create proper correlations during parsing * Fix some type inference issues with entity valued paths --- .../org/hibernate/grammars/hql/HqlParser.g4 | 28 +- .../internal/EntityCollectionPart.java | 4 +- .../internal/BasicDotIdentifierConsumer.java | 2 +- .../internal/QualifiedJoinPathConsumer.java | 2 +- .../QualifiedJoinPredicatePathConsumer.java | 12 +- .../hql/internal/SemanticQueryBuilder.java | 75 ++- .../hql/internal/SqmPathRegistryImpl.java | 64 +- .../query/hql/spi/SqmPathRegistry.java | 6 +- .../query/results/FromClauseAccessImpl.java | 4 +- .../sqm/internal/SqmMappingModelHelper.java | 18 +- .../hibernate/query/sqm/internal/SqmUtil.java | 8 +- .../sqm/sql/BaseSqmToSqlAstConverter.java | 607 +++++++++++------- .../EntityValuedPathInterpretation.java | 129 ++-- .../internal/SqmParameterInterpretation.java | 7 +- .../sqm/tree/domain/AbstractSqmFrom.java | 3 +- .../sqm/tree/domain/SqmCorrelatedBagJoin.java | 3 +- .../tree/domain/SqmCorrelatedCrossJoin.java | 3 +- .../tree/domain/SqmCorrelatedEntityJoin.java | 3 +- .../tree/domain/SqmCorrelatedListJoin.java | 3 +- .../sqm/tree/domain/SqmCorrelatedMapJoin.java | 3 +- .../domain/SqmCorrelatedPluralPartJoin.java | 3 +- .../sqm/tree/domain/SqmCorrelatedRoot.java | 2 +- .../sqm/tree/domain/SqmCorrelatedSetJoin.java | 3 +- .../domain/SqmCorrelatedSingularJoin.java | 3 +- .../sql/ast/spi/AbstractSqlAstTranslator.java | 49 +- .../sql/ast/spi/FromClauseAccess.java | 7 +- .../ast/spi/SimpleFromClauseAccessImpl.java | 14 +- .../MutatingTableReferenceGroupWrapper.java | 2 +- .../sql/ast/tree/from/UnionTableGroup.java | 2 +- .../indexcoll/IndexedCollectionTest.java | 8 + .../subquery/SubqueryInSelectClauseTest.java | 2 +- ...ciationEqualityPredicateParameterTest.java | 173 +++++ ...NonPkAssociationEqualityPredicateTest.java | 13 +- .../test/query/hql/IsEmptyPredicateTest.java | 9 + 34 files changed, 848 insertions(+), 426 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/valuehandlingmode/inline/NonPkAssociationEqualityPredicateParameterTest.java diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index bc3b929221..45693aade4 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -191,15 +191,14 @@ entityName */ variable : AS identifier - | IDENTIFIER - | QUOTED_IDENTIFIER + | nakedIdentifier ; /** * A 'cross join' to a second root entity (a cartesian product) */ crossJoin - : CROSS JOIN rootEntity variable? + : CROSS JOIN entityName variable? ; /** @@ -1461,7 +1460,8 @@ rollup * This parser rule helps with that. Here we expect that the caller already understands their * context enough to know that keywords-as-identifiers are allowed. */ -identifier + // All except the possible optional following keywords LEFT, RIGHT, INNER, FULL, OUTER + nakedIdentifier : IDENTIFIER | QUOTED_IDENTIFIER | (ALL @@ -1510,7 +1510,7 @@ identifier | FOR | FORMAT | FROM - | FULL +// | FULL | FUNCTION | GROUP | GROUPS @@ -1522,7 +1522,7 @@ identifier | IN | INDEX | INDICES - | INNER +// | INNER | INSERT | INSTANT | INTERSECT @@ -1532,7 +1532,7 @@ identifier | KEY | LAST | LEADING - | LEFT +// | LEFT | LIKE | LIMIT | LIST @@ -1569,7 +1569,7 @@ identifier | OR | ORDER | OTHERS - | OUTER +// | OUTER | OVER | OVERFLOW | OVERLAY @@ -1582,7 +1582,7 @@ identifier | QUARTER | RANGE | RESPECT - | RIGHT +// | RIGHT | ROLLUP | ROW | ROWS @@ -1621,3 +1621,13 @@ identifier logUseOfReservedWordAsIdentifier( getCurrentToken() ); } ; +identifier + : nakedIdentifier + | (FULL + | INNER + | LEFT + | OUTER + | RIGHT) { + logUseOfReservedWordAsIdentifier( getCurrentToken() ); + } + ; 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 c7a1e50c94..85fe38a082 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 @@ -548,7 +548,9 @@ public class EntityCollectionPart @Override public ForeignKeyDescriptor.Nature getSideNature() { - return ForeignKeyDescriptor.Nature.TARGET; + return collectionDescriptor.isOneToMany() + ? ForeignKeyDescriptor.Nature.TARGET + : ForeignKeyDescriptor.Nature.KEY; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java index 8f6cbd22cc..d283e988ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java @@ -130,7 +130,7 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { .getCurrent() .getPathRegistry(); - final SqmFrom pathRootByAlias = sqmPathRegistry.findFromByAlias( identifier ); + final SqmFrom pathRootByAlias = sqmPathRegistry.findFromByAlias( identifier, true ); if ( pathRootByAlias != null ) { // identifier is an alias (identification variable) validateAsRoot( pathRootByAlias ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java index 4224a3d0d7..e8888a6bbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java @@ -110,7 +110,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { final SqmCreationProcessingState processingState = creationState.getCurrentProcessingState(); final SqmPathRegistry pathRegistry = processingState.getPathRegistry(); - final SqmFrom pathRootByAlias = pathRegistry.findFromByAlias( identifier ); + final SqmFrom pathRootByAlias = pathRegistry.findFromByAlias( identifier, true ); if ( pathRootByAlias != null ) { // identifier is an alias (identification variable) diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPredicatePathConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPredicatePathConsumer.java index 15b7f9fcba..8463ae1c54 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPredicatePathConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPredicatePathConsumer.java @@ -13,6 +13,7 @@ import org.hibernate.query.hql.spi.SemanticPathPart; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.tree.SqmQuery; +import org.hibernate.query.sqm.tree.domain.SqmCorrelation; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmFromClause; import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin; @@ -45,7 +46,7 @@ public class QualifiedJoinPredicatePathConsumer extends BasicDotIdentifierConsum final SqmRoot root = pathRoot.findRoot(); final SqmRoot joinRoot = sqmJoin.findRoot(); if ( root != joinRoot ) { - // The root of a path within a query doesn't have the same root as the current join we are processing. + // The root of a path within a join condition doesn't have the same root as the current join we are processing. // The aim of this check is to prevent uses of different "spaces" i.e. `from A a, B b join b.id = a.id` would be illegal SqmCreationProcessingState processingState = getCreationState().getCurrentProcessingState(); // First, we need to find out if the current join is part of current processing query @@ -56,7 +57,14 @@ public class QualifiedJoinPredicatePathConsumer extends BasicDotIdentifierConsum // If the current processing query contains the root of the current join, // then the root of the processing path must be a root of one of the parent queries if ( fromClause != null && fromClause.getRoots().contains( joinRoot ) ) { - validateAsRootOnParentQueryClosure( pathRoot, root, processingState.getParentProcessingState() ); + // It is allowed to use correlations from the same query + if ( !( root instanceof SqmCorrelation ) || !fromClause.getRoots().contains( root ) ) { + validateAsRootOnParentQueryClosure( + pathRoot, + root, + processingState.getParentProcessingState() + ); + } return; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 06128b1db4..388d4c6967 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -86,7 +86,6 @@ import org.hibernate.query.sqm.UnaryArithmeticOperator; import org.hibernate.query.sqm.UnknownEntityException; import org.hibernate.query.sqm.function.FunctionKind; import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor; -import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.internal.ParameterCollector; import org.hibernate.query.sqm.internal.SqmCreationProcessingStateImpl; @@ -1047,7 +1046,10 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public SqmPath visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) { final String alias = ctx.getChild( 2 ).getText(); - final SqmFrom sqmFromByAlias = processingStateStack.getCurrent().getPathRegistry().findFromByAlias( alias ); + final SqmFrom sqmFromByAlias = processingStateStack.getCurrent().getPathRegistry().findFromByAlias( + alias, + true + ); if ( sqmFromByAlias == null ) { throw new SemanticException( "Unable to resolve alias [" + alias + "] in selection [" + ctx.getText() + "]" ); } @@ -1102,7 +1104,10 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return new SqmAliasedNodeRef( correspondingPosition, integerDomainType, creationContext.getNodeBuilder() ); } - final SqmFrom sqmFrom = getCurrentProcessingState().getPathRegistry().findFromByAlias( identifierText ); + final SqmFrom sqmFrom = getCurrentProcessingState().getPathRegistry().findFromByAlias( + identifierText, + true + ); if ( sqmFrom != null ) { if ( definedCollate ) { // This is syntactically disallowed @@ -1360,6 +1365,15 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public String visitIdentifier(HqlParser.IdentifierContext ctx) { + final ParseTree child = ctx.getChild( 0 ); + if ( child instanceof TerminalNode ) { + return child.getText(); + } + return visitNakedIdentifier( (HqlParser.NakedIdentifierContext) child ); + } + + @Override + public String visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) { final TerminalNode node = (TerminalNode) ctx.getChild( 0 ); if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) { return QuotingHelper.unquoteIdentifier( node.getText() ); @@ -1413,7 +1427,11 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public SqmRoot visitEntityWithJoins(HqlParser.EntityWithJoinsContext parserSpace) { final SqmRoot sqmRoot = visitRootEntity( (HqlParser.RootEntityContext) parserSpace.getChild( 0 ) ); - currentQuerySpec().getFromClause().addRoot( sqmRoot ); + final SqmFromClause fromClause = currentQuerySpec().getFromClause(); + // Correlations are implicitly added to the from clause + if ( !( sqmRoot instanceof SqmCorrelation ) ) { + fromClause.addRoot( sqmRoot ); + } final int size = parserSpace.getChildCount(); for ( int i = 1; i < size; i++ ) { final ParseTree parseTree = parserSpace.getChild( i ); @@ -1461,12 +1479,9 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem // Handle the use of a correlation path in subqueries if ( processingStateStack.depth() > 1 && size > 2 ) { final String parentAlias = entityNameParseTreeChildren.get( 0 ).getText(); - final AbstractSqmFrom correlationBasis = processingState.getParentProcessingState() - .getPathRegistry() - .findFromByAlias( parentAlias ); - if ( correlationBasis != null ) { - final SqmCorrelation correlation = correlationBasis.createCorrelation(); - pathRegistry.register( correlation ); + final AbstractSqmFrom correlation = processingState.getPathRegistry() + .findFromByAlias( parentAlias, true ); + if ( correlation instanceof SqmCorrelation ) { final DotIdentifierConsumer dotIdentifierConsumer = new QualifiedJoinPathConsumer( correlation, SqmJoinType.INNER, @@ -1487,7 +1502,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem false, true ); - return correlation.getCorrelatedRoot(); + return ( (SqmCorrelation) correlation ).getCorrelatedRoot(); } throw new SemanticException( "Could not resolve entity or correlation path '" + name + "'" ); } @@ -1545,11 +1560,24 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return visitIdentifier( identifierContext ); } else { - final TerminalNode node = (TerminalNode) lastChild; - if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) { - return QuotingHelper.unquoteIdentifier( node.getText() ); + final HqlParser.NakedIdentifierContext identifierContext = (HqlParser.NakedIdentifierContext) lastChild; + // in this branch, the alias could be a reserved word ("keyword as identifier") + // which JPA disallows... + if ( getCreationOptions().useStrictJpaCompliance() ) { + final Token identificationVariableToken = identifierContext.getStart(); + if ( identificationVariableToken.getType() != IDENTIFIER ) { + throw new StrictJpaComplianceViolation( + String.format( + Locale.ROOT, + "Strict JPQL compliance was violated : %s [%s]", + StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS.description(), + identificationVariableToken.getText() + ), + StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS + ); + } } - return node.getText(); + return visitNakedIdentifier( identifierContext ); } } @@ -1571,8 +1599,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } private void consumeCrossJoin(HqlParser.CrossJoinContext parserJoin, SqmRoot sqmRoot) { - final HqlParser.RootEntityContext pathRootContext = (HqlParser.RootEntityContext) parserJoin.getChild( 2 ); - final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) pathRootContext.getChild( 0 ); + final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) parserJoin.getChild( 2 ); final String name = getEntityName( entityNameContext ); SqmTreeCreationLogger.LOGGER.debugf( "Handling root path - %s", name ); @@ -1584,8 +1611,8 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem throw new SemanticException( "Unmapped polymorphic reference cannot be used as a CROSS JOIN target" ); } final HqlParser.VariableContext identificationVariableDefContext; - if ( pathRootContext.getChildCount() > 1 ) { - identificationVariableDefContext = (HqlParser.VariableContext) pathRootContext.getChild( 1 ); + if ( parserJoin.getChildCount() > 3 ) { + identificationVariableDefContext = (HqlParser.VariableContext) parserJoin.getChild( 3 ); } else { identificationVariableDefContext = null; @@ -3628,6 +3655,16 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem else { //for the shorter legacy Hibernate syntax "field(arg)" extractFieldExpression = (SqmExtractUnit) ctx.getChild( 0 ).accept(this); +// //Prefer an existing native version if available +// final SqmFunctionDescriptor functionDescriptor = getFunctionDescriptor( extractFieldExpression.getUnit().name() ); +// if ( functionDescriptor != null ) { +// return functionDescriptor.generateSqmExpression( +// expressionToExtract, +// null, +// creationContext.getQueryEngine(), +// creationContext.getJpaMetamodel().getTypeConfiguration() +// ); +// } } return getFunctionDescriptor("extract").generateSqmExpression( diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java index 86a72a0284..ef6439c50c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java @@ -14,17 +14,23 @@ import java.util.Map; import java.util.function.Function; import org.hibernate.jpa.spi.JpaCompliance; -import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.hql.HqlLogging; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.sqm.AliasCollisionException; import org.hibernate.query.sqm.ParsingException; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SqmTreeCreationLogger; import org.hibernate.query.sqm.tree.domain.SqmPath; +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.select.SqmAliasedNode; +import org.hibernate.query.sqm.tree.select.SqmSubQuery; + +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; /** * Container for indexing needed while building an SQM tree. @@ -117,27 +123,12 @@ public class SqmPathRegistryImpl implements SqmPathRegistry { @Override public > X findFromByPath(NavigablePath navigablePath) { - final SqmFrom found = sqmFromByPath.get( navigablePath ); - if ( found != null ) { - //noinspection unchecked - return (X) found; - } - - if ( associatedProcessingState.getParentProcessingState() != null ) { - final X containingQueryFrom = associatedProcessingState.getParentProcessingState() - .getPathRegistry() - .findFromByPath( navigablePath ); - if ( containingQueryFrom != null ) { - // todo (6.0) create a correlation? - return containingQueryFrom; - } - } - - return null; + //noinspection unchecked + return (X) sqmFromByPath.get( navigablePath ); } @Override - public > X findFromByAlias(String alias) { + public > X findFromByAlias(String alias, boolean searchParent) { final String localAlias = jpaCompliance.isJpaQueryComplianceEnabled() ? alias.toLowerCase( Locale.getDefault() ) : alias; @@ -149,8 +140,39 @@ public class SqmPathRegistryImpl implements SqmPathRegistry { return (X) registered; } - if ( associatedProcessingState.getParentProcessingState() != null ) { - return associatedProcessingState.getParentProcessingState().getPathRegistry().findFromByAlias( alias ); + SqmCreationProcessingState parentProcessingState = associatedProcessingState.getParentProcessingState(); + if ( searchParent && parentProcessingState != null ) { + X parentRegistered; + do { + parentRegistered = parentProcessingState.getPathRegistry().findFromByAlias( + alias, + false + ); + parentProcessingState = parentProcessingState.getParentProcessingState(); + } while (parentProcessingState != null && parentRegistered == null); + if ( parentRegistered != null ) { + // If a parent query contains the alias, we need to create a correlation on the subquery + final SqmSubQuery selectQuery = ( SqmSubQuery ) associatedProcessingState.getProcessingQuery(); + SqmFrom correlated; + if ( parentRegistered instanceof Root ) { + correlated = selectQuery.correlate( (Root) parentRegistered ); + } + else if ( parentRegistered instanceof Join ) { + correlated = selectQuery.correlate( (Join) parentRegistered ); + } + else if ( parentRegistered instanceof SqmCrossJoin ) { + correlated = selectQuery.correlate( (SqmCrossJoin) parentRegistered ); + } + else if ( parentRegistered instanceof SqmEntityJoin ) { + correlated = selectQuery.correlate( (SqmEntityJoin) parentRegistered ); + } + else { + throw new UnsupportedOperationException( "Can't correlate from node: " + parentRegistered ); + } + register( correlated ); + //noinspection unchecked + return (X) correlated; + } } return null; diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmPathRegistry.java b/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmPathRegistry.java index d11418a142..c74ff91f81 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmPathRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmPathRegistry.java @@ -33,12 +33,12 @@ public interface SqmPathRegistry { void register(SqmPath sqmPath); /** - * Find a SqmFrom by its identification variable (alias). Will search any - * parent contexts as well + * Find a SqmFrom by its identification variable (alias). + * If the SqmFrom is found in a parent context, the correlation for the path will be returned. * * @return matching SqmFrom or {@code null} */ - > X findFromByAlias(String identificationVariable); + > X findFromByAlias(String identificationVariable, boolean searchParent); /** * Find a SqmFrom by its NavigablePath. Will search any parent contexts as well diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/FromClauseAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/FromClauseAccessImpl.java index e36b02968a..02cf80767f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/FromClauseAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/FromClauseAccessImpl.java @@ -43,8 +43,8 @@ public class FromClauseAccessImpl implements FromClauseAccess { } @Override - public TableGroup findTableGroupOnLeaf(NavigablePath navigablePath) { - return findTableGroup( navigablePath ); + public TableGroup findTableGroupOnParents(NavigablePath navigablePath) { + return null; } @Override 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 cb093508ac..9173e97d97 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 @@ -7,14 +7,15 @@ package org.hibernate.query.sqm.internal; import java.util.function.Function; -import jakarta.persistence.metamodel.Bindable; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; +import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.metamodel.model.domain.AnyMappingDomainType; import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.metamodel.model.domain.DomainType; @@ -25,7 +26,6 @@ import org.hibernate.metamodel.model.domain.internal.AnyMappingSqmPathSource; import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource; -import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.model.domain.internal.MappedSuperclassSqmPathSource; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.NavigablePath; @@ -39,6 +39,8 @@ import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.type.BasicType; +import jakarta.persistence.metamodel.Bindable; + /** * Helper for dealing with Hibernate's "mapping model" while processing an SQM which is defined * in terms of the JPA/SQM metamodel @@ -165,7 +167,17 @@ public class SqmMappingModelHelper { sqmPath.getLhs().getReferencedPathSource().getPathName(), null ); - return pluralPart.findSubPart( sqmPath.getReferencedPathSource().getPathName(), null ); + final CollectionPart collectionPart = (CollectionPart) pluralPart.findSubPart( + sqmPath.getReferencedPathSource() + .getPathName(), + null + ); + // For entity collection parts, we must return the entity mapping type, + // as that is the mapping type of the expression + if ( collectionPart instanceof EntityCollectionPart ) { + return ( (EntityCollectionPart) collectionPart ).getEntityMappingType(); + } + return collectionPart; } if ( sqmPath.getLhs() == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java index 4e5dc0491e..1a5f750f9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java @@ -26,6 +26,7 @@ import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.mapping.Bindable; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.ConvertibleModelPart; +import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; @@ -352,9 +353,6 @@ public class SqmUtil { if ( parameterType == null ) { throw new SqlTreeCreationException( "Unable to interpret mapping-model type for Query parameter : " + domainParam ); } - if ( parameterType instanceof CollectionPart && ( (CollectionPart) parameterType ).getPartMappingType() instanceof Bindable ) { - parameterType = (Bindable) ( (CollectionPart) parameterType ).getPartMappingType(); - } if ( parameterType instanceof EntityIdentifierMapping ) { final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType; @@ -371,8 +369,8 @@ public class SqmUtil { bindValue = identifierMapping.getIdentifier( bindValue ); } } - else if ( parameterType instanceof ToOneAttributeMapping ) { - ToOneAttributeMapping association = (ToOneAttributeMapping) parameterType; + else if ( parameterType instanceof EntityAssociationMapping ) { + EntityAssociationMapping association = (EntityAssociationMapping) parameterType; bindValue = association.getForeignKeyDescriptor().getAssociationKeyFromSide( bindValue, association.getSideNature().inverse(), 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 9046aa0f6e..7a8be9b12a 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 @@ -248,6 +248,7 @@ import org.hibernate.query.sqm.tree.update.SqmSetClause; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstJoinType; +import org.hibernate.sql.ast.SqlTreeCreationException; import org.hibernate.sql.ast.SqlTreeCreationLogger; import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.sql.ast.spi.FromClauseAccess; @@ -565,13 +566,13 @@ public abstract class BaseSqmToSqlAstConverter extends Base // FromClauseAccess @Override - public TableGroup findTableGroupOnLeaf(NavigablePath navigablePath) { - return getFromClauseAccess().findTableGroupOnLeaf( navigablePath ); + public TableGroup findTableGroup(NavigablePath navigablePath) { + return getFromClauseAccess().findTableGroup( navigablePath ); } @Override - public TableGroup findTableGroup(NavigablePath navigablePath) { - return getFromClauseAccess().findTableGroup( navigablePath ); + public TableGroup findTableGroupOnParents(NavigablePath navigablePath) { + return getFromClauseAccess().findTableGroupOnParents( navigablePath ); } @Override @@ -1753,6 +1754,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base protected void visitOrderByOffsetAndFetch(SqmQueryPart sqmQueryPart, QueryPart sqlQueryPart) { if ( sqmQueryPart.getOrderByClause() != null ) { currentClauseStack.push( Clause.ORDER ); + inferrableTypeAccessStack.push( () -> null ); try { for ( SqmSortSpecification sortSpecification : sqmQueryPart.getOrderByClause() .getSortSpecifications() ) { @@ -1763,6 +1765,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } finally { + inferrableTypeAccessStack.pop(); currentClauseStack.pop(); } } @@ -1773,11 +1776,18 @@ public abstract class BaseSqmToSqlAstConverter extends Base // case by using a subquery e.g. `... where alias in (select subAlias from ... limit ...)` // or use window functions e.g. `select ... from (select ..., dense_rank() over(order by ..., id) rn from ...) tmp where tmp.rn between ...` // but these transformations/translations are non-trivial and can be done later + inferrableTypeAccessStack.push( () -> getTypeConfiguration().getBasicTypeForJavaType( Integer.class ) ); sqlQueryPart.setOffsetClauseExpression( visitOffsetExpression( sqmQueryPart.getOffsetExpression() ) ); + if ( sqmQueryPart.getFetchClauseType() == FetchClauseType.PERCENT_ONLY + || sqmQueryPart.getFetchClauseType() == FetchClauseType.PERCENT_WITH_TIES ) { + inferrableTypeAccessStack.pop(); + inferrableTypeAccessStack.push( () -> getTypeConfiguration().getBasicTypeForJavaType( Double.class ) ); + } sqlQueryPart.setFetchClauseExpression( visitFetchExpression( sqmQueryPart.getFetchExpression() ), sqmQueryPart.getFetchClauseType() ); + inferrableTypeAccessStack.pop(); } } @@ -2065,6 +2075,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base public List visitGroupByClause(List> groupByClauseExpressions) { if ( !groupByClauseExpressions.isEmpty() ) { currentClauseStack.push( Clause.GROUP ); + inferrableTypeAccessStack.push( () -> null ); try { final List expressions = new ArrayList<>( groupByClauseExpressions.size() ); for ( SqmExpression groupByClauseExpression : groupByClauseExpressions ) { @@ -2073,6 +2084,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base return expressions; } finally { + inferrableTypeAccessStack.pop(); currentClauseStack.pop(); } } @@ -2084,6 +2096,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base return null; } currentClauseStack.push( Clause.WHERE ); + inferrableTypeAccessStack.push( () -> null ); try { return SqlAstTreeHelper.combinePredicates( (Predicate) sqmPredicate.accept( this ), @@ -2091,6 +2104,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); } finally { + inferrableTypeAccessStack.pop(); currentClauseStack.pop(); } } @@ -2101,6 +2115,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base return null; } currentClauseStack.push( Clause.HAVING ); + inferrableTypeAccessStack.push( () -> null ); try { return SqlAstTreeHelper.combinePredicates( (Predicate) sqmPredicate.accept( this ), @@ -2108,6 +2123,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); } finally { + inferrableTypeAccessStack.pop(); currentClauseStack.pop(); } } @@ -2170,6 +2186,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base currentClauseStack.push( Clause.FROM ); try { + // First, consume correlated roots, because these table groups can be used in join predicates of other from nodes + sqmFromClause.visitRoots( this::consumeFromClauseCorrelatedRoot ); sqmFromClause.visitRoots( this::consumeFromClauseRoot ); } finally { @@ -2179,6 +2197,185 @@ public abstract class BaseSqmToSqlAstConverter extends Base return null; } + protected void consumeFromClauseCorrelatedRoot(SqmRoot sqmRoot) { + log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot ); + final FromClauseIndex fromClauseIndex = getFromClauseIndex(); + if ( fromClauseIndex.isResolved( sqmRoot ) ) { + log.tracef( "Already resolved SqmRoot [%s] to TableGroup", sqmRoot ); + } + final QuerySpec currentQuerySpec = currentQuerySpec(); + final TableGroup tableGroup; + if ( !sqmRoot.isCorrelated() ) { + return; + } + final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory(); + if ( sqmRoot.containsOnlyInnerJoins() ) { + // If we have just inner joins against a correlated root, we can render the joins as references + final SqmFrom from; + // If we correlate a join, we have to create a special SqmRoot shell called SqmCorrelatedRootJoin. + // The only purpose of that is to serve as SqmRoot, which is needed for the FROM clause. + // It will always contain just a single correlated join though, which is what is actually correlated + if ( sqmRoot instanceof SqmCorrelatedRootJoin ) { + assert sqmRoot.getSqmJoins().size() == 1; + assert sqmRoot.getSqmJoins().get( 0 ).isCorrelated(); + from = sqmRoot.getSqmJoins().get( 0 ); + } + else { + from = sqmRoot; + } + final TableGroup parentTableGroup = fromClauseIndex.findTableGroupOnParents( + from.getCorrelationParent().getNavigablePath() + ); + final SqlAliasBase sqlAliasBase = sqlAliasBaseManager.createSqlAliasBase( parentTableGroup.getGroupAlias() ); + if ( parentTableGroup instanceof PluralTableGroup ) { + final PluralTableGroup pluralTableGroup = (PluralTableGroup) parentTableGroup; + final CorrelatedPluralTableGroup correlatedPluralTableGroup = new CorrelatedPluralTableGroup( + parentTableGroup, + sqlAliasBase, + currentQuerySpec, + predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + predicate + ), + sessionFactory + ); + final TableGroup elementTableGroup = pluralTableGroup.getElementTableGroup(); + if ( elementTableGroup != null ) { + final TableGroup correlatedElementTableGroup = new CorrelatedTableGroup( + elementTableGroup, + sqlAliasBase, + currentQuerySpec, + predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + predicate + ), + sessionFactory + ); + final TableGroupJoin tableGroupJoin = new TableGroupJoin( + elementTableGroup.getNavigablePath(), + SqlAstJoinType.INNER, + correlatedElementTableGroup + ); + correlatedPluralTableGroup.registerElementTableGroup( tableGroupJoin ); + } + final TableGroup indexTableGroup = pluralTableGroup.getIndexTableGroup(); + if ( indexTableGroup != null ) { + final TableGroup correlatedIndexTableGroup = new CorrelatedTableGroup( + indexTableGroup, + sqlAliasBase, + currentQuerySpec, + predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + predicate + ), + sessionFactory + ); + final TableGroupJoin tableGroupJoin = new TableGroupJoin( + indexTableGroup.getNavigablePath(), + SqlAstJoinType.INNER, + correlatedIndexTableGroup + ); + correlatedPluralTableGroup.registerIndexTableGroup( tableGroupJoin ); + } + tableGroup = correlatedPluralTableGroup; + } + else { + tableGroup = new CorrelatedTableGroup( + parentTableGroup, + sqlAliasBase, + currentQuerySpec, + predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + predicate + ), + sessionFactory + ); + } + + fromClauseIndex.register( from, tableGroup ); + registerPluralTableGroupParts( tableGroup ); + + log.tracef( "Resolved SqmRoot [%s] to correlated TableGroup [%s]", sqmRoot, tableGroup ); + consumeExplicitJoins( from, tableGroup ); + return; + } + else { + final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() ); + final TableGroup parentTableGroup = fromClauseIndex.findTableGroupOnParents( + sqmRoot.getCorrelationParent().getNavigablePath() + ); + // If we have non-inner joins against a correlated root, we must render the root with a correlation predicate + tableGroup = entityDescriptor.createRootTableGroup( + true, + sqmRoot.getNavigablePath(), + sqmRoot.getExplicitAlias(), + () -> predicate -> {}, + this, + creationContext + ); + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final NavigablePath navigablePath = sqmRoot.getNavigablePath().append( identifierMapping.getNavigableRole().getNavigableName() ); + final int jdbcTypeCount = identifierMapping.getJdbcTypeCount(); + if ( jdbcTypeCount == 1 ) { + identifierMapping.forEachSelectable( + (index, selectable) -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + new ComparisonPredicate( + new ColumnReference( + parentTableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ), + selectable, + sessionFactory + ), + ComparisonOperator.EQUAL, + new ColumnReference( + tableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ), + selectable, + sessionFactory + ) + ) + ) + ); + } + else { + final List lhs = new ArrayList<>( jdbcTypeCount ); + final List rhs = new ArrayList<>( jdbcTypeCount ); + identifierMapping.forEachSelectable( + (index, selectable) -> { + lhs.add( + new ColumnReference( + parentTableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ), + selectable, + sessionFactory + ) + ); + rhs.add( + new ColumnReference( + tableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ), + selectable, + sessionFactory + ) + ); + } + ); + additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + new ComparisonPredicate( + new SqlTuple( lhs, identifierMapping ), + ComparisonOperator.EQUAL, + new SqlTuple( rhs, identifierMapping ) + ) + ); + } + } + + log.tracef( "Resolved SqmRoot [%s] to new TableGroup [%s]", sqmRoot, tableGroup ); + + fromClauseIndex.register( sqmRoot, tableGroup ); + currentQuerySpec.getFromClause().addRoot( tableGroup ); + + consumeJoins( sqmRoot, fromClauseIndex, tableGroup ); + } + protected void consumeFromClauseRoot(SqmRoot sqmRoot) { log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot ); final FromClauseIndex fromClauseIndex = getFromClauseIndex(); @@ -2188,195 +2385,39 @@ public abstract class BaseSqmToSqlAstConverter extends Base final QuerySpec currentQuerySpec = currentQuerySpec(); final TableGroup tableGroup; if ( sqmRoot.isCorrelated() ) { - final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory(); - final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() ); - if ( sqmRoot.containsOnlyInnerJoins() ) { - // If we have just inner joins against a correlated root, we can render the joins as references - final SqmFrom from; - // If we correlate a join, we have to create a special SqmRoot shell called SqmCorrelatedRootJoin. - // The only purpose of that is to serve as SqmRoot, which is needed for the FROM clause. - // It will always contain just a single correlated join though, which is what is actually correlated - if ( sqmRoot instanceof SqmCorrelatedRootJoin ) { - assert sqmRoot.getSqmJoins().size() == 1; - assert sqmRoot.getSqmJoins().get( 0 ).isCorrelated(); - from = sqmRoot.getSqmJoins().get( 0 ); - } - else { - from = sqmRoot; - } - final TableGroup parentTableGroup = fromClauseIndex.findTableGroup( - from.getCorrelationParent().getNavigablePath() - ); - final SqlAliasBase sqlAliasBase = sqlAliasBaseManager.createSqlAliasBase( parentTableGroup.getGroupAlias() ); - if ( parentTableGroup instanceof PluralTableGroup ) { - final PluralTableGroup pluralTableGroup = (PluralTableGroup) parentTableGroup; - final CorrelatedPluralTableGroup correlatedPluralTableGroup = new CorrelatedPluralTableGroup( - parentTableGroup, - sqlAliasBase, - currentQuerySpec, - predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( - additionalRestrictions, - predicate - ), - sessionFactory - ); - final TableGroup elementTableGroup = pluralTableGroup.getElementTableGroup(); - if ( elementTableGroup != null ) { - final TableGroup correlatedElementTableGroup = new CorrelatedTableGroup( - elementTableGroup, - sqlAliasBase, - currentQuerySpec, - predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( - additionalRestrictions, - predicate - ), - sessionFactory - ); - final TableGroupJoin tableGroupJoin = new TableGroupJoin( - elementTableGroup.getNavigablePath(), - SqlAstJoinType.INNER, - correlatedElementTableGroup - ); - correlatedPluralTableGroup.registerElementTableGroup( tableGroupJoin ); - } - final TableGroup indexTableGroup = pluralTableGroup.getIndexTableGroup(); - if ( indexTableGroup != null ) { - final TableGroup correlatedIndexTableGroup = new CorrelatedTableGroup( - indexTableGroup, - sqlAliasBase, - currentQuerySpec, - predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( - additionalRestrictions, - predicate - ), - sessionFactory - ); - final TableGroupJoin tableGroupJoin = new TableGroupJoin( - indexTableGroup.getNavigablePath(), - SqlAstJoinType.INNER, - correlatedIndexTableGroup - ); - correlatedPluralTableGroup.registerIndexTableGroup( tableGroupJoin ); - } - tableGroup = correlatedPluralTableGroup; - } - else { - tableGroup = new CorrelatedTableGroup( - parentTableGroup, - sqlAliasBase, - currentQuerySpec, - predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( - additionalRestrictions, - predicate - ), - sessionFactory - ); - } - - fromClauseIndex.register( from, tableGroup ); - registerPluralTableGroupParts( tableGroup ); - - log.tracef( "Resolved SqmRoot [%s] to correlated TableGroup [%s]", sqmRoot, tableGroup ); - consumeExplicitJoins( from, tableGroup ); - return; - } - else { - final TableGroup parentTableGroup = fromClauseIndex.findTableGroup( - sqmRoot.getCorrelationParent().getNavigablePath() - ); - // If we have non-inner joins against a correlated root, we must render the root with a correlation predicate - tableGroup = entityDescriptor.createRootTableGroup( - true, - sqmRoot.getNavigablePath(), - sqmRoot.getExplicitAlias(), - () -> predicate -> {}, - this, - creationContext - ); - final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); - final NavigablePath navigablePath = sqmRoot.getNavigablePath().append( identifierMapping.getNavigableRole().getNavigableName() ); - final int jdbcTypeCount = identifierMapping.getJdbcTypeCount(); - if ( jdbcTypeCount == 1 ) { - identifierMapping.forEachSelectable( - (index, selectable) -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( - additionalRestrictions, - new ComparisonPredicate( - new ColumnReference( - parentTableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ), - selectable, - sessionFactory - ), - ComparisonOperator.EQUAL, - new ColumnReference( - tableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ), - selectable, - sessionFactory - ) - ) - ) - ); - } - else { - final List lhs = new ArrayList<>( jdbcTypeCount ); - final List rhs = new ArrayList<>( jdbcTypeCount ); - identifierMapping.forEachSelectable( - (index, selectable) -> { - lhs.add( - new ColumnReference( - parentTableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ), - selectable, - sessionFactory - ) - ); - rhs.add( - new ColumnReference( - tableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ), - selectable, - sessionFactory - ) - ); - } - ); - additionalRestrictions = SqlAstTreeHelper.combinePredicates( - additionalRestrictions, - new ComparisonPredicate( - new SqlTuple( lhs, identifierMapping ), - ComparisonOperator.EQUAL, - new SqlTuple( rhs, identifierMapping ) - ) - ); - } - } + return; } - else { - final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() ); - tableGroup = entityDescriptor.createRootTableGroup( - true, - sqmRoot.getNavigablePath(), - sqmRoot.getExplicitAlias(), - () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( - additionalRestrictions, - predicate - ), - this, - creationContext - ); + final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() ); + tableGroup = entityDescriptor.createRootTableGroup( + true, + sqmRoot.getNavigablePath(), + sqmRoot.getExplicitAlias(), + () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + predicate + ), + this, + creationContext + ); - entityDescriptor.applyBaseRestrictions( - currentQuerySpec::applyPredicate, - tableGroup, - true, - getLoadQueryInfluencers().getEnabledFilters(), - null, - this - ); - } + entityDescriptor.applyBaseRestrictions( + currentQuerySpec::applyPredicate, + tableGroup, + true, + getLoadQueryInfluencers().getEnabledFilters(), + null, + this + ); log.tracef( "Resolved SqmRoot [%s] to new TableGroup [%s]", sqmRoot, tableGroup ); fromClauseIndex.register( sqmRoot, tableGroup ); currentQuerySpec.getFromClause().addRoot( tableGroup ); + consumeJoins( sqmRoot, fromClauseIndex, tableGroup ); + } + + private void consumeJoins(SqmRoot sqmRoot, FromClauseIndex fromClauseIndex, TableGroup tableGroup) { if ( sqmRoot.getOrderedJoins() == null ) { consumeExplicitJoins( sqmRoot, tableGroup ); } @@ -2401,7 +2442,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } assert ownerTableGroup != null; - lastTableGroup = consumeExplicitJoin( join, lastTableGroup, ownerTableGroup, false ); + final TableGroup actualTableGroup = findActualTableGroup( ownerTableGroup, join ); + lastTableGroup = consumeExplicitJoin( join, lastTableGroup, actualTableGroup, false ); } } } @@ -2762,6 +2804,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private X prepareReusablePath(SqmPath sqmPath, Supplier supplier) { + return prepareReusablePath( sqmPath, fromClauseIndexStack.getCurrent(), supplier ); + } + + private X prepareReusablePath(SqmPath sqmPath, FromClauseIndex fromClauseIndex, Supplier supplier) { final Consumer implicitJoinChecker; if ( getCurrentProcessingState() instanceof SqlAstQueryPartProcessingState ) { implicitJoinChecker = tg -> {}; @@ -2769,7 +2815,6 @@ public abstract class BaseSqmToSqlAstConverter extends Base else { implicitJoinChecker = BaseSqmToSqlAstConverter::verifyManipulationImplicitJoin; } - final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); prepareReusablePath( fromClauseIndex, sqmPath, implicitJoinChecker ); return supplier.get(); } @@ -2795,6 +2840,13 @@ public abstract class BaseSqmToSqlAstConverter extends Base parentPath, implicitJoinChecker ); + if ( parentTableGroup == null ) { + final TableGroup parent = fromClauseIndex.findTableGroupOnParents( parentPath.getNavigablePath() ); + if ( parent != null ) { + throw new SqlTreeCreationException( "Found un-correlated path usage in sub query - " + parentPath ); + } + throw new SqlTreeCreationException( "Could not locate TableGroup - " + parentPath.getNavigablePath() ); + } if ( parentPath instanceof SqmTreatedPath ) { fromClauseIndex.register( (SqmPath) parentPath, parentTableGroup ); return parentTableGroup; @@ -2877,9 +2929,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base final TableGroup tableGroup; if ( subPart instanceof TableGroupJoinProducer ) { final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) subPart; - final SqlAstJoinType defaultSqlAstJoinType; - - if ( fromClauseIndex.findTableGroupOnLeaf( actualParentTableGroup.getNavigablePath() ) == null ) { + if ( fromClauseIndex.findTableGroup( actualParentTableGroup.getNavigablePath() ) == null ) { final QuerySpec querySpec = currentQuerySpec(); // The parent table group is on a parent query, so we need a root table group tableGroup = joinProducer.createRootTableGroupJoin( @@ -3056,47 +3106,97 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private Expression visitTableGroup(TableGroup tableGroup, SqmFrom path) { - final ModelPartContainer modelPart = tableGroup.getModelPart(); - final ModelPart keyPart; - final ModelPart resultPart; + final ModelPartContainer modelPart; + final MappingModelExpressible inferredValueMapping = getInferredValueMapping(); + // For plain SqmFrom node uses, prefer the mapping type from the context if possible + if ( !( inferredValueMapping instanceof ModelPartContainer ) ) { + modelPart = tableGroup.getModelPart(); + } + else { + modelPart = (ModelPartContainer) inferredValueMapping; + } + final ModelPart resultModelPart; + final ModelPart interpretationModelPart; + final TableGroup parentGroupToUse; if ( modelPart instanceof ToOneAttributeMapping ) { final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) modelPart; - keyPart = toOneAttributeMapping.findSubPart( toOneAttributeMapping.getTargetKeyPropertyName() ); - resultPart = modelPart; + final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart( + toOneAttributeMapping.getSideNature().inverse() + ); + if ( tableGroup.getModelPart().getPartMappingType() == modelPart.getPartMappingType() ) { + resultModelPart = targetPart; + } + else { + // If the table group is for a different mapping type i.e. an inheritance subtype, + // lookup the target part on that mapping type + resultModelPart = tableGroup.getModelPart().findSubPart( targetPart.getPartName(), null ); + } + interpretationModelPart = modelPart; + parentGroupToUse = null; } else if ( modelPart instanceof PluralAttributeMapping ) { final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart; final CollectionPart elementDescriptor = pluralAttributeMapping.getElementDescriptor(); if ( elementDescriptor instanceof EntityCollectionPart ) { - keyPart = ( (EntityCollectionPart) elementDescriptor ).getKeyTargetMatchPart(); + // Usually, we need to resolve to the PK for visitTableGroup + final EntityCollectionPart collectionPart = (EntityCollectionPart) elementDescriptor; + final ModelPart collectionTargetPart = collectionPart.getForeignKeyDescriptor() + .getPart( collectionPart.getSideNature().inverse() ); + final EntityIdentifierMapping identifierMapping = collectionPart.getEntityMappingType() + .getIdentifierMapping(); + // If the FK points to the PK, we can use the FK part though, if this is not a root + if ( collectionTargetPart == identifierMapping && !( path instanceof SqmRoot ) ) { + resultModelPart = collectionPart.getForeignKeyDescriptor().getPart( collectionPart.getSideNature() ); + } + else { + resultModelPart = identifierMapping; + } } else { - keyPart = elementDescriptor; + resultModelPart = elementDescriptor; } - resultPart = elementDescriptor; + interpretationModelPart = elementDescriptor; + parentGroupToUse = null; } else if ( modelPart instanceof EntityCollectionPart ) { - keyPart = ( (EntityCollectionPart) modelPart ).getKeyTargetMatchPart(); - resultPart = modelPart; + // Usually, we need to resolve to the PK for visitTableGroup + final EntityCollectionPart collectionPart = (EntityCollectionPart) modelPart; + final ModelPart collectionTargetPart = collectionPart.getForeignKeyDescriptor() + .getPart( collectionPart.getSideNature().inverse() ); + final EntityIdentifierMapping identifierMapping = collectionPart.getEntityMappingType() + .getIdentifierMapping(); + // If the FK points to the PK, we can use the FK part though, if this is not a root + if ( collectionTargetPart == identifierMapping && !( path instanceof SqmRoot ) ) { + resultModelPart = collectionPart.getForeignKeyDescriptor().getPart( collectionPart.getSideNature() ); + } + else { + resultModelPart = identifierMapping; + } + interpretationModelPart = modelPart; + parentGroupToUse = findTableGroup( tableGroup.getNavigablePath().getParent() ); } else if ( modelPart instanceof EntityMappingType ) { - keyPart = ( (EntityMappingType) modelPart ).getIdentifierMapping(); - resultPart = modelPart; + resultModelPart = ( (EntityMappingType) modelPart ).getIdentifierMapping(); + interpretationModelPart = modelPart; + // todo: I think this will always be null anyways because EntityMappingType will only be the model part + // of a TableGroup if that is a root TableGroup, so check if we can just switch to null + parentGroupToUse = findTableGroup( tableGroup.getNavigablePath().getParent() ); } else { - keyPart = modelPart; - resultPart = modelPart; + resultModelPart = modelPart; + interpretationModelPart = modelPart; + parentGroupToUse = null; } final NavigablePath navigablePath; - if ( resultPart == modelPart ) { + if ( interpretationModelPart == modelPart ) { navigablePath = tableGroup.getNavigablePath(); } else { - navigablePath = tableGroup.getNavigablePath().append( resultPart.getPartName() ); + navigablePath = tableGroup.getNavigablePath().append( interpretationModelPart.getPartName() ); } final Expression result; - if ( resultPart instanceof EntityValuedModelPart ) { + if ( interpretationModelPart instanceof EntityValuedModelPart ) { final boolean expandToAllColumns; if ( currentClauseStack.getCurrent() == Clause.GROUP ) { // When the table group is known to be fetched i.e. a fetch join @@ -3108,7 +3208,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base expandToAllColumns = false; } - final EntityValuedModelPart mapping = (EntityValuedModelPart) resultPart; + final EntityValuedModelPart mapping = (EntityValuedModelPart) interpretationModelPart; EntityMappingType mappingType; if ( path instanceof SqmTreatedPath ) { mappingType = creationContext.getSessionFactory() @@ -3120,18 +3220,17 @@ public abstract class BaseSqmToSqlAstConverter extends Base mappingType = mapping.getEntityMappingType(); } - final TableGroup parentGroupToUse = findTableGroup( navigablePath.getParent() ); result = EntityValuedPathInterpretation.from( navigablePath, parentGroupToUse == null ? tableGroup : parentGroupToUse, - (EntityValuedModelPart) resultPart, + expandToAllColumns ? null : resultModelPart, + (EntityValuedModelPart) interpretationModelPart, mappingType, - expandToAllColumns, this ); } - else if ( resultPart instanceof EmbeddableValuedModelPart ) { - final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) keyPart; + else if ( interpretationModelPart instanceof EmbeddableValuedModelPart ) { + final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) resultModelPart; result = new EmbeddableValuedPathInterpretation<>( mapping.toSqlExpression( tableGroup, @@ -3140,15 +3239,15 @@ public abstract class BaseSqmToSqlAstConverter extends Base getSqlAstCreationState() ), navigablePath, - (EmbeddableValuedModelPart) resultPart, + (EmbeddableValuedModelPart) interpretationModelPart, tableGroup ); } else { - assert resultPart instanceof BasicValuedModelPart; - final BasicValuedModelPart mapping = (BasicValuedModelPart) keyPart; + assert interpretationModelPart instanceof BasicValuedModelPart; + final BasicValuedModelPart mapping = (BasicValuedModelPart) resultModelPart; final TableReference tableReference = tableGroup.resolveTableReference( - navigablePath.append( keyPart.getPartName() ), + navigablePath.append( resultModelPart.getPartName() ), mapping.getContainingTableExpression() ); @@ -3178,7 +3277,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base result = new BasicValuedPathInterpretation<>( columnReference, navigablePath, - (BasicValuedModelPart) resultPart, + (BasicValuedModelPart) interpretationModelPart, tableGroup ); } @@ -3328,7 +3427,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public Expression visitEntityValuedPath(SqmEntityValuedSimplePath sqmPath) { return withTreatRestriction( - prepareReusablePath( sqmPath, () -> EntityValuedPathInterpretation.from( sqmPath, this ) ), + prepareReusablePath( sqmPath, () -> EntityValuedPathInterpretation.from( sqmPath, getInferredValueMapping(), this ) ), sqmPath ); } @@ -4447,19 +4546,23 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public MappingModelExpressible determineValueMapping(SqmExpression sqmExpression) { + return determineValueMapping( sqmExpression, fromClauseIndexStack.getCurrent() ); + } + + private MappingModelExpressible determineValueMapping(SqmExpression sqmExpression, FromClauseIndex fromClauseIndex) { if ( sqmExpression instanceof SqmParameter ) { return determineValueMapping( (SqmParameter) sqmExpression ); } else if ( sqmExpression instanceof SqmPath ) { log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression ); - prepareReusablePath( (SqmPath) sqmExpression, () -> null ); + prepareReusablePath( (SqmPath) sqmExpression, fromClauseIndex, () -> null ); final MappingMetamodel domainModel = creationContext.getSessionFactory() .getRuntimeMetamodels() .getMappingMetamodel(); return SqmMappingModelHelper.resolveMappingModelExpressible( sqmExpression, domainModel, - getFromClauseAccess()::findTableGroup + fromClauseIndex::findTableGroup ); } // The model type of an enum literal is always inferred @@ -4492,12 +4595,12 @@ public abstract class BaseSqmToSqlAstConverter extends Base final SqmPath sqmPath = (SqmPath) subQuerySelection.getSelectableNode(); final NavigablePath navigablePath = sqmPath.getNavigablePath().getParent(); if ( navigablePath.getParent() != null ) { - final TableGroup parentTableGroup = findTableGroup( navigablePath.getParent() ); + final TableGroup parentTableGroup = fromClauseIndex.findTableGroup( navigablePath.getParent() ); final PluralAttributeMapping pluralPart = (PluralAttributeMapping) parentTableGroup.getModelPart() .findSubPart( navigablePath.getUnaliasedLocalName(), null ); return pluralPart.findSubPart( pathSource.getPathName(), null ); } - return findTableGroup( navigablePath ).getModelPart(); + return fromClauseIndex.findTableGroup( navigablePath ).getModelPart(); } } else { @@ -4537,7 +4640,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base .getMappingMetamodel(); final MappingModelExpressible valueMapping = domainModel.resolveMappingExpressible( nodeType, - this::findTableGroupByPath + fromClauseIndex::getTableGroup ); if ( valueMapping == null ) { @@ -4548,6 +4651,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base } if ( valueMapping == null ) { + // For literals it is totally possible that we can't figure out a mapping type + if ( sqmExpression instanceof SqmLiteral ) { + return null; + } throw new ConversionException( "Could not determine ValueMapping for SqmExpression: " + sqmExpression ); } @@ -4558,7 +4665,11 @@ public abstract class BaseSqmToSqlAstConverter extends Base final MappingModelExpressible inferredMapping = resolveInferredType(); if ( inferredMapping != null ) { if ( inferredMapping instanceof PluralAttributeMapping ) { - return ( (PluralAttributeMapping) inferredMapping ).getElementDescriptor(); + final CollectionPart elementDescriptor = ( (PluralAttributeMapping) inferredMapping ).getElementDescriptor(); + if ( elementDescriptor instanceof EntityCollectionPart ) { + return ( (EntityCollectionPart) elementDescriptor ).getEntityMappingType(); + } + return elementDescriptor; } else if ( !( inferredMapping instanceof JavaObjectType ) ) { // Never report back the "object type" as inferred type and instead rely on the value type @@ -4602,6 +4713,15 @@ public abstract class BaseSqmToSqlAstConverter extends Base return inferredValueMapping; } } + else if ( paramType instanceof EntityDomainType ) { + // In JPA Criteria, it is possible to define a parameter of an entity type, + // but that should infer the mapping type from context, + // otherwise this would default to binding the PK which might be wrong + final MappingModelExpressible inferredValueMapping = getInferredValueMapping(); + if ( inferredValueMapping != null ) { + return inferredValueMapping; + } + } final SqmExpressible paramSqmType = paramType.resolveExpressible( creationContext.getSessionFactory() ); @@ -4653,7 +4773,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base sqmParameterMappingModelTypes.put( expression, valueMapping ); final Bindable bindable; if ( valueMapping instanceof EntityAssociationMapping ) { - bindable = ( (EntityAssociationMapping) valueMapping ).getKeyTargetMatchPart(); + final EntityAssociationMapping mapping = (EntityAssociationMapping) valueMapping; + bindable = mapping.getForeignKeyDescriptor().getPart( mapping.getSideNature() ); } else if ( valueMapping instanceof EntityMappingType ) { bindable = ( (EntityMappingType) valueMapping ).getIdentifierMapping(); @@ -4905,10 +5026,11 @@ public abstract class BaseSqmToSqlAstConverter extends Base } else { // Infer one operand type through the other - inferrableTypeAccessStack.push( () -> determineValueMapping( rightOperand ) ); + final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); + inferrableTypeAccessStack.push( () -> determineValueMapping( rightOperand, fromClauseIndex ) ); final Expression lhs = toSqlExpression( leftOperand.accept( this ) ); inferrableTypeAccessStack.pop(); - inferrableTypeAccessStack.push( () -> determineValueMapping( leftOperand ) ); + inferrableTypeAccessStack.push( () -> determineValueMapping( leftOperand, fromClauseIndex ) ); final Expression rhs = toSqlExpression( rightOperand.accept( this ) ); inferrableTypeAccessStack.pop(); @@ -5435,7 +5557,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private X visitWithInferredType(SqmExpression expression, SqmExpression inferred) { - inferrableTypeAccessStack.push( () -> determineValueMapping( inferred ) ); + final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); + inferrableTypeAccessStack.push( () -> determineValueMapping( inferred, fromClauseIndex ) ); try { return (X) expression.accept( this ); } @@ -5762,7 +5885,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public ComparisonPredicate visitComparisonPredicate(SqmComparisonPredicate predicate) { - inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getRightHandExpression() ) ); + final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); + inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getRightHandExpression(), fromClauseIndex ) ); final Expression lhs; try { @@ -5772,7 +5896,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base inferrableTypeAccessStack.pop(); } - inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getLeftHandExpression() ) ); + inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getLeftHandExpression(), fromClauseIndex ) ); final Expression rhs; try { @@ -5862,14 +5986,15 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public BetweenPredicate visitBetweenPredicate(SqmBetweenPredicate predicate) { + final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); final Expression expression; final Expression lowerBound; final Expression upperBound; inferrableTypeAccessStack.push( () -> coalesceSuppliedValues( - () -> determineValueMapping( predicate.getLowerBound() ), - () -> determineValueMapping( predicate.getUpperBound() ) + () -> determineValueMapping( predicate.getLowerBound(), fromClauseIndex ), + () -> determineValueMapping( predicate.getUpperBound(), fromClauseIndex ) ) ); @@ -5882,8 +6007,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base inferrableTypeAccessStack.push( () -> coalesceSuppliedValues( - () -> determineValueMapping( predicate.getExpression() ), - () -> determineValueMapping( predicate.getUpperBound() ) + () -> determineValueMapping( predicate.getExpression(), fromClauseIndex ), + () -> determineValueMapping( predicate.getUpperBound(), fromClauseIndex ) ) ); try { @@ -5895,8 +6020,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base inferrableTypeAccessStack.push( () -> coalesceSuppliedValues( - () -> determineValueMapping( predicate.getExpression() ), - () -> determineValueMapping( predicate.getLowerBound() ) + () -> determineValueMapping( predicate.getExpression(), fromClauseIndex ), + () -> determineValueMapping( predicate.getLowerBound(), fromClauseIndex ) ) ); try { @@ -5960,10 +6085,11 @@ public abstract class BaseSqmToSqlAstConverter extends Base } // otherwise - no special case... + final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); inferrableTypeAccessStack.push( () -> { for ( SqmExpression listExpression : predicate.getListExpressions() ) { - final MappingModelExpressible mapping = determineValueMapping( listExpression ); + final MappingModelExpressible mapping = determineValueMapping( listExpression, fromClauseIndex ); if ( mapping != null ) { return mapping; } @@ -5985,7 +6111,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base getBooleanType() ); - inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getTestExpression() ) ); + inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getTestExpression(), fromClauseIndex ) ); try { for ( SqmExpression expression : predicate.getListExpressions() ) { @@ -6052,8 +6178,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base return inListPredicate; } + final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); inferrableTypeAccessStack.push( - () -> determineValueMapping( sqmPredicate.getTestExpression() ) + () -> determineValueMapping( sqmPredicate.getTestExpression(), fromClauseIndex ) ); try { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java index 2133b97c1e..a5de302338 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java @@ -12,13 +12,13 @@ import java.util.List; import java.util.function.Consumer; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityValuedModelPart; +import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; @@ -46,22 +46,67 @@ public class EntityValuedPathInterpretation extends AbstractSqmPathInterpreta public static EntityValuedPathInterpretation from( SqmEntityValuedSimplePath sqmPath, + MappingModelExpressible inferredMapping, SqmToSqlAstConverter sqlAstCreationState) { final TableGroup tableGroup = sqlAstCreationState .getFromClauseAccess() .findTableGroup( sqmPath.getLhs().getNavigablePath() ); - - final EntityValuedModelPart mapping = (EntityValuedModelPart) sqlAstCreationState + final EntityValuedModelPart pathMapping = (EntityValuedModelPart) sqlAstCreationState .getFromClauseAccess() .findTableGroup( sqmPath.getLhs().getNavigablePath() ) .getModelPart() .findSubPart( sqmPath.getReferencedPathSource().getPathName(), null ); + final EntityValuedModelPart mapping; + if ( inferredMapping instanceof EntityAssociationMapping ) { + final EntityAssociationMapping inferredAssociation = (EntityAssociationMapping) inferredMapping; + if ( pathMapping instanceof EntityAssociationMapping && inferredMapping != pathMapping ) { + // In here, the inferred mapping and the actual path mapping are association mappings, + // but for different associations, so we have to check if both associations point to the same target + final EntityAssociationMapping pathAssociation = (EntityAssociationMapping) pathMapping; + final ModelPart pathTargetPart = pathAssociation.getForeignKeyDescriptor() + .getPart( pathAssociation.getSideNature().inverse() ); + final ModelPart inferredTargetPart = inferredAssociation.getForeignKeyDescriptor() + .getPart( inferredAssociation.getSideNature().inverse() ); + // If the inferred association and path association targets are the same, we can use the path mapping type + // which will render the FK of the path association + if ( pathTargetPart == inferredTargetPart ) { + mapping = pathMapping; + } + else { + // Otherwise, we need to use the entity mapping type to force rendering the PK + // for e.g. `a.assoc1 = a.assoc2` when both associations have different target join columns + mapping = pathMapping.getEntityMappingType(); + } + } + else { + // This is the case when the inferred mapping is an association, but the path mapping is not, + // or the path mapping and the inferred mapping are for the same association + mapping = (EntityValuedModelPart) inferredMapping; + } + } + else { + mapping = pathMapping; + } + final ModelPart resultModelPart; + if ( mapping instanceof EntityAssociationMapping ) { + final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping; + final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart(); + if ( keyTargetMatchPart instanceof ToOneAttributeMapping ) { + resultModelPart = ( (ToOneAttributeMapping) keyTargetMatchPart ).getKeyTargetMatchPart(); + } + else { + resultModelPart = keyTargetMatchPart; + } + } + else { + resultModelPart = mapping.getEntityMappingType().getIdentifierMapping(); + } return from( sqmPath.getNavigablePath(), tableGroup, + resultModelPart, mapping, mapping, - false, sqlAstCreationState ); } @@ -69,15 +114,15 @@ public class EntityValuedPathInterpretation extends AbstractSqmPathInterpreta public static EntityValuedPathInterpretation from( NavigablePath navigablePath, TableGroup tableGroup, + ModelPart resultModelPart, EntityValuedModelPart mapping, EntityValuedModelPart treatedMapping, - boolean expandToAllColumns, SqmToSqlAstConverter sqlAstCreationState) { final SqlExpressionResolver sqlExprResolver = sqlAstCreationState.getSqlExpressionResolver(); final SessionFactoryImplementor sessionFactory = sqlAstCreationState.getCreationContext().getSessionFactory(); final Expression sqlExpression; - if ( expandToAllColumns ) { + if ( resultModelPart == null ) { final EntityMappingType entityMappingType = mapping.getEntityMappingType(); final EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping(); final EntityDiscriminatorMapping discriminatorMapping = entityMappingType.getDiscriminatorMapping(); @@ -113,19 +158,9 @@ public class EntityValuedPathInterpretation extends AbstractSqmPathInterpreta entityMappingType.forEachSelectable( selectableConsumer ); sqlExpression = new SqlTuple( expressions, entityMappingType ); } - else if ( mapping instanceof EntityAssociationMapping ) { - final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping; - final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart(); - final ModelPart lhsPart; - if ( keyTargetMatchPart instanceof ToOneAttributeMapping ) { - lhsPart = ( (ToOneAttributeMapping) keyTargetMatchPart ).getKeyTargetMatchPart(); - } - else { - lhsPart = keyTargetMatchPart; - } - - if ( lhsPart instanceof BasicValuedModelPart ) { - final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) lhsPart; + else { + if ( resultModelPart instanceof BasicValuedModelPart ) { + final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) resultModelPart; final TableReference tableReference = tableGroup.resolveTableReference( navigablePath, basicValuedModelPart.getContainingTableExpression() @@ -140,8 +175,8 @@ public class EntityValuedPathInterpretation extends AbstractSqmPathInterpreta ); } else { - final List expressions = new ArrayList<>( lhsPart.getJdbcTypeCount() ); - lhsPart.forEachSelectable( + final List expressions = new ArrayList<>( resultModelPart.getJdbcTypeCount() ); + resultModelPart.forEachSelectable( (selectionIndex, selectableMapping) -> { final TableReference tableReference = tableGroup.resolveTableReference( navigablePath, @@ -162,59 +197,9 @@ public class EntityValuedPathInterpretation extends AbstractSqmPathInterpreta ); } ); - sqlExpression = new SqlTuple( expressions, lhsPart ); + sqlExpression = new SqlTuple( expressions, resultModelPart ); } } - else { - assert mapping instanceof EntityMappingType; - - final TableGroup parentTableGroup = tableGroup; - final EntityMappingType entityMappingType = (EntityMappingType) mapping; - final EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping(); - if ( identifierMapping instanceof BasicEntityIdentifierMapping ) { - final BasicEntityIdentifierMapping simpleIdMapping = (BasicEntityIdentifierMapping) identifierMapping; - - final TableReference tableReference = parentTableGroup.resolveTableReference( - navigablePath, - simpleIdMapping.getContainingTableExpression() - ); - assert tableReference != null : "Could not resolve table-group : " + simpleIdMapping.getContainingTableExpression(); - - sqlExpression = sqlExprResolver.resolveSqlExpression( - createColumnReferenceKey( tableReference, simpleIdMapping.getSelectionExpression() ), - processingState -> new ColumnReference( - tableReference, - simpleIdMapping, - sessionFactory - ) - ); - } - else { - final List expressions = new ArrayList<>(); - identifierMapping.forEachSelectable( - (selectionIndex, selectableMapping) -> { - final TableReference tableReference = parentTableGroup.resolveTableReference( - navigablePath, selectableMapping.getContainingTableExpression() ); - - expressions.add( - sqlExprResolver.resolveSqlExpression( - createColumnReferenceKey( - tableReference, - selectableMapping.getSelectionExpression() - ), - processingState -> new ColumnReference( - tableReference, - selectableMapping, - sessionFactory - ) - ) - ); - } - ); - sqlExpression = new SqlTuple( expressions, identifierMapping ); - } - } - return new EntityValuedPathInterpretation<>( sqlExpression, navigablePath, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java index 041ebd6dc2..d62c6a4be4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java @@ -12,6 +12,7 @@ import java.util.function.Function; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.query.BindableType; @@ -51,7 +52,11 @@ public class SqmParameterInterpretation implements Expression, DomainResultProdu this.queryParameter = queryParameter; this.queryParameterBindingResolver = queryParameterBindingResolver; - if ( valueMapping instanceof EntityValuedModelPart ) { + if ( valueMapping instanceof EntityAssociationMapping ) { + final EntityAssociationMapping mapping = (EntityAssociationMapping) valueMapping; + this.valueMapping = mapping.getForeignKeyDescriptor().getPart( mapping.getSideNature() ); + } + else if ( valueMapping instanceof EntityValuedModelPart ) { this.valueMapping = ( (EntityValuedModelPart) valueMapping ).getEntityMappingType().getIdentifierMapping(); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java index 390a767183..67cf1b29ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java @@ -153,7 +153,8 @@ public abstract class AbstractSqmFrom extends AbstractSqmPath implements SqmPath resolvedPath = null; ModelPartContainer modelPartContainer = null; for ( SqmJoin sqmJoin : getSqmJoins() ) { - if ( sqmJoin instanceof SqmAttributeJoin + // We can only match singular joins here, as plural path parts are interpreted like sub-queries + if ( sqmJoin instanceof SqmSingularJoin && name.equals( sqmJoin.getReferencedPathSource().getPathName() ) ) { final SqmAttributeJoin attributeJoin = (SqmAttributeJoin) sqmJoin; if ( attributeJoin.getOn() == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java index 327adf9293..7daca4370b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java @@ -26,8 +26,9 @@ public class SqmCorrelatedBagJoin extends SqmBagJoin implements SqmC public SqmCorrelatedBagJoin(SqmBagJoin correlationParent) { super( correlationParent.getLhs(), + correlationParent.getNavigablePath(), correlationParent.getAttribute(), - null, + correlationParent.getExplicitAlias(), SqmJoinType.INNER, false, correlationParent.nodeBuilder() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java index 811d78918c..72dc71a8ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java @@ -23,8 +23,9 @@ public class SqmCorrelatedCrossJoin extends SqmCrossJoin implements SqmCor public SqmCorrelatedCrossJoin(SqmCrossJoin correlationParent) { super( + correlationParent.getNavigablePath(), correlationParent.getReferencedPathSource(), - null, + correlationParent.getExplicitAlias(), correlationParent.getRoot() ); this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java index a5820157e0..589ba9821d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java @@ -24,8 +24,9 @@ public class SqmCorrelatedEntityJoin extends SqmEntityJoin implements SqmC public SqmCorrelatedEntityJoin(SqmEntityJoin correlationParent) { super( + correlationParent.getNavigablePath(), correlationParent.getReferencedPathSource(), - null, + correlationParent.getExplicitAlias(), SqmJoinType.INNER, correlationParent.getRoot() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java index f94a42ff4e..4bae7141b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java @@ -26,8 +26,9 @@ public class SqmCorrelatedListJoin extends SqmListJoin implements Sq public SqmCorrelatedListJoin(SqmListJoin correlationParent) { super( correlationParent.getLhs(), + correlationParent.getNavigablePath(), correlationParent.getAttribute(), - null, + correlationParent.getExplicitAlias(), SqmJoinType.INNER, false, correlationParent.nodeBuilder() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java index 0a4ccf8246..3c01110409 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java @@ -26,8 +26,9 @@ public class SqmCorrelatedMapJoin extends SqmMapJoin implement public SqmCorrelatedMapJoin(SqmMapJoin correlationParent) { super( correlationParent.getLhs(), + correlationParent.getNavigablePath(), correlationParent.getAttribute(), - null, + correlationParent.getExplicitAlias(), SqmJoinType.INNER, false, correlationParent.nodeBuilder() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java index cf6afb2a93..ea01981c1e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java @@ -22,8 +22,9 @@ public class SqmCorrelatedPluralPartJoin extends SqmPluralPartJoin i public SqmCorrelatedPluralPartJoin(SqmPluralPartJoin correlationParent) { super( (SqmFrom) correlationParent.getLhs(), + correlationParent.getNavigablePath(), correlationParent.getReferencedPathSource(), - null, + correlationParent.getExplicitAlias(), SqmJoinType.INNER, correlationParent.nodeBuilder() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java index b74a202a8b..f4c754ab3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java @@ -22,7 +22,7 @@ public class SqmCorrelatedRoot extends SqmRoot implements SqmPathWrapper extends SqmSetJoin implements SqmC public SqmCorrelatedSetJoin(SqmSetJoin correlationParent) { super( correlationParent.getLhs(), + correlationParent.getNavigablePath(), correlationParent.getAttribute(), - null, + correlationParent.getExplicitAlias(), SqmJoinType.INNER, false, correlationParent.nodeBuilder() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java index af1e232fcb..9b14bc7e14 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java @@ -26,8 +26,9 @@ public class SqmCorrelatedSingularJoin extends SqmSingularJoin imple public SqmCorrelatedSingularJoin(SqmSingularJoin correlationParent) { super( correlationParent.getLhs(), + correlationParent.getNavigablePath(), correlationParent.getAttribute(), - null, + correlationParent.getExplicitAlias(), SqmJoinType.INNER, false, correlationParent.nodeBuilder() diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 99600a8f6d..817d7e7b55 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -24,51 +24,49 @@ import java.util.function.Consumer; import java.util.function.Function; import org.hibernate.LockMode; +import org.hibernate.LockOptions; import org.hibernate.QueryException; +import org.hibernate.dialect.Dialect; import org.hibernate.dialect.RowLockStrategy; import org.hibernate.dialect.SelectItemReferenceStrategy; -import org.hibernate.internal.util.MathHelper; -import org.hibernate.metamodel.mapping.AttributeMapping; -import org.hibernate.metamodel.mapping.EntityAssociationMapping; -import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.Queryable; -import org.hibernate.persister.internal.SqlFragmentPredicate; -import org.hibernate.query.IllegalQueryOperationException; -import org.hibernate.query.sqm.FrameExclusion; -import org.hibernate.query.sqm.FrameKind; -import org.hibernate.query.sqm.FrameMode; -import org.hibernate.query.sqm.SetOperator; -import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; -import org.hibernate.sql.ast.tree.cte.CteMaterialization; -import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind; -import org.hibernate.query.sqm.FetchClauseType; -import org.hibernate.LockOptions; -import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.AbstractDelegatingWrapperOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.FilterJdbcParameter; +import org.hibernate.internal.util.MathHelper; import org.hibernate.internal.util.StringHelper; 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.AttributeMapping; +import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; -import org.hibernate.query.sqm.ComparisonOperator; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.persister.internal.SqlFragmentPredicate; +import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.spi.Limit; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.sqm.ComparisonOperator; +import org.hibernate.query.sqm.FetchClauseType; +import org.hibernate.query.sqm.FrameExclusion; +import org.hibernate.query.sqm.FrameKind; +import org.hibernate.query.sqm.FrameMode; import org.hibernate.query.sqm.NullPrecedence; +import org.hibernate.query.sqm.SetOperator; import org.hibernate.query.sqm.SortOrder; import org.hibernate.query.sqm.UnaryArithmeticOperator; -import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; import org.hibernate.query.sqm.sql.internal.SqmParameterInterpretation; +import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstJoinType; @@ -80,6 +78,8 @@ import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.cte.CteColumn; import org.hibernate.sql.ast.tree.cte.CteContainer; +import org.hibernate.sql.ast.tree.cte.CteMaterialization; +import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind; import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification; import org.hibernate.sql.ast.tree.delete.DeleteStatement; @@ -3608,9 +3608,12 @@ public abstract class AbstractSqlAstTranslator implemen clauseStack.push( Clause.FROM ); String separator = NO_SEPARATOR; for ( TableGroup root : fromClause.getRoots() ) { - appendSql( separator ); - renderRootTableGroup( root, null ); - separator = COMA_SEPARATOR; + // Skip virtual table group roots which we use for simple correlations + if ( !( root instanceof VirtualTableGroup ) ) { + appendSql( separator ); + renderRootTableGroup( root, null ); + separator = COMA_SEPARATOR; + } } } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/FromClauseAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/FromClauseAccess.java index 435e06c65c..d4cc4ba176 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/FromClauseAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/FromClauseAccess.java @@ -20,7 +20,12 @@ import org.hibernate.sql.ast.tree.from.TableGroup; */ public interface FromClauseAccess { - TableGroup findTableGroupOnLeaf(NavigablePath navigablePath); + /** + * Find a TableGroup by the NavigablePath it is registered under, + * and if not found on the current from clause level, ask the parent. Returns + * {@code null} if no TableGroup is registered under that NavigablePath + */ + TableGroup findTableGroupOnParents(NavigablePath navigablePath); /** * Find a TableGroup by the NavigablePath it is registered under. Returns diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java index 09ec75f21b..641726528d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java @@ -32,17 +32,17 @@ public class SimpleFromClauseAccessImpl implements FromClauseAccess { } @Override - public TableGroup findTableGroupOnLeaf(NavigablePath navigablePath) { - return tableGroupMap.get( navigablePath ); + public TableGroup findTableGroupOnParents(NavigablePath navigablePath) { + final TableGroup tableGroup = tableGroupMap.get( navigablePath ); + if ( tableGroup == null && parent != null ) { + return parent.findTableGroupOnParents( navigablePath ); + } + return tableGroup; } @Override public TableGroup findTableGroup(NavigablePath navigablePath) { - final TableGroup tableGroup = tableGroupMap.get( navigablePath ); - if ( tableGroup == null && parent != null ) { - return parent.findTableGroup( navigablePath ); - } - return tableGroup; + return tableGroupMap.get( navigablePath ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MutatingTableReferenceGroupWrapper.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MutatingTableReferenceGroupWrapper.java index 90c9482d1c..a0b7171ba2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MutatingTableReferenceGroupWrapper.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MutatingTableReferenceGroupWrapper.java @@ -20,7 +20,7 @@ import org.hibernate.query.spi.NavigablePath; * * @author Steve Ebersole */ -public class MutatingTableReferenceGroupWrapper implements VirtualTableGroup { +public class MutatingTableReferenceGroupWrapper implements TableGroup { private final NavigablePath navigablePath; private final ModelPartContainer modelPart; private final NamedTableReference mutatingTableReference; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableGroup.java index c3813601f6..99529f695a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableGroup.java @@ -16,7 +16,7 @@ import org.hibernate.query.spi.NavigablePath; /** * @author Andrea Boriero */ -public class UnionTableGroup extends AbstractTableGroup implements VirtualTableGroup { +public class UnionTableGroup extends AbstractTableGroup { private final UnionTableReference tableReference; public UnionTableGroup( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/indexcoll/IndexedCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/indexcoll/IndexedCollectionTest.java index b0df0a30ec..78d1303a4f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/indexcoll/IndexedCollectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/indexcoll/IndexedCollectionTest.java @@ -869,4 +869,12 @@ public class IndexedCollectionTest { } ); } + + @Test + public void testQueryWithKeywordAsFromAlias(SessionFactoryScope scope) { + // This test would fail if we didn't use the proper parsing rule for the FROM alias + scope.inSession( + s -> s.createQuery( "from Version version" ).getResultList() + ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/subquery/SubqueryInSelectClauseTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/subquery/SubqueryInSelectClauseTest.java index 526ea2dd4b..6c9e845e4f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/subquery/SubqueryInSelectClauseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/subquery/SubqueryInSelectClauseTest.java @@ -35,7 +35,7 @@ public class SubqueryInSelectClauseTest extends AbstractSubqueryInSelectClauseTe Subquery personCount = query.subquery( Long.class ); Root person = personCount.from( Person.class ); - personCount.select( cb.count( person ) ).where( cb.equal( contacts.get( "id" ), person.get( "id" ) ) ); + personCount.select( cb.count( person ) ).where( cb.equal( personCount.correlate( contacts ).get( "id" ), person.get( "id" ) ) ); query.multiselect( document.get( "id" ), personCount.getSelection() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/valuehandlingmode/inline/NonPkAssociationEqualityPredicateParameterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/valuehandlingmode/inline/NonPkAssociationEqualityPredicateParameterTest.java new file mode 100644 index 0000000000..f2493cc1f3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/valuehandlingmode/inline/NonPkAssociationEqualityPredicateParameterTest.java @@ -0,0 +1,173 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.jpa.criteria.valuehandlingmode.inline; + +import java.util.List; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Jpa( + annotatedClasses = { + NonPkAssociationEqualityPredicateParameterTest.Customer.class, + NonPkAssociationEqualityPredicateParameterTest.Order.class + } + , properties = @Setting(name = AvailableSettings.CRITERIA_VALUE_HANDLING_MODE, value = "bind") +) +public class NonPkAssociationEqualityPredicateParameterTest { + + @Test + public void testEqualityCheck(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery orderCriteria = builder.createQuery( Order.class ); + Root orderRoot = orderCriteria.from( Order.class ); + + orderCriteria.select( orderRoot ); + Customer c = new Customer(); + c.customerNumber = 123L; + orderCriteria.where( + builder.equal( orderRoot.get( "customer" ), c ) + ); + + List orders = entityManager.createQuery( orderCriteria ).getResultList(); + assertTrue( orders.size() == 0 ); + } + ); + } + + @Entity + @Table(name = "ORDER_TABLE") + public static class Order { + private String id; + private double totalPrice; + private Customer customer; + + public Order() { + } + + public Order(String id, double totalPrice) { + this.id = id; + this.totalPrice = totalPrice; + } + + public Order(String id, Customer customer) { + this.id = id; + this.customer = customer; + } + + public Order(String id) { + this.id = id; + } + + @Id + @Column(name = "ID") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Column(name = "TOTALPRICE") + public double getTotalPrice() { + return totalPrice; + } + + public void setTotalPrice(double price) { + this.totalPrice = price; + } + + @ManyToOne + @JoinColumn(name = "FK4_FOR_CUSTOMER_TABLE", referencedColumnName = "CUSTOMER_NUMBER") + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + } + + @Entity + @Table(name = "CUSTOMER_TABLE") + public static class Customer { + private String id; + private Long customerNumber; + private String name; + private Integer age; + + public Customer() { + } + + public Customer(String id, String name) { + this.id = id; + this.name = name; + } + + // Used by test case for HHH-8699. + public Customer(String id, String name, String greeting, Boolean something) { + this.id = id; + this.name = name; + } + + @Id + @Column(name = "ID") + public String getId() { + return id; + } + + public void setId(String v) { + this.id = v; + } + + @Column(name = "CUSTOMER_NUMBER", unique = true) + public Long getCustomerNumber() { + return customerNumber; + } + + public void setCustomerNumber(Long customerNumber) { + this.customerNumber = customerNumber; + } + + @Column(name = "NAME") + public String getName() { + return name; + } + + public void setName(String v) { + this.name = v; + } + + @Column(name = "AGE") + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/valuehandlingmode/inline/NonPkAssociationEqualityPredicateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/valuehandlingmode/inline/NonPkAssociationEqualityPredicateTest.java index 53fa26ca4c..fa55c0b773 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/valuehandlingmode/inline/NonPkAssociationEqualityPredicateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/valuehandlingmode/inline/NonPkAssociationEqualityPredicateTest.java @@ -60,8 +60,17 @@ public class NonPkAssociationEqualityPredicateTest { } ); } + @Test + public void testDifferentAssociationsEqualityCheck(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + // This fails because we compare a ToOne with non-PK to something with a EntityValuedModelPart which defaults to the PK mapping + entityManager.createQuery( "from Order o, Customer c where o.customer = c" ).getResultList(); + } + ); + } - @Entity + @Entity(name = "Order") @Table(name = "ORDER_TABLE") public static class Order { private String id; @@ -115,7 +124,7 @@ public class NonPkAssociationEqualityPredicateTest { } } - @Entity + @Entity(name = "Customer") @Table(name = "CUSTOMER_TABLE") public static class Customer { private String id; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/IsEmptyPredicateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/IsEmptyPredicateTest.java index 86ed890a2b..7634d7de06 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/IsEmptyPredicateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/IsEmptyPredicateTest.java @@ -52,6 +52,15 @@ public class IsEmptyPredicateTest { } ); } + @Test + public void testEmptinessPredicatesWithJoin(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + final List ids = session.createQuery( "select p.id from Person p left join p.nicknames n where p.nicknames is not empty", Integer.class ).list(); + assertThat( ids ).contains( personaWithSingleNicknameId, personWithMultipleNicknamesId ); + assertThat( ids ).doesNotContain( personWithoutNicknameId ); + } ); + } + @BeforeEach protected void prepareTestData(SessionFactoryScope scope) { scope.inTransaction( (session) -> {