Skip to content

Commit 556d0d8

Browse files
committed
[CALCITE-7440] Preserve correlated scope mapping in RelToSqlConverter
1 parent c628e68 commit 556d0d8

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,14 @@
5454
import org.apache.calcite.rel.type.RelDataTypeField;
5555
import org.apache.calcite.rex.RexBuilder;
5656
import org.apache.calcite.rex.RexCall;
57+
import org.apache.calcite.rex.RexCorrelVariable;
58+
import org.apache.calcite.rex.RexFieldAccess;
5759
import org.apache.calcite.rex.RexInputRef;
5860
import org.apache.calcite.rex.RexLiteral;
5961
import org.apache.calcite.rex.RexLocalRef;
6062
import org.apache.calcite.rex.RexNode;
6163
import org.apache.calcite.rex.RexProgram;
64+
import org.apache.calcite.rex.RexSubQuery;
6265
import org.apache.calcite.sql.JoinConditionType;
6366
import org.apache.calcite.sql.JoinType;
6467
import org.apache.calcite.sql.SqlAsofJoin;
@@ -557,6 +560,7 @@ public Result visit(Filter e) {
557560
visitInput(e, 0, isAnon(), ignoreClauses,
558561
ImmutableSet.of(Clause.HAVING));
559562
parseCorrelTable(e, x);
563+
parseCorrelVariables(e.getCondition());
560564
final Builder builder = x.builder(e);
561565
x.asSelect().setHaving(
562566
SqlUtil.andExpressions(x.asSelect().getHaving(),
@@ -569,6 +573,7 @@ public Result visit(Filter e) {
569573
}
570574
parseCorrelTable(e, x);
571575
final Builder builder = x.builder(e);
576+
parseCorrelVariables(e.getCondition());
572577
if (input instanceof Join) {
573578
final Context context = x.qualifiedContext();
574579
if (selectListRequired(context)) {
@@ -604,6 +609,31 @@ private static boolean selectListRequired(Context context) {
604609
return false;
605610
}
606611

612+
private void parseCorrelVariables(RexNode rexNode) {
613+
if (rexNode instanceof RexCorrelVariable) {
614+
final RexCorrelVariable correl = (RexCorrelVariable) rexNode;
615+
correlTableMap.putIfAbsent(
616+
correl.id,
617+
aliasContext(ImmutableMap.of(correl.id.getName(), correl.getType()), true));
618+
return;
619+
}
620+
if (rexNode instanceof RexSubQuery) {
621+
for (RexNode operand : ((RexSubQuery) rexNode).operands) {
622+
parseCorrelVariables(operand);
623+
}
624+
return;
625+
}
626+
if (rexNode instanceof RexFieldAccess) {
627+
parseCorrelVariables(((RexFieldAccess) rexNode).getReferenceExpr());
628+
return;
629+
}
630+
if (rexNode instanceof RexCall) {
631+
for (RexNode operand : ((RexCall) rexNode).operands) {
632+
parseCorrelVariables(operand);
633+
}
634+
}
635+
}
636+
607637
/**
608638
* Extracts the table name from a SqlNode if it represents a simple table reference.
609639
* Returns null for complex nodes like subqueries or joins.
@@ -772,6 +802,7 @@ private Builder visitAggregate(Aggregate e, List<Integer> groupKeyList,
772802
// "select a, b, sum(x) from ( ... ) group by a, b"
773803
final boolean ignoreClauses = e.getInput() instanceof Project;
774804
final Result x = visitInput(e, 0, isAnon(), ignoreClauses, clauseSet);
805+
parseCorrelTable(e, x);
775806
final Builder builder = x.builder(e);
776807
final List<SqlNode> selectList = new ArrayList<>();
777808
final List<SqlNode> groupByList =

core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11887,6 +11887,49 @@ public Sql schema(CalciteAssert.SchemaSpec schemaSpec) {
1188711887
sql(sql).schema(CalciteAssert.SchemaSpec.JDBC_SCOTT).ok(expected);
1188811888
}
1188911889

11890+
/** Test case for
11891+
* <a href="https://issues.apache.org/jira/browse/CALCITE-7440">[CALCITE-7440]
11892+
* RelToSqlConverter throws NPE when correlation scope is missing after
11893+
* semi-join rewrites.</a>. */
11894+
@Test void testPostgresqlRoundTripCorrelatedProjectWithSemiJoinRules() {
11895+
final String query = "WITH product_keys AS (\n"
11896+
+ " SELECT p.\"product_id\",\n"
11897+
+ " (SELECT MAX(p3.\"product_id\")\n"
11898+
+ " FROM \"foodmart\".\"product\" p3\n"
11899+
+ " WHERE p3.\"product_id\" = p.\"product_id\") AS \"mx\"\n"
11900+
+ " FROM \"foodmart\".\"product\" p\n"
11901+
+ ")\n"
11902+
+ "SELECT DISTINCT pk.\"product_id\"\n"
11903+
+ "FROM product_keys pk\n"
11904+
+ "LEFT JOIN \"foodmart\".\"product\" p2 USING (\"product_id\")\n"
11905+
+ "WHERE pk.\"product_id\" IN (\n"
11906+
+ " SELECT p4.\"product_id\"\n"
11907+
+ " FROM \"foodmart\".\"product\" p4\n"
11908+
+ ")";
11909+
11910+
final RuleSet rules =
11911+
RuleSets.ofList(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE,
11912+
CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE,
11913+
CoreRules.JOIN_SUB_QUERY_TO_CORRELATE,
11914+
CoreRules.FILTER_SUB_QUERY_TO_MARK_CORRELATE,
11915+
CoreRules.PROJECT_SUB_QUERY_TO_MARK_CORRELATE,
11916+
CoreRules.MARK_TO_SEMI_OR_ANTI_JOIN_RULE,
11917+
CoreRules.PROJECT_TO_SEMI_JOIN,
11918+
CoreRules.JOIN_TO_SEMI_JOIN,
11919+
CoreRules.SEMI_JOIN_FILTER_TRANSPOSE,
11920+
CoreRules.SEMI_JOIN_JOIN_TRANSPOSE);
11921+
11922+
final String generated = sql(query).withPostgresql().optimize(rules, null).exec();
11923+
try {
11924+
sql(generated).withPostgresql().exec();
11925+
} catch (Exception e) {
11926+
throw new AssertionError(
11927+
"Generated SQL failed PostgreSQL round-trip validation:\n"
11928+
+ generated,
11929+
e);
11930+
}
11931+
}
11932+
1189011933
@Test void testNotBetween() {
1189111934
Sql f = fixture().withConvertletTable(new SqlRexConvertletTable() {
1189211935
@Override public @Nullable SqlRexConvertlet get(SqlCall call) {

0 commit comments

Comments
 (0)