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

hyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/master by this push:
     new 7be30db  [CALCITE-3997] Logical rules matched with physical operators 
but failed to handle traits
7be30db is described below

commit 7be30db36d449e0a7fcc76b7d4647e141f4bc72d
Author: Haisheng Yuan <h.y...@alibaba-inc.com>
AuthorDate: Wed May 13 14:18:07 2020 -0500

    [CALCITE-3997] Logical rules matched with physical operators but failed to 
handle traits
    
    Logical transformation rule, only logical operator can be rule operand, and
    only generate logical alternatives. It is only visible to VolcanoPlanner,
    HepPlanner will ignore this interface. That means, in HepPlanner, the rule 
that
    implements TransformationRule can still match with physical operator of
    PhysicalNode and generate physical alternatives.  But in VolcanoPlanner,
    TransformationRule doesn't match with physical operator that implements
    PhysicalNode. It is NOT allowed to generate physical operators in
    TransformationRule, unless you are using it in HepPlanner.
    
    This will also fix issue CALCITE-3968.
    
    Close #1976
---
 .../org/apache/calcite/plan/volcano/RuleQueue.java |  2 +-
 .../calcite/plan/volcano/VolcanoPlanner.java       | 10 ++++++
 .../calcite/plan/volcano/VolcanoRuleCall.java      |  2 +-
 .../rel/rules/AbstractJoinExtractFilterRule.java   |  3 +-
 .../rel/rules/AggregateCaseToFilterRule.java       |  3 +-
 .../AggregateExpandDistinctAggregatesRule.java     |  3 +-
 .../rel/rules/AggregateExtractProjectRule.java     |  3 +-
 .../rel/rules/AggregateFilterTransposeRule.java    |  3 +-
 .../rel/rules/AggregateJoinJoinRemoveRule.java     |  3 +-
 .../calcite/rel/rules/AggregateJoinRemoveRule.java |  2 +-
 .../rel/rules/AggregateJoinTransposeRule.java      |  2 +-
 .../calcite/rel/rules/AggregateMergeRule.java      |  2 +-
 .../rel/rules/AggregateProjectMergeRule.java       |  2 +-
 .../rules/AggregateProjectPullUpConstantsRule.java |  3 +-
 .../rel/rules/AggregateReduceFunctionsRule.java    |  3 +-
 .../calcite/rel/rules/AggregateRemoveRule.java     |  3 +-
 .../calcite/rel/rules/AggregateStarTableRule.java  |  2 +-
 .../rel/rules/AggregateUnionAggregateRule.java     |  2 +-
 .../rel/rules/AggregateUnionTransposeRule.java     |  2 +-
 .../calcite/rel/rules/AggregateValuesRule.java     |  1 -
 .../apache/calcite/rel/rules/CalcMergeRule.java    |  2 +-
 .../apache/calcite/rel/rules/CalcRemoveRule.java   |  1 -
 .../apache/calcite/rel/rules/CalcSplitRule.java    |  2 +-
 .../apache/calcite/rel/rules/CoerceInputsRule.java |  2 +-
 .../apache/calcite/rel/rules/DateRangeRules.java   |  2 +-
 .../rel/rules/ExchangeRemoveConstantKeysRule.java  |  1 -
 .../rel/rules/FilterAggregateTransposeRule.java    |  2 +-
 .../calcite/rel/rules/FilterCalcMergeRule.java     |  2 +-
 .../calcite/rel/rules/FilterCorrelateRule.java     |  2 +-
 .../apache/calcite/rel/rules/FilterJoinRule.java   |  2 +-
 .../apache/calcite/rel/rules/FilterMergeRule.java  |  1 -
 .../rel/rules/FilterMultiJoinMergeRule.java        |  2 +-
 .../rel/rules/FilterProjectTransposeRule.java      |  2 +-
 .../rules/FilterRemoveIsNotDistinctFromRule.java   |  3 +-
 .../rel/rules/FilterSetOpTransposeRule.java        |  2 +-
 .../rules/FilterTableFunctionTransposeRule.java    |  3 +-
 .../apache/calcite/rel/rules/FilterToCalcRule.java |  2 +-
 .../calcite/rel/rules/IntersectToDistinctRule.java |  2 +-
 .../rel/rules/JoinAddRedundantSemiJoinRule.java    |  3 +-
 .../calcite/rel/rules/JoinAssociateRule.java       |  2 +-
 .../apache/calcite/rel/rules/JoinCommuteRule.java  |  2 +-
 .../rel/rules/JoinProjectTransposeRule.java        |  2 +-
 .../calcite/rel/rules/JoinPushExpressionsRule.java |  2 +-
 .../calcite/rel/rules/JoinPushThroughJoinRule.java |  2 +-
 .../rules/JoinPushTransitivePredicatesRule.java    |  2 +-
 .../calcite/rel/rules/JoinToCorrelateRule.java     |  2 +-
 .../calcite/rel/rules/JoinToMultiJoinRule.java     |  2 +-
 .../calcite/rel/rules/JoinUnionTransposeRule.java  |  2 +-
 .../calcite/rel/rules/LoptOptimizeJoinRule.java    |  2 +-
 .../org/apache/calcite/rel/rules/MatchRule.java    |  2 +-
 .../rel/rules/MaterializedViewFilterScanRule.java  |  2 +-
 .../rel/rules/MultiJoinOptimizeBushyRule.java      |  2 +-
 .../calcite/rel/rules/ProjectCalcMergeRule.java    |  2 +-
 .../rel/rules/ProjectCorrelateTransposeRule.java   |  2 +-
 .../rel/rules/ProjectFilterTransposeRule.java      |  2 +-
 .../rel/rules/ProjectJoinJoinRemoveRule.java       |  1 -
 .../calcite/rel/rules/ProjectJoinRemoveRule.java   |  1 -
 .../rel/rules/ProjectJoinTransposeRule.java        |  2 +-
 .../apache/calcite/rel/rules/ProjectMergeRule.java |  8 ++++-
 .../rel/rules/ProjectMultiJoinMergeRule.java       |  2 +-
 .../calcite/rel/rules/ProjectRemoveRule.java       |  1 -
 .../rel/rules/ProjectSetOpTransposeRule.java       |  2 +-
 .../rel/rules/ProjectSortTransposeRule.java        |  2 +-
 .../calcite/rel/rules/ProjectToCalcRule.java       |  2 +-
 .../calcite/rel/rules/ProjectToWindowRule.java     |  2 +-
 .../rel/rules/ProjectWindowTransposeRule.java      |  2 +-
 .../apache/calcite/rel/rules/PruneEmptyRules.java  |  1 -
 .../calcite/rel/rules/ReduceDecimalsRule.java      |  2 +-
 .../calcite/rel/rules/ReduceExpressionsRule.java   |  1 -
 .../rel/rules/SemiJoinFilterTransposeRule.java     |  2 +-
 .../rel/rules/SemiJoinJoinTransposeRule.java       |  2 +-
 .../rel/rules/SemiJoinProjectTransposeRule.java    |  2 +-
 .../calcite/rel/rules/SemiJoinRemoveRule.java      |  2 +-
 .../org/apache/calcite/rel/rules/SemiJoinRule.java |  2 +-
 .../apache/calcite/rel/rules/SortJoinCopyRule.java |  2 +-
 .../calcite/rel/rules/SortJoinTransposeRule.java   |  2 +-
 .../rel/rules/SortProjectTransposeRule.java        |  2 +-
 .../rel/rules/SortRemoveConstantKeysRule.java      |  1 -
 .../apache/calcite/rel/rules/SortRemoveRule.java   |  2 +-
 .../calcite/rel/rules/SortUnionTransposeRule.java  |  2 +-
 .../calcite/rel/rules/SubQueryRemoveRule.java      |  2 +-
 .../{plan => rel/rules}/SubstitutionRule.java      |  4 +--
 .../apache/calcite/rel/rules/TableScanRule.java    |  2 +-
 .../calcite/rel/rules/TransformationRule.java      | 40 ++++++++++++++++++++++
 .../calcite/rel/rules/UnionEliminatorRule.java     |  1 -
 .../apache/calcite/rel/rules/UnionMergeRule.java   |  2 +-
 .../rel/rules/UnionPullUpConstantsRule.java        |  2 +-
 .../calcite/rel/rules/UnionToDistinctRule.java     |  2 +-
 .../apache/calcite/rel/rules/ValuesReduceRule.java |  2 +-
 .../org/apache/calcite/rel/stream/StreamRules.java | 17 ++++-----
 .../org/apache/calcite/test/RelOptRulesTest.java   | 24 +++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 24 +++++++++++++
 92 files changed, 201 insertions(+), 97 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/RuleQueue.java 
b/core/src/main/java/org/apache/calcite/plan/volcano/RuleQueue.java
index 7b651cd..06b9587 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/RuleQueue.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/RuleQueue.java
@@ -17,8 +17,8 @@
 package org.apache.calcite.plan.volcano;
 
 import org.apache.calcite.plan.RelOptRuleOperand;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.rules.SubstitutionRule;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.trace.CalciteTrace;
 
diff --git 
a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java 
b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
index 20a1ef0..d0adf48 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
@@ -37,6 +37,7 @@ import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTrait;
 import org.apache.calcite.plan.RelTraitDef;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.PhysicalNode;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.convert.Converter;
 import org.apache.calcite.rel.convert.ConverterRule;
@@ -46,6 +47,7 @@ import 
org.apache.calcite.rel.metadata.JaninoRelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.TransformationRule;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.sql.SqlExplainLevel;
@@ -410,6 +412,10 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
     for (RelOptRuleOperand operand : rule.getOperands()) {
       for (Class<? extends RelNode> subClass
           : subClasses(operand.getMatchedClass())) {
+        if (PhysicalNode.class.isAssignableFrom(subClass)
+            && rule instanceof TransformationRule) {
+          continue;
+        }
         classOperands.put(subClass, operand);
       }
     }
@@ -456,10 +462,14 @@ public class VolcanoPlanner extends AbstractRelOptPlanner 
{
   @Override protected void onNewClass(RelNode node) {
     super.onNewClass(node);
 
+    final boolean isPhysical = node instanceof PhysicalNode;
     // Create mappings so that instances of this class will match existing
     // operands.
     final Class<? extends RelNode> clazz = node.getClass();
     for (RelOptRule rule : mapDescToRule.values()) {
+      if (isPhysical && rule instanceof TransformationRule) {
+        continue;
+      }
       for (RelOptRuleOperand operand : rule.getOperands()) {
         if (operand.getMatchedClass().isAssignableFrom(clazz)) {
           classOperands.put(clazz, operand);
diff --git 
a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRuleCall.java 
b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRuleCall.java
index 2ac4f0e..189a47a 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRuleCall.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRuleCall.java
@@ -21,8 +21,8 @@ import org.apache.calcite.plan.RelOptListener;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptRuleOperand;
 import org.apache.calcite.plan.RelOptRuleOperandChildPolicy;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.rules.SubstitutionRule;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AbstractJoinExtractFilterRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AbstractJoinExtractFilterRule.java
index 401bd62..4582f63 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AbstractJoinExtractFilterRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AbstractJoinExtractFilterRule.java
@@ -38,7 +38,8 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * <p>The constructor is parameterized to allow any sub-class of
  * {@link org.apache.calcite.rel.core.Join}.</p>
  */
-public abstract class AbstractJoinExtractFilterRule extends RelOptRule {
+public abstract class AbstractJoinExtractFilterRule extends RelOptRule
+    implements TransformationRule {
   /** Creates an AbstractJoinExtractFilterRule. */
   protected AbstractJoinExtractFilterRule(RelOptRuleOperand operand,
       RelBuilderFactory relBuilderFactory, String description) {
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateCaseToFilterRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateCaseToFilterRule.java
index 92d46b3..a71ceba 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateCaseToFilterRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateCaseToFilterRule.java
@@ -62,7 +62,8 @@ import javax.annotation.Nullable;
  *   FROM Emp</code>
  * </blockquote>
  */
-public class AggregateCaseToFilterRule extends RelOptRule {
+public class AggregateCaseToFilterRule extends RelOptRule
+    implements TransformationRule {
   public static final AggregateCaseToFilterRule INSTANCE =
       new AggregateCaseToFilterRule(RelFactories.LOGICAL_BUILDER, null);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
index 57c50cb..62c9a54 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
@@ -77,7 +77,8 @@ import java.util.stream.Stream;
  * the rule creates separate {@code Aggregate}s and combines using a
  * {@link org.apache.calcite.rel.core.Join}.
  */
-public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
+public final class AggregateExpandDistinctAggregatesRule extends RelOptRule
+    implements TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   /** The default instance of the rule; operates only on logical expressions. 
*/
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
index 7303ba0..1a81683 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
@@ -48,7 +48,8 @@ import java.util.List;
  * <p>To prevent cycles, this rule will not extract a {@code Project} if the
  * {@code Aggregate}s input is already a {@code Project}.
  */
-public class AggregateExtractProjectRule extends RelOptRule {
+public class AggregateExtractProjectRule extends RelOptRule
+    implements TransformationRule {
 
   /**
    * Creates an AggregateExtractProjectRule.
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateFilterTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateFilterTransposeRule.java
index 79d9f07..1af63db 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateFilterTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateFilterTransposeRule.java
@@ -55,7 +55,8 @@ import java.util.List;
  *
  * @see org.apache.calcite.rel.rules.FilterAggregateTransposeRule
  */
-public class AggregateFilterTransposeRule extends RelOptRule {
+public class AggregateFilterTransposeRule extends RelOptRule
+    implements TransformationRule {
   public static final AggregateFilterTransposeRule INSTANCE =
       new AggregateFilterTransposeRule();
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinJoinRemoveRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinJoinRemoveRule.java
index bf5344f..ab4c4b2 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinJoinRemoveRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinJoinRemoveRule.java
@@ -66,7 +66,8 @@ import java.util.Set;
  * on s.product_id = pc.product_id</pre></blockquote>
  *
  */
-public class AggregateJoinJoinRemoveRule extends RelOptRule {
+public class AggregateJoinJoinRemoveRule extends RelOptRule
+    implements TransformationRule {
   public static final AggregateJoinJoinRemoveRule INSTANCE =
       new AggregateJoinJoinRemoveRule(LogicalAggregate.class,
           LogicalJoin.class, RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinRemoveRule.java
index dc7d467..cefd3df 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinRemoveRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinRemoveRule.java
@@ -58,7 +58,7 @@ import java.util.Set;
  * <pre>select distinct s.product_id from sales as s</pre></blockquote>
  *
  */
-public class AggregateJoinRemoveRule extends RelOptRule {
+public class AggregateJoinRemoveRule extends RelOptRule implements 
TransformationRule {
   public static final AggregateJoinRemoveRule INSTANCE =
       new AggregateJoinRemoveRule(LogicalAggregate.class, LogicalJoin.class,
           RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinTransposeRule.java
index 60daf7a..79b5155 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateJoinTransposeRule.java
@@ -61,7 +61,7 @@ import java.util.TreeMap;
  * {@link org.apache.calcite.rel.core.Aggregate}
  * past a {@link org.apache.calcite.rel.core.Join}.
  */
-public class AggregateJoinTransposeRule extends RelOptRule {
+public class AggregateJoinTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final AggregateJoinTransposeRule INSTANCE =
       new AggregateJoinTransposeRule(LogicalAggregate.class, LogicalJoin.class,
           RelFactories.LOGICAL_BUILDER, false);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateMergeRule.java
index e1501bd..043036a 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateMergeRule.java
@@ -46,7 +46,7 @@ import java.util.Objects;
  * MAX of MAX becomes MAX; MIN of MIN becomes MIN. AVG of AVG would not
  * match, nor would COUNT of COUNT.
  */
-public class AggregateMergeRule extends RelOptRule {
+public class AggregateMergeRule extends RelOptRule implements 
TransformationRule {
   public static final AggregateMergeRule INSTANCE =
       new AggregateMergeRule();
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectMergeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectMergeRule.java
index fa8b46a..acb25d7 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectMergeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectMergeRule.java
@@ -52,7 +52,7 @@ import java.util.Set;
  * <p>In some cases, this rule has the effect of trimming: the aggregate will
  * use fewer columns than the project did.
  */
-public class AggregateProjectMergeRule extends RelOptRule {
+public class AggregateProjectMergeRule extends RelOptRule implements 
TransformationRule {
   public static final AggregateProjectMergeRule INSTANCE =
       new AggregateProjectMergeRule(Aggregate.class, Project.class, 
RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectPullUpConstantsRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectPullUpConstantsRule.java
index decee3a..2e1537f 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectPullUpConstantsRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectPullUpConstantsRule.java
@@ -57,7 +57,8 @@ import java.util.TreeMap;
  * reduced aggregate. If those constants are not used, another rule will remove
  * them from the project.
  */
-public class AggregateProjectPullUpConstantsRule extends RelOptRule {
+public class AggregateProjectPullUpConstantsRule extends RelOptRule
+    implements TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   /** The singleton. */
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsRule.java
index 1fa8023..ecfd34d 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsRule.java
@@ -92,7 +92,8 @@ import java.util.Objects;
  * forms like {@code COUNT(x)}, the rule gathers common sub-expressions as it
  * goes.
  */
-public class AggregateReduceFunctionsRule extends RelOptRule {
+public class AggregateReduceFunctionsRule extends RelOptRule
+    implements TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   /** The singleton. */
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateRemoveRule.java
index bada07c..9b347f6 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateRemoveRule.java
@@ -18,7 +18,6 @@ package org.apache.calcite.rel.rules;
 
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
@@ -124,7 +123,7 @@ public class AggregateRemoveRule extends RelOptRule 
implements SubstitutionRule
       // aggregate functions, add a project for the same effect.
       relBuilder.project(relBuilder.fields(aggregate.getGroupSet()));
     }
-    call.getPlanner().setImportance(aggregate, 0d);
+    call.getPlanner().prune(aggregate);
     call.transformTo(relBuilder.build());
   }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
index da5998a..167cd9b 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
@@ -59,7 +59,7 @@ import java.util.List;
  * <p>This pattern indicates that an aggregate table may exist. The rule asks
  * the star table for an aggregate table at the required level of aggregation.
  */
-public class AggregateStarTableRule extends RelOptRule {
+public class AggregateStarTableRule extends RelOptRule implements 
TransformationRule {
   public static final AggregateStarTableRule INSTANCE =
       new AggregateStarTableRule(
           operandJ(Aggregate.class, null, Aggregate::isSimple,
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionAggregateRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionAggregateRule.java
index e9d8393..53c1bc7 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionAggregateRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionAggregateRule.java
@@ -39,7 +39,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * {@link org.apache.calcite.rel.core.Union}s
  * still have only two inputs.
  */
-public class AggregateUnionAggregateRule extends RelOptRule {
+public class AggregateUnionAggregateRule extends RelOptRule implements 
TransformationRule {
   /** Instance that matches an {@code Aggregate} as the left input of
    * {@code Union}. */
   public static final AggregateUnionAggregateRule AGG_ON_FIRST_INPUT =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionTransposeRule.java
index 6619713..18766dd 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionTransposeRule.java
@@ -53,7 +53,7 @@ import java.util.Map;
  * {@link org.apache.calcite.rel.core.Aggregate}
  * past a non-distinct {@link org.apache.calcite.rel.core.Union}.
  */
-public class AggregateUnionTransposeRule extends RelOptRule {
+public class AggregateUnionTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final AggregateUnionTransposeRule INSTANCE =
       new AggregateUnionTransposeRule(LogicalAggregate.class,
           LogicalUnion.class, RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
index c308c1c..4424155 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
@@ -18,7 +18,6 @@ package org.apache.calcite.rel.rules;
 
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.core.RelFactories;
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/CalcMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/CalcMergeRule.java
index fc4a2e8..a65f81e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CalcMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CalcMergeRule.java
@@ -35,7 +35,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * {@link org.apache.calcite.rel.logical.LogicalCalc}, but expressed in terms 
of
  * the lower {@link org.apache.calcite.rel.logical.LogicalCalc}'s inputs.
  */
-public class CalcMergeRule extends RelOptRule {
+public class CalcMergeRule extends RelOptRule implements TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   public static final CalcMergeRule INSTANCE =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/CalcRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/CalcRemoveRule.java
index 73c6209..bbf740a 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CalcRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CalcRemoveRule.java
@@ -18,7 +18,6 @@ package org.apache.calcite.rel.rules;
 
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.logical.LogicalCalc;
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/CalcSplitRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/CalcSplitRule.java
index e9b63f8..9cc1eea 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CalcSplitRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CalcSplitRule.java
@@ -38,7 +38,7 @@ import com.google.common.collect.ImmutableList;
  * specific tasks, such as optimizing before calling an
  * {@link org.apache.calcite.interpreter.Interpreter}.
  */
-public class CalcSplitRule extends RelOptRule {
+public class CalcSplitRule extends RelOptRule implements TransformationRule {
   public static final CalcSplitRule INSTANCE =
       new CalcSplitRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/CoerceInputsRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/CoerceInputsRule.java
index 4209f84..7e477a7 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CoerceInputsRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CoerceInputsRule.java
@@ -33,7 +33,7 @@ import java.util.List;
  * assist operator implementations which impose requirements on their input
  * types.
  */
-public class CoerceInputsRule extends RelOptRule {
+public class CoerceInputsRule extends RelOptRule implements TransformationRule 
{
   //~ Instance fields --------------------------------------------------------
 
   private final Class consumerRelClass;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/DateRangeRules.java 
b/core/src/main/java/org/apache/calcite/rel/rules/DateRangeRules.java
index 9bd4d47..cf1a13a 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/DateRangeRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/DateRangeRules.java
@@ -170,7 +170,7 @@ public abstract class DateRangeRules {
   /** Rule that converts EXTRACT, FLOOR and CEIL in a {@link Filter} into a 
date
    * range. */
   @SuppressWarnings("WeakerAccess")
-  public static class FilterDateRangeRule extends RelOptRule {
+  public static class FilterDateRangeRule extends RelOptRule implements 
TransformationRule {
     public FilterDateRangeRule(RelBuilderFactory relBuilderFactory) {
       super(operandJ(Filter.class, null, FILTER_PREDICATE, any()),
           relBuilderFactory, "FilterDateRangeRule");
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ExchangeRemoveConstantKeysRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ExchangeRemoveConstantKeysRule.java
index 2b08a1e..07af48b 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ExchangeRemoveConstantKeysRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ExchangeRemoveConstantKeysRule.java
@@ -19,7 +19,6 @@ package org.apache.calcite.rel.rules;
 import org.apache.calcite.plan.RelOptPredicateList;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelDistribution;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterAggregateTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterAggregateTransposeRule.java
index 1fc907e..80157f6 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterAggregateTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterAggregateTransposeRule.java
@@ -44,7 +44,7 @@ import java.util.List;
  *
  * @see org.apache.calcite.rel.rules.AggregateFilterTransposeRule
  */
-public class FilterAggregateTransposeRule extends RelOptRule {
+public class FilterAggregateTransposeRule extends RelOptRule implements 
TransformationRule {
 
   /** The default instance of
    * {@link FilterAggregateTransposeRule}.
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterCalcMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterCalcMergeRule.java
index 5c76fcc..f62ee95 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/FilterCalcMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/FilterCalcMergeRule.java
@@ -36,7 +36,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  *
  * @see FilterMergeRule
  */
-public class FilterCalcMergeRule extends RelOptRule {
+public class FilterCalcMergeRule extends RelOptRule implements 
TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   public static final FilterCalcMergeRule INSTANCE =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterCorrelateRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterCorrelateRule.java
index 15175aa..ed7dac5 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/FilterCorrelateRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/FilterCorrelateRule.java
@@ -38,7 +38,7 @@ import java.util.List;
  * Planner rule that pushes a {@link Filter} above a {@link Correlate} into the
  * inputs of the Correlate.
  */
-public class FilterCorrelateRule extends RelOptRule {
+public class FilterCorrelateRule extends RelOptRule implements 
TransformationRule {
 
   public static final FilterCorrelateRule INSTANCE =
       new FilterCorrelateRule(RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
index f016034..5de7796 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
@@ -47,7 +47,7 @@ import static org.apache.calcite.plan.RelOptUtil.conjunctions;
  * Planner rule that pushes filters above and
  * within a join node into the join node and/or its children nodes.
  */
-public abstract class FilterJoinRule extends RelOptRule {
+public abstract class FilterJoinRule extends RelOptRule implements 
TransformationRule {
   /** Predicate that always returns true. With this predicate, every filter
    * will be pushed into the ON clause. */
   public static final Predicate TRUE_PREDICATE = (join, joinType, exp) -> true;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterMergeRule.java
index 1517d2c..c03b9e8 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/FilterMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/FilterMergeRule.java
@@ -19,7 +19,6 @@ package org.apache.calcite.rel.rules;
 import org.apache.calcite.plan.Contexts;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.tools.RelBuilder;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterMultiJoinMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterMultiJoinMergeRule.java
index 57b4c82..3b8f054 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterMultiJoinMergeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterMultiJoinMergeRule.java
@@ -36,7 +36,7 @@ import java.util.List;
  *
  * @see org.apache.calcite.rel.rules.ProjectMultiJoinMergeRule
  */
-public class FilterMultiJoinMergeRule extends RelOptRule {
+public class FilterMultiJoinMergeRule extends RelOptRule implements 
TransformationRule {
   public static final FilterMultiJoinMergeRule INSTANCE =
       new FilterMultiJoinMergeRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
index 12e57f0..7ef6ae5 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
@@ -41,7 +41,7 @@ import java.util.function.Predicate;
  * a {@link org.apache.calcite.rel.core.Filter}
  * past a {@link org.apache.calcite.rel.core.Project}.
  */
-public class FilterProjectTransposeRule extends RelOptRule {
+public class FilterProjectTransposeRule extends RelOptRule implements 
TransformationRule {
   /** The default instance of
    * {@link org.apache.calcite.rel.rules.FilterProjectTransposeRule}.
    *
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterRemoveIsNotDistinctFromRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterRemoveIsNotDistinctFromRule.java
index eff8618..e8db145 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterRemoveIsNotDistinctFromRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterRemoveIsNotDistinctFromRule.java
@@ -37,7 +37,8 @@ import org.apache.calcite.tools.RelBuilderFactory;
  *
  * @see org.apache.calcite.sql.fun.SqlStdOperatorTable#IS_NOT_DISTINCT_FROM
  */
-public final class FilterRemoveIsNotDistinctFromRule extends RelOptRule {
+public final class FilterRemoveIsNotDistinctFromRule extends RelOptRule
+    implements TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   /** The singleton. */
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterSetOpTransposeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterSetOpTransposeRule.java
index 79dd548..d7d8877 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterSetOpTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterSetOpTransposeRule.java
@@ -37,7 +37,7 @@ import java.util.List;
  * Planner rule that pushes a {@link org.apache.calcite.rel.core.Filter}
  * past a {@link org.apache.calcite.rel.core.SetOp}.
  */
-public class FilterSetOpTransposeRule extends RelOptRule {
+public class FilterSetOpTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final FilterSetOpTransposeRule INSTANCE =
       new FilterSetOpTransposeRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterTableFunctionTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterTableFunctionTransposeRule.java
index 02ac662..74b4d71 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterTableFunctionTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterTableFunctionTransposeRule.java
@@ -39,7 +39,8 @@ import java.util.Set;
  * a {@link org.apache.calcite.rel.logical.LogicalFilter}
  * past a {@link org.apache.calcite.rel.logical.LogicalTableFunctionScan}.
  */
-public class FilterTableFunctionTransposeRule extends RelOptRule {
+public class FilterTableFunctionTransposeRule extends RelOptRule
+    implements TransformationRule {
   public static final FilterTableFunctionTransposeRule INSTANCE =
       new FilterTableFunctionTransposeRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterToCalcRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterToCalcRule.java
index b2a7d58..8cd0d62 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/FilterToCalcRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/FilterToCalcRule.java
@@ -42,7 +42,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * {@link org.apache.calcite.rel.logical.LogicalFilter} will eventually be
  * converted by {@link FilterCalcMergeRule}.
  */
-public class FilterToCalcRule extends RelOptRule {
+public class FilterToCalcRule extends RelOptRule implements TransformationRule 
{
   //~ Static fields/initializers ---------------------------------------------
 
   public static final FilterToCalcRule INSTANCE =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/IntersectToDistinctRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/IntersectToDistinctRule.java
index ffd24f9..e76a068 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/IntersectToDistinctRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/IntersectToDistinctRule.java
@@ -66,7 +66,7 @@ import java.math.BigDecimal;
  *
  * @see org.apache.calcite.rel.rules.UnionToDistinctRule
  */
-public class IntersectToDistinctRule extends RelOptRule {
+public class IntersectToDistinctRule extends RelOptRule implements 
TransformationRule {
   public static final IntersectToDistinctRule INSTANCE =
           new IntersectToDistinctRule(LogicalIntersect.class, 
RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinAddRedundantSemiJoinRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinAddRedundantSemiJoinRule.java
index 5cbd49d..032e1a6 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinAddRedundantSemiJoinRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinAddRedundantSemiJoinRule.java
@@ -38,7 +38,8 @@ import com.google.common.collect.ImmutableSet;
  * {@link org.apache.calcite.rel.core.Join}, not just
  * {@link org.apache.calcite.rel.logical.LogicalJoin}.
  */
-public class JoinAddRedundantSemiJoinRule extends RelOptRule {
+public class JoinAddRedundantSemiJoinRule extends RelOptRule
+    implements TransformationRule {
   public static final JoinAddRedundantSemiJoinRule INSTANCE =
       new JoinAddRedundantSemiJoinRule(LogicalJoin.class,
           RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinAssociateRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinAssociateRule.java
index 4488741..e90746e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/JoinAssociateRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/JoinAssociateRule.java
@@ -46,7 +46,7 @@ import java.util.List;
  *
  * @see JoinCommuteRule
  */
-public class JoinAssociateRule extends RelOptRule {
+public class JoinAssociateRule extends RelOptRule implements 
TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   /** The singleton. */
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinCommuteRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinCommuteRule.java
index dfc74fb..b7c8c4e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/JoinCommuteRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/JoinCommuteRule.java
@@ -48,7 +48,7 @@ import java.util.function.Predicate;
  * <p>To preserve the order of columns in the output row, the rule adds a
  * {@link org.apache.calcite.rel.core.Project}.
  */
-public class JoinCommuteRule extends RelOptRule {
+public class JoinCommuteRule extends RelOptRule implements TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   /** Instance of the rule that only swaps inner joins. */
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinProjectTransposeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinProjectTransposeRule.java
index a93fd22..085b3e6 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinProjectTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinProjectTransposeRule.java
@@ -57,7 +57,7 @@ import java.util.List;
  * {@link org.apache.calcite.rel.logical.LogicalProject} doesn't originate from
  * a null generating input in an outer join.
  */
-public class JoinProjectTransposeRule extends RelOptRule {
+public class JoinProjectTransposeRule extends RelOptRule implements 
TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   public static final JoinProjectTransposeRule BOTH_PROJECT =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinPushExpressionsRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinPushExpressionsRule.java
index cbbfe25..9e05043 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinPushExpressionsRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinPushExpressionsRule.java
@@ -35,7 +35,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * "emp.deptno + 1". The resulting join condition is a simple combination
  * of AND, equals, and input fields, plus the remaining non-equal conditions.
  */
-public class JoinPushExpressionsRule extends RelOptRule {
+public class JoinPushExpressionsRule extends RelOptRule implements 
TransformationRule {
 
   public static final JoinPushExpressionsRule INSTANCE =
       new JoinPushExpressionsRule(Join.class, RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinPushThroughJoinRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinPushThroughJoinRule.java
index 93a8ac2..ac205e9 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinPushThroughJoinRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinPushThroughJoinRule.java
@@ -62,7 +62,7 @@ import java.util.List;
  * <p>Before the rule, one join has two conditions and the other has none
  * ({@code ON TRUE}). After the rule, each join has one condition.</p>
  */
-public class JoinPushThroughJoinRule extends RelOptRule {
+public class JoinPushThroughJoinRule extends RelOptRule implements 
TransformationRule {
   /** Instance of the rule that works on logical joins only, and pushes to the
    * right. */
   public static final RelOptRule RIGHT =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinPushTransitivePredicatesRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinPushTransitivePredicatesRule.java
index f0c78dc..efa280f 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinPushTransitivePredicatesRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinPushTransitivePredicatesRule.java
@@ -39,7 +39,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * returns them in a {@link org.apache.calcite.plan.RelOptPredicateList}
  * and applies them appropriately.
  */
-public class JoinPushTransitivePredicatesRule extends RelOptRule {
+public class JoinPushTransitivePredicatesRule extends RelOptRule implements 
TransformationRule {
   /** The singleton. */
   public static final JoinPushTransitivePredicatesRule INSTANCE =
       new JoinPushTransitivePredicatesRule(Join.class,
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinToCorrelateRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinToCorrelateRule.java
index 5717865..5c747fa 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/JoinToCorrelateRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/JoinToCorrelateRule.java
@@ -56,7 +56,7 @@ import org.apache.calcite.util.ImmutableBitSet;
  * <p>would require emitting a NULL emp row if a certain department contained 
no
  * employees, and Correlator cannot do that.</p>
  */
-public class JoinToCorrelateRule extends RelOptRule {
+public class JoinToCorrelateRule extends RelOptRule implements 
TransformationRule {
 
   //~ Static fields/initializers ---------------------------------------------
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinToMultiJoinRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinToMultiJoinRule.java
index efb73c4..a6ea0cf 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/JoinToMultiJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/JoinToMultiJoinRule.java
@@ -102,7 +102,7 @@ import java.util.Map;
  * @see org.apache.calcite.rel.rules.FilterMultiJoinMergeRule
  * @see org.apache.calcite.rel.rules.ProjectMultiJoinMergeRule
  */
-public class JoinToMultiJoinRule extends RelOptRule {
+public class JoinToMultiJoinRule extends RelOptRule implements 
TransformationRule {
   public static final JoinToMultiJoinRule INSTANCE =
       new JoinToMultiJoinRule(LogicalJoin.class, RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinUnionTransposeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinUnionTransposeRule.java
index d788ede..1bceae5 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinUnionTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinUnionTransposeRule.java
@@ -34,7 +34,7 @@ import java.util.List;
  * {@link org.apache.calcite.rel.core.Join}
  * past a non-distinct {@link org.apache.calcite.rel.core.Union}.
  */
-public class JoinUnionTransposeRule extends RelOptRule {
+public class JoinUnionTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final JoinUnionTransposeRule LEFT_UNION =
       new JoinUnionTransposeRule(
           operand(Join.class,
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
index 9d333ca..d0d0b9c 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
@@ -65,7 +65,7 @@ import java.util.TreeSet;
  * {@link org.apache.calcite.rel.logical.LogicalProject}
  * ({@link MultiJoin}).
  */
-public class LoptOptimizeJoinRule extends RelOptRule {
+public class LoptOptimizeJoinRule extends RelOptRule implements 
TransformationRule {
   public static final LoptOptimizeJoinRule INSTANCE =
       new LoptOptimizeJoinRule(RelFactories.LOGICAL_BUILDER);
 
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/MatchRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/MatchRule.java
index 71f8a70..2816915 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/MatchRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/MatchRule.java
@@ -26,7 +26,7 @@ import org.apache.calcite.rel.logical.LogicalMatch;
  * {@link LogicalMatch} to the result
  * of calling {@link LogicalMatch#copy}.
  */
-public class MatchRule extends RelOptRule {
+public class MatchRule extends RelOptRule implements TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   public static final MatchRule INSTANCE = new MatchRule();
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/MaterializedViewFilterScanRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/MaterializedViewFilterScanRule.java
index fa87a48..d42dae6 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/MaterializedViewFilterScanRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/MaterializedViewFilterScanRule.java
@@ -41,7 +41,7 @@ import java.util.List;
  * on a {@link org.apache.calcite.rel.core.TableScan}
  * to a {@link org.apache.calcite.rel.core.Filter} on Materialized View
  */
-public class MaterializedViewFilterScanRule extends RelOptRule {
+public class MaterializedViewFilterScanRule extends RelOptRule implements 
TransformationRule {
   public static final MaterializedViewFilterScanRule INSTANCE =
       new MaterializedViewFilterScanRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/MultiJoinOptimizeBushyRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/MultiJoinOptimizeBushyRule.java
index afc0cba..70059ac 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/MultiJoinOptimizeBushyRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/MultiJoinOptimizeBushyRule.java
@@ -68,7 +68,7 @@ import static 
org.apache.calcite.util.mapping.Mappings.TargetMapping;
  *       e.g. {@code t0.c1 = t1.c1 and t1.c2 = t0.c3}
  * </ol>
  */
-public class MultiJoinOptimizeBushyRule extends RelOptRule {
+public class MultiJoinOptimizeBushyRule extends RelOptRule implements 
TransformationRule {
   public static final MultiJoinOptimizeBushyRule INSTANCE =
       new MultiJoinOptimizeBushyRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectCalcMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectCalcMergeRule.java
index 598c469..d2423c0 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectCalcMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectCalcMergeRule.java
@@ -42,7 +42,7 @@ import org.apache.calcite.util.Pair;
  *
  * @see FilterCalcMergeRule
  */
-public class ProjectCalcMergeRule extends RelOptRule {
+public class ProjectCalcMergeRule extends RelOptRule implements 
TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   public static final ProjectCalcMergeRule INSTANCE =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectCorrelateTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectCorrelateTransposeRule.java
index f14401c..5888d86 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectCorrelateTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectCorrelateTransposeRule.java
@@ -44,7 +44,7 @@ import java.util.Map;
 /**
  * Push Project under Correlate to apply on Correlate's left and right child
  */
-public class ProjectCorrelateTransposeRule extends RelOptRule {
+public class ProjectCorrelateTransposeRule extends RelOptRule implements 
TransformationRule {
 
   public static final ProjectCorrelateTransposeRule INSTANCE =
       new ProjectCorrelateTransposeRule(expr -> !(expr instanceof RexOver),
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectFilterTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectFilterTransposeRule.java
index 47a5803..c8202ec 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectFilterTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectFilterTransposeRule.java
@@ -34,7 +34,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * Planner rule that pushes a {@link org.apache.calcite.rel.core.Project}
  * past a {@link org.apache.calcite.rel.core.Filter}.
  */
-public class ProjectFilterTransposeRule extends RelOptRule {
+public class ProjectFilterTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final ProjectFilterTransposeRule INSTANCE =
       new ProjectFilterTransposeRule(LogicalProject.class, LogicalFilter.class,
           RelFactories.LOGICAL_BUILDER, expr -> false);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinJoinRemoveRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinJoinRemoveRule.java
index 16c2f4b..b225d1a 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinJoinRemoveRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinJoinRemoveRule.java
@@ -19,7 +19,6 @@ package org.apache.calcite.rel.rules;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinRelType;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinRemoveRule.java
index 9174c7a..0927828 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinRemoveRule.java
@@ -19,7 +19,6 @@ package org.apache.calcite.rel.rules;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinRelType;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java
index 96d2535..b975a3b 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java
@@ -48,7 +48,7 @@ import java.util.List;
  * by splitting the projection into a projection on top of each child of
  * the join.
  */
-public class ProjectJoinTransposeRule extends RelOptRule {
+public class ProjectJoinTransposeRule extends RelOptRule implements 
TransformationRule {
   /**
    * A instance for ProjectJoinTransposeRule that pushes a
    * {@link org.apache.calcite.rel.logical.LogicalProject}
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
index 818f7b5..9e44d4b 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
@@ -36,7 +36,7 @@ import java.util.List;
  * another {@link org.apache.calcite.rel.core.Project},
  * provided the projects aren't projecting identical sets of input references.
  */
-public class ProjectMergeRule extends RelOptRule {
+public class ProjectMergeRule extends RelOptRule implements TransformationRule 
{
   /** Default amount by which complexity is allowed to increase. */
   public static final int DEFAULT_BLOAT = 100;
 
@@ -81,6 +81,12 @@ public class ProjectMergeRule extends RelOptRule {
 
   //~ Methods ----------------------------------------------------------------
 
+  @Override public boolean matches(RelOptRuleCall call) {
+    final Project topProject = call.rel(0);
+    final Project bottomProject = call.rel(1);
+    return topProject.getConvention() == bottomProject.getConvention();
+  }
+
   public void onMatch(RelOptRuleCall call) {
     final Project topProject = call.rel(0);
     final Project bottomProject = call.rel(1);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMultiJoinMergeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMultiJoinMergeRule.java
index 20db7e3..322a795 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMultiJoinMergeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMultiJoinMergeRule.java
@@ -33,7 +33,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  *
  * @see org.apache.calcite.rel.rules.FilterMultiJoinMergeRule
  */
-public class ProjectMultiJoinMergeRule extends RelOptRule {
+public class ProjectMultiJoinMergeRule extends RelOptRule implements 
TransformationRule {
   public static final ProjectMultiJoinMergeRule INSTANCE =
       new ProjectMultiJoinMergeRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectRemoveRule.java
index a1435fd..ba8ff2e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectRemoveRule.java
@@ -18,7 +18,6 @@ package org.apache.calcite.rel.rules;
 
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.RelFactories;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectSetOpTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectSetOpTransposeRule.java
index e1933d1..6b89dc3 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectSetOpTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectSetOpTransposeRule.java
@@ -39,7 +39,7 @@ import java.util.List;
  * only the {@link RexInputRef}s referenced in the original
  * {@code LogicalProject}.
  */
-public class ProjectSetOpTransposeRule extends RelOptRule {
+public class ProjectSetOpTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final ProjectSetOpTransposeRule INSTANCE =
       new ProjectSetOpTransposeRule(expr -> !(expr instanceof RexOver),
           RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectSortTransposeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectSortTransposeRule.java
index 844b051..e380db3 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectSortTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectSortTransposeRule.java
@@ -34,7 +34,7 @@ import com.google.common.collect.ImmutableList;
  *
  * @see org.apache.calcite.rel.rules.SortProjectTransposeRule
  */
-public class ProjectSortTransposeRule extends RelOptRule {
+public class ProjectSortTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final ProjectSortTransposeRule INSTANCE =
       new ProjectSortTransposeRule(Project.class, Sort.class,
           RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectToCalcRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectToCalcRule.java
index 5c77191..9997265 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectToCalcRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectToCalcRule.java
@@ -39,7 +39,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  *
  * @see FilterToCalcRule
  */
-public class ProjectToCalcRule extends RelOptRule {
+public class ProjectToCalcRule extends RelOptRule implements 
TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   public static final ProjectToCalcRule INSTANCE =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectToWindowRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectToWindowRule.java
index f6fda46..53e3235 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectToWindowRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectToWindowRule.java
@@ -72,7 +72,7 @@ import java.util.Set;
  * <p>There is also a variant that matches
  * {@link org.apache.calcite.rel.core.Calc} rather than {@code Project}.
  */
-public abstract class ProjectToWindowRule extends RelOptRule {
+public abstract class ProjectToWindowRule extends RelOptRule implements 
TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   public static final ProjectToWindowRule INSTANCE =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
index d9a896c..edd688f 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
@@ -46,7 +46,7 @@ import java.util.List;
  * a {@link org.apache.calcite.rel.logical.LogicalProject}
  * past a {@link org.apache.calcite.rel.logical.LogicalWindow}.
  */
-public class ProjectWindowTransposeRule extends RelOptRule {
+public class ProjectWindowTransposeRule extends RelOptRule implements 
TransformationRule {
   /** The default instance of
    * {@link org.apache.calcite.rel.rules.ProjectWindowTransposeRule}. */
   public static final ProjectWindowTransposeRule INSTANCE =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java 
b/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
index 88d408d..61e1df2 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
@@ -21,7 +21,6 @@ import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptRuleOperand;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.plan.hep.HepRelVertex;
 import org.apache.calcite.plan.volcano.RelSubset;
 import org.apache.calcite.rel.RelNode;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ReduceDecimalsRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ReduceDecimalsRule.java
index c20c690..0a902a7 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ReduceDecimalsRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ReduceDecimalsRule.java
@@ -66,7 +66,7 @@ import static org.apache.calcite.util.Static.RESOURCE;
  * rule is optionally applied, in order to support the situation in which we
  * would like to push down decimal operations to an external database.
  */
-public class ReduceDecimalsRule extends RelOptRule {
+public class ReduceDecimalsRule extends RelOptRule implements 
TransformationRule {
   public static final ReduceDecimalsRule INSTANCE =
       new ReduceDecimalsRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
index a4c019b..2725fbc 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
@@ -21,7 +21,6 @@ import org.apache.calcite.plan.RelOptPredicateList;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinFilterTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinFilterTransposeRule.java
index 1e15233..977d228 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinFilterTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinFilterTransposeRule.java
@@ -40,7 +40,7 @@ import com.google.common.collect.ImmutableSet;
  *
  * @see SemiJoinProjectTransposeRule
  */
-public class SemiJoinFilterTransposeRule extends RelOptRule {
+public class SemiJoinFilterTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final SemiJoinFilterTransposeRule INSTANCE =
       new SemiJoinFilterTransposeRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinJoinTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinJoinTransposeRule.java
index 824d5e6..db214fb 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinJoinTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinJoinTransposeRule.java
@@ -49,7 +49,7 @@ import java.util.List;
  * first or second conversion is applied depends on which operands actually
  * participate in the semi-join.</p>
  */
-public class SemiJoinJoinTransposeRule extends RelOptRule {
+public class SemiJoinJoinTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final SemiJoinJoinTransposeRule INSTANCE =
       new SemiJoinJoinTransposeRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
index e786d8a..86347f9 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
@@ -53,7 +53,7 @@ import java.util.List;
  *
  * @see org.apache.calcite.rel.rules.SemiJoinFilterTransposeRule
  */
-public class SemiJoinProjectTransposeRule extends RelOptRule {
+public class SemiJoinProjectTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final SemiJoinProjectTransposeRule INSTANCE =
       new SemiJoinProjectTransposeRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRemoveRule.java
index b8f7801..ae03f26 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRemoveRule.java
@@ -33,7 +33,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * <p>It should only be enabled if all SemiJoins in the plan are advisory; that
  * is, they can be safely dropped without affecting the semantics of the query.
  */
-public class SemiJoinRemoveRule extends RelOptRule {
+public class SemiJoinRemoveRule extends RelOptRule implements 
TransformationRule {
   public static final SemiJoinRemoveRule INSTANCE =
       new SemiJoinRemoveRule(RelFactories.LOGICAL_BUILDER);
 
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
index 8bdb211..bfec750 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
@@ -42,7 +42,7 @@ import java.util.function.Predicate;
  * {@link org.apache.calcite.rel.core.Join} on top of a
  * {@link org.apache.calcite.rel.logical.LogicalAggregate}.
  */
-public abstract class SemiJoinRule extends RelOptRule {
+public abstract class SemiJoinRule extends RelOptRule implements 
TransformationRule {
   private static final Predicate<Join> NOT_GENERATE_NULLS_ON_LEFT =
       join -> !join.getJoinType().generatesNullsOnLeft();
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SortJoinCopyRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SortJoinCopyRule.java
index ca9b4d5..3945d74 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SortJoinCopyRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SortJoinCopyRule.java
@@ -48,7 +48,7 @@ import java.util.List;
  * sorted inputs; and allowing the sort to be performed on a possibly smaller
  * result.
  */
-public class SortJoinCopyRule extends RelOptRule {
+public class SortJoinCopyRule extends RelOptRule implements TransformationRule 
{
 
   public static final SortJoinCopyRule INSTANCE =
       new SortJoinCopyRule(LogicalSort.class,
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SortJoinTransposeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SortJoinTransposeRule.java
index 66c8d7f..8f8e1df 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SortJoinTransposeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SortJoinTransposeRule.java
@@ -42,7 +42,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * However, an extension for full outer joins for this rule could be 
envisioned.
  * Special attention should be paid to null values for correctness issues.
  */
-public class SortJoinTransposeRule extends RelOptRule {
+public class SortJoinTransposeRule extends RelOptRule implements 
TransformationRule {
 
   public static final SortJoinTransposeRule INSTANCE =
       new SortJoinTransposeRule(LogicalSort.class,
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java
index 9a98c6c..1a2c2b4 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java
@@ -53,7 +53,7 @@ import java.util.Objects;
  *
  * @see org.apache.calcite.rel.rules.ProjectSortTransposeRule
  */
-public class SortProjectTransposeRule extends RelOptRule {
+public class SortProjectTransposeRule extends RelOptRule implements 
TransformationRule {
   public static final SortProjectTransposeRule INSTANCE =
       new SortProjectTransposeRule(Sort.class, LogicalProject.class,
           RelFactories.LOGICAL_BUILDER, null);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveConstantKeysRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveConstantKeysRule.java
index 210e77d..2db5195 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveConstantKeysRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveConstantKeysRule.java
@@ -19,7 +19,6 @@ package org.apache.calcite.rel.rules;
 import org.apache.calcite.plan.RelOptPredicateList;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.RelCollationTraitDef;
 import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveRule.java
index 15b97f1..860e666 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveRule.java
@@ -31,7 +31,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  *
  * <p>Requires {@link RelCollationTraitDef}.
  */
-public class SortRemoveRule extends RelOptRule {
+public class SortRemoveRule extends RelOptRule implements TransformationRule {
   public static final SortRemoveRule INSTANCE =
       new SortRemoveRule(RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SortUnionTransposeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SortUnionTransposeRule.java
index 7e91743..6d512be 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SortUnionTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SortUnionTransposeRule.java
@@ -34,7 +34,7 @@ import java.util.List;
  * {@link org.apache.calcite.rel.core.Union}.
  *
  */
-public class SortUnionTransposeRule extends RelOptRule {
+public class SortUnionTransposeRule extends RelOptRule implements 
TransformationRule {
 
   /** Rule instance for Union implementation that does not preserve the
    * ordering of its inputs. Thus, it makes no sense to match this rule
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
index ee30dfc..065ff4c 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
@@ -65,7 +65,7 @@ import java.util.stream.Collectors;
  * the rewrite, and the product of the rewrite will be a {@link Correlate}.
  * The Correlate can be removed using {@link RelDecorrelator}.
  */
-public abstract class SubQueryRemoveRule extends RelOptRule {
+public abstract class SubQueryRemoveRule extends RelOptRule implements 
TransformationRule {
   public static final SubQueryRemoveRule PROJECT =
       new SubQueryProjectRemoveRule(RelFactories.LOGICAL_BUILDER);
 
diff --git a/core/src/main/java/org/apache/calcite/plan/SubstitutionRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SubstitutionRule.java
similarity index 93%
rename from core/src/main/java/org/apache/calcite/plan/SubstitutionRule.java
rename to core/src/main/java/org/apache/calcite/rel/rules/SubstitutionRule.java
index 6960f49..6f720fa 100644
--- a/core/src/main/java/org/apache/calcite/plan/SubstitutionRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SubstitutionRule.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.calcite.plan;
+package org.apache.calcite.rel.rules;
 
 /**
  * A rule that implements this interface indicates that the new RelNode
@@ -22,7 +22,7 @@ package org.apache.calcite.plan;
  * be executed first until they are done. The execution order of
  * substitution rules depends on the match order.
  */
-public interface SubstitutionRule {
+public interface SubstitutionRule extends TransformationRule {
 
   /**
    * Whether the planner should automatically prune old node when
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/TableScanRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/TableScanRule.java
index 872325c..0a60ec0 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/TableScanRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/TableScanRule.java
@@ -34,7 +34,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * has called {@link RelOptTable#toRel(RelOptTable.ToRelContext)}.
  */
 @Deprecated // to be removed before 2.0
-public class TableScanRule extends RelOptRule {
+public class TableScanRule extends RelOptRule implements TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   public static final TableScanRule INSTANCE =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/TransformationRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/TransformationRule.java
new file mode 100644
index 0000000..e5095be
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/TransformationRule.java
@@ -0,0 +1,40 @@
+/*
+ * 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.rel.rules;
+
+import org.apache.calcite.plan.hep.HepPlanner;
+import org.apache.calcite.plan.volcano.VolcanoPlanner;
+import org.apache.calcite.rel.PhysicalNode;
+
+/**
+ * Logical transformation rule, only logical operator can be rule operand,
+ * and only generate logical alternatives. It is only visible to
+ * {@link VolcanoPlanner}, {@link HepPlanner} will ignore this interface.
+ * That means, in {@link HepPlanner}, the rule that implements
+ * {@link TransformationRule} can still match with physical operator of
+ * {@link PhysicalNode} and generate physical alternatives.
+ *
+ * <p>But in {@link VolcanoPlanner}, {@link TransformationRule} doesn't match
+ * with physical operator that implements {@link PhysicalNode}. It is not
+ * allowed to generate physical operators in {@link TransformationRule},
+ * unless you are using it in {@link HepPlanner}.</p>
+ *
+ * @see VolcanoPlanner
+ * @see SubstitutionRule
+ */
+public interface TransformationRule {
+}
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/UnionEliminatorRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/UnionEliminatorRule.java
index 006d6fc..bcbf00e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/UnionEliminatorRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/UnionEliminatorRule.java
@@ -18,7 +18,6 @@ package org.apache.calcite.rel.rules;
 
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.SubstitutionRule;
 import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.logical.LogicalUnion;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/UnionMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/UnionMergeRule.java
index 34ccb3f..c84994c 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/UnionMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/UnionMergeRule.java
@@ -39,7 +39,7 @@ import org.apache.calcite.util.Util;
  * <p>Originally written for {@link Union} (hence the name),
  * but now also applies to {@link Intersect}.
  */
-public class UnionMergeRule extends RelOptRule {
+public class UnionMergeRule extends RelOptRule implements TransformationRule {
   public static final UnionMergeRule INSTANCE =
       new UnionMergeRule(LogicalUnion.class, "UnionMergeRule",
           RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/UnionPullUpConstantsRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/UnionPullUpConstantsRule.java
index 5f12360..2180612 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/UnionPullUpConstantsRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/UnionPullUpConstantsRule.java
@@ -45,7 +45,7 @@ import java.util.Map;
 /**
  * Planner rule that pulls up constants through a Union operator.
  */
-public class UnionPullUpConstantsRule extends RelOptRule {
+public class UnionPullUpConstantsRule extends RelOptRule implements 
TransformationRule {
 
   public static final UnionPullUpConstantsRule INSTANCE =
       new UnionPullUpConstantsRule(Union.class, RelFactories.LOGICAL_BUILDER);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/UnionToDistinctRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/UnionToDistinctRule.java
index 4ec2847..bb879a0 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/UnionToDistinctRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/UnionToDistinctRule.java
@@ -32,7 +32,7 @@ import org.apache.calcite.tools.RelBuilderFactory;
  * on top of a non-distinct {@link org.apache.calcite.rel.core.Union}
  * (<code>all</code> = <code>true</code>).
  */
-public class UnionToDistinctRule extends RelOptRule {
+public class UnionToDistinctRule extends RelOptRule implements 
TransformationRule {
   public static final UnionToDistinctRule INSTANCE =
       new UnionToDistinctRule(LogicalUnion.class, 
RelFactories.LOGICAL_BUILDER);
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ValuesReduceRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ValuesReduceRule.java
index c9dbe55..038fc03 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ValuesReduceRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ValuesReduceRule.java
@@ -63,7 +63,7 @@ import java.util.List;
  * <p>Ignores an empty {@code Values}; this is better dealt with by
  * {@link PruneEmptyRules}.
  */
-public abstract class ValuesReduceRule extends RelOptRule {
+public abstract class ValuesReduceRule extends RelOptRule implements 
TransformationRule {
   //~ Static fields/initializers ---------------------------------------------
 
   private static final Logger LOGGER = CalciteTrace.getPlannerTracer();
diff --git a/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java 
b/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java
index 287b376..c366a64 100644
--- a/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java
@@ -38,6 +38,7 @@ import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.logical.LogicalSort;
 import org.apache.calcite.rel.logical.LogicalTableScan;
 import org.apache.calcite.rel.logical.LogicalUnion;
+import org.apache.calcite.rel.rules.TransformationRule;
 import org.apache.calcite.schema.StreamableTable;
 import org.apache.calcite.schema.Table;
 import org.apache.calcite.tools.RelBuilder;
@@ -67,7 +68,7 @@ public class StreamRules {
           new DeltaTableScanToEmptyRule(RelFactories.LOGICAL_BUILDER));
 
   /** Planner rule that pushes a {@link Delta} through a {@link Project}. */
-  public static class DeltaProjectTransposeRule extends RelOptRule {
+  public static class DeltaProjectTransposeRule extends RelOptRule implements 
TransformationRule {
 
     /**
      * Creates a DeltaProjectTransposeRule.
@@ -96,7 +97,7 @@ public class StreamRules {
   }
 
   /** Planner rule that pushes a {@link Delta} through a {@link Filter}. */
-  public static class DeltaFilterTransposeRule extends RelOptRule {
+  public static class DeltaFilterTransposeRule extends RelOptRule implements 
TransformationRule {
 
     /**
      * Creates a DeltaFilterTransposeRule.
@@ -122,7 +123,7 @@ public class StreamRules {
   }
 
   /** Planner rule that pushes a {@link Delta} through an {@link Aggregate}. */
-  public static class DeltaAggregateTransposeRule extends RelOptRule {
+  public static class DeltaAggregateTransposeRule extends RelOptRule 
implements TransformationRule {
 
     /**
      * Creates a DeltaAggregateTransposeRule.
@@ -151,7 +152,7 @@ public class StreamRules {
   }
 
   /** Planner rule that pushes a {@link Delta} through an {@link Sort}. */
-  public static class DeltaSortTransposeRule extends RelOptRule {
+  public static class DeltaSortTransposeRule extends RelOptRule implements 
TransformationRule {
 
     /**
      * Creates a DeltaSortTransposeRule.
@@ -178,7 +179,7 @@ public class StreamRules {
   }
 
   /** Planner rule that pushes a {@link Delta} through an {@link Union}. */
-  public static class DeltaUnionTransposeRule extends RelOptRule {
+  public static class DeltaUnionTransposeRule extends RelOptRule implements 
TransformationRule {
 
     /**
      * Creates a DeltaUnionTransposeRule.
@@ -213,7 +214,7 @@ public class StreamRules {
    * <p>Very likely, the stream was only represented as a table for uniformity
    * with the other relations in the system. The Delta disappears and the 
stream
    * can be implemented directly. */
-  public static class DeltaTableScanRule extends RelOptRule {
+  public static class DeltaTableScanRule extends RelOptRule implements 
TransformationRule {
 
     /**
      * Creates a DeltaTableScanRule.
@@ -254,7 +255,7 @@ public class StreamRules {
    * a table other than {@link org.apache.calcite.schema.StreamableTable} to
    * an empty {@link Values}.
    */
-  public static class DeltaTableScanToEmptyRule extends RelOptRule {
+  public static class DeltaTableScanToEmptyRule extends RelOptRule implements 
TransformationRule {
 
     /**
      * Creates a DeltaTableScanToEmptyRule.
@@ -291,7 +292,7 @@ public class StreamRules {
    * <blockquote><code>stream(x join y) &rarr;
    * x join stream(y) union all stream(x) join y</code></blockquote>
    */
-  public static class DeltaJoinTransposeRule extends RelOptRule {
+  public static class DeltaJoinTransposeRule extends RelOptRule implements 
TransformationRule {
 
     @Deprecated // to be removed before 2.0
     public DeltaJoinTransposeRule() {
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java 
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index 37eaae0..12ef011 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -162,6 +162,7 @@ import org.apache.calcite.tools.RuleSets;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
@@ -6617,6 +6618,29 @@ class RelOptRulesTest extends RelOptTestBase {
     sql(query).withRule(FilterJoinRule.FILTER_ON_JOIN).check();
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-3997";>[CALCITE-3997]
+   * Logical rules applied on physical operator but failed handle traits</a>
+   */
+  @Test void testMergeJoinCollation() {
+    final String sql = "select r.ename, s.sal from\n"
+        + "sales.emp r join sales.bonus s\n"
+        + "on r.ename=s.ename where r.sal+1=s.sal";
+    sql(sql, false).check();
+  }
+
+  Sql sql(String sql, boolean topDown) {
+    VolcanoPlanner planner = new VolcanoPlanner();
+    planner.setTopDownOpt(topDown);
+    planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
+    planner.addRelTraitDef(RelCollationTraitDef.INSTANCE);
+    RelOptUtil.registerDefaultRules(planner, false, false);
+    Tester tester = createTester().withDecorrelation(true)
+        .withClusterFactory(cluster -> RelOptCluster.create(planner, 
cluster.getRexBuilder()));
+    return new Sql(tester, sql, null, planner,
+        ImmutableMap.of(), ImmutableList.of());
+  }
+
   /**
    * Custom implementation of {@link Filter} for use
    * in test case to verify that {@link FilterMultiJoinMergeRule}
diff --git 
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml 
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 07f8b80..f5f3d3e 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -12532,4 +12532,28 @@ LogicalProject(NAME=[$10], ENAME=[$1])
 ]]>
         </Resource>
     </TestCase>
+  <TestCase name="testMergeJoinCollation">
+    <Resource name="sql">
+      <![CDATA[select * from
+        sales.emp r join sales.bonus s
+        on r.ename=s.ename where r.sal+1=s.sal]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(ENAME=[$1], SAL=[$11])
+  LogicalFilter(condition=[=(+($5, 1), $11)])
+    LogicalJoin(condition=[=($1, $9)], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalTableScan(table=[[CATALOG, SALES, BONUS]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+EnumerableProject(ENAME=[$5], SAL=[$2])
+  EnumerableHashJoin(condition=[AND(=($0, $5), =(+($9, 1), $2))], 
joinType=[inner])
+    EnumerableTableScan(table=[[CATALOG, SALES, BONUS]])
+    EnumerableTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
 </Root>

Reply via email to