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<String>)</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