This is an automated email from the ASF dual-hosted git repository.

suibianwanwan 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 af502b523c [CALCITE-7024] Decorrelator does not always produce a query 
with the same type signature
af502b523c is described below

commit af502b523cfac706009e3a8aaa125c083fe14480
Author: Mihai Budiu <[email protected]>
AuthorDate: Thu May 22 15:13:52 2025 -0700

    [CALCITE-7024] Decorrelator does not always produce a query with the same 
type signature
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../apache/calcite/sql2rel/RelDecorrelator.java    | 29 +++++++++--
 .../calcite/sql2rel/RelDecorrelatorTest.java       | 56 ++++++++++++++++++++++
 2 files changed, 82 insertions(+), 3 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java 
b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
index e2808718a6..ae8b439ea7 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -253,10 +253,12 @@ public static RelNode decorrelateQuery(RelNode rootRel,
     if (!decorrelator.cm.mapCorToCorRel.isEmpty()) {
       newRootRel = decorrelator.decorrelate(newRootRel);
     }
+    Litmus.THROW.check(rootRel.getRowType().equals(newRootRel.getRowType()),
+        "Decorrelation produced a relation with a different type; before: "
+            + rootRel.getRowType() + " after: " + newRootRel.getRowType());
 
     // Re-propagate the hints.
     newRootRel = RelOptUtil.propagateRelHints(newRootRel, true);
-
     return newRootRel;
   }
 
@@ -331,6 +333,25 @@ protected RelNode decorrelate(RelNode root) {
 
     final Frame frame = getInvoke(root, false, null);
     if (frame != null) {
+      // Check if the frame has more fields than the original and discard the 
extra ones
+      RelNode result = frame.r;
+      int fields = frame.r.getRowType().getFieldCount();
+      if (fields > frame.oldToNewOutputs.size()) {
+        relBuilder.push(result);
+        final List<RexNode> exprList = new ArrayList<>();
+        List<Map.Entry<Integer, Integer>> entries =
+            new ArrayList<>(frame.oldToNewOutputs.entrySet());
+        entries.sort(Map.Entry.comparingByKey());
+        for (Map.Entry<Integer, Integer> entry : entries) {
+          exprList.add(relBuilder.field(entry.getValue()));
+        }
+        relBuilder.project(exprList);
+        result = relBuilder.build();
+      } else {
+        Litmus.THROW.check(fields == frame.oldToNewOutputs.size(),
+            "Produced relation has fewer columns than the original relation");
+      }
+
       // has been rewritten; apply rules post-decorrelation
       final HepProgramBuilder builder = HepProgram.builder()
           .addRuleInstance(
@@ -347,7 +368,7 @@ protected RelNode decorrelate(RelNode root) {
       final HepProgram program2 = builder.build();
 
       final HepPlanner planner2 = createPlanner(program2);
-      final RelNode newRoot = frame.r;
+      final RelNode newRoot = result;
       planner2.setRoot(newRoot);
       return planner2.findBestExp();
     }
@@ -618,7 +639,7 @@ protected RexNode removeCorrelationExpr(
       }
 
       // add mapping of group keys.
-      outputMap.put(idx, newPos);
+      outputMap.put(i, newPos);
       int newInputPos = requireNonNull(frame.oldToNewOutputs.get(idx));
       RexInputRef.add2(projects, newInputPos, newInputOutput);
       mapNewInputToProjOutputs.put(newInputPos, newPos);
@@ -3209,6 +3230,8 @@ assert allLessThan(this.oldToNewOutputs.keySet(),
           oldRel.getRowType().getFieldCount(), Litmus.THROW);
       assert allLessThan(this.oldToNewOutputs.values(),
           r.getRowType().getFieldCount(), Litmus.THROW);
+      RelDataType rowType = oldRel.getRowType();
+      assert this.oldToNewOutputs.size() >= rowType.getFieldCount();
     }
   }
 
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 029e185995..7f2dd759fc 100644
--- a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
@@ -16,17 +16,29 @@
  */
 package org.apache.calcite.sql2rel;
 
+import org.apache.calcite.config.CalciteConnectionConfig;
+import org.apache.calcite.config.CalciteConnectionConfigImpl;
+import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitDef;
 import org.apache.calcite.plan.hep.HepProgram;
+import org.apache.calcite.prepare.CalciteCatalogReader;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelRoot;
+import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.rules.CoreRules;
 import org.apache.calcite.rex.RexCorrelVariable;
 import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.sql.SqlExplainFormat;
+import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.test.SqlTestFactory;
 import org.apache.calcite.test.CalciteAssert;
 import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.calcite.tools.Frameworks;
@@ -46,6 +58,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Properties;
 
 import static org.apache.calcite.test.Matchers.hasTree;
 
@@ -66,6 +79,49 @@ public static Frameworks.ConfigBuilder config() {
         .traitDefs((List<RelTraitDef>) null);
   }
 
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7024";>[CALCITE-7024]
+   * Decorrelator does not always produce a query with the same type 
signature</a>. */
+  @Test void testTypeEquivalence() {
+    final String sql = "SELECT dname FROM \"scott\".DEPT WHERE 2000 > "
+        + "(SELECT EMP.sal FROM \"scott\".EMP where\n"
+        + "DEPT.deptno = EMP.deptno ORDER BY year(hiredate), EMP.sal limit 1)";
+    try {
+      SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+      CalciteAssert.addSchema(rootSchema, CalciteAssert.SchemaSpec.SCOTT);
+      CalciteConnectionConfig config = new CalciteConnectionConfigImpl(new 
Properties());
+      SqlTestFactory factory = SqlTestFactory.INSTANCE
+          .withCatalogReader((typeFactory, caseSensitive) ->
+              new CalciteCatalogReader(
+                  CalciteSchema.from(rootSchema),
+                  ImmutableList.of("SCOTT"),
+                  typeFactory,
+                  config))
+          .withSqlToRelConfig(c -> c.withExpand(true));
+      SqlParser parser = factory.createParser(sql);
+      SqlNode parsed = parser.parseQuery();
+      final SqlToRelConverter sqlToRelConverter = 
factory.createSqlToRelConverter();
+      final SqlNode validated = sqlToRelConverter.validator.validate(parsed);
+      RelRoot root = sqlToRelConverter.convertQuery(validated, false, true);
+      System.out.println(
+          RelOptUtil.dumpPlan("[Logical plan]", root.rel,
+              SqlExplainFormat.TEXT, SqlExplainLevel.NON_COST_ATTRIBUTES));
+
+      // The plan starts has this shape:
+      // LogicalProject(DNAME=[$1])
+      //  LogicalFilter(condition=[>(2000.00, CAST($3):DECIMAL(12, 2))])
+      //    LogicalCorrelate(correlation=[$cor0], joinType=[left], 
requiredColumns=[{0}])
+      // we invoke decorrelate on the LogicalCorrelate node directly
+      RelNode filter = ((Project) root.rel).getInput();
+      RelNode correlate = ((Filter) filter).getInput();
+
+      final RelBuilder relBuilder =
+          RelFactories.LOGICAL_BUILDER.create(filter.getCluster(), null);
+      RelDecorrelator.decorrelateQuery(correlate, relBuilder);
+    } catch (Exception e) {
+      throw TestUtil.rethrow(e);
+    }
+  }
+
   @Test void testGroupKeyNotInFrontWhenDecorrelate() {
     final RelBuilder builder = RelBuilder.create(config().build());
     final Holder<@Nullable RexCorrelVariable> v = Holder.empty();

Reply via email to