You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by ma...@apache.org on 2017/02/21 23:19:54 UTC

[3/3] calcite git commit: [CALCITE-1637] Add mutable equivalents for all relational expressions

[CALCITE-1637] Add mutable equivalents for all relational expressions


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/316a0587
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/316a0587
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/316a0587

Branch: refs/heads/master
Commit: 316a05872b358e8388890c04dee48adc45812f71
Parents: 5431bd9
Author: maryannxue <ma...@gmail.com>
Authored: Tue Feb 21 15:19:41 2017 -0800
Committer: maryannxue <ma...@gmail.com>
Committed: Tue Feb 21 15:19:41 2017 -0800

----------------------------------------------------------------------
 .../MaterializedViewSubstitutionVisitor.java    |   50 +-
 .../calcite/plan/SubstitutionVisitor.java       | 1106 ++----------------
 .../org/apache/calcite/rel/mutable/Holder.java  |   46 +
 .../calcite/rel/mutable/MutableAggregate.java   |   97 ++
 .../calcite/rel/mutable/MutableBiRel.java       |   77 ++
 .../apache/calcite/rel/mutable/MutableCalc.java |   64 +
 .../calcite/rel/mutable/MutableCollect.java     |   65 +
 .../calcite/rel/mutable/MutableCorrelate.java   |   91 ++
 .../calcite/rel/mutable/MutableExchange.java    |   62 +
 .../calcite/rel/mutable/MutableFilter.java      |   64 +
 .../calcite/rel/mutable/MutableIntersect.java   |   55 +
 .../apache/calcite/rel/mutable/MutableJoin.java |   94 ++
 .../calcite/rel/mutable/MutableLeafRel.java     |   48 +
 .../calcite/rel/mutable/MutableMinus.java       |   55 +
 .../calcite/rel/mutable/MutableMultiRel.java    |   65 +
 .../calcite/rel/mutable/MutableProject.java     |  100 ++
 .../apache/calcite/rel/mutable/MutableRel.java  |  148 +++
 .../calcite/rel/mutable/MutableRelType.java     |   44 +
 .../calcite/rel/mutable/MutableRelVisitor.java  |   34 +
 .../apache/calcite/rel/mutable/MutableRels.java |  400 +++++++
 .../calcite/rel/mutable/MutableSample.java      |   69 ++
 .../apache/calcite/rel/mutable/MutableScan.java |   56 +
 .../calcite/rel/mutable/MutableSemiJoin.java    |   90 ++
 .../calcite/rel/mutable/MutableSetOp.java       |   52 +
 .../calcite/rel/mutable/MutableSingleRel.java   |   61 +
 .../apache/calcite/rel/mutable/MutableSort.java |   81 ++
 .../rel/mutable/MutableTableFunctionScan.java   |   96 ++
 .../calcite/rel/mutable/MutableTableModify.java |  113 ++
 .../calcite/rel/mutable/MutableUncollect.java   |   67 ++
 .../calcite/rel/mutable/MutableUnion.java       |   55 +
 .../calcite/rel/mutable/MutableValues.java      |   56 +
 .../calcite/rel/mutable/MutableWindow.java      |   73 ++
 .../calcite/rel/mutable/package-info.java       |   39 +
 .../org/apache/calcite/test/CalciteSuite.java   |    1 +
 .../calcite/test/MaterializationTest.java       |   29 +
 .../org/apache/calcite/test/MutableRelTest.java |  235 ++++
 36 files changed, 2904 insertions(+), 1034 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/plan/MaterializedViewSubstitutionVisitor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/MaterializedViewSubstitutionVisitor.java b/core/src/main/java/org/apache/calcite/plan/MaterializedViewSubstitutionVisitor.java
index da7b18d..c1e0e37 100644
--- a/core/src/main/java/org/apache/calcite/plan/MaterializedViewSubstitutionVisitor.java
+++ b/core/src/main/java/org/apache/calcite/plan/MaterializedViewSubstitutionVisitor.java
@@ -17,6 +17,10 @@
 package org.apache.calcite.plan;
 
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.mutable.MutableFilter;
+import org.apache.calcite.rel.mutable.MutableProject;
+import org.apache.calcite.rel.mutable.MutableRel;
+import org.apache.calcite.rel.mutable.MutableRels;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexNode;
@@ -48,8 +52,8 @@ public class MaterializedViewSubstitutionVisitor extends SubstitutionVisitor {
 
   /**
    * Implementation of {@link SubstitutionVisitor.UnifyRule} that matches a
-   * {@link SubstitutionVisitor.MutableProject} to a
-   * {@link SubstitutionVisitor.MutableProject} where the condition of the target
+   * {@link MutableProject} to a
+   * {@link MutableProject} where the condition of the target
    * relation is weaker.
    *
    * <p>Example: target has a weaker condition and contains all columns selected
@@ -76,19 +80,18 @@ public class MaterializedViewSubstitutionVisitor extends SubstitutionVisitor {
       final MutableProject query = (MutableProject) call.query;
 
       final List<RelDataTypeField> oldFieldList =
-          query.getInput().getRowType().getFieldList();
+          query.getInput().rowType.getFieldList();
       final List<RelDataTypeField> newFieldList =
-          call.target.getRowType().getFieldList();
+          call.target.rowType.getFieldList();
       List<RexNode> newProjects;
       try {
-        newProjects = transformRex(query.getProjects(), oldFieldList, newFieldList);
+        newProjects = transformRex(query.projects, oldFieldList, newFieldList);
       } catch (MatchFailed e) {
         return null;
       }
 
       final MutableProject newProject =
-          MutableProject.of(
-              query.getRowType(), call.target, newProjects);
+          MutableProject.of(query.rowType, call.target, newProjects);
 
       final MutableRel newProject2 = MutableRels.strip(newProject);
       return call.result(newProject2);
@@ -109,9 +112,9 @@ public class MaterializedViewSubstitutionVisitor extends SubstitutionVisitor {
                 (MutableFilter) queryProject.getInput();
             RexNode newCondition;
             try {
-              newCondition = transformRex(innerFilter.getCondition(),
-                  innerFilter.getInput().getRowType().getFieldList(),
-                  target.getRowType().getFieldList());
+              newCondition = transformRex(innerFilter.condition,
+                  innerFilter.getInput().rowType.getFieldList(),
+                  target.rowType.getFieldList());
             } catch (MatchFailed e) {
               return null;
             }
@@ -129,8 +132,8 @@ public class MaterializedViewSubstitutionVisitor extends SubstitutionVisitor {
 
   /**
    * Implementation of {@link SubstitutionVisitor.UnifyRule} that matches a
-   * {@link SubstitutionVisitor.MutableFilter} to a
-   * {@link SubstitutionVisitor.MutableFilter} where the condition of the target
+   * {@link MutableFilter} to a
+   * {@link MutableFilter} where the condition of the target
    * relation is weaker.
    *
    * <p>Example: target has a weaker condition</p>
@@ -153,7 +156,7 @@ public class MaterializedViewSubstitutionVisitor extends SubstitutionVisitor {
     public UnifyResult apply(UnifyRuleCall call) {
       final MutableFilter query = (MutableFilter) call.query;
       final MutableFilter target = (MutableFilter) call.target;
-      final MutableFilter newFilter = MutableFilter.of(target, query.getCondition());
+      final MutableFilter newFilter = MutableFilter.of(target, query.condition);
       return call.result(newFilter);
     }
 
@@ -173,9 +176,9 @@ public class MaterializedViewSubstitutionVisitor extends SubstitutionVisitor {
 
   /**
    * Implementation of {@link SubstitutionVisitor.UnifyRule} that matches a
-   * {@link SubstitutionVisitor.MutableFilter} to a
-   * {@link SubstitutionVisitor.MutableProject} on top of a
-   * {@link SubstitutionVisitor.MutableFilter} where the condition of the target
+   * {@link MutableFilter} to a
+   * {@link MutableProject} on top of a
+   * {@link MutableFilter} where the condition of the target
    * relation is weaker.
    *
    * <p>Example: target has a weaker condition and is a permutation projection of
@@ -203,22 +206,21 @@ public class MaterializedViewSubstitutionVisitor extends SubstitutionVisitor {
       final MutableRel query = call.query;
 
       final List<RelDataTypeField> oldFieldList =
-          query.getRowType().getFieldList();
+          query.rowType.getFieldList();
       final List<RelDataTypeField> newFieldList =
-          call.target.getRowType().getFieldList();
+          call.target.rowType.getFieldList();
       List<RexNode> newProjects;
       try {
         newProjects = transformRex(
             (List<RexNode>) call.getCluster().getRexBuilder().identityProjects(
-                query.getRowType()),
+                query.rowType),
             oldFieldList, newFieldList);
       } catch (MatchFailed e) {
         return null;
       }
 
       final MutableProject newProject =
-          MutableProject.of(
-              query.getRowType(), call.target, newProjects);
+          MutableProject.of(query.rowType, call.target, newProjects);
 
       final MutableRel newProject2 = MutableRels.strip(newProject);
       return call.result(newProject2);
@@ -234,9 +236,9 @@ public class MaterializedViewSubstitutionVisitor extends SubstitutionVisitor {
             final MutableFilter filter = (MutableFilter) query;
             RexNode newCondition;
             try {
-              newCondition = transformRex(filter.getCondition(),
-                  filter.getInput().getRowType().getFieldList(),
-                  target.getRowType().getFieldList());
+              newCondition = transformRex(filter.condition,
+                  filter.getInput().rowType.getFieldList(),
+                  target.rowType.getFieldList());
             } catch (MatchFailed e) {
               return null;
             }

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
index edba57f..02c63b0 100644
--- a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
+++ b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
@@ -16,28 +16,21 @@
  */
 package org.apache.calcite.plan;
 
-import org.apache.calcite.avatica.util.Spaces;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.prepare.CalcitePrepareImpl;
-import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.SingleRel;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
-import org.apache.calcite.rel.core.CorrelationId;
-import org.apache.calcite.rel.core.Filter;
-import org.apache.calcite.rel.core.Join;
-import org.apache.calcite.rel.core.JoinRelType;
-import org.apache.calcite.rel.core.Project;
-import org.apache.calcite.rel.core.Sort;
-import org.apache.calcite.rel.core.TableScan;
-import org.apache.calcite.rel.core.Values;
-import org.apache.calcite.rel.logical.LogicalAggregate;
 import org.apache.calcite.rel.logical.LogicalFilter;
-import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.rel.logical.LogicalProject;
-import org.apache.calcite.rel.logical.LogicalSort;
-import org.apache.calcite.rel.logical.LogicalUnion;
+import org.apache.calcite.rel.mutable.Holder;
+import org.apache.calcite.rel.mutable.MutableAggregate;
+import org.apache.calcite.rel.mutable.MutableFilter;
+import org.apache.calcite.rel.mutable.MutableProject;
+import org.apache.calcite.rel.mutable.MutableRel;
+import org.apache.calcite.rel.mutable.MutableRelVisitor;
+import org.apache.calcite.rel.mutable.MutableRels;
+import org.apache.calcite.rel.mutable.MutableScan;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexBuilder;
@@ -51,7 +44,6 @@ import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.runtime.PredicateImpl;
 import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
-import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.util.Bug;
 import org.apache.calcite.util.ControlFlowException;
 import org.apache.calcite.util.ImmutableBitSet;
@@ -63,12 +55,10 @@ import org.apache.calcite.util.mapping.Mappings;
 import org.apache.calcite.util.trace.CalciteTrace;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Equivalence;
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -77,14 +67,11 @@ import com.google.common.collect.Sets;
 
 import org.slf4j.Logger;
 
-import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 
 import static org.apache.calcite.rex.RexUtil.andNot;
@@ -127,25 +114,6 @@ public class SubstitutionVisitor {
 
   private static final Logger LOGGER = CalciteTrace.getPlannerTracer();
 
-  /** Equivalence that compares objects by their {@link Object#toString()}
-   * method. */
-  private static final Equivalence<Object> STRING_EQUIVALENCE =
-      new Equivalence<Object>() {
-        @Override protected boolean doEquivalent(Object o, Object o2) {
-          return o.toString().equals(o2.toString());
-        }
-
-        @Override protected int doHash(Object o) {
-          return o.toString().hashCode();
-        }
-      };
-
-  /** Equivalence that compares {@link Lists}s by the
-   * {@link Object#toString()} of their elements. */
-  @SuppressWarnings("unchecked")
-  private static final Equivalence<List<?>> PAIRWISE_STRING_EQUIVALENCE =
-      (Equivalence) STRING_EQUIVALENCE.pairwise();
-
   protected static final ImmutableList<UnifyRule> DEFAULT_RULES =
       ImmutableList.<UnifyRule>of(
           TrivialRule.INSTANCE,
@@ -194,14 +162,14 @@ public class SubstitutionVisitor {
       ImmutableList<UnifyRule> rules) {
     this.cluster = target_.getCluster();
     this.rules = rules;
-    this.query = Holder.of(toMutable(query_));
-    this.target = toMutable(target_);
+    this.query = Holder.of(MutableRels.toMutable(query_));
+    this.target = MutableRels.toMutable(target_);
     final Set<MutableRel> parents = Sets.newIdentityHashSet();
     final List<MutableRel> allNodes = new ArrayList<>();
     final MutableRelVisitor visitor =
         new MutableRelVisitor() {
           public void visit(MutableRel node) {
-            parents.add(node.parent);
+            parents.add(node.getParent());
             allNodes.add(node);
             super.visit(node);
           }
@@ -221,46 +189,6 @@ public class SubstitutionVisitor {
     queryLeaves = ImmutableList.copyOf(allNodes);
   }
 
-  private static MutableRel toMutable(RelNode rel) {
-    if (rel instanceof TableScan) {
-      return MutableScan.of((TableScan) rel);
-    }
-    if (rel instanceof Values) {
-      return MutableValues.of((Values) rel);
-    }
-    if (rel instanceof Project) {
-      final Project project = (Project) rel;
-      final MutableRel input = toMutable(project.getInput());
-      return MutableProject.of(input, project.getProjects(),
-          project.getRowType().getFieldNames());
-    }
-    if (rel instanceof Filter) {
-      final Filter filter = (Filter) rel;
-      final MutableRel input = toMutable(filter.getInput());
-      return MutableFilter.of(input, filter.getCondition());
-    }
-    if (rel instanceof Aggregate) {
-      final Aggregate aggregate = (Aggregate) rel;
-      final MutableRel input = toMutable(aggregate.getInput());
-      return MutableAggregate.of(input, aggregate.indicator,
-          aggregate.getGroupSet(), aggregate.getGroupSets(),
-          aggregate.getAggCallList());
-    }
-    if (rel instanceof Join) {
-      final Join join = (Join) rel;
-      final MutableRel left = toMutable(join.getLeft());
-      final MutableRel right = toMutable(join.getRight());
-      return MutableJoin.of(join.getCluster(), left, right,
-          join.getCondition(), join.getJoinType(), join.getVariablesSet());
-    }
-    if (rel instanceof Sort) {
-      final Sort sort = (Sort) rel;
-      final MutableRel input = toMutable(sort.getInput());
-      return MutableSort.of(input, sort.getCollation(), sort.offset, sort.fetch);
-    }
-    throw new RuntimeException("cannot translate " + rel + " to MutableRel");
-  }
-
   void register(MutableRel result, MutableRel query) {
   }
 
@@ -406,8 +334,8 @@ public class SubstitutionVisitor {
 
   public RelNode go0(RelNode replacement_) {
     assert false; // not called
-    MutableRel replacement = toMutable(replacement_);
-    assert MutableRels.equalType(
+    MutableRel replacement = MutableRels.toMutable(replacement_);
+    assert equalType(
         "target", target, "replacement", replacement, Litmus.THROW);
     replacementMap.put(target, replacement);
     final UnifyResult unifyResult = matchRecurse(target);
@@ -430,7 +358,7 @@ public class SubstitutionVisitor {
           + "\nnode:\n"
           + node.deep());
     }
-    return fromMutable(node);
+    return MutableRels.fromMutable(node);
   }
 
   /**
@@ -442,12 +370,12 @@ public class SubstitutionVisitor {
    * A join R.
    */
   public List<RelNode> go(RelNode replacement_) {
-    List<List<Replacement>> matches = go(toMutable(replacement_));
+    List<List<Replacement>> matches = go(MutableRels.toMutable(replacement_));
     if (matches.isEmpty()) {
       return ImmutableList.of();
     }
     List<RelNode> sub = Lists.newArrayList();
-    sub.add(fromMutable(query.input));
+    sub.add(MutableRels.fromMutable(query.getInput()));
     reverseSubstitute(query, matches, sub, 0, matches.size());
     return sub;
   }
@@ -460,7 +388,7 @@ public class SubstitutionVisitor {
    * substitution in different places.
    */
   private List<List<Replacement>> go(MutableRel replacement) {
-    assert MutableRels.equalType(
+    assert equalType(
         "target", target, "replacement", replacement, Litmus.THROW);
     final List<MutableRel> queryDescendants = MutableRels.descendants(query);
     final List<MutableRel> targetDescendants = MutableRels.descendants(target);
@@ -530,8 +458,8 @@ public class SubstitutionVisitor {
                   // Meanwhile we stop matching the descendants and jump
                   // to the next subtree in pre-order traversal.
                   if (!target.equals(replacement)) {
-                    Replacement r = MutableRels.replace(
-                        query.input, target, copyMutable(replacement));
+                    Replacement r = replace(
+                        query.getInput(), target, replacement.clone());
                     assert r != null
                         : rule + "should have returned a result containing the target.";
                     attempted.add(r);
@@ -575,7 +503,7 @@ public class SubstitutionVisitor {
   /**
    * Represents a replacement action: before &rarr; after.
    */
-  private static class Replacement {
+  static class Replacement {
     final MutableRel before;
     final MutableRel after;
 
@@ -585,6 +513,37 @@ public class SubstitutionVisitor {
     }
   }
 
+  /** Within a relational expression {@code query}, replaces occurrences of
+   * {@code find} with {@code replace}.
+   *
+   * <p>Assumes relational expressions (and their descendants) are not null.
+   * Does not handle cycles. */
+  public static Replacement replace(MutableRel query, MutableRel find,
+      MutableRel replace) {
+    if (find.equals(replace)) {
+      // Short-cut common case.
+      return null;
+    }
+    assert equalType("find", find, "replace", replace, Litmus.THROW);
+    return replaceRecurse(query, find, replace);
+  }
+
+  /** Helper for {@link #replace}. */
+  private static Replacement replaceRecurse(MutableRel query,
+      MutableRel find, MutableRel replace) {
+    if (find.equals(query)) {
+      query.replaceInParent(replace);
+      return new Replacement(query, replace);
+    }
+    for (MutableRel input : query.getInputs()) {
+      Replacement r = replaceRecurse(input, find, replace);
+      if (r != null) {
+        return r;
+      }
+    }
+    return null;
+  }
+
   private static void undoReplacement(List<Replacement> replacement) {
     for (int i = replacement.size() - 1; i >= 0; i--) {
       Replacement r = replacement.get(i);
@@ -608,99 +567,12 @@ public class SubstitutionVisitor {
     reverseSubstitute(query, rem, sub, replaceCount, maxCount);
     undoReplacement(matches.get(0));
     if (++replaceCount < maxCount) {
-      sub.add(fromMutable(query.input));
+      sub.add(MutableRels.fromMutable(query.getInput()));
     }
     reverseSubstitute(query, rem, sub, replaceCount, maxCount);
     redoReplacement(matches.get(0));
   }
 
-  private static List<RelNode> fromMutables(List<MutableRel> nodes) {
-    return Lists.transform(nodes,
-        new Function<MutableRel, RelNode>() {
-          public RelNode apply(MutableRel mutableRel) {
-            return fromMutable(mutableRel);
-          }
-        });
-  }
-
-  private static RelNode fromMutable(MutableRel node) {
-    switch (node.type) {
-    case SCAN:
-    case VALUES:
-      return ((MutableLeafRel) node).rel;
-    case PROJECT:
-      final MutableProject project = (MutableProject) node;
-      return LogicalProject.create(fromMutable(project.input),
-          project.projects, project.rowType);
-    case FILTER:
-      final MutableFilter filter = (MutableFilter) node;
-      return LogicalFilter.create(fromMutable(filter.input),
-          filter.condition);
-    case AGGREGATE:
-      final MutableAggregate aggregate = (MutableAggregate) node;
-      return LogicalAggregate.create(fromMutable(aggregate.input),
-          aggregate.indicator, aggregate.groupSet, aggregate.groupSets,
-          aggregate.aggCalls);
-    case SORT:
-      final MutableSort sort = (MutableSort) node;
-      return LogicalSort.create(fromMutable(sort.input), sort.collation,
-          sort.offset, sort.fetch);
-    case UNION:
-      final MutableUnion union = (MutableUnion) node;
-      return LogicalUnion.create(fromMutables(union.inputs), union.all);
-    case JOIN:
-      final MutableJoin join = (MutableJoin) node;
-      return LogicalJoin.create(fromMutable(join.getLeft()), fromMutable(join.getRight()),
-          join.getCondition(), join.getVariablesSet(), join.getJoinType());
-    default:
-      throw new AssertionError(node.deep());
-    }
-  }
-
-  private static List<MutableRel> copyMutables(List<MutableRel> nodes) {
-    return Lists.transform(nodes,
-        new Function<MutableRel, MutableRel>() {
-          public MutableRel apply(MutableRel mutableRel) {
-            return copyMutable(mutableRel);
-          }
-        });
-  }
-
-  private static MutableRel copyMutable(MutableRel node) {
-    switch (node.type) {
-    case SCAN:
-      return MutableScan.of((TableScan) ((MutableScan) node).rel);
-    case VALUES:
-      return MutableValues.of((Values) ((MutableValues) node).rel);
-    case PROJECT:
-      final MutableProject project = (MutableProject) node;
-      return MutableProject.of(project.rowType,
-          copyMutable(project.input), project.projects);
-    case FILTER:
-      final MutableFilter filter = (MutableFilter) node;
-      return MutableFilter.of(copyMutable(filter.input), filter.condition);
-    case AGGREGATE:
-      final MutableAggregate aggregate = (MutableAggregate) node;
-      return MutableAggregate.of(copyMutable(aggregate.input),
-          aggregate.indicator, aggregate.groupSet, aggregate.groupSets,
-          aggregate.aggCalls);
-    case SORT:
-      final MutableSort sort = (MutableSort) node;
-      return MutableSort.of(copyMutable(sort.input), sort.collation,
-          sort.offset, sort.fetch);
-    case UNION:
-      final MutableUnion union = (MutableUnion) node;
-      return MutableUnion.of(copyMutables(union.inputs), union.all);
-    case JOIN:
-      final MutableJoin join = (MutableJoin) node;
-      return MutableJoin.of(join.cluster, copyMutable(join.getLeft()),
-          copyMutable(join.getRight()), join.getCondition(), join.getJoinType(),
-          join.getVariablesSet());
-    default:
-      throw new AssertionError(node.deep());
-    }
-  }
-
   private UnifyResult matchRecurse(MutableRel target) {
     assert false; // not called
     final List<MutableRel> targetInputs = target.getInputs();
@@ -901,13 +773,13 @@ public class SubstitutionVisitor {
 
     public UnifyResult result(MutableRel result) {
       assert MutableRels.contains(result, target);
-      assert MutableRels.equalType("result", result, "query", query,
+      assert equalType("result", result, "query", query,
           Litmus.THROW);
       MutableRel replace = replacementMap.get(target);
       if (replace != null) {
         assert false; // replacementMap is always empty
         // result =
-        MutableRels.replace(result, target, replace);
+        replace(result, target, replace);
       }
       register(result, query);
       return new UnifyResult(this, result);
@@ -938,7 +810,7 @@ public class SubstitutionVisitor {
 
     UnifyResult(UnifyRuleCall call, MutableRel result) {
       this.call = call;
-      assert MutableRels.equalType("query", call.query, "result", result,
+      assert equalType("query", call.query, "result", result,
           Litmus.THROW);
       this.result = result;
     }
@@ -1035,13 +907,12 @@ public class SubstitutionVisitor {
       final List<RexNode> newProjects;
       try {
         newProjects = (List<RexNode>)
-            shuttle.apply(rexBuilder.identityProjects(query.getRowType()));
+            shuttle.apply(rexBuilder.identityProjects(query.rowType));
       } catch (MatchFailed e) {
         return null;
       }
       final MutableProject newProject =
-          MutableProject.of(
-              query.getRowType(), target, newProjects);
+          MutableProject.of(query.rowType, target, newProjects);
       final MutableRel newProject2 = MutableRels.strip(newProject);
       return call.result(newProject2);
     }
@@ -1064,13 +935,12 @@ public class SubstitutionVisitor {
       final RexShuttle shuttle = getRexShuttle(target);
       final List<RexNode> newProjects;
       try {
-        newProjects = shuttle.apply(query.getProjects());
+        newProjects = shuttle.apply(query.projects);
       } catch (MatchFailed e) {
         return null;
       }
       final MutableProject newProject =
-          MutableProject.of(
-              query.getRowType(), target, newProjects);
+          MutableProject.of(query.rowType, target, newProjects);
       final MutableRel newProject2 = MutableRels.strip(newProject);
       return call.result(newProject2);
     }
@@ -1097,16 +967,16 @@ public class SubstitutionVisitor {
         final RexNode newCondition;
         final MutableFilter query = (MutableFilter) call.query;
         try {
-          newCondition = query.getCondition().accept(shuttle);
+          newCondition = query.condition.accept(shuttle);
         } catch (MatchFailed e) {
           return null;
         }
         final MutableFilter newFilter = MutableFilter.of(target, newCondition);
-        if (query.parent instanceof MutableProject) {
+        if (query.getParent() instanceof MutableProject) {
           final MutableRel inverse =
-              invert(((MutableProject) query.parent).getNamedProjects(),
+              invert(((MutableProject) query.getParent()).getNamedProjects(),
                   newFilter, shuttle);
-          return call.create(query.parent).result(inverse);
+          return call.create(query.getParent()).result(inverse);
         } else {
           final MutableRel inverse = invert(query, newFilter, target);
           return call.result(inverse);
@@ -1141,15 +1011,15 @@ public class SubstitutionVisitor {
         MutableProject project) {
       LOGGER.trace("SubstitutionVisitor: invert:\nmodel: {}\ninput: {}\nproject: {}\n",
           model, input, project);
-      if (project.getProjects().size() < model.getRowType().getFieldCount()) {
+      if (project.projects.size() < model.rowType.getFieldCount()) {
         throw MatchFailed.INSTANCE;
       }
       final List<RexNode> exprList = new ArrayList<>();
       final RexBuilder rexBuilder = model.cluster.getRexBuilder();
-      for (RelDataTypeField field : model.getRowType().getFieldList()) {
+      for (RelDataTypeField field : model.rowType.getFieldList()) {
         exprList.add(rexBuilder.makeZeroLiteral(field.getType()));
       }
-      for (Ord<RexNode> expr : Ord.zip(project.getProjects())) {
+      for (Ord<RexNode> expr : Ord.zip(project.projects)) {
         if (expr.e instanceof RexInputRef) {
           final int target = ((RexInputRef) expr.e).getIndex();
           exprList.set(target,
@@ -1194,8 +1064,8 @@ public class SubstitutionVisitor {
 
     MutableFilter createFilter(MutableFilter query, MutableFilter target) {
       final RexNode newCondition =
-          splitFilter(query.cluster.getRexBuilder(), query.getCondition(),
-              target.getCondition());
+          splitFilter(query.cluster.getRexBuilder(), query.condition,
+              target.condition);
       if (newCondition == null) {
         // Could not map query onto target.
         return null;
@@ -1219,8 +1089,8 @@ public class SubstitutionVisitor {
     }
 
     public UnifyResult apply(UnifyRuleCall call) {
-      if (call.query.parent instanceof MutableFilter) {
-        final UnifyRuleCall in2 = call.create(call.query.parent);
+      if (call.query.getParent() instanceof MutableFilter) {
+        final UnifyRuleCall in2 = call.create(call.query.getParent());
         final MutableFilter query = (MutableFilter) in2.query;
         final MutableFilter target = (MutableFilter) in2.target;
         final MutableFilter newFilter =
@@ -1258,7 +1128,7 @@ public class SubstitutionVisitor {
       //   target: SELECT x, y, SUM(a) AS s, COUNT(b) AS cb FROM t GROUP BY x, y
       // transforms to
       //   result: SELECT x, SUM(cb) FROM (target) GROUP BY x
-      if (!target.getGroupSet().contains(query.getGroupSet())) {
+      if (!target.groupSet.contains(query.groupSet)) {
         return null;
       }
       MutableRel result = unifyAggregates(query, target);
@@ -1271,11 +1141,11 @@ public class SubstitutionVisitor {
 
   public static MutableAggregate permute(MutableAggregate aggregate,
       MutableRel input, Mapping mapping) {
-    ImmutableBitSet groupSet = Mappings.apply(mapping, aggregate.getGroupSet());
+    ImmutableBitSet groupSet = Mappings.apply(mapping, aggregate.groupSet);
     ImmutableList<ImmutableBitSet> groupSets =
-        Mappings.apply2(mapping, aggregate.getGroupSets());
+        Mappings.apply2(mapping, aggregate.groupSets);
     List<AggregateCall> aggregateCalls =
-        apply(mapping, aggregate.getAggCallList());
+        apply(mapping, aggregate.aggCalls);
     return MutableAggregate.of(input, aggregate.indicator, groupSet, groupSets,
         aggregateCalls);
   }
@@ -1298,15 +1168,15 @@ public class SubstitutionVisitor {
       throw new AssertionError(Bug.CALCITE_461_FIXED);
     }
     MutableRel result;
-    if (query.getGroupSet().equals(target.getGroupSet())) {
+    if (query.groupSet.equals(target.groupSet)) {
       // Same level of aggregation. Generate a project.
       final List<Integer> projects = Lists.newArrayList();
-      final int groupCount = query.getGroupSet().cardinality();
+      final int groupCount = query.groupSet.cardinality();
       for (int i = 0; i < groupCount; i++) {
         projects.add(i);
       }
-      for (AggregateCall aggregateCall : query.getAggCallList()) {
-        int i = target.getAggCallList().indexOf(aggregateCall);
+      for (AggregateCall aggregateCall : query.aggCalls) {
+        int i = target.aggCalls.indexOf(aggregateCall);
         if (i < 0) {
           return null;
         }
@@ -1316,8 +1186,8 @@ public class SubstitutionVisitor {
     } else {
       // Target is coarser level of aggregation. Generate an aggregate.
       final ImmutableBitSet.Builder groupSet = ImmutableBitSet.builder();
-      final List<Integer> targetGroupList = target.getGroupSet().asList();
-      for (int c : query.getGroupSet()) {
+      final List<Integer> targetGroupList = target.groupSet.asList();
+      for (int c : query.groupSet) {
         int c2 = targetGroupList.indexOf(c);
         if (c2 < 0) {
           return null;
@@ -1325,11 +1195,11 @@ public class SubstitutionVisitor {
         groupSet.set(c2);
       }
       final List<AggregateCall> aggregateCalls = Lists.newArrayList();
-      for (AggregateCall aggregateCall : query.getAggCallList()) {
+      for (AggregateCall aggregateCall : query.aggCalls) {
         if (aggregateCall.isDistinct()) {
           return null;
         }
-        int i = target.getAggCallList().indexOf(aggregateCall);
+        int i = target.aggCalls.indexOf(aggregateCall);
         if (i < 0) {
           return null;
         }
@@ -1342,7 +1212,7 @@ public class SubstitutionVisitor {
       result = MutableAggregate.of(target, false, groupSet.build(), null,
           aggregateCalls);
     }
-    return MutableRels.createCastRel(result, query.getRowType(), true);
+    return MutableRels.createCastRel(result, query.rowType, true);
   }
 
   /** Implementation of {@link UnifyRule} that matches a
@@ -1402,7 +1272,7 @@ public class SubstitutionVisitor {
    * expressions to references to them. */
   protected static RexShuttle getRexShuttle(MutableProject target) {
     final Map<String, Integer> map = new HashMap<>();
-    for (RexNode e : target.getProjects()) {
+    for (RexNode e : target.projects) {
       map.put(e.toString(), map.size());
     }
     return new RexShuttle() {
@@ -1424,796 +1294,6 @@ public class SubstitutionVisitor {
     };
   }
 
-  /** Type of {@code MutableRel}. */
-  private enum MutableRelType {
-    SCAN,
-    PROJECT,
-    FILTER,
-    AGGREGATE,
-    SORT,
-    UNION,
-    JOIN,
-    HOLDER,
-    VALUES
-  }
-
-  /** Visitor over {@link MutableRel}. */
-  private static class MutableRelVisitor {
-    private MutableRel root;
-
-    public void visit(MutableRel node) {
-      node.childrenAccept(this);
-    }
-
-    public MutableRel go(MutableRel p) {
-      this.root = p;
-      visit(p);
-      return root;
-    }
-  }
-
-  /** Mutable equivalent of {@link RelNode}.
-   *
-   * <p>Each node has mutable state, and keeps track of its parent and position
-   * within parent.
-   * It doesn't make sense to canonize {@code MutableRels},
-   * otherwise one node could end up with multiple parents.
-   * It follows that {@code #hashCode} and {@code #equals} are less efficient
-   * than their {@code RelNode} counterparts.
-   * But, you don't need to copy a {@code MutableRel} in order to change it.
-   * For this reason, you should use {@code MutableRel} for short-lived
-   * operations, and transcribe back to {@code RelNode} when you are done.</p>
-   */
-  protected abstract static class MutableRel {
-    MutableRel parent;
-    int ordinalInParent;
-    public final RelOptCluster cluster;
-    final RelDataType rowType;
-    final MutableRelType type;
-
-    private MutableRel(RelOptCluster cluster, RelDataType rowType,
-        MutableRelType type) {
-      this.cluster = cluster;
-      this.rowType = rowType;
-      this.type = type;
-    }
-
-    public RelDataType getRowType() {
-      return rowType;
-    }
-
-    public abstract void setInput(int ordinalInParent, MutableRel input);
-
-    public abstract List<MutableRel> getInputs();
-
-    public abstract void childrenAccept(MutableRelVisitor visitor);
-
-    /** Replaces this {@code MutableRel} in its parent with another node at the
-     * same position.
-     *
-     * <p>Before the method, {@code child} must be an orphan (have null parent)
-     * and after this method, this {@code MutableRel} is an orphan.
-     *
-     * @return The parent
-     */
-    public MutableRel replaceInParent(MutableRel child) {
-      final MutableRel parent = this.parent;
-      if (this != child) {
-/*
-        if (child.parent != null) {
-          child.parent.setInput(child.ordinalInParent, null);
-          child.parent = null;
-        }
-*/
-        if (parent != null) {
-          parent.setInput(ordinalInParent, child);
-          this.parent = null;
-          this.ordinalInParent = 0;
-        }
-      }
-      return parent;
-    }
-
-    public abstract StringBuilder digest(StringBuilder buf);
-
-    public final String deep() {
-      return new MutableRelDumper().apply(this);
-    }
-
-    @Override public final String toString() {
-      return deep();
-    }
-
-    public MutableRel getParent() { return parent; }
-  }
-
-  /** Implementation of {@link MutableRel} whose only purpose is to have a
-   * child. Used as the root of a tree. */
-  private static class Holder extends MutableSingleRel {
-    private Holder(MutableRelType type, RelDataType rowType, MutableRel input) {
-      super(type, rowType, input);
-    }
-
-    static Holder of(MutableRel input) {
-      return new Holder(MutableRelType.HOLDER, input.rowType, input);
-    }
-
-    @Override public StringBuilder digest(StringBuilder buf) {
-      return buf.append("Holder");
-    }
-  }
-
-   /** Abstract base class for implementations of {@link MutableRel} that have
-   * no inputs. */
-  protected abstract static class MutableLeafRel extends MutableRel {
-    protected final RelNode rel;
-
-    MutableLeafRel(MutableRelType type, RelNode rel) {
-      super(rel.getCluster(), rel.getRowType(), type);
-      this.rel = rel;
-    }
-
-    public void setInput(int ordinalInParent, MutableRel input) {
-      throw new IllegalArgumentException();
-    }
-
-    public List<MutableRel> getInputs() {
-      return ImmutableList.of();
-    }
-
-    public void childrenAccept(MutableRelVisitor visitor) {
-      // no children - nothing to do
-    }
-  }
-
-  /** Mutable equivalent of {@link SingleRel}. */
-  protected abstract static class MutableSingleRel extends MutableRel {
-    protected MutableRel input;
-
-    MutableSingleRel(MutableRelType type, RelDataType rowType,
-        MutableRel input) {
-      super(input.cluster, rowType, type);
-      this.input = input;
-      input.parent = this;
-      input.ordinalInParent = 0;
-    }
-
-    public void setInput(int ordinalInParent, MutableRel input) {
-      if (ordinalInParent >= 1) {
-        throw new IllegalArgumentException();
-      }
-      this.input = input;
-      if (input != null) {
-        input.parent = this;
-        input.ordinalInParent = 0;
-      }
-    }
-
-    public List<MutableRel> getInputs() {
-      return ImmutableList.of(input);
-    }
-
-    public void childrenAccept(MutableRelVisitor visitor) {
-      visitor.visit(input);
-    }
-
-    public MutableRel getInput() {
-      return input;
-    }
-  }
-
-  /** Mutable equivalent of
-   * {@link org.apache.calcite.rel.logical.LogicalTableScan}. */
-  protected static class MutableScan extends MutableLeafRel {
-    private MutableScan(TableScan rel) {
-      super(MutableRelType.SCAN, rel);
-    }
-
-    static MutableScan of(TableScan rel) {
-      return new MutableScan(rel);
-    }
-
-    @Override public boolean equals(Object obj) {
-      return obj == this
-          || obj instanceof MutableScan
-          && rel.equals(((MutableScan) obj).rel);
-    }
-
-    @Override public int hashCode() {
-      return rel.hashCode();
-    }
-
-    @Override public StringBuilder digest(StringBuilder buf) {
-      return buf.append("Scan(table: ")
-          .append(rel.getTable().getQualifiedName()).append(")");
-    }
-  }
-
-  /** Mutable equivalent of {@link org.apache.calcite.rel.core.Values}. */
-  protected static class MutableValues extends MutableLeafRel {
-    private MutableValues(Values rel) {
-      super(MutableRelType.VALUES, rel);
-    }
-
-    static MutableValues of(Values rel) {
-      return new MutableValues(rel);
-    }
-
-    @Override public boolean equals(Object obj) {
-      return obj == this
-          || obj instanceof MutableValues
-          && rel == ((MutableValues) obj).rel;
-    }
-
-    @Override public int hashCode() {
-      return rel.hashCode();
-    }
-
-    @Override public StringBuilder digest(StringBuilder buf) {
-      return buf.append("Values(tuples: ")
-          .append(((Values) rel).getTuples()).append(")");
-    }
-  }
-
-  /** Mutable equivalent of
-   * {@link org.apache.calcite.rel.logical.LogicalProject}. */
-  protected static class MutableProject extends MutableSingleRel {
-    private final List<RexNode> projects;
-
-    private MutableProject(RelDataType rowType, MutableRel input,
-        List<RexNode> projects) {
-      super(MutableRelType.PROJECT, rowType, input);
-      this.projects = projects;
-      assert RexUtil.compatibleTypes(projects, rowType, Litmus.THROW);
-    }
-
-    public static MutableProject of(RelDataType rowType, MutableRel input,
-        List<RexNode> projects) {
-      return new MutableProject(rowType, input, projects);
-    }
-
-    /** Equivalent to
-     * {@link RelOptUtil#createProject(org.apache.calcite.rel.RelNode, java.util.List, java.util.List)}
-     * for {@link MutableRel}. */
-    public static MutableRel of(MutableRel child, List<RexNode> exprList,
-        List<String> fieldNameList) {
-      final RelDataType rowType =
-          RexUtil.createStructType(child.cluster.getTypeFactory(), exprList,
-              fieldNameList, SqlValidatorUtil.F_SUGGESTER);
-      return of(rowType, child, exprList);
-    }
-
-    @Override public boolean equals(Object obj) {
-      return obj == this
-          || obj instanceof MutableProject
-          && PAIRWISE_STRING_EQUIVALENCE.equivalent(
-              projects, ((MutableProject) obj).projects)
-          && input.equals(((MutableProject) obj).input);
-    }
-
-    @Override public int hashCode() {
-      return Objects.hash(input,
-          PAIRWISE_STRING_EQUIVALENCE.hash(projects));
-    }
-
-    @Override public StringBuilder digest(StringBuilder buf) {
-      return buf.append("Project(projects: ").append(projects).append(")");
-    }
-
-    public List<RexNode> getProjects() {
-      return projects;
-    }
-
-    /** Returns a list of (expression, name) pairs. */
-    public final List<Pair<RexNode, String>> getNamedProjects() {
-      return Pair.zip(getProjects(), getRowType().getFieldNames());
-    }
-
-    public Mappings.TargetMapping getMapping() {
-      return Project.getMapping(
-          input.getRowType().getFieldCount(), projects);
-    }
-  }
-
-  /** Mutable equivalent of
-   * {@link org.apache.calcite.rel.logical.LogicalFilter}. */
-  protected static class MutableFilter extends MutableSingleRel {
-    private final RexNode condition;
-
-    private MutableFilter(MutableRel input, RexNode condition) {
-      super(MutableRelType.FILTER, input.rowType, input);
-      this.condition = condition;
-    }
-
-    public static MutableFilter of(MutableRel input, RexNode condition) {
-      return new MutableFilter(input, condition);
-    }
-
-    @Override public boolean equals(Object obj) {
-      return obj == this
-          || obj instanceof MutableFilter
-          && condition.toString().equals(
-              ((MutableFilter) obj).condition.toString())
-          && input.equals(((MutableFilter) obj).input);
-    }
-
-    @Override public int hashCode() {
-      return Objects.hash(input, condition.toString());
-    }
-
-    @Override public StringBuilder digest(StringBuilder buf) {
-      return buf.append("Filter(condition: ").append(condition).append(")");
-    }
-
-    public RexNode getCondition() {
-      return condition;
-    }
-  }
-
-  /** Mutable equivalent of
-   * {@link org.apache.calcite.rel.logical.LogicalAggregate}. */
-  protected static class MutableAggregate extends MutableSingleRel {
-    public final boolean indicator;
-    private final ImmutableBitSet groupSet;
-    private final ImmutableList<ImmutableBitSet> groupSets;
-    private final List<AggregateCall> aggCalls;
-
-    private MutableAggregate(MutableRel input, RelDataType rowType,
-        boolean indicator, ImmutableBitSet groupSet,
-        List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
-      super(MutableRelType.AGGREGATE, rowType, input);
-      this.indicator = indicator;
-      this.groupSet = groupSet;
-      this.groupSets = groupSets == null
-          ? ImmutableList.of(groupSet)
-          : ImmutableList.copyOf(groupSets);
-      this.aggCalls = aggCalls;
-    }
-
-    static MutableAggregate of(MutableRel input, boolean indicator,
-        ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
-        List<AggregateCall> aggCalls) {
-      RelDataType rowType =
-          Aggregate.deriveRowType(input.cluster.getTypeFactory(),
-              input.getRowType(), indicator, groupSet, groupSets, aggCalls);
-      return new MutableAggregate(input, rowType, indicator, groupSet,
-          groupSets, aggCalls);
-    }
-
-    @Override public boolean equals(Object obj) {
-      return obj == this
-          || obj instanceof MutableAggregate
-          && groupSet.equals(((MutableAggregate) obj).groupSet)
-          && aggCalls.equals(((MutableAggregate) obj).aggCalls)
-          && input.equals(((MutableAggregate) obj).input);
-    }
-
-    @Override public int hashCode() {
-      return Objects.hash(input, groupSet, aggCalls);
-    }
-
-    @Override public StringBuilder digest(StringBuilder buf) {
-      return buf.append("Aggregate(groupSet: ").append(groupSet)
-          .append(", groupSets: ").append(groupSets)
-          .append(", calls: ").append(aggCalls).append(")");
-    }
-
-    public ImmutableBitSet getGroupSet() {
-      return groupSet;
-    }
-
-    public ImmutableList<ImmutableBitSet> getGroupSets() {
-      return groupSets;
-    }
-
-    public List<AggregateCall> getAggCallList() {
-      return aggCalls;
-    }
-
-    public Aggregate.Group getGroupType() {
-      return Aggregate.Group.induce(groupSet, groupSets);
-    }
-  }
-
-  /** Mutable equivalent of {@link org.apache.calcite.rel.core.Sort}. */
-  protected static class MutableSort extends MutableSingleRel {
-    private final RelCollation collation;
-    private final RexNode offset;
-    private final RexNode fetch;
-
-    private MutableSort(MutableRel input, RelCollation collation,
-        RexNode offset, RexNode fetch) {
-      super(MutableRelType.SORT, input.rowType, input);
-      this.collation = collation;
-      this.offset = offset;
-      this.fetch = fetch;
-    }
-
-    static MutableSort of(MutableRel input, RelCollation collation,
-        RexNode offset, RexNode fetch) {
-      return new MutableSort(input, collation, offset, fetch);
-    }
-
-    @Override public boolean equals(Object obj) {
-      return obj == this
-          || obj instanceof MutableSort
-          && collation.equals(((MutableSort) obj).collation)
-          && Objects.equals(offset, ((MutableSort) obj).offset)
-          && Objects.equals(fetch, ((MutableSort) obj).fetch)
-          && input.equals(((MutableSort) obj).input);
-    }
-
-    @Override public int hashCode() {
-      return Objects.hash(input, collation, offset, fetch);
-    }
-
-    @Override public StringBuilder digest(StringBuilder buf) {
-      buf.append("Sort(collation: ").append(collation);
-      if (offset != null) {
-        buf.append(", offset: ").append(offset);
-      }
-      if (fetch != null) {
-        buf.append(", fetch: ").append(fetch);
-      }
-      return buf.append(")");
-    }
-  }
-
-  /** Base class for set-operations. */
-  protected abstract static class MutableSetOp extends MutableRel {
-    protected final List<MutableRel> inputs;
-
-    private MutableSetOp(RelOptCluster cluster, RelDataType rowType,
-        MutableRelType type, List<MutableRel> inputs) {
-      super(cluster, rowType, type);
-      this.inputs = inputs;
-    }
-
-    @Override public void setInput(int ordinalInParent, MutableRel input) {
-      inputs.set(ordinalInParent, input);
-      if (input != null) {
-        input.parent = this;
-        input.ordinalInParent = ordinalInParent;
-      }
-    }
-
-    @Override public List<MutableRel> getInputs() {
-      return inputs;
-    }
-
-    @Override public void childrenAccept(MutableRelVisitor visitor) {
-      for (MutableRel input : inputs) {
-        visitor.visit(input);
-      }
-    }
-  }
-
-  /** Mutable equivalent of
-   * {@link org.apache.calcite.rel.logical.LogicalUnion}. */
-  protected static class MutableUnion extends MutableSetOp {
-    public boolean all;
-
-    private MutableUnion(RelOptCluster cluster, RelDataType rowType,
-        List<MutableRel> inputs, boolean all) {
-      super(cluster, rowType, MutableRelType.UNION, inputs);
-      this.all = all;
-    }
-
-    static MutableUnion of(List<MutableRel> inputs, boolean all) {
-      assert inputs.size() >= 2;
-      final MutableRel input0 = inputs.get(0);
-      return new MutableUnion(input0.cluster, input0.rowType, inputs, all);
-    }
-
-    @Override public boolean equals(Object obj) {
-      return obj == this
-          || obj instanceof MutableUnion
-          && inputs.equals(((MutableUnion) obj).getInputs());
-    }
-
-    @Override public int hashCode() {
-      return Objects.hash(type, inputs);
-    }
-
-    @Override public StringBuilder digest(StringBuilder buf) {
-      return buf.append("Union");
-    }
-  }
-
-  /** Base Class for relations with two inputs */
-  private abstract static class MutableBiRel extends MutableRel {
-    protected MutableRel left;
-    protected MutableRel right;
-
-    MutableBiRel(MutableRelType type, RelOptCluster cluster, RelDataType rowType,
-                        MutableRel left, MutableRel right) {
-      super(cluster, rowType, type);
-      this.left = left;
-      left.parent = this;
-      left.ordinalInParent = 0;
-
-      this.right = right;
-      right.parent = this;
-      right.ordinalInParent = 1;
-    }
-
-    public void setInput(int ordinalInParent, MutableRel input) {
-      if (ordinalInParent > 1) {
-        throw new IllegalArgumentException();
-      }
-      if (ordinalInParent == 0) {
-        this.left = input;
-      } else {
-        this.right = input;
-      }
-      if (input != null) {
-        input.parent = this;
-        input.ordinalInParent = ordinalInParent;
-      }
-    }
-
-    public List<MutableRel> getInputs() {
-      return ImmutableList.of(left, right);
-    }
-
-    public MutableRel getLeft() {
-      return left;
-    }
-
-    public MutableRel getRight() {
-      return right;
-    }
-
-    public void childrenAccept(MutableRelVisitor visitor) {
-
-      visitor.visit(left);
-      visitor.visit(right);
-    }
-  }
-
-  /** Mutable equivalent of
-   * {@link org.apache.calcite.rel.logical.LogicalJoin}. */
-  private static class MutableJoin extends MutableBiRel {
-    //~ Instance fields --------------------------------------------------------
-
-    protected final RexNode condition;
-    protected final ImmutableSet<CorrelationId> variablesSet;
-
-    /**
-     * Values must be of enumeration {@link JoinRelType}, except that
-     * {@link JoinRelType#RIGHT} is disallowed.
-     */
-    protected JoinRelType joinType;
-
-    private MutableJoin(
-        RelDataType rowType,
-        MutableRel left,
-        MutableRel right,
-        RexNode condition,
-        JoinRelType joinType,
-        Set<CorrelationId> variablesSet) {
-      super(MutableRelType.JOIN, left.cluster, rowType, left, right);
-      this.condition = Preconditions.checkNotNull(condition);
-      this.variablesSet = ImmutableSet.copyOf(variablesSet);
-      this.joinType = Preconditions.checkNotNull(joinType);
-    }
-
-    public RexNode getCondition() {
-      return condition;
-    }
-
-    public JoinRelType getJoinType() {
-      return joinType;
-    }
-
-    public ImmutableSet<CorrelationId> getVariablesSet() {
-      return variablesSet;
-    }
-
-    static MutableJoin of(RelOptCluster cluster, MutableRel left,
-        MutableRel right, RexNode condition, JoinRelType joinType,
-        Set<CorrelationId> variablesStopped) {
-      List<RelDataTypeField> fieldList = Collections.emptyList();
-      RelDataType rowType =
-          SqlValidatorUtil.deriveJoinRowType(left.getRowType(),
-              right.getRowType(), joinType, cluster.getTypeFactory(), null,
-              fieldList);
-      return new MutableJoin(rowType, left, right, condition, joinType,
-          variablesStopped);
-    }
-
-    @Override public boolean equals(Object obj) {
-      return obj == this
-          || obj instanceof MutableJoin
-          && joinType == ((MutableJoin) obj).joinType
-          && condition.toString().equals(
-              ((MutableJoin) obj).condition.toString())
-          && left.equals(((MutableJoin) obj).left)
-          && right.equals(((MutableJoin) obj).right);
-    }
-
-    @Override public int hashCode() {
-      return Objects.hash(left, right, condition.toString(), joinType);
-    }
-
-    @Override public StringBuilder digest(StringBuilder buf) {
-      return buf.append("Join(left: ").append(left)
-          .append(", right:").append(right)
-          .append(")");
-    }
-  }
-
-  /** Utilities for dealing with {@link MutableRel}s. */
-  protected static class MutableRels {
-    public static boolean contains(MutableRel ancestor,
-        final MutableRel target) {
-      if (ancestor.equals(target)) {
-        // Short-cut common case.
-        return true;
-      }
-      try {
-        new MutableRelVisitor() {
-          @Override public void visit(MutableRel node) {
-            if (node.equals(target)) {
-              throw Util.FoundOne.NULL;
-            }
-            super.visit(node);
-          }
-          // CHECKSTYLE: IGNORE 1
-        }.go(ancestor);
-        return false;
-      } catch (Util.FoundOne e) {
-        return true;
-      }
-    }
-
-    public static MutableRel preOrderTraverseNext(MutableRel node) {
-      MutableRel parent = node.getParent();
-      int ordinal = node.ordinalInParent + 1;
-      while (parent != null) {
-        if (parent.getInputs().size() > ordinal) {
-          return parent.getInputs().get(ordinal);
-        }
-        node = parent;
-        parent = node.getParent();
-        ordinal = node.ordinalInParent + 1;
-      }
-      return null;
-    }
-
-    private static List<MutableRel> descendants(MutableRel query) {
-      final List<MutableRel> list = new ArrayList<>();
-      descendantsRecurse(list, query);
-      return list;
-    }
-
-    private static void descendantsRecurse(List<MutableRel> list,
-        MutableRel rel) {
-      list.add(rel);
-      for (MutableRel input : rel.getInputs()) {
-        descendantsRecurse(list, input);
-      }
-    }
-
-    /** Returns whether two relational expressions have the same row-type. */
-    public static boolean equalType(String desc0, MutableRel rel0, String desc1,
-        MutableRel rel1, Litmus litmus) {
-      return RelOptUtil.equal(desc0, rel0.getRowType(),
-          desc1, rel1.getRowType(), litmus);
-    }
-
-    /** Within a relational expression {@code query}, replaces occurrences of
-     * {@code find} with {@code replace}.
-     *
-     * <p>Assumes relational expressions (and their descendants) are not null.
-     * Does not handle cycles. */
-    public static Replacement replace(MutableRel query, MutableRel find,
-        MutableRel replace) {
-      if (find.equals(replace)) {
-        // Short-cut common case.
-        return null;
-      }
-      assert equalType("find", find, "replace", replace, Litmus.THROW);
-      return replaceRecurse(query, find, replace);
-    }
-
-    /** Helper for {@link #replace}. */
-    private static Replacement replaceRecurse(MutableRel query,
-        MutableRel find, MutableRel replace) {
-      if (find.equals(query)) {
-        query.replaceInParent(replace);
-        return new Replacement(query, replace);
-      }
-      for (MutableRel input : query.getInputs()) {
-        Replacement r = replaceRecurse(input, find, replace);
-        if (r != null) {
-          return r;
-        }
-      }
-      return null;
-    }
-
-    /** Based on
-     * {@link org.apache.calcite.rel.rules.ProjectRemoveRule#strip}. */
-    public static MutableRel strip(MutableProject project) {
-      return isTrivial(project) ? project.getInput() : project;
-    }
-
-    /** Based on
-     * {@link org.apache.calcite.rel.rules.ProjectRemoveRule#isTrivial(org.apache.calcite.rel.core.Project)}. */
-    public static boolean isTrivial(MutableProject project) {
-      MutableRel child = project.getInput();
-      final RelDataType childRowType = child.getRowType();
-      return RexUtil.isIdentity(project.getProjects(), childRowType);
-    }
-
-    /** Equivalent to
-     * {@link RelOptUtil#createProject(org.apache.calcite.rel.RelNode, java.util.List)}
-     * for {@link MutableRel}. */
-    public static MutableRel createProject(final MutableRel child,
-        final List<Integer> posList) {
-      final RelDataType rowType = child.getRowType();
-      if (Mappings.isIdentity(posList, rowType.getFieldCount())) {
-        return child;
-      }
-      return MutableProject.of(
-          RelOptUtil.permute(child.cluster.getTypeFactory(), rowType,
-              Mappings.bijection(posList)),
-          child,
-          new AbstractList<RexNode>() {
-            public int size() {
-              return posList.size();
-            }
-
-            public RexNode get(int index) {
-              final int pos = posList.get(index);
-              return RexInputRef.of(pos, rowType);
-            }
-          });
-    }
-
-    /** Equivalence to {@link org.apache.calcite.plan.RelOptUtil#createCastRel}
-     * for {@link MutableRel}. */
-    public static MutableRel createCastRel(MutableRel rel,
-        RelDataType castRowType, boolean rename) {
-      RelDataType rowType = rel.getRowType();
-      if (RelOptUtil.areRowTypesEqual(rowType, castRowType, rename)) {
-        // nothing to do
-        return rel;
-      }
-      List<RexNode> castExps =
-          RexUtil.generateCastExpressions(rel.cluster.getRexBuilder(),
-              castRowType, rowType);
-      final List<String> fieldNames =
-          rename ? castRowType.getFieldNames() : rowType.getFieldNames();
-      return MutableProject.of(rel, castExps, fieldNames);
-    }
-  }
-
-  /** Visitor that prints an indented tree of {@link MutableRel}s. */
-  protected static class MutableRelDumper extends MutableRelVisitor {
-    private final StringBuilder buf = new StringBuilder();
-    private int level;
-
-    @Override public void visit(MutableRel node) {
-      Spaces.append(buf, level * 2);
-      if (node == null) {
-        buf.append("null");
-      } else {
-        node.digest(buf);
-        buf.append("\n");
-        ++level;
-        super.visit(node);
-        --level;
-      }
-    }
-
-    public String apply(MutableRel rel) {
-      go(rel);
-      return buf.toString();
-    }
-  }
-
   /** Returns if one rel is weaker than another. */
   protected boolean isWeaker(MutableRel rel0, MutableRel rel) {
     if (rel0 == rel || equivalents.get(rel0).contains(rel)) {
@@ -2225,7 +1305,7 @@ public class SubstitutionVisitor {
       return false;
     }
 
-    if (!rel.getRowType().equals(rel0.getRowType())) {
+    if (!rel.rowType.equals(rel0.rowType)) {
       return false;
     }
 
@@ -2238,12 +1318,18 @@ public class SubstitutionVisitor {
 
     RexExecutorImpl rexImpl =
         (RexExecutorImpl) (rel.cluster.getPlanner().getExecutor());
-    RexImplicationChecker rexImplicationChecker = new RexImplicationChecker(
-        rel.cluster.getRexBuilder(),
-        rexImpl, rel.getRowType());
+    RexImplicationChecker rexImplicationChecker =
+        new RexImplicationChecker(
+            rel.cluster.getRexBuilder(), rexImpl, rel.rowType);
+
+    return rexImplicationChecker.implies(((MutableFilter) rel0).condition,
+        ((MutableFilter) rel).condition);
+  }
 
-    return rexImplicationChecker.implies(((MutableFilter) rel0).getCondition(),
-        ((MutableFilter) rel).getCondition());
+  /** Returns whether two relational expressions have the same row-type. */
+  public static boolean equalType(String desc0, MutableRel rel0, String desc1,
+      MutableRel rel1, Litmus litmus) {
+    return RelOptUtil.equal(desc0, rel0.rowType, desc1, rel1.rowType, litmus);
   }
 
   /** Operand to a {@link UnifyRule}. */

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/rel/mutable/Holder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/Holder.java b/core/src/main/java/org/apache/calcite/rel/mutable/Holder.java
new file mode 100644
index 0000000..b4da54f
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/Holder.java
@@ -0,0 +1,46 @@
+/*
+ * 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.mutable;
+
+import org.apache.calcite.rel.type.RelDataType;
+
+/** Implementation of {@link MutableRel} whose only purpose is to have a
+ * child. Used as the root of a tree. */
+public class Holder extends MutableSingleRel {
+  private Holder(RelDataType rowType, MutableRel input) {
+    super(MutableRelType.HOLDER, rowType, input);
+  }
+
+  /**
+   * Creates a Holder.
+   *
+   * @param input Input relational expression
+   */
+  public static Holder of(MutableRel input) {
+    return new Holder(input.rowType, input);
+  }
+
+  @Override public StringBuilder digest(StringBuilder buf) {
+    return buf.append("Holder");
+  }
+
+  @Override public MutableRel clone() {
+    return Holder.of(input.clone());
+  }
+}
+
+// End Holder.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/rel/mutable/MutableAggregate.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableAggregate.java b/core/src/main/java/org/apache/calcite/rel/mutable/MutableAggregate.java
new file mode 100644
index 0000000..546b62a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableAggregate.java
@@ -0,0 +1,97 @@
+/*
+ * 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.mutable;
+
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.Objects;
+
+/** Mutable equivalent of {@link org.apache.calcite.rel.core.Aggregate}. */
+public class MutableAggregate extends MutableSingleRel {
+  public final boolean indicator;
+  public final ImmutableBitSet groupSet;
+  public final ImmutableList<ImmutableBitSet> groupSets;
+  public final List<AggregateCall> aggCalls;
+
+  private MutableAggregate(MutableRel input, RelDataType rowType,
+      boolean indicator, ImmutableBitSet groupSet,
+      List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
+    super(MutableRelType.AGGREGATE, rowType, input);
+    this.indicator = indicator;
+    this.groupSet = groupSet;
+    this.groupSets = groupSets == null
+        ? ImmutableList.of(groupSet)
+        : ImmutableList.copyOf(groupSets);
+    this.aggCalls = aggCalls;
+  }
+
+  /**
+   * Creates a MutableAggregate.
+   *
+   * @param input     Input relational expression
+   * @param indicator Whether row type should include indicator fields to
+   *                  indicate which grouping set is active; must be true if
+   *                  aggregate is not simple
+   * @param groupSet  Bit set of grouping fields
+   * @param groupSets List of all grouping sets; null for just {@code groupSet}
+   * @param aggCalls  Collection of calls to aggregate functions
+   */
+  public static MutableAggregate of(MutableRel input, boolean indicator,
+      ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
+      List<AggregateCall> aggCalls) {
+    RelDataType rowType =
+        Aggregate.deriveRowType(input.cluster.getTypeFactory(),
+            input.rowType, indicator, groupSet, groupSets, aggCalls);
+    return new MutableAggregate(input, rowType, indicator, groupSet,
+        groupSets, aggCalls);
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof MutableAggregate
+        && groupSet.equals(((MutableAggregate) obj).groupSet)
+        && aggCalls.equals(((MutableAggregate) obj).aggCalls)
+        && input.equals(((MutableAggregate) obj).input);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash(input, groupSet, aggCalls);
+  }
+
+  @Override public StringBuilder digest(StringBuilder buf) {
+    return buf.append("Aggregate(groupSet: ").append(groupSet)
+        .append(", groupSets: ").append(groupSets)
+        .append(", calls: ").append(aggCalls).append(")");
+  }
+
+  public Aggregate.Group getGroupType() {
+    return Aggregate.Group.induce(groupSet, groupSets);
+  }
+
+  @Override public MutableRel clone() {
+    return MutableAggregate.of(input.clone(),
+        indicator, groupSet, groupSets, aggCalls);
+  }
+}
+
+// End MutableAggregate.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/rel/mutable/MutableBiRel.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableBiRel.java b/core/src/main/java/org/apache/calcite/rel/mutable/MutableBiRel.java
new file mode 100644
index 0000000..2c082ac
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableBiRel.java
@@ -0,0 +1,77 @@
+/*
+ * 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.mutable;
+
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.rel.type.RelDataType;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/** Mutable equivalent of {@link org.apache.calcite.rel.BiRel}. */
+abstract class MutableBiRel extends MutableRel {
+  protected MutableRel left;
+  protected MutableRel right;
+
+  protected MutableBiRel(MutableRelType type, RelOptCluster cluster,
+      RelDataType rowType, MutableRel left, MutableRel right) {
+    super(cluster, rowType, type);
+    this.left = left;
+    left.parent = this;
+    left.ordinalInParent = 0;
+
+    this.right = right;
+    right.parent = this;
+    right.ordinalInParent = 1;
+  }
+
+  public void setInput(int ordinalInParent, MutableRel input) {
+    if (ordinalInParent > 1) {
+      throw new IllegalArgumentException();
+    }
+    if (ordinalInParent == 0) {
+      this.left = input;
+    } else {
+      this.right = input;
+    }
+    if (input != null) {
+      input.parent = this;
+      input.ordinalInParent = ordinalInParent;
+    }
+  }
+
+  public List<MutableRel> getInputs() {
+    return ImmutableList.of(left, right);
+  }
+
+  public MutableRel getLeft() {
+    return left;
+  }
+
+  public MutableRel getRight() {
+    return right;
+  }
+
+  public void childrenAccept(MutableRelVisitor visitor) {
+
+    visitor.visit(left);
+    visitor.visit(right);
+  }
+}
+
+// End MutableBiRel.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/rel/mutable/MutableCalc.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableCalc.java b/core/src/main/java/org/apache/calcite/rel/mutable/MutableCalc.java
new file mode 100644
index 0000000..3aca910
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableCalc.java
@@ -0,0 +1,64 @@
+/*
+ * 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.mutable;
+
+import org.apache.calcite.rex.RexProgram;
+
+import java.util.Objects;
+
+/** Mutable equivalent of {@link org.apache.calcite.rel.core.Calc}. */
+public class MutableCalc extends MutableSingleRel {
+  public final RexProgram program;
+
+  private MutableCalc(MutableRel input, RexProgram program) {
+    super(MutableRelType.CALC, program.getOutputRowType(), input);
+    this.program = program;
+  }
+
+  /**
+   * Creates a MutableCalc
+   *
+   * @param input   Input relational expression
+   * @param program Calc program
+   */
+  public static MutableCalc of(MutableRel input, RexProgram program) {
+    return new MutableCalc(input, program);
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof MutableCalc
+        && MutableRel.STRING_EQUIVALENCE.equivalent(
+            program, ((MutableCalc) obj).program)
+        && input.equals(((MutableCalc) obj).input);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash(input,
+        MutableRel.STRING_EQUIVALENCE.hash(program));
+  }
+
+  @Override public StringBuilder digest(StringBuilder buf) {
+    return buf.append("Calc(program: ").append(program).append(")");
+  }
+
+  @Override public MutableRel clone() {
+    return MutableCalc.of(input.clone(), program);
+  }
+}
+
+// End MutableCalc.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/rel/mutable/MutableCollect.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableCollect.java b/core/src/main/java/org/apache/calcite/rel/mutable/MutableCollect.java
new file mode 100644
index 0000000..4e413f5
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableCollect.java
@@ -0,0 +1,65 @@
+/*
+ * 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.mutable;
+
+import org.apache.calcite.rel.type.RelDataType;
+
+import java.util.Objects;
+
+/** Mutable equivalent of {@link org.apache.calcite.rel.core.Collect}. */
+public class MutableCollect extends MutableSingleRel {
+  public final String fieldName;
+
+  private MutableCollect(RelDataType rowType,
+      MutableRel input, String fieldName) {
+    super(MutableRelType.COLLECT, rowType, input);
+    this.fieldName = fieldName;
+  }
+
+  /**
+   * Creates a MutableCollect.
+   *
+   * @param rowType   Row type
+   * @param input     Input relational expression
+   * @param fieldName Name of the sole output field
+   */
+  public static MutableCollect of(RelDataType rowType,
+      MutableRel input, String fieldName) {
+    return new MutableCollect(rowType, input, fieldName);
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof MutableCollect
+        && fieldName.equals(((MutableCollect) obj).fieldName)
+        && input.equals(((MutableCollect) obj).input);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash(input, fieldName);
+  }
+
+  @Override public StringBuilder digest(StringBuilder buf) {
+    return buf.append("Collect(fieldName: ").append(fieldName).append(")");
+  }
+
+  @Override public MutableRel clone() {
+    return MutableCollect.of(rowType, input.clone(), fieldName);
+  }
+}
+
+// End MutableCollect.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/rel/mutable/MutableCorrelate.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableCorrelate.java b/core/src/main/java/org/apache/calcite/rel/mutable/MutableCorrelate.java
new file mode 100644
index 0000000..5be0152
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableCorrelate.java
@@ -0,0 +1,91 @@
+/*
+ * 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.mutable;
+
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.SemiJoinType;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.Objects;
+
+/** Mutable equivalent of {@link org.apache.calcite.rel.core.Correlate}. */
+public class MutableCorrelate extends MutableBiRel {
+  public final CorrelationId correlationId;
+  public final ImmutableBitSet requiredColumns;
+  public final SemiJoinType joinType;
+
+  private MutableCorrelate(
+      RelDataType rowType,
+      MutableRel left,
+      MutableRel right,
+      CorrelationId correlationId,
+      ImmutableBitSet requiredColumns,
+      SemiJoinType joinType) {
+    super(MutableRelType.CORRELATE, left.cluster, rowType, left, right);
+    this.correlationId = correlationId;
+    this.requiredColumns = requiredColumns;
+    this.joinType = joinType;
+  }
+
+  /**
+   * Creates a MutableCorrelate.
+   *
+   * @param rowType         Row type
+   * @param left            Left input relational expression
+   * @param right           Right input relational expression
+   * @param correlationId   Variable name for the row of left input
+   * @param requiredColumns Required columns
+   * @param joinType        Join type
+   */
+  public static MutableCorrelate of(RelDataType rowType, MutableRel left,
+      MutableRel right, CorrelationId correlationId,
+      ImmutableBitSet requiredColumns, SemiJoinType joinType) {
+    return new MutableCorrelate(rowType, left, right, correlationId,
+        requiredColumns, joinType);
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof MutableCorrelate
+        && correlationId.equals(
+            ((MutableCorrelate) obj).correlationId)
+        && requiredColumns.equals(
+            ((MutableCorrelate) obj).requiredColumns)
+        && joinType == ((MutableCorrelate) obj).joinType
+        && left.equals(((MutableCorrelate) obj).left)
+        && right.equals(((MutableCorrelate) obj).right);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash(left, right, correlationId, requiredColumns, joinType);
+  }
+
+  @Override public StringBuilder digest(StringBuilder buf) {
+    return buf.append("Correlate(correlationId: ").append(correlationId)
+        .append(", requiredColumns: ").append(requiredColumns)
+        .append(", joinType: ").append(joinType)
+        .append(")");
+  }
+
+  @Override public MutableRel clone() {
+    return MutableCorrelate.of(rowType, left.clone(),
+        right.clone(), correlationId, requiredColumns, joinType);
+  }
+}
+
+// End MutableCorrelate.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/rel/mutable/MutableExchange.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableExchange.java b/core/src/main/java/org/apache/calcite/rel/mutable/MutableExchange.java
new file mode 100644
index 0000000..2c3c28d
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableExchange.java
@@ -0,0 +1,62 @@
+/*
+ * 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.mutable;
+
+import org.apache.calcite.rel.RelDistribution;
+
+import java.util.Objects;
+
+/** Mutable equivalent of {@link org.apache.calcite.rel.core.Exchange}. */
+public class MutableExchange extends MutableSingleRel {
+  public final RelDistribution distribution;
+
+  private MutableExchange(MutableRel input, RelDistribution distribution) {
+    super(MutableRelType.EXCHANGE, input.rowType, input);
+    this.distribution = distribution;
+  }
+
+  /**
+   * Creates a MutableExchange.
+   *
+   * @param input         Input relational expression
+   * @param distribution  Distribution specification
+   */
+  public static MutableExchange of(MutableRel input, RelDistribution distribution) {
+    return new MutableExchange(input, distribution);
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof MutableExchange
+        && distribution.equals(((MutableExchange) obj).distribution)
+        && input.equals(((MutableExchange) obj).input);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash(input, distribution);
+  }
+
+  @Override public StringBuilder digest(StringBuilder buf) {
+    return buf.append("Exchange(distribution: ").append(distribution).append(")");
+  }
+
+  @Override public MutableRel clone() {
+    return MutableExchange.of(input.clone(), distribution);
+  }
+}
+
+// End MutableExchange.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/rel/mutable/MutableFilter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableFilter.java b/core/src/main/java/org/apache/calcite/rel/mutable/MutableFilter.java
new file mode 100644
index 0000000..787487f
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableFilter.java
@@ -0,0 +1,64 @@
+/*
+ * 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.mutable;
+
+import org.apache.calcite.rex.RexNode;
+
+import java.util.Objects;
+
+/** Mutable equivalent of {@link org.apache.calcite.rel.core.Filter}. */
+public class MutableFilter extends MutableSingleRel {
+  public final RexNode condition;
+
+  private MutableFilter(MutableRel input, RexNode condition) {
+    super(MutableRelType.FILTER, input.rowType, input);
+    this.condition = condition;
+  }
+
+  /**
+   * Creates a MutableFilter.
+   *
+   * @param input     Input relational expression
+   * @param condition Boolean expression which determines whether a row is
+   *                  allowed to pass
+   */
+  public static MutableFilter of(MutableRel input, RexNode condition) {
+    return new MutableFilter(input, condition);
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof MutableFilter
+        && condition.toString().equals(
+            ((MutableFilter) obj).condition.toString())
+        && input.equals(((MutableFilter) obj).input);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash(input, condition.toString());
+  }
+
+  @Override public StringBuilder digest(StringBuilder buf) {
+    return buf.append("Filter(condition: ").append(condition).append(")");
+  }
+
+  @Override public MutableRel clone() {
+    return MutableFilter.of(input.clone(), condition);
+  }
+}
+
+// End MutableFilter.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/316a0587/core/src/main/java/org/apache/calcite/rel/mutable/MutableIntersect.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableIntersect.java b/core/src/main/java/org/apache/calcite/rel/mutable/MutableIntersect.java
new file mode 100644
index 0000000..5829f80
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableIntersect.java
@@ -0,0 +1,55 @@
+/*
+ * 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.mutable;
+
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.rel.type.RelDataType;
+
+import java.util.List;
+
+/** Mutable equivalent of {@link org.apache.calcite.rel.core.Intersect}. */
+public class MutableIntersect extends MutableSetOp {
+  private MutableIntersect(RelOptCluster cluster, RelDataType rowType,
+      List<MutableRel> inputs, boolean all) {
+    super(cluster, rowType, MutableRelType.INTERSECT, inputs, all);
+  }
+
+  /**
+   * Creates a MutableIntersect.
+   *
+   * @param rowType Row type
+   * @param inputs  Input relational expressions
+   * @param all     Whether to perform a multiset intersection or a set
+   *                intersection
+   */
+  public static MutableIntersect of(
+      RelDataType rowType, List<MutableRel> inputs, boolean all) {
+    assert inputs.size() >= 2;
+    final MutableRel input0 = inputs.get(0);
+    return new MutableIntersect(input0.cluster, rowType, inputs, all);
+  }
+
+  @Override public StringBuilder digest(StringBuilder buf) {
+    return buf.append("Intersect(all: ").append(all).append(")");
+  }
+
+  @Override public MutableRel clone() {
+    return MutableIntersect.of(rowType, cloneChildren(), all);
+  }
+}
+
+// End MutableIntersect.java