From 31b52e2673a7aba8ba19830360dbaae4475f802b Mon Sep 17 00:00:00 2001 From: "jinpeng.wjp" Date: Tue, 12 May 2020 22:09:25 +0800 Subject: [PATCH] [CALCITE-3916] propagate traits during top down rule applying --- .../calcite/plan/volcano/CascadePlanner.java | 312 +++++++++++------- .../calcite/plan/volcano/CascadeRelSet.java | 19 ++ .../plan/volcano/CascadeRelSubset.java | 49 ++- .../apache/calcite/plan/volcano/RelSet.java | 10 +- .../apache/calcite/test/TopDownOptTest.java | 3 +- 5 files changed, 260 insertions(+), 133 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/CascadePlanner.java b/core/src/main/java/org/apache/calcite/plan/volcano/CascadePlanner.java index 3743af2aaf1..8c23ed839af 100644 --- a/core/src/main/java/org/apache/calcite/plan/volcano/CascadePlanner.java +++ b/core/src/main/java/org/apache/calcite/plan/volcano/CascadePlanner.java @@ -16,6 +16,7 @@ */ package org.apache.calcite.plan.volcano; +import org.apache.calcite.linq4j.function.Function0; import org.apache.calcite.plan.Context; import org.apache.calcite.plan.Convention; import org.apache.calcite.plan.RelOptCost; @@ -23,6 +24,7 @@ import org.apache.calcite.plan.RelOptRuleCall; import org.apache.calcite.plan.RelOptRuleOperand; import org.apache.calcite.plan.SubstitutionRule; +import org.apache.calcite.rel.PhysicalNode; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.convert.ConverterRule; import org.apache.calcite.rel.core.CorrelationId; @@ -30,6 +32,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Stack; @@ -55,25 +58,23 @@ public class CascadePlanner extends VolcanoPlanner { private Stack tasks = new Stack<>(); /** - * the current ApplyRule task. It provides a callback to schedule tasks for - * new RelNodes that registered by the transformTo method call during - * ApplyRule.perform + * A task that is currently applying and may generate new RelNode. + * It provides a callback to schedule tasks for new RelNodes that + * are registered during task performing */ - private ApplyRule applying = null; + private GeneratorTask applying = null; /** - * convention of root + * relnodes that are generated by passThrough or derive + * these nodes will not takes part in another passThrough or derive */ - private Convention rootConvention = null; + private Set passThroughCache = new HashSet<>(); //~ Constructors ----------------------------------------------------------- public CascadePlanner(RelOptCostFactory factory, Context context) { super(factory, context); infCost = costFactory.makeInfiniteCost(); - // topDownOpt will remove AbstractConverters which are essential in - // marking canConvert and needConvert relation between subsets - super.setTopDownOpt(false); } //~ Methods ---------------------------------------------------------------- @@ -110,7 +111,7 @@ protected boolean isSubstituteRule(VolcanoRuleMatch match) { * @return true if the relnode is a logical node */ protected boolean isLogical(RelNode relNode) { - return relNode.getConvention() != rootConvention; + return !(relNode instanceof PhysicalNode); } /** @@ -174,7 +175,6 @@ protected RelOptCost upperBoundForInputs( @Override public RelNode findBestExp() { registerMaterializations(); TaskDescriptor description = new TaskDescriptor(); - rootConvention = root.getConvention(); tasks.push( new OptimizeGroup((CascadeRelSubset) root, costFactory.makeInfiniteCost())); @@ -192,23 +192,87 @@ protected RelOptCost upperBoundForInputs( @Override protected RelSubset addRelToSet(RelNode rel, RelSet set) { RelSubset relSubset = super.addRelToSet(rel, set); - if (applying != null && set.id == applying.group.set.id) { - applying.onProduce(rel); - } + onProduce(rel, set); return relSubset; } - protected RelSet merge(RelSet set, RelSet set2) { - ApplyRule applying = this.applying; - this.applying = null; + private R applyGenerator(GeneratorTask task, Function0 function) { + GeneratorTask applying = this.applying; + this.applying = task; try { + return function.apply(); + } finally { + this.applying = applying; + } + } + + private void onProduce(RelNode node, RelSet equivSet) { + if (applying == null || equivSet.id != applying.group().set.id) { + return; + } + if (!applying.onProduce(node)) { + return; + } + + if (!isLogical(node)) { + CascadeRelSubset subset = (CascadeRelSubset) getSubset(node); + CascadeRelSubset group = null; + + boolean canPassThrough = topDownOpt + && node instanceof PhysicalNode && !passThroughCache.contains(node.getId()); + if (!canPassThrough && subset.state != CascadeRelSubset.OptimizeState.NEW) { + group = subset; + } else { + RelOptCost upperBound = zeroCost; + CascadeRelSet set = subset.getSet(); + List subsetsToPassThrough = new ArrayList<>(); + for (RelSubset relSubset : set.subsets) { + CascadeRelSubset g = (CascadeRelSubset) relSubset; + if (g != root && g.state != CascadeRelSubset.OptimizeState.OPTIMIZING) { + continue; + } + if (node.getTraitSet().satisfies(g.getTraitSet())) { + if (upperBound.isLt(g.upperBound)) { + upperBound = g.upperBound; + group = g; + } + } else if (canPassThrough) { + subsetsToPassThrough.add(g); + } + } + for (CascadeRelSubset g : subsetsToPassThrough) { + Task task = getOptimizeInputTask(node, g); + if (task != null) { + tasks.push(task); + } + } + } + if (group == null) { + return; + } + Task task = getOptimizeInputTask(node, group); + if (task != null) { + tasks.push(task); + } + } else { + tasks.push(new OptimizeMExpr(node, applying.group(), applying.exploring())); + } + } + + @Override public void clear() { + passThroughCache = null; + applying = null; + tasks.clear(); + cascadeRuleQueue = new CascadeRuleQueue(this); + } + + protected RelSet merge(RelSet set, RelSet set2) { + return applyGenerator(null, () -> { RelSet merge = super.merge(set, set2); RelSet canonize = canonize(merge); clearProcessed((CascadeRelSet) canonize); return canonize; - } finally { - this.applying = applying; - } + }); } private void clearProcessed(CascadeRelSet set) { @@ -251,11 +315,6 @@ private void clearProcessed(CascadeRelSet set) { } } - @Override public void setTopDownOpt(boolean value) { - // Do Nothing. AbstractConverters is essential - // in marking canConvert and needConvert - } - @Override protected RelSet newRelSet(int id, Set variablesPropagated, Set variablesUsed) { @@ -331,6 +390,14 @@ TaskDescriptor item(String name, Object value) { } } + private interface GeneratorTask extends Task { + CascadeRelSubset group(); + boolean exploring(); + default boolean onProduce(RelNode node) { + return true; + } + } + /** * O_GROUP */ @@ -355,14 +422,7 @@ private class OptimizeGroup implements Task { return; } - if (!group.bestCost.isInfinite() - && group.bestCost.isLt(upperBound)) { - // this group is partial optimized, there may be other candidate in the group - // prevOptimizeResult == null means this group is changed after previous optimizations - upperBound = group.bestCost; - } - - if (group.upperBound != null + if (group.state != CascadeRelSubset.OptimizeState.NEW && upperBound.isLe(group.upperBound)) { // this group failed to optimize before or it is a ring return; @@ -380,13 +440,13 @@ private class OptimizeGroup implements Task { List physicals = new ArrayList<>(); for (RelNode rel : group.set.rels) { if (isLogical(rel)) { - tasks.push(new OptimizeMExpr(rel, group, upperBound, false)); + tasks.push(new OptimizeMExpr(rel, group, false)); } else { if (!acceptConverter && rel instanceof AbstractConverter) { LOGGER.debug("Skip optimizing AC: {}", rel); continue; } - Task task = getOptimizeInputTask(rel, group, upperBound); + Task task = getOptimizeInputTask(rel, group); if (task != null) { physicals.add(task); } @@ -427,14 +487,12 @@ private static class GroupOptimized implements Task { private class OptimizeMExpr implements Task { private final RelNode mExpr; private final CascadeRelSubset group; - private final RelOptCost upperBound; private final boolean explore; OptimizeMExpr(RelNode mExpr, - CascadeRelSubset group, RelOptCost upperBound, boolean explore) { + CascadeRelSubset group, boolean explore) { this.mExpr = mExpr; this.group = group; - this.upperBound = upperBound; this.explore = explore; } @@ -445,7 +503,7 @@ private class OptimizeMExpr implements Task { } // 1. explode input // 2. apply other rules - tasks.push(new ApplyRules(mExpr, group, upperBound, explore)); + tasks.push(new ApplyRules(mExpr, group, explore)); for (int i = 0; i < mExpr.getInputs().size(); i++) { CascadeRelSubset input = (CascadeRelSubset) mExpr.getInput(i); if (input.getSet().state != CascadeRelSet.ExploreState.NEW) { @@ -512,9 +570,7 @@ private class ExploreInput implements Task { } for (RelNode rel : group.set.rels) { if (isLogical(rel)) { - tasks.push( - new OptimizeMExpr( - rel, group, costFactory.makeInfiniteCost(), true)); + tasks.push(new OptimizeMExpr(rel, group, true)); } } group.getSet().state = CascadeRelSet.ExploreState.EXPLORING; @@ -531,21 +587,18 @@ private class ExploreInput implements Task { private class ApplyRules implements Task { private final RelNode mExpr; private final CascadeRelSubset group; - private final RelOptCost upperBound; private final boolean exploring; - ApplyRules(RelNode mExpr, - CascadeRelSubset group, RelOptCost upperBound, boolean exploring) { + ApplyRules(RelNode mExpr, CascadeRelSubset group, boolean exploring) { this.mExpr = mExpr; this.group = group; - this.upperBound = upperBound; this.exploring = exploring; } @Override public void perform() { VolcanoRuleMatch match = cascadeRuleQueue.popMatch(mExpr, exploring); while (match != null) { - tasks.push(new ApplyRule(match, group, upperBound, exploring)); + tasks.push(new ApplyRule(match, group, exploring)); match = cascadeRuleQueue.popMatch(mExpr, exploring); } } @@ -558,17 +611,14 @@ private class ApplyRules implements Task { /** * APPLY_RULE */ - private class ApplyRule implements Task { + private class ApplyRule implements GeneratorTask { private final VolcanoRuleMatch match; private final CascadeRelSubset group; private final boolean exploring; - private RelOptCost upperBound; - ApplyRule(VolcanoRuleMatch match, - CascadeRelSubset group, RelOptCost upperBound, boolean exploring) { + ApplyRule(VolcanoRuleMatch match, CascadeRelSubset group, boolean exploring) { this.match = match; this.group = group; - this.upperBound = upperBound; this.exploring = exploring; } @@ -589,22 +639,17 @@ private class ApplyRule implements Task { } return; } - try { - applying = this; + applyGenerator(this, () -> { match.onMatch(); - } finally { - applying = null; - } + return null; + }); } private boolean checkLowerBound() { - RelOptCost bestCost = group.bestCost; - if (bestCost.isInfinite() && upperBound.isInfinite()) { + RelOptCost upperBound = group.upperBound; + if (upperBound.isInfinite()) { return true; } - if (!bestCost.isInfinite() && bestCost.isLt(upperBound)) { - upperBound = bestCost; - } RelOptCost lb = getLowerBound(match); if (upperBound.isLe(lb)) { if (LOGGER.isDebugEnabled()) { @@ -617,54 +662,34 @@ private boolean checkLowerBound() { return true; } - /** - * A callback scheduling tasks for new produced RelNodes - */ - public void onProduce(RelNode node) { - if (!isLogical(node)) { - RelOptCost upperBound = zeroCost; - CascadeRelSubset group = null; + @Override public CascadeRelSubset group() { + return group; + } - CascadeRelSubset subset = (CascadeRelSubset) getSubset(node); - if (subset.state != CascadeRelSubset.OptimizeState.NEW) { - group = subset; - upperBound = subset.upperBound; - } else { - if (!exploring) { - group = this.group; - upperBound = this.upperBound; - } - CascadeRelSet set = subset.getSet(); - for (RelSubset relSubset : set.subsets) { - CascadeRelSubset g = (CascadeRelSubset) relSubset; - if ((g == root || g.state == CascadeRelSubset.OptimizeState.OPTIMIZING) - && node.getTraitSet().satisfies(subset.getTraitSet()) - && upperBound.isLt(g.upperBound)) { - upperBound = g.upperBound; - group = g; - } - } - } - if (group == null) { - return; - } - Task task = getOptimizeInputTask(node, group, upperBound); - if (task != null) { - tasks.push(task); - } - } else { - tasks.push(new OptimizeMExpr(node, group, upperBound, exploring)); - } + @Override public boolean exploring() { + return exploring; } } - private Task getOptimizeInputTask(RelNode rel, - CascadeRelSubset group, RelOptCost upperBound) { + private Task getOptimizeInputTask(RelNode rel, CascadeRelSubset group) { if (!rel.getTraitSet().satisfies(group.getTraitSet())) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Skip optimizing because of traits: {}", rel); + RelNode passThroughRel = null; + if (topDownOpt && !passThroughCache.contains(rel.getId())) { + passThroughRel = group.passThrough(rel); + } + if (passThroughRel == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Skip optimizing because of traits: {}", rel); + } + return null; } - return null; + rel = passThroughRel; + final RelNode finalPassThroughRel = passThroughRel; + applyGenerator(null, () -> { + register(finalPassThroughRel, group); + passThroughCache.add(finalPassThroughRel.getId()); + return null; + }); } boolean unProcess = false; for (RelNode input : rel.getInputs()) { @@ -675,13 +700,12 @@ private Task getOptimizeInputTask(RelNode rel, } } if (!unProcess) { - // all input are processed, apply enforcing rules - return new ApplyRules(rel, group, upperBound, false); + return new DeriveTrait(rel, group); } if (rel.getInputs().size() == 1) { - return new OptimizeInput1(rel, group, upperBound); + return new OptimizeInput1(rel, group); } - return new OptimizeInputs(rel, group, upperBound); + return new OptimizeInputs(rel, group); } /** @@ -691,24 +715,19 @@ private class OptimizeInput1 implements Task { private final RelNode mExpr; private final CascadeRelSubset group; - private RelOptCost upperBound; - OptimizeInput1(RelNode mExpr, - CascadeRelSubset group, RelOptCost upperBound) { + OptimizeInput1(RelNode mExpr, CascadeRelSubset group) { this.mExpr = mExpr; this.group = group; - this.upperBound = upperBound; } @Override public void describe(TaskDescriptor desc) { - desc.item("mExpr", mExpr).item("upperBound", upperBound); + desc.item("mExpr", mExpr).item("upperBound", group.upperBound); } @Override public void perform() { - if (!group.bestCost.isInfinite() && group.bestCost.isLt(upperBound)) { - upperBound = group.bestCost; - } + RelOptCost upperBound = group.upperBound; RelOptCost upperForInput = upperBoundForInputs(mExpr, upperBound); if (upperForInput.isLe(zeroCost)) { if (LOGGER.isDebugEnabled()) { @@ -722,7 +741,7 @@ private class OptimizeInput1 implements Task { CascadeRelSubset input = (CascadeRelSubset) mExpr.getInput(0); // Apply enforcing rules - tasks.push(new ApplyRules(mExpr, group, upperBound, false)); + tasks.push(new DeriveTrait(mExpr, group)); tasks.push(new CheckInput(null, mExpr, input, 0, upperForInput)); tasks.push( @@ -743,11 +762,10 @@ private class OptimizeInputs implements Task { private RelOptCost upperForInput; private int processingChild; - OptimizeInputs(RelNode rel, - CascadeRelSubset group, RelOptCost upperBound) { + OptimizeInputs(RelNode rel, CascadeRelSubset group) { this.mExpr = rel; this.group = group; - this.upperBound = upperBound; + this.upperBound = group.upperBound; this.upperForInput = infCost; this.childCount = rel.getInputs().size(); this.processingChild = 0; @@ -762,8 +780,8 @@ private class OptimizeInputs implements Task { private RelOptCost lowerBoundSum; @Override public void perform() { RelOptCost bestCost = group.bestCost; - if (!upperBound.isInfinite() || !bestCost.isInfinite()) { - if (!bestCost.isInfinite() && bestCost.isLt(upperBound)) { + if (!bestCost.isInfinite()) { + if (bestCost.isLt(upperBound)) { upperBound = bestCost; upperForInput = upperBoundForInputs(mExpr, upperBound); } @@ -796,7 +814,7 @@ private class OptimizeInputs implements Task { if (processingChild == 0) { // Apply enforcing rules - tasks.push(new ApplyRules(mExpr, group, upperBound, false)); + tasks.push(new DeriveTrait(mExpr, group)); } while (processingChild < childCount) { @@ -814,7 +832,7 @@ private class OptimizeInputs implements Task { upper = upperForInput.minus(lowerBoundSum) .plus(lowerBounds.get(processingChild)); } - if (input.upperBound != null && upper.isLe(input.upperBound)) { + if (!input.upperBound.isInfinite() && upper.isLe(input.upperBound)) { return; } @@ -879,4 +897,50 @@ private class CheckInput implements Task { } } } + + private class DeriveTrait implements GeneratorTask { + + private final RelNode mExpr; + private final CascadeRelSubset group; + + DeriveTrait(RelNode mExpr, CascadeRelSubset group) { + this.mExpr = mExpr; + this.group = group; + } + + @Override public void perform() { + List inputs = mExpr.getInputs(); + for (RelNode input : inputs) { + if (((CascadeRelSubset) input).getWinnerCost() == null) { + // fail to optimize input, then no need to deliver traits + return; + } + } + // in case there would be some implementations using rules to propagate traits + tasks.push(new ApplyRules(mExpr, group, false)); + if (topDownOpt && !passThroughCache.contains(mExpr.getId())) { + applyGenerator(this, () -> { + new OptimizeTask.RelNodeOptTask(mExpr).execute(); + return null; + }); + } + } + + @Override public void describe(TaskDescriptor desc) { + desc.item("mExpr", mExpr).item("group", group); + } + + @Override public CascadeRelSubset group() { + return group; + } + + @Override public boolean exploring() { + return false; + } + + @Override public boolean onProduce(RelNode node) { + passThroughCache.add(node.getId()); + return true; + } + } } diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/CascadeRelSet.java b/core/src/main/java/org/apache/calcite/plan/volcano/CascadeRelSet.java index 07e41b8ec86..6416b034529 100644 --- a/core/src/main/java/org/apache/calcite/plan/volcano/CascadeRelSet.java +++ b/core/src/main/java/org/apache/calcite/plan/volcano/CascadeRelSet.java @@ -64,4 +64,23 @@ public enum ExploreState { public ExploreState getState() { return state; } + + @Override RelSubset getOrCreateSubset( + RelOptCluster cluster, RelTraitSet traits, boolean require) { + // always add converters eagerly + return getOrCreateSubset(cluster, traits, require, true); + } + + @Override void mergeWith(VolcanoPlanner planner, RelSet otherSet) { + super.mergeWith(planner, otherSet); + for (RelSubset subset : otherSet.subsets) { + CascadeRelSubset cascadeRelSubset = (CascadeRelSubset) subset; + CascadeRelSubset newSubset = (CascadeRelSubset) getSubset(subset.getTraitSet()); + if (newSubset.passThroughCache == null) { + newSubset.passThroughCache = cascadeRelSubset.passThroughCache; + } else if (cascadeRelSubset.passThroughCache != null) { + newSubset.passThroughCache.addAll(cascadeRelSubset.passThroughCache); + } + } + } } diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/CascadeRelSubset.java b/core/src/main/java/org/apache/calcite/plan/volcano/CascadeRelSubset.java index 6bba00cabcf..b70f6bb6a25 100644 --- a/core/src/main/java/org/apache/calcite/plan/volcano/CascadeRelSubset.java +++ b/core/src/main/java/org/apache/calcite/plan/volcano/CascadeRelSubset.java @@ -19,6 +19,14 @@ import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptCost; import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.PhysicalNode; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.util.Pair; + +import java.util.HashSet; +import java.util.Queue; +import java.util.Set; /*** * An implementation of RelSubset for CascadePlanner. @@ -57,20 +65,29 @@ enum OptimizeState { */ RelOptCost upperBound; + /** + * RelNode ids that is invoked passThrough method before + */ + Set passThroughCache; + CascadeRelSubset(RelOptCluster cluster, RelSet set, RelTraitSet traits) { super(cluster, set, traits); + upperBound = bestCost; } public RelOptCost getWinnerCost() { - if (bestCost == upperBound) { + if (bestCost == upperBound && state == OptimizeState.OPTIMIZED) { return bestCost; } + // if bestCost != upperBound, it means optimize failed return null; } public void startOptimize(RelOptCost ub) { + if (ub.isLt(bestCost)) { + upperBound = ub; + } state = OptimizeState.OPTIMIZING; - this.upperBound = ub; } @Override public CascadeRelSet getSet() { @@ -79,15 +96,35 @@ public void startOptimize(RelOptCost ub) { public void optimized() { state = OptimizeState.OPTIMIZED; - if (upperBound == null || bestCost.isLt(upperBound)) { - upperBound = bestCost; - } } public boolean resetOptimizing() { boolean optimized = state != OptimizeState.NEW; state = OptimizeState.NEW; - upperBound = null; + upperBound = bestCost; return optimized; } + + @Override void propagateCostImprovements0( + VolcanoPlanner planner, RelMetadataQuery mq, + RelNode rel, Set activeSet, + Queue> propagationQueue) { + super.propagateCostImprovements0(planner, mq, rel, activeSet, propagationQueue); + if (bestCost != upperBound && bestCost.isLe(upperBound)) { + upperBound = bestCost; + } + } + + public RelNode passThrough(RelNode rel) { + if (!(rel instanceof PhysicalNode)) { + return null; + } + if (passThroughCache == null) { + passThroughCache = new HashSet<>(); + passThroughCache.add(rel.getId()); + } else if (!passThroughCache.add(rel.getId())) { + return null; + } + return ((PhysicalNode) rel).passThrough(this.getTraitSet()); + } } diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java b/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java index e42520d2ed3..cd3cd787b56 100644 --- a/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java +++ b/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java @@ -288,6 +288,12 @@ RelSubset getOrCreateSubset(RelOptCluster cluster, RelTraitSet traits) { RelSubset getOrCreateSubset( RelOptCluster cluster, RelTraitSet traits, boolean required) { + final VolcanoPlanner planner = (VolcanoPlanner) cluster.getPlanner(); + return getOrCreateSubset(cluster, traits, required, !planner.topDownOpt); + } + + RelSubset getOrCreateSubset( + RelOptCluster cluster, RelTraitSet traits, boolean required, boolean eagerConverter) { boolean needsConverter = false; final VolcanoPlanner planner = (VolcanoPlanner) cluster.getPlanner(); RelSubset subset = getSubset(traits); @@ -317,8 +323,8 @@ RelSubset getOrCreateSubset( subset.setDelivered(); } - if (needsConverter && !planner.topDownOpt) { - addConverters(subset, required, true); + if (needsConverter && eagerConverter) { + addConverters(subset, required, !planner.topDownOpt); } return subset; diff --git a/core/src/test/java/org/apache/calcite/test/TopDownOptTest.java b/core/src/test/java/org/apache/calcite/test/TopDownOptTest.java index d188c7b937f..2bf1cfd2fd2 100644 --- a/core/src/test/java/org/apache/calcite/test/TopDownOptTest.java +++ b/core/src/test/java/org/apache/calcite/test/TopDownOptTest.java @@ -20,6 +20,7 @@ import org.apache.calcite.plan.ConventionTraitDef; import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptUtil; +import org.apache.calcite.plan.volcano.CascadePlanner; import org.apache.calcite.plan.volcano.VolcanoPlanner; import org.apache.calcite.rel.RelCollationTraitDef; import org.apache.calcite.rel.rules.JoinCommuteRule; @@ -71,7 +72,7 @@ protected DiffRepository getDiffRepos() { } Sql sql(String sql) { - VolcanoPlanner planner = new VolcanoPlanner(); + VolcanoPlanner planner = new CascadePlanner(null, null); // Always use top-down optimization planner.setTopDownOpt(true); planner.addRelTraitDef(ConventionTraitDef.INSTANCE);