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 2018/08/31 06:04:25 UTC

[3/3] calcite git commit: [CALCITE-2470] In RelBuilder, project method should combine expressions if the underlying node is a Project

[CALCITE-2470] In RelBuilder, project method should combine expressions if the underlying node is a Project

Add RelBuilder.shouldMergeProject() to allow sub-classes to disable
merging.

Improve the message given by CompositeMatcher when match fails.

When RelStructuredTypeFlattener rewrites a RexInputRef be sure to use
the field's new type. (It might have strengthened from say INTEGER to
INTEGER NOT NULL.)

Add a test case inspired by Drill (it passes in Calcite, but I gather
it fails in Drill).


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

Branch: refs/heads/master
Commit: 370e95ab8557946023ce209e975e1c321765559e
Parents: d0e3089
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Aug 16 01:26:13 2018 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Aug 30 23:03:22 2018 -0700

----------------------------------------------------------------------
 .../calcite/rel/logical/LogicalFilter.java      |   2 +
 .../java/org/apache/calcite/runtime/Hook.java   |   4 +-
 .../sql2rel/RelStructuredTypeFlattener.java     |  73 +-
 .../org/apache/calcite/tools/RelBuilder.java    | 166 +++--
 .../calcite/test/JdbcFrontLinqBackTest.java     |   4 +-
 .../java/org/apache/calcite/test/Matchers.java  |   5 +
 .../calcite/test/MaterializationTest.java       |   2 +-
 .../org/apache/calcite/test/RelBuilderTest.java |  82 ++-
 .../calcite/test/SqlToRelConverterTest.java     |   4 +-
 .../org/apache/calcite/test/StreamTest.java     |  11 +-
 .../org/apache/calcite/tools/PlannerTest.java   |   7 +-
 .../org/apache/calcite/test/HepPlannerTest.xml  |   5 +-
 .../org/apache/calcite/test/RelOptRulesTest.xml | 576 +++++++---------
 .../calcite/test/SqlToRelConverterTest.xml      | 664 ++++++++-----------
 core/src/test/resources/sql/agg.iq              |   6 +-
 core/src/test/resources/sql/misc.iq             |  19 +-
 core/src/test/resources/sql/sub-query.iq        |  44 +-
 core/src/test/resources/sql/winagg.iq           |   4 +-
 .../adapter/mongodb/MongoAdapterTest.java       |  10 +-
 .../org/apache/calcite/test/ServerTest.java     |   2 +-
 server/src/test/resources/sql/table.iq          |   2 +-
 site/_docs/algebra.md                           |   1 +
 22 files changed, 838 insertions(+), 855 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java b/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
index 3b5febf..fd98d66 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
@@ -31,6 +31,7 @@ import org.apache.calcite.rel.metadata.RelMdCollation;
 import org.apache.calcite.rel.metadata.RelMdDistribution;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.Litmus;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -66,6 +67,7 @@ public final class LogicalFilter extends Filter {
       ImmutableSet<CorrelationId> variablesSet) {
     super(cluster, traitSet, child, condition);
     this.variablesSet = Objects.requireNonNull(variablesSet);
+    assert isValid(Litmus.THROW, null);
   }
 
   @Deprecated // to be removed before 2.0

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/core/src/main/java/org/apache/calcite/runtime/Hook.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/Hook.java b/core/src/main/java/org/apache/calcite/runtime/Hook.java
index 10d4586..a02bb0f 100644
--- a/core/src/main/java/org/apache/calcite/runtime/Hook.java
+++ b/core/src/main/java/org/apache/calcite/runtime/Hook.java
@@ -108,13 +108,13 @@ public enum Hook {
    *     }</pre>
    * </blockquote>
    */
-  public <T, R> Closeable add(final Consumer<T> handler) {
+  public <T> Closeable add(final Consumer<T> handler) {
     //noinspection unchecked
     handlers.add((Consumer<Object>) handler);
     return () -> remove(handler);
   }
 
-  /** @deprecated Use {@link #addThread(Consumer)}. */
+  /** @deprecated Use {@link #add(Consumer)}. */
   @SuppressWarnings("Guava")
   @Deprecated // to be removed in 2.0
   public <T, R> Closeable add(final Function<T, R> handler) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/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 ff41b16..98a0aa8 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.sql2rel;
 
+import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.RelCollation;
@@ -285,34 +286,50 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
 
   /**
    * Maps the ordinal of a field pre-flattening to the ordinal of the
-   * corresponding field post-flattening, and optionally returns its type.
+   * corresponding field post-flattening.
    *
    * @param oldOrdinal Pre-flattening ordinal
    * @return Post-flattening ordinal
    */
   protected int getNewForOldInput(int oldOrdinal) {
+    return getNewFieldForOldInput(oldOrdinal).i;
+  }
+
+  /**
+   * Maps the ordinal of a field pre-flattening to the ordinal of the
+   * corresponding field post-flattening, and also returns its type.
+   *
+   * @param oldOrdinal Pre-flattening ordinal
+   * @return Post-flattening ordinal and type
+   */
+  protected Ord<RelDataType> getNewFieldForOldInput(int oldOrdinal) {
     assert currentRel != null;
     int newOrdinal = 0;
 
     // determine which input rel oldOrdinal references, and adjust
     // oldOrdinal to be relative to that input rel
     RelNode oldInput = null;
+    RelNode newInput = null;
     for (RelNode oldInput1 : currentRel.getInputs()) {
+      newInput = getNewForOldRel(oldInput1);
       RelDataType oldInputType = oldInput1.getRowType();
       int n = oldInputType.getFieldCount();
       if (oldOrdinal < n) {
         oldInput = oldInput1;
         break;
       }
-      RelNode newInput = getNewForOldRel(oldInput1);
       newOrdinal += newInput.getRowType().getFieldCount();
       oldOrdinal -= n;
     }
     assert oldInput != null;
+    assert newInput != null;
 
     RelDataType oldInputType = oldInput.getRowType();
-    newOrdinal += calculateFlattenedOffset(oldInputType, oldOrdinal);
-    return newOrdinal;
+    final int newOffset = calculateFlattenedOffset(oldInputType, oldOrdinal);
+    newOrdinal += newOffset;
+    final RelDataTypeField field =
+        newInput.getRowType().getFieldList().get(newOffset);
+    return Ord.of(newOrdinal, field.getType());
   }
 
   /**
@@ -523,10 +540,9 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
     // Translate the condition.
     final RexLocalRef conditionRef = program.getCondition();
     if (conditionRef != null) {
-      programBuilder.addCondition(
-          new RexLocalRef(
-              getNewForOldInput(conditionRef.getIndex()),
-              conditionRef.getType()));
+      final Ord<RelDataType> newField =
+          getNewFieldForOldInput(conditionRef.getIndex());
+      programBuilder.addCondition(new RexLocalRef(newField.i, newField.e));
     }
 
     RexProgram newProgram = programBuilder.getProgram();
@@ -576,7 +592,6 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
     if (exp.getType().isStruct()) {
       if (exp instanceof RexInputRef) {
         RexInputRef inputRef = (RexInputRef) exp;
-        int newOffset = getNewForOldInput(inputRef.getIndex());
 
         // expand to range
         RelDataType flattenedType =
@@ -587,10 +602,10 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
         List<RelDataTypeField> fieldList = flattenedType.getFieldList();
         int n = fieldList.size();
         for (int j = 0; j < n; ++j) {
-          RelDataTypeField field = fieldList.get(j);
+          final Ord<RelDataType> newField =
+              getNewFieldForOldInput(inputRef.getIndex());
           flattenedExps.add(
-              Pair.of(
-                  new RexInputRef(newOffset + j, field.getType()),
+              Pair.of(new RexInputRef(newField.i + j, newField.e),
                   fieldName));
         }
       } else if (isConstructor(exp) || exp.isA(SqlKind.CAST)) {
@@ -627,11 +642,13 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
         RexNode newExp = exp;
         List<RexNode> oldOperands = ((RexCall) exp).getOperands();
         if (oldOperands.get(0) instanceof RexInputRef) {
-          RexInputRef inputRef = (RexInputRef) oldOperands.get(0);
-          int newOffset = getNewForOldInput(inputRef.getIndex());
-          newExp = rexBuilder.makeCall(exp.getType(), ((RexCall) exp).getOperator(),
-              ImmutableList.of(
-                  rexBuilder.makeInputRef(inputRef.getType(), newOffset), oldOperands.get(1)));
+          final RexInputRef inputRef = (RexInputRef) oldOperands.get(0);
+          final Ord<RelDataType> newField =
+              getNewFieldForOldInput(inputRef.getIndex());
+          newExp = rexBuilder.makeCall(exp.getType(),
+              ((RexCall) exp).getOperator(),
+              ImmutableList.of(rexBuilder.makeInputRef(newField.e, newField.i),
+                  oldOperands.get(1)));
         }
         for (RelDataTypeField field : newExp.getType().getFieldList()) {
           flattenedExps.add(
@@ -759,15 +776,12 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
   private class RewriteRexShuttle extends RexShuttle {
     @Override public RexNode visitInputRef(RexInputRef input) {
       final int oldIndex = input.getIndex();
-      final int newIndex = getNewForOldInput(oldIndex);
-
-      // FIXME: jhyde, 2005/12/3: Once indicator fields have been
-      //  introduced, the new field type may be very different to the
-      //  old field type. We should look at the actual flattened types,
-      //  rather than trying to deduce the type from the current type.
-      RelDataType fieldType = removeDistinct(input.getType());
-      RexInputRef newInput = new RexInputRef(newIndex, fieldType);
-      return newInput;
+      final Ord<RelDataType> field = getNewFieldForOldInput(oldIndex);
+
+      // Use the actual flattened type, which may be different from the current
+      // type.
+      RelDataType fieldType = removeDistinct(field.e);
+      return new RexInputRef(field.i, fieldType);
     }
 
     private RelDataType removeDistinct(RelDataType type) {
@@ -781,7 +795,6 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
       // walk down the field access path expression, calculating
       // the desired input number
       int iInput = 0;
-      RelDataType fieldType = removeDistinct(fieldAccess.getType());
 
       for (;;) {
         RexNode refExp = fieldAccess.getReferenceExpr();
@@ -792,8 +805,10 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
                 ordinal);
         if (refExp instanceof RexInputRef) {
           RexInputRef inputRef = (RexInputRef) refExp;
-          iInput += getNewForOldInput(inputRef.getIndex());
-          return new RexInputRef(iInput, fieldType);
+          final Ord<RelDataType> newField =
+              getNewFieldForOldInput(inputRef.getIndex());
+          iInput += newField.i;
+          return new RexInputRef(iInput, removeDistinct(newField.e));
         } else if (refExp instanceof RexCorrelVariable) {
           RelDataType refType =
               SqlTypeUtil.flattenRecordType(

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 491be00..64ae6cb 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.tools;
 
 import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.linq4j.function.Experimental;
 import org.apache.calcite.plan.Context;
 import org.apache.calcite.plan.Contexts;
 import org.apache.calcite.plan.RelOptCluster;
@@ -95,7 +96,6 @@ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Deque;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -941,6 +941,12 @@ public class RelBuilder {
     return this;
   }
 
+  /** Creates a {@link Project} of the given
+   * expressions. */
+  public RelBuilder project(RexNode... nodes) {
+    return project(ImmutableList.copyOf(nodes));
+  }
+
   /** Creates a {@link Project} of the given list
    * of expressions.
    *
@@ -964,6 +970,19 @@ public class RelBuilder {
     return project(nodes, fieldNames, false);
   }
 
+  /** Creates a {@link Project} of all original fields, plus the given
+   * expressions. */
+  public RelBuilder projectPlus(RexNode... nodes) {
+    return projectPlus(ImmutableList.copyOf(nodes));
+  }
+
+  /** Creates a {@link Project} of all original fields, plus the given list of
+   * expressions. */
+  public RelBuilder projectPlus(Iterable<RexNode> nodes) {
+    final ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
+    return project(builder.addAll(fields()).addAll(nodes).build());
+  }
+
   /** Creates a {@link Project} of the given list
    * of expressions, using the given names.
    *
@@ -991,27 +1010,86 @@ public class RelBuilder {
       Iterable<? extends RexNode> nodes,
       Iterable<String> fieldNames,
       boolean force) {
-    final List<String> names = new ArrayList<>();
-    final List<RexNode> exprList = new ArrayList<>();
-    final Iterator<String> nameIterator = fieldNames.iterator();
-    for (RexNode node : nodes) {
-      if (simplify) {
-        node = simplifier.simplifyPreservingType(node);
+    final Frame frame = stack.peek();
+    final RelDataType inputRowType = frame.rel.getRowType();
+    final List<RexNode> nodeList = Lists.newArrayList(nodes);
+
+    // Perform a quick check for identity. We'll do a deeper check
+    // later when we've derived column names.
+    if (!force && Iterables.isEmpty(fieldNames)
+        && RexUtil.isIdentity(nodeList, inputRowType)) {
+      return this;
+    }
+
+    final List<String> fieldNameList = Lists.newArrayList(fieldNames);
+    while (fieldNameList.size() < nodeList.size()) {
+      fieldNameList.add(null);
+    }
+
+    if (frame.rel instanceof Project
+        && shouldMergeProject()) {
+      final Project project = (Project) frame.rel;
+      // Populate field names. If the upper expression is an input ref and does
+      // not have a recommended name, use the name of the underlying field.
+      for (int i = 0; i < fieldNameList.size(); i++) {
+        if (fieldNameList.get(i) == null) {
+          final RexNode node = nodeList.get(i);
+          if (node instanceof RexInputRef) {
+            final RexInputRef ref = (RexInputRef) node;
+            fieldNameList.set(i,
+                project.getRowType().getFieldNames().get(ref.getIndex()));
+          }
+        }
+      }
+      final List<RexNode> newNodes =
+          RelOptUtil.pushPastProject(nodeList, project);
+
+      // Carefully build a list of fields, so that table aliases from the input
+      // can be seen for fields that are based on a RexInputRef.
+      final Frame frame1 = stack.pop();
+      final List<Field> fields = new ArrayList<>();
+      for (RelDataTypeField f
+          : project.getInput().getRowType().getFieldList()) {
+        fields.add(new Field(ImmutableSet.of(), f));
+      }
+      for (Pair<RexNode, Field> pair
+          : Pair.zip(project.getProjects(), frame1.fields)) {
+        switch (pair.left.getKind()) {
+        case INPUT_REF:
+          final int i = ((RexInputRef) pair.left).getIndex();
+          final Field field = fields.get(i);
+          final ImmutableSet<String> aliases = pair.right.left;
+          fields.set(i, new Field(aliases, field.right));
+          break;
+        }
       }
-      exprList.add(node);
-      String name = nameIterator.hasNext() ? nameIterator.next() : null;
-      names.add(name != null ? name : inferAlias(exprList, node));
+      stack.push(new Frame(project.getInput(), ImmutableList.copyOf(fields)));
+      return project(newNodes, fieldNameList, force);
     }
-    final Frame frame = stack.peek();
+
+    // Simplify expressions.
+    if (simplify) {
+      for (int i = 0; i < nodeList.size(); i++) {
+        nodeList.set(i, simplifier.simplifyPreservingType(nodeList.get(i)));
+      }
+    }
+
+    // Replace null names with generated aliases.
+    for (int i = 0; i < fieldNameList.size(); i++) {
+      if (fieldNameList.get(i) == null) {
+        fieldNameList.set(i, inferAlias(nodeList, nodeList.get(i), i));
+      }
+    }
+
     final ImmutableList.Builder<Field> fields = ImmutableList.builder();
     final Set<String> uniqueNameList =
         getTypeFactory().getTypeSystem().isSchemaCaseSensitive()
-        ? new HashSet<String>()
+        ? new HashSet<>()
         : new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
     // calculate final names and build field list
-    for (int i = 0; i < names.size(); ++i) {
-      RexNode node = exprList.get(i);
-      String name = names.get(i);
+    for (int i = 0; i < fieldNameList.size(); ++i) {
+      final RexNode node = nodeList.get(i);
+      String name = fieldNameList.get(i);
       Field field;
       if (name == null || uniqueNameList.contains(name)) {
         int j = 0;
@@ -1021,7 +1099,7 @@ public class RelBuilder {
         do {
           name = SqlValidatorUtil.F_SUGGESTER.apply(name, j, j++);
         } while (uniqueNameList.contains(name));
-        names.set(i, name);
+        fieldNameList.set(i, name);
       }
       RelDataTypeField fieldType =
           new RelDataTypeFieldImpl(name, i, node.getType());
@@ -1038,9 +1116,8 @@ public class RelBuilder {
       uniqueNameList.add(name);
       fields.add(field);
     }
-    final RelDataType inputRowType = peek().getRowType();
-    if (!force && RexUtil.isIdentity(exprList, inputRowType)) {
-      if (names.equals(inputRowType.getFieldNames())) {
+    if (!force && RexUtil.isIdentity(nodeList, inputRowType)) {
+      if (fieldNameList.equals(inputRowType.getFieldNames())) {
         // Do not create an identity project if it does not rename any fields
         return this;
       } else {
@@ -1051,17 +1128,20 @@ public class RelBuilder {
       }
     }
     final RelNode project =
-        projectFactory.createProject(frame.rel, ImmutableList.copyOf(exprList),
-            names);
+        projectFactory.createProject(frame.rel, ImmutableList.copyOf(nodeList),
+            fieldNameList);
     stack.pop();
     stack.push(new Frame(project, fields.build()));
     return this;
   }
 
-  /** Creates a {@link Project} of the given
-   * expressions. */
-  public RelBuilder project(RexNode... nodes) {
-    return project(ImmutableList.copyOf(nodes));
+  /** Whether to attempt to merge consecutive {@link Project} operators.
+   *
+   * <p>The default implementation returns {@code true};
+   * sub-classes may disable merge by overriding to return {@code false}. */
+  @Experimental
+  protected boolean shouldMergeProject() {
+    return true;
   }
 
   /** Creates a {@link Project} of the given
@@ -1146,18 +1226,7 @@ public class RelBuilder {
       return values(v.tuples, b.build());
     }
 
-    project(fields(), newFieldNames, true);
-
-    // If, after de-duplication, the field names are unchanged, discard the
-    // identity project we just created.
-    if (peek().getRowType().getFieldNames().equals(oldFieldNames)) {
-      final RelNode r = peek();
-      if (r instanceof Project) {
-        stack.pop();
-        push(((Project) r).getInput());
-      }
-    }
-    return this;
+    return project(fields(), newFieldNames, true);
   }
 
   /** Infers the alias of an expression.
@@ -1165,20 +1234,16 @@ public class RelBuilder {
    * <p>If the expression was created by {@link #alias}, replaces the expression
    * in the project list.
    */
-  private String inferAlias(List<RexNode> exprList, RexNode expr) {
+  private String inferAlias(List<RexNode> exprList, RexNode expr, int i) {
     switch (expr.getKind()) {
     case INPUT_REF:
       final RexInputRef ref = (RexInputRef) expr;
       return stack.peek().fields.get(ref.getIndex()).getValue().getName();
     case CAST:
-      return inferAlias(exprList, ((RexCall) expr).getOperands().get(0));
+      return inferAlias(exprList, ((RexCall) expr).getOperands().get(0), -1);
     case AS:
       final RexCall call = (RexCall) expr;
-      for (;;) {
-        final int i = exprList.indexOf(expr);
-        if (i < 0) {
-          break;
-        }
+      if (i >= 0) {
         exprList.set(i, call.getOperands().get(0));
       }
       return ((NlsString) ((RexLiteral) call.getOperands().get(1)).getValue())
@@ -1526,9 +1591,7 @@ public class RelBuilder {
   public RelBuilder as(final String alias) {
     final Frame pair = stack.pop();
     List<Field> newFields =
-        Lists.transform(pair.fields, field ->
-            new Field(ImmutableSet.<String>builder().addAll(field.left)
-                .add(alias).build(), field.right));
+        Util.transform(pair.fields, field -> field.addAlias(alias));
     stack.push(new Frame(pair.rel, ImmutableList.copyOf(newFields)));
     return this;
   }
@@ -2090,6 +2153,15 @@ public class RelBuilder {
     Field(ImmutableSet<String> left, RelDataTypeField right) {
       super(left, right);
     }
+
+    Field addAlias(String alias) {
+      if (left.contains(alias)) {
+        return this;
+      }
+      final ImmutableSet<String> aliasList =
+          ImmutableSet.<String>builder().addAll(left).add(alias).build();
+      return new Field(aliasList, right);
+    }
   }
 
   /** Shuttle that shifts a predicate's inputs to the left, replacing early

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/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 a8c86f8..2c5d603 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
@@ -104,8 +104,8 @@ public class JdbcFrontLinqBackTest {
             + "from \"hr\".\"emps\" as e\n"
             + "order by \"deptno\", \"name\" desc")
         .explainContains(""
-            + "EnumerableCalc(expr#0..4=[{inputs}], expr#5=[UPPER($t2)], UN=[$t5], deptno=[$t1], name=[$t2])\n"
-            + "  EnumerableSort(sort0=[$1], sort1=[$2], dir0=[ASC], dir1=[DESC])\n"
+            + "EnumerableSort(sort0=[$1], sort1=[$2], dir0=[ASC], dir1=[DESC])\n"
+            + "  EnumerableCalc(expr#0..4=[{inputs}], expr#5=[UPPER($t2)], UN=[$t5], deptno=[$t1], name=[$t2])\n"
             + "    EnumerableTableScan(table=[[hr, emps]])")
         .returns("UN=THEODORE; deptno=10\n"
             + "UN=SEBASTIAN; deptno=10\n"

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/core/src/test/java/org/apache/calcite/test/Matchers.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/Matchers.java b/core/src/test/java/org/apache/calcite/test/Matchers.java
index 0b5e6bd..98bb820 100644
--- a/core/src/test/java/org/apache/calcite/test/Matchers.java
+++ b/core/src/test/java/org/apache/calcite/test/Matchers.java
@@ -255,6 +255,11 @@ public class Matchers {
     public void describeTo(Description description) {
       matcher.describeTo(description);
     }
+
+    @Override protected void describeMismatchSafely(F item,
+        Description mismatchDescription) {
+      mismatchDescription.appendText("was ").appendValue(f.apply(item));
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java b/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
index b32ec74..b8a929d 100644
--- a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
+++ b/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
@@ -1743,7 +1743,7 @@ public class MaterializationTest {
             + "join \"dependents\" on (\"depts\".\"name\" = \"dependents\".\"name\")",
         HR_FKUK_MODEL,
         CalciteAssert.checkResultContains(
-            "EnumerableCalc(expr#0..2=[{inputs}], empid0=[$t1])\n"
+            "EnumerableCalc(expr#0..2=[{inputs}], empid=[$t1])\n"
                 + "  EnumerableJoin(condition=[=($0, $2)], joinType=[inner])\n"
                 + "    EnumerableCalc(expr#0=[{inputs}], expr#1=[CAST($t0):VARCHAR CHARACTER SET \"ISO-8859-1\" "
                 + "COLLATE \"ISO-8859-1$en_US$primary\"], name=[$t1])\n"

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index b6ae845..5c18173 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -48,6 +48,7 @@ import org.apache.calcite.tools.RelRunner;
 import org.apache.calcite.tools.RelRunners;
 import org.apache.calcite.util.Holder;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.Mappings;
 
 import com.google.common.collect.ImmutableList;
@@ -808,9 +809,8 @@ public class RelBuilderTest {
             .build();
     final String expected = ""
         + "LogicalAggregate(group=[{0}])\n"
-        + "  LogicalProject(departmentNo=[$0])\n"
-        + "    LogicalProject(DEPTNO=[$7])\n"
-        + "      LogicalTableScan(table=[[scott, EMP]])\n";
+        + "  LogicalProject(departmentNo=[$7])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
     assertThat(root, hasTree(expected));
   }
 
@@ -828,10 +828,8 @@ public class RelBuilderTest {
             .build();
     final String expected = ""
         + "LogicalAggregate(group=[{1}])\n"
-        + "  LogicalProject(DEPTNO=[$0], d3=[$1])\n"
-        + "    LogicalProject(DEPTNO=[$0], $f1=[+($0, 3)])\n"
-        + "      LogicalProject(DEPTNO=[$7])\n"
-        + "        LogicalTableScan(table=[[scott, EMP]])\n";
+        + "  LogicalProject(DEPTNO=[$7], d3=[+($7, 3)])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
     assertThat(root, hasTree(expected));
   }
 
@@ -1397,8 +1395,53 @@ public class RelBuilderTest {
             .project(builder.field("EMP_alias", "DEPTNO"))
             .build();
     final String expected = ""
-        + "LogicalProject(DEPTNO=[$0])\n"
-        + "  LogicalProject(DEPTNO=[$7], $f1=[20])\n"
+        + "LogicalProject(DEPTNO=[$7])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
+  }
+
+  /** Tests that table aliases are propagated even when there is a project on
+   * top of a project. (Aliases tend to get lost when projects are merged). */
+  @Test public void testAliasProjectProject() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .as("EMP_alias")
+            .project(builder.field("DEPTNO"),
+                builder.literal(20))
+            .project(builder.field(1),
+                builder.literal(10),
+                builder.field(0))
+            .project(builder.alias(builder.field(1), "sum"),
+                builder.field("EMP_alias", "DEPTNO"))
+            .build();
+    final String expected = ""
+        + "LogicalProject(sum=[10], DEPTNO=[$7])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
+  }
+
+  /** Tests that table aliases are propagated and are available to a filter,
+   * even when there is a project on top of a project. (Aliases tend to get lost
+   * when projects are merged). */
+  @Test public void testAliasFilter() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .as("EMP_alias")
+            .project(builder.field("DEPTNO"),
+                builder.literal(20))
+            .project(builder.field(1), // literal 20
+                builder.literal(10),
+                builder.field(0)) // DEPTNO
+            .filter(
+                builder.call(SqlStdOperatorTable.GREATER_THAN,
+                    builder.field(1),
+                    builder.field("EMP_alias", "DEPTNO")))
+            .build();
+    final String expected = ""
+        + "LogicalFilter(condition=[>($1, $2)])\n"
+        + "  LogicalProject($f1=[20], $f12=[10], DEPTNO=[$7])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n";
     assertThat(root, hasTree(expected));
   }
@@ -1449,6 +1492,27 @@ public class RelBuilderTest {
     assertThat(root, hasTree(expected));
   }
 
+  /** Tests that a projection after a projection. */
+  @Test public void testProjectProject() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .as("e")
+            .projectPlus(
+                builder.alias(
+                    builder.call(SqlStdOperatorTable.PLUS, builder.field(0),
+                        builder.field(3)), "x"))
+            .project(builder.field("e", "DEPTNO"),
+                builder.field(0),
+                builder.field("e", "MGR"),
+                Util.last(builder.fields()))
+            .build();
+    final String expected = ""
+        + "LogicalProject(DEPTNO=[$7], EMPNO=[$0], MGR=[$3], x=[+($0, $3)])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
+  }
+
   @Test public void testMultiLevelAlias() {
     final RelBuilder builder = RelBuilder.create(config().build());
     RelNode root =

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index aba2274..cafe24a 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -2507,7 +2507,7 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
   }
 
   @Test public void testStarDynamicSchemaUnnest() {
-    final String sql3 = "select * \n"
+    final String sql3 = "select *\n"
         + "from SALES.CUSTOMER as t1,\n"
         + "lateral (select t2.\"$unnest\" as fake_col3\n"
         + "         from unnest(t1.fake_col) as t2) as t3";
@@ -2515,7 +2515,7 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
   }
 
   @Test public void testStarDynamicSchemaUnnest2() {
-    final String sql3 = "select * \n"
+    final String sql3 = "select *\n"
         + "from SALES.CUSTOMER as t1,\n"
         + "unnest(t1.fake_col) as t2";
     sql(sql3).with(getTesterWithDynamicTable()).ok();

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/core/src/test/java/org/apache/calcite/test/StreamTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/StreamTest.java b/core/src/test/java/org/apache/calcite/test/StreamTest.java
index b25da6d..502fd61 100644
--- a/core/src/test/java/org/apache/calcite/test/StreamTest.java
+++ b/core/src/test/java/org/apache/calcite/test/StreamTest.java
@@ -277,12 +277,11 @@ public class StreamTest {
             + "orders.rowtime as rowtime, orders.id as orderId, products.supplier as supplierId "
             + "from orders join products on orders.product = products.id")
         .convertContains("LogicalDelta\n"
-            + "  LogicalProject(ROWTIME=[$0], ORDERID=[$1], SUPPLIERID=[$5])\n"
-            + "    LogicalProject(ROWTIME=[$0], ID=[$1], PRODUCT=[$2], UNITS=[$3], ID0=[$5], SUPPLIER=[$6])\n"
-            + "      LogicalJoin(condition=[=($4, $5)], joinType=[inner])\n"
-            + "        LogicalProject(ROWTIME=[$0], ID=[$1], PRODUCT=[$2], UNITS=[$3], PRODUCT0=[CAST($2):VARCHAR(32) CHARACTER SET \"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\" NOT NULL])\n"
-            + "          LogicalTableScan(table=[[STREAM_JOINS, ORDERS]])\n"
-            + "        LogicalTableScan(table=[[STREAM_JOINS, PRODUCTS]])\n")
+            + "  LogicalProject(ROWTIME=[$0], ORDERID=[$1], SUPPLIERID=[$6])\n"
+            + "    LogicalJoin(condition=[=($4, $5)], joinType=[inner])\n"
+            + "      LogicalProject(ROWTIME=[$0], ID=[$1], PRODUCT=[$2], UNITS=[$3], PRODUCT0=[CAST($2):VARCHAR(32) CHARACTER SET \"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\" NOT NULL])\n"
+            + "        LogicalTableScan(table=[[STREAM_JOINS, ORDERS]])\n"
+            + "      LogicalTableScan(table=[[STREAM_JOINS, PRODUCTS]])\n")
         .explainContains(""
             + "EnumerableCalc(expr#0..6=[{inputs}], proj#0..1=[{exprs}], SUPPLIERID=[$t6])\n"
             + "  EnumerableJoin(condition=[=($4, $5)], joinType=[inner])\n"

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/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 da54ee8..04c3746 100644
--- a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
@@ -1117,10 +1117,9 @@ public class PlannerTest {
     assertThat(plan,
         equalTo("LogicalSort(sort0=[$0], dir0=[ASC])\n"
         + "  LogicalProject(psPartkey=[$0])\n"
-        + "    LogicalProject(psPartkey=[$0])\n"
-        + "      LogicalSort(sort0=[$0], sort1=[$1], dir0=[ASC], dir1=[ASC])\n"
-        + "        LogicalProject(psPartkey=[$0], psSupplyCost=[$1])\n"
-        + "          EnumerableTableScan(table=[[tpch, partsupp]])\n"));
+        + "    LogicalSort(sort0=[$0], sort1=[$1], dir0=[ASC], dir1=[ASC])\n"
+        + "      LogicalProject(psPartkey=[$0], psSupplyCost=[$1])\n"
+        + "        EnumerableTableScan(table=[[tpch, partsupp]])\n"));
   }
 
   /** Test case for

http://git-wip-us.apache.org/repos/asf/calcite/blob/370e95ab/core/src/test/resources/org/apache/calcite/test/HepPlannerTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/calcite/test/HepPlannerTest.xml b/core/src/test/resources/org/apache/calcite/test/HepPlannerTest.xml
index 217eb36..1e56768 100644
--- a/core/src/test/resources/org/apache/calcite/test/HepPlannerTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/HepPlannerTest.xml
@@ -167,9 +167,8 @@ LogicalIntersect(all=[false])
     </Resource>
     <Resource name="planBefore">
       <![CDATA[
-LogicalProject(EXPR$0=[UPPER($0)])
-  LogicalProject(ENAME=[LOWER($1)])
-    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+LogicalProject(EXPR$0=[UPPER(LOWER($1))])
+  LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
     <Resource name="planAfter">