This is an automated email from the ASF dual-hosted git repository.
snuyanzin 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 25d62c555a [CALCITE-7753] `CorrelateProjectExtractor` corrupts plans
with nested Correlates that reuse the same correlation id
25d62c555a is described below
commit 25d62c555af1814d0789f85ff5fc6b4489d52781
Author: Sergey Nuyanzin <[email protected]>
AuthorDate: Sat Aug 29 22:50:57 2026 +0200
[CALCITE-7753] `CorrelateProjectExtractor` corrupts plans with nested
Correlates that reuse the same correlation id
---
.../calcite/sql2rel/CorrelateProjectExtractor.java | 6 ++++
.../calcite/sql2rel/RelDecorrelatorTest.java | 39 ++++++++++++++++++++++
2 files changed, 45 insertions(+)
diff --git
a/core/src/main/java/org/apache/calcite/sql2rel/CorrelateProjectExtractor.java
b/core/src/main/java/org/apache/calcite/sql2rel/CorrelateProjectExtractor.java
index 127f4e4879..a0c4747d2e 100644
---
a/core/src/main/java/org/apache/calcite/sql2rel/CorrelateProjectExtractor.java
+++
b/core/src/main/java/org/apache/calcite/sql2rel/CorrelateProjectExtractor.java
@@ -18,6 +18,7 @@
import org.apache.calcite.rel.RelHomogeneousShuttle;
import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Correlate;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.Project;
@@ -208,6 +209,11 @@ private static Set<RexNode>
findCorrelationDependentCalls(CorrelationId corrId,
SimpleCorrelationCollector finder = new SimpleCorrelationCollector(corrId);
plan.accept(new RelHomogeneousShuttle() {
@Override public RelNode visit(RelNode other) {
+ // Don't descend into a nested correlate that reuses this id: the refs
below belong to it.
+ if (other instanceof Correlate
+ && ((Correlate) other).getCorrelationId().equals(corrId)) {
+ return other;
+ }
if (other instanceof Project || other instanceof Filter) {
other.accept(finder);
}
diff --git
a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
index 80ef7f588a..2757a7a49b 100644
--- a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
@@ -2548,4 +2548,43 @@ private RelNode decorrelateSql(String sql) {
+ " LogicalTableScan(table=[[bookstore, authors]])\n";
assertThat(after, hasTree(planAfter));
}
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7753">[CALCITE-7753]
+ * CorrelateProjectExtractor corrupts plans with nested Correlates that reuse
+ * the same correlation id</a>. */
+ @Test void testNestedCorrelatesSharingCorrelationId() {
+ final RelBuilder builder = RelBuilder.create(config().build())
+ .transform(c -> c.withSimplify(false));
+ final Holder<@Nullable RexCorrelVariable> v = Holder.empty();
+ builder.scan("EMP").variable(v::set);
+ final RelNode emp = builder.build();
+
+ final RelNode inner = builder
+ .scan("EMP")
+ .scan("EMP")
+ .filter(builder.equals(builder.field(v.get(), "DEPTNO"),
builder.field("DEPTNO")))
+ .correlate(JoinRelType.INNER, v.get().id, builder.field(2, 0,
"DEPTNO"))
+ .filter(builder.isNull(builder.field(v.get(), "COMM")))
+ .build();
+
+ final RelNode before = builder
+ .push(emp)
+ .push(inner)
+ .correlate(JoinRelType.LEFT, v.get().id, builder.field(2, 0, "COMM"))
+ .build();
+ final String planBefore = ""
+ + "LogicalCorrelate(correlation=[$cor0], joinType=[left],
requiredColumns=[{6}])\n"
+ + " LogicalTableScan(table=[[scott, EMP]])\n"
+ + " LogicalFilter(condition=[IS NULL($cor0.COMM)])\n"
+ + " LogicalCorrelate(correlation=[$cor0], joinType=[inner],
requiredColumns=[{7}])\n"
+ + " LogicalTableScan(table=[[scott, EMP]])\n"
+ + " LogicalFilter(condition=[=($cor0.DEPTNO, $7)])\n"
+ + " LogicalTableScan(table=[[scott, EMP]])\n";
+ assertThat(before, hasTree(planBefore));
+
+ RelDecorrelator.decorrelateQuery(before, builder,
+ RuleSets.ofList(Collections.emptyList()),
+ RuleSets.ofList(Collections.emptyList()));
+ }
}