Fix root validation for subqueries that appear in the on clause
This commit is contained in:
parent
0f02279f10
commit
1a71bb9787
|
@ -10,12 +10,15 @@ import java.util.Locale;
|
||||||
|
|
||||||
import org.hibernate.query.SemanticException;
|
import org.hibernate.query.SemanticException;
|
||||||
import org.hibernate.query.hql.spi.SemanticPathPart;
|
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.hql.spi.SqmCreationState;
|
||||||
import org.hibernate.query.sqm.tree.SqmQuery;
|
import org.hibernate.query.sqm.tree.SqmQuery;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmFrom;
|
import org.hibernate.query.sqm.tree.from.SqmFrom;
|
||||||
|
import org.hibernate.query.sqm.tree.from.SqmFromClause;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
|
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||||
|
import org.hibernate.query.sqm.tree.select.SqmSelectQuery;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
|
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,17 +43,29 @@ public class QualifiedJoinPredicatePathConsumer extends BasicDotIdentifierConsum
|
||||||
@Override
|
@Override
|
||||||
protected void validateAsRoot(SqmFrom<?, ?> pathRoot) {
|
protected void validateAsRoot(SqmFrom<?, ?> pathRoot) {
|
||||||
final SqmRoot<?> root = pathRoot.findRoot();
|
final SqmRoot<?> root = pathRoot.findRoot();
|
||||||
if ( root != sqmJoin.findRoot() ) {
|
final SqmRoot<?> joinRoot = sqmJoin.findRoot();
|
||||||
final SqmQuery<?> processingQuery = getCreationState().getCurrentProcessingState().getProcessingQuery();
|
if ( root != joinRoot ) {
|
||||||
if ( processingQuery instanceof SqmSubQuery<?> ) {
|
// The root of a path within a query doesn't have the same root as the current join we are processing.
|
||||||
final SqmQuerySpec<?> querySpec = ( (SqmSubQuery<?>) processingQuery ).getQuerySpec();
|
// 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
|
||||||
// If this "foreign" from element is used in a sub query
|
SqmCreationProcessingState processingState = getCreationState().getCurrentProcessingState();
|
||||||
// This is only an error if the from element is actually part of the sub query
|
// First, we need to find out if the current join is part of current processing query
|
||||||
if ( querySpec.getFromClause() == null || !querySpec.getFromClause().getRoots().contains( root ) ) {
|
final SqmQuery<?> currentProcessingQuery = processingState.getProcessingQuery();
|
||||||
super.validateAsRoot( pathRoot );
|
if ( currentProcessingQuery instanceof SqmSelectQuery<?> ) {
|
||||||
|
final SqmQuerySpec<?> querySpec = ( (SqmSelectQuery<?>) currentProcessingQuery ).getQuerySpec();
|
||||||
|
final SqmFromClause fromClause = querySpec.getFromClause();
|
||||||
|
// 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() );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If the current join is not part of the processing query, this must be a sub query in the ON clause
|
||||||
|
// in which case the path root is allowed to occur in the current processing query as root
|
||||||
|
if ( currentProcessingQuery instanceof SqmSubQuery<?> ) {
|
||||||
|
validateAsRootOnParentQueryClosure( pathRoot, root, processingState );
|
||||||
|
return;
|
||||||
|
}
|
||||||
throw new SemanticException(
|
throw new SemanticException(
|
||||||
String.format(
|
String.format(
|
||||||
Locale.ROOT,
|
Locale.ROOT,
|
||||||
|
@ -63,6 +78,35 @@ public class QualifiedJoinPredicatePathConsumer extends BasicDotIdentifierConsum
|
||||||
|
|
||||||
super.validateAsRoot( pathRoot );
|
super.validateAsRoot( pathRoot );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateAsRootOnParentQueryClosure(
|
||||||
|
SqmFrom<?, ?> pathRoot,
|
||||||
|
SqmRoot<?> root,
|
||||||
|
SqmCreationProcessingState processingState) {
|
||||||
|
while ( processingState != null ) {
|
||||||
|
final SqmQuery<?> processingQuery = processingState.getProcessingQuery();
|
||||||
|
if ( processingQuery instanceof SqmSelectQuery<?> ) {
|
||||||
|
final SqmQuerySpec<?> querySpec = ( (SqmSelectQuery<?>) processingQuery ).getQuerySpec();
|
||||||
|
final SqmFromClause fromClause = querySpec.getFromClause();
|
||||||
|
// If we are in a sub query, the "foreign" from element could be one of the sub query roots,
|
||||||
|
// which is totally fine. 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
|
||||||
|
if ( fromClause != null && fromClause.getRoots().contains( root ) ) {
|
||||||
|
super.validateAsRoot( pathRoot );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processingState = processingState.getParentProcessingState();
|
||||||
|
}
|
||||||
|
throw new SemanticException(
|
||||||
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"SqmQualifiedJoin predicate referred to SqmRoot [`%s`] other than the join's root [`%s`]",
|
||||||
|
pathRoot.getNavigablePath().getFullPath(),
|
||||||
|
sqmJoin.getNavigablePath().getFullPath()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -810,7 +810,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
}
|
}
|
||||||
|
|
||||||
// visit from-clause first!!!
|
// visit from-clause first!!!
|
||||||
sqmQuerySpec.setFromClause( visitFromClause( (HqlParser.FromClauseContext) ctx.getChild( fromIndex ) ) );
|
visitFromClause( (HqlParser.FromClauseContext) ctx.getChild( fromIndex ) );
|
||||||
|
|
||||||
final SqmSelectClause selectClause;
|
final SqmSelectClause selectClause;
|
||||||
if ( fromIndex == 1 ) {
|
if ( fromIndex == 1 ) {
|
||||||
|
@ -1392,16 +1392,18 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
final SqmFromClause fromClause;
|
final SqmFromClause fromClause;
|
||||||
if ( parserFromClause == null ) {
|
if ( parserFromClause == null ) {
|
||||||
fromClause = new SqmFromClause();
|
fromClause = new SqmFromClause();
|
||||||
|
currentQuerySpec().setFromClause( fromClause );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final int size = parserFromClause.getChildCount();
|
final int size = parserFromClause.getChildCount();
|
||||||
// Shift 1 bit instead of division by 2
|
// Shift 1 bit instead of division by 2
|
||||||
final int estimatedSize = size >> 1;
|
final int estimatedSize = size >> 1;
|
||||||
fromClause = new SqmFromClause( estimatedSize );
|
fromClause = new SqmFromClause( estimatedSize );
|
||||||
|
currentQuerySpec().setFromClause( fromClause );
|
||||||
for ( int i = 0; i < size; i++ ) {
|
for ( int i = 0; i < size; i++ ) {
|
||||||
final ParseTree parseTree = parserFromClause.getChild( i );
|
final ParseTree parseTree = parserFromClause.getChild( i );
|
||||||
if ( parseTree instanceof HqlParser.EntityWithJoinsContext ) {
|
if ( parseTree instanceof HqlParser.EntityWithJoinsContext ) {
|
||||||
fromClause.addRoot( visitEntityWithJoins( (HqlParser.EntityWithJoinsContext) parseTree ) );
|
visitEntityWithJoins( (HqlParser.EntityWithJoinsContext) parseTree );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1411,6 +1413,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
@Override
|
@Override
|
||||||
public SqmRoot<?> visitEntityWithJoins(HqlParser.EntityWithJoinsContext parserSpace) {
|
public SqmRoot<?> visitEntityWithJoins(HqlParser.EntityWithJoinsContext parserSpace) {
|
||||||
final SqmRoot<?> sqmRoot = visitRootEntity( (HqlParser.RootEntityContext) parserSpace.getChild( 0 ) );
|
final SqmRoot<?> sqmRoot = visitRootEntity( (HqlParser.RootEntityContext) parserSpace.getChild( 0 ) );
|
||||||
|
currentQuerySpec().getFromClause().addRoot( sqmRoot );
|
||||||
final int size = parserSpace.getChildCount();
|
final int size = parserSpace.getChildCount();
|
||||||
for ( int i = 1; i < size; i++ ) {
|
for ( int i = 1; i < size; i++ ) {
|
||||||
final ParseTree parseTree = parserSpace.getChild( i );
|
final ParseTree parseTree = parserSpace.getChild( i );
|
||||||
|
|
|
@ -377,6 +377,40 @@ public class OuterJoinTest {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||||
|
public void testSubQueryInOnClause(EntityManagerFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
em -> {
|
||||||
|
List<Tuple> resultList = em.createQuery(
|
||||||
|
"SELECT 1 " +
|
||||||
|
"FROM A a " +
|
||||||
|
"INNER JOIN B b ON exists(select 1 from C c where c.key = a.key) ",
|
||||||
|
Tuple.class
|
||||||
|
).getResultList();
|
||||||
|
|
||||||
|
assertEquals( 6, resultList.size() );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||||
|
public void testSubQueryCorrelationInOnClause(EntityManagerFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
em -> {
|
||||||
|
List<Tuple> resultList = em.createQuery(
|
||||||
|
"SELECT 1 " +
|
||||||
|
"FROM A a " +
|
||||||
|
"INNER JOIN B b ON exists(select 1 from a.association c where c.key = a.key) ",
|
||||||
|
Tuple.class
|
||||||
|
).getResultList();
|
||||||
|
|
||||||
|
assertEquals( 3, resultList.size() );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@MappedSuperclass
|
@MappedSuperclass
|
||||||
public static class BaseEntity {
|
public static class BaseEntity {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue