From 57d62e186d1c62e8c68a456e85396624f057b068 Mon Sep 17 00:00:00 2001 From: krooswu Date: Sat, 20 Jun 2026 21:06:26 +0800 Subject: [PATCH 1/5] [CALCITE-7505] RelToSqlConverter produces duplicate FROM aliases for correlated subqueries when hasImplicitTableAlias() is true --- .../rel/rel2sql/RelToSqlConverter.java | 111 +++++++++++++++++- .../rel/rel2sql/RelToSqlConverterTest.java | 37 +++++- 2 files changed, 140 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java index 0404fa7bc51c..fc33b4fef5c3 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java @@ -21,6 +21,7 @@ import org.apache.calcite.linq4j.tree.Expressions; import org.apache.calcite.plan.RelOptSamplingParameters; import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.rel.BiRel; import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelCollations; import org.apache.calcite.rel.RelFieldCollation; @@ -54,11 +55,15 @@ import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexCall; +import org.apache.calcite.rex.RexCorrelVariable; import org.apache.calcite.rex.RexInputRef; import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.rex.RexLocalRef; import org.apache.calcite.rex.RexNode; import org.apache.calcite.rex.RexProgram; +import org.apache.calcite.rex.RexShuttle; +import org.apache.calcite.rex.RexSubQuery; +import org.apache.calcite.rex.RexVisitorImpl; import org.apache.calcite.sql.JoinConditionType; import org.apache.calcite.sql.JoinType; import org.apache.calcite.sql.SqlAsofJoin; @@ -547,9 +552,97 @@ public Result visit(Correlate e) { return result(join, leftResult, rightResult); } + /** + * Returns whether any expression (including nested sub-queries) references a correlated variable + * defined by the given input and having the same row type. + */ + private static boolean inputRowTypeMatchesCorrelVariable( + RelNode input, + Set correlIds, + Iterable exprs) { + + if (correlIds.isEmpty()) { + return false; + } + + final RelDataType inputRowType = input.getRowType(); + + /** Visitor that detects matching correlated variables. */ + class Finder extends RexVisitorImpl { + boolean found; + + Finder() { + super(true); + } + + @Override public Void visitCorrelVariable(RexCorrelVariable v) { + if (correlIds.contains(v.id) + && v.getType().equals(inputRowType)) { + found = true; + } + return null; + } + + @Override public Void visitSubQuery(RexSubQuery subQuery) { + if (!found && containsInRelTree(subQuery.rel)) { + found = true; + } + return found ? null : super.visitSubQuery(subQuery); + } + + private boolean containsInRelTree(RelNode rel) { + if (found) { + return true; + } + + rel.accept(new RexShuttle() { + @Override public RexNode visitCorrelVariable( + RexCorrelVariable v) { + if (correlIds.contains(v.id) + && v.getType().equals(inputRowType)) { + found = true; + } + return v; + } + + @Override public RexNode visitSubQuery( + RexSubQuery subQuery) { + if (!found) { + containsInRelTree(subQuery.rel); + } + return subQuery; + } + }); + + if (found) { + return true; + } + + for (RelNode child : rel.getInputs()) { + if (containsInRelTree(child)) { + return true; + } + } + + return false; + } + } + + final Finder finder = new Finder(); + + for (RexNode expr : exprs) { + expr.accept(finder); + if (finder.found) { + return true; + } + } + return false; + } /** Visits a Filter; called by {@link #dispatch} via reflection. */ public Result visit(Filter e) { final RelNode input = e.getInput(); + final Set definedHere = e.getVariablesSet(); + if (input instanceof Aggregate) { final Aggregate aggregate = (Aggregate) input; final boolean ignoreClauses = aggregate.getInput() instanceof Project; @@ -564,8 +657,22 @@ public Result visit(Filter e) { return builder.result(); } else { Result x = visitInput(e, 0, Clause.WHERE); - if (!e.getVariablesSet().isEmpty()) { - x = x.resetAlias(); + final boolean pushed = !definedHere.isEmpty() + && !(e.getInput() instanceof BiRel) + && inputRowTypeMatchesCorrelVariable( + e.getInput(), definedHere, ImmutableList.of(e.getCondition())); + if (pushed) { + String alias = x.neededAlias; + if (alias != null) { + x = x.resetAliasForCorrelation(alias, e.getInput().getRowType()); + } else { + alias = unqualifiedName(x.node); + if (alias == null) { + alias = "t"; + } + x = x.resetAliasForCorrelation + (alias, e.getInput().getRowType()); + } } parseCorrelTable(e, x); final Builder builder = x.builder(e); diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java index 4e2825bb3b62..a72a80543606 100644 --- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java +++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java @@ -6527,7 +6527,7 @@ private void checkLiteral2(String expression, String expected) { + "from \"sales_fact_1997\"b " + "where b.\"product_id\" = a.\"product_id\")"; String expected = "SELECT \"product_name\"\n" - + "FROM \"foodmart\".\"product\"\n" + + "FROM \"foodmart\".\"product\" AS \"product\"\n" + "WHERE EXISTS (SELECT COUNT(*)\n" + "FROM \"foodmart\".\"sales_fact_1997\"\n" + "WHERE \"product_id\" = \"product\".\"product_id\")"; @@ -6540,7 +6540,7 @@ private void checkLiteral2(String expression, String expected) { + "from \"sales_fact_1997\"b " + "where b.\"product_id\" = a.\"product_id\")"; String expected = "SELECT \"product_name\"\n" - + "FROM \"foodmart\".\"product\"\n" + + "FROM \"foodmart\".\"product\" AS \"product\"\n" + "WHERE NOT EXISTS (SELECT COUNT(*)\n" + "FROM \"foodmart\".\"sales_fact_1997\"\n" + "WHERE \"product_id\" = \"product\".\"product_id\")"; @@ -6553,7 +6553,7 @@ private void checkLiteral2(String expression, String expected) { + "from \"sales_fact_1997\"b " + "where b.\"product_id\" = a.\"product_id\")"; String expected = "SELECT \"product_name\"\n" - + "FROM \"foodmart\".\"product\"\n" + + "FROM \"foodmart\".\"product\" AS \"product\"\n" + "WHERE \"product_id\" IN (SELECT \"product_id\"\n" + "FROM \"foodmart\".\"sales_fact_1997\"\n" + "WHERE \"product_id\" = \"product\".\"product_id\")"; @@ -6575,7 +6575,7 @@ private void checkLiteral2(String expression, String expected) { + "from \"sales_fact_1997\"b " + "where b.\"product_id\" = a.\"product_id\")"; String expected = "SELECT \"product_name\"\n" - + "FROM \"foodmart\".\"product\"\n" + + "FROM \"foodmart\".\"product\" AS \"product\"\n" + "WHERE \"product_id\" NOT IN (SELECT \"product_id\"\n" + "FROM \"foodmart\".\"sales_fact_1997\"\n" + "WHERE \"product_id\" = \"product\".\"product_id\")"; @@ -6593,7 +6593,7 @@ private void checkLiteral2(String expression, String expected) { + "where t2.\"product_id\" = t1.\"product_id\" " + "and t1.\"product_id\" = 2 and t2.\"product_id\" = 1)"; String expected = "SELECT \"product_name\"\n" - + "FROM \"foodmart\".\"product\"\n" + + "FROM \"foodmart\".\"product\" AS \"product\"\n" + "WHERE \"product_id\" NOT IN (SELECT \"product_id\"\n" + "FROM \"foodmart\".\"product\" AS \"product0\"\n" + "WHERE \"product_id\" = \"product\".\"product_id\" " @@ -8892,7 +8892,7 @@ private void checkLiteral2(String expression, String expected) { + "GROUP BY \"t1\".\"department_id\"\n" + "HAVING \"t1\".\"department_id\" = MIN(\"t1\".\"department_id\")) \"t4\" ON \"employee\".\"department_id\" = \"t4\".\"department_id0\""; final String expectedNoExpand = "SELECT \"department_id\"\n" - + "FROM \"foodmart\".\"employee\"\n" + + "FROM \"foodmart\".\"employee\" AS \"employee\"\n" + "WHERE \"department_id\" = (SELECT MIN(\"employee\".\"department_id\")\n" + "FROM \"foodmart\".\"department\"\n" + "WHERE 1 = 2)"; @@ -12324,4 +12324,29 @@ public Sql schema(CalciteAssert.SchemaSpec schemaSpec) { sql(query).withLibrary(SqlLibrary.HIVE).withHive().ok(expectedHive); sql(query).withLibrary(SqlLibrary.SPARK).withSpark().ok(expectedSpark); } + + /** Test case for + * [CALCITE-7505] + * RelToSqlConverter produces duplicate FROM aliases for correlated subqueries. */ + @Test void testExistsSubQueryAliasConflict() { + final String sql = + "select deptno, sum(sal) as total\n" + + "from emp t\n" + + "where exists (\n" + + " select * from dept t0\n" + + " where deptno = t.deptno\n" + + ")\n" + + "group by deptno"; + + + sql(sql) + .schema(CalciteAssert.SchemaSpec.JDBC_SCOTT) + .ok( + "SELECT \"DEPTNO\", SUM(\"SAL\") AS \"TOTAL\"\n" + + "FROM \"SCOTT\".\"EMP\" AS \"EMP\"\n" + + "WHERE EXISTS (SELECT *\n" + + "FROM \"SCOTT\".\"DEPT\"\n" + + "WHERE \"DEPTNO\" = \"EMP\".\"DEPTNO\")\n" + + "GROUP BY \"DEPTNO\""); + } } From 675a4ce287e4712fea8589cc3f2e6e6f3c9caf9f Mon Sep 17 00:00:00 2001 From: krooswu Date: Sun, 28 Jun 2026 20:54:32 +0800 Subject: [PATCH 2/5] [CALCITE-7505] RelToSqlConverter fails to alias outer relation for correlated sub-queries in Filter --- .../org/apache/calcite/rel/rel2sql/RelToSqlConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java index fc33b4fef5c3..e69affeb1b7f 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java @@ -553,8 +553,8 @@ public Result visit(Correlate e) { } /** - * Returns whether any expression (including nested sub-queries) references a correlated variable - * defined by the given input and having the same row type. + * Returns whether any expression (including nested sub-queries) + * references a correlated variable whose row type matches the given input. */ private static boolean inputRowTypeMatchesCorrelVariable( RelNode input, From 88011400e5f018c4a1707a839ed6b76e8b3e0298 Mon Sep 17 00:00:00 2001 From: krooswu Date: Sun, 28 Jun 2026 21:22:32 +0800 Subject: [PATCH 3/5] [CALCITE-7505] RelToSqlConverter fails to alias outer relation for correlated sub-queries in Filter --- .../java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java index e69affeb1b7f..de4d5ffa282f 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java @@ -21,7 +21,6 @@ import org.apache.calcite.linq4j.tree.Expressions; import org.apache.calcite.plan.RelOptSamplingParameters; import org.apache.calcite.plan.RelOptTable; -import org.apache.calcite.rel.BiRel; import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelCollations; import org.apache.calcite.rel.RelFieldCollation; @@ -658,7 +657,7 @@ public Result visit(Filter e) { } else { Result x = visitInput(e, 0, Clause.WHERE); final boolean pushed = !definedHere.isEmpty() - && !(e.getInput() instanceof BiRel) + && !(e.getInput() instanceof Join) && !(e.getInput() instanceof Correlate) && inputRowTypeMatchesCorrelVariable( e.getInput(), definedHere, ImmutableList.of(e.getCondition())); if (pushed) { From 419bab8258a0b3e7f28ae93787ea4c80323cb687 Mon Sep 17 00:00:00 2001 From: krooswu Date: Mon, 29 Jun 2026 07:44:47 +0800 Subject: [PATCH 4/5] [CALCITE-7505] RelToSqlConverter fails to alias outer relation for correlated sub-queries in Filter --- .../org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java index a72a80543606..52a45703fe6c 100644 --- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java +++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java @@ -12327,7 +12327,7 @@ public Sql schema(CalciteAssert.SchemaSpec schemaSpec) { /** Test case for * [CALCITE-7505] - * RelToSqlConverter produces duplicate FROM aliases for correlated subqueries. */ + * RelToSqlConverter fails to alias outer relation for correlated sub-queries in Filter. */ @Test void testExistsSubQueryAliasConflict() { final String sql = "select deptno, sum(sal) as total\n" From df14a2837fce9ab906fd63140d4821e46bbb034c Mon Sep 17 00:00:00 2001 From: krooswu Date: Sun, 12 Jul 2026 12:08:47 +0800 Subject: [PATCH 5/5] [CALCITE-7505] RelToSqlConverter fails to alias outer relation for correlated sub-queries in Filter --- .../rel/rel2sql/RelToSqlConverter.java | 113 +++--------------- 1 file changed, 18 insertions(+), 95 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java index de4d5ffa282f..586680778ada 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java @@ -54,15 +54,11 @@ import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexCall; -import org.apache.calcite.rex.RexCorrelVariable; import org.apache.calcite.rex.RexInputRef; import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.rex.RexLocalRef; import org.apache.calcite.rex.RexNode; import org.apache.calcite.rex.RexProgram; -import org.apache.calcite.rex.RexShuttle; -import org.apache.calcite.rex.RexSubQuery; -import org.apache.calcite.rex.RexVisitorImpl; import org.apache.calcite.sql.JoinConditionType; import org.apache.calcite.sql.JoinType; import org.apache.calcite.sql.SqlAsofJoin; @@ -550,93 +546,6 @@ public Result visit(Correlate e) { null); return result(join, leftResult, rightResult); } - - /** - * Returns whether any expression (including nested sub-queries) - * references a correlated variable whose row type matches the given input. - */ - private static boolean inputRowTypeMatchesCorrelVariable( - RelNode input, - Set correlIds, - Iterable exprs) { - - if (correlIds.isEmpty()) { - return false; - } - - final RelDataType inputRowType = input.getRowType(); - - /** Visitor that detects matching correlated variables. */ - class Finder extends RexVisitorImpl { - boolean found; - - Finder() { - super(true); - } - - @Override public Void visitCorrelVariable(RexCorrelVariable v) { - if (correlIds.contains(v.id) - && v.getType().equals(inputRowType)) { - found = true; - } - return null; - } - - @Override public Void visitSubQuery(RexSubQuery subQuery) { - if (!found && containsInRelTree(subQuery.rel)) { - found = true; - } - return found ? null : super.visitSubQuery(subQuery); - } - - private boolean containsInRelTree(RelNode rel) { - if (found) { - return true; - } - - rel.accept(new RexShuttle() { - @Override public RexNode visitCorrelVariable( - RexCorrelVariable v) { - if (correlIds.contains(v.id) - && v.getType().equals(inputRowType)) { - found = true; - } - return v; - } - - @Override public RexNode visitSubQuery( - RexSubQuery subQuery) { - if (!found) { - containsInRelTree(subQuery.rel); - } - return subQuery; - } - }); - - if (found) { - return true; - } - - for (RelNode child : rel.getInputs()) { - if (containsInRelTree(child)) { - return true; - } - } - - return false; - } - } - - final Finder finder = new Finder(); - - for (RexNode expr : exprs) { - expr.accept(finder); - if (finder.found) { - return true; - } - } - return false; - } /** Visits a Filter; called by {@link #dispatch} via reflection. */ public Result visit(Filter e) { final RelNode input = e.getInput(); @@ -645,10 +554,26 @@ public Result visit(Filter e) { if (input instanceof Aggregate) { final Aggregate aggregate = (Aggregate) input; final boolean ignoreClauses = aggregate.getInput() instanceof Project; - final Result x = + Result x = visitInput(e, 0, isAnon(), ignoreClauses, ImmutableSet.of(Clause.HAVING)); + final boolean pushed = !definedHere.isEmpty() + && e.getInput().getInputs().size() <= 1; + if (pushed) { + String alias = x.neededAlias; + if (alias != null) { + x = x.resetAliasForCorrelation(alias, e.getInput().getRowType()); + } else { + alias = unqualifiedName(x.node); + if (alias == null) { + alias = "t"; + } + x = x.resetAliasForCorrelation + (alias, e.getInput().getRowType()); + } + } parseCorrelTable(e, x); + final Builder builder = x.builder(e); x.asSelect().setHaving( SqlUtil.andExpressions(x.asSelect().getHaving(), @@ -657,9 +582,7 @@ public Result visit(Filter e) { } else { Result x = visitInput(e, 0, Clause.WHERE); final boolean pushed = !definedHere.isEmpty() - && !(e.getInput() instanceof Join) && !(e.getInput() instanceof Correlate) - && inputRowTypeMatchesCorrelVariable( - e.getInput(), definedHere, ImmutableList.of(e.getCondition())); + && e.getInput().getInputs().size() <= 1; if (pushed) { String alias = x.neededAlias; if (alias != null) {