Ian Bertolacci created CALCITE-7405:
---------------------------------------

             Summary: SqlToRelConverter creates invalid plan when converting 
SQL with correlated subquery and RelBuilder engages bloat optimizations
                 Key: CALCITE-7405
                 URL: https://issues.apache.org/jira/browse/CALCITE-7405
             Project: Calcite
          Issue Type: Bug
    Affects Versions: 1.41.0, 1.40.0, 1.39.0, 1.38.0, 1.37.0
            Reporter: Ian Bertolacci


In 
[SqlToRelConverter.convertNonAggregateSelectList|https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java#L5021],
 the converter will build the current projection, but then use the BlackBoard's 
root node as the input and rebuilds a Project node using the previously built 
Projects expressions.

The issue is that the initial Project may be the result of Project-Project 
fusion (because of bloat optimization) causing the resulting Project's 
RexInputRefs to be adjusted, and so using those expressions on top of 
BlackBoard's root node (which was eliminated by the fusion) is invalid, as the 
references are mis-aligned.

A trivial example:
{code:sql}
select C0_12 + (select sum(C0_32) from T3 where T2.id = T3.C0_31)
from T2 join T1 on T2.C0_21 + 1 = T1.id
{code}
During the conversion, we get to RelBuilder.project_ 
([L2150|https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/tools/RelBuilder.java#L2150])
{code:java}
/* This project elides the compound join expression */
7:LogicalProject(ID=[$0:ID], C0_21=[$1:C0_21], C0_22=[$2:C0_22], ID0=[$4:ID0], 
C0_12=[$5:C0_12], C0_13=[$6:C0_13], C0_14=[$7:C0_14])
└── 6:LogicalJoin(condition=[=($3:LHS.$f3, $4:T1.ID)], joinType=[inner])
    ├── 5:LogicalProject(ID=[$0:T2.ID], C0_21=[$1:T2.C0_21], 
C0_22=[$2:T2.C0_22], $f3=[+($1:T2.C0_21, 1)])
    │   └── 1:QueryTableScan(table=[T2], Schema=[ID:Dimension, C0_21:Dimension, 
C0_22:Decimal(3, 3)])
    └── 3:QueryTableScan(table=[T1], Schema=[ID:Dimension, C0_12:Decimal(3, 3), 
C0_13:Text, C0_14:Timestamp])
{code}
and is holding this projection expression:
{code:java}
+($4:C0_12, $SCALAR_QUERY({ ... }))
{code}
Then it engages RelOptUtil.pushPastProjectUnlessBloat, which detects that it 
can fuse the projections of the working projections and the input Projection's 
projections and produces, which shifts $4 to $5, and the result is this Project 
tree:
{code:java}
13:LogicalProject(EXPR$0=[+($5:C0_12 /*shifted, but references valid field */, 
$SCALAR_QUERY({...}))])
│ /* Join expression eliding project eliminated */
└── 6:LogicalJoin(condition=[=($3:LHS.$f3, $4:T1.ID)], joinType=[inner])
    ├── 5:LogicalProject(ID=[$0:T2.ID], C0_21=[$1:T2.C0_21], 
C0_22=[$2:T2.C0_22], $f3=[+($1:T2.C0_21, 1)])
    │   └── 1:QueryTableScan(table=[T2], Schema=[ID:Dimension, C0_21:Dimension, 
C0_22:Decimal(3, 3)])
    └── 3:QueryTableScan(table=[T1], Schema=[ID:Dimension, C0_12:Decimal(3, 3), 
C0_13:Text, C0_14:Timestamp])
{code}
This is still a valid tree

This is then propogated up to SqlToRelConverter.convertNonAggregateSelectList 
([L5012|http://example.com|https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java#L5012]),
 and it then detects that there is a correlation, and that it should add the 
correlation variable to that Project. To do this it will call 
RelBuilder.projectNamed. Since the node was already built, it needs to push 
some node onto the RelBuilder stack. [It uses the Blackboard's 
root|https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java#L5021],
 which happens to be the Project which was eliminated by the fusion. When it 
then uses the working Project's expressions, they now reference the incorrect 
position:
{code:java}
18:LogicalProject(variablesSet=[[$cor0]], EXPR$0=[+($5:C0_13 /* reference now 
points to a different field */, $SCALAR_QUERY({...})])
│ /* Join expression eliding project re-introduced */
└── 17:LogicalProject(ID=[$0:ID], C0_21=[$1:C0_21], C0_22=[$2:C0_22], 
ID0=[$4:ID0], C0_12=[$5:C0_12], C0_13=[$6:C0_13], C0_14=[$7:C0_14])
    └── 16:LogicalJoin(condition=[=($3:LHS.$f3, $4:T1.ID)], joinType=[inner])
        ├── 15:LogicalProject(ID=[$0:T2.ID], C0_21=[$1:T2.C0_21], 
C0_22=[$2:T2.C0_22], $f3=[+($1:T2.C0_21, 1)])
        │   └── 1:QueryTableScan(table=[T2], Schema=[ID:Dimension, 
C0_21:Dimension, C0_22:Decimal(3, 3)])
        └── 3:QueryTableScan(table=[T1], Schema=[ID:Dimension, C0_12:Decimal(3, 
3), C0_13:Text, C0_14:Timestamp])
{code}
In this case, there are other layers (in our runtime, at least) which will fail 
in type-checking because this type is incorrect for addition, but it is 
completely possible that the invalid reference has the correct type, and no 
issue is detected.

My belief is that the the converter should push the working Project's input 
onto the stack:
{code:java}
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java 
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 8fc83104c..6cc74eef5 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -5018,7 +5018,7 @@ private void convertNonAggregateSelectList(
       // correlation variables have been normalized in p.r, we should use 
expressions
       // in p.r instead of the original exprs
       Project project1 = (Project) p.r;
-      r = relBuilder.push(bb.root())
+      r = relBuilder.push(project1.getInput())
           .projectNamed(project1.getProjects(), uniqueFieldNames, true,
               ImmutableSet.of(p.id))
           .build();
{code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to