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/06/10 00:08:10 UTC

[6/7] incubator-calcite git commit: [CALCITE-748] Add RelBuilder, builder for expressions in relational algebra

[CALCITE-748] Add RelBuilder, builder for expressions in relational algebra

Deprecate RelTraitSet argument to SortFactory.createSort.

Add RelProtoBuilder and use it in one planner rule, FilterAggregateTransposeRule.


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

Branch: refs/heads/master
Commit: 6609cb1a30bf36b1223078e8ebaf7cc9f7289b7c
Parents: b181851
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Jun 1 21:00:59 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Jun 5 16:06:13 2015 -0700

----------------------------------------------------------------------
 .../java/org/apache/calcite/plan/Contexts.java  |  21 +-
 .../org/apache/calcite/plan/RelOptRuleCall.java |  12 +-
 .../calcite/prepare/CalcitePrepareImpl.java     |   8 +-
 .../apache/calcite/rel/core/RelFactories.java   | 108 ++-
 .../rel/rules/FilterAggregateTransposeRule.java |  30 +-
 .../java/org/apache/calcite/schema/Path.java    |   7 +
 .../java/org/apache/calcite/schema/Schemas.java |  27 +-
 .../apache/calcite/sql2rel/RelFieldTrimmer.java |  24 +-
 .../org/apache/calcite/tools/Frameworks.java    |   6 +-
 .../org/apache/calcite/tools/RelBuilder.java    | 958 +++++++++++++++++++
 .../java/org/apache/calcite/util/Stacks.java    |  17 +
 .../calcite/examples/RelBuilderExample.java     | 171 ++++
 .../org/apache/calcite/test/CalciteSuite.java   |   1 +
 .../org/apache/calcite/test/RelBuilderTest.java | 624 ++++++++++++
 .../apache/calcite/tools/FrameworksTest.java    |  34 +
 .../java/org/apache/calcite/util/UtilTest.java  |   5 +
 site/_docs/algebra.md                           | 317 ++++++
 site/_docs/reference.md                         |   6 +-
 site/_posts/2015-06-05-algebra-builder.md       |  89 ++
 19 files changed, 2419 insertions(+), 46 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/main/java/org/apache/calcite/plan/Contexts.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/Contexts.java b/core/src/main/java/org/apache/calcite/plan/Contexts.java
index 5a4eb74..7612999 100644
--- a/core/src/main/java/org/apache/calcite/plan/Contexts.java
+++ b/core/src/main/java/org/apache/calcite/plan/Contexts.java
@@ -22,6 +22,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -56,7 +57,16 @@ public class Contexts {
     return new WrapContext(o);
   }
 
-  /** Returns a context that wraps an object.
+  /** Returns a context that wraps an array of objects. */
+  public static Context of(Object... os) {
+    final List<Context> contexts = new ArrayList<>();
+    for (Object o : os) {
+      contexts.add(of(o));
+    }
+    return chain(contexts);
+  }
+
+  /** Returns a context that wraps a list of contexts.
    *
    * <p>A call to {@code unwrap(C)} will return the first object that is an
    * instance of {@code C}.
@@ -65,6 +75,10 @@ public class Contexts {
    * object. Thus this method can be used to chain contexts.
    */
   public static Context chain(Context... contexts) {
+    return chain(ImmutableList.copyOf(contexts));
+  }
+
+  private static Context chain(Iterable<? extends Context> contexts) {
     // Flatten any chain contexts in the list, and remove duplicates
     final List<Context> list = Lists.newArrayList();
     for (Context context : contexts) {
@@ -82,12 +96,15 @@ public class Contexts {
 
   /** Recursively populates a list of contexts. */
   private static void build(List<Context> list, Context context) {
+    if (context == EMPTY_CONTEXT || list.contains(context)) {
+      return;
+    }
     if (context instanceof ChainContext) {
       ChainContext chainContext = (ChainContext) context;
       for (Context child : chainContext.contexts) {
         build(list, child);
       }
-    } else if (!list.contains(context)) {
+    } else {
       list.add(context);
     }
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/main/java/org/apache/calcite/plan/RelOptRuleCall.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptRuleCall.java b/core/src/main/java/org/apache/calcite/plan/RelOptRuleCall.java
index 7b36cb9..9a1fb0d 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptRuleCall.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptRuleCall.java
@@ -17,6 +17,8 @@
 package org.apache.calcite.plan;
 
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.trace.CalciteTrace;
 
 import com.google.common.collect.ImmutableList;
@@ -116,6 +118,7 @@ public abstract class RelOptRuleCall {
    * @return matched relational expressions
    * @deprecated Use {@link #getRelList()} or {@link #rel(int)}
    */
+  @Deprecated // to be removed before 2.0
   public RelNode[] getRels() {
     return rels;
   }
@@ -151,7 +154,7 @@ public abstract class RelOptRuleCall {
    * {@link org.apache.calcite.plan.RelOptRuleOperandChildPolicy#ANY},
    * the children will have their
    * own operands and therefore be easily available in the array returned by
-   * the {@link #getRels} method, so this method returns null.
+   * the {@link #getRelList()} method, so this method returns null.
    *
    * <p>This method is for
    * {@link org.apache.calcite.plan.RelOptRuleOperandChildPolicy#ANY},
@@ -209,6 +212,13 @@ public abstract class RelOptRuleCall {
   public final void transformTo(RelNode rel) {
     transformTo(rel, ImmutableMap.<RelNode, RelNode>of());
   }
+
+  /** Creates a {@link org.apache.calcite.tools.RelBuilder} to be used by
+   * code within the call. The {@code protoBuilder} argument contains policies
+   * such as what implementation of {@link Filter} to create. */
+  public RelBuilder builder(RelBuilder.ProtoRelBuilder protoBuilder) {
+    return protoBuilder.create(rel(0).getCluster(), null);
+  }
 }
 
 // End RelOptRuleCall.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
index 9ebfe8a..d78f160 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -862,10 +862,14 @@ public class CalcitePrepareImpl implements CalcitePrepare {
     final CalcitePrepare.Context prepareContext =
         statement.createPrepareContext();
     final JavaTypeFactory typeFactory = prepareContext.getTypeFactory();
+    final CalciteSchema schema =
+        action.getConfig().getDefaultSchema() != null
+            ? CalciteSchema.from(action.getConfig().getDefaultSchema())
+            : prepareContext.getRootSchema();
     CalciteCatalogReader catalogReader =
-        new CalciteCatalogReader(prepareContext.getRootSchema(),
+        new CalciteCatalogReader(schema.root(),
             prepareContext.config().caseSensitive(),
-            prepareContext.getDefaultSchemaPath(),
+            schema.path(null),
             typeFactory);
     final RexBuilder rexBuilder = new RexBuilder(typeFactory);
     final RelOptPlanner planner =

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
index 2e2225a..787ab1f 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
@@ -17,6 +17,9 @@
 
 package org.apache.calcite.rel.core;
 
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollation;
@@ -27,10 +30,15 @@ import org.apache.calcite.rel.logical.LogicalIntersect;
 import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.rel.logical.LogicalMinus;
 import org.apache.calcite.rel.logical.LogicalSort;
+import org.apache.calcite.rel.logical.LogicalTableScan;
 import org.apache.calcite.rel.logical.LogicalUnion;
+import org.apache.calcite.rel.logical.LogicalValues;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
@@ -63,6 +71,27 @@ public class RelFactories {
   public static final SetOpFactory DEFAULT_SET_OP_FACTORY =
       new SetOpFactoryImpl();
 
+  public static final ValuesFactory DEFAULT_VALUES_FACTORY =
+      new ValuesFactoryImpl();
+
+  public static final TableScanFactory DEFAULT_TABLE_SCAN_FACTORY =
+      new TableScanFactoryImpl();
+
+  /** Creates a {@link RelBuilder} that will create logical relational
+   * expressions for everything.
+   */
+  public static final RelBuilder.ProtoRelBuilder DEFAULT_PROTO =
+      RelBuilder.proto(
+          Contexts.of(DEFAULT_PROJECT_FACTORY,
+              DEFAULT_FILTER_FACTORY,
+              DEFAULT_JOIN_FACTORY,
+              DEFAULT_SEMI_JOIN_FACTORY,
+              DEFAULT_SORT_FACTORY,
+              DEFAULT_AGGREGATE_FACTORY,
+              DEFAULT_SET_OP_FACTORY,
+              DEFAULT_VALUES_FACTORY,
+              DEFAULT_TABLE_SCAN_FACTORY));
+
   private RelFactories() {
   }
 
@@ -73,7 +102,7 @@ public class RelFactories {
    */
   public interface ProjectFactory {
     /** Creates a project. */
-    RelNode createProject(RelNode child, List<? extends RexNode> childExprs,
+    RelNode createProject(RelNode input, List<? extends RexNode> childExprs,
         List<String> fieldNames);
   }
 
@@ -82,9 +111,9 @@ public class RelFactories {
    * {@link org.apache.calcite.rel.logical.LogicalProject}.
    */
   private static class ProjectFactoryImpl implements ProjectFactory {
-    public RelNode createProject(RelNode child,
+    public RelNode createProject(RelNode input,
         List<? extends RexNode> childExprs, List<String> fieldNames) {
-      return RelOptUtil.createProject(child, childExprs, fieldNames);
+      return RelOptUtil.createProject(input, childExprs, fieldNames);
     }
   }
 
@@ -94,7 +123,11 @@ public class RelFactories {
    */
   public interface SortFactory {
     /** Creates a sort. */
-    RelNode createSort(RelTraitSet traits, RelNode child,
+    RelNode createSort(RelNode input, RelCollation collation, RexNode offset,
+        RexNode fetch);
+
+    @Deprecated // to be removed before 2.0
+    RelNode createSort(RelTraitSet traits, RelNode input,
         RelCollation collation, RexNode offset, RexNode fetch);
   }
 
@@ -103,9 +136,15 @@ public class RelFactories {
    * returns a vanilla {@link Sort}.
    */
   private static class SortFactoryImpl implements SortFactory {
-    public RelNode createSort(RelTraitSet traits, RelNode child,
+    public RelNode createSort(RelNode input, RelCollation collation,
+        RexNode offset, RexNode fetch) {
+      return LogicalSort.create(input, collation, offset, fetch);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public RelNode createSort(RelTraitSet traits, RelNode input,
         RelCollation collation, RexNode offset, RexNode fetch) {
-      return LogicalSort.create(child, collation, offset, fetch);
+      return createSort(input, collation, offset, fetch);
     }
   }
 
@@ -146,7 +185,7 @@ public class RelFactories {
    */
   public interface AggregateFactory {
     /** Creates an aggregate. */
-    RelNode createAggregate(RelNode child, boolean indicator,
+    RelNode createAggregate(RelNode input, boolean indicator,
         ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
         List<AggregateCall> aggCalls);
   }
@@ -156,10 +195,10 @@ public class RelFactories {
    * that returns a vanilla {@link LogicalAggregate}.
    */
   private static class AggregateFactoryImpl implements AggregateFactory {
-    public RelNode createAggregate(RelNode child, boolean indicator,
+    public RelNode createAggregate(RelNode input, boolean indicator,
         ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
         List<AggregateCall> aggCalls) {
-      return LogicalAggregate.create(child, indicator,
+      return LogicalAggregate.create(input, indicator,
           groupSet, groupSets, aggCalls);
     }
   }
@@ -170,7 +209,7 @@ public class RelFactories {
    */
   public interface FilterFactory {
     /** Creates a filter. */
-    RelNode createFilter(RelNode child, RexNode condition);
+    RelNode createFilter(RelNode input, RexNode condition);
   }
 
   /**
@@ -178,8 +217,8 @@ public class RelFactories {
    * returns a vanilla {@link LogicalFilter}.
    */
   private static class FilterFactoryImpl implements FilterFactory {
-    public RelNode createFilter(RelNode child, RexNode condition) {
-      return LogicalFilter.create(child, condition);
+    public RelNode createFilter(RelNode input, RexNode condition) {
+      return LogicalFilter.create(input, condition);
     }
   }
 
@@ -247,6 +286,51 @@ public class RelFactories {
         condition, joinInfo.leftKeys, joinInfo.rightKeys);
     }
   }
+
+  /**
+   * Can create a {@link Values} of the appropriate type for a rule's calling
+   * convention.
+   */
+  public interface ValuesFactory {
+    /**
+     * Creates a Values.
+     */
+    RelNode createValues(RelOptCluster cluster, RelDataType rowType,
+        List<ImmutableList<RexLiteral>> tuples);
+  }
+
+  /**
+   * Implementation of {@link ValuesFactory} that returns a
+   * {@link LogicalValues}.
+   */
+  private static class ValuesFactoryImpl implements ValuesFactory {
+    public RelNode createValues(RelOptCluster cluster, RelDataType rowType,
+        List<ImmutableList<RexLiteral>> tuples) {
+      return LogicalValues.create(cluster, rowType,
+          ImmutableList.copyOf(tuples));
+    }
+  }
+
+  /**
+   * Can create a {@link TableScan} of the appropriate type for a rule's calling
+   * convention.
+   */
+  public interface TableScanFactory {
+    /**
+     * Creates a {@link TableScan}.
+     */
+    RelNode createScan(RelOptCluster cluster, RelOptTable table);
+  }
+
+  /**
+   * Implementation of {@link TableScanFactory} that returns a
+   * {@link LogicalTableScan}.
+   */
+  private static class TableScanFactoryImpl implements TableScanFactory {
+    public RelNode createScan(RelOptCluster cluster, RelOptTable table) {
+      return LogicalTableScan.create(cluster, table);
+    }
+  }
 }
 
 // End RelFactories.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/main/java/org/apache/calcite/rel/rules/FilterAggregateTransposeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/FilterAggregateTransposeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/FilterAggregateTransposeRule.java
index 3701a1a..d3de47f 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/FilterAggregateTransposeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/FilterAggregateTransposeRule.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.rel.rules;
 
+import org.apache.calcite.plan.Contexts;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptUtil;
@@ -26,6 +27,7 @@ import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
@@ -46,33 +48,40 @@ public class FilterAggregateTransposeRule extends RelOptRule {
    *
    * <p>It matches any kind of agg. or filter */
   public static final FilterAggregateTransposeRule INSTANCE =
-      new FilterAggregateTransposeRule(Filter.class,
-          RelFactories.DEFAULT_FILTER_FACTORY,
+      new FilterAggregateTransposeRule(Filter.class, RelFactories.DEFAULT_PROTO,
           Aggregate.class);
 
-  private final RelFactories.FilterFactory filterFactory;
+  private final RelBuilder.ProtoRelBuilder protoBuilder;
 
   //~ Constructors -----------------------------------------------------------
 
   /**
-   * Creates a PushFilterPastAggRule.
+   * Creates a FilterAggregateTransposeRule.
    *
    * <p>If {@code filterFactory} is null, creates the same kind of filter as
    * matched in the rule. Similarly {@code aggregateFactory}.</p>
    */
   public FilterAggregateTransposeRule(
       Class<? extends Filter> filterClass,
-      RelFactories.FilterFactory filterFactory,
+      RelBuilder.ProtoRelBuilder protoBuilder,
       Class<? extends Aggregate> aggregateClass) {
     super(
         operand(filterClass,
             operand(aggregateClass, any())));
-    this.filterFactory = filterFactory;
+    this.protoBuilder = protoBuilder;
+  }
+
+  @Deprecated // to be removed before 2.0
+  public FilterAggregateTransposeRule(
+      Class<? extends Filter> filterClass,
+      RelFactories.FilterFactory filterFactory,
+      Class<? extends Aggregate> aggregateClass) {
+    this(filterClass, RelBuilder.proto(Contexts.of(filterFactory)),
+        aggregateClass);
   }
 
   //~ Methods ----------------------------------------------------------------
 
-  // implement RelOptRule
   public void onMatch(RelOptRuleCall call) {
     final Filter filterRel = call.rel(0);
     final Aggregate aggRel = call.rel(1);
@@ -112,13 +121,14 @@ public class FilterAggregateTransposeRule extends RelOptRule {
       }
     }
 
-    RelNode rel = RelOptUtil.createFilter(aggRel.getInput(), pushedConditions,
-        filterFactory);
+    final RelBuilder builder = call.builder(protoBuilder);
+    RelNode rel =
+        builder.push(aggRel.getInput()).filter(pushedConditions).build();
     if (rel == aggRel.getInput(0)) {
       return;
     }
     rel = aggRel.copy(aggRel.getTraitSet(), ImmutableList.of(rel));
-    rel = RelOptUtil.createFilter(rel, remainingConditions, filterFactory);
+    rel = builder.push(rel).filter(remainingConditions).build();
     call.transformTo(rel);
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/main/java/org/apache/calcite/schema/Path.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/Path.java b/core/src/main/java/org/apache/calcite/schema/Path.java
index 7c363a0..1575f58 100644
--- a/core/src/main/java/org/apache/calcite/schema/Path.java
+++ b/core/src/main/java/org/apache/calcite/schema/Path.java
@@ -32,7 +32,14 @@ import java.util.RandomAccess;
  * </ul>
  */
 public interface Path extends List<Pair<String, Schema>>, RandomAccess {
+  /** Returns the parent path, or null if the path is empty. */
   Path parent();
+
+  /** Returns the names of this path, not including the name of the root. */
+  List<String> names();
+
+  /** Returns the schemas of this path. */
+  List<Schema> schemas();
 }
 
 // End Path.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/main/java/org/apache/calcite/schema/Schemas.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/Schemas.java b/core/src/main/java/org/apache/calcite/schema/Schemas.java
index b634233..7c09ffb 100644
--- a/core/src/main/java/org/apache/calcite/schema/Schemas.java
+++ b/core/src/main/java/org/apache/calcite/schema/Schemas.java
@@ -514,6 +514,15 @@ public final class Schemas {
     return new PathImpl(build);
   }
 
+  /** Returns the path to get to a schema from its root. */
+  public static Path path(SchemaPlus schema) {
+    List<Pair<String, Schema>> list = new ArrayList<>();
+    for (SchemaPlus s = schema; s != null; s = s.getParentSchema()) {
+      list.add(Pair.<String, Schema>of(s.getName(), s));
+    }
+    return new PathImpl(ImmutableList.copyOf(Lists.reverse(list)));
+  }
+
   /** Dummy data context that has no variables. */
   private static class DummyDataContext implements DataContext {
     private final CalciteConnection connection;
@@ -572,12 +581,28 @@ public final class Schemas {
       return pairs.size();
     }
 
-    @Override public Path parent() {
+    public Path parent() {
       if (pairs.isEmpty()) {
         throw new IllegalArgumentException("at root");
       }
       return new PathImpl(pairs.subList(0, pairs.size() - 1));
     }
+
+    public List<String> names() {
+      return new AbstractList<String>() {
+        public String get(int index) {
+          return pairs.get(index + 1).left;
+        }
+
+        public int size() {
+          return pairs.size() - 1;
+        }
+      };
+    }
+
+    public List<Schema> schemas() {
+      return Pair.right(pairs);
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/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 99975b3..5e9e333 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
@@ -19,7 +19,6 @@ package org.apache.calcite.sql2rel;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
@@ -234,7 +233,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     }
     final RelDataType rowType = input.getRowType();
     List<RelDataTypeField> fieldList = rowType.getFieldList();
-    final List<RexNode> exprList = new ArrayList<RexNode>();
+    final List<RexNode> exprList = new ArrayList<>();
     final List<String> nameList = rowType.getFieldNames();
     RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
     assert trimResult.right.getSourceCount() == fieldList.size();
@@ -331,7 +330,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
 
     // Which fields are required from the input?
     final Set<RelDataTypeField> inputExtraFields =
-        new LinkedHashSet<RelDataTypeField>(extraFields);
+        new LinkedHashSet<>(extraFields);
     RelOptUtil.InputFinder inputFinder =
         new RelOptUtil.InputFinder(inputExtraFields);
     for (Ord<RexNode> ord : Ord.zip(project.getProjects())) {
@@ -436,7 +435,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     // We use the fields used by the consumer, plus any fields used in the
     // filter.
     final Set<RelDataTypeField> inputExtraFields =
-        new LinkedHashSet<RelDataTypeField>(extraFields);
+        new LinkedHashSet<>(extraFields);
     RelOptUtil.InputFinder inputFinder =
         new RelOptUtil.InputFinder(inputExtraFields);
     inputFinder.inputBitSet.addAll(fieldsUsed);
@@ -513,9 +512,8 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
 
     final RelCollation newCollation =
         sort.getTraitSet().canonize(RexUtil.apply(inputMapping, collation));
-    final RelTraitSet newTraitSet = sort.getTraitSet().replace(newCollation);
-    final RelNode newSort = sortFactory.createSort(
-        newTraitSet, newInput, newCollation, sort.offset, sort.fetch);
+    final RelNode newSort =
+        sortFactory.createSort(newInput, newCollation, sort.offset, sort.fetch);
 
     // The result has the same mapping as the input gave us. Sometimes we
     // return fields that the consumer didn't ask for, because the filter
@@ -539,7 +537,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
 
     // Add in fields used in the condition.
     final Set<RelDataTypeField> combinedInputExtraFields =
-        new LinkedHashSet<RelDataTypeField>(extraFields);
+        new LinkedHashSet<>(extraFields);
     RelOptUtil.InputFinder inputFinder =
         new RelOptUtil.InputFinder(combinedInputExtraFields);
     inputFinder.inputBitSet.addAll(fieldsUsed);
@@ -563,9 +561,9 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     int offset = systemFieldCount;
     int changeCount = 0;
     int newFieldCount = newSystemFieldCount;
-    List<RelNode> newInputs = new ArrayList<RelNode>(2);
-    List<Mapping> inputMappings = new ArrayList<Mapping>();
-    List<Integer> inputExtraFieldCounts = new ArrayList<Integer>();
+    final List<RelNode> newInputs = new ArrayList<>(2);
+    final List<Mapping> inputMappings = new ArrayList<>();
+    final List<Integer> inputExtraFieldCounts = new ArrayList<>();
     for (RelNode input : join.getInputs()) {
       final RelDataType inputRowType = input.getRowType();
       final int inputFieldCount = inputRowType.getFieldCount();
@@ -688,7 +686,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     final Mapping mapping = createMapping(fieldsUsed, fieldCount);
 
     // Create input with trimmed columns.
-    final List<RelNode> newInputs = new ArrayList<RelNode>();
+    final List<RelNode> newInputs = new ArrayList<>();
     for (RelNode input : setOp.getInputs()) {
       TrimResult trimResult =
           trimChild(setOp, input, fieldsUsed, extraFields);
@@ -914,7 +912,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
       Set<RelDataTypeField> extraFields) {
     final RelDataType rowType = tabFun.getRowType();
     final int fieldCount = rowType.getFieldCount();
-    List<RelNode> newInputs = new ArrayList<RelNode>();
+    final List<RelNode> newInputs = new ArrayList<>();
 
     for (RelNode input : tabFun.getInputs()) {
       final int inputFieldCount = input.getRowType().getFieldCount();

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/main/java/org/apache/calcite/tools/Frameworks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/Frameworks.java b/core/src/main/java/org/apache/calcite/tools/Frameworks.java
index a9efcc0..c73a1e2 100644
--- a/core/src/main/java/org/apache/calcite/tools/Frameworks.java
+++ b/core/src/main/java/org/apache/calcite/tools/Frameworks.java
@@ -101,12 +101,14 @@ public class Frameworks {
    * @return Return value from action
    */
   public static <R> R withPlanner(final PlannerAction<R> action, //
-      FrameworkConfig config) {
+      final FrameworkConfig config) {
     return withPrepare(
         new Frameworks.PrepareAction<R>(config) {
           public R apply(RelOptCluster cluster, RelOptSchema relOptSchema,
               SchemaPlus rootSchema, CalciteServerStatement statement) {
-            return action.apply(cluster, relOptSchema, rootSchema);
+            final CalciteSchema schema =
+                CalciteSchema.from(config.getDefaultSchema());
+            return action.apply(cluster, relOptSchema, schema.root().plus());
           }
         });
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/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
new file mode 100644
index 0000000..ce185d8
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -0,0 +1,958 @@
+/*
+ * 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.tools;
+
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.core.Sort;
+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.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.server.CalciteServerStatement;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.NlsString;
+import org.apache.calcite.util.Stacks;
+import org.apache.calcite.util.Util;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import java.math.BigDecimal;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builder for relational expressions.
+ *
+ * <p>{@code RelBuilder} does not make possible anything that you could not
+ * also accomplish by calling the factory methods of the particular relational
+ * expression. But it makes common tasks more straightforward and concise.
+ *
+ * <p>{@code RelBuilder} uses factories to create relational expressions.
+ * By default, it uses the default factories, which create logical relational
+ * expressions ({@link org.apache.calcite.rel.logical.LogicalFilter},
+ * {@link org.apache.calcite.rel.logical.LogicalProject} and so forth).
+ * But you could override those factories so that, say, {@code filter} creates
+ * instead a {@code HiveFilter}.
+ *
+ * <p>It is not thread-safe.
+ */
+public class RelBuilder {
+  private static final Function<RexNode, String> FN_TYPE =
+      new Function<RexNode, String>() {
+        public String apply(RexNode input) {
+          return input + ": " + input.getType();
+        }
+      };
+
+  private final RelOptCluster cluster;
+  private final RelOptSchema relOptSchema;
+  private final RelFactories.FilterFactory filterFactory;
+  private final RelFactories.ProjectFactory projectFactory;
+  private final RelFactories.AggregateFactory aggregateFactory;
+  private final RelFactories.SortFactory sortFactory;
+  private final RelFactories.SetOpFactory setOpFactory;
+  private final RelFactories.JoinFactory joinFactory;
+  private final RelFactories.ValuesFactory valuesFactory;
+  private final RelFactories.TableScanFactory scanFactory;
+  private final List<RelNode> stack = new ArrayList<>();
+
+  private RelBuilder(Context context, RelOptCluster cluster,
+      RelOptSchema relOptSchema) {
+    this.cluster = cluster;
+    this.relOptSchema = relOptSchema;
+    if (context == null) {
+      context = Contexts.EMPTY_CONTEXT;
+    }
+    this.aggregateFactory =
+        Util.first(context.unwrap(RelFactories.AggregateFactory.class),
+            RelFactories.DEFAULT_AGGREGATE_FACTORY);
+    this.filterFactory =
+        Util.first(context.unwrap(RelFactories.FilterFactory.class),
+            RelFactories.DEFAULT_FILTER_FACTORY);
+    this.projectFactory =
+        Util.first(context.unwrap(RelFactories.ProjectFactory.class),
+            RelFactories.DEFAULT_PROJECT_FACTORY);
+    this.sortFactory =
+        Util.first(context.unwrap(RelFactories.SortFactory.class),
+            RelFactories.DEFAULT_SORT_FACTORY);
+    this.setOpFactory =
+        Util.first(context.unwrap(RelFactories.SetOpFactory.class),
+            RelFactories.DEFAULT_SET_OP_FACTORY);
+    this.joinFactory =
+        Util.first(context.unwrap(RelFactories.JoinFactory.class),
+            RelFactories.DEFAULT_JOIN_FACTORY);
+    this.valuesFactory =
+        Util.first(context.unwrap(RelFactories.ValuesFactory.class),
+            RelFactories.DEFAULT_VALUES_FACTORY);
+    this.scanFactory =
+        Util.first(context.unwrap(RelFactories.TableScanFactory.class),
+            RelFactories.DEFAULT_TABLE_SCAN_FACTORY);
+  }
+
+  /** Creates a RelBuilder. */
+  public static RelBuilder create(FrameworkConfig config) {
+    final RelOptCluster[] clusters = {null};
+    final RelOptSchema[] relOptSchemas = {null};
+    Frameworks.withPrepare(
+        new Frameworks.PrepareAction<Void>(config) {
+          public Void apply(RelOptCluster cluster, RelOptSchema relOptSchema,
+              SchemaPlus rootSchema, CalciteServerStatement statement) {
+            clusters[0] = cluster;
+            relOptSchemas[0] = relOptSchema;
+            return null;
+          }
+        });
+    return new RelBuilder(config.getContext(), clusters[0], relOptSchemas[0]);
+  }
+
+  /** Returns the type factory. */
+  public RelDataTypeFactory getTypeFactory() {
+    return cluster.getTypeFactory();
+  }
+
+  /** Creates a {@link ProtoRelBuilder}, a partially-created RelBuilder.
+   * Just add a {@link RelOptCluster} and a {@link RelOptSchema} */
+  public static ProtoRelBuilder proto(final Context context) {
+    return new ProtoRelBuilder() {
+      public RelBuilder create(RelOptCluster cluster, RelOptSchema schema) {
+        return new RelBuilder(context, cluster, schema);
+      }
+    };
+  }
+
+  // Methods for manipulating the stack
+
+  /** Adds a relational expression to be the input to the next relational
+   * expression constructed.
+   *
+   * <p>This method is usual when you want to weave in relational expressions
+   * that are not supported by the builder. If, while creating such expressions,
+   * you need to use previously built expressions as inputs, call
+   * {@link #build()} to pop those inputs. */
+  public RelBuilder push(RelNode node) {
+    Stacks.push(stack, node);
+    return this;
+  }
+
+  /** Returns the final relational expression.
+   *
+   * <p>Throws if the stack is empty.
+   */
+  public RelNode build() {
+    if (stack.size() < 1) {
+      throw new IllegalArgumentException("expected stack size 1, but was "
+          + stack.size() + ": " + stack);
+    }
+    return Stacks.pop(stack);
+  }
+
+  /** Returns the relational expression at the top of the stack, but does not
+   * remove it. */
+  public RelNode peek() {
+    return Stacks.peek(stack);
+  }
+
+  /** Returns the relational expression {@code n} positions from the top of the
+   * stack, but does not remove it. */
+  public RelNode peek(int n) {
+    return Stacks.peek(n, stack);
+  }
+
+  // Methods that return scalar expressions
+
+  /** Creates a literal (constant expression). */
+  public RexNode literal(Object value) {
+    final RexBuilder rexBuilder = cluster.getRexBuilder();
+    if (value == null) {
+      return rexBuilder.constantNull();
+    } else if (value instanceof Boolean) {
+      return rexBuilder.makeLiteral((Boolean) value);
+    } else if (value instanceof BigDecimal) {
+      return rexBuilder.makeExactLiteral((BigDecimal) value);
+    } else if (value instanceof Float || value instanceof Double) {
+      return rexBuilder.makeApproxLiteral(
+          BigDecimal.valueOf(((Number) value).doubleValue()));
+    } else if (value instanceof Number) {
+      return rexBuilder.makeExactLiteral(
+          BigDecimal.valueOf(((Number) value).longValue()));
+    } else if (value instanceof String) {
+      return rexBuilder.makeLiteral((String) value);
+    } else {
+      throw new IllegalArgumentException("cannot convert " + value
+          + " (" + value.getClass() + ") to a constant");
+    }
+  }
+
+  /** Creates a reference to a field by name.
+   *
+   * <p>Equivalent to {@code field(1, 0, fieldName)}.
+   *
+   * @param fieldName Field name
+   */
+  public RexInputRef field(String fieldName) {
+    return field(1, 0, fieldName);
+  }
+
+  /** Creates a reference to a field of given input relational expression
+   * by name.
+   *
+   * @param inputCount Number of inputs
+   * @param inputOrdinal Input ordinal
+   * @param fieldName Field name
+   */
+  public RexInputRef field(int inputCount, int inputOrdinal, String fieldName) {
+    final RelNode input = peek(inputCount - 1 - inputOrdinal);
+    final RelDataType rowType = input.getRowType();
+    final int ordinal = rowType.getFieldNames().indexOf(fieldName);
+    if (ordinal < 0) {
+      throw new IllegalArgumentException("field [" + fieldName
+          + "] not found; input fields are: " + rowType.getFieldNames());
+    }
+    return field(inputCount, inputOrdinal, ordinal);
+  }
+
+  /** Creates a reference to an input field by ordinal.
+   *
+   * <p>Equivalent to {@code field(1, 0, ordinal)}.
+   *
+   * @param fieldOrdinal Field ordinal
+   */
+  public RexInputRef field(int fieldOrdinal) {
+    return field(1, 0, fieldOrdinal);
+  }
+
+  /** Creates a reference to a field of a given input relational expression
+   * by ordinal.
+   *
+   * @param inputCount Number of inputs
+   * @param inputOrdinal Input ordinal
+   * @param fieldOrdinal Field ordinal within input
+   */
+  public RexInputRef field(int inputCount, int inputOrdinal, int fieldOrdinal) {
+    final RelNode input = peek(inputCount - 1 - inputOrdinal);
+    final RelDataType rowType = input.getRowType();
+    if (fieldOrdinal < 0 || fieldOrdinal > rowType.getFieldCount()) {
+      throw new IllegalArgumentException("field ordinal [" + fieldOrdinal
+          + "] out of range; input fields are: " + rowType.getFieldNames());
+    }
+    return cluster.getRexBuilder().makeInputRef(input, fieldOrdinal);
+  }
+
+  /** Creates a call to a scalar operator. */
+  public RexNode call(SqlOperator operator, RexNode... operands) {
+    final RexBuilder builder = cluster.getRexBuilder();
+    final List<RexNode> operandList = ImmutableList.copyOf(operands);
+    final RelDataType type = builder.deriveReturnType(operator, operandList);
+    if (type == null) {
+      throw new IllegalArgumentException("cannot derive type: " + operator
+          + "; operands: " + Lists.transform(operandList, FN_TYPE));
+    }
+    return builder.makeCall(type, operator, operandList);
+  }
+
+  /** Creates a call to a scalar operator. */
+  public RexNode call(SqlOperator operator,
+      Iterable<? extends RexNode> operands) {
+    return cluster.getRexBuilder().makeCall(operator,
+        ImmutableList.copyOf(operands));
+  }
+
+  /** Creates an AND. */
+  public RexNode and(RexNode... operands) {
+    return and(ImmutableList.copyOf(operands));
+  }
+
+  /** Creates an AND. */
+  public RexNode and(Iterable<? extends RexNode> operands) {
+    return RexUtil.composeConjunction(cluster.getRexBuilder(), operands, false);
+  }
+
+  /** Creates an OR. */
+  public RexNode or(RexNode... operands) {
+    return or(ImmutableList.copyOf(operands));
+  }
+
+  /** Creates an OR. */
+  public RexNode or(Iterable<? extends RexNode> operands) {
+    return RexUtil.composeDisjunction(cluster.getRexBuilder(), operands, false);
+  }
+
+  /** Creates a NOT. */
+  public RexNode not(RexNode operand) {
+    return call(SqlStdOperatorTable.NOT, operand);
+  }
+
+  /** Creates an =. */
+  public RexNode equals(RexNode operand0, RexNode operand1) {
+    return call(SqlStdOperatorTable.EQUALS, operand0, operand1);
+  }
+
+  /** Creates a IS NULL. */
+  public RexNode isNull(RexNode operand) {
+    return call(SqlStdOperatorTable.IS_NULL, operand);
+  }
+
+  /** Creates a IS NOT NULL. */
+  public RexNode isNotNull(RexNode operand) {
+    return call(SqlStdOperatorTable.IS_NOT_NULL, operand);
+  }
+
+  /** Creates an expression that casts an expression to a given type. */
+  public RexNode cast(RexNode expr, SqlTypeName typeName) {
+    final RelDataType type = cluster.getTypeFactory().createSqlType(typeName);
+    return cluster.getRexBuilder().makeCast(type, expr);
+  }
+
+  /** Creates an expression that casts an expression to a type with a given name
+   * and precision or length. */
+  public RexNode cast(RexNode expr, SqlTypeName typeName, int precision) {
+    final RelDataType type =
+        cluster.getTypeFactory().createSqlType(typeName, precision);
+    return cluster.getRexBuilder().makeCast(type, expr);
+  }
+
+  /** Creates an expression that casts an expression to a type with a given
+   * name, precision and scale. */
+  public RexNode cast(RexNode expr, SqlTypeName typeName, int precision,
+      int scale) {
+    final RelDataType type =
+        cluster.getTypeFactory().createSqlType(typeName, precision, scale);
+    return cluster.getRexBuilder().makeCast(type, expr);
+  }
+
+  /**
+   * Returns an expression wrapped in an alias.
+   *
+   * @see #project
+   */
+  public RexNode alias(RexNode expr, String alias) {
+    return call(SqlStdOperatorTable.AS, expr, literal(alias));
+  }
+
+  /** Converts a sort expression to descending. */
+  public RexNode desc(RexNode node) {
+    return call(SqlStdOperatorTable.DESC, node);
+  }
+
+  /** Converts a sort expression to nulls last. */
+  public RexNode nullsLast(RexNode node) {
+    return call(SqlStdOperatorTable.NULLS_LAST, node);
+  }
+
+  /** Converts a sort expression to nulls first. */
+  public RexNode nullsFirst(RexNode node) {
+    return call(SqlStdOperatorTable.NULLS_FIRST, node);
+  }
+
+  // Methods that create group keys and aggregate calls
+
+  /** Creates an empty group key. */
+  public GroupKey groupKey() {
+    return groupKey(ImmutableList.<RexNode>of());
+  }
+
+  /** Creates a group key. */
+  public GroupKey groupKey(RexNode... nodes) {
+    return groupKey(ImmutableList.copyOf(nodes));
+  }
+
+  /** Creates a group key. */
+  public GroupKey groupKey(Iterable<? extends RexNode> nodes) {
+    return new GroupKeyImpl(ImmutableList.copyOf(nodes));
+  }
+
+  /** Creates a group key of fields identified by ordinal. */
+  public GroupKey groupKey(int... fieldOrdinals) {
+    final ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
+    for (int fieldOrdinal : fieldOrdinals) {
+      builder.add(field(fieldOrdinal));
+    }
+    return groupKey(builder.build());
+  }
+
+  /** Creates a group key of fields identified by name. */
+  public GroupKey groupKey(String... fieldNames) {
+    final ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
+    for (String fieldName : fieldNames) {
+      builder.add(field(fieldName));
+    }
+    return groupKey(builder.build());
+  }
+
+  /** Creates a call to an aggregate function. */
+  public AggCall aggregateCall(SqlAggFunction aggFunction,
+      boolean distinct, String alias, RexNode... operands) {
+    return new AggCallImpl(aggFunction, distinct, alias,
+        ImmutableList.copyOf(operands));
+  }
+
+  /** Creates a call to the COUNT aggregate function. */
+  public AggCall count(boolean distinct, String alias, RexNode... operands) {
+    return aggregateCall(SqlStdOperatorTable.COUNT, distinct, alias, operands);
+  }
+
+  /** Creates a call to the COUNT(*) aggregate function. */
+  public AggCall countStar(String alias) {
+    return aggregateCall(SqlStdOperatorTable.COUNT, false, alias);
+  }
+
+  /** Creates a call to the SUM aggregate function. */
+  public AggCall sum(boolean distinct, String alias, RexNode operand) {
+    return aggregateCall(SqlStdOperatorTable.SUM, distinct, alias, operand);
+  }
+
+  /** Creates a call to the MIN aggregate function. */
+  public AggCall min(String alias, RexNode operand) {
+    return aggregateCall(SqlStdOperatorTable.MIN, false, alias, operand);
+  }
+
+  /** Creates a call to the MAX aggregate function. */
+  public AggCall max(String alias, RexNode operand) {
+    return aggregateCall(SqlStdOperatorTable.MAX, false, alias, operand);
+  }
+
+  // Methods that create relational expressions
+
+  /** Creates a {@link org.apache.calcite.rel.core.TableScan} of the table
+   * with a given name.
+   *
+   * <p>Throws if the table does not exist within the current schema.
+   *
+   * <p>Returns this builder.
+   *
+   * @param tableName Name of table
+   */
+  public RelBuilder scan(String tableName) {
+    final RelOptTable relOptTable =
+        relOptSchema.getTableForMember(ImmutableList.of(tableName));
+    final RelNode scan = scanFactory.createScan(cluster, relOptTable);
+    push(scan);
+    return this;
+  }
+
+  /** Creates a {@link org.apache.calcite.rel.core.Filter} of an array of
+   * predicates.
+   *
+   * <p>The predicates are combined using AND,
+   * and optimized in a similar way to the {@link #and} method.
+   * If the result is TRUE no filter is created. */
+  public RelBuilder filter(RexNode... predicates) {
+    return filter(ImmutableList.copyOf(predicates));
+  }
+
+  /** Creates a {@link org.apache.calcite.rel.core.Filter} of a list of
+   * predicates.
+   *
+   * <p>The predicates are combined using AND,
+   * and optimized in a similar way to the {@link #and} method.
+   * If the result is TRUE no filter is created. */
+  public RelBuilder filter(Iterable<? extends RexNode> predicates) {
+    final RexNode x = RexUtil.composeConjunction(cluster.getRexBuilder(),
+        predicates, true);
+    if (x != null) {
+      final RelNode filter = filterFactory.createFilter(build(), x);
+      push(filter);
+    }
+    return this;
+  }
+
+  /** Creates a {@link org.apache.calcite.rel.core.Project} of the given list
+   * of expressions.
+   *
+   * <p>Infers all field names.
+   * If an expression projects an input field,
+   * or is a cast an input field,
+   * uses the input field name.
+   * If an expression is a call to
+   * {@link org.apache.calcite.sql.fun.SqlStdOperatorTable#AS}
+   * (see {@link #alias}), removes the
+   * call but uses the intended alias.
+   * After the field names have been inferred, makes the
+   * field names unique by appending numeric suffixes. */
+  public RelBuilder project(List<RexNode> nodes) {
+    final List<String> names = new ArrayList<>();
+    final List<RexNode> exprList = Lists.newArrayList(nodes);
+    for (RexNode node : nodes) {
+      names.add(inferAlias(exprList, node));
+    }
+    final RelNode project =
+        projectFactory.createProject(build(), ImmutableList.copyOf(exprList),
+            names);
+    push(project);
+    return this;
+  }
+
+  /** Creates a {@link org.apache.calcite.rel.core.Project} of the given
+   * expressions. */
+  public RelBuilder project(RexNode... nodes) {
+    return project(ImmutableList.copyOf(nodes));
+  }
+
+  /** Infers the alias of an expression.
+   *
+   * <p>If the expression was created by {@link #alias}, replaces the expression
+   * in the project list.
+   */
+  private String inferAlias(List<RexNode> exprList, RexNode expr) {
+    switch (expr.getKind()) {
+    case INPUT_REF:
+      final RexInputRef ref = (RexInputRef) expr;
+      return peek(0).getRowType().getFieldNames().get(ref.getIndex());
+    case CAST:
+      return inferAlias(exprList, ((RexCall) expr).getOperands().get(0));
+    case AS:
+      final RexCall call = (RexCall) expr;
+      for (;;) {
+        final int i = exprList.indexOf(expr);
+        if (i < 0) {
+          break;
+        }
+        exprList.set(i, call.getOperands().get(0));
+      }
+      return ((NlsString) ((RexLiteral) call.getOperands().get(1)).getValue())
+          .getValue();
+    default:
+      return null;
+    }
+  }
+
+  /** Creates an {@link org.apache.calcite.rel.core.Aggregate} that makes the
+   * relational expression distinct on all fields. */
+  public RelBuilder distinct() {
+    return aggregate(groupKey());
+  }
+
+  /** Creates an {@link org.apache.calcite.rel.core.Aggregate} with an array of
+   * calls. */
+  public RelBuilder aggregate(GroupKey groupKey, AggCall... aggCalls) {
+    return aggregate(groupKey, ImmutableList.copyOf(aggCalls));
+  }
+
+  /** Creates an {@link org.apache.calcite.rel.core.Aggregate} with a list of
+   * calls. */
+  public RelBuilder aggregate(GroupKey groupKey, Iterable<AggCall> aggCalls) {
+    final ImmutableBitSet.Builder builder = ImmutableBitSet.builder();
+    final RelDataType inputRowType = peek().getRowType();
+    final List<RexNode> extraNodes = projects(inputRowType);
+    for (RexNode node : ((GroupKeyImpl) groupKey).nodes) {
+      builder.set(registerExpression(extraNodes, node));
+    }
+    final ImmutableBitSet groupSet = builder.build();
+    for (AggCall aggCall : aggCalls) {
+      final AggCallImpl aggCall1 = (AggCallImpl) aggCall;
+      for (RexNode operand : aggCall1.operands) {
+        registerExpression(extraNodes, operand);
+      }
+    }
+    if (extraNodes.size() > inputRowType.getFieldCount()) {
+      project(extraNodes);
+    }
+    final RelNode r = build();
+    final List<AggregateCall> aggregateCalls = new ArrayList<>();
+    for (AggCall aggCall : aggCalls) {
+      final List<Integer> args = new ArrayList<>();
+      final AggCallImpl aggCall1 = (AggCallImpl) aggCall;
+      for (RexNode operand : aggCall1.operands) {
+        args.add(registerExpression(extraNodes, operand));
+      }
+      aggregateCalls.add(
+          AggregateCall.create(aggCall1.aggFunction, aggCall1.distinct,
+              args, -1, groupSet.cardinality(), r, null, aggCall1.alias));
+    }
+
+    RelNode aggregate = aggregateFactory.createAggregate(r, false, groupSet,
+        ImmutableList.of(groupSet), aggregateCalls);
+    push(aggregate);
+    return this;
+  }
+
+  private List<RexNode> projects(RelDataType inputRowType) {
+    final List<RexNode> exprList = new ArrayList<>();
+    for (RelDataTypeField field : inputRowType.getFieldList()) {
+      final RexBuilder rexBuilder = cluster.getRexBuilder();
+      exprList.add(rexBuilder.makeInputRef(field.getType(), field.getIndex()));
+    }
+    return exprList;
+  }
+
+  private static int registerExpression(List<RexNode> exprList, RexNode node) {
+    int i = exprList.indexOf(node);
+    if (i < 0) {
+      i = exprList.size();
+      exprList.add(node);
+    }
+    return i;
+  }
+
+  /** Creates a {@link org.apache.calcite.rel.core.Union} of the two most recent
+   * relational expressions on the stack.
+   *
+   * @param all Whether to create UNION ALL
+   */
+  public RelBuilder union(boolean all) {
+    final RelNode left = build();
+    final RelNode right = build();
+    final RelNode union = setOpFactory.createSetOp(SqlKind.UNION,
+        ImmutableList.of(left, right), all);
+    push(union);
+    return this;
+  }
+
+  /** Creates an {@link org.apache.calcite.rel.core.Intersect} of the two most
+   * recent relational expressions on the stack.
+   *
+   * @param all Whether to create INTERSECT ALL
+   */
+  public RelBuilder intersect(boolean all) {
+    final RelNode left = build();
+    final RelNode right = build();
+    final RelNode intersect = setOpFactory.createSetOp(SqlKind.INTERSECT,
+        ImmutableList.of(left, right), all);
+    push(intersect);
+    return this;
+  }
+
+  /** Creates a {@link org.apache.calcite.rel.core.Minus} of the two most recent
+   * relational expressions on the stack.
+   *
+   * @param all Whether to create EXCEPT ALL
+   */
+  public RelBuilder minus(boolean all) {
+    final RelNode left = build();
+    final RelNode right = build();
+    final RelNode except = setOpFactory.createSetOp(SqlKind.EXCEPT,
+        ImmutableList.of(left, right), all);
+    push(except);
+    return this;
+  }
+
+  /** Creates a {@link org.apache.calcite.rel.core.Join}. */
+  public RelBuilder join(JoinRelType joinType, RexNode condition) {
+    final RelNode left = build();
+    final RelNode right = build();
+    final RelNode join = joinFactory.createJoin(left, right, condition,
+        joinType, ImmutableSet.<String>of(), false);
+    push(join);
+    return this;
+  }
+
+  /** Creates a {@link org.apache.calcite.rel.core.Join} using USING syntax.
+   *
+   * <p>For each of the field names, both left and right inputs must have a
+   * field of that name. Constructs a join condition that the left and right
+   * fields are equal.
+   *
+   * @param joinType Join type
+   * @param fieldNames Field names
+   */
+  public RelBuilder join(JoinRelType joinType, String... fieldNames) {
+    final List<RexNode> conditions = new ArrayList<>();
+    for (String fieldName : fieldNames) {
+      conditions.add(
+          call(SqlStdOperatorTable.EQUALS,
+              field(2, 0, fieldName),
+              field(2, 1, fieldName)));
+    }
+    final RexNode condition =
+        RexUtil.composeConjunction(cluster.getRexBuilder(), conditions, false);
+    return join(joinType, condition);
+  }
+
+  /** Creates a {@link Values}.
+   *
+   * <p>The {@code values} array must have the same number of entries as
+   * {@code fieldNames}, or an integer multiple if you wish to create multiple
+   * rows.
+   *
+   * <p>If there are zero rows, or if all values of a any column are
+   * null, this method cannot deduce the type of columns. For these cases,
+   * call {@link #values(RelDataType, Iterable)}.
+   *
+   * @param fieldNames Field names
+   * @param values Values
+   */
+  public RelBuilder values(String[] fieldNames, Object... values) {
+    if (fieldNames == null
+        || fieldNames.length == 0
+        || values.length % fieldNames.length != 0
+        || values.length < fieldNames.length) {
+      throw new IllegalArgumentException(
+          "Value count must be a positive multiple of field count");
+    }
+    final int rowCount = values.length / fieldNames.length;
+    for (Ord<String> fieldName : Ord.zip(fieldNames)) {
+      if (allNull(values, fieldName.i, fieldNames.length)) {
+        throw new IllegalArgumentException("All values of field '" + fieldName.e
+            + "' are null; cannot deduce type");
+      }
+    }
+    final ImmutableList<ImmutableList<RexLiteral>> tupleList =
+        tupleList(fieldNames.length, values);
+    final RelDataTypeFactory.FieldInfoBuilder rowTypeBuilder =
+        cluster.getTypeFactory().builder();
+    for (final Ord<String> fieldName : Ord.zip(fieldNames)) {
+      final String name =
+          fieldName.e != null ? fieldName.e : "expr$" + fieldName.i;
+      final RelDataType type = cluster.getTypeFactory().leastRestrictive(
+          new AbstractList<RelDataType>() {
+            public RelDataType get(int index) {
+              return tupleList.get(index).get(fieldName.i).getType();
+            }
+
+            public int size() {
+              return rowCount;
+            }
+          });
+      rowTypeBuilder.add(name, type);
+    }
+    final RelDataType rowType = rowTypeBuilder.build();
+    return values(rowType, tupleList);
+  }
+
+  private ImmutableList<ImmutableList<RexLiteral>> tupleList(int columnCount,
+      Object[] values) {
+    final ImmutableList.Builder<ImmutableList<RexLiteral>> listBuilder =
+        ImmutableList.builder();
+    final List<RexLiteral> valueList = new ArrayList<>();
+    for (int i = 0; i < values.length; i++) {
+      Object value = values[i];
+      valueList.add((RexLiteral) literal(value));
+      if ((i + 1) % columnCount == 0) {
+        listBuilder.add(ImmutableList.copyOf(valueList));
+        valueList.clear();
+      }
+    }
+    return listBuilder.build();
+  }
+
+  /** Returns whether all values for a given column are null. */
+  private boolean allNull(Object[] values, int column, int columnCount) {
+    for (int i = column; i < values.length; i += columnCount) {
+      if (values[i] != null) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /** Creates a {@link Values} with a specified row type.
+   *
+   * <p>This method can handle cases that {@link #values(String[], Object...)}
+   * cannot, such as all values of a column being null, or there being zero
+   * rows.
+   *
+   * @param rowType Row type
+   * @param columnValues Values
+   */
+  public RelBuilder values(RelDataType rowType, Object... columnValues) {
+    final ImmutableList<ImmutableList<RexLiteral>> tupleList =
+        tupleList(rowType.getFieldCount(), columnValues);
+    RelNode values = valuesFactory.createValues(cluster, rowType,
+        ImmutableList.copyOf(tupleList));
+    push(values);
+    return this;
+  }
+
+  /** Creates a {@link Values} with a specified row type.
+   *
+   * <p>This method can handle cases that {@link #values(String[], Object...)}
+   * cannot, such as all values of a column being null, or there being zero
+   * rows.
+   *
+   * @param rowType Row type
+   * @param tupleList Tuple list
+   */
+  protected RelBuilder values(RelDataType rowType,
+      Iterable<ImmutableList<RexLiteral>> tupleList) {
+    RelNode values = valuesFactory.createValues(cluster, rowType,
+        ImmutableList.copyOf(tupleList));
+    push(values);
+    return this;
+  }
+
+  /** Creates a limit without a sort. */
+  public RelBuilder limit(int offset, int fetch) {
+    return sortLimit(offset, fetch, ImmutableList.<RexNode>of());
+  }
+
+  /** Creates a {@link Sort} by field ordinals.
+   *
+   * <p>Negative fields mean descending: -1 means field(0) descending,
+   * -2 means field(1) descending, etc.
+   */
+  public RelBuilder sort(int... fields) {
+    final ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
+    for (int field : fields) {
+      builder.add(field < 0 ? desc(field(-field - 1)) : field(field));
+    }
+    return sortLimit(-1, -1, builder.build());
+  }
+
+  /** Creates a {@link Sort} by expressions. */
+  public RelBuilder sort(RexNode... nodes) {
+    return sortLimit(-1, -1, ImmutableList.copyOf(nodes));
+  }
+
+  /** Creates a {@link Sort} by expressions. */
+  public RelBuilder sort(Iterable<? extends RexNode> nodes) {
+    return sortLimit(-1, -1, nodes);
+  }
+
+  /** Creates a {@link Sort} by expressions, with limit and offset. */
+  public RelBuilder sortLimit(int offset, int fetch, RexNode... nodes) {
+    return sortLimit(offset, fetch, ImmutableList.copyOf(nodes));
+  }
+
+  /** Creates a {@link Sort} by a list of expressions, with limit and offset.
+   *
+   * @param offset Number of rows to skip; non-positive means don't skip any
+   * @param fetch Maximum number of rows to fetch; negative means no limit
+   * @param nodes Sort expressions
+   */
+  public RelBuilder sortLimit(int offset, int fetch,
+      Iterable<? extends RexNode> nodes) {
+    final List<RelFieldCollation> fieldCollations = new ArrayList<>();
+    final RelDataType inputRowType = peek().getRowType();
+    final List<RexNode> extraNodes = projects(inputRowType);
+    final List<RexNode> originalExtraNodes = ImmutableList.copyOf(extraNodes);
+    for (RexNode node : nodes) {
+      fieldCollations.add(
+          collation(node, RelFieldCollation.Direction.ASCENDING,
+              RelFieldCollation.NullDirection.UNSPECIFIED, extraNodes));
+    }
+    final RexNode offsetNode = offset <= 0 ? null : literal(offset);
+    final RexNode fetchNode = fetch < 0 ? null : literal(fetch);
+    if (extraNodes.size() > inputRowType.getFieldCount()) {
+      project(extraNodes);
+    }
+    final RelNode sort =
+        sortFactory.createSort(build(), RelCollations.of(fieldCollations),
+            offsetNode, fetchNode);
+    push(sort);
+    if (extraNodes.size() > inputRowType.getFieldCount()) {
+      project(originalExtraNodes);
+    }
+    return this;
+  }
+
+  private static RelFieldCollation collation(RexNode node,
+      RelFieldCollation.Direction direction,
+      RelFieldCollation.NullDirection nullDirection, List<RexNode> extraNodes) {
+    switch (node.getKind()) {
+    case INPUT_REF:
+      return new RelFieldCollation(((RexInputRef) node).getIndex(),
+          direction, nullDirection);
+    case DESCENDING:
+      return collation(((RexCall) node).getOperands().get(0),
+          RelFieldCollation.Direction.DESCENDING,
+          nullDirection, extraNodes);
+    case NULLS_FIRST:
+      return collation(((RexCall) node).getOperands().get(0), direction,
+          RelFieldCollation.NullDirection.FIRST, extraNodes);
+    case NULLS_LAST:
+      return collation(((RexCall) node).getOperands().get(0), direction,
+          RelFieldCollation.NullDirection.LAST, extraNodes);
+    default:
+      final int fieldIndex = extraNodes.size();
+      extraNodes.add(node);
+      return new RelFieldCollation(fieldIndex, direction, nullDirection);
+    }
+  }
+
+  /** Information necessary to create a call to an aggregate function.
+   *
+   * @see RelBuilder#aggregateCall */
+  public interface AggCall {
+  }
+
+  /** Information necessary to create the GROUP BY clause of an Aggregate.
+   *
+   * @see RelBuilder#groupKey */
+  public interface GroupKey {
+  }
+
+  /** Implementation of {@link RelBuilder.GroupKey}. */
+  private static class GroupKeyImpl implements GroupKey {
+    private final ImmutableList<RexNode> nodes;
+
+    GroupKeyImpl(ImmutableList<RexNode> nodes) {
+      this.nodes = nodes;
+    }
+  }
+
+  /** Implementation of {@link RelBuilder.AggCall}. */
+  private static class AggCallImpl implements AggCall {
+    private final SqlAggFunction aggFunction;
+    private final boolean distinct;
+    private final String alias;
+    private final ImmutableList<RexNode> operands;
+
+    public AggCallImpl(SqlAggFunction aggFunction, boolean distinct,
+        String alias, ImmutableList<RexNode> operands) {
+      this.aggFunction = aggFunction;
+      this.distinct = distinct;
+      this.alias = alias;
+      this.operands = operands;
+    }
+  }
+
+  /** A partially-created RelBuilder.
+   *
+   * <p>Add a cluster, and optionally a schema,
+   * when you want to create a builder.
+   *
+   * <p>A {@code ProtoRelBuilder} can be shared among queries, and thus can
+   * be inside a {@link RelOptRule}. It is a nice way to encapsulate the policy
+   * that this particular rule instance should create {@code DrillFilter}
+   * and {@code DrillProject} versus {@code HiveFilter} and {@code HiveProject}.
+   *
+   * @see RelFactories#DEFAULT_PROTO
+   */
+  public interface ProtoRelBuilder {
+    RelBuilder create(RelOptCluster cluster, RelOptSchema schema);
+  }
+}
+
+// End RelBuilder.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/main/java/org/apache/calcite/util/Stacks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Stacks.java b/core/src/main/java/org/apache/calcite/util/Stacks.java
index 70ef045..2a41368 100644
--- a/core/src/main/java/org/apache/calcite/util/Stacks.java
+++ b/core/src/main/java/org/apache/calcite/util/Stacks.java
@@ -34,6 +34,14 @@ public class Stacks {
   }
 
   /**
+   * Returns the {@code n}th most recently added element in the stack.
+   * Throws if the stack is empty.
+   */
+  public static <T> T peek(int n, List<T> stack) {
+    return stack.get(stack.size() - n - 1);
+  }
+
+  /**
    * Adds an element to the stack.
    */
   public static <T> void push(List<T> stack, T element) {
@@ -48,6 +56,15 @@ public class Stacks {
     assert stack.get(stack.size() - 1) == element;
     stack.remove(stack.size() - 1);
   }
+
+  /**
+   * Removes an element from the stack and returns it.
+   *
+   * <p>Throws if the stack is empty.
+   */
+  public static <T> T pop(List<T> stack) {
+    return stack.remove(stack.size() - 1);
+  }
 }
 
 // End Stacks.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/test/java/org/apache/calcite/examples/RelBuilderExample.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/examples/RelBuilderExample.java b/core/src/test/java/org/apache/calcite/examples/RelBuilderExample.java
new file mode 100644
index 0000000..12801e8
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/examples/RelBuilderExample.java
@@ -0,0 +1,171 @@
+/*
+ * 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.examples;
+
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.test.RelBuilderTest;
+import org.apache.calcite.tools.FrameworkConfig;
+import org.apache.calcite.tools.RelBuilder;
+
+/**
+ * Example that uses {@link org.apache.calcite.tools.RelBuilder}
+ * to create various relational expressions.
+ */
+public class RelBuilderExample {
+  private final boolean verbose;
+
+  public RelBuilderExample(boolean verbose) {
+    this.verbose = verbose;
+  }
+
+  public static void main(String[] args) {
+    new RelBuilderExample(true).runAllExamples();
+  }
+
+  public void runAllExamples() {
+    // Create a builder. The config contains a schema mapped
+    // to the SCOTT database, with tables EMP and DEPT.
+    final FrameworkConfig config = RelBuilderTest.config().build();
+    final RelBuilder builder = RelBuilder.create(config);
+    for (int i = 0; i < 4; i++) {
+      doExample(builder, i);
+      final RelNode node = builder.build();
+      if (verbose) {
+        System.out.println(RelOptUtil.toString(node));
+      }
+    }
+  }
+
+  private RelBuilder doExample(RelBuilder builder, int i) {
+    switch (i) {
+    case 0:
+      return example0(builder);
+    case 1:
+      return example1(builder);
+    case 2:
+      return example2(builder);
+    case 3:
+      return example3(builder);
+    case 4:
+      return example4(builder);
+    default:
+      throw new AssertionError("unknown example " + i);
+    }
+  }
+
+  /**
+   * Creates a relational expression for a table scan.
+   * It is equivalent to
+   *
+   * <pre>
+   * SELECT *
+   * FROM emp</pre>
+   */
+  private RelBuilder example0(RelBuilder builder) {
+    return builder
+        .values(new String[] {"a", "b"}, 1, true, null, false);
+  }
+
+  /**
+   * Creates a relational expression for a table scan.
+   * It is equivalent to
+   *
+   * <pre>
+   * SELECT *
+   * FROM emp</pre>
+   */
+  private RelBuilder example1(RelBuilder builder) {
+    return builder
+        .scan("EMP");
+  }
+
+  /**
+   * Creates a relational expression for a table scan and project.
+   * It is equivalent to
+   *
+   * <pre>
+   * SELECT deptno, ename
+   * FROM emp</pre>
+   */
+  private RelBuilder example2(RelBuilder builder) {
+    return builder
+        .scan("EMP")
+        .project(builder.field("DEPTNO"), builder.field("ENAME"));
+  }
+
+  /**
+   * Creates a relational expression for a table scan, aggregate, filter.
+   * It is equivalent to
+   *
+   * <pre>
+   * SELECT deptno, count(*) AS c, sum(sal) AS s
+   * FROM emp
+   * GROUP BY deptno
+   * HAVING count(*) > 10</pre>
+   */
+  private RelBuilder example3(RelBuilder builder) {
+    return builder
+        .scan("EMP")
+        .aggregate(builder.groupKey("DEPTNO"),
+            builder.count(false, "C"),
+            builder.sum(false, "S", builder.field("SAL")))
+        .filter(
+            builder.call(SqlStdOperatorTable.GREATER_THAN, builder.field("C"),
+                builder.literal(10)));
+  }
+
+  /**
+   * Sometimes the stack becomes so deeply nested it gets confusing. To keep
+   * things straight, you can remove expressions from the stack. For example,
+   * here we are building a bushy join:
+   *
+   * <pre>
+   *                join
+   *              /      \
+   *         join          join
+   *       /      \      /      \
+   * CUSTOMERS ORDERS LINE_ITEMS PRODUCTS
+   * </pre>
+   *
+   * <p>We build it in three stages. Store the intermediate results in variables
+   * `left` and `right`, and use `push()` to put them back on the stack when it
+   * is time to create the final `Join`.
+   */
+  private RelBuilder example4(RelBuilder builder) {
+    final RelNode left = builder
+        .scan("CUSTOMERS")
+        .scan("ORDERS")
+        .join(JoinRelType.INNER, "ORDER_ID")
+        .build();
+
+    final RelNode right = builder
+        .scan("LINE_ITEMS")
+        .scan("PRODUCTS")
+        .join(JoinRelType.INNER, "PRODUCT_ID")
+        .build();
+
+    return builder
+        .push(left)
+        .push(right)
+        .join(JoinRelType.INNER, "ORDER_ID");
+  }
+}
+
+// End RelBuilderExample.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/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 cad237e..5a87733 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
@@ -113,6 +113,7 @@ import org.junit.runners.Suite;
 
     // slow tests (above 1s)
     PlannerTest.class,
+    RelBuilderTest.class,
     MaterializationTest.class,
     JdbcAdapterTest.class,
     LinqFrontJdbcBackTest.class,