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) {

Reply via email to