diff --git a/core/src/main/java/org/apache/calcite/sql2rel/CorrelateProjectExtractor.java b/core/src/main/java/org/apache/calcite/sql2rel/CorrelateProjectExtractor.java index 50d255c3312..d88111333ae 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/CorrelateProjectExtractor.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/CorrelateProjectExtractor.java @@ -88,6 +88,20 @@ public CorrelateProjectExtractor(RelBuilderFactory factory) { this.builderFactory = factory; } + /** Returns whether {@code node} is a direct field access on the correlation + * variable with the specified id, such as {@code $cor0.DEPTNO}. A nested + * access such as {@code $cor0.REC.DEPTNO} is not direct. */ + private static boolean isDirectFieldAccess(RexNode node, CorrelationId id) { + if (node instanceof RexFieldAccess) { + RexFieldAccess access = (RexFieldAccess) node; + if (access.getReferenceExpr() instanceof RexCorrelVariable) { + RexCorrelVariable var = (RexCorrelVariable) access.getReferenceExpr(); + return var.id.equals(id); + } + } + return false; + } + @Override public RelNode visit(LogicalCorrelate correlate) { RelNode left = correlate.getLeft().accept(this); RelNode right = correlate.getRight().accept(this); @@ -95,8 +109,12 @@ public CorrelateProjectExtractor(RelBuilderFactory factory) { // Find the correlated expressions from the right side that can be moved to the left Set callsWithCorrelationInRight = findCorrelationDependentCalls(correlate.getCorrelationId(), right); + // Only direct field accesses on the correlation variable, such as + // $cor0.DEPTNO, are left in place. A nested field access, such as + // $cor0.REC.DEPTNO, is extracted boolean isTrivialCorrelation = - callsWithCorrelationInRight.stream().allMatch(exp -> exp instanceof RexFieldAccess); + callsWithCorrelationInRight.stream() + .allMatch(exp -> isDirectFieldAccess(exp, correlate.getCorrelationId())); // Early exit condition if (isTrivialCorrelation) { if (correlate.getLeft().equals(left) && correlate.getRight().equals(right)) { @@ -116,27 +134,42 @@ public CorrelateProjectExtractor(RelBuilderFactory factory) { // Transform the correlated expression from the right side to an expression over the left side builder.push(left); + ImmutableBitSet.Builder requiredColumns = ImmutableBitSet.builder(); List callsWithCorrelationOverLeft = new ArrayList<>(); for (RexNode callInRight : callsWithCorrelationInRight) { - callsWithCorrelationOverLeft.add(replaceCorrelationsWithInputRef(callInRight, builder)); + if (isDirectFieldAccess(callInRight, correlate.getCorrelationId())) { + // Direct field accesses stay in the right side and keep reading their + // original left column; that column must remain a required column. + requiredColumns.set(((RexFieldAccess) callInRight).getField().getIndex()); + } else { + callsWithCorrelationOverLeft.add(replaceCorrelationsWithInputRef(callInRight, builder)); + } } builder.projectPlus(callsWithCorrelationOverLeft); // Construct the mapping to transform the expressions in the right side based on the new // projection in the left side. Map transformMapping = new HashMap<>(); + int newFieldIndex = oldLeft; for (RexNode callInRight : callsWithCorrelationInRight) { - RexBuilder xb = builder.getRexBuilder(); - RexNode v = xb.makeCorrel(builder.peek().getRowType(), correlate.getCorrelationId()); - RexNode flatCorrelationInRight = xb.makeFieldAccess(v, oldLeft + transformMapping.size()); - transformMapping.put(callInRight, flatCorrelationInRight); + if (!isDirectFieldAccess(callInRight, correlate.getCorrelationId())) { + RexBuilder xb = builder.getRexBuilder(); + RexNode v = xb.makeCorrel(builder.peek().getRowType(), correlate.getCorrelationId()); + RexNode flatCorrelationInRight = xb.makeFieldAccess(v, newFieldIndex); + transformMapping.put(callInRight, flatCorrelationInRight); + newFieldIndex++; + } } - // Select the required fields/columns from the left side of the correlation. Based on the code - // above all these fields should be at the end of the left relational expression. + // Select the required fields/columns from the left side of the correlation: the columns + // read by the direct field accesses plus the newly projected columns, which are at the + // end of the left relational expression. List requiredFields = builder.fields( - ImmutableBitSet.range(oldLeft, oldLeft + callsWithCorrelationOverLeft.size()).asList()); + requiredColumns + .set(oldLeft, oldLeft + callsWithCorrelationOverLeft.size()) + .build() + .asList()); final int newLeft = builder.fields().size(); // Transform the expressions in the right side using the mapping constructed earlier. @@ -264,8 +297,13 @@ private static boolean isSimpleCorrelatedExpression(RexNode node, CorrelationId * +(10, $cor0.DEPTNO) -> TRUE * /(100,+(10, $cor0.DEPTNO)) -> TRUE * CAST(+(10, $cor0.DEPTNO)):INTEGER NOT NULL -> TRUE + * CASE(IS NOT NULL($cor0.PATH), $cor0.PATH, ARRAY(null:INTEGER)) -> TRUE * +($0, $cor0.DEPTNO) -> FALSE * } + * + *

A subexpression built only from literals and dynamic parameters, such + * as {@code ARRAY(null:INTEGER)} above, is neutral: it neither qualifies nor + * disqualifies the enclosing call. */ private static class SimpleCorrelationDetector extends RexVisitorImpl<@Nullable Boolean> { @@ -284,7 +322,8 @@ private SimpleCorrelationDetector(CorrelationId corrId) { return Boolean.FALSE; } - @Override public Boolean visitCall(RexCall call) { + @Override public @Nullable Boolean visitCall(RexCall call) { + // Constant operands must not disqualify the call Boolean hasSimpleCorrelation = null; for (RexNode op : call.operands) { Boolean b = op.accept(this); @@ -292,7 +331,8 @@ private SimpleCorrelationDetector(CorrelationId corrId) { hasSimpleCorrelation = hasSimpleCorrelation == null ? b : hasSimpleCorrelation && b; } } - return hasSimpleCorrelation == null ? Boolean.FALSE : hasSimpleCorrelation; + // If unsure return null; caller will decide + return hasSimpleCorrelation; } @Override public @Nullable Boolean visitFieldAccess(RexFieldAccess fieldAccess) { @@ -332,8 +372,10 @@ private static RexNode replaceCorrelationsWithInputRef(RexNode exp, RelBuilder b } /** - * A visitor traversing row expressions and replacing calls with other expressions according - * to the specified mapping. + * A visitor traversing row expressions and replacing calls and field + * accesses with other expressions according to the specified mapping. + * The mapping is consulted before recursing so that the outermost + * matching expression wins. */ private static final class CallReplacer extends RexShuttle { private final Map mapping; @@ -350,5 +392,14 @@ private static final class CallReplacer extends RexShuttle { return super.visitCall(oldCall); } } + + @Override public RexNode visitFieldAccess(RexFieldAccess fieldAccess) { + RexNode replacement = mapping.get(fieldAccess); + if (replacement != null) { + return replacement; + } else { + return super.visitFieldAccess(fieldAccess); + } + } } } diff --git a/core/src/test/java/org/apache/calcite/sql2rel/CorrelateProjectExtractorTest.java b/core/src/test/java/org/apache/calcite/sql2rel/CorrelateProjectExtractorTest.java index 72276d42c33..da0a17296b3 100644 --- a/core/src/test/java/org/apache/calcite/sql2rel/CorrelateProjectExtractorTest.java +++ b/core/src/test/java/org/apache/calcite/sql2rel/CorrelateProjectExtractorTest.java @@ -84,6 +84,78 @@ public static Frameworks.ConfigBuilder config() { assertThat(after, hasTree(planAfter)); } + /** Test case for [CALCITE-7646] + * CorrelateProjectExtractor does not handle nested field accesses cor0.field0.field1. */ + @Test void testNestedCorrelationFieldAccessInFilter() { + final RelBuilder builder = RelBuilder.create(config().build()); + final Holder<@Nullable RexCorrelVariable> v = Holder.empty(); + RelNode before = builder.scan("EMP") + .project( + builder.alias( + builder.call(SqlStdOperatorTable.ROW, + builder.field("EMPNO"), builder.field("DEPTNO")), "R")) + .variable(v::set) + .scan("DEPT") + .filter( + builder.equals(builder.field(0), + builder.getRexBuilder().makeFieldAccess(builder.field(v.get(), "R"), 1))) + .correlate(JoinRelType.LEFT, v.get().id, builder.field(2, 0, "R")) + .build(); + + final String planBefore = "" + + "LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{0}])\n" + + " LogicalProject(R=[ROW($0, $7)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n" + + " LogicalFilter(condition=[=($0, $cor0.R.EXPR$1)])\n" + + " LogicalTableScan(table=[[scott, DEPT]])\n"; + assertThat(before, hasTree(planBefore)); + + RelNode after = before.accept(new CorrelateProjectExtractor(RelFactories.LOGICAL_BUILDER)); + final String planAfter = "" + + "LogicalProject(R=[$0], DEPTNO=[$2], DNAME=[$3], LOC=[$4])\n" + + " LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{1}])\n" + + " LogicalProject(R=[ROW($0, $7)], $f1=[ROW($0, $7).EXPR$1])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n" + + " LogicalFilter(condition=[=($0, $cor0.$f1)])\n" + + " LogicalTableScan(table=[[scott, DEPT]])\n"; + assertThat(after, hasTree(planAfter)); + } + + /** Tests that a constant call operand, such as {@code POWER(2, 3)}, does + * not prevent extracting the enclosing correlated call. */ + @Test void testCorrelationCallWithConstantCallOperandInFilter() { + final RelBuilder builder = RelBuilder.create(config().build()); + final Holder<@Nullable RexCorrelVariable> v = Holder.empty(); + RelNode before = builder.scan("EMP") + .variable(v::set) + .scan("DEPT") + .filter( + builder.equals(builder.field(0), + builder.call(SqlStdOperatorTable.PLUS, + builder.call(SqlStdOperatorTable.POWER, + builder.literal(2), builder.literal(3)), + builder.field(v.get(), "DEPTNO")))) + .correlate(JoinRelType.LEFT, v.get().id, builder.field(2, 0, "DEPTNO")) + .build(); + + final String planBefore = "" + + "LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{7}])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n" + + " LogicalFilter(condition=[=($0, +(POWER(2, 3), $cor0.DEPTNO))])\n" + + " LogicalTableScan(table=[[scott, DEPT]])\n"; + assertThat(before, hasTree(planBefore)); + + RelNode after = before.accept(new CorrelateProjectExtractor(RelFactories.LOGICAL_BUILDER)); + final String planAfter = "" + + "LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], DEPTNO0=[$9], DNAME=[$10], LOC=[$11])\n" + + " LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{8}])\n" + + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[+(POWER(2, 3), $7)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n" + + " LogicalFilter(condition=[=($0, $cor0.$f8)])\n" + + " LogicalTableScan(table=[[scott, DEPT]])\n"; + assertThat(after, hasTree(planAfter)); + } + @Test void testDoubleCorrelationCallOverVariableInFilters() { final RelBuilder builder = RelBuilder.create(config().build()); final Holder<@Nullable RexCorrelVariable> v = Holder.empty(); diff --git a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java index 240f9a36ae0..a7345450d86 100644 --- a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java +++ b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java @@ -290,9 +290,9 @@ public static Frameworks.ConfigBuilder config() { + " LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[left])\n" + " LogicalValues(tuples=[[{ 7369 }, { 7499 }]])\n" + " LogicalAggregate(group=[{0}], agg#0=[SINGLE_VALUE($1)])\n" - + " LogicalProject(EMPNO1=[$12], EXPR$0=[||(||($1, ' from dept '), $13)])\n" - + " LogicalJoin(condition=[AND(=($7, $10), =($9, $11))], joinType=[left])\n" - + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], DEPTNO0=[$7], EMPNO0=[CAST($0):INTEGER NOT NULL])\n" + + " LogicalProject(EMPNO1=[$11], EXPR$0=[||(||($1, ' from dept '), $12)])\n" + + " LogicalJoin(condition=[AND(=($7, $9), =($8, $10))], joinType=[left])\n" + + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], EMPNO0=[CAST($0):INTEGER NOT NULL])\n" + " LogicalTableScan(table=[[scott, EMP]])\n" + " LogicalAggregate(group=[{0, 1, 2}], agg#0=[SINGLE_VALUE($3)])\n" + " LogicalProject(DEPTNO0=[$3], EMPNO0=[$4], EMPNO=[$5], DNAME=[$1])\n" @@ -556,29 +556,29 @@ public static Frameworks.ConfigBuilder config() { // LogicalTableScan(table=[[scott, EMP]]) final String planAfter = "" + "LogicalSort(sort0=[$0], dir0=[ASC])\n" - + " LogicalProject(DNAME=[$1], C=[$7])\n" - + " LogicalJoin(condition=[AND(=($0, $5), =($4, $6))], joinType=[left])\n" - + " LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], DEPTNO0=[$0], $f4=[*($0, 100)])\n" + + " LogicalProject(DNAME=[$1], C=[$6])\n" + + " LogicalJoin(condition=[AND(=($0, $4), =($3, $5))], joinType=[left])\n" + + " LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], $f3=[*($0, 100)])\n" + " LogicalTableScan(table=[[scott, DEPT]])\n" - + " LogicalProject(DEPTNO8=[$0], $f4=[$1], EXPR$0=[CASE(IS NOT NULL($4), $4, 0)])\n" + + " LogicalProject(DEPTNO8=[$0], $f3=[$1], EXPR$0=[CASE(IS NOT NULL($4), $4, 0)])\n" + " LogicalJoin(condition=[AND(IS NOT DISTINCT FROM($0, $2), IS NOT DISTINCT FROM($1, $3))], joinType=[left])\n" - + " LogicalProject(DEPTNO=[$0], $f4=[*($0, 100)])\n" + + " LogicalProject(DEPTNO=[$0], $f3=[*($0, 100)])\n" + " LogicalTableScan(table=[[scott, DEPT]])\n" + " LogicalAggregate(group=[{0, 1}], EXPR$0=[COUNT()])\n" - + " LogicalProject(DEPTNO8=[$7], $f4=[$9])\n" + + " LogicalProject(DEPTNO8=[$7], $f3=[$9])\n" + " LogicalFilter(condition=[IS NOT NULL($7)])\n" - + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], i=[$11], $f4=[$9])\n" + + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], i=[$11], $f3=[$9])\n" + " LogicalJoin(condition=[=($8, $10)], joinType=[inner])\n" + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SAL0=[CAST($5):DECIMAL(12, 2)])\n" + " LogicalTableScan(table=[[scott, EMP]])\n" - + " LogicalProject($f4=[$0], SAL0=[$1], $f2=[true])\n" + + " LogicalProject($f3=[$0], SAL0=[$1], $f2=[true])\n" + " LogicalAggregate(group=[{0, 1}])\n" - + " LogicalProject($f4=[$1], SAL0=[$2])\n" + + " LogicalProject($f3=[$1], SAL0=[$2])\n" + " LogicalJoin(condition=[AND(>($2, CAST($0):DECIMAL(12, 2) NOT NULL), <($1, $0))], joinType=[inner])\n" + " LogicalValues(tuples=[[{ 1000 }, { 2000 }, { 3000 }]])\n" + " LogicalJoin(condition=[true], joinType=[inner])\n" + " LogicalAggregate(group=[{0}])\n" - + " LogicalProject($f4=[*($0, 100)])\n" + + " LogicalProject($f3=[*($0, 100)])\n" + " LogicalTableScan(table=[[scott, DEPT]])\n" + " LogicalAggregate(group=[{0}])\n" + " LogicalProject(SAL0=[CAST($5):DECIMAL(12, 2)])\n" @@ -1793,9 +1793,9 @@ public static Frameworks.ConfigBuilder config() { RelDecorrelator.decorrelateQuery(before, builder, RuleSets.ofList(Collections.emptyList()), RuleSets.ofList(Collections.emptyList())); final String planAfter = "" - + "LogicalProject(DEPTNO=[$0], I0=[$3], I1=[$8])\n" - + " LogicalJoin(condition=[AND(=($0, $6), =($5, $7))], joinType=[left])\n" - + " LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], EXPR$0=[$5], DEPTNO0=[$0], $f5=[>(CAST($0):INTEGER NOT NULL, 0)])\n" + + "LogicalProject(DEPTNO=[$0], I0=[$3], I1=[$7])\n" + + " LogicalJoin(condition=[AND(=($0, $5), =($4, $6))], joinType=[left])\n" + + " LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], EXPR$0=[$5], $f4=[>(CAST($0):INTEGER NOT NULL, 0)])\n" + " LogicalJoin(condition=[=($3, $4)], joinType=[left])\n" + " LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], DEPTNO0=[CAST($0):SMALLINT NOT NULL])\n" + " LogicalTableScan(table=[[scott, DEPT]])\n" @@ -1807,12 +1807,12 @@ public static Frameworks.ConfigBuilder config() { + " LogicalProject(DEPTNO0=[CAST($0):SMALLINT NOT NULL])\n" + " LogicalTableScan(table=[[scott, DEPT]])\n" + " LogicalAggregate(group=[{0, 1}], EXPR$0=[MIN($2)])\n" - + " LogicalProject(DEPTNO0=[$8], $f5=[$9], $f0=[0])\n" + + " LogicalProject(DEPTNO0=[$8], $f4=[$9], $f0=[0])\n" + " LogicalJoin(condition=[=($7, $8)], joinType=[inner])\n" + " LogicalFilter(condition=[=($1, 'SMITH')])\n" + " LogicalTableScan(table=[[scott, EMP]])\n" + " LogicalFilter(condition=[$1])\n" - + " LogicalProject(DEPTNO=[$0], $f5=[>(CAST($0):INTEGER NOT NULL, 0)])\n" + + " LogicalProject(DEPTNO=[$0], $f4=[>(CAST($0):INTEGER NOT NULL, 0)])\n" + " LogicalJoin(condition=[=($3, $4)], joinType=[left])\n" + " LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], DEPTNO0=[CAST($0):SMALLINT NOT NULL])\n" + " LogicalTableScan(table=[[scott, DEPT]])\n" diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml index 64061b8910f..cc710a58e69 100644 --- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml @@ -3097,9 +3097,9 @@ LogicalProject(NAME=[$1], EXPR$1=[$2]) ($5, 1000))]) LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]]) LogicalFilter(condition=[=($1, $0)]) LogicalAggregate(group=[{0, 1, 2}]) - LogicalProject(SAL=[$5], SAL0=[$8], $f9=[$9]) + LogicalProject(SAL=[$5], SAL0=[$8], $f8=[$9]) LogicalJoin(condition=[OR(=($8, $5), $9)], joinType=[inner]) LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], SLACKER=[$8]) LogicalFilter(condition=[AND(=($7, 20), >($5, 1000))]) LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]]) LogicalAggregate(group=[{0, 1}]) - LogicalProject(SAL=[$5], $f9=[=($5, 4)]) + LogicalProject(SAL=[$5], $f8=[=($5, 4)]) LogicalFilter(condition=[AND(=($7, 20), >($5, 1000))]) LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]]) ]]> @@ -9212,9 +9212,9 @@ LEFT JOIN LATERAL ( @@ -1796,18 +1796,18 @@ cross join lateral @@ -5222,9 +5222,9 @@ LogicalProject(C=[$0], D=[$1], C0=[$2]) diff --git a/core/src/test/resources/sql/struct.iq b/core/src/test/resources/sql/struct.iq index bf25d4cadb2..d5b3b681b39 100644 --- a/core/src/test/resources/sql/struct.iq +++ b/core/src/test/resources/sql/struct.iq @@ -337,4 +337,193 @@ select row(emp.*, dept.*).deptno0 from emp join dept on emp.deptno = dept.deptno !ok +# 4 test cases for [CALCITE-7646] CorrelateProjectExtractor +# does not handle nested field accesses cor0.field0.field1. + +# All queries use LATERAL, which converts directly to a Correlate. +# The results were validated on Postgres + +!use scott + +# Before the fix planning failes with +# "Mappings$NoElementException: source #0 has no target in mapping". +select t.dd, t.x +from dept d, +lateral (select d.deptno as dd, u.x + from unnest(array[d.deptno + 100]) as u(x)) as t +where d.dname = 'SALES'; ++----+-----+ +| DD | X | ++----+-----+ +| 30 | 130 | ++----+-----+ +(1 row) + +!ok +!if (use_old_decorr) { +EnumerableCalc(expr#0..3=[{inputs}], DD=[$t2], X=[$t3]) + EnumerableCorrelate(correlation=[$cor1], joinType=[inner], requiredColumns=[{0, 1}]) + EnumerableCalc(expr#0..2=[{inputs}], expr#3=[100], expr#4=[+($t0, $t3)], expr#5=[ARRAY($t4)], expr#6=['SALES':VARCHAR(14)], expr#7=[=($t1, $t6)], DEPTNO=[$t0], $f3=[$t5], $condition=[$t7]) + EnumerableTableScan(table=[[scott, DEPT]]) + EnumerableCalc(expr#0=[{inputs}], expr#1=[$cor1], expr#2=[$t1.DEPTNO], DD=[$t2], X=[$t0]) + EnumerableUncollect + EnumerableCalc(expr#0=[{inputs}], expr#1=[$cor1], expr#2=[$t1.$f3], EXPR$0=[$t2]) + EnumerableValues(tuples=[[{ 0 }]]) +!plan +!} +!if (use_new_decorr) { +EnumerableCalc(expr#0..3=[{inputs}], expr#4=['SALES':VARCHAR(14)], expr#5=[=($t1, $t4)], DD=[$t2], X=[$t3], $condition=[$t5]) + EnumerableCorrelate(correlation=[$cor1], joinType=[inner], requiredColumns=[{0}]) + EnumerableCalc(expr#0..2=[{inputs}], proj#0..1=[{exprs}]) + EnumerableTableScan(table=[[scott, DEPT]]) + EnumerableCalc(expr#0=[{inputs}], expr#1=[$cor1], expr#2=[$t1.DEPTNO], DD=[$t2], X=[$t0]) + EnumerableUncollect + EnumerableCalc(expr#0=[{inputs}], expr#1=[$cor1], expr#2=[$t1.DEPTNO], expr#3=[100], expr#4=[+($t2, $t3)], expr#5=[ARRAY($t4)], EXPR$0=[$t5]) + EnumerableValues(tuples=[[{ 0 }]]) +!plan +!} + +select t.dd, t.dd1, t.x +from dept d, +lateral (select d.deptno as dd, d.deptno + 1 as dd1, u.x + from unnest(array[1, 2]) as u(x)) as t +where d.dname = 'SALES'; ++----+-----+---+ +| DD | DD1 | X | ++----+-----+---+ +| 30 | 31 | 1 | +| 30 | 31 | 2 | ++----+-----+---+ +(2 rows) + +!ok +!if (use_old_decorr) { +EnumerableCalc(expr#0..4=[{inputs}], proj#0..2=[{exprs}]) + EnumerableHashJoin(condition=[AND(=($3, $5), =($4, $6))], joinType=[semi]) + EnumerableCalc(expr#0..2=[{inputs}], proj#0..2=[{exprs}], DEPTNO=[$t0], $f3=[$t1]) + EnumerableNestedLoopJoin(condition=[true], joinType=[inner]) + EnumerableCalc(expr#0..2=[{inputs}], expr#3=[1], expr#4=[+($t0, $t3)], expr#5=['SALES':VARCHAR(14)], expr#6=[=($t1, $t5)], DEPTNO=[$t0], $f3=[$t4], $condition=[$t6]) + EnumerableTableScan(table=[[scott, DEPT]]) + EnumerableUncollect + EnumerableCalc(expr#0=[{inputs}], expr#1=[1], expr#2=[2], expr#3=[ARRAY($t1, $t2)], EXPR$0=[$t3]) + EnumerableValues(tuples=[[{ 0 }]]) + EnumerableCalc(expr#0..2=[{inputs}], expr#3=[1], expr#4=[+($t0, $t3)], expr#5=['SALES':VARCHAR(14)], expr#6=[=($t1, $t5)], DEPTNO=[$t0], $f3=[$t4], $condition=[$t6]) + EnumerableTableScan(table=[[scott, DEPT]]) +!plan +!} +!if (use_new_decorr) { +EnumerableCalc(expr#0..3=[{inputs}], proj#0..2=[{exprs}]) + EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($3, $4)], joinType=[semi]) + EnumerableCalc(expr#0..1=[{inputs}], expr#2=[1], expr#3=[+($t0, $t2)], DEPTNO=[$t0], $f1=[$t3], EXPR$0=[$t1], DEPTNO0=[$t0]) + EnumerableNestedLoopJoin(condition=[true], joinType=[inner]) + EnumerableCalc(expr#0..2=[{inputs}], expr#3=['SALES':VARCHAR(14)], expr#4=[=($t1, $t3)], DEPTNO=[$t0], $condition=[$t4]) + EnumerableTableScan(table=[[scott, DEPT]]) + EnumerableUncollect + EnumerableCalc(expr#0=[{inputs}], expr#1=[1], expr#2=[2], expr#3=[ARRAY($t1, $t2)], EXPR$0=[$t3]) + EnumerableValues(tuples=[[{ 0 }]]) + EnumerableCalc(expr#0..2=[{inputs}], expr#3=['SALES':VARCHAR(14)], expr#4=[=($t1, $t3)], DEPTNO=[$t0], $condition=[$t4]) + EnumerableTableScan(table=[[scott, DEPT]]) +!plan +!} + +# COALESCE(d.path, ARRAY[CAST(NULL AS INTEGER)]) converts to +# CASE(IS NOT NULL($cor0.PATH), $cor0.PATH, ARRAY(null:INTEGER)). The constant +# ARRAY(null:INTEGER) operand must not prevent extracting the CASE to the +# left input of the Correlate operator +select d.deptno, t.x +from (select deptno, + case when deptno = 10 then array[deptno, deptno + 1] end as path + from dept) as d, +lateral (select * from unnest(coalesce(d.path, array[cast(null as integer)])) as u(x)) as t +order by d.deptno, t.x; ++--------+----+ +| DEPTNO | X | ++--------+----+ +| 10 | 10 | +| 10 | 11 | +| 20 | | +| 30 | | +| 40 | | ++--------+----+ +(5 rows) + +!ok +!if (use_old_decorr) { +EnumerableSort(sort0=[$0], sort1=[$1], dir0=[ASC], dir1=[ASC]) + EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0], X=[$t2]) + EnumerableCorrelate(correlation=[$cor0], joinType=[inner], requiredColumns=[{1}]) + EnumerableCalc(expr#0..2=[{inputs}], expr#3=[CAST($t0):INTEGER NOT NULL], expr#4=[10], expr#5=[=($t3, $t4)], expr#6=[1], expr#7=[+($t0, $t6)], expr#8=[ARRAY($t3, $t7)], expr#9=[null:INTEGER NOT NULL ARRAY], expr#10=[CASE($t5, $t8, $t9)], expr#11=[IS NOT NULL($t10)], expr#12=[CAST($t10):INTEGER NOT NULL ARRAY NOT NULL], expr#13=[CAST($t12):INTEGER ARRAY NOT NULL], expr#14=[null:INTEGER], expr#15=[ARRAY($t14)], expr#16=[CASE($t11, $t13, $t15)], DEPTNO=[$t0], $f2=[$t16]) + EnumerableTableScan(table=[[scott, DEPT]]) + EnumerableUncollect + EnumerableCalc(expr#0=[{inputs}], expr#1=[$cor0], expr#2=[$t1.$f2], EXPR$0=[$t2]) + EnumerableValues(tuples=[[{ 0 }]]) +!plan +!} +!if (use_new_decorr) { +EnumerableSort(sort0=[$0], sort1=[$1], dir0=[ASC], dir1=[ASC]) + EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0], X=[$t2]) + EnumerableCorrelate(correlation=[$cor0], joinType=[inner], requiredColumns=[{1}]) + EnumerableCalc(expr#0..2=[{inputs}], expr#3=[CAST($t0):INTEGER NOT NULL], expr#4=[10], expr#5=[=($t3, $t4)], expr#6=[1], expr#7=[+($t0, $t6)], expr#8=[ARRAY($t3, $t7)], expr#9=[null:INTEGER NOT NULL ARRAY], expr#10=[CASE($t5, $t8, $t9)], DEPTNO=[$t0], PATH=[$t10]) + EnumerableTableScan(table=[[scott, DEPT]]) + EnumerableUncollect + EnumerableCalc(expr#0=[{inputs}], expr#1=[$cor0], expr#2=[$t1.PATH], expr#3=[IS NOT NULL($t2)], expr#4=[CAST($t2):INTEGER NOT NULL ARRAY NOT NULL], expr#5=[CAST($t4):INTEGER ARRAY NOT NULL], expr#6=[null:INTEGER], expr#7=[ARRAY($t6)], expr#8=[CASE($t3, $t5, $t7)], EXPR$0=[$t8]) + EnumerableValues(tuples=[[{ 0 }]]) +!plan +!} + +!use bookstore + +# Before the fix decorrelation left a dangling +# correlated expression, and code generation failed with "AssertionError: +# Correlation variable $cor1 should be defined". + +# EnumerableRules.ENUMERABLE_CORRELATE_RULE is removed so that this test +# fails if the plan cannot be fully decorrelated. +!set planner-rules "-EnumerableRules.ENUMERABLE_CORRELATE_RULE" + +select a."aid", t.lat +from "bookstore"."authors" a, +lateral (select b."aid" as c, (a."birthPlace")."coords"."latitude" as lat + from "bookstore"."authors" b + where b."aid" = a."aid") as t; ++-----+---------+ +| aid | LAT | ++-----+---------+ +| 1 | 47.24 | +| 2 | 35.3387 | +| 3 | | ++-----+---------+ +(3 rows) + +!ok +!if (use_old_decorr) { +EnumerableCalc(expr#0..4=[{inputs}], aid=[$t3], LAT=[$t0]) + EnumerableHashJoin(condition=[AND(=($1, $3), IS NOT DISTINCT FROM($2, $4))], joinType=[inner]) + EnumerableCalc(expr#0..1=[{inputs}], LAT=[$t1], aid=[$t0], $f4=[$t1]) + EnumerableNestedLoopJoin(condition=[true], joinType=[inner]) + EnumerableCalc(expr#0..3=[{inputs}], aid=[$t0]) + EnumerableTableScan(table=[[bookstore, authors]]) + EnumerableAggregate(group=[{0}]) + EnumerableCalc(expr#0..3=[{inputs}], expr#4=[$t2.coords], expr#5=[$t4.latitude], $f4=[$t5]) + EnumerableTableScan(table=[[bookstore, authors]]) + EnumerableCalc(expr#0..3=[{inputs}], expr#4=[$t2.coords], expr#5=[$t4.latitude], aid=[$t0], $f4=[$t5]) + EnumerableTableScan(table=[[bookstore, authors]]) +!plan +!} +!if (use_new_decorr) { +EnumerableCalc(expr#0..4=[{inputs}], aid=[$t3], LAT=[$t0]) + EnumerableHashJoin(condition=[AND(IS NOT DISTINCT FROM($1, $3), IS NOT DISTINCT FROM($4.coords, $2.coords), IS NOT DISTINCT FROM($4.city, $2.city), IS NOT DISTINCT FROM($4.country, $2.country))], joinType=[inner]) + EnumerableCalc(expr#0..2=[{inputs}], expr#3=[$t1.coords], expr#4=[$t3.latitude], $f0=[$t4], aid0=[$t0], birthPlace=[$t1]) + EnumerableHashJoin(condition=[=($0, $2)], joinType=[inner]) + EnumerableAggregate(group=[{0, 2}]) + EnumerableTableScan(table=[[bookstore, authors]]) + EnumerableCalc(expr#0..3=[{inputs}], aid=[$t0]) + EnumerableTableScan(table=[[bookstore, authors]]) + EnumerableCalc(expr#0..3=[{inputs}], aid=[$t0], birthPlace=[$t2]) + EnumerableTableScan(table=[[bookstore, authors]]) +!plan +!} + +!set planner-rules original + # End struct.iq