This is an automated email from the ASF dual-hosted git repository.
mihaibudiu 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 7d2e5e0490 [CALCITE-7440] RelToSqlConverter throws NPE (variable $cor1
not found) for correlated projection after semi-join rewrites
7d2e5e0490 is described below
commit 7d2e5e04903ff7fefd94cc8ebdbf3d697db6b9e2
Author: bvolpato <[email protected]>
AuthorDate: Wed Aug 5 02:45:03 2026 -0400
[CALCITE-7440] RelToSqlConverter throws NPE (variable $cor1 not found) for
correlated projection after semi-join rewrites
---
.../apache/calcite/rel/rel2sql/SqlImplementor.java | 8 ++++++
.../calcite/rel/rel2sql/RelToSqlConverterTest.java | 30 ++++++++++++++++++++++
2 files changed, 38 insertions(+)
diff --git
a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
index c2804db09e..18177ecae3 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
@@ -867,6 +867,7 @@ public SqlNode toSql(@Nullable RexProgram program, RexNode
rex) {
case ALL:
if (rex instanceof RexSubQuery) {
subQuery = (RexSubQuery) rex;
+ registerSubQueryCorrelations(subQuery);
sqlSubQuery =
implementor().visitRoot(subQuery.rel).asQueryOrValues();
final List<RexNode> operands = subQuery.operands;
SqlNode op0;
@@ -894,6 +895,7 @@ public SqlNode toSql(@Nullable RexProgram program, RexNode
rex) {
case UNIQUE:
case SCALAR_QUERY:
subQuery = (RexSubQuery) rex;
+ registerSubQueryCorrelations(subQuery);
sqlSubQuery =
implementor().visitRoot(subQuery.rel).asQueryOrValues();
return subQuery.getOperator().createCall(POS, sqlSubQuery);
@@ -939,6 +941,12 @@ public SqlNode toSql(@Nullable RexProgram program, RexNode
rex) {
}
}
+ private void registerSubQueryCorrelations(RexSubQuery subQuery) {
+ for (CorrelationId id : RelOptUtil.getVariablesUsed(subQuery.rel)) {
+ implementor().correlTableMap.putIfAbsent(id, this);
+ }
+ }
+
private SqlNode callToSql(@Nullable RexProgram program, RexCall call0,
boolean not) {
final RexCall call1 = reverseCall(call0);
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 ad43cd0591..ce06127a9f 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
@@ -12711,6 +12711,36 @@ public Sql schema(CalciteAssert.SchemaSpec schemaSpec)
{
sql(sql).schema(CalciteAssert.SchemaSpec.JDBC_SCOTT).ok(expected);
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7440">[CALCITE-7440]
+ * RelToSqlConverter throws NPE (variable $cor1 not found) for correlated
+ * projection after semi-join rewrites.</a>. */
+ @Test void testPostgresqlRoundTripCorrelatedProjectWithSemiJoinRules() {
+ final String query = "WITH product_keys AS (\n"
+ + " SELECT p.\"product_id\",\n"
+ + " (SELECT MAX(p3.\"product_id\")\n"
+ + " FROM \"foodmart\".\"product\" p3\n"
+ + " WHERE p3.\"product_id\" = p.\"product_id\") AS \"mx\"\n"
+ + " FROM \"foodmart\".\"product\" p\n"
+ + ")\n"
+ + "SELECT DISTINCT pk.\"product_id\"\n"
+ + "FROM product_keys pk\n"
+ + "LEFT JOIN \"foodmart\".\"product\" p2 USING (\"product_id\")\n"
+ + "WHERE pk.\"product_id\" IN (\n"
+ + " SELECT p4.\"product_id\"\n"
+ + " FROM \"foodmart\".\"product\" p4\n"
+ + ")";
+
+ final RuleSet rules =
+ RuleSets.ofList(CoreRules.FILTER_SUB_QUERY_TO_MARK_CORRELATE,
+ CoreRules.PROJECT_SUB_QUERY_TO_MARK_CORRELATE,
+ CoreRules.MARK_TO_SEMI_OR_ANTI_JOIN_RULE,
+ CoreRules.SEMI_JOIN_JOIN_TRANSPOSE);
+
+ final String generated = sql(query).withPostgresql().optimize(rules,
null).exec();
+ sql(generated).withPostgresql().exec();
+ }
+
@Test void testNotBetween() {
Sql f = fixture().withConvertletTable(new SqlRexConvertletTable() {
@Override public @Nullable SqlRexConvertlet get(SqlCall call) {