This is an automated email from the ASF dual-hosted git repository.
iwanttobepowerful pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new cf01ee8205 [CALCITE-7899] SubQueryRemoveRule binds an outer query's
correlation variable to the wrong input when a Project has several sub-queries
cf01ee8205 is described below
commit cf01ee82056237eb2dfb58d93dc184331ce80527
Author: Weihua Zhang <[email protected]>
AuthorDate: Wed Oct 28 14:48:51 2026 +0800
[CALCITE-7899] SubQueryRemoveRule binds an outer query's correlation
variable to the wrong input when a Project has several sub-queries
---
.../calcite/rel/rules/SubQueryRemoveRule.java | 38 +++++++----
core/src/test/resources/sql/sub-query.iq | 74 ++++++++++++++++++++++
2 files changed, 98 insertions(+), 14 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
index c14e654a01..74cab57980 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
@@ -967,24 +967,34 @@ private static void matchProject(SubQueryRemoveRule rule,
final Project project = call.rel(0);
final Set<CorrelationId> projectVariablesSet = project.getVariablesSet();
final RelBuilder builder = call.builder();
- final RexSubQuery e =
- requireNonNull(RexUtil.SubQueryFinder.find(project.getProjects()));
- final RelOptUtil.Logic logic =
- LogicVisitor.find(RelOptUtil.Logic.TRUE_FALSE_UNKNOWN,
- project.getProjects(), e);
builder.push(project.getInput());
- final int fieldCount = builder.peek().getRowType().getFieldCount();
- final Set<CorrelationId> variablesSet =
- RelOptUtil.getVariablesUsed(e.rel);
- if (!projectVariablesSet.isEmpty()) {
+ // Rewrite all sub-queries in one call, as matchFilter does. If only one
+ // were rewritten per call, the Project created for the remaining ones
+ // would no longer define the correlation variables that they use, and a
+ // later call could not tell this Project's variables from those of an
+ // outer scope.
+ int count = 0;
+ List<RexNode> projects = project.getProjects();
+ while (true) {
+ final RexSubQuery e = RexUtil.SubQueryFinder.find(projects);
+ if (e == null) {
+ assert count > 0;
+ break;
+ }
+ ++count;
+ final RelOptUtil.Logic logic =
+ LogicVisitor.find(RelOptUtil.Logic.TRUE_FALSE_UNKNOWN, projects, e);
+ final Set<CorrelationId> variablesSet =
+ RelOptUtil.getVariablesUsed(e.rel);
// Only consider the correlated variables which originated from this
sub-query level.
variablesSet.retainAll(projectVariablesSet);
+ final RexNode target =
+ rule.apply(e, variablesSet, logic,
+ builder, 1, builder.peek().getRowType().getFieldCount(), count);
+ final RexShuttle shuttle = new ReplaceSubQueryShuttle(e, target);
+ projects = shuttle.apply(projects);
}
- final RexNode target =
- rule.apply(e, variablesSet, logic, builder, 1, fieldCount, 0);
- final RexShuttle shuttle = new ReplaceSubQueryShuttle(e, target);
- builder.project(shuttle.apply(project.getProjects()),
- project.getRowType().getFieldNames());
+ builder.project(projects, project.getRowType().getFieldNames());
call.transformTo(builder.build());
}
diff --git a/core/src/test/resources/sql/sub-query.iq
b/core/src/test/resources/sql/sub-query.iq
index 5df47b92f3..97ddb5c65d 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -11168,3 +11168,77 @@ ORDER BY empno;
(14 rows)
!ok
+
+# [CALCITE-7887] SubQueryRemoveRule fails for a sub-query inside a window
function
+# SELECT list with a sub-query correlated to the current scope followed by
+# one correlated to an outer scope. SubQueryRemoveRule used to rewrite one
+# sub-query of a Project per call, so the second one was bound to the inner
+# input instead of DEPT ("Correlation variable $cor1 should be defined").
+select d.dname,
+ (select max((select count(*) from emp as s where s.mgr = e.empno)
+ + (select count(*) from emp as s where s.deptno = d.deptno))
+ from emp as e) as c
+from dept as d
+order by d.dname;
++------------+----+
+| DNAME | C |
++------------+----+
+| ACCOUNTING | 8 |
+| OPERATIONS | 5 |
+| RESEARCH | 10 |
+| SALES | 11 |
++------------+----+
+(4 rows)
+
+!ok
+
+# The same sub-queries in the other order
+select d.dname,
+ (select max((select count(*) from emp as s where s.deptno = d.deptno)
+ + (select count(*) from emp as s where s.mgr = e.empno))
+ from emp as e) as c
+from dept as d
+order by d.dname;
++------------+----+
+| DNAME | C |
++------------+----+
+| ACCOUNTING | 8 |
+| OPERATIONS | 5 |
+| RESEARCH | 10 |
+| SALES | 11 |
++------------+----+
+(4 rows)
+
+!ok
+
+# Several sub-queries of different kinds in one SELECT list, each adding
+# auxiliary fields to the input
+select e.ename,
+ (select count(*) from emp as s where s.mgr = e.empno) as c1,
+ e.sal < (select max(s.sal) from emp as s) as c2,
+ exists (select 1 from emp as s where s.mgr = e.empno) as c3,
+ e.deptno in (select d.deptno from dept as d where d.loc = 'DALLAS') as c4,
+ e.mgr not in (select s.mgr from emp as s where s.job = 'CLERK') as c5
+from emp as e
+order by e.ename;
++--------+----+-------+-------+-------+-------+
+| ENAME | C1 | C2 | C3 | C4 | C5 |
++--------+----+-------+-------+-------+-------+
+| ADAMS | 0 | true | false | true | false |
+| ALLEN | 0 | true | false | false | false |
+| BLAKE | 5 | true | true | false | true |
+| CLARK | 1 | true | true | false | true |
+| FORD | 1 | true | true | true | true |
+| JAMES | 0 | true | false | false | false |
+| JONES | 2 | true | true | true | true |
+| KING | 3 | false | true | false | |
+| MARTIN | 0 | true | false | false | false |
+| MILLER | 0 | true | false | false | false |
+| SCOTT | 1 | true | true | true | true |
+| SMITH | 0 | true | false | true | false |
+| TURNER | 0 | true | false | false | false |
+| WARD | 0 | true | false | false | false |
++--------+----+-------+-------+-------+-------+
+(14 rows)
+
+!ok