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 2016/12/30 00:08:46 UTC

calcite git commit: [CALCITE-1551] Preserve alias in RelBuilder.project (Jess Balint)

Repository: calcite
Updated Branches:
  refs/heads/master 0ef3660d6 -> 31d68f71b


[CALCITE-1551] Preserve alias in RelBuilder.project (Jess Balint)

This is accomplished by changing the structure of the Frame in
RelBuilder to include alias and field information for all fields in the
RelNode irrespective of their origin. Rel aliases also preserved on
group keys through aggregate operations.

In PigRelBuilder, we retain aggregate aliases, because Pig Latin
relies upon them.

Close apache/calcite#340


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

Branch: refs/heads/master
Commit: 31d68f71b01f016625a1def800c283820c600840
Parents: 0ef3660
Author: Jess Balint <jb...@gmail.com>
Authored: Wed Dec 21 19:49:23 2016 -0600
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Dec 29 14:33:33 2016 -0800

----------------------------------------------------------------------
 .../org/apache/calcite/tools/PigRelBuilder.java |  27 ++-
 .../org/apache/calcite/tools/RelBuilder.java    | 235 ++++++++++++-------
 .../apache/calcite/test/PigRelBuilderTest.java  |   3 +-
 .../org/apache/calcite/test/RelBuilderTest.java | 180 +++++++++++++-
 .../org/apache/calcite/test/StreamTest.java     |   2 +-
 .../org/apache/calcite/test/RelOptRulesTest.xml |  12 +-
 6 files changed, 361 insertions(+), 98 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/31d68f71/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java b/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java
index 644525a..4451f5c 100644
--- a/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java
@@ -22,6 +22,7 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptSchema;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
@@ -36,6 +37,8 @@ import java.util.List;
  * Extension to {@link RelBuilder} for Pig relational operators.
  */
 public class PigRelBuilder extends RelBuilder {
+  private String lastAlias;
+
   private PigRelBuilder(Context context,
       RelOptCluster cluster,
       RelOptSchema relOptSchema) {
@@ -50,10 +53,12 @@ public class PigRelBuilder extends RelBuilder {
   }
 
   @Override public PigRelBuilder scan(String... tableNames) {
+    lastAlias = null;
     return (PigRelBuilder) super.scan(tableNames);
   }
 
   @Override public PigRelBuilder scan(Iterable<String> tableNames) {
+    lastAlias = null;
     return (PigRelBuilder) super.scan(tableNames);
   }
 
@@ -135,14 +140,13 @@ public class PigRelBuilder extends RelBuilder {
       if (groupKey.i < n - 1) {
         r = build();
       }
-      String alias = getAlias();
       // Create a ROW to pass to COLLECT. Interestingly, this is not allowed
       // by standard SQL; see [CALCITE-877] Allow ROW as argument to COLLECT.
       final RexNode row =
           cluster.getRexBuilder().makeCall(peek(1, 0).getRowType(),
               SqlStdOperatorTable.ROW, fields());
       aggregate(groupKey.e,
-          aggregateCall(SqlStdOperatorTable.COLLECT, false, null, alias, row));
+          aggregateCall(SqlStdOperatorTable.COLLECT, false, null, getAlias(), row));
       if (groupKey.i < n - 1) {
         push(r);
         List<RexNode> predicates = new ArrayList<>();
@@ -155,6 +159,25 @@ public class PigRelBuilder extends RelBuilder {
     return this;
   }
 
+  String getAlias() {
+    if (lastAlias != null) {
+      return lastAlias;
+    } else {
+      RelNode top = peek();
+      if (top instanceof TableScan) {
+        return Util.last(top.getTable().getQualifiedName());
+      } else {
+        return null;
+      }
+    }
+  }
+
+  /** As super-class method, but also retains alias for naming of aggregates. */
+  @Override public RelBuilder as(final String alias) {
+    lastAlias = alias;
+    return super.as(alias);
+  }
+
   /** Partitioner for group and join */
   interface Partitioner {
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/31d68f71/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 f83da05..02d04dc 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -39,6 +39,7 @@ import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexCall;
 import org.apache.calcite.rex.RexCorrelVariable;
@@ -57,7 +58,6 @@ import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
-import org.apache.calcite.util.CompositeList;
 import org.apache.calcite.util.Holder;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
@@ -82,6 +82,7 @@ import java.util.AbstractList;
 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;
@@ -224,6 +225,13 @@ public class RelBuilder {
     return this;
   }
 
+  /** Adds a rel node to the top of the stack while preserving the field names
+   * and aliases. */
+  private void replaceTop(RelNode node) {
+    final Frame frame = stack.pop();
+    stack.push(new Frame(node, frame.fields));
+  }
+
   /** Pushes a collection of relational expressions. */
   public RelBuilder pushAll(Iterable<? extends RelNode> nodes) {
     for (RelNode node : nodes) {
@@ -405,28 +413,23 @@ public class RelBuilder {
   public RexNode field(int inputCount, String alias, String fieldName) {
     Preconditions.checkNotNull(alias);
     Preconditions.checkNotNull(fieldName);
-    final List<String> aliases = new ArrayList<>();
+    final List<String> fields = new ArrayList<>();
     for (int inputOrdinal = 0; inputOrdinal < inputCount; ++inputOrdinal) {
       final Frame frame = peek_(inputOrdinal);
-      int offset = 0; // relative to this frame
-      for (Pair<String, RelDataType> pair : frame.right) {
-        if (pair.left != null && pair.left.equals(alias)) {
-          int i = pair.right.getFieldNames().indexOf(fieldName);
-          if (i >= 0) {
-            return field(inputCount, inputCount - 1 - inputOrdinal,
-                offset + i);
-          } else {
-            throw new IllegalArgumentException("no field '" + fieldName
-                + "' in relation '" + alias
-                + "'; fields are: " + pair.right.getFieldNames());
-          }
+      for (Ord<Field> p
+          : Ord.zip(frame.fields)) {
+        // If alias and field name match, reference that field.
+        if (p.e.left.contains(alias)
+            && p.e.right.getName().equals(fieldName)) {
+          return field(inputCount, inputCount - 1 - inputOrdinal, p.i);
         }
-        aliases.add(pair.left);
-        offset += pair.right.getFieldCount();
+        fields.add(
+            String.format("{aliases=%s,fieldName=%s}", p.e.left,
+                p.e.right.getName()));
       }
     }
-    throw new IllegalArgumentException("no relation with alias '" + alias
-        + "'; aliases are: " + aliases);
+    throw new IllegalArgumentException("no aliased field found; fields are: "
+        + fields);
   }
 
   /** Returns a reference to a given field of a record-valued expression. */
@@ -476,7 +479,7 @@ public class RelBuilder {
   public ImmutableList<RexNode> fields(List<? extends Number> ordinals) {
     final ImmutableList.Builder<RexNode> nodes = ImmutableList.builder();
     for (Number ordinal : ordinals) {
-      RexNode node = field(1, 0, ordinal.intValue(), true);
+      RexNode node = field(1, 0, ordinal.intValue(), false);
       nodes.add(node);
     }
     return nodes.build();
@@ -796,7 +799,7 @@ public class RelBuilder {
     if (!x.isAlwaysTrue()) {
       final Frame frame = stack.pop();
       final RelNode filter = filterFactory.createFilter(frame.rel, x);
-      stack.push(new Frame(filter, frame.right));
+      stack.push(new Frame(filter, frame.fields));
     }
     return this;
   }
@@ -816,9 +819,6 @@ public class RelBuilder {
   /** Creates a {@link org.apache.calcite.rel.core.Project} of the given list
    * of expressions and field names.
    *
-   * <p>Infers names as would {@link #project(Iterable, Iterable)} if all
-   * suggested names were null.
-   *
    * @param nodes Expressions
    * @param fieldNames field names for expressions
    */
@@ -856,17 +856,51 @@ public class RelBuilder {
       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 = RexUtil.simplifyPreservingType(getRexBuilder(), node);
       }
       exprList.add(node);
-    }
-    final Iterator<String> nameIterator = fieldNames.iterator();
-    for (RexNode node : nodes) {
-      final String name = nameIterator.hasNext() ? nameIterator.next() : null;
+      String name = nameIterator.hasNext() ? nameIterator.next() : null;
       names.add(name != null ? name : inferAlias(exprList, node));
     }
+    final Frame frame = stack.peek();
+    final ImmutableList.Builder<Field> fields = ImmutableList.builder();
+    final Set<String> uniqueNameList =
+        getTypeFactory().getTypeSystem().isSchemaCaseSensitive()
+        ? new HashSet<String>()
+        : 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);
+      Field field;
+      if (name == null || uniqueNameList.contains(name)) {
+        int j = 0;
+        if (name == null) {
+          j = i;
+        }
+        do {
+          name = SqlValidatorUtil.F_SUGGESTER.apply(name, j, j++);
+        } while (uniqueNameList.contains(name));
+        names.set(i, name);
+      }
+      RelDataTypeField fieldType =
+          new RelDataTypeFieldImpl(name, i, node.getType());
+      switch (node.getKind()) {
+      case INPUT_REF:
+        // preserve rel aliases for INPUT_REF fields
+        final int index = ((RexInputRef) node).getIndex();
+        field = new Field(frame.fields.get(index).left, fieldType);
+        break;
+      default:
+        field = new Field(ImmutableSet.<String>of(), fieldType);
+        break;
+      }
+      uniqueNameList.add(name);
+      fields.add(field);
+    }
     final RelDataType inputRowType = peek().getRowType();
     if (!force && RexUtil.isIdentity(exprList, inputRowType)) {
       if (names.equals(inputRowType.getFieldNames())) {
@@ -874,20 +908,16 @@ public class RelBuilder {
         return this;
       } else {
         // create "virtual" row type for project only rename fields
-        final Frame frame = stack.pop();
-        final RelDataType rowType =
-            RexUtil.createStructType(cluster.getTypeFactory(), exprList,
-                names, SqlValidatorUtil.F_SUGGESTER);
-        stack.push(
-            new Frame(frame.rel,
-                ImmutableList.of(Pair.of(frame.right.get(0).left, rowType))));
+        stack.pop();
+        stack.push(new Frame(frame.rel, fields.build()));
         return this;
       }
     }
     final RelNode project =
-        projectFactory.createProject(build(), ImmutableList.copyOf(exprList),
+        projectFactory.createProject(frame.rel, ImmutableList.copyOf(exprList),
             names);
-    push(project);
+    stack.pop();
+    stack.push(new Frame(project, fields.build()));
     return this;
   }
 
@@ -906,7 +936,7 @@ public class RelBuilder {
     switch (expr.getKind()) {
     case INPUT_REF:
       final RexInputRef ref = (RexInputRef) expr;
-      return peek(0).getRowType().getFieldNames().get(ref.getIndex());
+      return stack.peek().fields.get(ref.getIndex()).getValue().getName();
     case CAST:
       return inferAlias(exprList, ((RexCall) expr).getOperands().get(0));
     case AS:
@@ -980,7 +1010,8 @@ public class RelBuilder {
     if (extraNodes.size() > inputRowType.getFieldCount()) {
       project(extraNodes);
     }
-    final RelNode r = build();
+    final Frame frame = stack.pop();
+    final RelNode r = frame.rel;
     final List<AggregateCall> aggregateCalls = new ArrayList<>();
     for (AggCall aggCall : aggCalls) {
       final AggregateCall aggregateCall;
@@ -1004,7 +1035,48 @@ public class RelBuilder {
     }
     RelNode aggregate = aggregateFactory.createAggregate(r,
         groupKey_.indicator, groupSet, groupSets, aggregateCalls);
-    push(aggregate);
+
+    // build field list
+    final ImmutableList.Builder<Field> fields = ImmutableList.builder();
+    final List<RelDataTypeField> aggregateFields =
+        aggregate.getRowType().getFieldList();
+    int i = 0;
+    // first, group fields
+    for (Integer groupField : groupSet.asList()) {
+      RexNode node = extraNodes.get(groupField);
+      final SqlKind kind = node.getKind();
+      switch (kind) {
+      case INPUT_REF:
+        fields.add(frame.fields.get(((RexInputRef) node).getIndex()));
+        break;
+      default:
+        String name = aggregateFields.get(i).getName();
+        RelDataTypeField fieldType =
+            new RelDataTypeFieldImpl(name, i, node.getType());
+        fields.add(new Field(ImmutableSet.<String>of(), fieldType));
+        break;
+      }
+      i++;
+    }
+    // second, indicator fields (copy from aggregate rel type)
+    if (groupKey_.indicator) {
+      for (int j = 0; j < groupSet.cardinality(); ++j) {
+        final RelDataTypeField field = aggregateFields.get(i);
+        final RelDataTypeField fieldType =
+            new RelDataTypeFieldImpl(field.getName(), i, field.getType());
+        fields.add(new Field(ImmutableSet.<String>of(), fieldType));
+        i++;
+      }
+    }
+    // third, aggregate fields. retain `i' as field index
+    for (int j = 0; j < aggregateCalls.size(); ++j) {
+      final AggregateCall call = aggregateCalls.get(j);
+      final RelDataTypeField fieldType =
+          new RelDataTypeFieldImpl(aggregateFields.get(i + j).getName(), i + j,
+              call.getType());
+      fields.add(new Field(ImmutableSet.<String>of(), fieldType));
+    }
+    stack.push(new Frame(aggregate, fields.build()));
     return this;
   }
 
@@ -1169,10 +1241,10 @@ public class RelBuilder {
       join = joinFactory.createJoin(left.rel, right.rel, condition,
           variablesSet, joinType, false);
     }
-    final List<Pair<String, RelDataType>> pairs = new ArrayList<>();
-    pairs.addAll(left.right);
-    pairs.addAll(right.right);
-    stack.push(new Frame(join, ImmutableList.copyOf(pairs)));
+    final ImmutableList.Builder<Field> fields = ImmutableList.builder();
+    fields.addAll(left.fields);
+    fields.addAll(right.fields);
+    stack.push(new Frame(join, fields.build()));
     filter(postCondition);
     return this;
   }
@@ -1200,10 +1272,9 @@ public class RelBuilder {
   /** Creates a {@link org.apache.calcite.rel.core.SemiJoin}. */
   public RelBuilder semiJoin(Iterable<? extends RexNode> conditions) {
     final Frame right = stack.pop();
-    final Frame left = stack.pop();
     final RelNode semiJoin =
-        semiJoinFactory.createSemiJoin(left.rel, right.rel, and(conditions));
-    stack.push(new Frame(semiJoin, left.right));
+        semiJoinFactory.createSemiJoin(peek(), right.rel, and(conditions));
+    replaceTop(semiJoin);
     return this;
   }
 
@@ -1213,11 +1284,16 @@ public class RelBuilder {
   }
 
   /** Assigns a table alias to the top entry on the stack. */
-  public RelBuilder as(String alias) {
+  public RelBuilder as(final String alias) {
     final Frame pair = stack.pop();
-    stack.push(
-        new Frame(pair.rel,
-            ImmutableList.of(Pair.of(alias, pair.right.get(0).right))));
+    List<Field> newFields =
+        Lists.transform(pair.fields, new Function<Field, Field>() {
+          public Field apply(Field field) {
+            return new Field(ImmutableSet.<String>builder().addAll(field.left)
+                .add(alias).build(), field.right);
+          }
+        });
+    stack.push(new Frame(pair.rel, ImmutableList.copyOf(newFields)));
     return this;
   }
 
@@ -1449,12 +1525,11 @@ public class RelBuilder {
       if (top instanceof Sort) {
         final Sort sort2 = (Sort) top;
         if (sort2.offset == null && sort2.fetch == null) {
-          stack.pop();
-          push(sort2.getInput());
+          replaceTop(sort2.getInput());
           final RelNode sort =
-              sortFactory.createSort(build(), sort2.collation,
+              sortFactory.createSort(peek(), sort2.collation,
                   offsetNode, fetchNode);
-          push(sort);
+          replaceTop(sort);
           return this;
         }
       }
@@ -1463,12 +1538,11 @@ public class RelBuilder {
         if (project.getInput() instanceof Sort) {
           final Sort sort2 = (Sort) project.getInput();
           if (sort2.offset == null && sort2.fetch == null) {
-            stack.pop();
-            push(sort2.getInput());
+            replaceTop(sort2.getInput());
             final RelNode sort =
-                sortFactory.createSort(build(), sort2.collation,
+                sortFactory.createSort(peek(), sort2.collation,
                     offsetNode, fetchNode);
-            push(sort);
+            replaceTop(sort);
             project(project.getProjects());
             return this;
           }
@@ -1479,9 +1553,9 @@ public class RelBuilder {
       project(extraNodes);
     }
     final RelNode sort =
-        sortFactory.createSort(build(), RelCollations.of(fieldCollations),
+        sortFactory.createSort(peek(), RelCollations.of(fieldCollations),
             offsetNode, fetchNode);
-    push(sort);
+    replaceTop(sort);
     if (addedFields) {
       project(originalExtraNodes);
     }
@@ -1560,13 +1634,6 @@ public class RelBuilder {
     stack.clear();
   }
 
-  protected String getAlias() {
-    final Frame frame = stack.peek();
-    return frame.right.size() == 1
-        ? frame.right.get(0).left
-        : null;
-  }
-
   /** Information necessary to create a call to an aggregate function.
    *
    * @see RelBuilder#aggregateCall */
@@ -1642,23 +1709,25 @@ public class RelBuilder {
    * <p>Describes a previously created relational expression and
    * information about how table aliases map into its row type. */
   private static class Frame {
-    static final Function<Pair<String, RelDataType>, List<RelDataTypeField>> FN =
-        new Function<Pair<String, RelDataType>, List<RelDataTypeField>>() {
-          public List<RelDataTypeField> apply(Pair<String, RelDataType> input) {
-            return input.right.getFieldList();
-          }
-        };
-
     final RelNode rel;
-    final ImmutableList<Pair<String, RelDataType>> right;
+    final ImmutableList<Field> fields;
 
-    private Frame(RelNode rel, ImmutableList<Pair<String, RelDataType>> pairs) {
+    private Frame(RelNode rel, ImmutableList<Field> fields) {
       this.rel = rel;
-      this.right = pairs;
+      this.fields = fields;
     }
 
     private Frame(RelNode rel) {
-      this(rel, ImmutableList.of(Pair.of(deriveAlias(rel), rel.getRowType())));
+      String tableAlias = deriveAlias(rel);
+      ImmutableList.Builder<Field> builder = ImmutableList.builder();
+      ImmutableSet<String> aliases = tableAlias == null
+          ? ImmutableSet.<String>of()
+          : ImmutableSet.of(tableAlias);
+      for (RelDataTypeField field : rel.getRowType().getFieldList()) {
+        builder.add(new Field(aliases, field));
+      }
+      this.rel = rel;
+      this.fields = builder.build();
     }
 
     private static String deriveAlias(RelNode rel) {
@@ -1672,7 +1741,15 @@ public class RelBuilder {
     }
 
     List<RelDataTypeField> fields() {
-      return CompositeList.ofCopy(Iterables.transform(right, FN));
+      return Pair.right(fields);
+    }
+  }
+
+  /** A field that belongs to a stack {@link Frame}. */
+  private static class Field
+      extends Pair<ImmutableSet<String>, RelDataTypeField> {
+    public Field(ImmutableSet<String> left, RelDataTypeField right) {
+      super(left, right);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/31d68f71/core/src/test/java/org/apache/calcite/test/PigRelBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/PigRelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/PigRelBuilderTest.java
index d877097..38a3a3a 100644
--- a/core/src/test/java/org/apache/calcite/test/PigRelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/PigRelBuilderTest.java
@@ -119,7 +119,8 @@ public class PigRelBuilderTest {
         .build();
     final String plan = "LogicalJoin(condition=[=($0, $2)], joinType=[inner])\n"
         + "  LogicalAggregate(group=[{0}], EMP=[COLLECT($8)])\n"
-        + "    LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[ROW($0, $1, $2, $3, $4, $5, $6, $7)])\n      LogicalTableScan(table=[[scott, EMP]])\n  LogicalAggregate(group=[{0}], DEPT=[COLLECT($3)])\n"
+        + "    LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[ROW($0, $1, $2, $3, $4, $5, $6, $7)])\n"
+        + "      LogicalTableScan(table=[[scott, EMP]])\n  LogicalAggregate(group=[{0}], DEPT=[COLLECT($3)])\n"
         + "    LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], $f3=[ROW($0, $1, $2)])\n"
         + "      LogicalTableScan(table=[[scott, DEPT]])\n";
     assertThat(str(root), is(plan));

http://git-wip-us.apache.org/repos/asf/calcite/blob/31d68f71/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 6c0b99e..533dad4 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -47,6 +47,7 @@ import org.apache.calcite.util.mapping.Mappings;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 
 import org.junit.Test;
 
@@ -366,7 +367,7 @@ public class RelBuilderTest {
     // Note: AS(COMM, C) becomes just $6
     assertThat(str(root),
         is(
-            "LogicalProject(DEPTNO=[$7], COMM=[CAST($6):SMALLINT NOT NULL], $f2=[20], COMM3=[$6], C=[$6])\n"
+            "LogicalProject(DEPTNO=[$7], COMM=[CAST($6):SMALLINT NOT NULL], $f2=[20], COMM0=[$6], C=[$6])\n"
             + "  LogicalTableScan(table=[[scott, EMP]])\n"));
   }
 
@@ -399,7 +400,7 @@ public class RelBuilderTest {
         is("LogicalProject(DEPTNO=[$7], COMM=[CAST($6):SMALLINT NOT NULL],"
                 + " $f2=[OR(=($7, 20), AND(null, =($7, 10), IS NULL($6),"
                 + " IS NULL($7)), =($7, 30))], n2=[IS NULL($2)],"
-                + " nn2=[IS NOT NULL($3)], $f5=[20], COMM6=[$6], C=[$6])\n"
+                + " nn2=[IS NOT NULL($3)], $f5=[20], COMM0=[$6], C=[$6])\n"
                 + "  LogicalTableScan(table=[[scott, EMP]])\n"));
   }
 
@@ -428,7 +429,7 @@ public class RelBuilderTest {
             .project(builder.field("a"),
                 builder.field("t1", "c"))
             .build();
-    final String expected = "LogicalProject(DEPTNO=[$0], LOC=[$2])\n"
+    final String expected = "LogicalProject(a=[$0], c=[$2])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
     assertThat(str(root), is(expected));
   }
@@ -446,15 +447,18 @@ public class RelBuilderTest {
                 builder.call(SqlStdOperatorTable.EQUALS,
                     builder.field("a"),
                     builder.literal(20)))
-            .aggregate(builder.groupKey(0, 1, 2))
+            .aggregate(builder.groupKey(0, 1, 2),
+                builder.aggregateCall(SqlStdOperatorTable.SUM,
+                    false, null, null,
+                    builder.field(0)))
             .project(builder.field("c"),
                 builder.field("a"))
             .build();
-    final String expected = "LogicalProject(c=[$2], a=[$0])\n"
-        + "  LogicalAggregate(group=[{3, 4, 5}])\n"
-        + "    LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], a=[$0], b=[$1], c=[$2])\n"
-        + "      LogicalFilter(condition=[=($0, 20)])\n"
-        + "        LogicalTableScan(table=[[scott, DEPT]])\n";
+    final String expected = ""
+        + "LogicalProject(c=[$2], a=[$0])\n"
+        + "  LogicalAggregate(group=[{0, 1, 2}], agg#0=[SUM($0)])\n"
+        + "    LogicalFilter(condition=[=($0, 20)])\n"
+        + "      LogicalTableScan(table=[[scott, DEPT]])\n";
     assertThat(str(root), is(expected));
   }
 
@@ -1058,6 +1062,164 @@ public class RelBuilderTest {
     assertThat(str(root), is(expected));
   }
 
+  @Test public void testAliasSort() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .as("e")
+            .sort(0)
+            .project(builder.field("e", "EMPNO"))
+            .build();
+    final String expected = ""
+        + "LogicalProject(EMPNO=[$0])\n"
+        + "  LogicalSort(sort0=[$0], dir0=[ASC])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(str(root), is(expected));
+  }
+
+  @Test public void testAliasLimit() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .as("e")
+            .sort(1)
+            .sortLimit(10, 20) // aliases were lost here if preceded by sort()
+            .project(builder.field("e", "EMPNO"))
+            .build();
+    final String expected = ""
+        + "LogicalProject(EMPNO=[$0])\n"
+        + "  LogicalSort(sort0=[$1], dir0=[ASC], offset=[10], fetch=[20])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(str(root), is(expected));
+  }
+
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-1551">[CALCITE-1551]
+   * RelBuilder's project() doesn't preserve alias</a>. */
+  @Test public void testAliasProject() {
+    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("EMP_alias", "DEPTNO"))
+            .build();
+    final String expected = ""
+        + "LogicalProject(DEPTNO=[$0])\n"
+        + "  LogicalProject(DEPTNO=[$7], $f1=[20])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(str(root), is(expected));
+  }
+
+  @Test public void testAliasAggregate() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .as("EMP_alias")
+            .project(builder.field("DEPTNO"),
+                builder.literal(20))
+            .aggregate(builder.groupKey(builder.field("EMP_alias", "DEPTNO")),
+                builder.aggregateCall(SqlStdOperatorTable.SUM, false, null,
+                    null, builder.field(1)))
+            .project(builder.alias(builder.field(1), "sum"),
+                builder.field("EMP_alias", "DEPTNO"))
+            .build();
+    final String expected = ""
+        + "LogicalProject(sum=[$1], DEPTNO=[$0])\n"
+        + "  LogicalAggregate(group=[{0}], agg#0=[SUM($1)])\n"
+        + "    LogicalProject(DEPTNO=[$7], $f1=[20])\n"
+        + "      LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(str(root), is(expected));
+  }
+
+  /** Tests that a projection retains field names after a join. */
+  @Test public void testProjectJoin() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .as("e")
+            .scan("DEPT")
+            .join(JoinRelType.INNER)
+            .project(builder.field("DEPT", "DEPTNO"),
+                builder.field(0),
+                builder.field("e", "MGR"))
+            // essentially a no-op, was previously throwing exception due to
+            // project() using join-renamed fields
+            .project(builder.field("DEPT", "DEPTNO"),
+                builder.field(1),
+                builder.field("e", "MGR"))
+            .build();
+    final String expected = ""
+        + "LogicalProject(DEPTNO=[$8], EMPNO=[$0], MGR=[$3])\n"
+        + "  LogicalJoin(condition=[true], joinType=[inner])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n"
+        + "    LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(str(root), is(expected));
+  }
+
+  @Test public void testMultiLevelAlias() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .as("e")
+            .scan("EMP")
+            .as("m")
+            .scan("DEPT")
+            .join(JoinRelType.INNER)
+            .join(JoinRelType.INNER)
+            .project(builder.field("DEPT", "DEPTNO"),
+                builder.field(16),
+                builder.field("m", "EMPNO"),
+                builder.field("e", "MGR"))
+            .as("all")
+            .filter(
+                builder.call(SqlStdOperatorTable.GREATER_THAN,
+                    builder.field("DEPT", "DEPTNO"),
+                    builder.literal(100)))
+            .project(builder.field("DEPT", "DEPTNO"),
+                builder.field("all", "EMPNO"))
+            .build();
+    final String expected = ""
+        + "LogicalProject(DEPTNO=[$0], EMPNO=[$2])\n"
+        + "  LogicalFilter(condition=[>($0, 100)])\n"
+        + "    LogicalProject(DEPTNO=[$16], DEPTNO0=[$16], EMPNO=[$8], MGR=[$3])\n"
+        + "      LogicalJoin(condition=[true], joinType=[inner])\n"
+        + "        LogicalTableScan(table=[[scott, EMP]])\n"
+        + "        LogicalJoin(condition=[true], joinType=[inner])\n"
+        + "          LogicalTableScan(table=[[scott, EMP]])\n"
+        + "          LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(str(root), is(expected));
+  }
+
+  @Test public void testUnionAlias() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .as("e1")
+            .project(builder.field("EMPNO"),
+                builder.call(SqlStdOperatorTable.CONCAT,
+                    builder.field("ENAME"),
+                    builder.literal("-1")))
+            .scan("EMP")
+            .as("e2")
+            .project(builder.field("EMPNO"),
+                builder.call(SqlStdOperatorTable.CONCAT,
+                    builder.field("ENAME"),
+                    builder.literal("-2")))
+            .union(false) // aliases lost here
+            .project(builder.fields(Lists.newArrayList(1, 0)))
+            .build();
+    final String expected = ""
+        + "LogicalProject($f1=[$1], EMPNO=[$0])\n"
+        + "  LogicalUnion(all=[false])\n"
+        + "    LogicalProject(EMPNO=[$0], $f1=[||($1, '-1')])\n"
+        + "      LogicalTableScan(table=[[scott, EMP]])\n"
+        + "    LogicalProject(EMPNO=[$0], $f1=[||($1, '-2')])\n"
+        + "      LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(str(root), is(expected));
+  }
+
   /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-1523">[CALCITE-1523]
    * Add RelBuilder field() method to reference aliased relations not on top of

http://git-wip-us.apache.org/repos/asf/calcite/blob/31d68f71/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 6b6e225..ebb420f 100644
--- a/core/src/test/java/org/apache/calcite/test/StreamTest.java
+++ b/core/src/test/java/org/apache/calcite/test/StreamTest.java
@@ -286,7 +286,7 @@ public class StreamTest {
             + "  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], PRODUCT4=[CAST($2):VARCHAR(32) CHARACTER SET \"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\" NOT NULL])\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(""

http://git-wip-us.apache.org/repos/asf/calcite/blob/31d68f71/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 24f60c6..fd5fe5f 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -1221,11 +1221,11 @@ LogicalProject(X=[$0], Y=[$1])
             <![CDATA[
 LogicalProject(EXPR$0=[CAST($1):VARCHAR(128) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary" NOT NULL], EXPR$1=[CAST($2):INTEGER NOT NULL])
   LogicalFilter(condition=[=(CAST(CAST($4):VARCHAR(1) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary" NOT NULL):VARCHAR(7) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary" NOT NULL, 'Manager')])
-    LogicalProject(DEPTNO=[$0], NAME=[$1], EMPNO=[$3], ENAME=[$4], JOB=[$5], MGR=[$6], HIREDATE=[$7], SAL=[$8], COMM=[$9], DEPTNO0=[$10], SLACKER=[$11])
+    LogicalProject(DEPTNO=[$0], NAME=[$1], EMPNO=[$3], ENAME=[$4], JOB=[$5], MGR=[$6], HIREDATE=[$7], SAL=[$8], COMM=[$9], DEPTNO1=[$10], SLACKER=[$11])
       LogicalJoin(condition=[=($2, $12)], joinType=[inner])
-        LogicalProject(DEPTNO=[$0], NAME=[$1], DEPTNO2=[CAST($0):INTEGER NOT NULL])
+        LogicalProject(DEPTNO=[$0], NAME=[$1], DEPTNO0=[CAST($0):INTEGER NOT NULL])
           LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
-        LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO9=[CAST($7):INTEGER NOT NULL])
+        LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[CAST($7):INTEGER NOT NULL])
           LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
         </Resource>
@@ -1233,11 +1233,11 @@ LogicalProject(EXPR$0=[CAST($1):VARCHAR(128) CHARACTER SET "ISO-8859-1" COLLATE
             <![CDATA[
 LogicalProject(EXPR$0=[CAST($1):VARCHAR(128) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary" NOT NULL], EXPR$1=[$2])
   LogicalFilter(condition=[=(CAST(CAST($4):VARCHAR(1) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary" NOT NULL):VARCHAR(7) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary" NOT NULL, 'Manager')])
-    LogicalProject(DEPTNO=[$0], NAME=[$1], EMPNO=[$3], ENAME=[$4], JOB=[$5], MGR=[$6], HIREDATE=[$7], SAL=[$8], COMM=[$9], DEPTNO0=[$10], SLACKER=[$11])
+    LogicalProject(DEPTNO=[$0], NAME=[$1], EMPNO=[$3], ENAME=[$4], JOB=[$5], MGR=[$6], HIREDATE=[$7], SAL=[$8], COMM=[$9], DEPTNO1=[$10], SLACKER=[$11])
       LogicalJoin(condition=[=($2, $12)], joinType=[inner])
-        LogicalProject(DEPTNO=[$0], NAME=[$1], DEPTNO2=[$0])
+        LogicalProject(DEPTNO=[$0], NAME=[$1], DEPTNO0=[$0])
           LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
-        LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO9=[$7])
+        LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[$7])
           LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
         </Resource>