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/02/08 18:25:03 UTC

[4/9] incubator-calcite git commit: [CALCITE-88] Add collation as a trait and a kind of RelNode metadata

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
index e108c78..2b129bb 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
@@ -36,6 +36,7 @@ import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.logical.LogicalTableFunctionScan;
 import org.apache.calcite.rel.logical.LogicalTableModify;
 import org.apache.calcite.rel.logical.LogicalValues;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.rules.ProjectRemoveRule;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
@@ -157,9 +158,8 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
    * @param setOpFactory SetOp factory
    * @param useNamesInIdentityProjCalc
    *            Include field names in identity project determination
-   *
-   * @deprecated Remove before
-   * {@link org.apache.calcite.util.Bug#upgrade Calcite-1.1}. */
+   */
+  @Deprecated // to be removed before 1.1
   public RelFieldTrimmer(SqlValidator validator,
       RelFactories.ProjectFactory projectFactory,
       RelFactories.FilterFactory filterFactory,
@@ -231,6 +231,13 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
       // So, disable trimming.
       fieldsUsed = ImmutableBitSet.range(input.getRowType().getFieldCount());
     }
+    final ImmutableList<RelCollation> collations =
+        RelMetadataQuery.collations(rel);
+    for (RelCollation collation : collations) {
+      for (RelFieldCollation fieldCollation : collation.getFieldCollations()) {
+        fieldsUsed = fieldsUsed.set(fieldCollation.getFieldIndex());
+      }
+    }
     return dispatchTrimFields(input, fieldsUsed, extraFields);
   }
 
@@ -1014,7 +1021,8 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
         RelOptUtil.permute(values.getCluster().getTypeFactory(), rowType,
             mapping);
     final LogicalValues newValues =
-        new LogicalValues(values.getCluster(), newRowType, newTuples.build());
+        LogicalValues.create(values.getCluster(), newRowType,
+            newTuples.build());
     return new TrimResult(newValues, mapping);
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java b/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
index aa8e548..8fa93a1 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
@@ -70,7 +70,6 @@ import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.Mappings;
 
 import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.SortedSetMultimap;
@@ -342,14 +341,9 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
 
   public void rewriteRel(LogicalTableModify rel) {
     LogicalTableModify newRel =
-        new LogicalTableModify(
-            rel.getCluster(),
-            rel.getTable(),
-            rel.getCatalogReader(),
-            getNewForOldRel(rel.getInput()),
-            rel.getOperation(),
-            rel.getUpdateColumnList(),
-            true);
+        LogicalTableModify.create(rel.getTable(), rel.getCatalogReader(),
+            getNewForOldRel(rel.getInput()), rel.getOperation(),
+            rel.getUpdateColumnList(), true);
     setNewForOldRel(rel, newRel);
   }
 
@@ -398,8 +392,7 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
 
   public void rewriteRel(LogicalJoin rel) {
     LogicalJoin newRel =
-        new LogicalJoin(
-            rel.getCluster(),
+        LogicalJoin.create(
             getNewForOldRel(rel.getLeft()),
             getNewForOldRel(rel.getRight()),
             rel.getCondition().accept(new RewriteRexShuttle()),
@@ -420,9 +413,7 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
       newPos.set(getNewForOldInput(pos));
     }
     LogicalCorrelate newRel =
-        new LogicalCorrelate(
-            rel.getCluster(),
-            getNewForOldRel(rel.getLeft()),
+        LogicalCorrelate.create(getNewForOldRel(rel.getLeft()),
             getNewForOldRel(rel.getRight()),
             rel.getCorrelationId(),
             newPos.build(),
@@ -483,12 +474,12 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
 
   public void rewriteRel(LogicalCalc rel) {
     // Translate the child.
-    final RelNode newChild = getNewForOldRel(rel.getInput());
+    final RelNode newInput = getNewForOldRel(rel.getInput());
 
     final RelOptCluster cluster = rel.getCluster();
     RexProgramBuilder programBuilder =
         new RexProgramBuilder(
-            newChild.getRowType(),
+            newInput.getRowType(),
             cluster.getRexBuilder());
 
     // Convert the common expressions.
@@ -524,13 +515,7 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
     RexProgram newProgram = programBuilder.getProgram();
 
     // Create a new calc relational expression.
-    LogicalCalc newRel =
-        new LogicalCalc(
-            cluster,
-            rel.getTraitSet(),
-            newChild,
-            newProgram,
-            ImmutableList.<RelCollation>of());
+    LogicalCalc newRel = LogicalCalc.create(newInput, newProgram);
     setNewForOldRel(rel, newRel);
   }
 
@@ -640,10 +625,7 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
       RelDataType type,
       List<Pair<RexNode, String>> flattenedExps) {
     RelDataType flattenedType =
-        SqlTypeUtil.flattenRecordType(
-            rexBuilder.getTypeFactory(),
-            type,
-            null);
+        SqlTypeUtil.flattenRecordType(rexBuilder.getTypeFactory(), type, null);
     for (RelDataTypeField field : flattenedType.getFieldList()) {
       flattenedExps.add(
           Pair.of(

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 2eb7d2c..0ace1e9 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -27,7 +27,7 @@ import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.prepare.RelOptTableImpl;
 import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelCollationImpl;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelShuttle;
@@ -602,7 +602,7 @@ public class SqlToRelConverter {
         orderExprList,
         collationList);
     final RelCollation collation =
-        cluster.traitSetOf().canonize(RelCollationImpl.of(collationList));
+        cluster.traitSet().canonize(RelCollations.of(collationList));
 
     if (validator.isAggregate(select)) {
       convertAgg(
@@ -674,12 +674,8 @@ public class SqlToRelConverter {
         }
       }
       rel =
-          new LogicalProject(
-              cluster,
-              rel,
-              Pair.left(newProjects),
+          LogicalProject.create(rel, Pair.left(newProjects),
               Pair.right(newProjects));
-
       bb.root = rel;
       distinctify(bb, false);
       rel = bb.root;
@@ -698,12 +694,8 @@ public class SqlToRelConverter {
       }
 
       rel =
-          new LogicalProject(
-              cluster,
-              rel,
-              Pair.left(undoProjects),
+          LogicalProject.create(rel, Pair.left(undoProjects),
               Pair.right(undoProjects));
-
       bb.setRoot(
           rel,
           false);
@@ -780,7 +772,7 @@ public class SqlToRelConverter {
       bb.setRoot(
           new LogicalProject(
               cluster,
-              cluster.traitSetOf(RelCollationImpl.PRESERVE),
+              cluster.traitSetOf(RelCollations.PRESERVE),
               bb.root,
               exprs,
               cluster.getTypeFactory().createStructType(
@@ -1030,16 +1022,17 @@ public class SqlToRelConverter {
         final int keyCount = leftKeys.size();
         final List<Integer> args = ImmutableIntList.range(0, keyCount);
         LogicalAggregate aggregate =
-            new LogicalAggregate(cluster, seek, false, ImmutableBitSet.of(),
-                null,
+            LogicalAggregate.create(seek, false, ImmutableBitSet.of(), null,
                 ImmutableList.of(
                     new AggregateCall(SqlStdOperatorTable.COUNT, false,
                         ImmutableList.<Integer>of(), longType, null),
                     new AggregateCall(SqlStdOperatorTable.COUNT, false,
                         args, longType, null)));
         LogicalJoin join =
-            new LogicalJoin(cluster, bb.root, aggregate,
-                rexBuilder.makeLiteral(true), JoinRelType.INNER,
+            LogicalJoin.create(bb.root,
+                aggregate,
+                rexBuilder.makeLiteral(true),
+                JoinRelType.INNER,
                 ImmutableSet.<String>of());
         bb.setRoot(join, false);
       }
@@ -1487,10 +1480,7 @@ public class SqlToRelConverter {
       unionInputs.add(convertRowConstructor(bb, call));
     }
     LogicalValues values =
-        new LogicalValues(
-            cluster,
-            rowType,
-            tupleList.build());
+        LogicalValues.create(cluster, rowType, tupleList.build());
     RelNode resultRel;
     if (unionInputs.isEmpty()) {
       resultRel = values;
@@ -1498,12 +1488,7 @@ public class SqlToRelConverter {
       if (!values.getTuples().isEmpty()) {
         unionInputs.add(values);
       }
-      LogicalUnion union =
-          new LogicalUnion(
-              cluster,
-              unionInputs,
-              true);
-      resultRel = union;
+      resultRel = LogicalUnion.create(unionInputs, true);
     }
     leaves.add(resultRel);
     return resultRel;
@@ -1862,7 +1847,7 @@ public class SqlToRelConverter {
       if (shouldConvertTableAccess) {
         tableRel = toRel(table);
       } else {
-        tableRel = new LogicalTableScan(cluster, table);
+        tableRel = LogicalTableScan.create(cluster, table);
       }
       bb.setRoot(tableRel, true);
       if (usedDataset[0]) {
@@ -2004,8 +1989,7 @@ public class SqlToRelConverter {
     Set<RelColumnMapping> columnMappings =
         getColumnMappings(operator);
     LogicalTableFunctionScan callRel =
-        new LogicalTableFunctionScan(
-            cluster,
+        LogicalTableFunctionScan.create(cluster,
             inputs,
             rexCall,
             elementType,
@@ -2145,12 +2129,8 @@ public class SqlToRelConverter {
                   ImmutableSet.copyOf(Util.skip(correlNames)));
           rightRel = rightRel.accept(dedup);
         }
-        LogicalCorrelate corr = new LogicalCorrelate(
-            rightRel.getCluster(),
-            leftRel,
-            rightRel,
-            new CorrelationId(correlNames.get(0)),
-            requiredColumns.build(),
+        LogicalCorrelate corr = LogicalCorrelate.create(leftRel, rightRel,
+            new CorrelationId(correlNames.get(0)), requiredColumns.build(),
             SemiJoinType.of(joinType));
         if (!joinCond.isAlwaysTrue()) {
           return RelOptUtil.createFilter(corr, joinCond);
@@ -2753,12 +2733,7 @@ public class SqlToRelConverter {
   protected RelNode createAggregate(Blackboard bb, boolean indicator,
       ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
       List<AggregateCall> aggCalls) {
-    return new LogicalAggregate(
-        cluster,
-        bb.root,
-        indicator,
-        groupSet,
-        groupSets,
+    return LogicalAggregate.create(bb.root, indicator, groupSet, groupSets,
         aggCalls);
   }
 
@@ -2957,18 +2932,12 @@ public class SqlToRelConverter {
     }
     switch (call.getKind()) {
     case UNION:
-      return new LogicalUnion(
-          cluster,
-          ImmutableList.of(left, right),
-          all);
+      return LogicalUnion.create(ImmutableList.of(left, right), all);
 
     case INTERSECT:
       // TODO:  all
       if (!all) {
-        return new LogicalIntersect(
-            cluster,
-            ImmutableList.of(left, right),
-            all);
+        return LogicalIntersect.create(ImmutableList.of(left, right), all);
       } else {
         throw Util.newInternal(
             "set operator INTERSECT ALL not suported");
@@ -2977,10 +2946,7 @@ public class SqlToRelConverter {
     case EXCEPT:
       // TODO:  all
       if (!all) {
-        return new LogicalMinus(
-            cluster,
-            ImmutableList.of(left, right),
-            all);
+        return LogicalMinus.create(ImmutableList.of(left, right), all);
       } else {
         throw Util.newInternal(
             "set operator EXCEPT ALL not suported");
@@ -3016,14 +2982,8 @@ public class SqlToRelConverter {
           null,
           false);
     }
-    return new LogicalTableModify(
-        cluster,
-        targetTable,
-        catalogReader,
-        massagedRel,
-        LogicalTableModify.Operation.INSERT,
-        null,
-        false);
+    return LogicalTableModify.create(targetTable, catalogReader, massagedRel,
+        LogicalTableModify.Operation.INSERT, null, false);
   }
 
   private RelOptTable.ToRelContext createToRelContext() {
@@ -3164,14 +3124,8 @@ public class SqlToRelConverter {
   private RelNode convertDelete(SqlDelete call) {
     RelOptTable targetTable = getTargetTable(call);
     RelNode sourceRel = convertSelect(call.getSourceSelect());
-    return new LogicalTableModify(
-        cluster,
-        targetTable,
-        catalogReader,
-        sourceRel,
-        LogicalTableModify.Operation.DELETE,
-        null,
-        false);
+    return LogicalTableModify.create(targetTable, catalogReader, sourceRel,
+        LogicalTableModify.Operation.DELETE, null, false);
   }
 
   private RelNode convertUpdate(SqlUpdate call) {
@@ -3187,14 +3141,8 @@ public class SqlToRelConverter {
 
     RelNode sourceRel = convertSelect(call.getSourceSelect());
 
-    return new LogicalTableModify(
-        cluster,
-        targetTable,
-        catalogReader,
-        sourceRel,
-        LogicalTableModify.Operation.UPDATE,
-        targetColumnNameList,
-        false);
+    return LogicalTableModify.create(targetTable, catalogReader, sourceRel,
+        LogicalTableModify.Operation.UPDATE, targetColumnNameList, false);
   }
 
   private RelNode convertMerge(SqlMerge call) {
@@ -3270,14 +3218,8 @@ public class SqlToRelConverter {
     RelNode massagedRel =
         RelOptUtil.createProject(join, projects, null, true);
 
-    return new LogicalTableModify(
-        cluster,
-        targetTable,
-        catalogReader,
-        massagedRel,
-        LogicalTableModify.Operation.MERGE,
-        targetColumnNameList,
-        false);
+    return LogicalTableModify.create(targetTable, catalogReader, massagedRel,
+        LogicalTableModify.Operation.MERGE, targetColumnNameList, false);
   }
 
   /**
@@ -3507,12 +3449,7 @@ public class SqlToRelConverter {
       RexNode condition,
       JoinRelType joinType,
       Set<String> variablesStopped) {
-    return new LogicalJoin(
-        cluster,
-        left,
-        right,
-        condition,
-        joinType,
+    return LogicalJoin.create(left, right, condition, joinType,
         variablesStopped);
   }
 
@@ -3692,10 +3629,7 @@ public class SqlToRelConverter {
           true);
     } else {
       bb.setRoot(
-          new LogicalUnion(
-              cluster,
-              unionRels,
-              true),
+          LogicalUnion.create(unionRels, true),
           true);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/main/java/org/apache/calcite/tools/Programs.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/Programs.java b/core/src/main/java/org/apache/calcite/tools/Programs.java
index 115cbf1..154766f 100644
--- a/core/src/main/java/org/apache/calcite/tools/Programs.java
+++ b/core/src/main/java/org/apache/calcite/tools/Programs.java
@@ -101,6 +101,7 @@ public class Programs {
   public static final ImmutableSet<RelOptRule> RULE_SET =
       ImmutableSet.of(
           EnumerableRules.ENUMERABLE_JOIN_RULE,
+          EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE,
           EnumerableRules.ENUMERABLE_SEMI_JOIN_RULE,
           EnumerableRules.ENUMERABLE_CORRELATE_RULE,
           EnumerableRules.ENUMERABLE_PROJECT_RULE,
@@ -261,7 +262,9 @@ public class Programs {
           public RelNode run(RelOptPlanner planner, RelNode rel,
               RelTraitSet requiredOutputTraits) {
             final RelNode rootRel2 =
-                planner.changeTraits(rel, requiredOutputTraits);
+                rel.getTraitSet().equals(requiredOutputTraits)
+                ? rel
+                : planner.changeTraits(rel, requiredOutputTraits);
             assert rootRel2 != null;
 
             planner.setRoot(rootRel2);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/main/java/org/apache/calcite/util/Bug.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Bug.java b/core/src/main/java/org/apache/calcite/util/Bug.java
index c91cde4..46189c2 100644
--- a/core/src/main/java/org/apache/calcite/util/Bug.java
+++ b/core/src/main/java/org/apache/calcite/util/Bug.java
@@ -182,6 +182,10 @@ public abstract class Bug {
   /**
    * Use this method to flag code that should be re-visited after upgrading
    * a component.
+   *
+   * <p>If the intended change is that a class or member be removed, flag
+   * instead using a {@link Deprecated} annotation followed by a comment such as
+   * "to be removed before 2.0".
    */
   public static boolean upgrade(String remark) {
     Util.discard(remark);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 779196a..fac2ef1 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -81,6 +81,7 @@ import java.util.Map;
 import java.util.TimeZone;
 import javax.sql.DataSource;
 
+import static org.apache.calcite.rel.metadata.BuiltInMetadata.Collation;
 import static org.apache.calcite.rel.metadata.BuiltInMetadata.ColumnOrigin;
 import static org.apache.calcite.rel.metadata.BuiltInMetadata.ColumnUniqueness;
 import static org.apache.calcite.rel.metadata.BuiltInMetadata.CumulativeCost;
@@ -124,6 +125,9 @@ public enum BuiltInMethod {
       String.class, Function1.class),
   JOIN(ExtendedEnumerable.class, "join", Enumerable.class, Function1.class,
       Function1.class, Function2.class),
+  MERGE_JOIN(Enumerables.class, "mergeJoin", Enumerable.class, Enumerable.class,
+      Function1.class, Function1.class, Function2.class, boolean.class,
+      boolean.class),
   SLICE0(Enumerables.class, "slice0", Enumerable.class),
   SEMI_JOIN(Enumerables.class, "semiJoin", Enumerable.class, Enumerable.class,
       Function1.class, Function1.class),
@@ -164,6 +168,7 @@ public enum BuiltInMethod {
   LIST_N(FlatLists.class, "of", Object[].class),
   LIST2(FlatLists.class, "of", Object.class, Object.class),
   LIST3(FlatLists.class, "of", Object.class, Object.class, Object.class),
+  COMPARABLE_EMPTY_LIST(FlatLists.class, "COMPARABLE_EMPTY_LIST", true),
   IDENTITY_COMPARER(Functions.class, "identityComparer"),
   IDENTITY_SELECTOR(Functions.class, "identitySelector"),
   AS_ENUMERABLE(Linq4j.class, "asEnumerable", Object[].class),
@@ -286,6 +291,7 @@ public enum BuiltInMethod {
   UNIQUE_KEYS(UniqueKeys.class, "getUniqueKeys", boolean.class),
   COLUMN_UNIQUENESS(ColumnUniqueness.class, "areColumnsUnique",
       ImmutableBitSet.class, boolean.class),
+  COLLATIONS(Collation.class, "collations"),
   ROW_COUNT(RowCount.class, "getRowCount"),
   DISTINCT_ROW_COUNT(DistinctRowCount.class, "getDistinctRowCount",
       ImmutableBitSet.class, RexNode.class),
@@ -323,22 +329,26 @@ public enum BuiltInMethod {
     MAP = builder.build();
   }
 
+  private BuiltInMethod(Method method, Constructor constructor, Field field) {
+    this.method = method;
+    this.constructor = constructor;
+    this.field = field;
+  }
+
+  /** Defines a method. */
   BuiltInMethod(Class clazz, String methodName, Class... argumentTypes) {
-    this.method = Types.lookupMethod(clazz, methodName, argumentTypes);
-    this.constructor = null;
-    this.field = null;
+    this(Types.lookupMethod(clazz, methodName, argumentTypes), null, null);
   }
 
+  /** Defines a constructor. */
   BuiltInMethod(Class clazz, Class... argumentTypes) {
-    this.method = null;
-    this.constructor = Types.lookupConstructor(clazz, argumentTypes);
-    this.field = null;
+    this(null, Types.lookupConstructor(clazz, argumentTypes), null);
   }
 
+  /** Defines a field. */
   BuiltInMethod(Class clazz, String fieldName, boolean dummy) {
-    this.method = null;
-    this.constructor = null;
-    this.field = Types.lookupField(clazz, fieldName);
+    this(null, null, Types.lookupField(clazz, fieldName));
+    assert dummy : "dummy value for method overloading must be true";
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/main/java/org/apache/calcite/util/ImmutableBitSet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/ImmutableBitSet.java b/core/src/main/java/org/apache/calcite/util/ImmutableBitSet.java
index 43c3d37..670b10e 100644
--- a/core/src/main/java/org/apache/calcite/util/ImmutableBitSet.java
+++ b/core/src/main/java/org/apache/calcite/util/ImmutableBitSet.java
@@ -735,6 +735,11 @@ public class ImmutableBitSet
     throw new IndexOutOfBoundsException("index out of range: " + n);
   }
 
+  /** Returns a bit set the same as this but with a given bit set. */
+  public ImmutableBitSet set(int i) {
+    return union(ImmutableBitSet.of(i));
+  }
+
   /** Returns a bit set the same as this but with a given bit cleared. */
   public ImmutableBitSet clear(int i) {
     return except(ImmutableBitSet.of(i));

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java b/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
index ace0c95..e4d147d 100644
--- a/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
+++ b/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
@@ -131,7 +131,7 @@ public class ImmutableIntList extends FlatLists.AbstractFlatList<Integer> {
           : (T[]) Array.newInstance(
               a.getClass().getComponentType(), size);
     }
-    if (a.getClass() == Integer[].class) {
+    if ((Class) a.getClass() == Integer[].class) {
       final Integer[] integers = (Integer[]) a;
       for (int i = 0; i < integers.length; i++) {
         integers[i] = ints[i];

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/main/java/org/apache/calcite/util/Util.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Util.java b/core/src/main/java/org/apache/calcite/util/Util.java
index 4831a60..5aa8246 100644
--- a/core/src/main/java/org/apache/calcite/util/Util.java
+++ b/core/src/main/java/org/apache/calcite/util/Util.java
@@ -1153,6 +1153,11 @@ public class Util {
     return buf.toString();
   }
 
+  /** Converts a list of strings to a string separated by newlines. */
+  public static String lines(Iterable<String> strings) {
+    return toString(strings, "", "\n", "");
+  }
+
   /**
    * Converts a Java timezone to POSIX format, so that the boost C++ library
    * can instantiate timezone objects.
@@ -1978,9 +1983,19 @@ public class Util {
 
   /** Returns whether one list is a prefix of another. */
   public static <E> boolean startsWith(List<E> list0, List<E> list1) {
-    return list0.equals(list1)
-        || list0.size() > list1.size()
-        && list0.subList(0, list1.size()).equals(list1);
+    if (list0 == list1) {
+      return true;
+    }
+    final int size = list1.size();
+    if (list0.size() < size) {
+      return false;
+    }
+    for (int i = 0; i < size; i++) {
+      if (!Objects.equal(list0.get(i), list1.get(i))) {
+        return false;
+      }
+    }
+    return true;
   }
 
   /** Converts a number into human-readable form, with 3 digits and a "K", "M"

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
index 0410398..f5dd8a2 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
@@ -119,24 +119,24 @@ public class RelWriterTest {
                   RelOptSchema relOptSchema, SchemaPlus rootSchema) {
                 rootSchema.add("hr",
                     new ReflectiveSchema(new JdbcTest.HrSchema()));
-                LogicalTableScan table =
-                    new LogicalTableScan(cluster,
+                LogicalTableScan scan =
+                    LogicalTableScan.create(cluster,
                         relOptSchema.getTableForMember(
                             Arrays.asList("hr", "emps")));
                 final RexBuilder rexBuilder = cluster.getRexBuilder();
                 LogicalFilter filter =
-                    new LogicalFilter(cluster, table,
+                    LogicalFilter.create(scan,
                         rexBuilder.makeCall(
                             SqlStdOperatorTable.EQUALS,
                             rexBuilder.makeFieldAccess(
-                                rexBuilder.makeRangeReference(table),
+                                rexBuilder.makeRangeReference(scan),
                                 "deptno", true),
                             rexBuilder.makeExactLiteral(BigDecimal.TEN)));
                 final RelJsonWriter writer = new RelJsonWriter();
                 final RelDataType bigIntType =
                     cluster.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
                 LogicalAggregate aggregate =
-                    new LogicalAggregate(cluster, filter, false,
+                    LogicalAggregate.create(filter, false,
                         ImmutableBitSet.of(0), null,
                         ImmutableList.of(
                             new AggregateCall(SqlStdOperatorTable.COUNT,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/plan/volcano/VolcanoPlannerTraitTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/plan/volcano/VolcanoPlannerTraitTest.java b/core/src/test/java/org/apache/calcite/plan/volcano/VolcanoPlannerTraitTest.java
index aefed30..66654da 100644
--- a/core/src/test/java/org/apache/calcite/plan/volcano/VolcanoPlannerTraitTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/volcano/VolcanoPlannerTraitTest.java
@@ -121,9 +121,9 @@ public class VolcanoPlannerTraitTest {
             new NoneSingleRel(cluster, noneLeafRel), ALT_TRAIT2);
 
     RelNode convertedRel =
-        planner.changeTraits(
-            noneRel,
-            cluster.traitSetOf(EnumerableConvention.INSTANCE, ALT_TRAIT2));
+        planner.changeTraits(noneRel,
+            cluster.traitSetOf(EnumerableConvention.INSTANCE)
+                .replace(ALT_TRAIT2));
 
     planner.setRoot(convertedRel);
     RelNode result = planner.chooseDelegate().findBestExp();
@@ -177,10 +177,9 @@ public class VolcanoPlannerTraitTest {
             new NoneSingleRel(cluster, noneLeafRel), ALT_TRAIT2);
 
     RelNode convertedRel =
-        planner.changeTraits(
-            noneRel,
-            cluster.traitSetOf(
-                EnumerableConvention.INSTANCE, ALT_TRAIT2));
+        planner.changeTraits(noneRel,
+            cluster.traitSetOf(EnumerableConvention.INSTANCE)
+                .replace(ALT_TRAIT2));
 
     planner.setRoot(convertedRel);
     RelNode result = planner.chooseDelegate().findBestExp();
@@ -251,7 +250,7 @@ public class VolcanoPlannerTraitTest {
       return ordinal;
     }
 
-    public boolean subsumes(RelTrait trait) {
+    public boolean satisfies(RelTrait trait) {
       return equals(trait);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/rel/RelCollationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/rel/RelCollationTest.java b/core/src/test/java/org/apache/calcite/rel/RelCollationTest.java
new file mode 100644
index 0000000..5486e42
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/rel/RelCollationTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for {@link RelCollation} and {@link RelFieldCollation}.
+ */
+public class RelCollationTest {
+  /** Unit test for {@link RelCollations#contains}. */
+  @Test public void testCollationContains() {
+    final RelCollation collation =
+        RelCollations.of(
+            new RelFieldCollation(2, RelFieldCollation.Direction.ASCENDING),
+            new RelFieldCollation(1, RelFieldCollation.Direction.DESCENDING));
+    assertThat(RelCollations.contains(collation, Arrays.asList(2)), is(true));
+    assertThat(RelCollations.contains(collation, Arrays.asList(1)), is(false));
+    assertThat(RelCollations.contains(collation, Arrays.asList(0)), is(false));
+    assertThat(RelCollations.contains(collation, Arrays.asList(2, 1)),
+        is(true));
+    assertThat(RelCollations.contains(collation, Arrays.asList(2, 0)),
+        is(false));
+    assertThat(RelCollations.contains(collation, Arrays.asList(2, 1, 3)),
+        is(false));
+    assertThat(RelCollations.contains(collation, Arrays.<Integer>asList()),
+        is(true));
+  }
+
+  /** Unit test for
+   *  {@link org.apache.calcite.rel.RelCollationImpl#compareTo}. */
+  @Test public void testCollationCompare() {
+    assertThat(collation(1, 2).compareTo(collation(1, 2)), equalTo(0));
+    assertThat(collation(1, 2).compareTo(collation(1)), equalTo(1));
+    assertThat(collation(1).compareTo(collation(1, 2)), equalTo(-1));
+    assertThat(collation(1, 3).compareTo(collation(1, 2)), equalTo(1));
+    assertThat(collation(0, 3).compareTo(collation(1, 2)), equalTo(-1));
+    assertThat(collation().compareTo(collation(0)), equalTo(-1));
+    assertThat(collation(1).compareTo(collation()), equalTo(1));
+  }
+
+  private static RelCollation collation(int... ordinals) {
+    final List<RelFieldCollation> list = Lists.newArrayList();
+    for (int ordinal : ordinals) {
+      list.add(new RelFieldCollation(ordinal));
+    }
+    return RelCollations.of(list);
+  }
+}
+
+// End RelCollationTest.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index bec93d8..48eb1d5 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -1062,6 +1062,22 @@ public class SqlParserTest {
             + "ORDER BY 1, 2 DESC");
   }
 
+  @Test public void testOrderUnion() {
+    // ORDER BY inside UNION not allowed
+    sql("select a from t order by a\n"
+        + "^union^ all\n"
+        + "select b from t order by b")
+        .fails("(?s).*Encountered \"union\" at .*");
+  }
+
+  @Test public void testLimitUnion() {
+    // LIMIT inside UNION not allowed
+    sql("select a from t limit 10\n"
+        + "^union^ all\n"
+        + "select b from t order by b")
+        .fails("(?s).*Encountered \"union\" at .*");
+  }
+
   @Test public void testUnionOfNonQueryFails() {
     checkFails(
         "select 1 from emp union ^2^ + 5",

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index 25f249a..77faaf0 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -78,7 +78,6 @@ import javax.sql.DataSource;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
@@ -342,8 +341,10 @@ public class CalciteAssert {
           CalciteAssert.toStringList(resultSet, actualList);
           Collections.sort(actualList);
 
-          // Use assertArrayEquals since it implements fine-grained comparison.
-          assertArrayEquals(expectedList.toArray(), actualList.toArray());
+          if (!actualList.equals(expectedList)) {
+            assertThat(Util.lines(actualList),
+                equalTo(Util.lines(expectedList)));
+          }
           return null;
         } catch (SQLException e) {
           throw new RuntimeException(e);
@@ -358,9 +359,7 @@ public class CalciteAssert {
       public Void apply(ResultSet s) {
         try {
           final String actual = Util.toLinux(CalciteAssert.toString(s));
-          if (!actual.contains(expected)) {
-            assertEquals("contains", expected, actual);
-          }
+          assertThat(actual, containsString(expected));
           return null;
         } catch (SQLException e) {
           throw new RuntimeException(e);
@@ -377,9 +376,7 @@ public class CalciteAssert {
           final String actual = Util.toLinux(CalciteAssert.toString(s));
           final String maskedActual =
               actual.replaceAll(", id = [0-9]+", "");
-          if (!maskedActual.contains(expected)) {
-            assertEquals("contains", expected, maskedActual);
-          }
+          assertThat(maskedActual, containsString(expected));
           return null;
         } catch (SQLException e) {
           throw new RuntimeException(e);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/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 d36e3a5..d9ac400 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
@@ -22,6 +22,7 @@ import org.apache.calcite.plan.RelOptUtilTest;
 import org.apache.calcite.plan.RelWriterTest;
 import org.apache.calcite.plan.volcano.VolcanoPlannerTest;
 import org.apache.calcite.plan.volcano.VolcanoPlannerTraitTest;
+import org.apache.calcite.rel.RelCollationTest;
 import org.apache.calcite.rex.RexExecutorTest;
 import org.apache.calcite.runtime.BinarySearchTest;
 import org.apache.calcite.runtime.EnumerablesTest;
@@ -64,6 +65,7 @@ import org.junit.runners.Suite;
     DirectedGraphTest.class,
     ReflectVisitorTest.class,
     RelOptUtilTest.class,
+    RelCollationTest.class,
     UtilTest.class,
     MappingTest.class,
     CalciteResourceTest.class,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/test/JdbcFrontJdbcBackLinqMiddleTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcFrontJdbcBackLinqMiddleTest.java b/core/src/test/java/org/apache/calcite/test/JdbcFrontJdbcBackLinqMiddleTest.java
index 540c6a8..3ef4dea 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcFrontJdbcBackLinqMiddleTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcFrontJdbcBackLinqMiddleTest.java
@@ -64,7 +64,7 @@ public class JdbcFrontJdbcBackLinqMiddleTest {
 
   @Test public void testCase() {
     that()
-        .with(CalciteAssert.Config.JDBC_FOODMART)
+        .with(CalciteAssert.Config.FOODMART_CLONE)
         .query("select \"day\",\n"
             + " \"week_day\",\n"
             + " case when \"day\" < 3 then upper(\"week_day\")\n"

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java b/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
index c34becd..a6d23e0 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
@@ -68,9 +68,9 @@ public class JdbcFrontLinqBackTest {
             + "from \"foodmart\".\"sales_fact_1997\" as s\n"
             + "join \"hr\".\"emps\" as e\n"
             + "on e.\"empid\" = s.\"cust_id\"")
-        .returns(""
-            + "cust_id=100; prod_id=10; empid=100; deptno=10; name=Bill; salary=10000.0; commission=1000\n"
-            + "cust_id=150; prod_id=20; empid=150; deptno=10; name=Sebastian; salary=7000.0; commission=null\n");
+        .returnsUnordered(
+            "cust_id=100; prod_id=10; empid=100; deptno=10; name=Bill; salary=10000.0; commission=1000",
+            "cust_id=150; prod_id=20; empid=150; deptno=10; name=Sebastian; salary=7000.0; commission=null");
   }
 
   /**
@@ -93,6 +93,11 @@ public class JdbcFrontLinqBackTest {
         .query("select upper(\"name\") as un, \"deptno\"\n"
             + "from \"hr\".\"emps\" as e\n"
             + "order by \"deptno\", \"name\" desc")
+        .explainContains(
+            "EnumerableCalc(expr#0..1=[{inputs}], expr#2=[UPPER($t1)], UN=[$t2], deptno=[$t0])\n"
+            + "  EnumerableSort(sort0=[$0], sort1=[$1], dir0=[ASC], dir1=[DESC])\n"
+            + "    EnumerableCalc(expr#0..4=[{inputs}], deptno=[$t1], name=[$t2])\n"
+            + "      EnumerableTableScan(table=[[hr, emps]])")
         .returns("UN=THEODORE; deptno=10\n"
             + "UN=SEBASTIAN; deptno=10\n"
             + "UN=BILL; deptno=10\n"

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 89c3b47..425cb6c 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -2542,6 +2542,26 @@ public class JdbcTest {
         .returns("EMPNO=1; DESC=SameName\n");
   }
 
+  /** Tests a merge-join. */
+  @Test public void testMergeJoin() {
+    CalciteAssert.that()
+        .with(CalciteAssert.Config.REGULAR)
+        .query("select \"emps\".\"empid\",\n"
+            + " \"depts\".\"deptno\", \"depts\".\"name\"\n"
+            + "from \"hr\".\"emps\"\n"
+            + " join \"hr\".\"depts\" using (\"deptno\")")
+        .explainContains(""
+            + "EnumerableCalc(expr#0..3=[{inputs}], empid=[$t2], deptno=[$t0], name=[$t1])\n"
+            + "  EnumerableJoin(condition=[=($0, $3)], joinType=[inner])\n"
+            + "    EnumerableCalc(expr#0..3=[{inputs}], proj#0..1=[{exprs}])\n"
+            + "      EnumerableTableScan(table=[[hr, depts]])\n"
+            + "    EnumerableCalc(expr#0..4=[{inputs}], proj#0..1=[{exprs}])\n"
+            + "      EnumerableTableScan(table=[[hr, emps]])")
+        .returns("empid=100; deptno=10; name=Sales\n"
+            + "empid=150; deptno=10; name=Sales\n"
+            + "empid=110; deptno=10; name=Sales\n");
+  }
+
   /** Tests a cartesian product aka cross join. */
   @Test public void testCartesianJoin() {
     CalciteAssert.hr()
@@ -2870,9 +2890,8 @@ public class JdbcTest {
             + "where \"store_id\" < 10\n"
             + "order by 1 fetch first 5 rows only")
         .explainContains("PLAN=EnumerableLimit(fetch=[5])\n"
-            + "  EnumerableSort(sort0=[$0], dir0=[ASC])\n"
-            + "    EnumerableCalc(expr#0..23=[{inputs}], expr#24=[10], expr#25=[<($t0, $t24)], store_id=[$t0], grocery_sqft=[$t16], $condition=[$t25])\n"
-            + "      EnumerableTableScan(table=[[foodmart2, store]])\n")
+            + "  EnumerableCalc(expr#0..23=[{inputs}], expr#24=[10], expr#25=[<($t0, $t24)], store_id=[$t0], grocery_sqft=[$t16], $condition=[$t25])\n"
+            + "    EnumerableTableScan(table=[[foodmart2, store]])\n")
         .returns("store_id=0; grocery_sqft=null\n"
             + "store_id=1; grocery_sqft=17475\n"
             + "store_id=2; grocery_sqft=22271\n"
@@ -3098,6 +3117,36 @@ public class JdbcTest {
         .returnsCount(10);
   }
 
+  /** ORDER BY on a sort-key does not require a sort. */
+  @Test public void testOrderOnSortedTable() throws IOException {
+    // The ArrayTable "store" is sorted by "store_id".
+    CalciteAssert.that()
+        .with(CalciteAssert.Config.FOODMART_CLONE)
+        .query("select \"day\"\n"
+            + "from \"days\"\n"
+            + "order by \"day\"")
+        .returns("day=1\n"
+            + "day=2\n"
+            + "day=3\n"
+            + "day=4\n"
+            + "day=5\n"
+            + "day=6\n"
+            + "day=7\n");
+  }
+
+  /** ORDER BY on a sort-key does not require a sort. */
+  @Test public void testOrderSorted() throws IOException {
+    // The ArrayTable "store" is sorted by "store_id".
+    CalciteAssert.that()
+        .with(CalciteAssert.Config.FOODMART_CLONE)
+        .query("select \"store_id\"\n"
+            + "from \"store\"\n"
+            + "order by \"store_id\" limit 3")
+        .returns("store_id=0\n"
+            + "store_id=1\n"
+            + "store_id=2\n");
+  }
+
   @Test public void testWhereNot() throws IOException {
     CalciteAssert.that()
         .with(CalciteAssert.Config.FOODMART_CLONE)
@@ -3187,8 +3236,8 @@ public class JdbcTest {
         .with(CalciteAssert.Config.FOODMART_CLONE)
         .query("select * from \"time_by_day\"\n"
             + "order by \"time_id\"")
-        .explainContains("PLAN=EnumerableSort(sort0=[$0], dir0=[ASC])\n"
-            + "  EnumerableTableScan(table=[[foodmart2, time_by_day]])\n\n");
+        .explainContains(
+            "PLAN=EnumerableTableScan(table=[[foodmart2, time_by_day]])\n");
   }
 
   /** Tests sorting by a column that is already sorted. */
@@ -4374,6 +4423,10 @@ public class JdbcTest {
     checkRun("sql/sequence.oq");
   }
 
+  @Test public void testRunSort() throws Exception {
+    checkRun("sql/sort.oq");
+  }
+
   @Test public void testRunSubquery() throws Exception {
     checkRun("sql/subquery.oq");
   }
@@ -6256,8 +6309,7 @@ public class JdbcTest {
         TableModify.Operation operation,
         List<String> updateColumnList,
         boolean flattened) {
-      return new LogicalTableModify(
-          cluster, table, catalogReader, child, operation,
+      return LogicalTableModify.create(table, catalogReader, child, operation,
           updateColumnList, flattened);
     }
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/test/LatticeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/LatticeTest.java b/core/src/test/java/org/apache/calcite/test/LatticeTest.java
index 86e8007..b51e51c 100644
--- a/core/src/test/java/org/apache/calcite/test/LatticeTest.java
+++ b/core/src/test/java/org/apache/calcite/test/LatticeTest.java
@@ -346,6 +346,32 @@ public class LatticeTest {
         .returnsCount(4);
   }
 
+  /** Tests a query that is created within {@link #testTileAlgorithm()}. */
+  @Test public void testJG() {
+    CalciteAssert.that().with(CalciteAssert.Config.JDBC_FOODMART)
+        .query(
+            "SELECT \"s\".\"unit_sales\", \"p\".\"recyclable_package\", \"t\".\"the_day\", \"t\".\"the_year\", \"t\".\"quarter\", \"pc\".\"product_family\", COUNT(*) AS \"m0\", SUM(\"s\".\"store_sales\") AS \"m1\", SUM(\"s\".\"unit_sales\") AS \"m2\"\n"
+                + "FROM \"foodmart\".\"sales_fact_1997\" AS \"s\"\n"
+                + "JOIN \"foodmart\".\"product\" AS \"p\" ON \"s\".\"product_id\" = \"p\".\"product_id\"\n"
+                + "JOIN \"foodmart\".\"time_by_day\" AS \"t\" ON \"s\".\"time_id\" = \"t\".\"time_id\"\n"
+                + "JOIN \"foodmart\".\"product_class\" AS \"pc\" ON \"p\".\"product_class_id\" = \"pc\".\"product_class_id\"\n"
+                + "GROUP BY \"s\".\"unit_sales\", \"p\".\"recyclable_package\", \"t\".\"the_day\", \"t\".\"the_year\", \"t\".\"quarter\", \"pc\".\"product_family\"")
+        .explainContains(
+            "EnumerableAggregate(group=[{0, 1, 2, 3, 4, 5}], m0=[COUNT()], m1=[SUM($6)], m2=[SUM($0)])\n"
+                + "  EnumerableCalc(expr#0..37=[{inputs}], unit_sales=[$t17], recyclable_package=[$t26], the_day=[$t2], the_year=[$t4], quarter=[$t8], product_family=[$t37], store_sales=[$t15])\n"
+                + "    EnumerableJoin(condition=[=($0, $11)], joinType=[inner])\n"
+                + "      JdbcToEnumerableConverter\n"
+                + "        JdbcTableScan(table=[[foodmart, time_by_day]])\n"
+                + "      EnumerableJoin(condition=[=($8, $23)], joinType=[inner])\n"
+                + "        EnumerableJoin(condition=[=($0, $9)], joinType=[inner])\n"
+                + "          JdbcToEnumerableConverter\n"
+                + "            JdbcTableScan(table=[[foodmart, sales_fact_1997]])\n"
+                + "          JdbcToEnumerableConverter\n"
+                + "            JdbcTableScan(table=[[foodmart, product]])\n"
+                + "        JdbcToEnumerableConverter\n"
+                + "          JdbcTableScan(table=[[foodmart, product_class]])");
+  }
+
   /** Tests a query that uses no columns from the fact table. */
   @Test public void testGroupByEmpty() {
     foodmartModel()

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
index ed8e9a6..d5e94a9 100644
--- a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
+++ b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
@@ -22,7 +22,7 @@ import org.apache.calcite.plan.RelOptSchema;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelCollationImpl;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.logical.LogicalTableScan;
@@ -369,10 +369,8 @@ public class MockCatalogReader implements Prepare.CatalogReader {
                 ? RelFieldCollation.Direction.DESCENDING
                 : RelFieldCollation.Direction.ASCENDING;
         collationList.add(
-            RelCollationImpl.of(
-                new RelFieldCollation(
-                    i,
-                    direction,
+            RelCollations.of(
+                new RelFieldCollation(i, direction,
                     RelFieldCollation.NullDirection.UNSPECIFIED)));
       }
     }
@@ -442,7 +440,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     }
 
     public RelNode toRel(ToRelContext context) {
-      return new LogicalTableScan(context.getCluster(), this);
+      return LogicalTableScan.create(context.getCluster(), this);
     }
 
     public List<RelCollation> getCollationList() {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index 3782957..0e60487 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -16,22 +16,49 @@
  */
 package org.apache.calcite.test;
 
+import org.apache.calcite.adapter.enumerable.EnumerableMergeJoin;
+import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptSchema;
 import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.rel.InvalidRelException;
+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.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.logical.LogicalAggregate;
 import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.logical.LogicalSort;
+import org.apache.calcite.rel.logical.LogicalTableScan;
+import org.apache.calcite.rel.logical.LogicalValues;
 import org.apache.calcite.rel.metadata.CachingRelMetadataProvider;
 import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider;
 import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider;
 import org.apache.calcite.rel.metadata.Metadata;
 import org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelColumnOrigin;
+import org.apache.calcite.rel.metadata.RelMdCollation;
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.ImmutableIntList;
 
+import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 
@@ -41,6 +68,7 @@ import org.junit.Ignore;
 import org.junit.Test;
 
 import java.lang.reflect.Method;
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -635,6 +663,136 @@ public class RelMetadataTest extends SqlToRelTestBase {
     assertThat(buf.size(), equalTo(7));
   }
 
+  /** Unit test for
+   * {@link org.apache.calcite.rel.metadata.RelMdCollation#project}
+   * and other helper functions for deducing collations. */
+  @Test public void testCollation() {
+    final Project rel = (Project) convertSql("select * from emp, dept");
+    final Join join = (Join) rel.getInput();
+    final RelOptTable empTable = join.getInput(0).getTable();
+    final RelOptTable deptTable = join.getInput(1).getTable();
+    Frameworks.withPlanner(
+        new Frameworks.PlannerAction<Void>() {
+          public Void apply(RelOptCluster cluster,
+              RelOptSchema relOptSchema,
+              SchemaPlus rootSchema) {
+            checkCollation(cluster, empTable, deptTable);
+            return null;
+          }
+        });
+  }
+
+  private void checkCollation(RelOptCluster cluster, RelOptTable empTable,
+      RelOptTable deptTable) {
+    final RexBuilder rexBuilder = cluster.getRexBuilder();
+    final LogicalTableScan empScan = LogicalTableScan.create(cluster, empTable);
+
+    List<RelCollation> collations =
+        RelMdCollation.table(empScan.getTable());
+    assertThat(collations.size(), equalTo(0));
+
+    // ORDER BY field#0 ASC, field#1 ASC
+    final RelCollation collation =
+        RelCollations.of(new RelFieldCollation(0), new RelFieldCollation(1));
+    collations = RelMdCollation.sort(collation);
+    assertThat(collations.size(), equalTo(1));
+    assertThat(collations.get(0).getFieldCollations().size(), equalTo(2));
+
+    final Sort empSort = LogicalSort.create(empScan, collation, null, null);
+
+    final List<RexNode> projects =
+        ImmutableList.of(rexBuilder.makeInputRef(empSort, 1),
+            rexBuilder.makeLiteral("foo"),
+            rexBuilder.makeInputRef(empSort, 0),
+            rexBuilder.makeCall(SqlStdOperatorTable.MINUS,
+                rexBuilder.makeInputRef(empSort, 0),
+                rexBuilder.makeInputRef(empSort, 3)));
+
+    collations = RelMdCollation.project(empSort, projects);
+    assertThat(collations.size(), equalTo(1));
+    assertThat(collations.get(0).getFieldCollations().size(), equalTo(2));
+    assertThat(collations.get(0).getFieldCollations().get(0).getFieldIndex(),
+        equalTo(2));
+    assertThat(collations.get(0).getFieldCollations().get(1).getFieldIndex(),
+        equalTo(0));
+
+    final LogicalProject project = LogicalProject.create(empSort, projects,
+        ImmutableList.of("a", "b", "c", "d"));
+
+    final LogicalTableScan deptScan =
+        LogicalTableScan.create(cluster, deptTable);
+
+    final RelCollation deptCollation =
+        RelCollations.of(new RelFieldCollation(0), new RelFieldCollation(1));
+    final Sort deptSort =
+        LogicalSort.create(deptScan, deptCollation, null, null);
+
+    final ImmutableIntList leftKeys = ImmutableIntList.of(2);
+    final ImmutableIntList rightKeys = ImmutableIntList.of(0);
+    final EnumerableMergeJoin join;
+    try {
+      join = EnumerableMergeJoin.create(project, deptSort,
+          rexBuilder.makeLiteral(true), leftKeys, rightKeys, JoinRelType.INNER);
+    } catch (InvalidRelException e) {
+      throw Throwables.propagate(e);
+    }
+    collations =
+        RelMdCollation.mergeJoin(project, deptSort, leftKeys, rightKeys);
+    assertThat(collations,
+        equalTo(join.getTraitSet().getTraits(RelCollationTraitDef.INSTANCE)));
+
+    // Values (empty)
+    collations = RelMdCollation.values(empTable.getRowType(),
+        ImmutableList.<ImmutableList<RexLiteral>>of());
+    assertThat(collations.toString(),
+        equalTo("[[0, 1, 2, 3, 4, 5, 6, 7, 8], "
+            + "[1, 2, 3, 4, 5, 6, 7, 8], "
+            + "[2, 3, 4, 5, 6, 7, 8], "
+            + "[3, 4, 5, 6, 7, 8], "
+            + "[4, 5, 6, 7, 8], "
+            + "[5, 6, 7, 8], "
+            + "[6, 7, 8], "
+            + "[7, 8], "
+            + "[8]]"));
+
+    final LogicalValues emptyValues =
+        LogicalValues.createEmpty(cluster, empTable.getRowType());
+    assertThat(RelMetadataQuery.collations(emptyValues), equalTo(collations));
+
+    // Values (non-empty)
+    final RelDataType rowType = cluster.getTypeFactory().builder()
+        .add("a", SqlTypeName.INTEGER)
+        .add("b", SqlTypeName.INTEGER)
+        .add("c", SqlTypeName.INTEGER)
+        .add("d", SqlTypeName.INTEGER)
+        .build();
+    final ImmutableList.Builder<ImmutableList<RexLiteral>> tuples =
+        ImmutableList.builder();
+    // sort keys are [a], [a, b], [a, b, c], [a, b, c, d], [a, c], [b], [b, a],
+    //   [b, d]
+    // algorithm deduces [a, b, c, d], [b, d] which is a useful sub-set
+    addRow(tuples, rexBuilder, 1, 1, 1, 1);
+    addRow(tuples, rexBuilder, 1, 2, 0, 3);
+    addRow(tuples, rexBuilder, 2, 3, 2, 2);
+    addRow(tuples, rexBuilder, 3, 3, 1, 4);
+    collations = RelMdCollation.values(rowType, tuples.build());
+    assertThat(collations.toString(),
+        equalTo("[[0, 1, 2, 3], [1, 3]]"));
+
+    final LogicalValues values =
+        LogicalValues.create(cluster, rowType, tuples.build());
+    assertThat(RelMetadataQuery.collations(values), equalTo(collations));
+  }
+
+  private void addRow(ImmutableList.Builder<ImmutableList<RexLiteral>> builder,
+      RexBuilder rexBuilder, Object... values) {
+    ImmutableList.Builder<RexLiteral> b = ImmutableList.builder();
+    for (Object value : values) {
+      b.add(rexBuilder.makeExactLiteral(BigDecimal.valueOf((Integer) value)));
+    }
+    builder.add(b.build());
+  }
+
   /** Custom metadata interface. */
   public interface ColType extends Metadata {
     String getColType(int column);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/test/ScannableTableTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/ScannableTableTest.java b/core/src/test/java/org/apache/calcite/test/ScannableTableTest.java
index 1eb6b8c..4b51753 100644
--- a/core/src/test/java/org/apache/calcite/test/ScannableTableTest.java
+++ b/core/src/test/java/org/apache/calcite/test/ScannableTableTest.java
@@ -127,7 +127,7 @@ public class ScannableTableTest {
     resultSet.close();
     // Only 2 rows came out of the table. If the value is 4, it means that the
     // planner did not pass the filter down.
-    assertThat(buf.toString(), equalTo("returnCount=2"));
+    assertThat(buf.toString(), equalTo("returnCount=2, filter=4"));
     buf.setLength(0);
 
     // Now with an "uncooperative" filterable table that refuses to accept
@@ -161,7 +161,7 @@ public class ScannableTableTest {
     resultSet.close();
     // Only 2 rows came out of the table. If the value is 4, it means that the
     // planner did not pass the filter down.
-    assertThat(buf.toString(), equalTo("returnCount=2"));
+    assertThat(buf.toString(), equalTo("returnCount=2, filter=4"));
     buf.setLength(0);
 
     // Now with an "uncooperative" filterable table that refuses to accept
@@ -195,7 +195,8 @@ public class ScannableTableTest {
     assertThat(CalciteAssert.toString(resultSet),
         equalTo("k=1940; j=John\nk=1942; j=Paul\n"));
     resultSet.close();
-    assertThat(buf.toString(), equalTo("returnCount=2, projects=[2, 1]"));
+    assertThat(buf.toString(),
+        equalTo("returnCount=2, filter=4, projects=[2, 1]"));
     buf.setLength(0);
 
     // Filter on one of the projected columns.
@@ -467,6 +468,9 @@ public class ScannableTableTest {
       public void close() {
         current = null;
         buf.append("returnCount=").append(returnCount);
+        if (filter != null) {
+          buf.append(", filter=").append(filter);
+        }
         if (projects != null) {
           buf.append(", projects=").append(Arrays.toString(projects));
         }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
index 32e2dbe..180f877 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
@@ -24,7 +24,7 @@ import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelCollationImpl;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.logical.LogicalTableScan;
@@ -248,10 +248,8 @@ public abstract class SqlToRelTestBase {
                   ? RelFieldCollation.Direction.DESCENDING
                   : RelFieldCollation.Direction.ASCENDING;
           collationList.add(
-              RelCollationImpl.of(
-                  new RelFieldCollation(
-                      i,
-                      direction,
+              RelCollations.of(
+                  new RelFieldCollation(i, direction,
                       RelFieldCollation.NullDirection.UNSPECIFIED)));
         }
       }
@@ -344,9 +342,8 @@ public abstract class SqlToRelTestBase {
         return MockRelOptSchema.this;
       }
 
-      public RelNode toRel(
-          ToRelContext context) {
-        return new LogicalTableScan(context.getCluster(), this);
+      public RelNode toRel(ToRelContext context) {
+        return LogicalTableScan.create(context.getCluster(), this);
       }
 
       public List<RelCollation> getCollationList() {
@@ -411,7 +408,7 @@ public abstract class SqlToRelTestBase {
     }
 
     public RelNode toRel(ToRelContext context) {
-      return new LogicalTableScan(context.getCluster(), this);
+      return LogicalTableScan.create(context.getCluster(), this);
     }
 
     public List<RelCollation> getCollationList() {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java b/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java
index e882fc2..288adf3 100644
--- a/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java
@@ -93,13 +93,13 @@ public class FrameworksTest {
                         rexBuilder.makeRangeReference(tableRel), "i", true),
                     rexBuilder.makeExactLiteral(BigDecimal.ONE));
             final LogicalFilter filter =
-                new LogicalFilter(cluster, tableRel, condition);
+                LogicalFilter.create(tableRel, condition);
 
             // Specify that the result should be in Enumerable convention.
             final RelNode rootRel = filter;
             final RelOptPlanner planner = cluster.getPlanner();
-            RelTraitSet desiredTraits = rootRel.getTraitSet().replace(
-                EnumerableConvention.INSTANCE);
+            RelTraitSet desiredTraits =
+                cluster.traitSet().replace(EnumerableConvention.INSTANCE);
             final RelNode rootRel2 = planner.changeTraits(rootRel,
                 desiredTraits);
             planner.setRoot(rootRel2);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
index 54bee88..701d66a 100644
--- a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
@@ -352,7 +352,7 @@ public class PlannerTest {
 
   /** Unit test that parses, validates, converts and
    * plans for query using two duplicate order by.
-   * The duplicate order by should be removed by SortRemoveRule*/
+   * The duplicate order by should be removed by SortRemoveRule. */
   @Test public void testDuplicateSortPlan() throws Exception {
     RuleSet ruleSet =
         RuleSets.ofList(
@@ -370,6 +370,10 @@ public class PlannerTest {
     RelNode convert = planner.convert(validate);
     RelTraitSet traitSet = planner.getEmptyTraitSet()
         .replace(EnumerableConvention.INSTANCE);
+    if (traitSet.getTrait(RelCollationTraitDef.INSTANCE) == null) {
+      // SortRemoveRule can only work if collation trait is enabled.
+      return;
+    }
     RelNode transform = planner.transform(0, traitSet, convert);
     assertThat(toString(transform),
         equalTo("EnumerableProject(empid=[$0])\n"
@@ -721,6 +725,7 @@ public class PlannerTest {
             + "cross join \"department\"",
         "EnumerableProject(product_id=[$0], time_id=[$1], customer_id=[$2], promotion_id=[$3], store_id=[$4], store_sales=[$5], store_cost=[$6], unit_sales=[$7], customer_id0=[$8], account_num=[$9], lname=[$10], fname=[$11], mi=[$12], address1=[$13], address2=[$14], address3=[$15], address4=[$16], city=[$17], state_province=[$18], postal_code=[$19], country=[$20], customer_region_id=[$21], phone1=[$22], phone2=[$23], birthdate=[$24], marital_status=[$25], yearly_income=[$26], gender=[$27], total_children=[$28], num_children_at_home=[$29], education=[$30], date_accnt_opened=[$31], member_card=[$32], occupation=[$33], houseowner=[$34], num_cars_owned=[$35], fullname=[$36], department_id=[$37], department_description=[$38])\n"
             + "  EnumerableProject($f0=[$31], $f1=[$32], $f2=[$33], $f3=[$34], $f4=[$35], $f5=[$36], $f6=[$37], $f7=[$38], $f8=[$2], $f9=[$3], $f10=[$4], $f11=[$5], $f12=[$6], $f13=[$7], $f14=[$8], $f15=[$9], $f16=[$10], $f17=[$11], $f18=[$12], $f19=[$13], $f20=[$14], $f21=[$15], $f22=[$16], $f23=[$17], $f24=[$18], $f25=[$19], $f26=[$20], $f27=[$21], $f28=[$22], $f29=[$23], $f30=[$24], $f31=[$25], $f32=[$26], $f33=[$27], $f34=[$28], $f35=[$29], $f36=[$30], $f37=[$0], $f38=[$1])\n"
+            //+ "    EnumerableMergeJoin(condition=[true], joinType=[inner])\n"
             + "    EnumerableJoin(condition=[true], joinType=[inner])\n"
             + "      EnumerableTableScan(table=[[foodmart2, department]])\n"
             + "      EnumerableJoin(condition=[=($0, $31)], joinType=[inner])\n"
@@ -737,6 +742,7 @@ public class PlannerTest {
             + "join \"employee\" using (\"department_id\")",
         "EnumerableProject(product_id=[$0], time_id=[$1], customer_id=[$2], promotion_id=[$3], store_id=[$4], store_sales=[$5], store_cost=[$6], unit_sales=[$7], customer_id0=[$8], account_num=[$9], lname=[$10], fname=[$11], mi=[$12], address1=[$13], address2=[$14], address3=[$15], address4=[$16], city=[$17], state_province=[$18], postal_code=[$19], country=[$20], customer_region_id=[$21], phone1=[$22], phone2=[$23], birthdate=[$24], marital_status=[$25], yearly_income=[$26], gender=[$27], total_children=[$28], num_children_at_home=[$29], education=[$30], date_accnt_opened=[$31], member_card=[$32], occupation=[$33], houseowner=[$34], num_cars_owned=[$35], fullname=[$36], department_id=[$37], department_description=[$38], employee_id=[$39], full_name=[$40], first_name=[$41], last_name=[$42], position_id=[$43], position_title=[$44], store_id0=[$45], department_id0=[$46], birth_date=[$47], hire_date=[$48], end_date=[$49], salary=[$50], supervisor_id=[$51], education_level=[$52], marita
 l_status0=[$53], gender0=[$54], management_role=[$55])\n"
             + "  EnumerableProject($f0=[$48], $f1=[$49], $f2=[$50], $f3=[$51], $f4=[$52], $f5=[$53], $f6=[$54], $f7=[$55], $f8=[$19], $f9=[$20], $f10=[$21], $f11=[$22], $f12=[$23], $f13=[$24], $f14=[$25], $f15=[$26], $f16=[$27], $f17=[$28], $f18=[$29], $f19=[$30], $f20=[$31], $f21=[$32], $f22=[$33], $f23=[$34], $f24=[$35], $f25=[$36], $f26=[$37], $f27=[$38], $f28=[$39], $f29=[$40], $f30=[$41], $f31=[$42], $f32=[$43], $f33=[$44], $f34=[$45], $f35=[$46], $f36=[$47], $f37=[$0], $f38=[$1], $f39=[$2], $f40=[$3], $f41=[$4], $f42=[$5], $f43=[$6], $f44=[$7], $f45=[$8], $f46=[$9], $f47=[$10], $f48=[$11], $f49=[$12], $f50=[$13], $f51=[$14], $f52=[$15], $f53=[$16], $f54=[$17], $f55=[$18])\n"
+            // + "    EnumerableMergeJoin(condition=[true], joinType=[inner])\n"
             + "    EnumerableJoin(condition=[true], joinType=[inner])\n"
             + "      EnumerableJoin(condition=[=($0, $9)], joinType=[inner])\n"
             + "        EnumerableTableScan(table=[[foodmart2, department]])\n"

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/java/org/apache/calcite/util/ImmutableBitSetTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/util/ImmutableBitSetTest.java b/core/src/test/java/org/apache/calcite/util/ImmutableBitSetTest.java
index 7f2b984..605817c 100644
--- a/core/src/test/java/org/apache/calcite/util/ImmutableBitSetTest.java
+++ b/core/src/test/java/org/apache/calcite/util/ImmutableBitSetTest.java
@@ -425,6 +425,22 @@ public class ImmutableBitSetTest {
     assertThat(ImmutableBitSet.valueOf(LongBuffer.wrap(bitSet.toLongArray())),
         equalTo(bitSet));
   }
+
+  @Test public void testSet() {
+    final ImmutableBitSet bitSet = ImmutableBitSet.of(29, 4, 1969);
+    assertThat(bitSet.set(30),
+        equalTo(ImmutableBitSet.of(29, 4, 1969, 30)));
+    assertThat(bitSet.set(29),
+        equalTo(bitSet));
+  }
+
+  @Test public void testClear() {
+    final ImmutableBitSet bitSet = ImmutableBitSet.of(29, 4, 1969);
+    assertThat(bitSet.clear(29),
+        equalTo(ImmutableBitSet.of(4, 1969)));
+    assertThat(bitSet.clear(29).clear(4).clear(29).clear(1969),
+        equalTo(ImmutableBitSet.of()));
+  }
 }
 
 // End ImmutableBitSetTest.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/core/src/test/resources/sql/sort.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/sort.oq b/core/src/test/resources/sql/sort.oq
new file mode 100644
index 0000000..df6e385
--- /dev/null
+++ b/core/src/test/resources/sql/sort.oq
@@ -0,0 +1,71 @@
+# sort.oq - Sorting and collation
+#
+# 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.
+#
+!use foodmart
+!set outputformat mysql
+
+# The ArrayTable "days" is sorted by "day", so plan must not contain sort
+select * from "days" order by "day";
++-----+-----------+
+| day | week_day  |
++-----+-----------+
+|   1 | Sunday    |
+|   2 | Monday    |
+|   3 | Tuesday   |
+|   4 | Wednesday |
+|   5 | Thursday  |
+|   6 | Friday    |
+|   7 | Saturday  |
++-----+-----------+
+(7 rows)
+
+!ok
+EnumerableTableScan(table=[[foodmart2, days]])
+!plan
+
+# The ArrayTable "days" is sorted by "day", so the plan does not sort, only applies limit
+select * from "days" order by "day" limit 2;
++-----+----------+
+| day | week_day |
++-----+----------+
+|   1 | Sunday   |
+|   2 | Monday   |
++-----+----------+
+(2 rows)
+
+!ok
+EnumerableLimit(fetch=[2])
+  EnumerableTableScan(table=[[foodmart2, days]])
+!plan
+
+# The ArrayTable "days" is sorted by "day", so the plan must not contain Sort
+select * from "days" where "day" between 2 and 4 order by "day";
++-----+-----------+
+| day | week_day  |
++-----+-----------+
+|   2 | Monday    |
+|   3 | Tuesday   |
+|   4 | Wednesday |
++-----+-----------+
+(3 rows)
+
+!ok
+EnumerableCalc(expr#0..1=[{inputs}], expr#2=[2], expr#3=[>=($t0, $t2)], expr#4=[4], expr#5=[<=($t0, $t4)], expr#6=[AND($t3, $t5)], proj#0..1=[{exprs}], $condition=[$t6])
+  EnumerableTableScan(table=[[foodmart2, days]])
+!plan
+
+# End sort.oq

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java
----------------------------------------------------------------------
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java b/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java
index 18aa812..c7eedf5 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/Ord.java
@@ -17,10 +17,10 @@
 package org.apache.calcite.linq4j;
 
 import java.util.AbstractList;
-import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.RandomAccess;
 
 /**
  * Pair of an element and an ordinal.
@@ -49,7 +49,7 @@ public class Ord<E> implements Map.Entry<Integer, E> {
   /**
    * Creates an iterable of {@code Ord}s over an iterable.
    */
-  public static <E> Iterable<Ord<E>> zip(final Iterable<E> iterable) {
+  public static <E> Iterable<Ord<E>> zip(final Iterable<? extends E> iterable) {
     return new Iterable<Ord<E>>() {
       public Iterator<Ord<E>> iterator() {
         return zip(iterable.iterator());
@@ -60,7 +60,7 @@ public class Ord<E> implements Map.Entry<Integer, E> {
   /**
    * Creates an iterator of {@code Ord}s over an iterator.
    */
-  public static <E> Iterator<Ord<E>> zip(final Iterator<E> iterator) {
+  public static <E> Iterator<Ord<E>> zip(final Iterator<? extends E> iterator) {
     return new Iterator<Ord<E>>() {
       int n = 0;
 
@@ -82,22 +82,16 @@ public class Ord<E> implements Map.Entry<Integer, E> {
    * Returns a numbered list based on an array.
    */
   public static <E> List<Ord<E>> zip(final E[] elements) {
-    return zip(Arrays.asList(elements));
+    return new OrdArrayList<E>(elements);
   }
 
   /**
    * Returns a numbered list.
    */
-  public static <E> List<Ord<E>> zip(final List<E> elements) {
-    return new AbstractList<Ord<E>>() {
-      public Ord<E> get(int index) {
-        return of(index, elements.get(index));
-      }
-
-      public int size() {
-        return elements.size();
-      }
-    };
+  public static <E> List<Ord<E>> zip(final List<? extends E> elements) {
+    return elements instanceof RandomAccess
+        ? new OrdRandomAccessList<E>(elements)
+        : new OrdList<E>(elements);
   }
 
   public Integer getKey() {
@@ -111,6 +105,49 @@ public class Ord<E> implements Map.Entry<Integer, E> {
   public E setValue(E value) {
     throw new UnsupportedOperationException();
   }
+
+  /** List of {@link Ord} backed by a list of elements. */
+  private static class OrdList<E> extends AbstractList<Ord<E>> {
+    private final List<? extends E> elements;
+
+    public OrdList(List<? extends E> elements) {
+      this.elements = elements;
+    }
+
+    public Ord<E> get(int index) {
+      return of(index, elements.get(index));
+    }
+
+    public int size() {
+      return elements.size();
+    }
+  }
+
+  /** List of {@link Ord} backed by a random-access list of elements. */
+  private static class OrdRandomAccessList<E> extends OrdList<E>
+      implements RandomAccess {
+    public OrdRandomAccessList(List<? extends E> elements) {
+      super(elements);
+    }
+  }
+
+  /** List of {@link Ord} backed by an array of elements. */
+  private static class OrdArrayList<E> extends AbstractList<Ord<E>>
+      implements RandomAccess {
+    private final E[] elements;
+
+    public OrdArrayList(E[] elements) {
+      this.elements = elements;
+    }
+
+    @Override public Ord<E> get(int index) {
+      return Ord.of(index, elements[index]);
+    }
+
+    @Override public int size() {
+      return elements.length;
+    }
+  }
 }
 
 // End Ord.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java
----------------------------------------------------------------------
diff --git a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java
index f67e0ee..0ccee35 100644
--- a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java
+++ b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java
@@ -24,7 +24,7 @@ import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelTrait;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.InvalidRelException;
-import org.apache.calcite.rel.RelCollationImpl;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.convert.ConverterRule;
 import org.apache.calcite.rel.core.Sort;
@@ -216,7 +216,7 @@ public class MongoRules {
           sort.getTraitSet().replace(out)
               .replace(sort.getCollation());
       return new MongoSort(rel.getCluster(), traitSet,
-          convert(sort.getInput(), traitSet.replace(RelCollationImpl.EMPTY)),
+          convert(sort.getInput(), traitSet.replace(RelCollations.EMPTY)),
           sort.getCollation(), sort.offset, sort.fetch);
     }
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2709896e/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
----------------------------------------------------------------------
diff --git a/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java b/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
index 2976f87..d432466 100644
--- a/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
+++ b/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
@@ -20,7 +20,6 @@ import org.apache.calcite.adapter.splunk.util.StringUtils;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptRuleOperand;
-import org.apache.calcite.rel.RelCollationImpl;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.logical.LogicalProject;
@@ -279,13 +278,7 @@ public class SplunkPushDownRule
     if (proj == null) {
       return rel;
     }
-    return new LogicalProject(
-        proj.getCluster(),
-        proj.getCluster().traitSetOf(
-            proj.getCollationList().isEmpty()
-                ? RelCollationImpl.EMPTY
-                : proj.getCollationList().get(0)),
-        rel, proj.getProjects(), proj.getRowType());
+    return LogicalProject.create(rel, proj.getProjects(), proj.getRowType());
   }
 
   // TODO: use StringBuilder instead of String