From 1b9c81c71d967e633a7e03cb8aa17b7428aa561e Mon Sep 17 00:00:00 2001 From: Bruno Cunha Date: Fri, 13 Mar 2026 08:10:15 -0400 Subject: [PATCH 1/2] [CALCITE-7440] Preserve correlated scope mapping in rel2sql --- .../rel/rel2sql/RelToSqlConverter.java | 1 + .../calcite/rel/rel2sql/SqlImplementor.java | 14 ++++++-- .../rel/rel2sql/RelToSqlConverterTest.java | 33 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 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 0404fa7bc51..d6e46a07e41 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 @@ -772,6 +772,7 @@ private Builder visitAggregate(Aggregate e, List groupKeyList, // "select a, b, sum(x) from ( ... ) group by a, b" final boolean ignoreClauses = e.getInput() instanceof Project; final Result x = visitInput(e, 0, isAnon(), ignoreClauses, clauseSet); + parseCorrelTable(e, x); final Builder builder = x.builder(e); final List selectList = new ArrayList<>(); final List groupByList = 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 dec24ca1f24..2dcc6c5ad45 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 @@ -1587,9 +1587,17 @@ public static SqlNode toSql(RexLiteral literal) { } protected Context getAliasContext(RexCorrelVariable variable) { - return requireNonNull( - correlTableMap.get(variable.id), - () -> "variable " + variable.id + " is not found"); + Context context = correlTableMap.get(variable.id); + if (context == null) { + if (correlTableMap.isEmpty()) { + context = + aliasContext(ImmutableMap.of(variable.id.getName(), variable.getType()), true); + } + if (context != null) { + correlTableMap.put(variable.id, context); + } + } + return requireNonNull(context, () -> "variable " + variable.id + " is not found"); } /** Simple implementation of {@link Context} that cannot handle sub-queries 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 41054312202..9ca14a6fd17 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 @@ -11887,6 +11887,39 @@ public Sql schema(CalciteAssert.SchemaSpec schemaSpec) { sql(sql).schema(CalciteAssert.SchemaSpec.JDBC_SCOTT).ok(expected); } + /** Test case for + * [CALCITE-7440] + * RelToSqlConverter throws NPE when correlation scope is missing after + * semi-join rewrites.. */ + @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(); + + final String generated = sql(query).withPostgresql().optimize(rules, null).exec(); + try { + sql(generated).withPostgresql().exec(); + } catch (Exception e) { + throw new AssertionError( + "Generated SQL failed PostgreSQL round-trip validation:\n" + + generated, + e); + } + } + @Test void testNotBetween() { Sql f = fixture().withConvertletTable(new SqlRexConvertletTable() { @Override public @Nullable SqlRexConvertlet get(SqlCall call) { From a685206c8e3313a1170af7268a26c803dbd4128f Mon Sep 17 00:00:00 2001 From: Bruno Cunha Date: Fri, 13 Mar 2026 08:10:17 -0400 Subject: [PATCH 2/2] [CALCITE-7440] Add correlated-project regression coverage --- .../rel/rel2sql/RelToSqlConverterTest.java | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) 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 9ca14a6fd17..417a73776fe 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 @@ -11891,8 +11891,42 @@ public Sql schema(CalciteAssert.SchemaSpec schemaSpec) { * [CALCITE-7440] * RelToSqlConverter throws NPE when correlation scope is missing after * semi-join rewrites.. */ + @Test void testPostgresqlRoundTripCorrelatedProject() { + final String query = correlatedProjectQuery7440(); + final RuleSet rules = RuleSets.ofList(); + final String generated = sql(query).withPostgresql().optimize(rules, null).exec(); + try { + sql(generated).withPostgresql().exec(); + } catch (Exception e) { + throw new AssertionError( + "Generated SQL failed PostgreSQL round-trip validation:\n" + + generated, + e); + } + } + @Test void testPostgresqlRoundTripCorrelatedProjectWithSemiJoinRules() { - final String query = "WITH product_keys AS (\n" + final String query = correlatedProjectQuery7440(); + + 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(); + try { + sql(generated).withPostgresql().exec(); + } catch (Exception e) { + throw new AssertionError( + "Generated SQL failed PostgreSQL round-trip validation:\n" + + generated, + e); + } + } + + private static String correlatedProjectQuery7440() { + return "WITH product_keys AS (\n" + " SELECT p.\"product_id\",\n" + " (SELECT MAX(p3.\"product_id\")\n" + " FROM \"foodmart\".\"product\" p3\n" @@ -11906,18 +11940,6 @@ public Sql schema(CalciteAssert.SchemaSpec schemaSpec) { + " SELECT p4.\"product_id\"\n" + " FROM \"foodmart\".\"product\" p4\n" + ")"; - - final RuleSet rules = RuleSets.ofList(); - - final String generated = sql(query).withPostgresql().optimize(rules, null).exec(); - try { - sql(generated).withPostgresql().exec(); - } catch (Exception e) { - throw new AssertionError( - "Generated SQL failed PostgreSQL round-trip validation:\n" - + generated, - e); - } } @Test void testNotBetween() {