You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2015/04/01 22:05:15 UTC

[1/3] incubator-calcite git commit: Fix TestTraitPropagation.withoutHack (withoutHack now succeeds, withHack now fails).

Repository: incubator-calcite
Updated Branches:
  refs/heads/master a13137dc9 -> 3b55c35a5


Fix TestTraitPropagation.withoutHack (withoutHack now succeeds, withHack now fails).

Deduce the collations of a RelSubset by looking at its traits. (For other kinds of RelNode that would cause a cycle.)

Remove AbstractConverters again (Jacques had restored them, but performance was terrible.)

When creating a RelSubset make sure that its traits are simple. (Occurs when creating a subset to convert an existing rel that has multiple collations to a different calling convention. The subset can only have one collation.)

Add a version of RelOptRule.convert that takes a single trait, to avoid simplifying other traits of that RelNode. Many current calls to convert(RelNode, RelTraitSet) should probably use this method.

Add a short-cut to RelTraitSet.replace for the case where the new trait is canonized and the same as the old.

In TestTraitPropagation, use SortRemoveRule; implement RelOptTable.unwrap and Table.getStatistic and use EnumerableTableScan.create, so that table scan is sorted; make PhysSortRule a ConvertRule, so that its input ends up in the same RelSet; fix a typo giving PhysTable the wrong convention.

Call simplify in RelOptRule.convert rather than VolcanoPlanner.


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

Branch: refs/heads/master
Commit: 3b55c35a58b45d2a8538f9bc77f11e51a2d45e6b
Parents: b312031
Author: Julian Hyde <jh...@apache.org>
Authored: Tue Mar 3 20:23:02 2015 -0800
Committer: julianhyde <jh...@apache.org>
Committed: Wed Apr 1 01:52:46 2015 -0400

----------------------------------------------------------------------
 .../org/apache/calcite/plan/RelOptRule.java     |  27 +-
 .../org/apache/calcite/plan/RelTraitSet.java    |  28 +-
 .../org/apache/calcite/plan/volcano/RelSet.java |  44 +-
 .../calcite/plan/volcano/VolcanoPlanner.java    |   6 +-
 .../calcite/rel/metadata/RelMdCollation.java    |   7 +
 .../plan/volcano/TestTraitPropagation.java      | 418 ------------------
 .../plan/volcano/TraitPropagationTest.java      | 433 +++++++++++++++++++
 .../org/apache/calcite/test/CalciteSuite.java   |   2 +
 8 files changed, 499 insertions(+), 466 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3b55c35a/core/src/main/java/org/apache/calcite/plan/RelOptRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptRule.java b/core/src/main/java/org/apache/calcite/plan/RelOptRule.java
index 058736d..464568a 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptRule.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptRule.java
@@ -480,9 +480,8 @@ public abstract class RelOptRule {
   }
 
   /**
-   * Converts a relation expression to a give set of traits, if it does not
-   * already have those traits. If the conversion is not possible, returns
-   * null.
+   * Converts a relation expression to a given set of traits, if it does not
+   * already have those traits.
    *
    * @param rel      Relational expression to convert
    * @param toTraits desired traits
@@ -511,6 +510,28 @@ public abstract class RelOptRule {
   }
 
   /**
+   * Converts one trait of a relational expression, if it does not
+   * already have that trait.
+   *
+   * @param rel      Relational expression to convert
+   * @param toTrait  Desired trait
+   * @return a relational expression with the desired trait; never null
+   */
+  public static RelNode convert(RelNode rel, RelTrait toTrait) {
+    RelOptPlanner planner = rel.getCluster().getPlanner();
+    RelTraitSet outTraits = rel.getTraitSet();
+    if (toTrait != null) {
+      outTraits = outTraits.replace(toTrait);
+    }
+
+    if (rel.getTraitSet().matches(outTraits)) {
+      return rel;
+    }
+
+    return planner.changeTraits(rel, outTraits.simplify());
+  }
+
+  /**
    * Converts a list of relational expressions.
    *
    * @param rels     Relational expressions

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3b55c35a/core/src/main/java/org/apache/calcite/plan/RelTraitSet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelTraitSet.java b/core/src/main/java/org/apache/calcite/plan/RelTraitSet.java
index f4b0eac..5499bdb 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelTraitSet.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelTraitSet.java
@@ -180,6 +180,10 @@ public final class RelTraitSet extends AbstractList<RelTrait> {
    */
   public RelTraitSet replace(
       RelTrait trait) {
+    // Quick check for common case
+    if (containsShallow(traits, trait)) {
+      return this;
+    }
     final RelTraitDef traitDef = trait.getTraitDef();
     int index = findIndex(traitDef);
     if (index < 0) {
@@ -190,6 +194,18 @@ public final class RelTraitSet extends AbstractList<RelTrait> {
     return replace(index, trait);
   }
 
+  /** Returns whether an element occurs within an array.
+   *
+   * <p>Uses {@code ==}, not {@link #equals}. Nulls are allowed. */
+  private static <T> boolean containsShallow(T[] ts, RelTrait seek) {
+    for (T t : ts) {
+      if (t == seek) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /** Replaces the trait(s) of a given type with a list of traits of the same
    * type.
    *
@@ -259,7 +275,7 @@ public final class RelTraitSet extends AbstractList<RelTrait> {
     if (trait instanceof RelCompositeTrait) {
       // Composite traits are canonized on creation
       //noinspection unchecked
-      return (T) trait;
+      return trait;
     }
 
     //noinspection unchecked
@@ -452,6 +468,7 @@ public final class RelTraitSet extends AbstractList<RelTrait> {
     // Then we can justify the cost of computing RelTraitSet.string in the
     // constructor.
     final RelTrait canonizedTrait = canonize(trait);
+    assert canonizedTrait != null;
     List<RelTrait> newTraits;
     switch (traits.length) {
     case 0:
@@ -511,13 +528,10 @@ public final class RelTraitSet extends AbstractList<RelTrait> {
     for (int i = 0; i < traits.length; i++) {
       final RelTrait trait = traits[i];
       if (trait instanceof RelCompositeTrait) {
-        //noinspection unchecked
-        final RelCompositeTrait<RelMultipleTrait> compositeTrait =
-            (RelCompositeTrait<RelMultipleTrait>) trait;
         x = x.replace(i,
-            compositeTrait.size() == 0
-                ?  trait.getTraitDef().getDefault()
-                : compositeTrait.trait(0));
+            ((RelCompositeTrait) trait).size() == 1
+                ? ((RelCompositeTrait) trait).trait(0)
+                : trait.getTraitDef().getDefault());
       }
     }
     return x;

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3b55c35a/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java b/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java
index ed2c6b7..93d8509 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java
@@ -16,14 +16,6 @@
  */
 package org.apache.calcite.plan.volcano;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.logging.Logger;
-
-import org.apache.calcite.plan.Convention;
-import org.apache.calcite.plan.ConventionTraitDef;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptListener;
 import org.apache.calcite.plan.RelOptUtil;
@@ -34,6 +26,12 @@ import org.apache.calcite.util.trace.CalciteTrace;
 
 import com.google.common.collect.ImmutableList;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
 /**
  * A <code>RelSet</code> is an equivalence-set of expressions; that is, a set of
  * expressions which have identical semantics. We are generally interested in
@@ -158,35 +156,11 @@ class RelSet {
       final VolcanoPlanner planner =
           (VolcanoPlanner) cluster.getPlanner();
 
-//      if (planner.root != null
-//          && planner.root.set == this) {
-//        planner.ensureRootConverters();
-//      }
-
-      // Add converters to convert the new subset to each existing subset.
-      for (RelSubset subset1 : subsets) {
-        if (subset1.getConvention() == Convention.NONE) {
-          continue;
-        }
-        final AbstractConverter converter =
-            new AbstractConverter(
-                cluster, subset, ConventionTraitDef.INSTANCE,
-                subset1.getTraitSet());
-        planner.register(converter, subset1);
-      }
-
       subsets.add(subset);
 
-      // Add converters to convert each existing subset to this subset.
-      for (RelSubset subset1 : subsets) {
-        if (subset1 == subset) {
-          continue;
-        }
-        final AbstractConverter converter =
-            new AbstractConverter(
-                cluster, subset1, ConventionTraitDef.INSTANCE,
-                traits);
-        planner.register(converter, subset);
+      if (planner.root != null
+          && planner.root.set == this) {
+        planner.ensureRootConverters();
       }
 
       if (planner.listener != null) {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3b55c35a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
----------------------------------------------------------------------
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 d26ad6b..06bff19 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
@@ -684,15 +684,15 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
   }
 
   public RelNode changeTraits(final RelNode rel, RelTraitSet toTraits) {
-    assert !rel.getTraitSet().equals(toTraits)
-        : "pre: !rel.getTraits().equals(toTraits)";
+    assert !rel.getTraitSet().equals(toTraits);
+    assert toTraits.allSimple();
 
     RelSubset rel2 = ensureRegistered(rel, null);
     if (rel2.getTraitSet().equals(toTraits)) {
       return rel2;
     }
 
-    return rel2.set.getOrCreateSubset(rel.getCluster(), toTraits);
+    return rel2.set.getOrCreateSubset(rel.getCluster(), toTraits.simplify());
   }
 
   public RelOptPlanner chooseDelegate() {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3b55c35a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
index 98830e8..614c111 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
@@ -20,7 +20,9 @@ import org.apache.calcite.adapter.enumerable.EnumerableMergeJoin;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.hep.HepRelVertex;
+import org.apache.calcite.plan.volcano.RelSubset;
 import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollationTraitDef;
 import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
@@ -134,6 +136,11 @@ public class RelMdCollation {
     return RelMetadataQuery.collations(rel.getCurrentRel());
   }
 
+  public ImmutableList<RelCollation> collations(RelSubset rel) {
+    return ImmutableList.copyOf(
+        rel.getTraitSet().getTraits(RelCollationTraitDef.INSTANCE));
+  }
+
   // Helper methods
 
   /** Helper method to determine a

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3b55c35a/core/src/test/java/org/apache/calcite/plan/volcano/TestTraitPropagation.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/plan/volcano/TestTraitPropagation.java b/core/src/test/java/org/apache/calcite/plan/volcano/TestTraitPropagation.java
deleted file mode 100644
index e2dc6fd..0000000
--- a/core/src/test/java/org/apache/calcite/plan/volcano/TestTraitPropagation.java
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.calcite.plan.volcano;
-
-import static org.junit.Assert.assertEquals;
-
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.util.Collections;
-import java.util.List;
-import java.util.Properties;
-
-import org.junit.Test;
-
-import com.google.common.collect.ImmutableList;
-
-
-import org.apache.calcite.adapter.enumerable.EnumerableConvention;
-import org.apache.calcite.adapter.enumerable.EnumerableTableScan;
-import org.apache.calcite.adapter.java.JavaTypeFactory;
-import org.apache.calcite.jdbc.CalcitePrepare;
-import org.apache.calcite.plan.Convention;
-import org.apache.calcite.plan.ConventionTraitDef;
-import org.apache.calcite.plan.RelOptAbstractTable;
-import org.apache.calcite.plan.RelOptCluster;
-import org.apache.calcite.plan.RelOptCost;
-import org.apache.calcite.plan.RelOptPlanner;
-import org.apache.calcite.plan.RelOptQuery;
-import org.apache.calcite.plan.RelOptRule;
-import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.RelOptRuleOperand;
-import org.apache.calcite.plan.RelOptSchema;
-import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.plan.RelTrait;
-import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.plan.volcano.AbstractConverter.ExpandConversionRule;
-import org.apache.calcite.prepare.CalciteCatalogReader;
-import org.apache.calcite.rel.AbstractRelNode;
-import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelCollationTraitDef;
-import org.apache.calcite.rel.RelCollations;
-import org.apache.calcite.rel.RelFieldCollation;
-import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.core.Aggregate;
-import org.apache.calcite.rel.core.AggregateCall;
-import org.apache.calcite.rel.core.Project;
-import org.apache.calcite.rel.core.Sort;
-import org.apache.calcite.rel.logical.LogicalAggregate;
-import org.apache.calcite.rel.logical.LogicalProject;
-import org.apache.calcite.rel.metadata.RelMetadataQuery;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.rex.RexBuilder;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.schema.SchemaPlus;
-import org.apache.calcite.schema.Table;
-import org.apache.calcite.schema.impl.AbstractTable;
-import org.apache.calcite.server.CalciteServerStatement;
-import org.apache.calcite.sql.SqlExplainLevel;
-import org.apache.calcite.sql.fun.SqlStdOperatorTable;
-import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.calcite.tools.FrameworkConfig;
-import org.apache.calcite.tools.Frameworks;
-import org.apache.calcite.tools.RuleSet;
-import org.apache.calcite.tools.RuleSets;
-import org.apache.calcite.util.ImmutableBitSet;
-
-/**
- * Tests that determine whether trait propagation work in Volcano Planner
- */
-public class TestTraitPropagation {
-
-  static final Convention PHYSICAL =
-      new Convention.Impl("PHYSICAL", Phys.class);
-  static final RelCollation COLLATION =
-      RelCollations.of(new RelFieldCollation(0,
-        RelFieldCollation.Direction.ASCENDING,
-        RelFieldCollation.NullDirection.FIRST));
-
-  static final RuleSet RULES_NO_HACK = RuleSets.ofList(
-      PhysAggRule.INSTANCE, //
-      PhysProjRule.INSTANCE, //
-      PhysTableRule.INSTANCE, //
-      PhysSortRule.INSTANCE, //
-      ExpandConversionRule.INSTANCE //
-  );
-
-  static final RuleSet RULES_HACK = RuleSets.ofList(
-      PhysAggRule.INSTANCE, //
-      PhysProjRule.INSTANCE_HACK, //
-      PhysTableRule.INSTANCE, //
-      PhysSortRule.INSTANCE, //
-      ExpandConversionRule.INSTANCE //
-  );
-
-  @Test
-  public void withoutHack() throws Exception {
-    RelNode planned = run(new PropAction(), RULES_NO_HACK);
-    System.out.println(RelOptUtil.dumpPlan("LOGICAL PLAN", planned, false,
-        SqlExplainLevel.ALL_ATTRIBUTES));
-    assertEquals("Sortedness was not propagated", 3,
-        RelMetadataQuery.getCumulativeCost(planned).getRows(), 0);
-  }
-
-  @Test
-  public void withHack() throws Exception {
-    RelNode planned = run(new PropAction(), RULES_HACK);
-    System.out.println(RelOptUtil.dumpPlan("LOGICAL PLAN", planned, false,
-        SqlExplainLevel.ALL_ATTRIBUTES));
-    assertEquals("Sortedness was not propagated", 3,
-        RelMetadataQuery.getCumulativeCost(planned).getRows(), 0);
-  }
-
-  /**
-   * Materialized anonymous class for simplicity
-   */
-  private class PropAction {
-    public RelNode apply(RelOptCluster cluster, RelOptSchema relOptSchema,
-        SchemaPlus rootSchema) {
-      final RelDataTypeFactory typeFactory = cluster.getTypeFactory();
-      final RexBuilder rexBuilder = cluster.getRexBuilder();
-      final RelOptPlanner planner = cluster.getPlanner();
-
-      final RelDataType stringType = typeFactory.createJavaType(String.class);
-      final RelDataType integerType = typeFactory.createJavaType(Integer.class);
-      final RelDataType sqlBigInt = typeFactory
-          .createSqlType(SqlTypeName.BIGINT);
-
-      // SELECT * from T;
-      final Table table = new AbstractTable() {
-        public RelDataType getRowType(RelDataTypeFactory typeFactory) {
-          return typeFactory.builder().add("s", stringType)
-              .add("i", integerType).build();
-        }
-      };
-
-      final RelOptAbstractTable t1 = new RelOptAbstractTable(relOptSchema,
-          "t1", table.getRowType(typeFactory)) {
-      };
-
-      final RelNode rt1 = new EnumerableTableScan(cluster,
-          cluster.traitSetOf(EnumerableConvention.INSTANCE), t1,
-          Object[].class);
-
-      // project s column
-      RelNode project = new LogicalProject(cluster,
-          cluster.traitSetOf(Convention.NONE), rt1,
-          ImmutableList.of(
-              (RexNode) rexBuilder.makeInputRef(stringType, 0),
-              rexBuilder.makeInputRef(integerType, 1)),
-          typeFactory.builder().add("s", stringType).add("i", integerType)
-          .build());
-
-      // aggregate on s, count
-      AggregateCall aggCall = new AggregateCall(SqlStdOperatorTable.COUNT,
-          false, Collections.singletonList(1),
-          sqlBigInt, "cnt");
-      RelNode agg = new LogicalAggregate(cluster,
-          cluster.traitSetOf(Convention.NONE), project, false,
-          ImmutableBitSet.of(0), null, Collections.singletonList(aggCall));
-
-      final RelNode rootRel = agg;
-
-      RelOptUtil.dumpPlan("LOGICAL PLAN", rootRel, false,
-          SqlExplainLevel.DIGEST_ATTRIBUTES);
-
-      RelTraitSet desiredTraits = rootRel.getTraitSet().replace(PHYSICAL);
-      final RelNode rootRel2 = planner.changeTraits(rootRel, desiredTraits);
-      planner.setRoot(rootRel2);
-      return planner.findBestExp();
-    }
-  }
-
-
-  /* RULES */
-  /** Rule for PhysAgg */
-  private static class PhysAggRule extends RelOptRule {
-    static final PhysAggRule INSTANCE = new PhysAggRule();
-
-    private PhysAggRule() {
-      super(anyChild(LogicalAggregate.class), "PhysAgg");
-    }
-
-    public void onMatch(RelOptRuleCall call) {
-      RelTraitSet empty = call.getPlanner().emptyTraitSet();
-      LogicalAggregate rel = (LogicalAggregate) call.rel(0);
-      assert rel.getGroupSet().cardinality() == 1;
-      int aggIndex = rel.getGroupSet().iterator().next();
-      RelTrait collation = RelCollations.of(new RelFieldCollation(aggIndex,
-          RelFieldCollation.Direction.ASCENDING,
-          RelFieldCollation.NullDirection.FIRST));
-      RelTraitSet desiredTraits = empty.replace(PHYSICAL).replace(collation);
-      RelNode convertedInput = convert(rel.getInput(), desiredTraits);
-      call.transformTo(new PhysAgg(rel.getCluster(), empty.replace(PHYSICAL),
-          convertedInput, rel.indicator, rel
-          .getGroupSet(), rel.getGroupSets(), rel.getAggCallList()));
-    }
-  }
-
-  /** Rule for PhysProj */
-  private static class PhysProjRule extends RelOptRule {
-    static final PhysProjRule INSTANCE = new PhysProjRule(false);
-    static final PhysProjRule INSTANCE_HACK = new PhysProjRule(true);
-
-    final boolean subsetHack;
-
-    private PhysProjRule(boolean subsetHack) {
-      super(RelOptRule.operand(LogicalProject.class,
-          anyChild(RelNode.class)), "PhysProj");
-      this.subsetHack = subsetHack;
-    }
-
-    public void onMatch(RelOptRuleCall call) {
-      RelTraitSet empty = call.getPlanner().emptyTraitSet();
-      LogicalProject rel = (LogicalProject) call.rel(0);
-      RelNode input = convert(rel.getInput(), empty.replace(PHYSICAL));
-
-
-      if (subsetHack && input instanceof RelSubset) {
-        RelSubset subset = (RelSubset) input;
-        for (RelNode child : subset.getRels()) {
-          // skip logical nodes
-          if (child.getTraitSet().getTrait(ConventionTraitDef.INSTANCE)
-              == Convention.NONE) {
-            continue;
-          } else {
-            RelTraitSet outcome = child.getTraitSet().replace(PHYSICAL);
-            call.transformTo(new PhysProj(rel.getCluster(), outcome,
-                convert(child, outcome), rel.getChildExps(), rel.getRowType()));
-          }
-        }
-      } else {
-        call.transformTo(new PhysProj(rel.getCluster(), input.getTraitSet(),
-            input, rel.getChildExps(), rel.getRowType()));
-      }
-
-    }
-  }
-
-  /** Rule for PhysSort */
-  private static class PhysSortRule extends RelOptRule {
-    static final PhysSortRule INSTANCE = new PhysSortRule();
-
-    private PhysSortRule() {
-      super(anyChild(Sort.class), "PhysSort");
-    }
-
-    public boolean matches(RelOptRuleCall call) {
-      return !(call.rel(0) instanceof PhysSort);
-    }
-
-    public void onMatch(RelOptRuleCall call) {
-      RelTraitSet empty = call.getPlanner().emptyTraitSet();
-      Sort rel = (Sort) call.rel(0);
-      RelNode input = convert(rel.getInput(), empty.plus(PHYSICAL));
-      call.transformTo(
-          new PhysSort(rel.getCluster(),
-          input.getTraitSet().plus(rel.getCollation()),
-          input, rel.getCollation(), rel.offset,
-          rel.fetch));
-    }
-  }
-
-  /** Rule for PhysTable */
-  private static class PhysTableRule extends RelOptRule {
-    static final PhysTableRule INSTANCE = new PhysTableRule();
-
-    private PhysTableRule() {
-      super(anyChild(EnumerableTableScan.class), "PhysScan");
-    }
-
-    public void onMatch(RelOptRuleCall call) {
-      EnumerableTableScan rel = (EnumerableTableScan) call.rel(0);
-      call.transformTo(new PhysTable(rel.getCluster()));
-    }
-  }
-
-  /* RELS */
-  /** Market interface for Phys nodes */
-  private interface Phys extends RelNode { }
-
-  /** Physical Aggregate RelNode */
-  private static class PhysAgg extends Aggregate implements Phys {
-    public PhysAgg(RelOptCluster cluster, RelTraitSet traits, RelNode child,
-        boolean indicator, ImmutableBitSet groupSet,
-        List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
-      super(cluster, traits, child, indicator, groupSet, groupSets, aggCalls);
-
-    }
-
-    public Aggregate copy(RelTraitSet traitSet, RelNode input,
-        boolean indicator, ImmutableBitSet groupSet,
-        List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
-      return new PhysAgg(getCluster(), traitSet, input, indicator, groupSet,
-          groupSets, aggCalls);
-    }
-
-    public RelOptCost computeSelfCost(RelOptPlanner planner) {
-      return planner.getCostFactory().makeCost(1, 1, 1);
-    }
-  }
-
-  /** Physical Project RelNode */
-  private static class PhysProj extends Project implements Phys {
-    public PhysProj(RelOptCluster cluster, RelTraitSet traits, RelNode child,
-        List<RexNode> exps, RelDataType rowType) {
-      super(cluster, traits, child, exps, rowType);
-    }
-
-    public PhysProj copy(RelTraitSet traitSet, RelNode input,
-        List<RexNode> exps, RelDataType rowType) {
-      return new PhysProj(getCluster(), traitSet, input, exps, rowType);
-    }
-
-    public RelOptCost computeSelfCost(RelOptPlanner planner) {
-      return planner.getCostFactory().makeCost(1, 1, 1);
-    }
-  }
-
-  /** Physical Sort RelNode */
-  private static class PhysSort extends Sort implements Phys {
-    public PhysSort(RelOptCluster cluster, RelTraitSet traits, RelNode child,
-        RelCollation collation, RexNode offset,
-        RexNode fetch) {
-      super(cluster, traits, child, collation, offset, fetch);
-
-    }
-
-    public PhysSort copy(RelTraitSet traitSet, RelNode newInput,
-        RelCollation newCollation, RexNode offset,
-        RexNode fetch) {
-      return new PhysSort(getCluster(), traitSet, newInput, newCollation,
-          offset, fetch);
-    }
-
-    public RelOptCost computeSelfCost(RelOptPlanner planner) {
-      return planner.getCostFactory().makeCost(1, 1, 1);
-    }
-  }
-
-  /** Physical Table RelNode */
-  private static class PhysTable extends AbstractRelNode implements Phys {
-    public PhysTable(RelOptCluster cluster) {
-      super(cluster, cluster.traitSet().replace(PHYSICAL).replace(COLLATION));
-      RelDataTypeFactory typeFactory = cluster.getTypeFactory();
-      final RelDataType stringType = typeFactory.createJavaType(String.class);
-      final RelDataType integerType = typeFactory.createJavaType(Integer.class);
-      this.rowType = typeFactory.builder().add("s", stringType)
-          .add("i", integerType).build();
-    }
-
-    public RelOptCost computeSelfCost(RelOptPlanner planner) {
-      return planner.getCostFactory().makeCost(1, 1, 1);
-    }
-  }
-
-  /* UTILS */
-  public static RelOptRuleOperand anyChild(Class<? extends RelNode> first) {
-    return RelOptRule.operand(first, RelOptRule.any());
-  }
-
-  // Created so that we can control when the TraitDefs are defined (e.g.
-  // before the cluster is created).
-  private static RelNode run(PropAction action, RuleSet rules)
-      throws Exception {
-
-    FrameworkConfig config = Frameworks.newConfigBuilder()
-        .ruleSets(rules).build();
-
-    final Properties info = new Properties();
-    final Connection connection = DriverManager
-        .getConnection("jdbc:calcite:", info);
-    final CalciteServerStatement statement = connection
-        .createStatement().unwrap(CalciteServerStatement.class);
-    final CalcitePrepare.Context prepareContext =
-          statement.createPrepareContext();
-    final JavaTypeFactory typeFactory = prepareContext.getTypeFactory();
-    CalciteCatalogReader catalogReader =
-          new CalciteCatalogReader(prepareContext.getRootSchema(),
-              prepareContext.config().caseSensitive(),
-              prepareContext.getDefaultSchemaPath(),
-              typeFactory);
-    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
-    final RelOptPlanner planner = new VolcanoPlanner(config.getCostFactory(),
-        config.getContext());
-
-    // set up rules before we generate cluster
-    planner.clearRelTraitDefs();
-    planner.addRelTraitDef(RelCollationTraitDef.INSTANCE);
-    planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
-
-    planner.clear();
-    for (RelOptRule r : rules) {
-      planner.addRule(r);
-    }
-
-    final RelOptQuery query = new RelOptQuery(planner);
-    final RelOptCluster cluster = query.createCluster(
-        rexBuilder.getTypeFactory(), rexBuilder);
-    return action.apply(cluster, catalogReader,
-        prepareContext.getRootSchema().plus());
-
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3b55c35a/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java b/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
new file mode 100644
index 0000000..dbd56b8
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
@@ -0,0 +1,433 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.plan.volcano;
+
+import org.apache.calcite.adapter.enumerable.EnumerableTableScan;
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.jdbc.CalcitePrepare;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.ConventionTraitDef;
+import org.apache.calcite.plan.RelOptAbstractTable;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptQuery;
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptRuleOperand;
+import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelTrait;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.plan.volcano.AbstractConverter.ExpandConversionRule;
+import org.apache.calcite.prepare.CalciteCatalogReader;
+import org.apache.calcite.prepare.CalcitePrepareImpl;
+import org.apache.calcite.rel.AbstractRelNode;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollationTraitDef;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.convert.ConverterRule;
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.Sort;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.metadata.RelMdCollation;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.SortRemoveRule;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.Statistic;
+import org.apache.calcite.schema.Statistics;
+import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.impl.AbstractTable;
+import org.apache.calcite.server.CalciteServerStatement;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.tools.FrameworkConfig;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.calcite.tools.RuleSet;
+import org.apache.calcite.tools.RuleSets;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests that determine whether trait propagation work in Volcano Planner.
+ */
+public class TraitPropagationTest {
+  static final Convention PHYSICAL =
+      new Convention.Impl("PHYSICAL", Phys.class);
+  static final RelCollation COLLATION =
+      RelCollations.of(
+          new RelFieldCollation(0,
+              RelFieldCollation.Direction.ASCENDING,
+              RelFieldCollation.NullDirection.FIRST));
+
+  static final RuleSet RULES =
+      RuleSets.ofList(PhysAggRule.INSTANCE,
+          PhysProjRule.INSTANCE,
+          PhysTableRule.INSTANCE,
+          PhysSortRule.INSTANCE,
+          SortRemoveRule.INSTANCE,
+          ExpandConversionRule.INSTANCE);
+
+  @Test public void testOne() throws Exception {
+    RelNode planned = run(new PropAction(), RULES);
+    if (CalcitePrepareImpl.DEBUG) {
+      System.out.println(
+          RelOptUtil.dumpPlan("LOGICAL PLAN", planned, false,
+              SqlExplainLevel.ALL_ATTRIBUTES));
+    }
+    assertEquals("Sortedness was not propagated", 3,
+        RelMetadataQuery.getCumulativeCost(planned).getRows(), 0);
+  }
+
+  /**
+   * Materialized anonymous class for simplicity
+   */
+  private static class PropAction {
+    public RelNode apply(RelOptCluster cluster, RelOptSchema relOptSchema,
+        SchemaPlus rootSchema) {
+      final RelDataTypeFactory typeFactory = cluster.getTypeFactory();
+      final RexBuilder rexBuilder = cluster.getRexBuilder();
+      final RelOptPlanner planner = cluster.getPlanner();
+
+      final RelDataType stringType = typeFactory.createJavaType(String.class);
+      final RelDataType integerType = typeFactory.createJavaType(Integer.class);
+      final RelDataType sqlBigInt =
+          typeFactory.createSqlType(SqlTypeName.BIGINT);
+
+      // SELECT * from T;
+      final Table table = new AbstractTable() {
+        public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+          return typeFactory.builder()
+              .add("s", stringType)
+              .add("i", integerType).build();
+        }
+
+        @Override public Statistic getStatistic() {
+          return Statistics.of(100d, ImmutableList.<ImmutableBitSet>of(),
+              ImmutableList.of(COLLATION));
+        }
+      };
+
+      final RelOptAbstractTable t1 = new RelOptAbstractTable(relOptSchema,
+          "t1", table.getRowType(typeFactory)) {
+        @Override public <T> T unwrap(Class<T> clazz) {
+          return clazz.isInstance(table)
+              ? clazz.cast(table)
+              : super.unwrap(clazz);
+        }
+      };
+
+      final RelNode rt1 = EnumerableTableScan.create(cluster, t1);
+
+      // project s column
+      RelNode project = LogicalProject.create(rt1,
+          ImmutableList.of(
+              (RexNode) rexBuilder.makeInputRef(stringType, 0),
+              rexBuilder.makeInputRef(integerType, 1)),
+          typeFactory.builder().add("s", stringType).add("i", integerType)
+          .build());
+
+      // aggregate on s, count
+      AggregateCall aggCall = new AggregateCall(SqlStdOperatorTable.COUNT,
+          false, Collections.singletonList(1),
+          sqlBigInt, "cnt");
+      RelNode agg = new LogicalAggregate(cluster,
+          cluster.traitSetOf(Convention.NONE), project, false,
+          ImmutableBitSet.of(0), null, Collections.singletonList(aggCall));
+
+      final RelNode rootRel = agg;
+
+      RelOptUtil.dumpPlan("LOGICAL PLAN", rootRel, false,
+          SqlExplainLevel.DIGEST_ATTRIBUTES);
+
+      RelTraitSet desiredTraits = rootRel.getTraitSet().replace(PHYSICAL);
+      final RelNode rootRel2 = planner.changeTraits(rootRel, desiredTraits);
+      planner.setRoot(rootRel2);
+      return planner.findBestExp();
+    }
+  }
+
+  // RULES
+
+  /** Rule for PhysAgg */
+  private static class PhysAggRule extends RelOptRule {
+    static final PhysAggRule INSTANCE = new PhysAggRule();
+
+    private PhysAggRule() {
+      super(anyChild(LogicalAggregate.class), "PhysAgg");
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      RelTraitSet empty = call.getPlanner().emptyTraitSet();
+      LogicalAggregate rel = call.rel(0);
+      assert rel.getGroupSet().cardinality() == 1;
+      int aggIndex = rel.getGroupSet().iterator().next();
+      RelTrait collation = RelCollations.of(
+          new RelFieldCollation(aggIndex,
+              RelFieldCollation.Direction.ASCENDING,
+              RelFieldCollation.NullDirection.FIRST));
+      RelTraitSet desiredTraits = empty.replace(PHYSICAL).replace(collation);
+      RelNode convertedInput = convert(rel.getInput(), desiredTraits);
+      call.transformTo(
+          new PhysAgg(rel.getCluster(), empty.replace(PHYSICAL),
+              convertedInput, rel.indicator, rel.getGroupSet(),
+              rel.getGroupSets(), rel.getAggCallList()));
+    }
+  }
+
+  /** Rule for PhysProj */
+  private static class PhysProjRule extends RelOptRule {
+    static final PhysProjRule INSTANCE = new PhysProjRule(false);
+
+    final boolean subsetHack;
+
+    private PhysProjRule(boolean subsetHack) {
+      super(
+          RelOptRule.operand(LogicalProject.class,
+              anyChild(RelNode.class)),
+          "PhysProj");
+      this.subsetHack = subsetHack;
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      LogicalProject rel = call.rel(0);
+      RelNode rawInput = call.rel(1);
+      RelNode input = convert(rawInput, PHYSICAL);
+
+      if (subsetHack && input instanceof RelSubset) {
+        RelSubset subset = (RelSubset) input;
+        for (RelNode child : subset.getRels()) {
+          // skip logical nodes
+          if (child.getTraitSet().getTrait(ConventionTraitDef.INSTANCE)
+              == Convention.NONE) {
+            continue;
+          } else {
+            RelTraitSet outcome = child.getTraitSet().replace(PHYSICAL);
+            call.transformTo(
+                new PhysProj(rel.getCluster(), outcome, convert(child, outcome),
+                    rel.getChildExps(), rel.getRowType()));
+          }
+        }
+      } else {
+        call.transformTo(
+            PhysProj.create(input, rel.getChildExps(), rel.getRowType()));
+      }
+
+    }
+  }
+
+  /** Rule for PhysSort */
+  private static class PhysSortRule extends ConverterRule {
+    static final PhysSortRule INSTANCE = new PhysSortRule();
+
+    PhysSortRule() {
+      super(Sort.class, Convention.NONE, PHYSICAL, "PhysSortRule");
+    }
+
+    public RelNode convert(RelNode rel) {
+      final Sort sort = (Sort) rel;
+      final RelNode input = convert(sort.getInput(),
+          rel.getCluster().traitSetOf(PHYSICAL));
+      return new PhysSort(
+          rel.getCluster(),
+          input.getTraitSet().plus(sort.getCollation()),
+          convert(input, input.getTraitSet().replace(PHYSICAL)),
+          sort.getCollation(),
+          null,
+          null);
+    }
+  }
+
+  /** Rule for PhysTable */
+  private static class PhysTableRule extends RelOptRule {
+    static final PhysTableRule INSTANCE = new PhysTableRule();
+
+    private PhysTableRule() {
+      super(anyChild(EnumerableTableScan.class), "PhysScan");
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      EnumerableTableScan rel = call.rel(0);
+      call.transformTo(new PhysTable(rel.getCluster()));
+    }
+  }
+
+  /* RELS */
+  /** Market interface for Phys nodes */
+  private interface Phys extends RelNode { }
+
+  /** Physical Aggregate RelNode */
+  private static class PhysAgg extends Aggregate implements Phys {
+    public PhysAgg(RelOptCluster cluster, RelTraitSet traits, RelNode child,
+        boolean indicator, ImmutableBitSet groupSet,
+        List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
+      super(cluster, traits, child, indicator, groupSet, groupSets, aggCalls);
+
+    }
+
+    public Aggregate copy(RelTraitSet traitSet, RelNode input,
+        boolean indicator, ImmutableBitSet groupSet,
+        List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
+      return new PhysAgg(getCluster(), traitSet, input, indicator, groupSet,
+          groupSets, aggCalls);
+    }
+
+    public RelOptCost computeSelfCost(RelOptPlanner planner) {
+      return planner.getCostFactory().makeCost(1, 1, 1);
+    }
+  }
+
+  /** Physical Project RelNode */
+  private static class PhysProj extends Project implements Phys {
+    public PhysProj(RelOptCluster cluster, RelTraitSet traits, RelNode child,
+        List<RexNode> exps, RelDataType rowType) {
+      super(cluster, traits, child, exps, rowType);
+    }
+
+    public static PhysProj create(final RelNode input,
+        final List<RexNode> projects, RelDataType rowType) {
+      final RelOptCluster cluster = input.getCluster();
+      final RelTraitSet traitSet =
+          cluster.traitSet().replace(PHYSICAL)
+              .replaceIfs(
+                  RelCollationTraitDef.INSTANCE,
+                  new Supplier<List<RelCollation>>() {
+                    public List<RelCollation> get() {
+                      return RelMdCollation.project(input, projects);
+                    }
+                  });
+      return new PhysProj(cluster, traitSet, input, projects, rowType);
+    }
+
+    public PhysProj copy(RelTraitSet traitSet, RelNode input,
+        List<RexNode> exps, RelDataType rowType) {
+      return new PhysProj(getCluster(), traitSet, input, exps, rowType);
+    }
+
+    public RelOptCost computeSelfCost(RelOptPlanner planner) {
+      return planner.getCostFactory().makeCost(1, 1, 1);
+    }
+  }
+
+  /** Physical Sort RelNode */
+  private static class PhysSort extends Sort implements Phys {
+    public PhysSort(RelOptCluster cluster, RelTraitSet traits, RelNode child,
+        RelCollation collation, RexNode offset,
+        RexNode fetch) {
+      super(cluster, traits, child, collation, offset, fetch);
+
+    }
+
+    public PhysSort copy(RelTraitSet traitSet, RelNode newInput,
+        RelCollation newCollation, RexNode offset,
+        RexNode fetch) {
+      return new PhysSort(getCluster(), traitSet, newInput, newCollation,
+          offset, fetch);
+    }
+
+    public RelOptCost computeSelfCost(RelOptPlanner planner) {
+      return planner.getCostFactory().makeCost(1, 1, 1);
+    }
+  }
+
+  /** Physical Table RelNode */
+  private static class PhysTable extends AbstractRelNode implements Phys {
+    public PhysTable(RelOptCluster cluster) {
+      super(cluster, cluster.traitSet().replace(PHYSICAL).replace(COLLATION));
+      RelDataTypeFactory typeFactory = cluster.getTypeFactory();
+      final RelDataType stringType = typeFactory.createJavaType(String.class);
+      final RelDataType integerType = typeFactory.createJavaType(Integer.class);
+      this.rowType = typeFactory.builder().add("s", stringType)
+          .add("i", integerType).build();
+    }
+
+    public RelOptCost computeSelfCost(RelOptPlanner planner) {
+      return planner.getCostFactory().makeCost(1, 1, 1);
+    }
+  }
+
+  /* UTILS */
+  public static RelOptRuleOperand anyChild(Class<? extends RelNode> first) {
+    return RelOptRule.operand(first, RelOptRule.any());
+  }
+
+  // Created so that we can control when the TraitDefs are defined (e.g.
+  // before the cluster is created).
+  private static RelNode run(PropAction action, RuleSet rules)
+      throws Exception {
+
+    FrameworkConfig config = Frameworks.newConfigBuilder()
+        .ruleSets(rules).build();
+
+    final Properties info = new Properties();
+    final Connection connection = DriverManager
+        .getConnection("jdbc:calcite:", info);
+    final CalciteServerStatement statement = connection
+        .createStatement().unwrap(CalciteServerStatement.class);
+    final CalcitePrepare.Context prepareContext =
+          statement.createPrepareContext();
+    final JavaTypeFactory typeFactory = prepareContext.getTypeFactory();
+    CalciteCatalogReader catalogReader =
+          new CalciteCatalogReader(prepareContext.getRootSchema(),
+              prepareContext.config().caseSensitive(),
+              prepareContext.getDefaultSchemaPath(),
+              typeFactory);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    final RelOptPlanner planner = new VolcanoPlanner(config.getCostFactory(),
+        config.getContext());
+
+    // set up rules before we generate cluster
+    planner.clearRelTraitDefs();
+    planner.addRelTraitDef(RelCollationTraitDef.INSTANCE);
+    planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
+
+    planner.clear();
+    for (RelOptRule r : rules) {
+      planner.addRule(r);
+    }
+
+    final RelOptQuery query = new RelOptQuery(planner);
+    final RelOptCluster cluster = query.createCluster(
+        rexBuilder.getTypeFactory(), rexBuilder);
+    return action.apply(cluster, catalogReader,
+        prepareContext.getRootSchema().plus());
+  }
+}
+
+// End TraitPropagationTest.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3b55c35a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
index 24e642b..f50a8f1 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
@@ -20,6 +20,7 @@ import org.apache.calcite.adapter.clone.ArrayTableTest;
 import org.apache.calcite.jdbc.CalciteRemoteDriverTest;
 import org.apache.calcite.plan.RelOptUtilTest;
 import org.apache.calcite.plan.RelWriterTest;
+import org.apache.calcite.plan.volcano.TraitPropagationTest;
 import org.apache.calcite.plan.volcano.VolcanoPlannerTest;
 import org.apache.calcite.plan.volcano.VolcanoPlannerTraitTest;
 import org.apache.calcite.rel.RelCollationTest;
@@ -79,6 +80,7 @@ import org.junit.runners.Suite;
     InterpreterTest.class,
     VolcanoPlannerTest.class,
     HepPlannerTest.class,
+    TraitPropagationTest.class,
     RelWriterTest.class,
     RexProgramTest.class,
     RexTransformerTest.class,


[3/3] incubator-calcite git commit: Remove checkstyle Eclipse properties from git tracking

Posted by jh...@apache.org.
Remove checkstyle Eclipse properties from git tracking


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

Branch: refs/heads/master
Commit: d3fc7cf2595a3290c1f2de2ef9daf0dafb782be4
Parents: a13137d
Author: Jacques Nadeau <ja...@apache.org>
Authored: Sun Mar 1 08:50:23 2015 -0800
Committer: julianhyde <jh...@apache.org>
Committed: Wed Apr 1 01:52:46 2015 -0400

----------------------------------------------------------------------
 .gitignore | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/d3fc7cf2/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 144930f..9c54233 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ settings.xml
 .buildpath
 .classpath
 .settings
+.checkstyle


[2/3] incubator-calcite git commit: [CALCITE-606] Fix trait propagation and add test case

Posted by jh...@apache.org.
[CALCITE-606] Fix trait propagation and add test case


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

Branch: refs/heads/master
Commit: b312031f3ead3adb272b79d02d7fcfc095ec4deb
Parents: d3fc7cf
Author: Jacques Nadeau <ja...@apache.org>
Authored: Sun Mar 1 07:57:39 2015 -0800
Committer: julianhyde <jh...@apache.org>
Committed: Wed Apr 1 01:52:46 2015 -0400

----------------------------------------------------------------------
 .../org/apache/calcite/plan/volcano/RelSet.java |  44 +-
 .../calcite/plan/volcano/VolcanoPlanner.java    |   7 +-
 .../plan/volcano/TestTraitPropagation.java      | 418 +++++++++++++++++++
 3 files changed, 457 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/b312031f/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java b/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java
index 93d8509..ed2c6b7 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java
@@ -16,6 +16,14 @@
  */
 package org.apache.calcite.plan.volcano;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.ConventionTraitDef;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptListener;
 import org.apache.calcite.plan.RelOptUtil;
@@ -26,12 +34,6 @@ import org.apache.calcite.util.trace.CalciteTrace;
 
 import com.google.common.collect.ImmutableList;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.logging.Logger;
-
 /**
  * A <code>RelSet</code> is an equivalence-set of expressions; that is, a set of
  * expressions which have identical semantics. We are generally interested in
@@ -156,11 +158,35 @@ class RelSet {
       final VolcanoPlanner planner =
           (VolcanoPlanner) cluster.getPlanner();
 
+//      if (planner.root != null
+//          && planner.root.set == this) {
+//        planner.ensureRootConverters();
+//      }
+
+      // Add converters to convert the new subset to each existing subset.
+      for (RelSubset subset1 : subsets) {
+        if (subset1.getConvention() == Convention.NONE) {
+          continue;
+        }
+        final AbstractConverter converter =
+            new AbstractConverter(
+                cluster, subset, ConventionTraitDef.INSTANCE,
+                subset1.getTraitSet());
+        planner.register(converter, subset1);
+      }
+
       subsets.add(subset);
 
-      if (planner.root != null
-          && planner.root.set == this) {
-        planner.ensureRootConverters();
+      // Add converters to convert each existing subset to this subset.
+      for (RelSubset subset1 : subsets) {
+        if (subset1 == subset) {
+          continue;
+        }
+        final AbstractConverter converter =
+            new AbstractConverter(
+                cluster, subset1, ConventionTraitDef.INSTANCE,
+                traits);
+        planner.register(converter, subset);
       }
 
       if (planner.listener != null) {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/b312031f/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
----------------------------------------------------------------------
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 e21cdbb..d26ad6b 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
@@ -22,6 +22,7 @@ import org.apache.calcite.linq4j.tree.Expressions;
 import org.apache.calcite.plan.AbstractRelOptPlanner;
 import org.apache.calcite.plan.Context;
 import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.ConventionTraitDef;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptCostFactory;
 import org.apache.calcite.plan.RelOptLattice;
@@ -1090,7 +1091,8 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
     if (rel instanceof RelSubset) {
       return ((RelSubset) rel).bestCost;
     }
-    if (rel.getTraitSet().getTrait(0) == Convention.NONE) {
+    if (rel.getTraitSet().getTrait(ConventionTraitDef.INSTANCE)
+        == Convention.NONE) {
       return costFactory.makeInfiniteCost();
     }
     RelOptCost cost = RelMetadataQuery.getNonCumulativeCost(rel);
@@ -1622,8 +1624,7 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
     // Now is a good time to ensure that the relational expression
     // implements the interface required by its calling convention.
     final RelTraitSet traits = rel.getTraitSet();
-    final Convention convention =
-        (Convention) traits.getTrait(0);
+    final Convention convention = traits.getTrait(ConventionTraitDef.INSTANCE);
     if (!convention.getInterface().isInstance(rel)
         && !(rel instanceof Converter)) {
       throw Util.newInternal(

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/b312031f/core/src/test/java/org/apache/calcite/plan/volcano/TestTraitPropagation.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/plan/volcano/TestTraitPropagation.java b/core/src/test/java/org/apache/calcite/plan/volcano/TestTraitPropagation.java
new file mode 100644
index 0000000..e2dc6fd
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/plan/volcano/TestTraitPropagation.java
@@ -0,0 +1,418 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.plan.volcano;
+
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+
+import org.apache.calcite.adapter.enumerable.EnumerableConvention;
+import org.apache.calcite.adapter.enumerable.EnumerableTableScan;
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.jdbc.CalcitePrepare;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.ConventionTraitDef;
+import org.apache.calcite.plan.RelOptAbstractTable;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptQuery;
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptRuleOperand;
+import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelTrait;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.plan.volcano.AbstractConverter.ExpandConversionRule;
+import org.apache.calcite.prepare.CalciteCatalogReader;
+import org.apache.calcite.rel.AbstractRelNode;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollationTraitDef;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.Sort;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.impl.AbstractTable;
+import org.apache.calcite.server.CalciteServerStatement;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.tools.FrameworkConfig;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.calcite.tools.RuleSet;
+import org.apache.calcite.tools.RuleSets;
+import org.apache.calcite.util.ImmutableBitSet;
+
+/**
+ * Tests that determine whether trait propagation work in Volcano Planner
+ */
+public class TestTraitPropagation {
+
+  static final Convention PHYSICAL =
+      new Convention.Impl("PHYSICAL", Phys.class);
+  static final RelCollation COLLATION =
+      RelCollations.of(new RelFieldCollation(0,
+        RelFieldCollation.Direction.ASCENDING,
+        RelFieldCollation.NullDirection.FIRST));
+
+  static final RuleSet RULES_NO_HACK = RuleSets.ofList(
+      PhysAggRule.INSTANCE, //
+      PhysProjRule.INSTANCE, //
+      PhysTableRule.INSTANCE, //
+      PhysSortRule.INSTANCE, //
+      ExpandConversionRule.INSTANCE //
+  );
+
+  static final RuleSet RULES_HACK = RuleSets.ofList(
+      PhysAggRule.INSTANCE, //
+      PhysProjRule.INSTANCE_HACK, //
+      PhysTableRule.INSTANCE, //
+      PhysSortRule.INSTANCE, //
+      ExpandConversionRule.INSTANCE //
+  );
+
+  @Test
+  public void withoutHack() throws Exception {
+    RelNode planned = run(new PropAction(), RULES_NO_HACK);
+    System.out.println(RelOptUtil.dumpPlan("LOGICAL PLAN", planned, false,
+        SqlExplainLevel.ALL_ATTRIBUTES));
+    assertEquals("Sortedness was not propagated", 3,
+        RelMetadataQuery.getCumulativeCost(planned).getRows(), 0);
+  }
+
+  @Test
+  public void withHack() throws Exception {
+    RelNode planned = run(new PropAction(), RULES_HACK);
+    System.out.println(RelOptUtil.dumpPlan("LOGICAL PLAN", planned, false,
+        SqlExplainLevel.ALL_ATTRIBUTES));
+    assertEquals("Sortedness was not propagated", 3,
+        RelMetadataQuery.getCumulativeCost(planned).getRows(), 0);
+  }
+
+  /**
+   * Materialized anonymous class for simplicity
+   */
+  private class PropAction {
+    public RelNode apply(RelOptCluster cluster, RelOptSchema relOptSchema,
+        SchemaPlus rootSchema) {
+      final RelDataTypeFactory typeFactory = cluster.getTypeFactory();
+      final RexBuilder rexBuilder = cluster.getRexBuilder();
+      final RelOptPlanner planner = cluster.getPlanner();
+
+      final RelDataType stringType = typeFactory.createJavaType(String.class);
+      final RelDataType integerType = typeFactory.createJavaType(Integer.class);
+      final RelDataType sqlBigInt = typeFactory
+          .createSqlType(SqlTypeName.BIGINT);
+
+      // SELECT * from T;
+      final Table table = new AbstractTable() {
+        public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+          return typeFactory.builder().add("s", stringType)
+              .add("i", integerType).build();
+        }
+      };
+
+      final RelOptAbstractTable t1 = new RelOptAbstractTable(relOptSchema,
+          "t1", table.getRowType(typeFactory)) {
+      };
+
+      final RelNode rt1 = new EnumerableTableScan(cluster,
+          cluster.traitSetOf(EnumerableConvention.INSTANCE), t1,
+          Object[].class);
+
+      // project s column
+      RelNode project = new LogicalProject(cluster,
+          cluster.traitSetOf(Convention.NONE), rt1,
+          ImmutableList.of(
+              (RexNode) rexBuilder.makeInputRef(stringType, 0),
+              rexBuilder.makeInputRef(integerType, 1)),
+          typeFactory.builder().add("s", stringType).add("i", integerType)
+          .build());
+
+      // aggregate on s, count
+      AggregateCall aggCall = new AggregateCall(SqlStdOperatorTable.COUNT,
+          false, Collections.singletonList(1),
+          sqlBigInt, "cnt");
+      RelNode agg = new LogicalAggregate(cluster,
+          cluster.traitSetOf(Convention.NONE), project, false,
+          ImmutableBitSet.of(0), null, Collections.singletonList(aggCall));
+
+      final RelNode rootRel = agg;
+
+      RelOptUtil.dumpPlan("LOGICAL PLAN", rootRel, false,
+          SqlExplainLevel.DIGEST_ATTRIBUTES);
+
+      RelTraitSet desiredTraits = rootRel.getTraitSet().replace(PHYSICAL);
+      final RelNode rootRel2 = planner.changeTraits(rootRel, desiredTraits);
+      planner.setRoot(rootRel2);
+      return planner.findBestExp();
+    }
+  }
+
+
+  /* RULES */
+  /** Rule for PhysAgg */
+  private static class PhysAggRule extends RelOptRule {
+    static final PhysAggRule INSTANCE = new PhysAggRule();
+
+    private PhysAggRule() {
+      super(anyChild(LogicalAggregate.class), "PhysAgg");
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      RelTraitSet empty = call.getPlanner().emptyTraitSet();
+      LogicalAggregate rel = (LogicalAggregate) call.rel(0);
+      assert rel.getGroupSet().cardinality() == 1;
+      int aggIndex = rel.getGroupSet().iterator().next();
+      RelTrait collation = RelCollations.of(new RelFieldCollation(aggIndex,
+          RelFieldCollation.Direction.ASCENDING,
+          RelFieldCollation.NullDirection.FIRST));
+      RelTraitSet desiredTraits = empty.replace(PHYSICAL).replace(collation);
+      RelNode convertedInput = convert(rel.getInput(), desiredTraits);
+      call.transformTo(new PhysAgg(rel.getCluster(), empty.replace(PHYSICAL),
+          convertedInput, rel.indicator, rel
+          .getGroupSet(), rel.getGroupSets(), rel.getAggCallList()));
+    }
+  }
+
+  /** Rule for PhysProj */
+  private static class PhysProjRule extends RelOptRule {
+    static final PhysProjRule INSTANCE = new PhysProjRule(false);
+    static final PhysProjRule INSTANCE_HACK = new PhysProjRule(true);
+
+    final boolean subsetHack;
+
+    private PhysProjRule(boolean subsetHack) {
+      super(RelOptRule.operand(LogicalProject.class,
+          anyChild(RelNode.class)), "PhysProj");
+      this.subsetHack = subsetHack;
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      RelTraitSet empty = call.getPlanner().emptyTraitSet();
+      LogicalProject rel = (LogicalProject) call.rel(0);
+      RelNode input = convert(rel.getInput(), empty.replace(PHYSICAL));
+
+
+      if (subsetHack && input instanceof RelSubset) {
+        RelSubset subset = (RelSubset) input;
+        for (RelNode child : subset.getRels()) {
+          // skip logical nodes
+          if (child.getTraitSet().getTrait(ConventionTraitDef.INSTANCE)
+              == Convention.NONE) {
+            continue;
+          } else {
+            RelTraitSet outcome = child.getTraitSet().replace(PHYSICAL);
+            call.transformTo(new PhysProj(rel.getCluster(), outcome,
+                convert(child, outcome), rel.getChildExps(), rel.getRowType()));
+          }
+        }
+      } else {
+        call.transformTo(new PhysProj(rel.getCluster(), input.getTraitSet(),
+            input, rel.getChildExps(), rel.getRowType()));
+      }
+
+    }
+  }
+
+  /** Rule for PhysSort */
+  private static class PhysSortRule extends RelOptRule {
+    static final PhysSortRule INSTANCE = new PhysSortRule();
+
+    private PhysSortRule() {
+      super(anyChild(Sort.class), "PhysSort");
+    }
+
+    public boolean matches(RelOptRuleCall call) {
+      return !(call.rel(0) instanceof PhysSort);
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      RelTraitSet empty = call.getPlanner().emptyTraitSet();
+      Sort rel = (Sort) call.rel(0);
+      RelNode input = convert(rel.getInput(), empty.plus(PHYSICAL));
+      call.transformTo(
+          new PhysSort(rel.getCluster(),
+          input.getTraitSet().plus(rel.getCollation()),
+          input, rel.getCollation(), rel.offset,
+          rel.fetch));
+    }
+  }
+
+  /** Rule for PhysTable */
+  private static class PhysTableRule extends RelOptRule {
+    static final PhysTableRule INSTANCE = new PhysTableRule();
+
+    private PhysTableRule() {
+      super(anyChild(EnumerableTableScan.class), "PhysScan");
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      EnumerableTableScan rel = (EnumerableTableScan) call.rel(0);
+      call.transformTo(new PhysTable(rel.getCluster()));
+    }
+  }
+
+  /* RELS */
+  /** Market interface for Phys nodes */
+  private interface Phys extends RelNode { }
+
+  /** Physical Aggregate RelNode */
+  private static class PhysAgg extends Aggregate implements Phys {
+    public PhysAgg(RelOptCluster cluster, RelTraitSet traits, RelNode child,
+        boolean indicator, ImmutableBitSet groupSet,
+        List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
+      super(cluster, traits, child, indicator, groupSet, groupSets, aggCalls);
+
+    }
+
+    public Aggregate copy(RelTraitSet traitSet, RelNode input,
+        boolean indicator, ImmutableBitSet groupSet,
+        List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
+      return new PhysAgg(getCluster(), traitSet, input, indicator, groupSet,
+          groupSets, aggCalls);
+    }
+
+    public RelOptCost computeSelfCost(RelOptPlanner planner) {
+      return planner.getCostFactory().makeCost(1, 1, 1);
+    }
+  }
+
+  /** Physical Project RelNode */
+  private static class PhysProj extends Project implements Phys {
+    public PhysProj(RelOptCluster cluster, RelTraitSet traits, RelNode child,
+        List<RexNode> exps, RelDataType rowType) {
+      super(cluster, traits, child, exps, rowType);
+    }
+
+    public PhysProj copy(RelTraitSet traitSet, RelNode input,
+        List<RexNode> exps, RelDataType rowType) {
+      return new PhysProj(getCluster(), traitSet, input, exps, rowType);
+    }
+
+    public RelOptCost computeSelfCost(RelOptPlanner planner) {
+      return planner.getCostFactory().makeCost(1, 1, 1);
+    }
+  }
+
+  /** Physical Sort RelNode */
+  private static class PhysSort extends Sort implements Phys {
+    public PhysSort(RelOptCluster cluster, RelTraitSet traits, RelNode child,
+        RelCollation collation, RexNode offset,
+        RexNode fetch) {
+      super(cluster, traits, child, collation, offset, fetch);
+
+    }
+
+    public PhysSort copy(RelTraitSet traitSet, RelNode newInput,
+        RelCollation newCollation, RexNode offset,
+        RexNode fetch) {
+      return new PhysSort(getCluster(), traitSet, newInput, newCollation,
+          offset, fetch);
+    }
+
+    public RelOptCost computeSelfCost(RelOptPlanner planner) {
+      return planner.getCostFactory().makeCost(1, 1, 1);
+    }
+  }
+
+  /** Physical Table RelNode */
+  private static class PhysTable extends AbstractRelNode implements Phys {
+    public PhysTable(RelOptCluster cluster) {
+      super(cluster, cluster.traitSet().replace(PHYSICAL).replace(COLLATION));
+      RelDataTypeFactory typeFactory = cluster.getTypeFactory();
+      final RelDataType stringType = typeFactory.createJavaType(String.class);
+      final RelDataType integerType = typeFactory.createJavaType(Integer.class);
+      this.rowType = typeFactory.builder().add("s", stringType)
+          .add("i", integerType).build();
+    }
+
+    public RelOptCost computeSelfCost(RelOptPlanner planner) {
+      return planner.getCostFactory().makeCost(1, 1, 1);
+    }
+  }
+
+  /* UTILS */
+  public static RelOptRuleOperand anyChild(Class<? extends RelNode> first) {
+    return RelOptRule.operand(first, RelOptRule.any());
+  }
+
+  // Created so that we can control when the TraitDefs are defined (e.g.
+  // before the cluster is created).
+  private static RelNode run(PropAction action, RuleSet rules)
+      throws Exception {
+
+    FrameworkConfig config = Frameworks.newConfigBuilder()
+        .ruleSets(rules).build();
+
+    final Properties info = new Properties();
+    final Connection connection = DriverManager
+        .getConnection("jdbc:calcite:", info);
+    final CalciteServerStatement statement = connection
+        .createStatement().unwrap(CalciteServerStatement.class);
+    final CalcitePrepare.Context prepareContext =
+          statement.createPrepareContext();
+    final JavaTypeFactory typeFactory = prepareContext.getTypeFactory();
+    CalciteCatalogReader catalogReader =
+          new CalciteCatalogReader(prepareContext.getRootSchema(),
+              prepareContext.config().caseSensitive(),
+              prepareContext.getDefaultSchemaPath(),
+              typeFactory);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    final RelOptPlanner planner = new VolcanoPlanner(config.getCostFactory(),
+        config.getContext());
+
+    // set up rules before we generate cluster
+    planner.clearRelTraitDefs();
+    planner.addRelTraitDef(RelCollationTraitDef.INSTANCE);
+    planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
+
+    planner.clear();
+    for (RelOptRule r : rules) {
+      planner.addRule(r);
+    }
+
+    final RelOptQuery query = new RelOptQuery(planner);
+    final RelOptCluster cluster = query.createCluster(
+        rexBuilder.getTypeFactory(), rexBuilder);
+    return action.apply(cluster, catalogReader,
+        prepareContext.getRootSchema().plus());
+
+  }
+}