diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java index 0223c778a618..ce6c00e295c2 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -30,6 +30,7 @@ import org.apache.calcite.plan.ViewExpanders; import org.apache.calcite.prepare.Prepare; import org.apache.calcite.prepare.RelOptTableImpl; +import org.apache.calcite.rel.BiRel; import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelCollationTraitDef; import org.apache.calcite.rel.RelCollations; @@ -47,7 +48,6 @@ import org.apache.calcite.rel.core.CorrelationId; import org.apache.calcite.rel.core.Filter; import org.apache.calcite.rel.core.Join; -import org.apache.calcite.rel.core.JoinInfo; import org.apache.calcite.rel.core.JoinRelType; import org.apache.calcite.rel.core.Project; import org.apache.calcite.rel.core.RelFactories; @@ -1514,9 +1514,17 @@ private void substituteSubQuery(Blackboard bb, SubQuery subQuery) { AggregateCall.create(call.getParserPosition(), SqlStdOperatorTable.COUNT, false, false, false, ImmutableList.of(), args, -1, null, RelCollations.EMPTY, longType, null))); - LogicalJoin join = - LogicalJoin.create(bb.root(), aggregate, ImmutableList.of(), - rexBuilder.makeLiteral(true), ImmutableSet.of(), JoinRelType.INNER); + // seek may reference the outer query; createJoin then returns + // a Correlate that binds the variable. + final RelNode join = + createJoin(bb, bb.root(), aggregate, rexBuilder.makeLiteral(true), + JoinRelType.INNER); + // As in Blackboard.register, keep the group by mapping for the new root + final Map projection = + bb.mapRootRelToFieldProjection.get(bb.root()); + if (projection != null) { + bb.mapRootRelToFieldProjection.put(join, projection); + } bb.setRoot(join, false); } final RexNode rex = @@ -1811,9 +1819,10 @@ private RexNode translateIn(RelOptUtil.Logic logic, @Nullable RelNode root, // cross join (select count(*) as c, count(deptno) as ck from v) as ct // left join (select distinct deptno, true as i from v) as dt // on e.deptno = dt.deptno - final Join join = (Join) requireNonNull(root, "root"); + // Either join is a Correlate when the sub-query is correlated. + final BiRel join = (BiRel) requireNonNull(root, "root"); final Project left = (Project) join.getLeft(); - final RelNode leftLeft = ((Join) left.getInput()).getLeft(); + final RelNode leftLeft = ((BiRel) left.getInput()).getLeft(); final int leftLeftCount = leftLeft.getRowType().getFieldCount(); final RelDataType longType = typeFactory.createSqlType(SqlTypeName.BIGINT); @@ -1834,10 +1843,14 @@ private RexNode translateIn(RelOptUtil.Logic logic, @Nullable RelNode root, falseLiteral, rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, iRef), trueLiteral); - final JoinInfo joinInfo = join.analyzeCondition(); - for (int leftKey : joinInfo.leftKeys) { - final RexNode kRef = rexBuilder.makeInputRef(root, leftKey); - args.add(rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, kRef), + // Take the IN keys from rex rather than from the join condition, + // because a Correlate has no condition. rex has the layout described + // in TRUE_FALSE. + final int keyCount = (rex.getType().getFieldCount() - 1) / 2; + for (int i = 0; i < keyCount; i++) { + args.add( + rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, + rexBuilder.makeFieldAccess(rex, i)), unknownLiteral); } args.add(rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ckRef, cRef), diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java index 4b26c3f28446..dbce3a21c0ee 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java @@ -2728,6 +2728,31 @@ void checkCorrelatedMapSubQuery(boolean expand) { sql(sql).ok(); } + /** Test case for + * [CALCITE-7821] + * Correlated query crashes SqlToRelConverter. */ + @Test void testNotInCorrelatedNullable() { + final String sql = "select * from emp as t where t.mgr not in" + + " (select s.mgr from emp as s where s.empno = t.empno)"; + sql(sql).withExpand(true).ok(); + } + + /** As {@link #testNotInCorrelatedNullable()}, but without decorrelation. */ + @Test void testNotInCorrelatedNullableNoDecorrelate() { + final String sql = "select * from emp as t where t.mgr not in" + + " (select s.mgr from emp as s where s.empno = t.empno)"; + sql(sql).withExpand(true).withDecorrelate(false).ok(); + } + + /** As {@link #testNotInCorrelatedNullable()}, but in HAVING, where the + * outer query is an aggregate. */ + @Test void testNotInCorrelatedNullableHaving() { + final String sql = "select deptno from emp as e group by deptno\n" + + "having deptno not in\n" + + " (select s.mgr from emp as s where s.deptno = e.deptno)"; + sql(sql).withExpand(true).ok(); + } + @Test void testAllValueList() { final String sql = "select empno from emp where deptno > all (10, 20)"; sql(sql).withExpand(false).ok(); diff --git a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml index 21db30b73ee2..71376384ef9d 100644 --- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml @@ -6509,6 +6509,89 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$ LogicalProject($f0=[true]) LogicalFilter(condition=[=($cor0.DEPTNO, $0)]) LogicalTableScan(table=[[CATALOG, SALES, DEPT]]) +]]> + + + + + + + + =($10, $9), IS NOT NULL($11)))]) + LogicalJoin(condition=[AND(=($0, $13), =($11, $12))], joinType=[left]) + LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f0=[$10], $f1=[$11], MGR0=[$3]) + LogicalJoin(condition=[=($0, $9)], joinType=[inner]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) + LogicalProject(EMPNO=[$0], $f1=[CASE(IS NOT NULL($2), $2, 0)], $f2=[CASE(IS NOT NULL($3), $3, 0)]) + LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[left]) + LogicalAggregate(group=[{0}]) + LogicalProject(EMPNO=[$0]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) + LogicalAggregate(group=[{0}], agg#0=[COUNT()], agg#1=[COUNT($1)]) + LogicalProject(EMPNO=[$0], MGR=[$3], $f1=[true]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) + LogicalFilter(condition=[IS NOT NULL($0)]) + LogicalAggregate(group=[{0, 1}], agg#0=[MIN($2)]) + LogicalProject(MGR=[$3], EMPNO=[$0], $f1=[true]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) +]]> + + + + + + + + =($2, $1)))]) + LogicalJoin(condition=[AND(=($0, $5), =($3, $4))], joinType=[left]) + LogicalProject(DEPTNO=[$0], $f0=[$2], $f1=[$3], DEPTNO0=[$0]) + LogicalJoin(condition=[=($0, $1)], joinType=[inner]) + LogicalAggregate(group=[{0}]) + LogicalProject(DEPTNO=[$7]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) + LogicalProject(DEPTNO=[$0], $f1=[CASE(IS NOT NULL($2), $2, 0)], $f2=[CASE(IS NOT NULL($3), $3, 0)]) + LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[left]) + LogicalAggregate(group=[{0}]) + LogicalAggregate(group=[{0}]) + LogicalProject(DEPTNO=[$7]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) + LogicalAggregate(group=[{0}], agg#0=[COUNT()], agg#1=[COUNT($1)]) + LogicalProject(DEPTNO=[$7], MGR=[$3], $f1=[true]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) + LogicalFilter(condition=[IS NOT NULL($0)]) + LogicalAggregate(group=[{0, 1}], agg#0=[MIN($2)]) + LogicalProject(MGR=[$3], DEPTNO=[$7], $f1=[true]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) +]]> + + + + + + + + diff --git a/core/src/test/resources/sql/sub-query.iq b/core/src/test/resources/sql/sub-query.iq index 4a71d6a3887e..717697d29f89 100644 --- a/core/src/test/resources/sql/sub-query.iq +++ b/core/src/test/resources/sql/sub-query.iq @@ -10231,3 +10231,69 @@ where e.n > ( !ok !set trimfields true + +# [CALCITE-7821] Correlated query crashes SqlToRelConverter +# The results of the following four queries were validated using Postgres. +!set expand true +with +t(k, a) as (select * from (values (1, 10), (2, cast(null as int)), (3, 30), (4, 40), (5, cast(null as int)))), +s(k, b) as (select * from (values (1, 10), (3, cast(null as int)), (4, 41), (5, 50))) +select * from t where t.a not in (select s.b from s where s.k = t.k); ++---+----+ +| K | A | ++---+----+ +| 2 | | +| 4 | 40 | ++---+----+ +(2 rows) + +!ok + +with +t(k, a) as (select * from (values (1, 10), (2, cast(null as int)), (3, 30), (4, 40), (5, cast(null as int)))), +s(k, b) as (select * from (values (1, 10), (3, cast(null as int)), (4, 41), (5, 50))) +select k, a in (select s.b from s where s.k = t.k) as i from t; ++---+-------+ +| K | I | ++---+-------+ +| 1 | true | +| 2 | false | +| 3 | | +| 4 | false | +| 5 | | ++---+-------+ +(5 rows) + +!ok + +with +t(k, a) as (select * from (values (1, 10), (2, cast(null as int)), (3, 30), (4, 40), (5, cast(null as int)))), +s(k, b) as (select * from (values (1, 10), (3, cast(null as int)), (4, 41), (5, 50))) +select k, a not in (select s.b from s where s.k = t.k) as ni from t; ++---+-------+ +| K | NI | ++---+-------+ +| 1 | false | +| 2 | true | +| 3 | | +| 4 | true | +| 5 | | ++---+-------+ +(5 rows) + +!ok + +with +t(a, k) as (select * from (values (10, 1), (cast(null as int), 2), (30, 3), (40, 4), (cast(null as int), 5))), +s(k, b) as (select * from (values (1, 10), (3, cast(null as int)), (4, 41), (5, 50))) +select k from t group by k having max(a) not in (select s.b from s where s.k = t.k); ++---+ +| K | ++---+ +| 2 | +| 4 | ++---+ +(2 rows) + +!ok +!set expand false