| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.calcite.plan.volcano; |
| |
| import org.apache.calcite.adapter.enumerable.EnumerableConvention; |
| import org.apache.calcite.adapter.enumerable.EnumerableRules; |
| import org.apache.calcite.adapter.enumerable.EnumerableUnion; |
| import org.apache.calcite.plan.Convention; |
| import org.apache.calcite.plan.ConventionTraitDef; |
| import org.apache.calcite.plan.RelOptCluster; |
| import org.apache.calcite.plan.RelOptListener; |
| import org.apache.calcite.plan.RelOptRule; |
| import org.apache.calcite.plan.RelOptRuleCall; |
| import org.apache.calcite.plan.RelOptUtil; |
| import org.apache.calcite.plan.RelRule; |
| import org.apache.calcite.plan.RelTraitSet; |
| import org.apache.calcite.rel.RelCollationTraitDef; |
| import org.apache.calcite.rel.RelCollations; |
| import org.apache.calcite.rel.RelNode; |
| import org.apache.calcite.rel.convert.ConverterImpl; |
| import org.apache.calcite.rel.convert.ConverterRule; |
| import org.apache.calcite.rel.core.JoinRelType; |
| import org.apache.calcite.rel.core.RelFactories; |
| import org.apache.calcite.rel.externalize.RelDotWriter; |
| import org.apache.calcite.rel.logical.LogicalProject; |
| import org.apache.calcite.rel.rules.CoreRules; |
| import org.apache.calcite.sql.SqlExplainLevel; |
| import org.apache.calcite.tools.RelBuilder; |
| import org.apache.calcite.util.ImmutableBeans; |
| import org.apache.calcite.util.Pair; |
| |
| import org.junit.jupiter.api.Disabled; |
| import org.junit.jupiter.api.Test; |
| |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import static org.apache.calcite.plan.volcano.PlannerTests.AssertOperandsDifferentRule; |
| import static org.apache.calcite.plan.volcano.PlannerTests.GoodSingleRule; |
| import static org.apache.calcite.plan.volcano.PlannerTests.NoneLeafRel; |
| import static org.apache.calcite.plan.volcano.PlannerTests.NoneSingleRel; |
| import static org.apache.calcite.plan.volcano.PlannerTests.PHYS_CALLING_CONVENTION; |
| import static org.apache.calcite.plan.volcano.PlannerTests.PHYS_CALLING_CONVENTION_2; |
| import static org.apache.calcite.plan.volcano.PlannerTests.PHYS_CALLING_CONVENTION_3; |
| import static org.apache.calcite.plan.volcano.PlannerTests.PhysBiRel; |
| import static org.apache.calcite.plan.volcano.PlannerTests.PhysLeafRel; |
| import static org.apache.calcite.plan.volcano.PlannerTests.PhysLeafRule; |
| import static org.apache.calcite.plan.volcano.PlannerTests.PhysSingleRel; |
| import static org.apache.calcite.plan.volcano.PlannerTests.TestSingleRel; |
| import static org.apache.calcite.plan.volcano.PlannerTests.newCluster; |
| import static org.apache.calcite.test.Matchers.isLinux; |
| |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotSame; |
| import static org.junit.jupiter.api.Assertions.assertSame; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| /** |
| * Unit test for {@link VolcanoPlanner the optimizer}. |
| */ |
| class VolcanoPlannerTest { |
| |
| //~ Methods ---------------------------------------------------------------- |
| /** |
| * Tests transformation of a leaf from NONE to PHYS. |
| */ |
| @Test void testTransformLeaf() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = |
| new NoneLeafRel( |
| cluster, |
| "a"); |
| RelNode convertedRel = |
| planner.changeTraits( |
| leafRel, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| planner.setRoot(convertedRel); |
| RelNode result = planner.chooseDelegate().findBestExp(); |
| assertTrue(result instanceof PhysLeafRel); |
| } |
| |
| /** |
| * Tests transformation of a single+leaf from NONE to PHYS. |
| */ |
| @Test void testTransformSingleGood() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| planner.addRule(GoodSingleRule.INSTANCE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = |
| new NoneLeafRel( |
| cluster, |
| "a"); |
| NoneSingleRel singleRel = |
| new NoneSingleRel( |
| cluster, |
| leafRel); |
| RelNode convertedRel = |
| planner.changeTraits( |
| singleRel, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| planner.setRoot(convertedRel); |
| RelNode result = planner.chooseDelegate().findBestExp(); |
| assertTrue(result instanceof PhysSingleRel); |
| } |
| |
| @Test void testPlanToDot() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = |
| new NoneLeafRel( |
| cluster, |
| "a"); |
| NoneSingleRel singleRel = |
| new NoneSingleRel( |
| cluster, |
| leafRel); |
| RelNode convertedRel = |
| planner.changeTraits( |
| singleRel, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| planner.setRoot(convertedRel); |
| |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter(sw); |
| |
| RelDotWriter planWriter = new RelDotWriter(pw, SqlExplainLevel.NO_ATTRIBUTES, false); |
| planner.getRoot().explain(planWriter); |
| String planStr = sw.toString(); |
| |
| assertThat( |
| planStr, isLinux("digraph {\n" |
| + "\"NoneLeafRel\\n\" -> \"NoneSingleRel\\n\" [label=\"0\"]\n" |
| + "}\n")); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3118">[CALCITE-3118] |
| * VolcanoRuleCall should look at RelSubset rather than RelSet |
| * when checking child ordinal of a parent operand</a>. */ |
| @Test void testMatchedOperandsDifferent() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| RelOptCluster cluster = newCluster(planner); |
| |
| // The rule that triggers the assert rule |
| planner.addRule(PhysLeafRule.INSTANCE); |
| |
| // The rule asserting that the matched operands are different |
| planner.addRule(AssertOperandsDifferentRule.INSTANCE); |
| |
| // Construct two children in the same set and a parent RelNode |
| NoneLeafRel leftRel = new NoneLeafRel(cluster, "a"); |
| RelNode leftPhy = planner |
| .changeTraits(leftRel, cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| PhysLeafRel rightPhy = |
| new PhysLeafRel(cluster, PHYS_CALLING_CONVENTION_2, "b"); |
| |
| PhysBiRel parent = |
| new PhysBiRel(cluster, cluster.traitSetOf(PHYS_CALLING_CONVENTION), |
| leftPhy, rightPhy); |
| planner.setRoot(parent); |
| |
| // Make sure both RelNodes are in the same set, but different subset |
| planner.ensureRegistered(leftPhy, rightPhy); |
| |
| planner.chooseDelegate().findBestExp(); |
| } |
| |
| /** |
| * A pattern that matches a three input union with third child matching for |
| * a PhysLeafRel node. |
| */ |
| public static class ThreeInputsUnionRule |
| extends RelRule<ThreeInputsUnionRule.Config> { |
| static final ThreeInputsUnionRule INSTANCE = Config.EMPTY |
| .withOperandSupplier(b0 -> |
| b0.operand(EnumerableUnion.class).inputs( |
| b1 -> b1.operand(PhysBiRel.class).anyInputs(), |
| b2 -> b2.operand(PhysBiRel.class).anyInputs(), |
| b3 -> b3.operand(PhysLeafRel.class).anyInputs())) |
| .as(Config.class) |
| .toRule(); |
| |
| ThreeInputsUnionRule(Config config) { |
| super(config); |
| } |
| |
| @Override public void onMatch(RelOptRuleCall call) { |
| } |
| |
| /** Rule configuration. */ |
| public interface Config extends RelRule.Config { |
| @Override default ThreeInputsUnionRule toRule() { |
| return new ThreeInputsUnionRule(this); |
| } |
| } |
| } |
| |
| @Test void testMultiInputsParentOpMatching() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| RelOptCluster cluster = newCluster(planner); |
| |
| // The trigger rule that generates PhysLeafRel from NoneLeafRel |
| planner.addRule(PhysLeafRule.INSTANCE); |
| |
| // The rule with third child op matching PhysLeafRel, which should not be |
| // matched at all |
| planner.addRule(ThreeInputsUnionRule.INSTANCE); |
| |
| // Construct a union with only two children |
| NoneLeafRel leftRel = new NoneLeafRel(cluster, "b"); |
| RelNode leftPhy = planner |
| .changeTraits(leftRel, cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| PhysLeafRel rightPhy = |
| new PhysLeafRel(cluster, PHYS_CALLING_CONVENTION, "b"); |
| |
| planner.setRoot( |
| new EnumerableUnion(cluster, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION), |
| Arrays.asList(leftPhy, rightPhy), false)); |
| |
| planner.chooseDelegate().findBestExp(); |
| } |
| |
| /** |
| * Tests a rule that is fired once per subset. (Whereas most rules are fired |
| * once per rel in a set or rel in a subset.) |
| */ |
| @Test void testSubsetRule() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| planner.addRelTraitDef(RelCollationTraitDef.INSTANCE); |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| planner.addRule(GoodSingleRule.INSTANCE); |
| final List<String> buf = new ArrayList<>(); |
| planner.addRule(SubsetRule.config(buf).toRule()); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = |
| new NoneLeafRel( |
| cluster, |
| "a"); |
| NoneSingleRel singleRel = |
| new NoneSingleRel( |
| cluster, |
| leafRel); |
| RelNode convertedRel = |
| planner.changeTraits( |
| singleRel, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| planner.changeTraits(leafRel, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION) |
| .plus(RelCollations.of(0))); |
| planner.setRoot(convertedRel); |
| RelNode result = planner.chooseDelegate().findBestExp(); |
| assertTrue(result instanceof PhysSingleRel); |
| assertThat(sort(buf), |
| equalTo( |
| sort( |
| "NoneSingleRel:RelSubset#0.NONE.[]", |
| "PhysSingleRel:RelSubset#0.PHYS.[0]", |
| "PhysSingleRel:RelSubset#0.PHYS.[]"))); |
| } |
| |
| private static <E extends Comparable> List<E> sort(List<E> list) { |
| final List<E> list2 = new ArrayList<>(list); |
| Collections.sort(list2); |
| return list2; |
| } |
| |
| private static <E extends Comparable> List<E> sort(E... es) { |
| return sort(Arrays.asList(es)); |
| } |
| |
| /** |
| * Tests that VolcanoPlanner should fire rule match from subsets after a |
| * RelSet merge. The rules matching for a RelSubset should be able to fire |
| * on the subsets that are merged into the RelSets. |
| */ |
| @Test void testSetMergeMatchSubsetRule() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| planner.addRelTraitDef(RelCollationTraitDef.INSTANCE); |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| planner.addRule(GoodSingleRule.INSTANCE); |
| planner.addRule(PhysSingleInputSetMergeRule.INSTANCE); |
| final List<String> buf = new ArrayList<>(); |
| planner.addRule(PhysSingleSubsetRule.config(buf).toRule()); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = new NoneLeafRel(cluster, "a"); |
| NoneSingleRel singleRel = new NoneSingleRel(cluster, leafRel); |
| RelNode convertedRel = planner |
| .changeTraits(singleRel, cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| planner.setRoot(convertedRel); |
| RelNode result = planner.chooseDelegate().findBestExp(); |
| assertTrue(result instanceof PhysSingleRel); |
| assertThat(sort(buf), |
| equalTo( |
| sort("PhysSingleRel:RelSubset#0.PHYS.[]", |
| "PhysSingleRel:RelSubset#0.PHYS_3.[]"))); |
| } |
| |
| /** |
| * Tests transformation of a single+leaf from NONE to PHYS. In the past, |
| * this one didn't work due to the definition of ReformedSingleRule. |
| */ |
| @Disabled // broken, because ReformedSingleRule matches child traits strictly |
| @Test void testTransformSingleReformed() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| planner.addRule(ReformedSingleRule.INSTANCE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = |
| new NoneLeafRel( |
| cluster, |
| "a"); |
| NoneSingleRel singleRel = |
| new NoneSingleRel( |
| cluster, |
| leafRel); |
| RelNode convertedRel = |
| planner.changeTraits( |
| singleRel, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| planner.setRoot(convertedRel); |
| RelNode result = planner.chooseDelegate().findBestExp(); |
| assertTrue(result instanceof PhysSingleRel); |
| } |
| |
| private void removeTrivialProject(boolean useRule) { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| if (useRule) { |
| planner.addRule(CoreRules.PROJECT_REMOVE); |
| } |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| planner.addRule(GoodSingleRule.INSTANCE); |
| planner.addRule(PhysProjectRule.INSTANCE); |
| |
| planner.addRule(PhysToIteratorRule.INSTANCE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| PhysLeafRel leafRel = |
| new PhysLeafRel( |
| cluster, |
| "a"); |
| final RelBuilder relBuilder = |
| RelFactories.LOGICAL_BUILDER.create(leafRel.getCluster(), null); |
| RelNode projectRel = |
| relBuilder.push(leafRel) |
| .project(relBuilder.alias(relBuilder.field(0), "this")) |
| .build(); |
| NoneSingleRel singleRel = |
| new NoneSingleRel( |
| cluster, |
| projectRel); |
| RelNode convertedRel = |
| planner.changeTraits( |
| singleRel, |
| cluster.traitSetOf(EnumerableConvention.INSTANCE)); |
| planner.setRoot(convertedRel); |
| RelNode result = planner.chooseDelegate().findBestExp(); |
| assertTrue(result instanceof PhysToIteratorConverter); |
| } |
| |
| // NOTE: this used to fail but now works |
| @Test void testWithRemoveTrivialProject() { |
| removeTrivialProject(true); |
| } |
| |
| // NOTE: this always worked; it's here as contrast to |
| // testWithRemoveTrivialProject() |
| @Test void testWithoutRemoveTrivialProject() { |
| removeTrivialProject(false); |
| } |
| |
| /** |
| * Previously, this didn't work because ReformedRemoveSingleRule uses a |
| * pattern which spans calling conventions. |
| */ |
| @Disabled // broken, because ReformedSingleRule matches child traits strictly |
| @Test void testRemoveSingleReformed() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| planner.addRule(ReformedRemoveSingleRule.INSTANCE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = |
| new NoneLeafRel( |
| cluster, |
| "a"); |
| NoneSingleRel singleRel = |
| new NoneSingleRel( |
| cluster, |
| leafRel); |
| RelNode convertedRel = |
| planner.changeTraits( |
| singleRel, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| planner.setRoot(convertedRel); |
| RelNode result = planner.chooseDelegate().findBestExp(); |
| assertTrue(result instanceof PhysLeafRel); |
| PhysLeafRel resultLeaf = (PhysLeafRel) result; |
| assertEquals( |
| "c", |
| resultLeaf.label); |
| } |
| |
| /** |
| * This always worked (in contrast to testRemoveSingleReformed) because it |
| * uses a completely-physical pattern (requiring GoodSingleRule to fire |
| * first). |
| */ |
| @Test void testRemoveSingleGood() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| planner.addRule(GoodSingleRule.INSTANCE); |
| planner.addRule(GoodRemoveSingleRule.INSTANCE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = |
| new NoneLeafRel( |
| cluster, |
| "a"); |
| NoneSingleRel singleRel = |
| new NoneSingleRel( |
| cluster, |
| leafRel); |
| RelNode convertedRel = |
| planner.changeTraits( |
| singleRel, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| planner.setRoot(convertedRel); |
| RelNode result = planner.chooseDelegate().findBestExp(); |
| assertTrue(result instanceof PhysLeafRel); |
| PhysLeafRel resultLeaf = (PhysLeafRel) result; |
| assertEquals( |
| "c", |
| resultLeaf.label); |
| } |
| |
| @Disabled("CALCITE-2592 EnumerableMergeJoin is never taken") |
| @Test void testMergeJoin() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| // Below two lines are important for the planner to use collation trait and generate merge join |
| planner.addRelTraitDef(RelCollationTraitDef.INSTANCE); |
| planner.registerAbstractRelationalRules(); |
| |
| planner.addRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE); |
| planner.addRule(EnumerableRules.ENUMERABLE_VALUES_RULE); |
| planner.addRule(EnumerableRules.ENUMERABLE_SORT_RULE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| |
| RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(cluster, null); |
| RelNode logicalPlan = relBuilder |
| .values(new String[]{"id", "name"}, "2", "a", "1", "b") |
| .values(new String[]{"id", "name"}, "1", "x", "2", "y") |
| .join(JoinRelType.INNER, "id") |
| .build(); |
| |
| RelTraitSet desiredTraits = |
| cluster.traitSet().replace(EnumerableConvention.INSTANCE); |
| final RelNode newRoot = planner.changeTraits(logicalPlan, desiredTraits); |
| planner.setRoot(newRoot); |
| |
| RelNode bestExp = planner.findBestExp(); |
| |
| final String plan = "" |
| + "EnumerableMergeJoin(condition=[=($0, $2)], joinType=[inner])\n" |
| + " EnumerableSort(sort0=[$0], dir0=[ASC])\n" |
| + " EnumerableValues(tuples=[[{ '2', 'a' }, { '1', 'b' }]])\n" |
| + " EnumerableValues(tuples=[[{ '1', 'x' }, { '2', 'y' }]])\n"; |
| assertThat("Merge join + sort is expected", plan, |
| isLinux(RelOptUtil.toString(bestExp))); |
| } |
| |
| @Test public void testPruneNode() { |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = |
| new NoneLeafRel( |
| cluster, |
| "a"); |
| planner.setRoot(leafRel); |
| |
| // prune the node |
| planner.prune(leafRel); |
| |
| // verify that the rule match cannot be popped, |
| // as the related node has been pruned |
| RuleQueue ruleQueue = planner.ruleDriver.getRuleQueue(); |
| while (true) { |
| VolcanoRuleMatch ruleMatch; |
| if (ruleQueue instanceof IterativeRuleQueue) { |
| ruleMatch = ((IterativeRuleQueue) ruleQueue).popMatch(); |
| } else { |
| ruleMatch = ((TopDownRuleQueue) ruleQueue).popMatch(Pair.of(leafRel, null)); |
| } |
| if (ruleMatch == null) { |
| break; |
| } |
| assertNotSame(leafRel, ruleMatch.rels[0]); |
| } |
| } |
| |
| /** |
| * Tests whether planner correctly notifies listeners of events. |
| */ |
| @Disabled |
| @Test void testListener() { |
| TestListener listener = new TestListener(); |
| |
| VolcanoPlanner planner = new VolcanoPlanner(); |
| planner.addListener(listener); |
| |
| planner.addRelTraitDef(ConventionTraitDef.INSTANCE); |
| |
| planner.addRule(PhysLeafRule.INSTANCE); |
| |
| RelOptCluster cluster = newCluster(planner); |
| NoneLeafRel leafRel = |
| new NoneLeafRel( |
| cluster, |
| "a"); |
| RelNode convertedRel = |
| planner.changeTraits( |
| leafRel, |
| cluster.traitSetOf(PHYS_CALLING_CONVENTION)); |
| planner.setRoot(convertedRel); |
| RelNode result = planner.chooseDelegate().findBestExp(); |
| assertTrue(result instanceof PhysLeafRel); |
| |
| List<RelOptListener.RelEvent> eventList = listener.getEventList(); |
| |
| // add node |
| checkEvent( |
| eventList, |
| 0, |
| RelOptListener.RelEquivalenceEvent.class, |
| leafRel, |
| null); |
| |
| // internal subset |
| checkEvent( |
| eventList, |
| 1, |
| RelOptListener.RelEquivalenceEvent.class, |
| null, |
| null); |
| |
| // before rule |
| checkEvent( |
| eventList, |
| 2, |
| RelOptListener.RuleAttemptedEvent.class, |
| leafRel, |
| PhysLeafRule.class); |
| |
| // before rule |
| checkEvent( |
| eventList, |
| 3, |
| RelOptListener.RuleProductionEvent.class, |
| result, |
| PhysLeafRule.class); |
| |
| // result of rule |
| checkEvent( |
| eventList, |
| 4, |
| RelOptListener.RelEquivalenceEvent.class, |
| result, |
| null); |
| |
| // after rule |
| checkEvent( |
| eventList, |
| 5, |
| RelOptListener.RuleProductionEvent.class, |
| result, |
| PhysLeafRule.class); |
| |
| // after rule |
| checkEvent( |
| eventList, |
| 6, |
| RelOptListener.RuleAttemptedEvent.class, |
| leafRel, |
| PhysLeafRule.class); |
| |
| // choose plan |
| checkEvent( |
| eventList, |
| 7, |
| RelOptListener.RelChosenEvent.class, |
| result, |
| null); |
| |
| // finish choosing plan |
| checkEvent( |
| eventList, |
| 8, |
| RelOptListener.RelChosenEvent.class, |
| null, |
| null); |
| } |
| |
| private void checkEvent( |
| List<RelOptListener.RelEvent> eventList, |
| int iEvent, |
| Class expectedEventClass, |
| RelNode expectedRel, |
| Class<? extends RelOptRule> expectedRuleClass) { |
| assertTrue(iEvent < eventList.size()); |
| RelOptListener.RelEvent event = eventList.get(iEvent); |
| assertSame( |
| expectedEventClass, |
| event.getClass()); |
| if (expectedRel != null) { |
| assertSame( |
| expectedRel, |
| event.getRel()); |
| } |
| if (expectedRuleClass != null) { |
| RelOptListener.RuleEvent ruleEvent = |
| (RelOptListener.RuleEvent) event; |
| assertSame( |
| expectedRuleClass, |
| ruleEvent.getRuleCall().getRule().getClass()); |
| } |
| } |
| |
| //~ Inner Classes ---------------------------------------------------------- |
| |
| /** Converter from PHYS to ENUMERABLE convention. */ |
| static class PhysToIteratorConverter extends ConverterImpl { |
| PhysToIteratorConverter( |
| RelOptCluster cluster, |
| RelNode child) { |
| super( |
| cluster, |
| ConventionTraitDef.INSTANCE, |
| cluster.traitSetOf(EnumerableConvention.INSTANCE), |
| child); |
| } |
| |
| public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { |
| assert traitSet.comprises(EnumerableConvention.INSTANCE); |
| return new PhysToIteratorConverter( |
| getCluster(), |
| sole(inputs)); |
| } |
| } |
| |
| /** Rule that matches a {@link RelSubset}. */ |
| public static class SubsetRule extends RelRule<SubsetRule.Config> { |
| static Config config(List<String> buf) { |
| return Config.EMPTY |
| .withOperandSupplier(b0 -> |
| b0.operand(TestSingleRel.class).oneInput(b1 -> |
| b1.operand(RelSubset.class).anyInputs())) |
| .as(Config.class) |
| .withBuf(buf); |
| } |
| |
| protected SubsetRule(Config config) { |
| super(config); |
| } |
| |
| public Convention getOutConvention() { |
| return PHYS_CALLING_CONVENTION; |
| } |
| |
| @Override public void onMatch(RelOptRuleCall call) { |
| // Do not transform to anything; just log the calls. |
| TestSingleRel singleRel = call.rel(0); |
| RelSubset childRel = call.rel(1); |
| assertThat(call.rels.length, equalTo(2)); |
| final List<String> buf = config.buf(); |
| buf.add(singleRel.getClass().getSimpleName() + ":" |
| + childRel.getDigest()); |
| } |
| |
| /** Rule configuration. */ |
| public interface Config extends RelRule.Config { |
| @Override default SubsetRule toRule() { |
| return new SubsetRule(this); |
| } |
| |
| @ImmutableBeans.Property(makeImmutable = false) |
| List<String> buf(); |
| |
| /** Sets {@link #buf()}. */ |
| Config withBuf(List<String> buf); |
| } |
| } |
| |
| /** Rule that matches a PhysSingle on a RelSubset. */ |
| public static class PhysSingleSubsetRule |
| extends RelRule<PhysSingleSubsetRule.Config> { |
| static Config config(List<String> buf) { |
| return Config.EMPTY |
| .withOperandSupplier(b0 -> |
| b0.operand(PhysSingleRel.class).oneInput(b1 -> |
| b1.operand(RelSubset.class).anyInputs())) |
| .as(Config.class) |
| .withBuf(buf); |
| } |
| |
| protected PhysSingleSubsetRule(Config config) { |
| super(config); |
| } |
| |
| @Override public Convention getOutConvention() { |
| return PHYS_CALLING_CONVENTION; |
| } |
| |
| @Override public void onMatch(RelOptRuleCall call) { |
| PhysSingleRel singleRel = call.rel(0); |
| RelSubset subset = call.rel(1); |
| final List<String> buf = config.buf(); |
| buf.add(singleRel.getClass().getSimpleName() + ":" |
| + subset.getDigest()); |
| } |
| |
| /** Rule configuration. */ |
| public interface Config extends RelRule.Config { |
| @Override default PhysSingleSubsetRule toRule() { |
| return new PhysSingleSubsetRule(this); |
| } |
| |
| @ImmutableBeans.Property(makeImmutable = false) |
| List<String> buf(); |
| |
| /** Sets {@link #buf()}. */ |
| Config withBuf(List<String> buf); |
| } |
| } |
| |
| /** Creates an artificial RelSet merge in the PhysSingleRel's input RelSet. */ |
| public static class PhysSingleInputSetMergeRule |
| extends RelRule<PhysSingleInputSetMergeRule.Config> { |
| static final PhysSingleInputSetMergeRule INSTANCE = |
| Config.EMPTY |
| .withOperandSupplier(b0 -> |
| b0.operand(PhysSingleRel.class).oneInput(b1 -> |
| b1.operand(PhysLeafRel.class) |
| .trait(PHYS_CALLING_CONVENTION).anyInputs())) |
| .as(Config.class) |
| .toRule(); |
| |
| protected PhysSingleInputSetMergeRule(Config config) { |
| super(config); |
| } |
| |
| @Override public void onMatch(RelOptRuleCall call) { |
| PhysSingleRel singleRel = call.rel(0); |
| PhysLeafRel input = call.rel(1); |
| RelNode newInput = |
| new PhysLeafRel(input.getCluster(), PHYS_CALLING_CONVENTION_3, "a"); |
| |
| VolcanoPlanner planner = (VolcanoPlanner) call.getPlanner(); |
| // Register into a new RelSet first |
| planner.ensureRegistered(newInput, null); |
| // Merge into the old RelSet |
| planner.ensureRegistered(newInput, input); |
| } |
| |
| /** Rule configuration. */ |
| public interface Config extends RelRule.Config { |
| @Override default PhysSingleInputSetMergeRule toRule() { |
| return new PhysSingleInputSetMergeRule(this); |
| } |
| } |
| } |
| |
| // NOTE: Previously, ReformedSingleRule didn't work because it explicitly |
| // specifies PhysLeafRel rather than RelNode for the single input. Since |
| // the PhysLeafRel is in a different subset from the original NoneLeafRel, |
| // ReformedSingleRule never saw it. (GoodSingleRule saw the NoneLeafRel |
| // instead and fires off of that; later the NoneLeafRel gets converted into |
| // a PhysLeafRel). Now Volcano supports rules which match across subsets. |
| |
| /** Planner rule that matches a {@link NoneSingleRel} whose input is |
| * a {@link PhysLeafRel} in a different subset. */ |
| public static class ReformedSingleRule |
| extends RelRule<ReformedSingleRule.Config> { |
| static final ReformedSingleRule INSTANCE = |
| Config.EMPTY |
| .withOperandSupplier(b0 -> |
| b0.operand(NoneSingleRel.class).oneInput(b1 -> |
| b1.operand(PhysLeafRel.class).anyInputs())) |
| .as(Config.class) |
| .toRule(); |
| |
| protected ReformedSingleRule(Config config) { |
| super(config); |
| } |
| |
| @Override public Convention getOutConvention() { |
| return PHYS_CALLING_CONVENTION; |
| } |
| |
| @Override public void onMatch(RelOptRuleCall call) { |
| NoneSingleRel singleRel = call.rel(0); |
| RelNode childRel = call.rel(1); |
| RelNode physInput = |
| convert( |
| childRel, |
| singleRel.getTraitSet().replace(PHYS_CALLING_CONVENTION)); |
| call.transformTo( |
| new PhysSingleRel( |
| singleRel.getCluster(), |
| physInput)); |
| } |
| |
| /** Rule configuration. */ |
| public interface Config extends RelRule.Config { |
| @Override default ReformedSingleRule toRule() { |
| return new ReformedSingleRule(this); |
| } |
| } |
| } |
| |
| /** Planner rule that converts a {@link LogicalProject} to PHYS convention. */ |
| public static class PhysProjectRule |
| extends RelRule<PhysProjectRule.Config> { |
| static final PhysProjectRule INSTANCE = |
| Config.EMPTY |
| .withOperandSupplier(b -> |
| b.operand(LogicalProject.class).anyInputs()) |
| .as(Config.class) |
| .toRule(); |
| |
| PhysProjectRule(Config config) { |
| super(config); |
| } |
| |
| @Override public Convention getOutConvention() { |
| return PHYS_CALLING_CONVENTION; |
| } |
| |
| @Override public void onMatch(RelOptRuleCall call) { |
| final LogicalProject project = call.rel(0); |
| RelNode childRel = project.getInput(); |
| call.transformTo( |
| new PhysLeafRel( |
| childRel.getCluster(), |
| "b")); |
| } |
| |
| /** Rule configuration. */ |
| public interface Config extends RelRule.Config { |
| @Override default PhysProjectRule toRule() { |
| return new PhysProjectRule(this); |
| } |
| } |
| } |
| |
| /** Planner rule that successfully removes a {@link PhysSingleRel}. */ |
| public static class GoodRemoveSingleRule |
| extends RelRule<GoodRemoveSingleRule.Config> { |
| static final GoodRemoveSingleRule INSTANCE = |
| Config.EMPTY |
| .withOperandSupplier(b0 -> |
| b0.operand(PhysSingleRel.class).oneInput(b1 -> |
| b1.operand(PhysLeafRel.class).anyInputs())) |
| .as(Config.class) |
| .toRule(); |
| |
| |
| protected GoodRemoveSingleRule(Config config) { |
| super(config); |
| } |
| |
| @Override public Convention getOutConvention() { |
| return PHYS_CALLING_CONVENTION; |
| } |
| |
| @Override public void onMatch(RelOptRuleCall call) { |
| PhysSingleRel singleRel = call.rel(0); |
| PhysLeafRel leafRel = call.rel(1); |
| call.transformTo( |
| new PhysLeafRel( |
| singleRel.getCluster(), |
| "c")); |
| } |
| |
| /** Rule configuration. */ |
| public interface Config extends RelRule.Config { |
| @Override default GoodRemoveSingleRule toRule() { |
| return new GoodRemoveSingleRule(this); |
| } |
| } |
| } |
| |
| /** Planner rule that removes a {@link NoneSingleRel}. */ |
| public static class ReformedRemoveSingleRule |
| extends RelRule<ReformedRemoveSingleRule.Config> { |
| static final ReformedRemoveSingleRule INSTANCE = |
| Config.EMPTY |
| .withOperandSupplier(b0 -> |
| b0.operand(NoneSingleRel.class).oneInput(b1 -> |
| b1.operand(PhysLeafRel.class).anyInputs())) |
| .as(Config.class) |
| .toRule(); |
| |
| protected ReformedRemoveSingleRule(Config config) { |
| super(config); |
| } |
| |
| public Convention getOutConvention() { |
| return PHYS_CALLING_CONVENTION; |
| } |
| |
| @Override public void onMatch(RelOptRuleCall call) { |
| NoneSingleRel singleRel = call.rel(0); |
| PhysLeafRel leafRel = call.rel(1); |
| call.transformTo( |
| new PhysLeafRel( |
| singleRel.getCluster(), |
| "c")); |
| } |
| |
| /** Rule configuration. */ |
| public interface Config extends RelRule.Config { |
| @Override default ReformedRemoveSingleRule toRule() { |
| return new ReformedRemoveSingleRule(this); |
| } |
| } |
| } |
| |
| /** Implementation of {@link RelOptListener}. */ |
| private static class TestListener implements RelOptListener { |
| private List<RelEvent> eventList; |
| |
| TestListener() { |
| eventList = new ArrayList<>(); |
| } |
| |
| List<RelEvent> getEventList() { |
| return eventList; |
| } |
| |
| private void recordEvent(RelEvent event) { |
| eventList.add(event); |
| } |
| |
| public void relChosen(RelChosenEvent event) { |
| recordEvent(event); |
| } |
| |
| public void relDiscarded(RelDiscardedEvent event) { |
| // Volcano is quite a pack rat--it never discards anything! |
| throw new AssertionError(event); |
| } |
| |
| public void relEquivalenceFound(RelEquivalenceEvent event) { |
| if (!event.isPhysical()) { |
| return; |
| } |
| recordEvent(event); |
| } |
| |
| public void ruleAttempted(RuleAttemptedEvent event) { |
| recordEvent(event); |
| } |
| |
| public void ruleProductionSucceeded(RuleProductionEvent event) { |
| recordEvent(event); |
| } |
| } |
| |
| /** Rule that converts a physical RelNode to an iterator. */ |
| private static class PhysToIteratorRule extends ConverterRule { |
| static final PhysToIteratorRule INSTANCE = Config.INSTANCE |
| .withConversion(RelNode.class, PlannerTests.PHYS_CALLING_CONVENTION, |
| EnumerableConvention.INSTANCE, "PhysToIteratorRule") |
| .withRuleFactory(PhysToIteratorRule::new) |
| .toRule(PhysToIteratorRule.class); |
| |
| PhysToIteratorRule(Config config) { |
| super(config); |
| } |
| |
| @Override public RelNode convert(RelNode rel) { |
| return new PhysToIteratorConverter( |
| rel.getCluster(), |
| rel); |
| } |
| } |
| } |