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:09 UTC

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

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/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
new file mode 100644
index 0000000..ed351d5
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -0,0 +1,624 @@
+/*
+ * 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.test;
+
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelTraitDef;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.Correlate;
+import org.apache.calcite.rel.core.Exchange;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.TableFunctionScan;
+import org.apache.calcite.rel.core.TableModify;
+import org.apache.calcite.rel.core.Window;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.calcite.tools.Programs;
+import org.apache.calcite.tools.RelBuilder;
+
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * Unit test for {@link RelBuilder}.
+ *
+ * <p>Tasks:</p>
+ * <ol>
+ *   <li>Add RelBuilder.scan(List&lt;String&gt;)</li>
+ *   <li>Add RelBuilder.scan(Table)</li>
+ *   <li>Test that {@link RelBuilder#filter} does not create a filter if the
+ *   predicates optimize to true</li>
+ *   <li>Test that {@link RelBuilder#filter} DOES create a filter if the
+ *   predicates optimize to false. (Creating an empty Values seems too
+ *   devious.)</li>
+ *   <li>Test that {@link RelBuilder#scan} throws good error if table not
+ *   found</li>
+ *   <li>Test that {@link RelBuilder#scan} obeys case-sensitivity</li>
+ *   <li>Test that {@link RelBuilder#join(JoinRelType, String...)} obeys
+ *   case-sensitivity</li>
+ *   <li>Test RelBuilder with alternative factories</li>
+ *   <li>Test that {@link RelBuilder#field(String)} obeys case-sensitivity</li>
+ *   <li>Test case-insensitive unique field names</li>
+ *   <li>Test that an alias created using
+ *      {@link RelBuilder#alias(RexNode, String)} is removed if not a top-level
+ *      project</li>
+ *   <li>{@link RelBuilder#aggregate} with grouping sets</li>
+ *   <li>{@link RelBuilder#aggregateCall} with filter</li>
+ *   <li>Add call to create {@link TableFunctionScan}</li>
+ *   <li>Add call to create {@link Window}</li>
+ *   <li>Add call to create {@link TableModify}</li>
+ *   <li>Add call to create {@link Exchange}</li>
+ *   <li>Add call to create {@link Correlate}</li>
+ *   <li>Add call to create {@link AggregateCall} with filter</li>
+ * </ol>
+ */
+public class RelBuilderTest {
+  /** Creates a config based on the "scott" schema. */
+  public static Frameworks.ConfigBuilder config() {
+    final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    return Frameworks.newConfigBuilder()
+        .parserConfig(SqlParser.Config.DEFAULT)
+        .defaultSchema(
+            CalciteAssert.addSchema(rootSchema, CalciteAssert.SchemaSpec.SCOTT))
+        .traitDefs((List<RelTraitDef>) null)
+        .programs(Programs.heuristicJoinOrder(Programs.RULE_SET, true, 2));
+  }
+
+  @Test public void testScan() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM emp
+    final RelNode root =
+        RelBuilder.create(config().build())
+            .scan("EMP")
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testScanFilterTrue() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM emp
+    //   WHERE TRUE
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .filter(builder.literal(true))
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testScanFilterEquals() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM emp
+    //   WHERE deptno = 20
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .filter(
+                builder.equals(builder.field("DEPTNO"), builder.literal(20)))
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalFilter(condition=[=($7, 20)])\n"
+            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testScanFilterOr() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM emp
+    //   WHERE (deptno = 20 OR comm IS NULL) AND mgr IS NOT NULL
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .filter(
+                builder.call(SqlStdOperatorTable.OR,
+                    builder.call(SqlStdOperatorTable.EQUALS,
+                        builder.field("DEPTNO"),
+                        builder.literal(20)),
+                    builder.isNull(builder.field(6))),
+                builder.isNotNull(builder.field(3)))
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalFilter(condition=[AND(OR(=($7, 20), IS NULL($6)), IS NOT NULL($3))])\n"
+            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testBadFieldName() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    try {
+      RexInputRef ref = builder.scan("EMP").field("deptno");
+      fail("expected error, got " + ref);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          is("field [deptno] not found; input fields are: [EMPNO, ENAME, JOB, "
+              + "MGR, HIREDATE, SAL, COMM, DEPTNO]"));
+    }
+  }
+
+  @Test public void testBadFieldOrdinal() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    try {
+      RexInputRef ref = builder.scan("DEPT").field(20);
+      fail("expected error, got " + ref);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          is("field ordinal [20] out of range; "
+                  + "input fields are: [DEPTNO, DNAME, LOC]"));
+    }
+  }
+
+  @Test public void testBadType() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    try {
+      builder.scan("EMP");
+      RexNode call = builder.call(SqlStdOperatorTable.PLUS, builder.field(1),
+          builder.field(3));
+      fail("expected error, got " + call);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          is("cannot derive type: +; "
+              + "operands: [$1: VARCHAR(10), $3: SMALLINT]"));
+    }
+  }
+
+  @Test public void testProject() {
+    // Equivalent SQL:
+    //   SELECT deptno, CAST(comm AS SMALLINT) AS comm, 20 AS $f2,
+    //     comm AS comm3, comm AS c
+    //   FROM emp
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .project(builder.field("DEPTNO"),
+                builder.cast(builder.field(6), SqlTypeName.SMALLINT),
+                builder.literal(20),
+                builder.field(6),
+                builder.alias(builder.field(6), "C"))
+            .build();
+    // Note: CAST(COMM) gets the COMM alias because it occurs first
+    // Note: AS(COMM, C) becomes just $6
+    assertThat(RelOptUtil.toString(root),
+        is(
+            "LogicalProject(DEPTNO=[$7], COMM=[CAST($6):SMALLINT NOT NULL], $f2=[20], COMM3=[$6], C=[$6])\n"
+            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  /** Tests each method that creates a scalar expression. */
+  @Test public void testProject2() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .project(builder.field("DEPTNO"),
+                builder.cast(builder.field(6), SqlTypeName.SMALLINT),
+                builder.or(
+                    builder.equals(builder.field("DEPTNO"),
+                        builder.literal(20)),
+                    builder.and(builder.literal(false),
+                        builder.equals(builder.field("DEPTNO"),
+                            builder.literal(10)),
+                        builder.and(builder.isNull(builder.field(6)),
+                            builder.not(builder.isNotNull(builder.field(7))))),
+                    builder.equals(builder.field("DEPTNO"),
+                        builder.literal(20)),
+                    builder.equals(builder.field("DEPTNO"),
+                        builder.literal(30))),
+                builder.alias(builder.isNull(builder.field(2)), "n2"),
+                builder.alias(builder.isNotNull(builder.field(3)), "nn2"),
+                builder.literal(20),
+                builder.field(6),
+                builder.alias(builder.field(6), "C"))
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalProject(DEPTNO=[$7], COMM=[CAST($6):SMALLINT NOT NULL],"
+                + " $f2=[OR(=($7, 20), AND(false, =($7, 10), IS NULL($6),"
+                + " NOT(IS NOT NULL($7))), =($7, 30))], n2=[IS NULL($2)],"
+                + " nn2=[IS NOT NULL($3)], $f5=[20], COMM6=[$6], C=[$6])\n"
+                + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testAggregate() {
+    // Equivalent SQL:
+    //   SELECT COUNT(DISTINCT deptno) AS c
+    //   FROM emp
+    //   GROUP BY ()
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .aggregate(
+                builder.groupKey(), builder.aggregateCall(
+                    SqlStdOperatorTable.COUNT,
+                    true,
+                    "C",
+                    builder.field("DEPTNO")))
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalAggregate(group=[{}], C=[COUNT(DISTINCT $7)])\n"
+            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testAggregate2() {
+    // Equivalent SQL:
+    //   SELECT COUNT(*) AS c, SUM(mgr + 1) AS s
+    //   FROM emp
+    //   GROUP BY ename, hiredate + mgr
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .aggregate(
+                builder.groupKey(builder.field(1),
+                    builder.call(SqlStdOperatorTable.PLUS, builder.field(4),
+                        builder.field(3)),
+                    builder.field(1)),
+                builder.aggregateCall(SqlStdOperatorTable.COUNT, false, "C"),
+                builder.aggregateCall(SqlStdOperatorTable.SUM, false, "S",
+                    builder.call(
+                        SqlStdOperatorTable.PLUS,
+                        builder.field(3),
+                        builder.literal(1))))
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is(""
+            + "LogicalAggregate(group=[{1, 8}], C=[COUNT()], S=[SUM($9)])\n"
+            + "  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[+($4, $3)], $f9=[+($3, 1)])\n"
+            + "    LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testDistinct() {
+    // Equivalent SQL:
+    //   SELECT DISTINCT *
+    //   FROM emp
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .distinct()
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalAggregate(group=[{}])\n"
+                + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testUnion() {
+    // Equivalent SQL:
+    //   SELECT deptno FROM emp
+    //   UNION ALL
+    //   SELECT deptno FROM dept
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .filter(
+                builder.call(SqlStdOperatorTable.EQUALS,
+                    builder.field("DEPTNO"),
+                    builder.literal(20)))
+            .project(builder.field("EMPNO"))
+            .scan("DEPT")
+            .project(builder.field("DEPTNO"))
+            .union(true)
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalUnion(all=[true])\n"
+            + "  LogicalProject(DEPTNO=[$0])\n"
+            + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+            + "  LogicalProject(EMPNO=[$0])\n"
+            + "    LogicalFilter(condition=[=($7, 20)])\n"
+            + "      LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testIntersect() {
+    // Equivalent SQL:
+    //   SELECT empno FROM emp
+    //   WHERE deptno = 20
+    //   INTERSECT
+    //   SELECT deptno FROM dept
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .filter(
+                builder.call(SqlStdOperatorTable.EQUALS,
+                    builder.field("DEPTNO"),
+                    builder.literal(20)))
+            .project(builder.field("EMPNO"))
+            .scan("DEPT")
+            .project(builder.field("DEPTNO"))
+            .intersect(false)
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalIntersect(all=[false])\n"
+            + "  LogicalProject(DEPTNO=[$0])\n"
+            + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+            + "  LogicalProject(EMPNO=[$0])\n"
+            + "    LogicalFilter(condition=[=($7, 20)])\n"
+            + "      LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testExcept() {
+    // Equivalent SQL:
+    //   SELECT empno FROM emp
+    //   WHERE deptno = 20
+    //   MINUS
+    //   SELECT deptno FROM dept
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .filter(
+                builder.call(SqlStdOperatorTable.EQUALS,
+                    builder.field("DEPTNO"),
+                    builder.literal(20)))
+            .project(builder.field("EMPNO"))
+            .scan("DEPT")
+            .project(builder.field("DEPTNO"))
+            .minus(false)
+            .build();
+    assertThat(RelOptUtil.toString(root),
+        is("LogicalMinus(all=[false])\n"
+            + "  LogicalProject(DEPTNO=[$0])\n"
+            + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+            + "  LogicalProject(EMPNO=[$0])\n"
+            + "    LogicalFilter(condition=[=($7, 20)])\n"
+            + "      LogicalTableScan(table=[[scott, EMP]])\n"));
+  }
+
+  @Test public void testJoin() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM (SELECT * FROM emp WHERE comm IS NULL)
+    //   JOIN dept ON emp.deptno = dept.deptno
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .filter(
+                builder.call(SqlStdOperatorTable.IS_NULL,
+                    builder.field("COMM")))
+            .scan("DEPT")
+            .join(JoinRelType.INNER,
+                builder.call(SqlStdOperatorTable.EQUALS,
+                    builder.field(2, 0, "DEPTNO"),
+                    builder.field(2, 1, "DEPTNO")))
+            .build();
+    final String expected =
+        "LogicalJoin(condition=[=($7, $0)], joinType=[inner])\n"
+            + "  LogicalTableScan(table=[[scott, DEPT]])\n"
+            + "  LogicalFilter(condition=[IS NULL($6)])\n"
+            + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+
+    // Using USING method
+    final RelNode root2 =
+        builder.scan("EMP")
+            .filter(
+                builder.call(SqlStdOperatorTable.IS_NULL,
+                    builder.field("COMM")))
+            .scan("DEPT")
+            .join(JoinRelType.INNER, "DEPTNO")
+            .build();
+    assertThat(RelOptUtil.toString(root2), is(expected));
+  }
+
+  @Test public void testJoinCartesian() {
+    // Equivalent SQL:
+    //   SELECT * emp CROSS JOIN dept
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.scan("EMP")
+            .scan("DEPT")
+            .join(JoinRelType.INNER)
+            .build();
+    final String expected =
+        "LogicalJoin(condition=[true], joinType=[inner])\n"
+            + "  LogicalTableScan(table=[[scott, DEPT]])\n"
+            + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+  }
+
+  @Test public void testValues() {
+    // Equivalent SQL:
+    //   VALUES (true, 1), (false, -50) AS t(a, b)
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.values(new String[]{"a", "b"}, true, 1, false, -50)
+            .build();
+    final String expected =
+        "LogicalValues(tuples=[[{ true, 1 }, { false, -50 }]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+    final String expectedType =
+        "RecordType(BOOLEAN NOT NULL a, INTEGER NOT NULL b) NOT NULL";
+    assertThat(root.getRowType().getFullTypeString(), is(expectedType));
+  }
+
+  /** Tests creating Values with some field names and some values null. */
+  @Test public void testValuesNullable() {
+    // Equivalent SQL:
+    //   VALUES (null, 1, 'abc'), (false, null, 'longer string')
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root =
+        builder.values(new String[]{"a", null, "c"},
+            null, 1, "abc",
+            false, null, "longer string").build();
+    final String expected =
+        "LogicalValues(tuples=[[{ null, 1, 'abc' }, { false, null, 'longer string' }]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+    final String expectedType =
+        "RecordType(BOOLEAN a, INTEGER expr$1, CHAR(13) CHARACTER SET \"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\" NOT NULL c) NOT NULL";
+    assertThat(root.getRowType().getFullTypeString(), is(expectedType));
+  }
+
+  @Test public void testValuesBadNullFieldNames() {
+    try {
+      final RelBuilder builder = RelBuilder.create(config().build());
+      RelBuilder root = builder.values((String[]) null, "a", "b");
+      fail("expected error, got " + root);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          is("Value count must be a positive multiple of field count"));
+    }
+  }
+
+  @Test public void testValuesBadNoFields() {
+    try {
+      final RelBuilder builder = RelBuilder.create(config().build());
+      RelBuilder root = builder.values(new String[0], 1, 2, 3);
+      fail("expected error, got " + root);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          is("Value count must be a positive multiple of field count"));
+    }
+  }
+
+  @Test public void testValuesBadNoValues() {
+    try {
+      final RelBuilder builder = RelBuilder.create(config().build());
+      RelBuilder root = builder.values(new String[]{"a", "b"});
+      fail("expected error, got " + root);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          is("Value count must be a positive multiple of field count"));
+    }
+  }
+
+  @Test public void testValuesBadOddMultiple() {
+    try {
+      final RelBuilder builder = RelBuilder.create(config().build());
+      RelBuilder root = builder.values(new String[] {"a", "b"}, 1, 2, 3, 4, 5);
+      fail("expected error, got " + root);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          is("Value count must be a positive multiple of field count"));
+    }
+  }
+
+  @Test public void testValuesBadAllNull() {
+    try {
+      final RelBuilder builder = RelBuilder.create(config().build());
+      RelBuilder root =
+          builder.values(new String[] {"a", "b"}, null, null, 1, null);
+      fail("expected error, got " + root);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          is("All values of field 'b' are null; cannot deduce type"));
+    }
+  }
+
+  @Test public void testValuesAllNull() {
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelDataType rowType =
+        builder.getTypeFactory().builder()
+            .add("a", SqlTypeName.BIGINT)
+            .add("a", SqlTypeName.VARCHAR, 10)
+            .build();
+    RelNode root =
+        builder.values(rowType, null, null, 1, null).build();
+    final String expected =
+        "LogicalValues(tuples=[[{ null, null }, { 1, null }]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+    final String expectedType =
+        "RecordType(BIGINT NOT NULL a, VARCHAR(10) CHARACTER SET \"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\" NOT NULL a) NOT NULL";
+    assertThat(root.getRowType().getFullTypeString(), is(expectedType));
+  }
+
+  @Test public void testSort() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM emp
+    //   ORDER BY 3. 1 DESC
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final RelNode root =
+        builder.scan("EMP")
+            .sort(builder.field(2), builder.desc(builder.field(0)))
+            .build();
+    final String expected =
+        "LogicalSort(sort0=[$2], sort1=[$0], dir0=[ASC], dir1=[DESC])\n"
+            + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+
+    // same result using ordinals
+    final RelNode root2 =
+        builder.scan("EMP")
+            .sort(2, -1)
+            .build();
+    assertThat(RelOptUtil.toString(root2), is(expected));
+  }
+
+  @Test public void testSortByExpression() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM emp
+    //   ORDER BY ename ASC NULLS LAST, hiredate + mgr DESC NULLS FIRST
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final RelNode root =
+        builder.scan("EMP")
+            .sort(builder.nullsLast(builder.desc(builder.field(1))),
+                builder.nullsFirst(
+                    builder.call(SqlStdOperatorTable.PLUS, builder.field(4),
+                        builder.field(3))))
+            .build();
+    final String expected =
+        "LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7])\n"
+            + "  LogicalSort(sort0=[$1], sort1=[$8], dir0=[DESC-nulls-last], dir1=[ASC-nulls-first])\n"
+            + "    LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[+($4, $3)])\n"
+            + "      LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+  }
+
+  @Test public void testLimit() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM emp
+    //   OFFSET 2 FETCH 10
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final RelNode root =
+        builder.scan("EMP")
+            .limit(2, 10)
+            .build();
+    final String expected =
+        "LogicalSort(offset=[2], fetch=[10])\n"
+            + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+  }
+
+  @Test public void testSortLimit() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM emp
+    //   ORDER BY deptno DESC FETCH 10
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final RelNode root =
+        builder.scan("EMP")
+            .sortLimit(-1, 10, builder.desc(builder.field("DEPTNO")))
+            .build();
+    final String expected =
+        "LogicalSort(sort0=[$7], dir0=[DESC], fetch=[10])\n"
+            + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(RelOptUtil.toString(root), is(expected));
+  }
+}
+
+// End RelBuilderTest.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java b/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java
index 9a4756b..3d33234 100644
--- a/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/FrameworksTest.java
@@ -33,7 +33,9 @@ import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.schema.Path;
 import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.Schemas;
 import org.apache.calcite.schema.Table;
 import org.apache.calcite.schema.impl.AbstractTable;
 import org.apache.calcite.server.CalciteServerStatement;
@@ -50,8 +52,10 @@ import org.junit.Test;
 import java.math.BigDecimal;
 
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 /**
  * Unit tests for methods in {@link Frameworks}.
@@ -188,6 +192,36 @@ public class FrameworksTest {
     assertThat(Util.toLinux(valStr), equalTo(expandedStr));
   }
 
+  /** Test for {@link Path}. */
+  @Test public void testSchemaPath() {
+    final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    final FrameworkConfig config = Frameworks.newConfigBuilder()
+        .defaultSchema(
+            CalciteAssert.addSchema(rootSchema, CalciteAssert.SchemaSpec.HR))
+        .build();
+    final Path path = Schemas.path(config.getDefaultSchema());
+    assertThat(path.size(), is(2));
+    assertThat(path.get(0).left, is(""));
+    assertThat(path.get(1).left, is("hr"));
+    assertThat(path.names().size(), is(1));
+    assertThat(path.names().get(0), is("hr"));
+    assertThat(path.schemas().size(), is(2));
+
+    final Path parent = path.parent();
+    assertThat(parent.size(), is(1));
+    assertThat(parent.names().size(), is(0));
+
+    final Path grandparent = parent.parent();
+    assertThat(grandparent.size(), is(0));
+
+    try {
+      Object o = grandparent.parent();
+      fail("expected exception, got " + o);
+    } catch (IllegalArgumentException e) {
+      // ok
+    }
+  }
+
   /** Dummy type system, similar to Hive's, accessed via an INSTANCE member. */
   public static class HiveLikeTypeSystem extends RelDataTypeSystemImpl {
     public static final RelDataTypeSystem INSTANCE = new HiveLikeTypeSystem();

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/core/src/test/java/org/apache/calcite/util/UtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index 8aca657..2d731a7 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -18,6 +18,7 @@ package org.apache.calcite.util;
 
 import org.apache.calcite.avatica.AvaticaUtils;
 import org.apache.calcite.avatica.util.Spaces;
+import org.apache.calcite.examples.RelBuilderExample;
 import org.apache.calcite.linq4j.function.Function1;
 import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.Resources;
@@ -1376,6 +1377,10 @@ public class UtilTest {
     assertThat(map.get("X"), is((String) null));
     assertThat(map.get("Y"), equalTo("y"));
   }
+
+  @Test public void testRelBuilderExample() {
+    new RelBuilderExample(false).runAllExamples();
+  }
 }
 
 // End UtilTest.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/site/_docs/algebra.md
----------------------------------------------------------------------
diff --git a/site/_docs/algebra.md b/site/_docs/algebra.md
index 1116362..ef93f97 100644
--- a/site/_docs/algebra.md
+++ b/site/_docs/algebra.md
@@ -22,6 +22,9 @@ limitations under the License.
 {% endcomment %}
 -->
 
+{% assign sourceRoot = "http://github.com/apache/incubator-calcite/blob/master" %}
+{% assign apiRoot = "http://calcite.hydromatic.net/apidocs" %}
+
 Relational algebra is at the heart of Calcite. Every query is
 represented as a tree of relational operators. You can translate from
 SQL to relational algebra, or you can build the tree directly.
@@ -38,3 +41,317 @@ semantics as the original but a lower cost.
 
 The planning process is extensible. You can add your own relational
 operators, planner rules, cost model, and statistics.
+
+## Algebra builder
+
+The simplest way to build a relational expression is to use the algebra builder,
+[RelBuilder]({{ apiRoot }}/org/apache/calcite/tools/RelBuilder.html).
+Here is an example:
+
+### TableScan
+
+{% highlight java %}
+final FrameworkConfig config;
+final RelBuilder builder = RelBuilder.create(config);
+final RelNode node = builder
+  .scan("EMP")
+  .build();
+System.out.println(RelOptUtil.toString(node));
+{% endhighlight %}
+
+(You can find the full code for this and other examples in
+[RelBuilderExample.java]({{ sourceRoot }}/core/src/test/java/org/apache/calcite/examples/RelBuilderExample.java).)
+
+The code prints
+
+{% highlight text %}
+LogicalTableScan(table=[[scott, EMP]])
+{% endhighlight %}
+
+It has created a scan of the `EMP` table; equivalent to the SQL
+
+{% highlight sql %}
+SELECT *
+FROM scott.EMP;
+{% endhighlight %}
+
+### Adding a Project
+
+Now, let's add a Project, the equivalent of
+
+{% highlight sql %}
+SELECT ename, deptno
+FROM scott.EMP;
+{% endhighlight %}
+
+We just add a call to the `project` method before calling
+`build`:
+
+{% highlight java %}
+final RelNode node = builder
+  .scan("EMP")
+  .project(builder.field("DEPTNO"), builder.field("ENAME"))
+  .build();
+System.out.println(RelOptUtil.toString(node));
+{% endhighlight %}
+
+and the output is
+
+{% highlight text %}
+LogicalProject(DEPTNO=[$7], ENAME=[$1])
+  LogicalTableScan(table=[[scott, EMP]])
+{% endhighlight %}
+
+The two calls to `builder.field` create simple expressions
+that return the fields from the input relational expression,
+namely the TableScan created by the `scan` call.
+
+Calcite has converted them to field references by ordinal,
+`$7` and `$1`.
+
+### Adding a Filter and Aggregate
+
+A query with an Aggregate, and a Filter:
+
+{% highlight java %}
+final RelNode node = 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)))
+  .build();
+System.out.println(RelOptUtil.toString(node));
+{% endhighlight %}
+
+is equivalent to SQL
+
+{% highlight sql %}
+SELECT deptno, count(*) AS c, sum(sal) AS s
+FROM emp
+GROUP BY deptno
+HAVING count(*) > 10
+{% endhighlight %}
+
+and produces 
+
+{% highlight text %}
+LogicalFilter(condition=[>($1, 10)])
+  LogicalAggregate(group=[{7}], C=[COUNT()], S=[SUM($5)])
+    LogicalTableScan(table=[[scott, EMP]])
+{% endhighlight %}
+
+### Push and pop
+
+The builder uses a stack to store the relational expression produced by
+one step and pass it as an input to the next step. This allows the
+methods that produce relational expressions to produce a builder.
+
+Most of the time, the only stack method you will use is `build()`, to get the
+last relational expression, namely the root of the tree.
+
+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:
+
+{% highlight text %}
+.
+               join
+             /      \
+        join          join 
+      /      \      /      \
+CUSTOMERS ORDERS LINE_ITEMS PRODUCTS
+{% endhighlight %}
+
+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`:
+
+{% highlight java %}
+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();
+
+final RelNode result = builder
+  .push(left)
+  .push(right)
+  .join(JoinRelType.INNER, "ORDER_ID")
+  .build();
+{% endhighlight %}
+
+### Field names and ordinals
+
+You can reference a field by name or ordinal.
+
+Ordinals are zero-based. Each operator guarantees the order in which its output
+fields occur. For example, `Project` returns the fields in the generated by
+each of the scalar expressions.
+
+The field names of an operator are guaranteed to be unique, but sometimes that
+means that the names are not exactly what you expect. For example, when you
+join EMP to DEPT, one of the output fields will be called DEPTNO and another
+will be called something like DEPTNO_1.
+
+Some relational expression methods give you more control over field names:
+
+* `project` lets you wrap expressions using `alias(expr, fieldName)`. It
+  removes the wrapper but keeps the suggested name (as long as it is unique).
+* `values(String[] fieldNames, Object... values)` accepts an array of field
+  names. If any element of the array is null, the builder will generate a unique
+  name.
+
+If an expression projects an input field, or a cast of an input field, it will
+use the name of that input field.
+
+Once the unique field names have been assigned, the names are immutable.
+If you have a particular `RelNode` instance, you can rely on the field names not
+changing. In fact, the whole relational expression is immutable.
+
+But if a relational expression has passed through several rewrite rules (see
+([RelOptRule]({{ apiRoot }}/org/apache/calcite/plan/RelOptRule.html)), the field
+names of the resulting expression might not look much like the originals.
+At that point it is better to reference fields by ordinal.
+
+When you are building a relational expression that accepts multiple inputs,
+you need to build field references that take that into account. This occurs
+most often when building join conditions.
+
+Suppose you are building a join on EMP,
+which has 8 fields [EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO]
+and DEPT,
+which has 3 fields [DEPTNO, DNAME, LOC].
+Internally, Calcite represents those fields as offsets into
+a combined input row with 11 fields: the first field of the left input is
+field #0 (0-based, remember), and the first field of the right input is
+field #8.
+
+But through the builder API, you specify which field of which input.
+To reference "SAL", internal field #5,
+write `builder.field(2, 0, "SAL")`
+or `builder.field(2, 0, 5)`.
+This means "the field #5 of input #0 of two inputs".
+(Why does it need to know that there are two inputs? Because they are stored on
+the stack; input #1 is at the top of the stack, and input #0 is below it.
+If we did not tell the builder that were two inputs, it would not know how deep
+to go for input #0.)
+
+Similarly, to reference "DNAME", internal field #9 (8 + 1),
+write `builder.field(2, 1, "DNAME")`
+or `builder.field(2, 1, 1)`.
+
+### API summary
+
+#### Relational operators
+
+The following methods create a relational expression
+([RelNode]({{ apiRoot }}/org/apache/calcite/rel/RelNode.html)),
+push it onto the stack, and
+return the `RelBuilder`.
+
+| Method              | Description
+|:------------------- |:-----------
+| `scan(tableName)` | Creates a [TableScan]({{ apiRoot }}/org/apache/calcite/rel/core/TableScan.html).
+| `values(fieldNames, value...)`<br/>`values(rowType, tupleList)` | Creates a [Values]({{ apiRoot }}/org/apache/calcite/rel/core/Values.html).
+| `filter(expr...)`<br/>`filter(exprList)` | Creates a [Filter]({{ apiRoot }}/org/apache/calcite/rel/core/Filter.html) over the AND of the given predicates.
+| `project(expr...)`<br/>`project(exprList)` | Creates a [Project]({{ apiRoot }}/org/apache/calcite/rel/core/Project.html). To override the default name, wrap expressions using `alias`.
+| `aggregate(groupKey, aggCall...)`<br/>`aggregate(groupKey, aggCallList)` | Creates an [Aggregate]({{ apiRoot }}/org/apache/calcite/rel/core/Aggregate.html).
+| `distinct()` | Creates an [Aggregate]({{ apiRoot }}/org/apache/calcite/rel/core/Aggregate.html) that eliminates duplicate records.
+| `sort(fieldOrdinal...)`<br/>`sort(expr...)`<br/>`sort(exprList)` | Creates a [Sort]({{ apiRoot }}/org/apache/calcite/rel/core/Sort.html).<br/><br/>In the first form, field ordinals are 0-based, and a negative ordinal indicates descending; for example, -2 means field 1 descending.<br/><br/>In the other forms, you can wrap expressions in `as`, `nullsFirst` or `nullsLast`.
+| `sortLimit(offset, fetch, expr...)`<br/>`sortLimit(offset, fetch, exprList)` | Creates a [Sort]({{ apiRoot }}/org/apache/calcite/rel/core/Sort.html) with offset and limit.
+| `limit(offset, fetch)` | Creates a [Sort]({{ apiRoot }}/org/apache/calcite/rel/core/Sort.html) that does not sort, only applies with offset and limit.
+| `join(joinType, expr)`<br/>`join(joinType, fieldName...)` | Creates a [Join]({{ apiRoot }}/org/apache/calcite/rel/core/Join.html) of the two most recent relational expressions.<br/><br/>The first form joins on an boolean expression.<br/><br/>The second form joins on named fields; each side must have a field of each name.
+| `union(all)` | Creates a [Union]({{ apiRoot }}/org/apache/calcite/rel/core/Union.html) of the two most recent relational expressions.
+| `intersect(all)` | Creates an [Intersect]({{ apiRoot }}/org/apache/calcite/rel/core/Intersect.html) of the two most recent relational expressions.
+| `minus(all)` | Creates a [Minus]({{ apiRoot }}/org/apache/calcite/rel/core/Minus.html) of the two most recent relational expressions.
+
+Argument types:
+
+* `expr`  [RexNode]({{ apiRoot }}/org/apache/calcite/rex/RexNode.html)
+* `expr...` Array of [RexNode]({{ apiRoot }}/org/apache/calcite/rex/RexNode.html)
+* `exprList` Iterable of [RexNode]({{ apiRoot }}/org/apache/calcite/rex/RexNode.html)
+* `fieldOrdinal` Ordinal of a field within its row (starting from 0)
+* `fieldName` Name of a field, unique within its row
+* `fieldName...` Array of String
+* `fieldNames` Iterable of String
+* `rowType` [RelDataType]({{ apiRoot }}/org/apache/calcite/rel/type/RelDataType.html)
+* `groupKey` [RelBuilder.GroupKey]({{ apiRoot }}/org/apache/calcite/tools/RelBuilder/GroupKey.html)
+* `aggCall...` Array of [RelBuilder.AggCall]({{ apiRoot }}/org/apache/calcite/tools/RelBuilder/AggCall.html)
+* `aggCallList` Iterable of [RelBuilder.AggCall]({{ apiRoot }}/org/apache/calcite/tools/RelBuilder/AggCall.html)
+* `value...` Array of Object
+* `value` Object
+* `tupleList` Iterable of List of [RexLiteral]({{ apiRoot }}/org/apache/calcite/rex/RexLiteral.html)
+* `all` boolean
+* `distinct` boolean
+* `alias` String
+
+### Stack methods
+
+
+| Method              | Description
+|:------------------- |:-----------
+| `build()`           | Pops the most recently created relational expression off the stack
+| `push(rel)`         | Pushes a relational expression onto the stack. Relational methods such as `scan`, above, call this method, but user code generally does not
+| `peek()`            | Returns the relational expression most recently put onto the stack, but does not remove it
+
+#### Scalar expression methods
+
+The following methods return a scalar expression
+([RexNode]({{ apiRoot }}/org/apache/calcite/rex/RexNode.html)).
+
+Many of them use the contents of the stack. For example, `field("DEPTNO")`
+returns a reference to the "DEPTNO" field of the relational expression just
+added to the stack.
+
+| Method              | Description
+|:------------------- |:-----------
+| `literal(value)` | Constant
+| `field(fieldName)` | Reference, by name, to a field of the top-most relational expression
+| `field(fieldOrdinal)` | Reference, by ordinal, to a field of the top-most relational expression
+| `field(inputCount, inputOrdinal, fieldName)` | Reference, by name, to a field of the (`inputCount` - `inputOrdinal`)th relational expression
+| `field(inputCount, inputOrdinal, fieldOrdinal)` | Reference, by ordinal, to a field of the (`inputCount` - `inputOrdinal`)th relational expression
+| `call(op, expr...)`<br/>`call(op, exprList)` | Call to a function or operator
+| `and(expr...)`<br/>`and(exprList)` | Logical AND. Flattens nested ANDs, and optimizes cases involving TRUE and FALSE. 
+| `or(expr...)`<br/>`or(exprList)` | Logical OR. Flattens nested ORs, and optimizes cases involving TRUE and FALSE.
+| `not(expr)` | Logical NOT
+| `equals(expr, expr)` | Equals
+| `isNull(expr)` | Checks whether an expression is null
+| `isNotNull(expr)` | Checks whether an expression is not null
+| `alias(expr, fieldName)` | Renames an expression (only valid as an argument to `project`)
+| `cast(expr, typeName)`<br/>`cast(expr, typeName, precision)`<br/>`cast(expr, typeName, precision, scale)`<br/> | Converts an expression to a given type
+| `desc(expr)` | Changes sort direction to descending (only valid as an argument to `sort` or `sortLimit`)
+| `nullsFirst(expr)` | Changes sort order to nulls first (only valid as an argument to `sort` or `sortLimit`)
+| `nullsLast(expr)` | Changes sort order to nulls last (only valid as an argument to `sort` or `sortLimit`)
+
+### Group key methods
+
+The following methods return a
+[RelBuilder.GroupKey]({{ apiRoot }}/org/apache/calcite/tools/RelBuilder/GroupKey.html).
+
+| Method              | Description
+|:------------------- |:-----------
+| `groupKey(fieldName...)`<br/>`groupKey(fieldOrdinal...)`<br/>`groupKey(expr...)`<br/>`groupKey(exprList)` | Creates a group key of the given expressions
+
+### Aggregate call methods
+
+The following methods return an
+[RelBuilder.AggCall]({{ apiRoot }}/org/apache/calcite/tools/RelBuilder/AggCall.html).
+
+| Method              | Description
+|:------------------- |:-----------
+| `aggregateCall(op, distinct, alias, expr...)`<br/>`aggregateCall(op, distinct, alias, exprList)` | Creates a call to a given aggregate function
+| `count(distinct, alias, expr...)` | Creates a call to the COUNT aggregate function
+| `countStar(alias)` | Creates a call to the COUNT(*) aggregate function
+| `sum(distinct, alias, expr)` | Creates a call to the SUM aggregate function
+| `min(alias, expr)` | Creates a call to the MIN aggregate function
+| `max(alias, expr)` | Creates a call to the MAX aggregate function

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index dc7efb5..3dc4ca2 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -29,7 +29,7 @@ The page describes the SQL dialect recognized by Calcite's default SQL parser.
 SQL grammar in [BNF](http://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form)-like
 form.
 
-{% highlight SQL %}
+{% highlight sql %}
 statement:
       setStatement
   |   explain
@@ -228,7 +228,7 @@ name will have been converted to upper case also.
 
 Where:
 
-{% highlight SQL %}
+{% highlight sql %}
 timeUnit:
   YEAR | MONTH | DAY | HOUR | MINUTE | SECOND
 {% endhighlight %}
@@ -514,7 +514,7 @@ Not implemented:
 
 Syntax:
 
-{% highlight SQL %}
+{% highlight sql %}
 aggregateCall:
         agg( [ DISTINCT ] value [, value]* ) [ FILTER ( WHERE condition ) ]
     |   agg(*) [ FILTER ( WHERE condition ) ]

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/6609cb1a/site/_posts/2015-06-05-algebra-builder.md
----------------------------------------------------------------------
diff --git a/site/_posts/2015-06-05-algebra-builder.md b/site/_posts/2015-06-05-algebra-builder.md
new file mode 100644
index 0000000..fb25ce1
--- /dev/null
+++ b/site/_posts/2015-06-05-algebra-builder.md
@@ -0,0 +1,89 @@
+---
+layout: news_item
+title: "Algebra builder"
+date: "2015-06-05 19:29:07 -0800"
+author: jhyde
+categories: ["new features"]
+---
+<!--
+{% comment %}
+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.
+{% endcomment %}
+-->
+
+{% assign apiRoot = "http://calcite.hydromatic.net/apidocs" %}
+
+Calcite's foundation is a comprehensive implementation of relational
+algebra (together with transformation rules, cost model, and metadata)
+but to create algebra expressions you had to master a complex API.
+
+We're solving this problem by introducing an
+[algebra builder]({{ apiRoot }}/org/apache/calcite/tools/RelBuilder.html),
+a single class with all the methods you need to build any relational
+expression.
+
+For example,
+
+{% highlight java %}
+final FrameworkConfig config;
+final RelBuilder builder = RelBuilder.create(config);
+final RelNode node = 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)))
+  .build();
+System.out.println(RelOptUtil.toString(node));
+{% endhighlight %}
+
+creates the algebra
+
+{% highlight text %}
+LogicalFilter(condition=[>($1, 10)])
+  LogicalAggregate(group=[{7}], C=[COUNT()], S=[SUM($5)])
+    LogicalTableScan(table=[[scott, EMP]])
+{% endhighlight %}
+
+which is equivalent to the SQL
+
+{% highlight sql %}
+SELECT deptno, count(*) AS c, sum(sal) AS s
+FROM emp
+GROUP BY deptno
+HAVING count(*) > 10
+{% endhighlight %}
+
+The [algebra builder documentation](/docs/algebra.html) describes the
+full API and has lots of examples.
+
+We're still working on the algebra builder, but plan to release it
+with Calcite 1.4 (see
+[[CALCITE-748](https://issues.apache.org/jira/browse/CALCITE-748)]).
+
+The algebra builder will make some existing tasks easier (such as
+writing planner rules), but will also enable new things, such as
+writing applications directly on top of Calcite, or implementing
+non-SQL query languages. These applications and languages will be able
+to take advantage of Calcite's existing back-ends (including
+Hive-on-Tez, Drill, MongoDB, Splunk, Spark, JDBC data sources) and
+extensive set of query-optimization rules.
+
+If you have questions or comments, please post to the
+[mailing list](/develop).
\ No newline at end of file