From d5d350e5e7052f13847f482d38c0182183746227 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 20 Dec 2021 16:14:46 +0100 Subject: [PATCH] HHH-11433 Allow usage of KEY expression in a join --- .../domain/internal/EntitySqmPathSource.java | 30 ++++- .../internal/QualifiedJoinPathConsumer.java | 20 +-- .../query/hql/internal/QuerySplitter.java | 22 ++++ .../hql/internal/SemanticQueryBuilder.java | 58 ++++++++- .../query/sqm/SemanticQueryWalker.java | 3 + .../org/hibernate/query/sqm/SqmJoinable.java | 7 +- .../query/sqm/internal/SqmTreePrinter.java | 12 ++ .../sqm/spi/BaseSemanticQueryWalker.java | 8 ++ .../sqm/sql/BaseSqmToSqlAstConverter.java | 85 ++++++++++--- .../domain/SqmCorrelatedPluralPartJoin.java | 52 ++++++++ .../sqm/tree/domain/SqmPluralPartJoin.java | 114 ++++++++++++++++++ .../sqm/tree/domain/SqmSingularJoin.java | 10 ++ .../tree/domain/SqmTreatedPluralPartJoin.java | 72 +++++++++++ .../expression/AbstractSqmExpression.java | 2 +- .../CollectionMapWithComponentValueTest.java | 41 ++++++- 15 files changed, 496 insertions(+), 40 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/hql/CollectionMapWithComponentValueTest.java (84%) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntitySqmPathSource.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntitySqmPathSource.java index 23a3a39c95..a8e86bc65a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntitySqmPathSource.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntitySqmPathSource.java @@ -8,14 +8,21 @@ package org.hibernate.metamodel.model.domain.internal; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.NavigablePath; +import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.SqmJoinable; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; +import org.hibernate.query.sqm.tree.domain.SqmSingularJoin; +import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; +import org.hibernate.query.sqm.tree.from.SqmFrom; /** * @author Steve Ebersole */ -public class EntitySqmPathSource extends AbstractSqmPathSource { +public class EntitySqmPathSource extends AbstractSqmPathSource implements SqmJoinable { public EntitySqmPathSource( String localPathName, EntityDomainType domainType, @@ -51,4 +58,25 @@ public class EntitySqmPathSource extends AbstractSqmPathSource { lhs.nodeBuilder() ); } + + @Override + public SqmPluralPartJoin createSqmJoin( + SqmFrom lhs, + SqmJoinType joinType, + String alias, + boolean fetched, + SqmCreationState creationState) { + return new SqmPluralPartJoin<>( + lhs, + this, + alias, + joinType, + creationState.getCreationContext().getNodeBuilder() + ); + } + + @Override + public String getName() { + return getPathName(); + } } 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 905a720173..8b2c3efba0 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 @@ -21,7 +21,6 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor; -import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmJoin; @@ -45,7 +44,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { private final String alias; private ConsumerDelegate delegate; - private boolean treated; + private boolean nested; public QualifiedJoinPathConsumer( SqmRoot sqmRoot, @@ -80,8 +79,12 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { ); } - public void setTreated(boolean treated) { - this.treated = treated; + public boolean isNested() { + return nested; + } + + public void setNested(boolean nested) { + this.nested = nested; } @Override @@ -91,13 +94,12 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { @Override public void consumeIdentifier(String identifier, boolean isBase, boolean isTerminal) { - if ( isBase ) { - assert delegate == null; - delegate = resolveBase( identifier, !treated && isTerminal ); + if ( isBase && delegate == null ) { + delegate = resolveBase( identifier, !nested && isTerminal ); } else { assert delegate != null; - delegate.consumeIdentifier( identifier, !treated && isTerminal ); + delegate.consumeIdentifier( identifier, !nested && isTerminal ); } } @@ -194,7 +196,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { } } @SuppressWarnings("unchecked") - final SqmAttributeJoin join = ( (SqmJoinable) subPathSource ).createSqmJoin( + final SqmJoin join = ( (SqmJoinable) subPathSource ).createSqmJoin( lhs, joinType, isTerminal ? alias : SqmCreationHelper.IMPLICIT_ALIAS, diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java index ba5fe06ffb..f57211c9f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java @@ -33,6 +33,7 @@ import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; @@ -410,6 +411,27 @@ public class QuerySplitter { return copy; } + @Override + public SqmPluralPartJoin visitPluralPartJoin(SqmPluralPartJoin join) { + final SqmFrom sqmFrom = sqmFromCopyMap.get( join ); + if ( sqmFrom != null ) { + return (SqmPluralPartJoin) sqmFrom; + } + final SqmFrom newLhs = (SqmFrom) sqmFromCopyMap.get( join.getLhs() ); + final SqmPluralPartJoin copy = new SqmPluralPartJoin<>( + newLhs, + join.getReferencedPathSource(), + join.getExplicitAlias(), + join.getSqmJoinType(), + join.nodeBuilder() + ); + getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); + sqmFromCopyMap.put( join, copy ); + sqmPathCopyMap.put( join.getNavigablePath(), copy ); + newLhs.addSqmJoin( copy ); + return copy; + } + @Override public SqmEntityJoin visitQualifiedEntityJoin(SqmEntityJoin join) { final SqmFrom sqmFrom = sqmFromCopyMap.get( join ); 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 fb1d5aeaa8..eb2d686738 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 @@ -1660,7 +1660,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem if ( join instanceof SqmEntityJoin ) { sqmRoot.addSqmJoin( join ); } - else { + else if ( join instanceof SqmAttributeJoin ) { final SqmAttributeJoin attributeJoin = (SqmAttributeJoin) join; if ( getCreationOptions().useStrictJpaCompliance() ) { if ( join.getExplicitAlias() != null ) { @@ -4106,7 +4106,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem public SqmPath visitTreatedNavigablePath(HqlParser.TreatedNavigablePathContext ctx) { final DotIdentifierConsumer consumer = dotIdentifierConsumerStack.getCurrent(); if ( consumer instanceof QualifiedJoinPathConsumer ) { - ( (QualifiedJoinPathConsumer) consumer ).setTreated( true ); + ( (QualifiedJoinPathConsumer) consumer ).setNested( true ); } consumeManagedTypeReference( (HqlParser.PathContext) ctx.getChild( 2 ) ); @@ -4189,21 +4189,67 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override @SuppressWarnings({ "rawtypes" }) public SqmPath visitMapKeyNavigablePath(HqlParser.MapKeyNavigablePathContext ctx) { + final DotIdentifierConsumer consumer = dotIdentifierConsumerStack.getCurrent(); + final boolean madeNested; + if ( consumer instanceof QualifiedJoinPathConsumer ) { + final QualifiedJoinPathConsumer qualifiedJoinPathConsumer = (QualifiedJoinPathConsumer) consumer; + madeNested = !qualifiedJoinPathConsumer.isNested(); + if ( madeNested ) { + qualifiedJoinPathConsumer.setNested( true ); + } + } + else { + madeNested = false; + } final SqmPath sqmPath = consumeDomainPath( (HqlParser.PathContext) ctx.getChild( 2 ) ); + final boolean hasContinuation = ctx.getChildCount() == 5; SqmPath result; if ( sqmPath instanceof SqmMapJoin ) { final SqmMapJoin sqmMapJoin = (SqmMapJoin) sqmPath; - result = sqmMapJoin.key(); + if ( consumer instanceof QualifiedJoinPathConsumer ) { + if ( madeNested && !hasContinuation ) { + // Reset the nested state before consuming the terminal identifier + ( (QualifiedJoinPathConsumer) consumer ).setNested( false ); + } + consumer.consumeIdentifier( CollectionPart.Nature.INDEX.getName(), false, !hasContinuation ); + result = (SqmPath) consumer.getConsumedPart(); + } + else { + result = sqmMapJoin.key(); + } } else { assert sqmPath instanceof SqmPluralValuedSimplePath; final SqmPluralValuedSimplePath mapPath = (SqmPluralValuedSimplePath) sqmPath; - result = mapPath.resolvePathPart( CollectionPart.Nature.INDEX.getName(), true, this ); + result = mapPath.resolvePathPart( CollectionPart.Nature.INDEX.getName(), !hasContinuation, this ); } - if ( ctx.getChildCount() == 5 ) { - result = consumeDomainPath( (HqlParser.DotIdentifierSequenceContext) ctx.getChild( 4 ).getChild( 1 ) ); + if ( hasContinuation ) { + if ( madeNested ) { + // Reset the nested state before consuming the terminal identifier + ( (QualifiedJoinPathConsumer) consumer ).setNested( false ); + } + final HqlParser.DotIdentifierSequenceContext identCtx = (HqlParser.DotIdentifierSequenceContext) ctx.getChild( 4 ) + .getChild( 1 ); + if ( consumer instanceof QualifiedJoinPathConsumer ) { + result = consumeDomainPath( identCtx ); + } + else { + dotIdentifierConsumerStack.push( + new BasicDotIdentifierConsumer( result, this ) { + @Override + protected void reset() { + } + } + ); + try { + result = consumeDomainPath( identCtx ); + } + finally { + dotIdentifierConsumerStack.pop(); + } + } } return result; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java index 6141197208..4b68bf937b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java @@ -25,6 +25,7 @@ import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath; import org.hibernate.query.sqm.tree.domain.SqmMaxIndexPath; import org.hibernate.query.sqm.tree.domain.SqmMinElementPath; import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath; +import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.SqmAny; @@ -130,6 +131,8 @@ public interface SemanticQueryWalker { T visitCrossJoin(SqmCrossJoin joinedFromElement); + T visitPluralPartJoin(SqmPluralPartJoin joinedFromElement); + T visitQualifiedEntityJoin(SqmEntityJoin joinedFromElement); T visitQualifiedAttributeJoin(SqmAttributeJoin joinedFromElement); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmJoinable.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmJoinable.java index 422cbbe0b9..2a46e4e4de 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmJoinable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmJoinable.java @@ -10,6 +10,7 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmJoin; /** * Specialization for attributes that that can be used in creating SQM joins @@ -19,9 +20,9 @@ import org.hibernate.query.sqm.tree.from.SqmFrom; * * @author Steve Ebersole */ -public interface SqmJoinable { - SqmAttributeJoin createSqmJoin( - SqmFrom lhs, +public interface SqmJoinable { + SqmJoin createSqmJoin( + SqmFrom lhs, SqmJoinType joinType, String alias, boolean fetched, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java index ccd869d269..6ea630336c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java @@ -29,6 +29,7 @@ import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath; import org.hibernate.query.sqm.tree.domain.SqmMaxIndexPath; import org.hibernate.query.sqm.tree.domain.SqmMinElementPath; import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath; +import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; @@ -505,6 +506,17 @@ public class SqmTreePrinter implements SemanticQueryWalker { return null; } + @Override + public Object visitPluralPartJoin(SqmPluralPartJoin joinedFromElement) { + processStanza( + "plural-part", + '`' + joinedFromElement.getNavigablePath().getFullPath() + '`', + () -> processJoins( joinedFromElement ) + ); + + return null; + } + private boolean inJoinPredicate; private void processJoinPredicate(SqmQualifiedJoin joinedFromElement) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java index 8076aa4ed5..9df99943c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java @@ -28,6 +28,7 @@ import org.hibernate.query.sqm.tree.domain.SqmMaxIndexPath; import org.hibernate.query.sqm.tree.domain.SqmMinElementPath; import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath; import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; @@ -249,6 +250,13 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker joinedFromElement) { + joinedFromElement.visitReusablePaths( path -> path.accept( this ) ); + joinedFromElement.visitSqmJoins( sqmJoin -> sqmJoin.accept( this ) ); + return joinedFromElement; + } + @Override public Object visitQualifiedEntityJoin(SqmEntityJoin joinedFromElement) { joinedFromElement.visitReusablePaths( path -> path.accept( this ) ); 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 431f8d24f6..7a878738ee 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 @@ -101,6 +101,7 @@ import org.hibernate.query.sqm.produce.function.internal.PatternRenderer; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRootJoin; import org.hibernate.query.sqm.tree.domain.SqmCorrelation; +import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression; @@ -114,7 +115,6 @@ import org.hibernate.sql.ast.tree.from.QueryPartTableGroup; import org.hibernate.sql.ast.tree.from.QueryPartTableReference; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.exec.internal.VersionTypeSeedParameterSpecification; -import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.query.BinaryArithmeticOperator; @@ -126,8 +126,6 @@ import org.hibernate.query.QueryLogging; import org.hibernate.query.SemanticException; import org.hibernate.query.TemporalUnit; import org.hibernate.query.UnaryArithmeticOperator; -import org.hibernate.query.criteria.JpaPath; -import org.hibernate.query.internal.QueryHelper; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindings; @@ -138,10 +136,8 @@ import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; import org.hibernate.query.sqm.function.SelfRenderingAggregateFunctionSqlAstExpression; -import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmMappingModelHelper; -import org.hibernate.query.sqm.produce.function.internal.PatternRenderer; import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker; import org.hibernate.query.sqm.sql.internal.BasicValuedPathInterpretation; import org.hibernate.query.sqm.sql.internal.DiscriminatedAssociationPathInterpretation; @@ -157,7 +153,6 @@ import org.hibernate.query.sqm.sql.internal.SqmMapEntryResult; import org.hibernate.query.sqm.sql.internal.SqmParameterInterpretation; import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; import org.hibernate.query.sqm.sql.internal.TypeHelper; -import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.cte.SqmCteContainer; @@ -170,8 +165,6 @@ import org.hibernate.query.sqm.tree.domain.AbstractSqmSpecificPluralPartPath; import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath; import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath; -import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRootJoin; -import org.hibernate.query.sqm.tree.domain.SqmCorrelation; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath; @@ -183,7 +176,6 @@ import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; -import org.hibernate.query.sqm.tree.domain.SqmTreatedRoot; import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; @@ -208,7 +200,6 @@ import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmLiteral; import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType; import org.hibernate.query.sqm.tree.expression.SqmLiteralNull; -import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression; import org.hibernate.query.sqm.tree.expression.SqmNamedParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType; @@ -227,7 +218,6 @@ import org.hibernate.query.sqm.tree.from.SqmFromClause; import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; -import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.query.sqm.tree.predicate.SqmAndPredicate; @@ -304,21 +294,16 @@ import org.hibernate.sql.ast.tree.expression.ExtractUnit; import org.hibernate.sql.ast.tree.expression.Format; import org.hibernate.sql.ast.tree.expression.JdbcLiteral; import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression; -import org.hibernate.sql.ast.tree.expression.Over; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression; -import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression; import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression; import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.expression.Star; import org.hibernate.sql.ast.tree.expression.Summarization; import org.hibernate.sql.ast.tree.expression.TrimSpecification; import org.hibernate.sql.ast.tree.expression.UnaryOperation; -import org.hibernate.sql.ast.tree.from.CorrelatedPluralTableGroup; import org.hibernate.sql.ast.tree.from.CorrelatedTableGroup; import org.hibernate.sql.ast.tree.from.LazyTableGroup; -import org.hibernate.sql.ast.tree.from.PluralTableGroup; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; @@ -329,7 +314,6 @@ import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; -import org.hibernate.sql.ast.tree.predicate.FilterPredicate; import org.hibernate.sql.ast.tree.predicate.GroupedPredicate; import org.hibernate.sql.ast.tree.predicate.InListPredicate; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; @@ -351,7 +335,6 @@ import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.exec.internal.JdbcParameterImpl; import org.hibernate.sql.exec.internal.JdbcParametersImpl; -import org.hibernate.sql.exec.internal.VersionTypeSeedParameterSpecification; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParameters; @@ -2469,6 +2452,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base else if ( sqmJoin instanceof SqmEntityJoin ) { return consumeEntityJoin( ( (SqmEntityJoin) sqmJoin ), lhsTableGroup, transitive ); } + else if ( sqmJoin instanceof SqmPluralPartJoin ) { + return consumePluralPartJoin( ( (SqmPluralPartJoin) sqmJoin ), ownerTableGroup, transitive ); + } else { throw new InterpretationException( "Could not resolve SqmJoin [" + sqmJoin.getNavigablePath() + "] to TableGroupJoin" ); } @@ -2694,6 +2680,31 @@ public abstract class BaseSqmToSqlAstConverter extends Base return tableGroup; } + private TableGroup consumePluralPartJoin(SqmPluralPartJoin sqmJoin, TableGroup lhsTableGroup, boolean transitive) { + final PluralTableGroup pluralTableGroup = (PluralTableGroup) lhsTableGroup; + final TableGroup tableGroup = getPluralPartTableGroup( pluralTableGroup, sqmJoin.getReferencedPathSource() ); + getFromClauseIndex().register( sqmJoin, tableGroup ); + + assert sqmJoin.getJoinPredicate() == null; + if ( transitive ) { + consumeExplicitJoins( sqmJoin, tableGroup ); + } + return tableGroup; + } + + private TableGroup getPluralPartTableGroup(PluralTableGroup pluralTableGroup, SqmPathSource pathSource) { + final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( pathSource.getPathName() ); + if ( nature != null ) { + switch ( nature ) { + case INDEX: + return pluralTableGroup.getIndexTableGroup(); + case ELEMENT: + return pluralTableGroup.getElementTableGroup(); + } + } + throw new UnsupportedOperationException( "Unsupported plural part join nature: " + nature ); + } + private X prepareReusablePath(SqmPath sqmPath, Supplier supplier) { final Consumer implicitJoinChecker; if ( getCurrentProcessingState() instanceof SqlAstQueryPartProcessingState ) { @@ -2719,6 +2730,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base else { parentPath = sqmPath.getParentPath(); } + if ( parentPath == null ) { + return null; + } final TableGroup tableGroup = fromClauseIndex.findTableGroup( parentPath.getNavigablePath() ); if ( tableGroup == null ) { final TableGroup parentTableGroup = prepareReusablePath( @@ -2923,6 +2937,20 @@ public abstract class BaseSqmToSqlAstConverter extends Base throw new InterpretationException( "SqmCrossJoin not yet resolved to TableGroup" ); } + @Override + public Object visitPluralPartJoin(SqmPluralPartJoin sqmJoin) { + // todo (6.0) : have this resolve to TableGroup instead? + // - trying to remove tracking of TableGroupJoin in the x-refs + + final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() ); + if ( existing != null ) { + log.tracef( "SqmPluralPartJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); + return visitTableGroup( existing, sqmJoin ); + } + + throw new InterpretationException( "SqmPluralPartJoin not yet resolved to TableGroup" ); + } + @Override public Expression visitQualifiedEntityJoin(SqmEntityJoin sqmJoin) { // todo (6.0) : have this resolve to TableGroup instead? @@ -4165,6 +4193,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base final MappingMetamodel domainModel = getCreationContext().getDomainModel(); if ( sqmExpression instanceof SqmPath ) { log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression ); + prepareReusablePath( (SqmPath) sqmExpression, () -> null ); return SqmMappingModelHelper.resolveMappingModelExpressable( sqmExpression, domainModel, @@ -5547,9 +5576,27 @@ public abstract class BaseSqmToSqlAstConverter extends Base } // otherwise - no special case... + inferrableTypeAccessStack.push( + () -> { + for ( SqmExpression listExpression : predicate.getListExpressions() ) { + final MappingModelExpressable mapping = determineValueMapping( listExpression ); + if ( mapping != null ) { + return mapping; + } + } + return null; + } + ); + final Expression testExpression; + try { + testExpression = (Expression) predicate.getTestExpression().accept( this ); + } + finally { + inferrableTypeAccessStack.pop(); + } final InListPredicate inPredicate = new InListPredicate( - (Expression) predicate.getTestExpression().accept( this ), + testExpression, predicate.isNegated(), getBooleanType() ); 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 new file mode 100644 index 0000000000..e8bb43ade7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.tree.domain; + +import org.hibernate.query.sqm.tree.SqmJoinType; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmRoot; + +/** + * @author Christian Beikov + */ +public class SqmCorrelatedPluralPartJoin extends SqmPluralPartJoin implements SqmCorrelation { + + private final SqmCorrelatedRootJoin correlatedRootJoin; + private final SqmPluralPartJoin correlationParent; + + public SqmCorrelatedPluralPartJoin(SqmPluralPartJoin correlationParent) { + super( + (SqmFrom) correlationParent.getLhs(), + correlationParent.getReferencedPathSource(), + null, + SqmJoinType.INNER, + correlationParent.nodeBuilder() + ); + this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); + this.correlationParent = correlationParent; + } + + @Override + public SqmPluralPartJoin getCorrelationParent() { + return correlationParent; + } + + @Override + public SqmPath getWrappedPath() { + return correlationParent; + } + + @Override + public boolean isCorrelated() { + return true; + } + + @Override + public SqmRoot getCorrelatedRoot() { + return correlatedRootJoin; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java new file mode 100644 index 0000000000..15d1b12282 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java @@ -0,0 +1,114 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.tree.domain; + +import java.util.Locale; + +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.query.NavigablePath; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.spi.SqmCreationHelper; +import org.hibernate.query.sqm.sql.internal.DomainResultProducer; +import org.hibernate.query.sqm.tree.SqmJoinType; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin; +import org.hibernate.query.sqm.tree.predicate.SqmPredicate; + +/** + * @author Christian Beikov + */ +public class SqmPluralPartJoin extends AbstractSqmJoin implements DomainResultProducer, SqmQualifiedJoin { + + public SqmPluralPartJoin( + SqmFrom lhs, + SqmPathSource joinedNavigable, + String alias, + SqmJoinType joinType, + NodeBuilder nodeBuilder) { + super( + SqmCreationHelper.buildSubNavigablePath( lhs, joinedNavigable.getPathName(), alias ), + joinedNavigable, + lhs, + alias == SqmCreationHelper.IMPLICIT_ALIAS ? null : alias, + joinType, + nodeBuilder + ); + } + + protected SqmPluralPartJoin( + SqmFrom lhs, + NavigablePath navigablePath, + SqmPathSource joinedNavigable, + String alias, + SqmJoinType joinType, + NodeBuilder nodeBuilder) { + super( + navigablePath, + joinedNavigable, + lhs, + alias == SqmCreationHelper.IMPLICIT_ALIAS ? null : alias, + joinType, + nodeBuilder + ); + } + + @Override + public SqmPredicate getJoinPredicate() { + return null; + } + + @Override + public void setJoinPredicate(SqmPredicate predicate) { + throw new UnsupportedOperationException( "Setting a predicate for a plural part join is unsupported!" ); + } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitPluralPartJoin( this ); + } + + @Override + public SqmTreatedPluralPartJoin treatAs(Class treatJavaType) { + return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ) ); + } + + @Override + public SqmTreatedPluralPartJoin treatAs(EntityDomainType treatTarget) { + return treatAs( treatTarget, null ); + } + + @Override + public SqmTreatedPluralPartJoin treatAs(Class treatJavaType, String alias) { + return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ), alias ); + } + + @Override + public SqmTreatedPluralPartJoin treatAs(EntityDomainType treatTarget, String alias) { + final SqmTreatedPluralPartJoin treat = findTreat( treatTarget, alias ); + if ( treat == null ) { + return addTreat( new SqmTreatedPluralPartJoin<>( this, treatTarget, alias ) ); + } + return treat; + } + + @Override + public SqmCorrelatedPluralPartJoin createCorrelation() { + return new SqmCorrelatedPluralPartJoin<>( this ); + } + + @Override + public String toString() { + return String.format( + Locale.ROOT, + "SqmPluralPartJoin(%s : %s)", + getNavigablePath().getFullPath(), + getReferencedPathSource().getPathName() + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java index f70a6e161f..c1c15270af 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java @@ -34,6 +34,16 @@ public class SqmSingularJoin extends AbstractSqmAttributeJoin implemen super( lhs, joinedNavigable, alias, joinType, fetched, nodeBuilder ); } + public SqmSingularJoin( + SqmFrom lhs, + SqmJoinable joinedNavigable, + String alias, + SqmJoinType joinType, + boolean fetched, + NodeBuilder nodeBuilder) { + super( lhs, joinedNavigable, alias, joinType, fetched, nodeBuilder ); + } + protected SqmSingularJoin( SqmFrom lhs, NavigablePath navigablePath, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java new file mode 100644 index 0000000000..6ef01e3840 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java @@ -0,0 +1,72 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.tree.domain; + +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmJoin; + +/** + * @author Steve Ebersole + */ +public class SqmTreatedPluralPartJoin extends SqmPluralPartJoin implements SqmTreatedPath { + private final SqmPluralPartJoin wrappedPath; + private final EntityDomainType treatTarget; + + @SuppressWarnings("WeakerAccess") + public SqmTreatedPluralPartJoin( + SqmPluralPartJoin wrappedPath, + EntityDomainType treatTarget, + String alias) { + //noinspection unchecked + super( + (SqmFrom) wrappedPath.getLhs(), + wrappedPath.getNavigablePath().treatAs( + treatTarget.getHibernateEntityName(), + alias + ), + (SqmPathSource) wrappedPath.getReferencedPathSource(), + alias, + wrappedPath.getSqmJoinType(), + wrappedPath.nodeBuilder() + ); + this.treatTarget = treatTarget; + this.wrappedPath = wrappedPath; + } + + @Override + public void addSqmJoin(SqmJoin join) { + super.addSqmJoin( join ); + //noinspection unchecked + wrappedPath.addSqmJoin( (SqmJoin) join ); + } + + @Override + public SqmPluralPartJoin getWrappedPath() { + return wrappedPath; + } + + @Override + public EntityDomainType getTreatTarget() { + return treatTarget; + } + + @Override + public SqmPathSource getNodeType() { + return treatTarget; + } + + @Override + public void appendHqlString(StringBuilder sb) { + sb.append( "treat(" ); + wrappedPath.appendHqlString( sb ); + sb.append( " as " ); + sb.append( treatTarget.getName() ); + sb.append( ')' ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java index d5534b3d63..1973c22b6e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java @@ -153,6 +153,6 @@ public abstract class AbstractSqmExpression extends AbstractJpaSelection i @Override public JavaType getJavaTypeDescriptor() { - return getNodeType().getExpressableJavaTypeDescriptor(); + return getNodeType() == null ? null : getNodeType().getExpressableJavaTypeDescriptor(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/CollectionMapWithComponentValueTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/CollectionMapWithComponentValueTest.java similarity index 84% rename from hibernate-core/src/test/java/org/hibernate/test/hql/CollectionMapWithComponentValueTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/hql/CollectionMapWithComponentValueTest.java index cfa8e37481..e896463fc0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/CollectionMapWithComponentValueTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/CollectionMapWithComponentValueTest.java @@ -4,13 +4,15 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.hql; +package org.hibernate.orm.test.hql; import jakarta.persistence.ElementCollection; import jakarta.persistence.Embeddable; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.Arrays; @@ -55,6 +57,7 @@ public class CollectionMapWithComponentValueTest extends BaseCoreFunctionalTestC @Override protected void prepareTest() throws Exception { doInHibernate( this::sessionFactory, s -> { + keyValue.base = null; s.save( keyValue ); BaseTestEntity baseTestEntity1 = new BaseTestEntity(); @@ -67,6 +70,8 @@ public class CollectionMapWithComponentValueTest extends BaseCoreFunctionalTestC baseTestEntity1.entities.add( testEntity ); s.save( baseTestEntity1 ); + keyValue.base = baseTestEntity1; + KeyValue keyValue2 = new KeyValue( "key2" ); s.save( keyValue2 ); BaseTestEntity baseTestEntity2 = new BaseTestEntity(); @@ -197,6 +202,37 @@ public class CollectionMapWithComponentValueTest extends BaseCoreFunctionalTestC } ); } + @Test + @TestForIssue(jiraKey = "HHH-11433") + public void testJoinMapKey() { + doInHibernate( this::sessionFactory, s -> { + // Assert that a left join is used for joining the map key entity table + List keyValues= s.createQuery( "select k from BaseTestEntity bte left join bte.entities te left join te.values v left join key(v) k" ).list(); + System.out.println( keyValues ); + assertEquals( 2, keyValues.size() ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11433") + public void testJoinMapKeyAssociation() { + doInHibernate( this::sessionFactory, s -> { + List keyValues= s.createQuery( "select b from BaseTestEntity bte left join bte.entities te left join te.values v left join key(v) k join k.base b" ).list(); + System.out.println( keyValues ); + assertEquals( 1, keyValues.size() ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11433") + public void testJoinMapKeyAssociationImplicit() { + doInHibernate( this::sessionFactory, s -> { + List keyValues= s.createQuery( "select b from BaseTestEntity bte left join bte.entities te left join te.values v join key(v).base b" ).list(); + System.out.println( keyValues ); + assertEquals( 1, keyValues.size() ); + } ); + } + @Override protected boolean isCleanupTestDataRequired() { return true; @@ -233,6 +269,9 @@ public class CollectionMapWithComponentValueTest extends BaseCoreFunctionalTestC String name; + @ManyToOne(fetch = FetchType.LAZY) + BaseTestEntity base; + public KeyValue() { }