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();