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>