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 38165f25b0..79812ae045 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 @@ -2593,6 +2593,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base final TableGroup parentTableGroup = fromClauseIndex.findTableGroup( from.getCorrelationParent().getNavigablePath() ); + if ( parentTableGroup == null ) { + throw new InterpretationException( "Access to from node '" + from.getCorrelationParent() + "' is not possible in from-clause subqueries, unless the 'lateral' keyword is used for the subquery!" ); + } final SqlAliasBase sqlAliasBase = sqlAliasBaseManager.createSqlAliasBase( parentTableGroup.getGroupAlias() ); if ( parentTableGroup instanceof PluralTableGroup ) { final PluralTableGroup pluralTableGroup = (PluralTableGroup) parentTableGroup; @@ -2744,7 +2747,11 @@ public abstract class BaseSqmToSqlAstConverter extends Base final TableGroup tableGroup; if ( sqmRoot instanceof SqmDerivedRoot ) { final SqmDerivedRoot derivedRoot = (SqmDerivedRoot) sqmRoot; + // Temporarily push an empty FromClauseIndex to disallow access to aliases from the top query + // Only lateral subqueries are allowed to see the aliases + fromClauseIndexStack.push( new FromClauseIndex( null ) ); final SelectStatement statement = (SelectStatement) derivedRoot.getQueryPart().accept( this ); + fromClauseIndexStack.pop(); final AnonymousTupleType tupleType = (AnonymousTupleType) sqmRoot.getNodeType(); final List sqlSelections = statement.getQueryPart().getFirstQuerySpec().getSelectClause().getSqlSelections(); final AnonymousTupleTableGroupProducer tableGroupProducer = tupleType.resolveTableGroupProducer( @@ -3485,7 +3492,15 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private TableGroup consumeDerivedJoin(SqmDerivedJoin sqmJoin, TableGroup parentTableGroup, boolean transitive) { + if ( !sqmJoin.isLateral() ) { + // Temporarily push an empty FromClauseIndex to disallow access to aliases from the top query + // Only lateral subqueries are allowed to see the aliases + fromClauseIndexStack.push( new FromClauseIndex( null ) ); + } final SelectStatement statement = (SelectStatement) sqmJoin.getQueryPart().accept( this ); + if ( !sqmJoin.isLateral() ) { + fromClauseIndexStack.pop(); + } final AnonymousTupleType tupleType = (AnonymousTupleType) sqmJoin.getNodeType(); final List sqlSelections = statement.getQueryPart().getFirstQuerySpec().getSelectClause().getSqlSelections(); final AnonymousTupleTableGroupProducer tableGroupProducer = tupleType.resolveTableGroupProducer( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/SubQueryInFromTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/SubQueryInFromTests.java index ebe01e7d23..4acccacb25 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/SubQueryInFromTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/SubQueryInFromTests.java @@ -17,9 +17,11 @@ import org.hibernate.query.criteria.JpaDerivedRoot; import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.criteria.JpaSubQuery; import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.query.sqm.InterpretationException; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; +import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.domain.StandardDomainModel; import org.hibernate.testing.orm.domain.contacts.Address; @@ -35,7 +37,9 @@ import org.junit.jupiter.api.Test; import jakarta.persistence.Tuple; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.Root; +import org.assertj.core.api.Assertions; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -81,6 +85,51 @@ public class SubQueryInFromTests { ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-17898") + public void testJoinSubqueryUsingInvalidAlias1(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + try { + session.createQuery( + "select c.name, a.name from Contact c " + + "join (" + + "select c2.name as name " + + "from Contact c2 " + + "where c2 = c" + + ") a", + Tuple.class + ).getResultList(); + } + catch (InterpretationException ex) { + assertThat( ex.getMessage() ).contains( "lateral" ); + } + } + ); + } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-17898") + public void testJoinSubqueryUsingInvalidAlias2(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + try { + session.createQuery( + "select c.name, a.address from Contact c " + + "join (" + + "select address.line1 as address " + + "from c.addresses address " + + ") a", + Tuple.class + ).getResultList(); + } + catch (InterpretationException ex) { + assertThat( ex.getMessage() ).contains( "lateral" ); + } + } + ); + } + @Test @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)