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 fe9860693bcc..e2e57d4c943c 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 @@ -45,6 +45,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { private final SqmJoinType joinType; private final boolean fetch; private final String alias; + private final boolean allowReuse; private ConsumerDelegate delegate; private boolean nested; @@ -54,11 +55,13 @@ public QualifiedJoinPathConsumer( SqmJoinType joinType, boolean fetch, String alias, + boolean allowReuse, SqmCreationState creationState) { this.sqmRoot = sqmRoot; this.joinType = joinType; this.fetch = fetch; this.alias = alias; + this.allowReuse = allowReuse; this.creationState = creationState; } @@ -72,6 +75,8 @@ public QualifiedJoinPathConsumer( this.joinType = joinType; this.fetch = fetch; this.alias = alias; + // This constructor is only used for entity names, so no need for join reuse + this.allowReuse = false; this.creationState = creationState; this.delegate = new AttributeJoinDelegate( sqmFrom, @@ -102,7 +107,13 @@ public void consumeIdentifier(String identifier, boolean isBase, boolean isTermi } else { assert delegate != null; - delegate.consumeIdentifier( identifier, !nested && isTerminal, !( nested && isTerminal ) ); + delegate.consumeIdentifier( + identifier, + !nested && isTerminal, + // Non-nested joins shall allow reuse, but nested ones (i.e. in treat) + // only allow join reuse for non-terminal parts + allowReuse && (!nested || !isTerminal) + ); } } @@ -192,7 +203,12 @@ private AttributeJoinDelegate resolveAlias(String identifier, boolean isTerminal if ( allowReuse ) { if ( !isTerminal ) { for ( SqmJoin sqmJoin : lhs.getSqmJoins() ) { - if ( sqmJoin.getAlias() == null && sqmJoin.getModel() == subPathSource ) { + // In order for an HQL join to be reusable, is must have the same path source, + if ( sqmJoin.getModel() == subPathSource + // must not have a join condition + && sqmJoin.getJoinPredicate() == null + // and the same join type + && sqmJoin.getSqmJoinType() == joinType ) { return sqmJoin; } } 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 c1645c852dc2..231e0149dc71 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 @@ -2188,10 +2188,12 @@ protected void consumeJoin(HqlParser.JoinContext parserJoin, SqmRoot sqmR throw new SemanticException( "The 'from' clause of a subquery has a 'fetch'", query ); } - dotIdentifierConsumerStack.push( new QualifiedJoinPathConsumer( sqmRoot, joinType, fetch, alias, this ) ); + final var joinRestrictionContext = parserJoin.joinRestriction(); + // Joins are allowed to be reused if they don't have a join condition + final var allowReuse = joinRestrictionContext == null; + dotIdentifierConsumerStack.push( new QualifiedJoinPathConsumer( sqmRoot, joinType, fetch, alias, allowReuse, this ) ); try { final SqmJoin join = getJoin( sqmRoot, joinType, qualifiedJoinTargetContext, alias, fetch ); - final var joinRestrictionContext = parserJoin.joinRestriction(); if ( join instanceof SqmEntityJoin || join instanceof SqmDerivedJoin || join instanceof SqmCteJoin ) { sqmRoot.addSqmJoin( join ); } @@ -2329,7 +2331,7 @@ protected void consumeJpaCollectionJoin(HqlParser.JpaCollectionJoinContext ctx, final String alias = extractAlias( ctx.variable() ); dotIdentifierConsumerStack.push( // According to JPA spec 4.4.6 this is an inner join - new QualifiedJoinPathConsumer( sqmRoot, SqmJoinType.INNER, false, alias, this ) + new QualifiedJoinPathConsumer( sqmRoot, SqmJoinType.INNER, false, alias, true, this ) ); try { 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 3d9611f4ba95..e372b307d99f 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 @@ -120,6 +120,7 @@ import org.hibernate.query.sqm.mutation.internal.SqmInsertStrategyHelper; import org.hibernate.query.sqm.produce.function.internal.PatternRenderer; import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker; +import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.sql.internal.AnyDiscriminatorPathInterpretation; import org.hibernate.query.sqm.sql.internal.AsWrappedExpression; import org.hibernate.query.sqm.sql.internal.BasicValuedPathInterpretation; @@ -226,6 +227,7 @@ import org.hibernate.query.sqm.tree.from.SqmFunctionJoin; import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; +import org.hibernate.query.sqm.tree.from.SqmTreatedAttributeJoin; import org.hibernate.query.sqm.tree.insert.SqmConflictClause; import org.hibernate.query.sqm.tree.insert.SqmConflictUpdateAction; import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; @@ -2684,7 +2686,12 @@ protected void consumeFromClauseCorrelatedRoot(SqmRoot sqmRoot) { // as roots anyway, so nothing to worry about // LOG.tracef( "Resolved SqmRoot [%s] to correlated TableGroup [%s]", sqmRoot, tableGroup ); - consumeExplicitJoins( from, tableGroup ); + if ( from instanceof SqmRoot correlatedRoot ) { + consumeJoins( correlatedRoot, fromClauseIndex, tableGroup ); + } + else { + consumeExplicitJoins( from, tableGroup ); + } return; } else { @@ -3339,6 +3346,38 @@ private TableGroup consumeAttributeJoin( .findSubPart( pathSource.getPathName(), resolveExplicitTreatTarget( sqmJoin, this ) ); + final List> sqmTreats = sqmJoin.getSqmTreats(); + final SqmPredicate joinPredicate; + final SqmPredicate[] treatPredicates; + final boolean hasPredicate; + if ( !sqmTreats.isEmpty() ) { + if ( sqmTreats.size() == 1 ) { + // If there is only a single treat, combine the predicates just as they are + joinPredicate = SqmCreationHelper.combinePredicates( + sqmJoin.getJoinPredicate(), + ( (SqmTreatedAttributeJoin) sqmTreats.get( 0 ) ).getJoinPredicate() + ); + treatPredicates = null; + hasPredicate = joinPredicate != null; + } + else { + // When there are multiple predicates, we have to apply type filters + joinPredicate = sqmJoin.getJoinPredicate(); + treatPredicates = new SqmPredicate[sqmTreats.size()]; + boolean hasTreatPredicate = false; + for ( int i = 0; i < sqmTreats.size(); i++ ) { + final var p = ( (SqmTreatedAttributeJoin) sqmTreats.get( i ) ).getJoinPredicate(); + treatPredicates[i] = p; + hasTreatPredicate = hasTreatPredicate || p != null; + } + hasPredicate = joinPredicate != null || hasTreatPredicate; + } + } + else { + joinPredicate = sqmJoin.getJoinPredicate(); + treatPredicates = null; + hasPredicate = joinPredicate != null; + } final TableGroupJoin joinedTableGroupJoin = ((TableGroupJoinProducer) modelPart) .createTableGroupJoin( @@ -3348,7 +3387,7 @@ private TableGroup consumeAttributeJoin( null, sqmJoinType.getCorrespondingSqlJoinType(), sqmJoin.isFetched(), - sqmJoin.getJoinPredicate() != null, + hasPredicate, this ); final TableGroup joinedTableGroup = joinedTableGroupJoin.getJoinedGroup(); @@ -3363,8 +3402,7 @@ private TableGroup consumeAttributeJoin( // Since this is an explicit join, we force the initialization of a possible lazy table group // to retain the cardinality, but only if this is a non-trivial attribute join. // Left or inner singular attribute joins without a predicate can be safely optimized away - if ( sqmJoin.getJoinPredicate() != null - || sqmJoinType != SqmJoinType.INNER && sqmJoinType != SqmJoinType.LEFT ) { + if ( hasPredicate || sqmJoinType != SqmJoinType.INNER && sqmJoinType != SqmJoinType.LEFT ) { joinedTableGroup.getPrimaryTableReference(); } } @@ -3399,14 +3437,25 @@ private TableGroup consumeAttributeJoin( final TableGroupJoin joinForPredicate; // add any additional join restrictions - if ( sqmJoin.getJoinPredicate() != null ) { + if ( hasPredicate ) { if ( sqmJoin.isFetched() ) { QUERY_MESSAGE_LOGGER.debugf( "Join fetch [%s] is restricted", sqmJoinNavigablePath ); } final SqmJoin oldJoin = currentlyProcessingJoin; currentlyProcessingJoin = sqmJoin; - final Predicate predicate = visitNestedTopLevelPredicate( sqmJoin.getJoinPredicate() ); + Predicate predicate = joinPredicate == null ? null : visitNestedTopLevelPredicate( joinPredicate ); + if ( treatPredicates != null ) { + final Junction orPredicate = new Junction( Junction.Nature.DISJUNCTION ); + for ( int i = 0; i < treatPredicates.length; i++ ) { + final var treatType = (EntityDomainType) sqmTreats.get( i ).getTreatTarget(); + orPredicate.add( combinePredicates( + createTreatTypeRestriction( sqmJoin, treatType ), + treatPredicates[i] == null ? null : visitNestedTopLevelPredicate( treatPredicates[i] ) + ) ); + } + predicate = predicate != null ? combinePredicates( predicate, orPredicate ) : orPredicate; + } joinForPredicate = determineJoinForPredicateApply( joinedTableGroupJoin ); // If translating the join predicate didn't initialize the table group, // we can safely apply it on the collection table group instead 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 05273791b0c2..d0616b9fdc40 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 @@ -168,7 +168,7 @@ public SqmPath resolvePathPart( // We can only match singular joins here, as plural path parts are interpreted like sub-queries if ( sqmJoin instanceof SqmSingularJoin attributeJoin && name.equals( sqmJoin.getReferencedPathSource().getPathName() ) ) { - if ( attributeJoin.getOn() == null ) { + if ( attributeJoin.getJoinPredicate() == null ) { // todo (6.0): to match the expectation of the JPA spec I think we also have to check // that the join type is INNER or the default join type for the attribute, // but as far as I understand, in 5.x we expect to ignore this behavior diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java index 2aa43b60802f..c1a042d45589 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java @@ -11,7 +11,9 @@ import org.hibernate.query.sqm.tree.SqmCacheable; import org.hibernate.query.sqm.tree.SqmCopyContext; +import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.SqmRenderContext; +import org.hibernate.query.sqm.tree.domain.SqmTreatedFrom; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import static java.util.Collections.emptyList; @@ -113,55 +115,60 @@ public void appendHqlString(StringBuilder sb, SqmRenderContext context) { } public static void appendJoins(SqmFrom sqmFrom, StringBuilder sb, SqmRenderContext context) { - for ( SqmJoin sqmJoin : sqmFrom.getSqmJoins() ) { - switch ( sqmJoin.getSqmJoinType() ) { - case LEFT: - sb.append( " left join " ); - break; - case RIGHT: - sb.append( " right join " ); - break; - case INNER: - sb.append( " join " ); - break; - case FULL: - sb.append( " full join " ); - break; - case CROSS: - sb.append( " cross join " ); - break; - } + if ( sqmFrom instanceof SqmRoot root && root.getOrderedJoins() != null ) { + appendJoins( sqmFrom, root.getOrderedJoins(), sb, false, context ); + } + else { + appendJoins( sqmFrom, sqmFrom.getSqmJoins(), sb, true, context ); + } + } + + private static void appendJoins(SqmFrom sqmFrom, List> joins, StringBuilder sb, boolean transitive, SqmRenderContext context) { + for ( SqmJoin sqmJoin : joins ) { + appendJoinType( sb, sqmJoin.getSqmJoinType() ); if ( sqmJoin instanceof SqmAttributeJoin attributeJoin ) { - if ( sqmFrom instanceof SqmTreatedPath treatedPath ) { - sb.append( "treat(" ); - treatedPath.getWrappedPath().appendHqlString( sb, context ); -// sb.append( treatedPath.getWrappedPath().resolveAlias( context ) ); - sb.append( " as " ).append( treatedPath.getTreatTarget().getTypeName() ).append( ')' ); + final List> sqmTreats = attributeJoin.getSqmTreats(); + if ( attributeJoin.isImplicitJoin() && !sqmTreats.isEmpty() ) { + for ( int i = 0; i < sqmTreats.size(); i++ ) { + final var treatJoin = (SqmTreatedAttributeJoin) sqmTreats.get( i ); + if ( i != 0 ) { + appendJoinType( sb, sqmJoin.getSqmJoinType() ); + } + sb.append( "treat(" ); + appendAttributeJoin( sqmFrom, sb, context, attributeJoin ); + sb.append( " as " ); + sb.append( treatJoin.getTreatTarget().getTypeName() ); + sb.append( ')' ); + appendJoinAliasAndOnClause( sb, context, treatJoin ); + if ( transitive ) { + appendJoins( treatJoin, sb, context ); + } + } } else { - sb.append( sqmFrom.resolveAlias( context ) ); - } - sb.append( '.' ).append( attributeJoin.getAttribute().getName() ); - sb.append( ' ' ).append( sqmJoin.resolveAlias( context ) ); - if ( attributeJoin.getJoinPredicate() != null ) { - sb.append( " on " ); - attributeJoin.getJoinPredicate().appendHqlString( sb, context ); + appendAttributeJoin( sqmFrom, sb, context, attributeJoin ); + appendJoinAliasAndOnClause( sb, context, attributeJoin ); + if ( transitive ) { + appendJoins( attributeJoin, sb, context ); + appendTreatJoins( sqmJoin, sb, context ); + } } - appendJoins( sqmJoin, sb, context ); } else if ( sqmJoin instanceof SqmCrossJoin sqmCrossJoin ) { sb.append( sqmCrossJoin.getEntityName() ); sb.append( ' ' ).append( sqmCrossJoin.resolveAlias( context ) ); - appendJoins( sqmJoin, sb, context ); + if ( transitive ) { + appendJoins( sqmJoin, sb, context ); + appendTreatJoins( sqmJoin, sb, context ); + } } else if ( sqmJoin instanceof SqmEntityJoin sqmEntityJoin ) { sb.append( sqmEntityJoin.getEntityName() ); - sb.append( ' ' ).append( sqmJoin.resolveAlias( context ) ); - if ( sqmEntityJoin.getJoinPredicate() != null ) { - sb.append( " on " ); - sqmEntityJoin.getJoinPredicate().appendHqlString( sb, context ); + appendJoinAliasAndOnClause( sb, context, sqmEntityJoin ); + if ( transitive ) { + appendJoins( sqmJoin, sb, context ); + appendTreatJoins( sqmJoin, sb, context ); } - appendJoins( sqmJoin, sb, context ); } else { throw new UnsupportedOperationException( "Unsupported join: " + sqmJoin ); @@ -169,6 +176,37 @@ else if ( sqmJoin instanceof SqmEntityJoin sqmEntityJoin ) { } } + private static void appendJoinAliasAndOnClause(StringBuilder sb, SqmRenderContext context, SqmJoin join) { + sb.append( ' ' ).append( join.resolveAlias( context ) ); + if ( join.getJoinPredicate() != null ) { + sb.append( " on " ); + join.getJoinPredicate().appendHqlString( sb, context ); + } + } + + private static void appendAttributeJoin(SqmFrom sqmFrom, StringBuilder sb, SqmRenderContext context, SqmAttributeJoin attributeJoin) { + if ( sqmFrom instanceof SqmTreatedPath treatedPath ) { + sb.append( "treat(" ); + treatedPath.getWrappedPath().appendHqlString( sb, context ); +// sb.append( treatedPath.getWrappedPath().resolveAlias( context ) ); + sb.append( " as " ).append( treatedPath.getTreatTarget().getTypeName() ).append( ')' ); + } + else { + sb.append( sqmFrom.resolveAlias( context ) ); + } + sb.append( '.' ).append( attributeJoin.getAttribute().getName() ); + } + + private static void appendJoinType(StringBuilder sb, SqmJoinType sqmJoinType) { + sb.append( switch ( sqmJoinType ) { + case LEFT -> " left join "; + case RIGHT -> " right join "; + case INNER -> " join "; + case FULL -> " full join "; + case CROSS -> " cross join "; + } ); + } + private void appendJoins(SqmFrom sqmFrom, String correlationPrefix, StringBuilder sb, SqmRenderContext context) { String separator = ""; for ( SqmJoin sqmJoin : sqmFrom.getSqmJoins() ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/JoinedInheritanceTreatQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/JoinedInheritanceTreatQueryTest.java index 35f3dbf6bdbf..b81c5a9bfb49 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/JoinedInheritanceTreatQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/JoinedInheritanceTreatQueryTest.java @@ -22,6 +22,8 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -78,6 +80,95 @@ public void testTreatedJoin(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testTreatedJoinWithCondition(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final List result = session.createSelectionQuery( + "from Product p " + + "join treat(p.owner AS ProductOwner2) as own1 on own1.basicProp = 'unknown value'", + Product.class + ).getResultList(); + assertThat( result ).isEmpty(); + inspector.assertNumberOfJoins( 0, 2 ); + } ); + } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testMultipleTreatedJoinWithCondition(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final List result = session.createSelectionQuery( + "from Product p " + + "join treat(p.owner AS ProductOwner1) as own1 on own1.description is null " + + "join treat(p.owner AS ProductOwner2) as own2 on own2.basicProp = 'unknown value'", + Product.class + ).getResultList(); + assertThat( result ).isEmpty(); + inspector.assertNumberOfJoins( 0, 4 ); + } ); + } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testMultipleTreatedJoinSameAttribute(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final List result = session.createSelectionQuery( + "from Product p " + + "join treat(p.owner AS ProductOwner1) as own1 " + + "join treat(p.owner AS ProductOwner2) as own2", + Product.class + ).getResultList(); + // No rows, because treat joining the same association with disjunct types can't emit results + assertThat( result ).isEmpty(); + inspector.assertNumberOfJoins( 0, 4 ); + } ); + } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testMultipleTreatedJoinSameAttributeCriteria(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final var cb = session.getCriteriaBuilder(); + final var query = cb.createQuery(Product.class); + final var p = query.from( Product.class ); + p.join( "owner" ).treatAs( ProductOwner1.class ); + p.join( "owner" ).treatAs( ProductOwner2.class ); + final List result = session.createSelectionQuery( query ).getResultList(); + // No rows, because treat joining the same association with disjunct types can't emit results + assertThat( result ).isEmpty(); + inspector.assertNumberOfJoins( 0, 4 ); + } ); + } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testMultipleTreatedJoinCriteria(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final var cb = session.getCriteriaBuilder(); + final var query = cb.createQuery(Product.class); + final var p = query.from( Product.class ); + final var ownerJoin = p.join( "owner" ); + ownerJoin.treatAs( ProductOwner1.class ); + ownerJoin.treatAs( ProductOwner2.class ); + final List result = session.createSelectionQuery( query ).getResultList(); + // The owner attribute is inner joined, but since there are multiple subtype treats, + // the type restriction for the treat usage does not filter rows + assertThat( result ).hasSize( 2 ); + inspector.assertNumberOfJoins( 0, 3 ); + } ); + } + @Test public void testImplicitTreatedJoin(SessionFactoryScope scope) { final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/ManyToManyTreatJoinTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/ManyToManyTreatJoinTest.java index 4ace95f26213..945e824736fc 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/ManyToManyTreatJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/ManyToManyTreatJoinTest.java @@ -13,6 +13,7 @@ import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.AfterAll; @@ -94,6 +95,21 @@ public void testSingleTableSelectParent(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testSingleTableTreatJoinCondition(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final Integer result = session.createSelectionQuery( + "select s.subProp from ParentEntity p join treat(p.singleEntities as SingleSub1) s on s.subProp<>2", + Integer.class + ).getSingleResultOrNull(); + assertThat( result ).isNull(); + inspector.assertNumberOfJoins( 0, 2 ); + } ); + } + @Test public void testJoinedSelectChild(SessionFactoryScope scope) { final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); @@ -122,6 +138,21 @@ public void testJoinedSelectParent(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testJoinedTreatJoinCondition(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final Integer result = session.createSelectionQuery( + "select s.subProp from ParentEntity p join treat(p.joinedEntities as JoinedSub1) s on s.subProp<>3", + Integer.class + ).getSingleResultOrNull(); + assertThat( result ).isNull(); + inspector.assertNumberOfJoins( 0, 3 ); + } ); + } + @Test public void testTablePerClassSelectChild(SessionFactoryScope scope) { final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); @@ -150,6 +181,21 @@ public void testTablePerClassSelectParent(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testTablePerClassTreatJoinCondition(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final Integer result = session.createSelectionQuery( + "select s.subProp from ParentEntity p join treat(p.unionEntities as UnionSub1) s on s.subProp<>4", + Integer.class + ).getSingleResultOrNull(); + assertThat( result ).isNull(); + inspector.assertNumberOfJoins( 0, 2 ); + } ); + } + @Entity( name = "ParentEntity" ) public static class ParentEntity { @Id diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithJoinedInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithJoinedInheritanceTest.java index 87a1b068b550..119ae08808d0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithJoinedInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithJoinedInheritanceTest.java @@ -131,6 +131,29 @@ public void testLeftJoinExplicitTreat(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testTreatedJoinWithCondition(SessionFactoryScope scope) { + scope.inTransaction( s -> { + final ChildEntityA childEntityA1 = new SubChildEntityA1( 11 ); + childEntityA1.setName( "childA1" ); + s.persist( childEntityA1 ); + final ChildEntityA childEntityA2 = new SubChildEntityA2( 21 ); + childEntityA2.setName( "childA2" ); + s.persist( childEntityA2 ); + s.persist( new RootOne( 1, childEntityA1 ) ); + s.persist( new RootOne( 2, childEntityA2 ) ); + } ); + scope.inTransaction( s -> { + final Tuple tuple = s.createQuery( + "select r, ce " + + "from RootOne r join treat(r.child as ChildEntityA) ce on ce.name = 'childA1'", + Tuple.class + ).getSingleResult(); + assertResult( tuple, 1, 11, 11, "child_a_1", SubChildEntityA1.class ); + } ); + } + @Test public void testRightJoin(SessionFactoryScope scope) { scope.inTransaction( s -> { @@ -259,6 +282,7 @@ private void assertResult( public static class BaseClass { @Id private Integer id; + private String name; @Column( name = "disc_col", insertable = false, updatable = false ) private String discCol; @@ -277,6 +301,14 @@ public Integer getId() { public String getDiscCol() { return discCol; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } @Entity( name = "ChildEntityA" ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithNaturalJoinedInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithNaturalJoinedInheritanceTest.java index 40a8da273ed8..4ad166096ff4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithNaturalJoinedInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithNaturalJoinedInheritanceTest.java @@ -68,6 +68,29 @@ public void testLeftJoinWithDiscriminatorFiltering(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testTreatedJoinWithCondition(SessionFactoryScope scope) { + scope.inTransaction( s -> { + final ChildEntityA childEntityA1 = new SubChildEntityA1( 11 ); + childEntityA1.setName( "childA1" ); + s.persist( childEntityA1 ); + final ChildEntityA childEntityA2 = new SubChildEntityA2( 21 ); + childEntityA2.setName( "childA2" ); + s.persist( childEntityA2 ); + s.persist( new RootOne( 1, childEntityA1 ) ); + s.persist( new RootOne( 2, childEntityA2 ) ); + } ); + scope.inTransaction( s -> { + final Tuple tuple = s.createQuery( + "select r, ce, ce.uk " + + "from RootOne r join treat(r.child as ChildEntityA) ce on ce.name = 'childA1'", + Tuple.class + ).getSingleResult(); + assertResult( tuple, 1, 11, 11, "child_a_1", SubChildEntityA1.class, 11 ); + } ); + } + private void assertResult( Tuple result, Integer rootId, @@ -113,6 +136,7 @@ private void assertResult( public static class BaseClass { @Id private Integer id; + private String name; @Column( name = "disc_col", insertable = false, updatable = false ) private String discCol; @@ -131,6 +155,14 @@ public Integer getId() { public String getDiscCol() { return discCol; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } @Entity( name = "ChildEntityA" ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithRestrictedJoinedInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithRestrictedJoinedInheritanceTest.java index 3a06d52a8924..0a070cf69ac4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithRestrictedJoinedInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithRestrictedJoinedInheritanceTest.java @@ -70,6 +70,29 @@ public void testLeftJoinWithDiscriminatorFiltering(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testTreatedJoinWithCondition(SessionFactoryScope scope) { + scope.inTransaction( s -> { + final ChildEntityA childEntityA1 = new SubChildEntityA1( 11 ); + childEntityA1.setName( "childA1" ); + s.persist( childEntityA1 ); + final ChildEntityA childEntityA2 = new SubChildEntityA2( 21 ); + childEntityA2.setName( "childA2" ); + s.persist( childEntityA2 ); + s.persist( new RootOne( 1, childEntityA1 ) ); + s.persist( new RootOne( 2, childEntityA2 ) ); + } ); + scope.inTransaction( s -> { + final Tuple tuple = s.createQuery( + "select r, ce " + + "from RootOne r join treat(r.child as ChildEntityA) ce on ce.name = 'childA1'", + Tuple.class + ).getSingleResult(); + assertResult( tuple, 1, 11, 11, "child_a_1", SubChildEntityA1.class ); + } ); + } + private void assertResult( Tuple result, Integer rootId, @@ -110,6 +133,7 @@ public static class BaseClass { @Id @Column(name = "ident") private Integer id; + private String name; @Column( name = "disc_col", insertable = false, updatable = false ) private String discCol; @@ -128,6 +152,14 @@ public Integer getId() { public String getDiscCol() { return discCol; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } @Entity( name = "ChildEntityA" ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithSingleTableInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithSingleTableInheritanceTest.java index 722c7d5f0d2c..b7baee53d220 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithSingleTableInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithSingleTableInheritanceTest.java @@ -104,6 +104,29 @@ public void testLeftJoinExplicitTreat(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testTreatedJoinWithCondition(SessionFactoryScope scope) { + scope.inTransaction( s -> { + final ChildEntityA childEntityA1 = new SubChildEntityA1( 11 ); + childEntityA1.setName( "childA1" ); + s.persist( childEntityA1 ); + final ChildEntityA childEntityA2 = new SubChildEntityA2( 21 ); + childEntityA2.setName( "childA2" ); + s.persist( childEntityA2 ); + s.persist( new RootOne( 1, childEntityA1 ) ); + s.persist( new RootOne( 2, childEntityA2 ) ); + } ); + scope.inTransaction( s -> { + final Tuple tuple = s.createQuery( + "select r, ce " + + "from RootOne r join treat(r.child as ChildEntityA) ce on ce.name = 'childA1'", + Tuple.class + ).getSingleResult(); + assertResult( tuple, 1, 11, 11, "child_a_1", SubChildEntityA1.class ); + } ); + } + @Test public void testRightJoin(SessionFactoryScope scope) { scope.inTransaction( s -> { @@ -226,6 +249,7 @@ private void assertResult( public static class BaseClass { @Id private Integer id; + private String name; @Column( name = "disc_col", insertable = false, updatable = false ) private String discCol; @@ -244,6 +268,14 @@ public Integer getId() { public String getDiscCol() { return discCol; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } @Entity( name = "ChildEntityA" ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithTablePerClassInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithTablePerClassInheritanceTest.java index 65f1275d34df..faa31a39e7f5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithTablePerClassInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithTablePerClassInheritanceTest.java @@ -102,6 +102,29 @@ public void testLeftJoinExplicitTreat(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19883") + public void testTreatedJoinWithCondition(SessionFactoryScope scope) { + scope.inTransaction( s -> { + final ChildEntityA childEntityA1 = new SubChildEntityA1( 11 ); + childEntityA1.setName( "childA1" ); + s.persist( childEntityA1 ); + final ChildEntityA childEntityA2 = new SubChildEntityA2( 21 ); + childEntityA2.setName( "childA2" ); + s.persist( childEntityA2 ); + s.persist( new RootOne( 1, childEntityA1 ) ); + s.persist( new RootOne( 2, childEntityA2 ) ); + } ); + scope.inTransaction( s -> { + final Tuple tuple = s.createQuery( + "select r, ce " + + "from RootOne r join treat(r.child as ChildEntityA) ce on ce.name = 'childA1'", + Tuple.class + ).getSingleResult(); + assertResult( tuple, 1, 11, 11, SubChildEntityA1.class ); + } ); + } + @Test public void testRightJoin(SessionFactoryScope scope) { scope.inTransaction( s -> { @@ -221,6 +244,7 @@ private void assertResult( public static class BaseClass { @Id private Integer id; + private String name; public BaseClass() { } @@ -232,6 +256,14 @@ public BaseClass(Integer id) { public Integer getId() { return id; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } @Entity( name = "ChildEntityA" )