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

rubenql 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 b0b3e3e64 [CALCITE-5177] Query loses hint after decorrelation
b0b3e3e64 is described below

commit b0b3e3e645b57ab22d7f9a31a00a3210be6e87a5
Author: rubenada <rube...@gmail.com>
AuthorDate: Tue Jun 7 11:18:45 2022 +0100

    [CALCITE-5177] Query loses hint after decorrelation
---
 .../apache/calcite/sql2rel/RelDecorrelator.java    |  8 +--
 .../apache/calcite/sql2rel/RelFieldTrimmer.java    | 41 +++++++-------
 .../sql2rel/RelStructuredTypeFlattener.java        |  4 +-
 .../apache/calcite/test/SqlHintsConverterTest.java | 42 ++++++++++++++
 .../apache/calcite/test/SqlHintsConverterTest.xml  | 66 ++++++++++++++++++++++
 5 files changed, 133 insertions(+), 28 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 7347eeb64..1786ffcaa 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -586,8 +586,6 @@ public class RelDecorrelator implements ReflectiveVisitor {
         .projectNamed(Pair.left(projects), Pair.right(projects), true)
         .build();
 
-    newProject = RelOptUtil.copyRelHints(newInput, newProject);
-
     // update mappings:
     // oldInput ----> newInput
     //
@@ -682,7 +680,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
       relBuilder.project(postProjects);
     }
 
-    RelNode newRel = RelOptUtil.copyRelHints(rel, relBuilder.build());
+    RelNode newRel = relBuilder.build();
 
     // Aggregate does not change input ordering so corVars will be
     // located at the same position as the input newProject.
@@ -793,8 +791,6 @@ public class RelDecorrelator implements ReflectiveVisitor {
         .projectNamed(Pair.left(projects), Pair.right(projects), true)
         .build();
 
-    newProject = RelOptUtil.copyRelHints(rel, newProject);
-
     return register(rel, newProject, mapOldToNewOutputs, corDefOutputs);
   }
 
@@ -1305,7 +1301,6 @@ public class RelDecorrelator implements ReflectiveVisitor 
{
         .join(rel.getJoinType(),
             decorrelateExpr(castNonNull(currentRel), map, cm, 
rel.getCondition()),
             ImmutableSet.of())
-        .hints(rel.getHints())
         .build();
 
     // Create the mapping between the output of the old correlation rel
@@ -1595,6 +1590,7 @@ public class RelDecorrelator implements ReflectiveVisitor 
{
   Frame register(RelNode rel, RelNode newRel,
       Map<Integer, Integer> oldToNewOutputs,
       NavigableMap<CorDef, Integer> corDefOutputs) {
+    newRel = RelOptUtil.copyRelHints(rel, newRel);
     final Frame frame = new Frame(rel, newRel, corDefOutputs, oldToNewOutputs);
     map.put(rel, frame);
     return frame;
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java 
b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
index 2a53e9427..3790026bf 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
@@ -265,7 +265,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     relBuilder.push(trimResult.left)
         .project(exprList, nameList);
     return result(relBuilder.build(),
-        Mappings.createIdentity(fieldList.size()));
+        Mappings.createIdentity(fieldList.size()), rel);
   }
 
   /**
@@ -297,11 +297,15 @@ public class RelFieldTrimmer implements ReflectiveVisitor 
{
       assert newFieldCount > 0 : "rel has no fields after trim: " + rel;
     }
     if (newRel.equals(rel)) {
-      return result(rel, mapping);
+      return result(rel, mapping, rel);
     }
     return trimResult;
   }
 
+  protected TrimResult result(RelNode rel, final Mapping mapping, RelNode 
oldRel) {
+    return result(RelOptUtil.copyRelHints(oldRel, rel), mapping);
+  }
+
   protected TrimResult result(RelNode r, final Mapping mapping) {
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
     for (final CorrelationId correlation : r.getVariablesSet()) {
@@ -370,7 +374,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
       newInputs.add(trimResult.left);
     }
     RelNode newRel = rel.copy(rel.getTraitSet(), newInputs);
-    return result(newRel, 
Mappings.createIdentity(newRel.getRowType().getFieldCount()));
+    return result(newRel, 
Mappings.createIdentity(newRel.getRowType().getFieldCount()), rel);
   }
 
   /**
@@ -462,7 +466,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
         newProjects, newConditionExpr, newRowType.getFieldNames(),
         newInputRelNode.getCluster().getRexBuilder());
     final Calc newCalc = calc.copy(calc.getTraitSet(), newInputRelNode, 
newRexProgram);
-    return result(newCalc, mapping);
+    return result(newCalc, mapping, calc);
   }
 
   /**
@@ -532,8 +536,8 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
 
     relBuilder.push(newInput);
     relBuilder.project(newProjects, newRowType.getFieldNames());
-    final RelNode newProject = RelOptUtil.propagateRelHints(project, 
relBuilder.build());
-    return result(newProject, mapping);
+    final RelNode newProject = relBuilder.build();
+    return result(newProject, mapping, project);
   }
 
   /** Creates a project with a dummy column, to protect the parts of the system
@@ -624,7 +628,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     // The result has the same mapping as the input gave us. Sometimes we
     // return fields that the consumer didn't ask for, because the filter
     // needs them for its condition.
-    return result(relBuilder.build(), inputMapping);
+    return result(relBuilder.build(), inputMapping, filter);
   }
 
   /**
@@ -680,7 +684,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     // The result has the same mapping as the input gave us. Sometimes we
     // return fields that the consumer didn't ask for, because the filter
     // needs them for its condition.
-    return result(relBuilder.build(), inputMapping);
+    return result(relBuilder.build(), inputMapping, sort);
   }
 
   public TrimResult trimFields(
@@ -718,7 +722,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     final RelDistribution newDistribution = distribution.apply(inputMapping);
     relBuilder.exchange(newDistribution);
 
-    return result(relBuilder.build(), inputMapping);
+    return result(relBuilder.build(), inputMapping, exchange);
   }
 
   public TrimResult trimFields(
@@ -761,7 +765,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     RelDistribution newDistribution = distribution.apply(inputMapping);
     relBuilder.sortExchange(newDistribution, newCollation);
 
-    return result(relBuilder.build(), inputMapping);
+    return result(relBuilder.build(), inputMapping, sortExchange);
   }
 
   /**
@@ -906,8 +910,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     default:
       relBuilder.join(join.getJoinType(), newConditionExpr);
     }
-    relBuilder.hints(join.getHints());
-    return result(relBuilder.build(), mapping);
+    return result(relBuilder.build(), mapping, join);
   }
 
   /**
@@ -995,7 +998,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     default:
       throw new AssertionError("unknown setOp " + setOp);
     }
-    return result(relBuilder.build(), mapping);
+    return result(relBuilder.build(), mapping, setOp);
   }
 
   /**
@@ -1112,8 +1115,8 @@ public class RelFieldTrimmer implements ReflectiveVisitor 
{
     final RelBuilder.GroupKey groupKey = relBuilder.groupKey(newGroupSet, 
newGroupSets);
     relBuilder.aggregate(groupKey, newAggCallList);
 
-    final RelNode newAggregate = RelOptUtil.propagateRelHints(aggregate, 
relBuilder.build());
-    return result(newAggregate, mapping);
+    final RelNode newAggregate = relBuilder.build();
+    return result(newAggregate, mapping, aggregate);
   }
 
   /**
@@ -1160,7 +1163,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor 
{
 
     // Always project all fields.
     Mapping mapping = Mappings.createIdentity(fieldCount);
-    return result(newModifier, mapping);
+    return result(newModifier, mapping, modifier);
   }
 
   /**
@@ -1199,7 +1202,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor 
{
 
     // Always project all fields.
     Mapping mapping = Mappings.createIdentity(fieldCount);
-    return result(newTabFun, mapping);
+    return result(newTabFun, mapping, tabFun);
   }
 
   /**
@@ -1243,7 +1246,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor 
{
     final LogicalValues newValues =
         LogicalValues.create(values.getCluster(), newRowType,
             newTuples.build());
-    return result(newValues, mapping);
+    return result(newValues, mapping, values);
   }
 
   protected Mapping createMapping(ImmutableBitSet fieldsUsed, int fieldCount) {
@@ -1295,7 +1298,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor 
{
     }
 
     final Mapping mapping = createMapping(fieldsUsed, fieldCount);
-    return result(newTableAccessRel, mapping);
+    return result(newTableAccessRel, mapping, tableAccessRel);
   }
 
   //~ Inner Classes ----------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java 
b/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
index f4bf28aa2..0bb228af3 100644
--- 
a/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
+++ 
b/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
@@ -272,6 +272,7 @@ public class RelStructuredTypeFlattener implements 
ReflectiveVisitor {
   }
 
   protected void setNewForOldRel(RelNode oldRel, RelNode newRel) {
+    newRel = RelOptUtil.copyRelHints(oldRel, newRel);
     oldToNewRelMap.put(oldRel, newRel);
   }
 
@@ -757,8 +758,6 @@ public class RelStructuredTypeFlattener implements 
ReflectiveVisitor {
     RelNode newRel = rel.getTable().toRel(toRelContext);
     if (!SqlTypeUtil.isFlat(rel.getRowType())) {
       newRel = coverNewRelByFlatteningProjection(rel, newRel);
-    } else {
-      newRel = RelOptUtil.copyRelHints(rel, newRel);
     }
     setNewForOldRel(rel, newRel);
   }
@@ -774,7 +773,6 @@ public class RelStructuredTypeFlattener implements 
ReflectiveVisitor {
     newRel = relBuilder.push(newRel)
         .projectNamed(projects, fieldNames, true)
         .build();
-    newRel = RelOptUtil.copyRelHints(rel, newRel);
     return newRel;
   }
 
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
index 0f3154a0e..da4ed9caa 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
@@ -495,6 +495,48 @@ class SqlHintsConverterTest {
         .check();
   }
 
+  @Test void testHintsPropagationInVolcanoPlannerRules2() {
+    final String sql = "select /*+ no_hash_join */ ename, job\n"
+        + "from emp where not exists (select 1 from dept where emp.deptno = 
dept.deptno)";
+    final RelHint hint = RelHint.builder("NO_HASH_JOIN")
+        .inheritPath(0, 0)
+        .build();
+    // Validate Volcano planner.
+    RuleSet ruleSet = RuleSets.ofList(
+        MockEnumerableJoinRule.create(hint) // Rule to validate the hint.
+    );
+    ruleFixture()
+        .sql(sql)
+        .withTrim(true)
+        .withVolcanoPlanner(false, p -> {
+          p.addRelTraitDef(RelCollationTraitDef.INSTANCE);
+          RelOptUtil.registerDefaultRules(p, false, false);
+          ruleSet.forEach(p::addRule);
+        })
+        .check();
+  }
+
+  @Test void testHintsPropagationInVolcanoPlannerRules3() {
+    final String sql = "select /*+ no_hash_join */ ename, job\n"
+        + "from emp where not exists (select 1 from dept where emp.deptno = 
dept.deptno) order by ename";
+    final RelHint hint = RelHint.builder("NO_HASH_JOIN")
+        .inheritPath(0, 0, 0)
+        .build();
+    // Validate Volcano planner.
+    RuleSet ruleSet = RuleSets.ofList(
+        MockEnumerableJoinRule.create(hint) // Rule to validate the hint.
+    );
+    ruleFixture()
+        .sql(sql)
+        .withTrim(true)
+        .withVolcanoPlanner(false, p -> {
+          p.addRelTraitDef(RelCollationTraitDef.INSTANCE);
+          RelOptUtil.registerDefaultRules(p, false, false);
+          ruleSet.forEach(p::addRule);
+        })
+        .check();
+  }
+
   @Test void testHintsPropagateWithDifferentKindOfRels() {
     final String sql = "select /*+ AGG_STRATEGY(TWO_PHASE) */\n"
         + "ename, avg(sal)\n"
diff --git 
a/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml 
b/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
index 052a6b72a..c85524313 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
@@ -195,6 +195,72 @@ EnumerableProject(ENAME=[$3], JOB=[$4], SAL=[$7], 
NAME=[$1])
   EnumerableHashJoin(condition=[=($0, $9)], joinType=[inner])
     EnumerableTableScan(table=[[CATALOG, SALES, DEPT]])
     EnumerableTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testHintsPropagationInVolcanoPlannerRules2">
+    <Resource name="sql">
+      <![CDATA[select /*+ my_hint */ ename, job
+from emp where not exists (select 1 from dept where emp.deptno = dept.deptno)
+]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(ENAME=[$0], JOB=[$1])
+  LogicalFilter(condition=[IS NULL($4)])
+    LogicalJoin(condition=[=($2, $3)], joinType=[left])
+      LogicalProject(ENAME=[$1], JOB=[$2], DEPTNO=[$7])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalAggregate(group=[{0}], agg#0=[MIN($1)])
+        LogicalProject(DEPTNO=[$0], $f0=[true])
+          LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+EnumerableProject(ENAME=[$0], JOB=[$1])
+  EnumerableFilter(condition=[IS NULL($4)])
+    EnumerableMergeJoin(condition=[=($2, $3)], joinType=[left])
+      EnumerableSort(sort0=[$2], dir0=[ASC])
+        EnumerableProject(ENAME=[$1], JOB=[$2], DEPTNO=[$7])
+          EnumerableTableScan(table=[[CATALOG, SALES, EMP]])
+      EnumerableSort(sort0=[$0], dir0=[ASC])
+        EnumerableProject(DEPTNO=[$0], $f0=[true])
+          EnumerableTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testHintsPropagationInVolcanoPlannerRules3">
+    <Resource name="sql">
+      <![CDATA[select /*+ my_hint */ ename, job
+from emp where not exists (select 1 from dept where emp.deptno = dept.deptno) 
order by ename
+]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalSort(sort0=[$0], dir0=[ASC])
+  LogicalProject(ENAME=[$0], JOB=[$1])
+    LogicalFilter(condition=[IS NULL($4)])
+      LogicalJoin(condition=[=($2, $3)], joinType=[left])
+        LogicalProject(ENAME=[$1], JOB=[$2], DEPTNO=[$7])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+        LogicalAggregate(group=[{0}], agg#0=[MIN($1)])
+          LogicalProject(DEPTNO=[$0], $f0=[true])
+            LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+EnumerableSort(sort0=[$0], dir0=[ASC])
+  EnumerableProject(ENAME=[$0], JOB=[$1])
+    EnumerableFilter(condition=[IS NULL($4)])
+      EnumerableMergeJoin(condition=[=($2, $3)], joinType=[left])
+        EnumerableSort(sort0=[$2], dir0=[ASC])
+          EnumerableProject(ENAME=[$1], JOB=[$2], DEPTNO=[$7])
+            EnumerableTableScan(table=[[CATALOG, SALES, EMP]])
+        EnumerableSort(sort0=[$0], dir0=[ASC])
+          EnumerableProject(DEPTNO=[$0], $f0=[true])
+            EnumerableTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
     </Resource>
   </TestCase>

Reply via email to