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 2014/10/28 18:50:49 UTC

[1/9] [CALCITE-436] Simpler SPI to query Table

Repository: incubator-calcite
Updated Branches:
  refs/heads/master e4fcf2a3b -> 1f91bbf81


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastFilterRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastFilterRule.java b/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastFilterRule.java
index ea211df..70ba40e 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastFilterRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastFilterRule.java
@@ -82,7 +82,7 @@ public class PushProjectPastFilterRule extends RelOptRule {
     PushProjector pushProjector =
         new PushProjector(
             origProj, origFilter, rel, preserveExprCondition);
-    ProjectRel topProject = pushProjector.convertProject(null);
+    RelNode topProject = pushProjector.convertProject(null);
 
     if (topProject != null) {
       call.transformTo(topProject);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastJoinRule.java b/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastJoinRule.java
index 91e18b2..9d6eddb 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastJoinRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/PushProjectPastJoinRule.java
@@ -120,7 +120,7 @@ public class PushProjectPastJoinRule extends RelOptRule {
 
     // put the original project on top of the join, converting it to
     // reference the modified projection list
-    ProjectRel topProject =
+    RelNode topProject =
         pushProject.createNewProject(newJoinRel, adjustments);
 
     call.transformTo(topProject);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/rel/rules/PushProjector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/PushProjector.java b/core/src/main/java/org/eigenbase/rel/rules/PushProjector.java
index b51e3b8..4709439 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/PushProjector.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/PushProjector.java
@@ -30,6 +30,7 @@ import net.hydromatic.linq4j.Ord;
 import net.hydromatic.optiq.util.BitSets;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 /**
  * PushProjector is a utility class used to perform operations used in push
@@ -240,7 +241,7 @@ public class PushProjector {
    * @return the converted projection if it makes sense to push elements of
    * the projection; otherwise returns null
    */
-  public ProjectRel convertProject(RexNode defaultExpr) {
+  public RelNode convertProject(RexNode defaultExpr) {
     // locate all fields referenced in the projection and filter
     locateAllRefs();
 
@@ -304,9 +305,7 @@ public class PushProjector {
     // put the original project on top of the filter/project, converting
     // it to reference the modified projection list; otherwise, create
     // a projection that essentially selects all fields
-    ProjectRel topProject = createNewProject(projChild, adjustments);
-
-    return topProject;
+    return createNewProject(projChild, adjustments);
   }
 
   /**
@@ -530,9 +529,8 @@ public class PushProjector {
    *                    be adjusted by
    * @return the created projection
    */
-  public ProjectRel createNewProject(RelNode projChild, int[] adjustments) {
-    List<Pair<RexNode, String>> projects =
-        new ArrayList<Pair<RexNode, String>>();
+  public RelNode createNewProject(RelNode projChild, int[] adjustments) {
+    final List<Pair<RexNode, String>> projects = Lists.newArrayList();
 
     if (origProj != null) {
       for (Pair<RexNode, String> p : origProj.getNamedProjects()) {
@@ -552,7 +550,7 @@ public class PushProjector {
                     field.e.getType(), field.i), field.e.getName()));
       }
     }
-    return (ProjectRel) RelOptUtil.createProject(
+    return RelOptUtil.createProject(
         projChild,
         Pair.left(projects),
         Pair.right(projects),

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
index 79a59e7..2f3bcb8 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
@@ -445,7 +445,8 @@ public abstract class RelOptUtil {
 
   /**
    * Creates a relational expression which filters according to a given
-   * condition, returning the same fields as its input.
+   * condition, returning the same fields as its input, using the default
+   * filter factory.
    *
    * @param child     Child relational expression
    * @param condition Condition
@@ -477,7 +478,8 @@ public abstract class RelOptUtil {
     return createFilter(child, conditions, RelFactories.DEFAULT_FILTER_FACTORY);
   }
 
-  /** Creates a filter, or returns the original relational expression if the
+  /** Creates a filter using the default factory,
+   * or returns the original relational expression if the
    * condition is trivial. */
   public static RelNode createFilter(RelNode child,
       Iterable<? extends RexNode> conditions,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/resource/EigenbaseNewResource.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/resource/EigenbaseNewResource.java b/core/src/main/java/org/eigenbase/resource/EigenbaseNewResource.java
index ee75010..82b06a1 100644
--- a/core/src/main/java/org/eigenbase/resource/EigenbaseNewResource.java
+++ b/core/src/main/java/org/eigenbase/resource/EigenbaseNewResource.java
@@ -532,6 +532,12 @@ public interface EigenbaseNewResource {
 
   @BaseMessage("In user-defined aggregate class ''{0}'', first parameter to ''add'' method must be the accumulator (the return type of the ''init'' method)")
   ExInst<RuntimeException> firstParameterOfAdd(String className);
+
+  @BaseMessage("FilterableTable.scan returned a filter that was not in the original list: {0}")
+  ExInst<EigenbaseException> filterableTableInventedFilter(String s);
+
+  @BaseMessage("FilterableTable.scan must not return null")
+  ExInst<EigenbaseException> filterableTableScanReturnedNull();
 }
 
 // End EigenbaseNewResource.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/rex/RexProgram.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rex/RexProgram.java b/core/src/main/java/org/eigenbase/rex/RexProgram.java
index d4ed909..aafe361 100644
--- a/core/src/main/java/org/eigenbase/rex/RexProgram.java
+++ b/core/src/main/java/org/eigenbase/rex/RexProgram.java
@@ -29,6 +29,7 @@ import org.eigenbase.sql.type.*;
 import org.eigenbase.util.*;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 /**
  * A collection of expressions which read inputs, compute output expressions,
@@ -521,6 +522,23 @@ public class RexProgram {
     return ref.accept(shuttle);
   }
 
+  /** Splits this program into a list of project expressions and a list of
+   * filter expressions.
+   *
+   * <p>Neither list is null.
+   * The filters are evaluated first. */
+  public Pair<ImmutableList<RexNode>, ImmutableList<RexNode>> split() {
+    final List<RexNode> filters = Lists.newArrayList();
+    if (condition != null) {
+      RelOptUtil.decomposeConjunction(expandLocalRef(condition), filters);
+    }
+    final ImmutableList.Builder<RexNode> projects = ImmutableList.builder();
+    for (RexLocalRef project : this.projects) {
+      projects.add(expandLocalRef(project));
+    }
+    return Pair.of(projects.build(), ImmutableList.copyOf(filters));
+  }
+
   /**
    * Given a list of collations which hold for the input to this program,
    * returns a list of collations which hold for its output. The result is

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/util/Bug.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/util/Bug.java b/core/src/main/java/org/eigenbase/util/Bug.java
index a070b51..37a9db6 100644
--- a/core/src/main/java/org/eigenbase/util/Bug.java
+++ b/core/src/main/java/org/eigenbase/util/Bug.java
@@ -153,20 +153,21 @@ public abstract class Bug {
    */
   public static final boolean FRG375_FIXED = false;
 
-  /**
-   * Whether
-   * <a href="https://issues.apache.org/jira/browse/CALCITE-194">CALCITE-194</a>,
-   * "Array items in MongoDB adapter" is fixed.
-   */
+  /** Whether
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-194">[CALCITE-194]
+   * Array items in MongoDB adapter</a> is fixed. */
   public static final boolean CALCITE_194_FIXED = false;
 
-  /**
-   * Whether
-   * <a href="https://issues.apache.org/jira/browse/CALCITE-319">CALCITE-319</a>,
-   * "Array items in MongoDB adapter" is fixed.
-   */
+  /** Whether
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-319">[CALCITE-319]
+   * Table aliases should follow case-sensitivity policy</a> is fixed. */
   public static final boolean CALCITE_319_FIXED = false;
 
+  /** Whether
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-445">[CALCITE-445]
+   * Pull up filters rejected by a ProjectableFilterableTable</a> is fixed. */
+  public static final boolean CALCITE_445_FIXED = false;
+
   /**
    * Use this to flag temporary code.
    */

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/resources/org/eigenbase/resource/EigenbaseResource.properties
----------------------------------------------------------------------
diff --git a/core/src/main/resources/org/eigenbase/resource/EigenbaseResource.properties b/core/src/main/resources/org/eigenbase/resource/EigenbaseResource.properties
index 43990b5..6f6eb5d 100644
--- a/core/src/main/resources/org/eigenbase/resource/EigenbaseResource.properties
+++ b/core/src/main/resources/org/eigenbase/resource/EigenbaseResource.properties
@@ -174,4 +174,6 @@ ColumnCountMismatch=Number of columns must match number of query columns
 DuplicateColumnAndNoColumnList=Column has duplicate column name ''{0}'' and no column list specified
 RequireDefaultConstructor=Declaring class ''{0}'' of non-static user-defined function must have a public constructor with zero parameters
 FirstParameterOfAdd=In user-defined aggregate class ''{0}'', first parameter to ''add'' method must be the accumulator (the return type of the ''init'' method)
+FilterableTableInventedFilter=FilterableTable.scan returned a filter that was not in the original list: {0}
+FilterableTableScanReturnedNull=FilterableTable.scan must not return null
 # End org.eigenbase.resource.EigenbaseResource.properties

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/test/java/net/hydromatic/optiq/impl/generate/RangeTable.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/impl/generate/RangeTable.java b/core/src/test/java/net/hydromatic/optiq/impl/generate/RangeTable.java
index f06ac4a..4bba4e9 100644
--- a/core/src/test/java/net/hydromatic/optiq/impl/generate/RangeTable.java
+++ b/core/src/test/java/net/hydromatic/optiq/impl/generate/RangeTable.java
@@ -40,18 +40,16 @@ public class RangeTable extends AbstractQueryableTable {
   private final int start;
   private final int end;
 
-  protected RangeTable(String columnName, String tableName, int start,
-      int end) {
-    super(Object[].class);
+  protected RangeTable(String columnName, int start, int end) {
+    super(Object.class);
     this.columnName = columnName;
     this.start = start;
     this.end = end;
   }
 
   /** Creates a RangeTable. */
-  public static RangeTable create(String tableName, String columnName,
-      int start, int end) {
-    return new RangeTable(columnName, tableName, start, end);
+  public static RangeTable create(String columnName, int start, int end) {
+    return new RangeTable(columnName, start, end);
   }
 
   public RelDataType getRowType(RelDataTypeFactory typeFactory) {
@@ -108,7 +106,7 @@ public class RangeTable extends AbstractQueryableTable {
       final String columnName = (String) operand.get("column");
       final int start = (Integer) operand.get("start");
       final int end = (Integer) operand.get("end");
-      return RangeTable.create(name, columnName, start, end);
+      return RangeTable.create(columnName, start, end);
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/test/java/net/hydromatic/optiq/test/InterpreterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/InterpreterTest.java b/core/src/test/java/net/hydromatic/optiq/test/InterpreterTest.java
index 7cf16b6..6364b2e 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/InterpreterTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/InterpreterTest.java
@@ -22,7 +22,6 @@ import net.hydromatic.optiq.DataContext;
 import net.hydromatic.optiq.SchemaPlus;
 import net.hydromatic.optiq.config.Lex;
 import net.hydromatic.optiq.impl.interpreter.Interpreter;
-import net.hydromatic.optiq.impl.interpreter.Row;
 import net.hydromatic.optiq.impl.java.JavaTypeFactory;
 import net.hydromatic.optiq.tools.FrameworkConfig;
 import net.hydromatic.optiq.tools.Frameworks;
@@ -109,8 +108,8 @@ public class InterpreterTest {
 
   private static void assertRows(Interpreter interpreter, String... rows) {
     final List<String> list = Lists.newArrayList();
-    for (Row row : interpreter) {
-      list.add(row.toString());
+    for (Object[] row : interpreter) {
+      list.add(Arrays.toString(row));
     }
     assertThat(list, equalTo(Arrays.asList(rows)));
   }
@@ -131,6 +130,40 @@ public class InterpreterTest {
         "[150, 10, Sebastian, 7000.0, null]",
         "[200, 20, Eric, 8000.0, 500]");
   }
+
+  /** Tests executing a plan on a {@link net.hydromatic.optiq.ScannableTable}
+   * using an interpreter. */
+  @Test public void testInterpretScannableTable() throws Exception {
+    rootSchema.add("beatles", new ScannableTableTest.BeatlesTable());
+    SqlNode parse =
+        planner.parse("select * from \"beatles\" order by \"i\"");
+
+    SqlNode validate = planner.validate(parse);
+    RelNode convert = planner.convert(validate);
+
+    final Interpreter interpreter =
+        new Interpreter(new MyDataContext(planner), convert);
+    assertRows(interpreter,
+        "[4, John]",
+        "[4, Paul]",
+        "[5, Ringo]",
+        "[6, George]");
+  }
+
+  /** Tests executing a plan on a single-column
+   * {@link net.hydromatic.optiq.ScannableTable} using an interpreter. */
+  @Test public void testInterpretSimpleScannableTable() throws Exception {
+    rootSchema.add("simple", new ScannableTableTest.SimpleTable());
+    SqlNode parse =
+        planner.parse("select * from \"simple\" limit 2");
+
+    SqlNode validate = planner.validate(parse);
+    RelNode convert = planner.convert(validate);
+
+    final Interpreter interpreter =
+        new Interpreter(new MyDataContext(planner), convert);
+    assertRows(interpreter, "[0]", "[10]");
+  }
 }
 
 // End InterpreterTest.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java b/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
index 0a84bcc..110eacc 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
@@ -66,6 +66,7 @@ import org.junit.runners.Suite;
     ModelTest.class,
     SqlValidatorFeatureTest.class,
     VolcanoPlannerTraitTest.class,
+    ScannableTableTest.class,
     InterpreterTest.class,
     VolcanoPlannerTest.class,
     SargTest.class,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/test/java/net/hydromatic/optiq/test/ReflectiveSchemaTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/ReflectiveSchemaTest.java b/core/src/test/java/net/hydromatic/optiq/test/ReflectiveSchemaTest.java
index 739db1a..bd9a91f 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/ReflectiveSchemaTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/ReflectiveSchemaTest.java
@@ -64,9 +64,7 @@ public class ReflectiveSchemaTest {
         queryProvider.createQuery(
             Expressions.call(
                 Expressions.call(
-                    net.hydromatic.linq4j.expressions.Types.of(
-                        Enumerable.class,
-                        Employee.class),
+                    Types.of(Enumerable.class, Employee.class),
                     null,
                     LINQ4J_AS_ENUMERABLE_METHOD,
                     Expressions.constant(
@@ -115,8 +113,7 @@ public class ReflectiveSchemaTest {
         queryProvider.createQuery(
             Expressions.call(
                 Expressions.call(
-                    net.hydromatic.linq4j.expressions.Types.of(
-                        Enumerable.class, Employee.class),
+                    Types.of(Enumerable.class, Employee.class),
                     null,
                     LINQ4J_AS_ENUMERABLE_METHOD,
                     Arrays.<Expression>asList(

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/test/java/net/hydromatic/optiq/test/ScannableTableTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/ScannableTableTest.java b/core/src/test/java/net/hydromatic/optiq/test/ScannableTableTest.java
new file mode 100644
index 0000000..ed989ab
--- /dev/null
+++ b/core/src/test/java/net/hydromatic/optiq/test/ScannableTableTest.java
@@ -0,0 +1,433 @@
+/*
+ * 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 net.hydromatic.optiq.test;
+
+import net.hydromatic.linq4j.AbstractEnumerable;
+import net.hydromatic.linq4j.Enumerable;
+import net.hydromatic.linq4j.Enumerator;
+
+import net.hydromatic.optiq.*;
+import net.hydromatic.optiq.impl.AbstractSchema;
+import net.hydromatic.optiq.impl.AbstractTable;
+import net.hydromatic.optiq.impl.java.ReflectiveSchema;
+import net.hydromatic.optiq.jdbc.OptiqConnection;
+
+import org.eigenbase.reltype.RelDataType;
+import org.eigenbase.reltype.RelDataTypeFactory;
+import org.eigenbase.rex.RexCall;
+import org.eigenbase.rex.RexInputRef;
+import org.eigenbase.rex.RexLiteral;
+import org.eigenbase.rex.RexNode;
+import org.eigenbase.sql.fun.SqlStdOperatorTable;
+import org.eigenbase.sql.type.SqlTypeName;
+import org.eigenbase.util.Bug;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.sql.*;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit test for {@link net.hydromatic.optiq.ScannableTable}.
+ */
+public class ScannableTableTest {
+  @Test public void testTens() throws SQLException {
+    final Enumerator<Object[]> cursor = tens();
+    assertTrue(cursor.moveNext());
+    Assert.assertThat(cursor.current()[0], equalTo((Object) 0));
+    Assert.assertThat(cursor.current().length, equalTo(1));
+    assertTrue(cursor.moveNext());
+    Assert.assertThat(cursor.current()[0], equalTo((Object) 10));
+    assertTrue(cursor.moveNext());
+    Assert.assertThat(cursor.current()[0], equalTo((Object) 20));
+    assertTrue(cursor.moveNext());
+    Assert.assertThat(cursor.current()[0], equalTo((Object) 30));
+    assertFalse(cursor.moveNext());
+  }
+
+  /** A table with one column. */
+  @Test public void testSimple() throws Exception {
+    Connection connection =
+        DriverManager.getConnection("jdbc:calcite:");
+    OptiqConnection optiqConnection =
+        connection.unwrap(OptiqConnection.class);
+    SchemaPlus rootSchema = optiqConnection.getRootSchema();
+    SchemaPlus schema = rootSchema.add("s", new AbstractSchema());
+    schema.add("simple", new SimpleTable());
+    rootSchema.add("hr", new ReflectiveSchema(new JdbcTest.HrSchema()));
+    ResultSet resultSet = connection.createStatement().executeQuery(
+        "select * from \"s\".\"simple\"");
+    assertThat(OptiqAssert.toString(resultSet),
+        equalTo("i=0\ni=10\ni=20\ni=30\n"));
+  }
+
+  /** A table with two columns. */
+  @Test public void testSimple2() throws Exception {
+    Connection connection =
+        DriverManager.getConnection("jdbc:calcite:");
+    OptiqConnection optiqConnection =
+        connection.unwrap(OptiqConnection.class);
+    SchemaPlus rootSchema = optiqConnection.getRootSchema();
+    SchemaPlus schema = rootSchema.add("s", new AbstractSchema());
+    schema.add("beatles", new BeatlesTable());
+    rootSchema.add("hr", new ReflectiveSchema(new JdbcTest.HrSchema()));
+    ResultSet resultSet = connection.createStatement().executeQuery(
+        "select * from \"s\".\"beatles\"");
+    assertThat(OptiqAssert.toString(resultSet),
+        equalTo("i=4; j=John\ni=4; j=Paul\ni=6; j=George\ni=5; j=Ringo\n"));
+  }
+
+  /** A filter on a {@link FilterableTable} with two columns. */
+  @Test public void testSimpleFilter2() throws Exception {
+    Connection connection =
+        DriverManager.getConnection("jdbc:calcite:");
+    OptiqConnection optiqConnection =
+        connection.unwrap(OptiqConnection.class);
+    SchemaPlus rootSchema = optiqConnection.getRootSchema();
+    SchemaPlus schema = rootSchema.add("s", new AbstractSchema());
+    final StringBuilder buf = new StringBuilder();
+    schema.add("beatles", new BeatlesFilterableTable(buf, true));
+    final Statement statement = connection.createStatement();
+    ResultSet resultSet = statement.executeQuery(
+        "select * from \"s\".\"beatles\" where \"i\" = 4");
+    assertThat(OptiqAssert.toString(resultSet),
+        equalTo("i=4; j=John; k=1940\ni=4; j=Paul; k=1942\n"));
+    resultSet.close();
+    // Only 2 rows came out of the table. If the value is 4, it means that the
+    // planner did not pass the filter down.
+    assertThat(buf.toString(), equalTo("returnCount=2"));
+    buf.setLength(0);
+
+    // Now with an "uncooperative" filterable table that refuses to accept
+    // filters.
+    schema.add("beatles2", new BeatlesFilterableTable(buf, false));
+    resultSet = statement.executeQuery(
+        "select * from \"s\".\"beatles2\" where \"i\" = 4");
+    assertThat(OptiqAssert.toString(resultSet),
+        equalTo("i=4; j=John; k=1940\ni=4; j=Paul; k=1942\n"));
+    resultSet.close();
+    assertThat(buf.toString(), equalTo("returnCount=4"));
+    buf.setLength(0);
+  }
+
+  /** A filter on a {@link net.hydromatic.optiq.ProjectableFilterableTable} with
+   * two columns. */
+  @Test public void testProjectableFilterable2() throws Exception {
+    Connection connection =
+        DriverManager.getConnection("jdbc:calcite:");
+    OptiqConnection optiqConnection =
+        connection.unwrap(OptiqConnection.class);
+    SchemaPlus rootSchema = optiqConnection.getRootSchema();
+    SchemaPlus schema = rootSchema.add("s", new AbstractSchema());
+    final StringBuilder buf = new StringBuilder();
+    schema.add("beatles", new BeatlesProjectableFilterableTable(buf, true));
+    final Statement statement = connection.createStatement();
+    ResultSet resultSet = statement.executeQuery(
+        "select * from \"s\".\"beatles\" where \"i\" = 4");
+    assertThat(OptiqAssert.toString(resultSet),
+        equalTo("i=4; j=John; k=1940\ni=4; j=Paul; k=1942\n"));
+    resultSet.close();
+    // Only 2 rows came out of the table. If the value is 4, it means that the
+    // planner did not pass the filter down.
+    assertThat(buf.toString(), equalTo("returnCount=2, projects=[0, 1, 2]"));
+    buf.setLength(0);
+
+    // Now with an "uncooperative" filterable table that refuses to accept
+    // filters.
+    schema.add("beatles2", new BeatlesProjectableFilterableTable(buf, false));
+    resultSet = statement.executeQuery(
+        "select * from \"s\".\"beatles2\" where \"i\" = 4");
+    assertThat(OptiqAssert.toString(resultSet),
+        equalTo("i=4; j=John; k=1940\ni=4; j=Paul; k=1942\n"));
+    resultSet.close();
+    assertThat(buf.toString(), equalTo("returnCount=4"));
+    buf.setLength(0);
+  }
+
+  /** A filter on a {@link net.hydromatic.optiq.ProjectableFilterableTable} with
+   * two columns, and a project in the query. */
+  @Test public void testProjectableFilterable2WithProject() throws Exception {
+    Connection connection =
+        DriverManager.getConnection("jdbc:calcite:");
+    OptiqConnection optiqConnection =
+        connection.unwrap(OptiqConnection.class);
+    SchemaPlus rootSchema = optiqConnection.getRootSchema();
+    SchemaPlus schema = rootSchema.add("s", new AbstractSchema());
+    final StringBuilder buf = new StringBuilder();
+    schema.add("beatles", new BeatlesProjectableFilterableTable(buf, true));
+
+    // Now with a project.
+    final Statement statement = connection.createStatement();
+    ResultSet resultSet = statement.executeQuery(
+        "select \"k\",\"j\" from \"s\".\"beatles\" where \"i\" = 4");
+    assertThat(OptiqAssert.toString(resultSet),
+        equalTo("k=1940; j=John\nk=1942; j=Paul\n"));
+    resultSet.close();
+    assertThat(buf.toString(), equalTo("returnCount=2, projects=[2, 1]"));
+    buf.setLength(0);
+  }
+
+  /** A filter and project on a
+   * {@link net.hydromatic.optiq.ProjectableFilterableTable}. The table
+   * refuses to execute the filter, so Calcite should add a pull up and
+   * transform the filter (projecting the column needed by the filter). */
+  @Test public void testPFTableRefusesFilter() throws Exception {
+    Connection connection =
+        DriverManager.getConnection("jdbc:calcite:");
+    OptiqConnection optiqConnection =
+        connection.unwrap(OptiqConnection.class);
+    SchemaPlus rootSchema = optiqConnection.getRootSchema();
+    SchemaPlus schema = rootSchema.add("s", new AbstractSchema());
+    final StringBuilder buf = new StringBuilder();
+    schema.add("beatles2", new BeatlesProjectableFilterableTable(buf, false));
+
+    // Now with an "uncooperative" filterable table that refuses to accept
+    // filters.
+    final Statement statement = connection.createStatement();
+    ResultSet resultSet = statement.executeQuery(
+        "select \"k\" from \"s\".\"beatles2\" where \"i\" = 4");
+    assertThat(OptiqAssert.toString(resultSet),
+        equalTo(Bug.CALCITE_445_FIXED ? "k=1940\nk=1942\n" : ""));
+    resultSet.close();
+    assertThat(buf.toString(),
+        equalTo(Bug.CALCITE_445_FIXED
+                ? "returnCount=4, projects=[0, 2]"
+                : "returnCount=4, projects=[2]"));
+    buf.setLength(0);
+  }
+
+  private static Integer getFilter(boolean cooperative, List<RexNode> filters) {
+    final Iterator<RexNode> filterIter = filters.iterator();
+    while (filterIter.hasNext()) {
+      final RexNode node = filterIter.next();
+      if (cooperative
+          && node instanceof RexCall
+          && ((RexCall) node).getOperator() == SqlStdOperatorTable.EQUALS
+          && ((RexCall) node).getOperands().get(0) instanceof RexInputRef
+          && ((RexInputRef) ((RexCall) node).getOperands().get(0)).getIndex()
+          == 0
+          && ((RexCall) node).getOperands().get(1) instanceof RexLiteral) {
+        final RexNode op1 = ((RexCall) node).getOperands().get(1);
+        filterIter.remove();
+        return ((BigDecimal) ((RexLiteral) op1).getValue()).intValue();
+      }
+    }
+    return null;
+  }
+
+  /** Table that returns one column via the {@link ScannableTable} interface. */
+  public static class SimpleTable implements ScannableTable {
+    public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+      return typeFactory.builder().add("i", SqlTypeName.INTEGER).build();
+    }
+
+    public Statistic getStatistic() {
+      return Statistics.UNKNOWN;
+    }
+
+    public Schema.TableType getJdbcTableType() {
+      return Schema.TableType.TABLE;
+    }
+
+    public Enumerable<Object[]> scan(DataContext root) {
+      return new AbstractEnumerable<Object[]>() {
+        public Enumerator<Object[]> enumerator() {
+          return tens();
+        }
+      };
+    }
+  }
+
+  /** Table that returns two columns via the ScannableTable interface. */
+  public static class BeatlesTable implements ScannableTable {
+    public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+      return typeFactory.builder()
+          .add("i", SqlTypeName.INTEGER)
+          .add("j", SqlTypeName.VARCHAR)
+          .build();
+    }
+
+    public Statistic getStatistic() {
+      return Statistics.UNKNOWN;
+    }
+
+    public Schema.TableType getJdbcTableType() {
+      return Schema.TableType.TABLE;
+    }
+
+    public Enumerable<Object[]> scan(DataContext root) {
+      return new AbstractEnumerable<Object[]>() {
+        public Enumerator<Object[]> enumerator() {
+          return beatles(new StringBuilder(), null, null);
+        }
+      };
+    }
+  }
+
+  /** Table that returns two columns via the {@link FilterableTable}
+   * interface. */
+  public static class BeatlesFilterableTable extends AbstractTable
+      implements FilterableTable {
+    private final StringBuilder buf;
+    private final boolean cooperative;
+
+    public BeatlesFilterableTable(StringBuilder buf, boolean cooperative) {
+      this.buf = buf;
+      this.cooperative = cooperative;
+    }
+
+    public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+      return typeFactory.builder()
+          .add("i", SqlTypeName.INTEGER)
+          .add("j", SqlTypeName.VARCHAR)
+          .add("k", SqlTypeName.INTEGER)
+          .build();
+    }
+
+    public Enumerable<Object[]> scan(DataContext root, List<RexNode> filters) {
+      final Integer filter = getFilter(cooperative, filters);
+      return new AbstractEnumerable<Object[]>() {
+        public Enumerator<Object[]> enumerator() {
+          return beatles(buf, filter, null);
+        }
+      };
+    }
+  }
+
+  /** Table that returns two columns via the {@link FilterableTable}
+   * interface. */
+  public static class BeatlesProjectableFilterableTable
+      extends AbstractTable implements ProjectableFilterableTable {
+    private final StringBuilder buf;
+    private final boolean cooperative;
+
+    public BeatlesProjectableFilterableTable(StringBuilder buf,
+        boolean cooperative) {
+      this.buf = buf;
+      this.cooperative = cooperative;
+    }
+
+    public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+      return typeFactory.builder()
+          .add("i", SqlTypeName.INTEGER)
+          .add("j", SqlTypeName.VARCHAR)
+          .add("k", SqlTypeName.INTEGER)
+          .build();
+    }
+
+    public Enumerable<Object[]> scan(DataContext root, List<RexNode> filters,
+        final int[] projects) {
+      final Integer filter = getFilter(cooperative, filters);
+      return new AbstractEnumerable<Object[]>() {
+        public Enumerator<Object[]> enumerator() {
+          return beatles(buf, filter, projects);
+        }
+      };
+    }
+  }
+
+  private static Enumerator<Object[]> tens() {
+    return new Enumerator<Object[]>() {
+      int row = -1;
+      Object[] current;
+
+      public Object[] current() {
+        return current;
+      }
+
+      public boolean moveNext() {
+        if (++row < 4) {
+          current = new Object[] {row * 10};
+          return true;
+        } else {
+          return false;
+        }
+      }
+
+      public void reset() {
+        row = -1;
+      }
+
+      public void close() {
+        current = null;
+      }
+    };
+  }
+
+  private static final Object[][] BEATLES = {
+    {4, "John", 1940},
+    {4, "Paul", 1942},
+    {6, "George", 1943},
+    {5, "Ringo", 1940}
+  };
+
+  private static Enumerator<Object[]> beatles(final StringBuilder buf,
+      final Integer filter, final int[] projects) {
+    return new Enumerator<Object[]>() {
+      int row = -1;
+      int returnCount = 0;
+      Object[] current;
+
+      public Object[] current() {
+        return current;
+      }
+
+      public boolean moveNext() {
+        while (++row < 4) {
+          Object[] current = BEATLES[row % 4];
+          if (filter == null || filter.equals(current[0])) {
+            if (projects == null) {
+              this.current = current;
+            } else {
+              Object[] newCurrent = new Object[projects.length];
+              for (int i = 0; i < projects.length; i++) {
+                newCurrent[i] = current[projects[i]];
+              }
+              this.current = newCurrent;
+            }
+            ++returnCount;
+            return true;
+          }
+        }
+        return false;
+      }
+
+      public void reset() {
+        row = -1;
+      }
+
+      public void close() {
+        current = null;
+        buf.append("returnCount=").append(returnCount);
+        if (projects != null) {
+          buf.append(", projects=").append(Arrays.toString(projects));
+        }
+      }
+    };
+  }
+}
+
+// End ScannableTableTest.java


[8/9] git commit: Various lattice improvements.

Posted by jh...@apache.org.
Various lattice improvements.

Add model that demonstrates lattices.

Allow lattice 'sql' attribute to be multi-line in lattice, view and materialization.

Don't allow roll up distinct-count.

When generating SQL in JDBC adapter, translate $SUM0 as SUM.

Fix enumerator over single-column ArrayTable.

Document model for lattice, tile, measure.

Improve algorithm to look for tiles that match a query, to allow inexact matches.

Look for materialized tiles that can be rolled up before creating a tile that is an exact match for the query. If there is a tile that has the right dimensionality but the wrong measures, in the worst case create a new tile with the union of the measures. If there are multiple tiles that can be rolled up, choose the one with the fewest rows.

Add attribute "algorithmMaxMillis" to JSON lattice element, and improve documentation for JSON model.

Make class TileKey top-level.

Add AggregateCall.create, which can infer the type of the call before creating it. This allows us to make SqlSumEmptyIsZeroAggFunction a singleton, by removing its type argument.

Deprecate interface Aggregation; new code should use SqlAggFunction.


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

Branch: refs/heads/master
Commit: 41215c2f358a0006c944c48c30f26506ea60e493
Parents: 667ca64
Author: Julian Hyde <jh...@apache.org>
Authored: Fri Oct 3 20:24:39 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Oct 24 23:20:46 2014 -0700

----------------------------------------------------------------------
 .../net/hydromatic/avatica/ColumnMetaData.java  |   2 +-
 .../main/java/net/hydromatic/optiq/Schemas.java |  10 +
 .../optiq/impl/MaterializedViewTable.java       |   4 +-
 .../net/hydromatic/optiq/impl/StarTable.java    |   2 -
 .../hydromatic/optiq/impl/clone/ArrayTable.java |  98 +++++--
 .../optiq/impl/clone/CloneSchema.java           |  15 +-
 .../optiq/impl/jdbc/JdbcImplementor.java        |   4 +
 .../optiq/jdbc/OptiqConnectionImpl.java         |   2 +-
 .../net/hydromatic/optiq/jdbc/OptiqSchema.java  |  32 ++-
 .../hydromatic/optiq/materialize/Lattice.java   |  24 +-
 .../optiq/materialize/MaterializationActor.java |   8 +-
 .../materialize/MaterializationService.java     | 270 ++++++++++++-------
 .../hydromatic/optiq/materialize/TileKey.java   |  57 ++++
 .../optiq/materialize/TileSuggester.java        |  14 +-
 .../net/hydromatic/optiq/model/JsonLattice.java |  41 ++-
 .../optiq/model/JsonMaterialization.java        |   8 +-
 .../net/hydromatic/optiq/model/JsonRoot.java    |  14 +-
 .../net/hydromatic/optiq/model/JsonView.java    |   8 +-
 .../hydromatic/optiq/model/ModelHandler.java    |   9 +-
 .../net/hydromatic/optiq/runtime/Utilities.java |   2 +
 .../java/org/eigenbase/rel/AggregateCall.java   |  39 +--
 .../java/org/eigenbase/rel/Aggregation.java     |  14 +
 .../rel/rules/AggregateStarTableRule.java       |  43 +--
 .../rules/PushAggregateThroughUnionRule.java    |  45 ++--
 .../rel/rules/ReduceAggregatesRule.java         |  44 +--
 .../org/eigenbase/relopt/RelOptLattice.java     |   7 +-
 .../java/org/eigenbase/relopt/RelOptUtil.java   |  26 +-
 .../eigenbase/relopt/SubstitutionVisitor.java   |  10 +-
 .../eigenbase/sql/fun/SqlStdOperatorTable.java  |   2 +-
 .../sql/fun/SqlSumEmptyIsZeroAggFunction.java   |  18 +-
 .../net/hydromatic/optiq/test/LatticeTest.java  | 207 +++++++++-----
 .../optiq/test/MaterializationTest.java         |   2 +-
 .../net/hydromatic/optiq/test/ModelTest.java    |  66 ++++-
 .../resources/mysql-foodmart-lattice-model.json |  59 ++++
 doc/MODEL.md                                    | 167 ++++++++++--
 35 files changed, 1006 insertions(+), 367 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/avatica/src/main/java/net/hydromatic/avatica/ColumnMetaData.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/net/hydromatic/avatica/ColumnMetaData.java b/avatica/src/main/java/net/hydromatic/avatica/ColumnMetaData.java
index 65e1931..598a604 100644
--- a/avatica/src/main/java/net/hydromatic/avatica/ColumnMetaData.java
+++ b/avatica/src/main/java/net/hydromatic/avatica/ColumnMetaData.java
@@ -175,7 +175,7 @@ public class ColumnMetaData {
     STRING(String.class),
     OBJECT(Object.class);
 
-    private final Class clazz;
+    public final Class clazz;
 
     public static final Map<Class, Rep> VALUE_MAP;
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/Schemas.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/Schemas.java b/core/src/main/java/net/hydromatic/optiq/Schemas.java
index 6a7a16e..00233d7 100644
--- a/core/src/main/java/net/hydromatic/optiq/Schemas.java
+++ b/core/src/main/java/net/hydromatic/optiq/Schemas.java
@@ -34,6 +34,7 @@ import org.eigenbase.reltype.RelProtoDataType;
 import org.eigenbase.rex.RexNode;
 import org.eigenbase.sql.type.SqlTypeUtil;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
@@ -419,6 +420,15 @@ public final class Schemas {
     return schema;
   }
 
+  /** Generates a table name that is unique within the given schema. */
+  public static String uniqueTableName(OptiqSchema schema, String base) {
+    String t = Preconditions.checkNotNull(base);
+    for (int x = 0; schema.getTable(t, true) != null; x++) {
+      t = base + x;
+    }
+    return t;
+  }
+
   /** Dummy data context that has no variables. */
   private static class DummyDataContext implements DataContext {
     private final OptiqConnection connection;

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/impl/MaterializedViewTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/MaterializedViewTable.java b/core/src/main/java/net/hydromatic/optiq/impl/MaterializedViewTable.java
index 023dedd..aac77b5 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/MaterializedViewTable.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/MaterializedViewTable.java
@@ -102,11 +102,11 @@ public class MaterializedViewTable extends ViewTable {
     private final MaterializationKey key;
 
     private MaterializedViewTableMacro(OptiqSchema schema, String viewSql,
-        List<String> viewSchemaPath, String tableName) {
+        List<String> viewSchemaPath, String suggestedTableName) {
       super(schema, viewSql, viewSchemaPath);
       this.key = Preconditions.checkNotNull(
           MaterializationService.instance().defineMaterialization(
-              schema, null, viewSql, schemaPath, tableName, true));
+              schema, null, viewSql, schemaPath, suggestedTableName, true));
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java b/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
index ba71e30..a2f31d6 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
@@ -72,12 +72,10 @@ public class StarTable extends AbstractTable implements TranslatableTable {
 
   public RelDataType getRowType(RelDataTypeFactory typeFactory) {
     final List<RelDataType> typeList = new ArrayList<RelDataType>();
-    final List<String> nameList = new ArrayList<String>();
     final List<Integer> fieldCounts = new ArrayList<Integer>();
     for (Table table : tables) {
       final RelDataType rowType = table.getRowType(typeFactory);
       typeList.addAll(RelOptUtil.getFieldTypeList(rowType));
-      nameList.addAll(rowType.getFieldNames());
       fieldCounts.add(rowType.getFieldCount());
     }
     // Compute fieldCounts the first time this method is called. Safe to assume

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java b/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java
index 8b56b7b..b97bb18 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java
@@ -74,31 +74,7 @@ class ArrayTable extends AbstractQueryableTable {
       @SuppressWarnings("unchecked")
       public Enumerator<T> enumerator() {
         final Content content = supplier.get();
-        return new Enumerator() {
-          final int rowCount = content.size;
-          final int columnCount = content.columns.size();
-          int i = -1;
-
-          public Object[] current() {
-            Object[] objects = new Object[columnCount];
-            for (int j = 0; j < objects.length; j++) {
-              final Column pair = content.columns.get(j);
-              objects[j] = pair.representation.getObject(pair.dataSet, i);
-            }
-            return objects;
-          }
-
-          public boolean moveNext() {
-            return ++i < rowCount;
-          }
-
-          public void reset() {
-            i = -1;
-          }
-
-          public void close() {
-          }
-        };
+        return content.enumerator();
       }
     };
   }
@@ -810,6 +786,78 @@ class ArrayTable extends AbstractQueryableTable {
       this.size = size;
       this.sortField = sortField;
     }
+
+    @SuppressWarnings("unchecked")
+    public <T> Enumerator<T> enumerator() {
+      if (columns.size() == 1) {
+        return (Enumerator<T>) new ObjectEnumerator(size, columns.get(0));
+      } else {
+        return (Enumerator<T>) new ArrayEnumerator(size, columns);
+      }
+    }
+
+    /** Enumerator over a table with a single column; each element
+     * returned is an object. */
+    private static class ObjectEnumerator implements Enumerator<Object> {
+      final int rowCount;
+      final Object dataSet;
+      final Representation representation;
+      int i = -1;
+
+      public ObjectEnumerator(int rowCount, Column column) {
+        this.rowCount = rowCount;
+        this.dataSet = column.dataSet;
+        this.representation = column.representation;
+      }
+
+      public Object current() {
+        return representation.getObject(dataSet, i);
+      }
+
+      public boolean moveNext() {
+        return ++i < rowCount;
+      }
+
+      public void reset() {
+        i = -1;
+      }
+
+      public void close() {
+      }
+    }
+
+    /** Enumerator over a table with more than one column; each element
+     * returned is an array. */
+    private static class ArrayEnumerator implements Enumerator {
+      final int rowCount;
+      final List<Column> columns;
+      int i = -1;
+
+      public ArrayEnumerator(int rowCount, List<Column> columns) {
+        this.rowCount = rowCount;
+        this.columns = columns;
+      }
+
+      public Object[] current() {
+        Object[] objects = new Object[columns.size()];
+        for (int j = 0; j < objects.length; j++) {
+          final Column pair = columns.get(j);
+          objects[j] = pair.representation.getObject(pair.dataSet, i);
+        }
+        return objects;
+      }
+
+      public boolean moveNext() {
+        return ++i < rowCount;
+      }
+
+      public void reset() {
+        i = -1;
+      }
+
+      public void close() {
+      }
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java b/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java
index e3fa950..2f37b43 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java
@@ -86,9 +86,18 @@ public class CloneSchema extends AbstractSchema {
       final RelProtoDataType protoRowType,
       final List<ColumnMetaData.Rep> repList,
       final Enumerable<T> source) {
-    final Type elementType = source instanceof QueryableTable
-        ? ((QueryableTable) source).getElementType()
-        : Object[].class;
+    final Type elementType;
+    if (source instanceof QueryableTable) {
+      elementType = ((QueryableTable) source).getElementType();
+    } else if (protoRowType.apply(typeFactory).getFieldCount() == 1) {
+      if (repList != null) {
+        elementType = repList.get(0).clazz;
+      } else {
+        elementType = Object.class;
+      }
+    } else {
+      elementType = Object[].class;
+    }
     return new ArrayTable(
         elementType,
         protoRowType,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/impl/jdbc/JdbcImplementor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/jdbc/JdbcImplementor.java b/core/src/main/java/net/hydromatic/optiq/impl/jdbc/JdbcImplementor.java
index 104fba2..89ae40f 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/jdbc/JdbcImplementor.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/jdbc/JdbcImplementor.java
@@ -27,6 +27,7 @@ import org.eigenbase.rex.*;
 import org.eigenbase.sql.*;
 import org.eigenbase.sql.fun.SqlCase;
 import org.eigenbase.sql.fun.SqlStdOperatorTable;
+import org.eigenbase.sql.fun.SqlSumEmptyIsZeroAggFunction;
 import org.eigenbase.sql.parser.SqlParserPos;
 import org.eigenbase.sql.type.BasicSqlType;
 import org.eigenbase.sql.type.SqlTypeName;
@@ -259,6 +260,9 @@ public class JdbcImplementor {
     /** Converts a call to an aggregate function to an expression. */
     public SqlNode toSql(AggregateCall aggCall) {
       SqlOperator op = (SqlAggFunction) aggCall.getAggregation();
+      if (op instanceof SqlSumEmptyIsZeroAggFunction) {
+        op = SqlStdOperatorTable.SUM;
+      }
       final List<SqlNode> operands = Expressions.list();
       for (int arg : aggCall.getArgList()) {
         operands.add(field(arg));

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
index 34ddd64..4e8ee07 100644
--- a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
@@ -122,7 +122,7 @@ abstract class OptiqConnectionImpl
       final Lattice lattice = e.getLattice();
       for (Lattice.Tile tile : lattice.computeTiles()) {
         service.defineTile(lattice, tile.bitSet(), tile.measures, e.schema,
-            true);
+            true, true);
       }
     }
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java
index b97b06b..83044ce 100644
--- a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java
+++ b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java
@@ -28,6 +28,7 @@ import net.hydromatic.optiq.util.Compatible;
 
 import org.eigenbase.util.Pair;
 
+import com.google.common.base.Preconditions;
 import com.google.common.cache.*;
 import com.google.common.collect.*;
 
@@ -124,8 +125,14 @@ public class OptiqSchema {
 
   /** Defines a table within this schema. */
   public TableEntry add(String tableName, Table table) {
+    return add(tableName, table, ImmutableList.<String>of());
+  }
+
+  /** Defines a table within this schema. */
+  public TableEntry add(String tableName, Table table,
+      ImmutableList<String> sqls) {
     final TableEntryImpl entry =
-        new TableEntryImpl(this, tableName, table);
+        new TableEntryImpl(this, tableName, table, sqls);
     tableMap.put(tableName, entry);
     return entry;
   }
@@ -227,6 +234,16 @@ public class OptiqSchema {
     return optiqSchema;
   }
 
+  /** Returns a table that materializes the given SQL statement. */
+  public final Pair<String, Table> getTableBySql(String sql) {
+    for (TableEntry tableEntry : tableMap.values()) {
+      if (tableEntry.sqls.contains(sql)) {
+        return Pair.of(tableEntry.name, tableEntry.getTable());
+      }
+    }
+    return null;
+  }
+
   /** Returns a table with the given name. Does not look for views. */
   public final Pair<String, Table> getTable(String tableName,
       boolean caseSensitive) {
@@ -502,8 +519,12 @@ public class OptiqSchema {
 
   /** Membership of a table in a schema. */
   public abstract static class TableEntry extends Entry {
-    public TableEntry(OptiqSchema schema, String name) {
+    public final List<String> sqls;
+
+    public TableEntry(OptiqSchema schema, String name,
+        ImmutableList<String> sqls) {
       super(schema, name);
+      this.sqls = Preconditions.checkNotNull(sqls);
     }
 
     public abstract Table getTable();
@@ -636,10 +657,11 @@ public class OptiqSchema {
     private final Table table;
 
     /** Creates a TableEntryImpl. */
-    public TableEntryImpl(OptiqSchema schema, String name, Table table) {
-      super(schema, name);
+    public TableEntryImpl(OptiqSchema schema, String name, Table table,
+        ImmutableList<String> sqls) {
+      super(schema, name, sqls);
       assert table != null;
-      this.table = table;
+      this.table = Preconditions.checkNotNull(table);
     }
 
     public Table getTable() {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java b/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java
index d12d335..24a2146 100644
--- a/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java
+++ b/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java
@@ -72,6 +72,7 @@ public class Lattice {
   public final ImmutableList<Column> columns;
   public final boolean auto;
   public final boolean algorithm;
+  public final long algorithmMaxMillis;
   public final double rowCountEstimate;
   public final ImmutableList<Measure> defaultMeasures;
   public final ImmutableList<Tile> tiles;
@@ -92,12 +93,14 @@ public class Lattice {
       };
 
   private Lattice(ImmutableList<Node> nodes, boolean auto, boolean algorithm,
+      long algorithmMaxMillis,
       Double rowCountEstimate, ImmutableList<Column> columns,
       ImmutableList<Measure> defaultMeasures, ImmutableList<Tile> tiles) {
     this.nodes = Preconditions.checkNotNull(nodes);
     this.columns = Preconditions.checkNotNull(columns);
     this.auto = auto;
     this.algorithm = algorithm;
+    this.algorithmMaxMillis = algorithmMaxMillis;
     this.defaultMeasures = Preconditions.checkNotNull(defaultMeasures);
     this.tiles = Preconditions.checkNotNull(tiles);
 
@@ -223,6 +226,9 @@ public class Lattice {
         use(usedNodes, node);
       }
     }
+    if (usedNodes.isEmpty()) {
+      usedNodes.add(nodes.get(0));
+    }
     final SqlDialect dialect = SqlDialect.DatabaseProduct.OPTIQ.getDialect();
     final StringBuilder buf = new StringBuilder("SELECT ");
     final StringBuilder groupBuf = new StringBuilder("\nGROUP BY ");
@@ -243,6 +249,9 @@ public class Lattice {
         dialect.quoteIdentifier(buf, fieldName);
       }
     }
+    if (groupSet.isEmpty()) {
+      groupBuf.append("()");
+    }
     int m = 0;
     for (Measure measure : aggCallList) {
       if (k++ > 0) {
@@ -480,6 +489,10 @@ public class Lattice {
       return compare(args, measure.args);
     }
 
+    @Override public String toString() {
+      return "Measure: [agg: " + agg + ", args: " + args + "]";
+    }
+
     @Override public int hashCode() {
       return Util.hashV(agg, args);
     }
@@ -487,7 +500,7 @@ public class Lattice {
     @Override public boolean equals(Object obj) {
       return obj == this
           || obj instanceof Measure
-          && this.agg == ((Measure) obj).agg
+          && this.agg.equals(((Measure) obj).agg)
           && this.args.equals(((Measure) obj).args);
     }
 
@@ -564,6 +577,7 @@ public class Lattice {
     private final ImmutableList.Builder<Tile> tileListBuilder =
         ImmutableList.builder();
     private boolean algorithm = false;
+    private long algorithmMaxMillis = -1;
     private boolean auto = true;
     private Double rowCountEstimate;
 
@@ -659,6 +673,12 @@ public class Lattice {
       return this;
     }
 
+    /** Sets the "algorithmMaxMillis" attribute (default -1). */
+    public Builder algorithmMaxMillis(long algorithmMaxMillis) {
+      this.algorithmMaxMillis = algorithmMaxMillis;
+      return this;
+    }
+
     /** Sets the "rowCountEstimate" attribute (default null). */
     public Builder rowCountEstimate(double rowCountEstimate) {
       this.rowCountEstimate = rowCountEstimate;
@@ -668,7 +688,7 @@ public class Lattice {
     /** Builds a lattice. */
     public Lattice build() {
       return new Lattice(ImmutableList.copyOf(nodes), auto, algorithm,
-          rowCountEstimate, columns,
+          algorithmMaxMillis, rowCountEstimate, columns,
           defaultMeasureListBuilder.build(), tileListBuilder.build());
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationActor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationActor.java b/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationActor.java
index 2a02a37..32193ef 100644
--- a/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationActor.java
+++ b/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationActor.java
@@ -37,7 +37,13 @@ class MaterializationActor {
 
   final Map<QueryKey, MaterializationKey> keyBySql = Maps.newHashMap();
 
-  final List<MaterializationService.TileKey> tileKeys = Lists.newArrayList();
+  final Map<TileKey, MaterializationKey> keyByTile = Maps.newHashMap();
+
+  /** Tiles grouped by dimensionality. We use a
+   *  {@link TileKey} with no measures to represent a
+   *  dimensionality. */
+  final Multimap<TileKey, TileKey> tilesByDimensionality =
+      HashMultimap.create();
 
   /** A query materialized in a table, so that reading from the table gives the
    * same results as executing the query. */

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationService.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationService.java b/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationService.java
index d608810..596c7df 100644
--- a/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationService.java
+++ b/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationService.java
@@ -36,6 +36,7 @@ import net.hydromatic.optiq.util.BitSets;
 import org.eigenbase.reltype.RelDataType;
 import org.eigenbase.reltype.RelDataTypeImpl;
 import org.eigenbase.util.Pair;
+import org.eigenbase.util.Util;
 
 import com.google.common.collect.*;
 
@@ -59,6 +60,23 @@ public class MaterializationService {
         }
       };
 
+  private static final Comparator<Pair<OptiqSchema.TableEntry, TileKey>> C =
+      new Comparator<Pair<OptiqSchema.TableEntry, TileKey>>() {
+        public int compare(Pair<OptiqSchema.TableEntry, TileKey> o0,
+            Pair<OptiqSchema.TableEntry, TileKey> o1) {
+          // We prefer rolling up from the table with the fewest rows.
+          final Table t0 = o0.left.getTable();
+          final Table t1 = o1.left.getTable();
+          int c = Double.compare(t0.getStatistic().getRowCount(),
+              t1.getStatistic().getRowCount());
+          if (c != 0) {
+            return c;
+          }
+          // Tie-break based on table name.
+          return o0.left.name.compareTo(o1.left.name);
+        }
+      };
+
   private final MaterializationActor actor = new MaterializationActor();
 
   private MaterializationService() {
@@ -67,7 +85,7 @@ public class MaterializationService {
   /** Defines a new materialization. Returns its key. */
   public MaterializationKey defineMaterialization(final OptiqSchema schema,
       TileKey tileKey, String viewSql, List<String> viewSchemaPath,
-      String tableName, boolean create) {
+      final String suggestedTableName, boolean create) {
     final MaterializationActor.QueryKey queryKey =
         new MaterializationActor.QueryKey(viewSql, schema, viewSchemaPath);
     final MaterializationKey existingKey = actor.keyBySql.get(queryKey);
@@ -80,75 +98,71 @@ public class MaterializationService {
 
     final OptiqConnection connection =
         MetaImpl.connect(schema.root(), null);
-    final MaterializationKey key = new MaterializationKey();
-    Table materializedTable;
+    final Pair<String, Table> pair = schema.getTableBySql(viewSql);
+    Table materializedTable = pair == null ? null : pair.right;
     RelDataType rowType = null;
-    OptiqSchema.TableEntry tableEntry;
-    if (tableName != null) {
-      final Pair<String, Table> pair = schema.getTable(tableName, true);
-      materializedTable = pair == null ? null : pair.right;
-      if (materializedTable == null) {
-        final ImmutableMap<OptiqConnectionProperty, String> map =
-            ImmutableMap.of(OptiqConnectionProperty.CREATE_MATERIALIZATIONS,
-                "false");
-        final OptiqPrepare.PrepareResult<Object> prepareResult =
-            Schemas.prepare(connection, schema, viewSchemaPath, viewSql, map);
-        rowType = prepareResult.rowType;
-        final JavaTypeFactory typeFactory = connection.getTypeFactory();
-        materializedTable =
-            CloneSchema.createCloneTable(typeFactory,
-                RelDataTypeImpl.proto(prepareResult.rowType),
-                Functions.adapt(prepareResult.structType.columns,
-                    new Function1<ColumnMetaData, ColumnMetaData.Rep>() {
-                      public ColumnMetaData.Rep apply(ColumnMetaData column) {
-                        return column.type.representation;
-                      }
-                    }),
-                new AbstractQueryable<Object>() {
-                  public Enumerator<Object> enumerator() {
-                    final DataContext dataContext =
-                        Schemas.createDataContext(connection);
-                    return prepareResult.enumerator(dataContext);
-                  }
-
-                  public Type getElementType() {
-                    return Object.class;
-                  }
-
-                  public Expression getExpression() {
-                    throw new UnsupportedOperationException();
-                  }
-
-                  public QueryProvider getProvider() {
-                    return connection;
-                  }
-
-                  public Iterator<Object> iterator() {
-                    final DataContext dataContext =
-                        Schemas.createDataContext(connection);
-                    return prepareResult.iterator(dataContext);
-                  }
-                });
-        schema.add(tableName, materializedTable);
-      }
-      tableEntry = schema.add(tableName, materializedTable);
-      Hook.CREATE_MATERIALIZATION.run(tableName);
-    } else {
-      tableEntry = null;
+    if (materializedTable == null) {
+      final ImmutableMap<OptiqConnectionProperty, String> map =
+          ImmutableMap.of(OptiqConnectionProperty.CREATE_MATERIALIZATIONS,
+              "false");
+      final OptiqPrepare.PrepareResult<Object> prepareResult =
+          Schemas.prepare(connection, schema, viewSchemaPath, viewSql, map);
+      rowType = prepareResult.rowType;
+      final JavaTypeFactory typeFactory = connection.getTypeFactory();
+      materializedTable =
+          CloneSchema.createCloneTable(typeFactory,
+              RelDataTypeImpl.proto(prepareResult.rowType),
+              Functions.adapt(prepareResult.structType.columns,
+                  new Function1<ColumnMetaData, ColumnMetaData.Rep>() {
+                    public ColumnMetaData.Rep apply(ColumnMetaData column) {
+                      return column.type.representation;
+                    }
+                  }),
+              new AbstractQueryable<Object>() {
+                public Enumerator<Object> enumerator() {
+                  final DataContext dataContext =
+                      Schemas.createDataContext(connection);
+                  return prepareResult.enumerator(dataContext);
+                }
+
+                public Type getElementType() {
+                  return Object.class;
+                }
+
+                public Expression getExpression() {
+                  throw new UnsupportedOperationException();
+                }
+
+                public QueryProvider getProvider() {
+                  return connection;
+                }
+
+                public Iterator<Object> iterator() {
+                  final DataContext dataContext =
+                      Schemas.createDataContext(connection);
+                  return prepareResult.iterator(dataContext);
+                }
+              });
     }
+    final String tableName =
+        Schemas.uniqueTableName(schema, Util.first(suggestedTableName, "m"));
+    final OptiqSchema.TableEntry tableEntry =
+        schema.add(tableName, materializedTable, ImmutableList.of(viewSql));
+    Hook.CREATE_MATERIALIZATION.run(tableName);
     if (rowType == null) {
       // If we didn't validate the SQL by populating a table, validate it now.
       final OptiqPrepare.ParseResult parse =
           Schemas.parse(connection, schema, viewSchemaPath, viewSql);
       rowType = parse.rowType;
     }
+    final MaterializationKey key = new MaterializationKey();
     final MaterializationActor.Materialization materialization =
         new MaterializationActor.Materialization(key, schema.root(),
             tableEntry, viewSql, rowType);
     actor.keyMap.put(materialization.key, materialization);
     actor.keyBySql.put(queryKey, materialization.key);
     if (tileKey != null) {
-      actor.tileKeys.add(tileKey);
+      actor.keyByTile.put(tileKey, materialization.key);
     }
     return key;
   }
@@ -174,43 +188,114 @@ public class MaterializationService {
    */
   public Pair<OptiqSchema.TableEntry, TileKey> defineTile(Lattice lattice,
       BitSet groupSet, List<Lattice.Measure> measureList, OptiqSchema schema,
-      boolean create) {
-    // FIXME This is all upside down. We are looking for a materialization
-    // first. But we should define a tile first, then find out whether an
-    // exact materialization exists, then find out whether an acceptable
-    // approximate materialization exists, and if it does not, then maybe
-    // create a materialization.
-    //
-    // The SQL should not be part of the key of the materialization. There are
-    // better, more concise keys. And especially, check that we are not using
-    // that SQL to populate the materialization. There may be finer-grained
-    // materializations that we can roll up. (Maybe the SQL on the fact table
-    // gets optimized to use those materializations.)
-    String sql = lattice.sql(groupSet, measureList);
+      boolean create, boolean exact) {
+    MaterializationKey materializationKey;
     final TileKey tileKey =
         new TileKey(lattice, groupSet, ImmutableList.copyOf(measureList));
-    MaterializationKey materializationKey =
-        defineMaterialization(schema, tileKey, sql, schema.path(null),
-            "m" + groupSet, create);
+
+    // Step 1. Look for an exact match for the tile.
+    materializationKey = actor.keyByTile.get(tileKey);
     if (materializationKey != null) {
       final OptiqSchema.TableEntry tableEntry = checkValid(materializationKey);
       if (tableEntry != null) {
         return Pair.of(tableEntry, tileKey);
       }
     }
-    // No direct hit. Look for roll-ups.
-    for (TileKey tileKey2 : actor.tileKeys) {
-      if (BitSets.contains(tileKey2.dimensions, groupSet)
-          && allSatisfiable(measureList, tileKey2)) {
-        sql = lattice.sql(tileKey2.dimensions, tileKey2.measures);
-        materializationKey =
-            defineMaterialization(schema, tileKey2, sql, schema.path(null),
-                "m" + tileKey2.dimensions, create);
-        final OptiqSchema.TableEntry tableEntry =
-            checkValid(materializationKey);
-        if (tableEntry != null) {
-          return Pair.of(tableEntry, tileKey2);
+
+    // Step 2. Look for a match of the tile with the same dimensionality and an
+    // acceptable list of measures.
+    final TileKey tileKey0 =
+        new TileKey(lattice, groupSet, ImmutableList.<Lattice.Measure>of());
+    for (TileKey tileKey1 : actor.tilesByDimensionality.get(tileKey0)) {
+      assert tileKey1.dimensions.equals(groupSet);
+      if (allSatisfiable(measureList, tileKey1)) {
+        materializationKey = actor.keyByTile.get(tileKey1);
+        if (materializationKey != null) {
+          final OptiqSchema.TableEntry tableEntry =
+              checkValid(materializationKey);
+          if (tableEntry != null) {
+            return Pair.of(tableEntry, tileKey1);
+          }
+        }
+      }
+    }
+
+    // Step 3. There's nothing at the exact dimensionality. Look for a roll-up
+    // from tiles that have a super-set of dimensions and all the measures we
+    // need.
+    //
+    // If there are several roll-ups, choose the one with the fewest rows.
+    //
+    // TODO: Allow/deny roll-up based on a size factor. If the source is only
+    // say 2x larger than the target, don't materialize, but if it is 3x, do.
+    //
+    // TODO: Use a partially-ordered set data structure, so we are not scanning
+    // through all tiles.
+    if (!exact) {
+      final PriorityQueue<Pair<OptiqSchema.TableEntry, TileKey>> queue =
+          new PriorityQueue<Pair<OptiqSchema.TableEntry, TileKey>>(1, C);
+      for (Map.Entry<TileKey, MaterializationKey> entry
+          : actor.keyByTile.entrySet()) {
+        final TileKey tileKey2 = entry.getKey();
+        if (tileKey2.lattice == lattice
+            && BitSets.contains(tileKey2.dimensions, groupSet)
+            && !tileKey2.dimensions.equals(groupSet)
+            && allSatisfiable(measureList, tileKey2)) {
+          materializationKey = entry.getValue();
+          final OptiqSchema.TableEntry tableEntry =
+              checkValid(materializationKey);
+          if (tableEntry != null) {
+            queue.add(Pair.of(tableEntry, tileKey2));
+          }
+        }
+      }
+      if (!queue.isEmpty()) {
+        final Pair<OptiqSchema.TableEntry, TileKey> best = queue.peek();
+        for (Pair<OptiqSchema.TableEntry, TileKey> pair : queue) {
+          System.out.println("table=" + pair.left.path() + " "
+              + pair.left.getTable().getStatistic().getRowCount());
+        }
+        return best;
+      }
+    }
+
+    // What we need is not there. If we can't create, we're done.
+    if (!create) {
+      return null;
+    }
+
+    // Step 4. Create the tile we need.
+    //
+    // If there were any tiles at this dimensionality, regardless of
+    // whether they were current, create a wider tile that contains their
+    // measures plus the currently requested measures. Then we can obsolete all
+    // other tiles.
+    final List<TileKey> obsolete = Lists.newArrayList();
+    final LinkedHashSet<Lattice.Measure> measureSet = Sets.newLinkedHashSet();
+    for (TileKey tileKey1 : actor.tilesByDimensionality.get(tileKey0)) {
+      measureSet.addAll(tileKey1.measures);
+      obsolete.add(tileKey1);
+    }
+    measureSet.addAll(measureList);
+    final TileKey newTileKey =
+        new TileKey(lattice, groupSet, ImmutableList.copyOf(measureSet));
+
+    final String sql = lattice.sql(groupSet, newTileKey.measures);
+    materializationKey =
+        defineMaterialization(schema, newTileKey, sql, schema.path(null),
+            "m" + groupSet, true);
+    if (materializationKey != null) {
+      final OptiqSchema.TableEntry tableEntry = checkValid(materializationKey);
+      if (tableEntry != null) {
+        // Obsolete all of the narrower tiles.
+        for (TileKey tileKey1 : obsolete) {
+          actor.tilesByDimensionality.remove(tileKey0, tileKey1);
+          actor.keyByTile.remove(tileKey1);
         }
+
+        actor.tilesByDimensionality.put(tileKey0, newTileKey);
+        actor.keyByTile.put(newTileKey, materializationKey);
+        return Pair.of(tableEntry, newTileKey);
       }
     }
     return null;
@@ -269,23 +354,6 @@ public class MaterializationService {
     return INSTANCE;
   }
 
-  /** Definition of a particular combination of dimensions and measures of a
-   * lattice that is the basis of a materialization.
-   *
-   * <p>Holds similar information to a {@link Lattice.Tile} but a lattice is
-   * immutable and tiles are not added after their creation. */
-  public static class TileKey {
-    public final Lattice lattice;
-    public final BitSet dimensions;
-    public final ImmutableList<Lattice.Measure> measures;
-
-    public TileKey(Lattice lattice, BitSet dimensions,
-        ImmutableList<Lattice.Measure> measures) {
-      this.lattice = lattice;
-      this.dimensions = dimensions;
-      this.measures = measures;
-    }
-  }
 }
 
 // End MaterializationService.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/materialize/TileKey.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/materialize/TileKey.java b/core/src/main/java/net/hydromatic/optiq/materialize/TileKey.java
new file mode 100644
index 0000000..1ba6136
--- /dev/null
+++ b/core/src/main/java/net/hydromatic/optiq/materialize/TileKey.java
@@ -0,0 +1,57 @@
+/*
+ * 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 net.hydromatic.optiq.materialize;
+
+import org.eigenbase.util.Util;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.BitSet;
+
+/** Definition of a particular combination of dimensions and measures of a
+ * lattice that is the basis of a materialization.
+ *
+ * <p>Holds similar information to a
+ * {@link net.hydromatic.optiq.materialize.Lattice.Tile} but a lattice is
+ * immutable and tiles are not added after their creation. */
+public class TileKey {
+  public final Lattice lattice;
+  public final BitSet dimensions;
+  public final ImmutableList<Lattice.Measure> measures;
+
+  /** Creates a TileKey. */
+  public TileKey(Lattice lattice, BitSet dimensions,
+      ImmutableList<Lattice.Measure> measures) {
+    this.lattice = lattice;
+    this.dimensions = dimensions;
+    this.measures = measures;
+  }
+
+  @Override public int hashCode() {
+    return Util.hashV(lattice, dimensions);
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof TileKey
+        && lattice == ((TileKey) obj).lattice
+        && dimensions.equals(((TileKey) obj).dimensions)
+        && measures.equals(((TileKey) obj).measures);
+  }
+}
+
+// End TileKey.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/materialize/TileSuggester.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/materialize/TileSuggester.java b/core/src/main/java/net/hydromatic/optiq/materialize/TileSuggester.java
index 7d58b81..74ffe27 100644
--- a/core/src/main/java/net/hydromatic/optiq/materialize/TileSuggester.java
+++ b/core/src/main/java/net/hydromatic/optiq/materialize/TileSuggester.java
@@ -64,13 +64,15 @@ public class TileSuggester {
     final StatisticsProvider statisticsProvider =
         new StatisticsProviderImpl(lattice);
     final double f = statisticsProvider.getFactRowCount();
-    final ImmutableMap<Parameter, Object> map =
-        ImmutableMap.<Parameter, Object>of(
-            Algorithm.ParameterEnum.timeLimitSeconds, 1,
-            Algorithm.ParameterEnum.aggregateLimit, 3,
-            Algorithm.ParameterEnum.costLimit, f * 5d);
+    final ImmutableMap.Builder<Parameter, Object> map = ImmutableMap.builder();
+    if (lattice.algorithmMaxMillis >= 0) {
+      map.put(Algorithm.ParameterEnum.timeLimitSeconds,
+          Math.max(1, (int) (lattice.algorithmMaxMillis / 1000L)));
+    }
+    map.put(Algorithm.ParameterEnum.aggregateLimit, 3);
+    map.put(Algorithm.ParameterEnum.costLimit, f * 5d);
     final SchemaImpl schema = new SchemaImpl(lattice, statisticsProvider);
-    final Result result = algorithm.run(schema, map, progress);
+    final Result result = algorithm.run(schema, map.build(), progress);
     final ImmutableList.Builder<Lattice.Tile> tiles = ImmutableList.builder();
     for (Aggregate aggregate : result.getAggregates()) {
       System.out.println(aggregate);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/model/JsonLattice.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/model/JsonLattice.java b/core/src/main/java/net/hydromatic/optiq/model/JsonLattice.java
index a53fd60..ec8578d 100644
--- a/core/src/main/java/net/hydromatic/optiq/model/JsonLattice.java
+++ b/core/src/main/java/net/hydromatic/optiq/model/JsonLattice.java
@@ -29,7 +29,13 @@ import java.util.List;
  */
 public class JsonLattice {
   public String name;
-  public String sql;
+
+  /** SQL query that defines the lattice.
+   *
+   * <p>Must be a string or a list of strings (which are concatenated separated
+   * by newlines).
+   */
+  public Object sql;
 
   /** Whether to create in-memory materialized aggregates on demand.
    *
@@ -41,6 +47,9 @@ public class JsonLattice {
    * <p>Default is false. */
   public boolean algorithm = false;
 
+  /** Maximum time to run the algorithm. Default is -1, meaning no timeout. */
+  public long algorithmMaxMillis = -1;
+
   /** Estimated number of rows.
    *
    * <p>If null, Calcite will a query to find the real value. */
@@ -61,7 +70,35 @@ public class JsonLattice {
   }
 
   @Override public String toString() {
-    return "JsonLattice(name=" + name + ", sql=" + sql + ")";
+    return "JsonLattice(name=" + name + ", sql=" + getSql() + ")";
+  }
+
+  /** Returns the SQL query as a string, concatenating a list of lines if
+   * necessary. */
+  public String getSql() {
+    return toString(sql);
+  }
+
+  /** Converts a string or a list of strings to a string. The list notation
+   * is a convenient way of writing long multi-line strings in JSON. */
+  static String toString(Object o) {
+    return o == null ? null
+        : o instanceof String ? (String) o
+        : concatenate((List) o);
+  }
+
+  /** Converts a list of strings into a multi-line string. */
+  private static String concatenate(List list) {
+    final StringBuilder buf = new StringBuilder();
+    for (Object o : list) {
+      if (!(o instanceof String)) {
+        throw new RuntimeException(
+            "each element of a string list must be a string; found: " + o);
+      }
+      buf.append((String) o);
+      buf.append("\n");
+    }
+    return buf.toString();
   }
 
   public void visitChildren(ModelHandler modelHandler) {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/model/JsonMaterialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/model/JsonMaterialization.java b/core/src/main/java/net/hydromatic/optiq/model/JsonMaterialization.java
index 72c0119..17d08fa 100644
--- a/core/src/main/java/net/hydromatic/optiq/model/JsonMaterialization.java
+++ b/core/src/main/java/net/hydromatic/optiq/model/JsonMaterialization.java
@@ -24,7 +24,7 @@ package net.hydromatic.optiq.model;
 public class JsonMaterialization {
   public String view;
   public String table;
-  public String sql;
+  public Object sql;
 
   public void accept(ModelHandler handler) {
     handler.visit(this);
@@ -34,6 +34,12 @@ public class JsonMaterialization {
   public String toString() {
     return "JsonMaterialization(table=" + table + ", view=" + view + ")";
   }
+
+  /** Returns the SQL query as a string, concatenating a list of lines if
+   * necessary. */
+  public String getSql() {
+    return JsonLattice.toString(sql);
+  }
 }
 
 // End JsonMaterialization.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/model/JsonRoot.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/model/JsonRoot.java b/core/src/main/java/net/hydromatic/optiq/model/JsonRoot.java
index 315fda6..52ae471 100644
--- a/core/src/main/java/net/hydromatic/optiq/model/JsonRoot.java
+++ b/core/src/main/java/net/hydromatic/optiq/model/JsonRoot.java
@@ -23,20 +23,22 @@ import java.util.List;
  * Root schema element.
  *
  * <p>A POJO with fields of {@link Boolean}, {@link String}, {@link ArrayList},
- * {@link java.util.LinkedHashMap}, per Jackson simple data binding.</p>
+ * {@link java.util.LinkedHashMap LinkedHashMap}, per Jackson simple data
+ * binding.</p>
  *
  * <p>Schema structure is as follows:</p>
  *
  * <pre>{@code Root}
  *   {@link JsonSchema} (in collection {@link JsonRoot#schemas schemas})
  *     {@link JsonTable} (in collection {@link JsonMapSchema#tables tables})
- *       {@link JsonColumn} (in collection {@link JsonTable#columns column}
+ *       {@link JsonColumn} (in collection {@link JsonTable#columns columns}
  *     {@link JsonView}
  *     {@link JsonFunction}  (in collection {@link JsonMapSchema#functions functions})
- *     {@link JsonLattice} (in collection {@link JsonSchema#lattices})
- *       {@link JsonMeasure} (in collection {@link JsonLattice#defaultMeasures})
- *       {@link JsonTile} (in collection {@link JsonLattice#tiles})
- *         {@link JsonMeasure} (in collection {@link JsonTile#measures})
+ *     {@link JsonLattice} (in collection {@link JsonSchema#lattices lattices})
+ *       {@link JsonMeasure} (in collection {@link JsonLattice#defaultMeasures defaultMeasures})
+ *       {@link JsonTile} (in collection {@link JsonLattice#tiles tiles})
+ *         {@link JsonMeasure} (in collection {@link JsonTile#measures measures})
+ *     {@link JsonMaterialization} (in collection {@link JsonSchema#materializations materializations})
  * </pre>
  */
 public class JsonRoot {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/model/JsonView.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/model/JsonView.java b/core/src/main/java/net/hydromatic/optiq/model/JsonView.java
index 5fc7f66..4ba9dca 100644
--- a/core/src/main/java/net/hydromatic/optiq/model/JsonView.java
+++ b/core/src/main/java/net/hydromatic/optiq/model/JsonView.java
@@ -25,7 +25,7 @@ import java.util.List;
  */
 public class JsonView extends JsonTable {
   /** SQL query that is the definition of the view. */
-  public String sql;
+  public Object sql;
 
   /** Schema name(s) to use when resolving query. If not specified, defaults
    * to current schema. */
@@ -39,6 +39,12 @@ public class JsonView extends JsonTable {
   public String toString() {
     return "JsonView(name=" + name + ")";
   }
+
+  /** Returns the SQL query as a string, concatenating a list of lines if
+   * necessary. */
+  public String getSql() {
+    return JsonLattice.toString(sql);
+  }
 }
 
 // End JsonView.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java b/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java
index 68dc7b1..d85d179 100644
--- a/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java
+++ b/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java
@@ -223,9 +223,8 @@ public class ModelHandler {
       }
       OptiqSchema optiqSchema = OptiqSchema.from(schema);
       schema.add(jsonMaterialization.view,
-          MaterializedViewTable.create(
-              optiqSchema, jsonMaterialization.sql, null,
-              jsonMaterialization.table));
+          MaterializedViewTable.create(optiqSchema,
+              jsonMaterialization.getSql(), null, jsonMaterialization.table));
     } catch (Exception e) {
       throw new RuntimeException("Error instantiating " + jsonMaterialization,
           e);
@@ -243,7 +242,7 @@ public class ModelHandler {
       }
       OptiqSchema optiqSchema = OptiqSchema.from(schema);
       Lattice.Builder latticeBuilder =
-          Lattice.builder(optiqSchema, jsonLattice.sql)
+          Lattice.builder(optiqSchema, jsonLattice.getSql())
               .auto(jsonLattice.auto)
               .algorithm(jsonLattice.algorithm);
       if (jsonLattice.rowCountEstimate != null) {
@@ -288,7 +287,7 @@ public class ModelHandler {
       final SchemaPlus schema = currentMutableSchema("view");
       final List<String> path = Util.first(jsonView.path, currentSchemaPath());
       schema.add(jsonView.name,
-          ViewTable.viewMacro(schema, jsonView.sql, path));
+          ViewTable.viewMacro(schema, jsonView.getSql(), path));
     } catch (Exception e) {
       throw new RuntimeException("Error instantiating " + jsonView, e);
     }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/net/hydromatic/optiq/runtime/Utilities.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/runtime/Utilities.java b/core/src/main/java/net/hydromatic/optiq/runtime/Utilities.java
index 5b37a2b..73e7d4d 100644
--- a/core/src/main/java/net/hydromatic/optiq/runtime/Utilities.java
+++ b/core/src/main/java/net/hydromatic/optiq/runtime/Utilities.java
@@ -30,6 +30,8 @@ public class Utilities {
   }
 
   public static boolean equal(Object o0, Object o1) {
+    // Same as java.lang.Objects.equals (JDK 1.7 and later)
+    // and com.google.common.base.Objects.equal
     return o0 == o1 || o0 != null && o0.equals(o1);
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/rel/AggregateCall.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/AggregateCall.java b/core/src/main/java/org/eigenbase/rel/AggregateCall.java
index 3edfa47..651c2f3 100644
--- a/core/src/main/java/org/eigenbase/rel/AggregateCall.java
+++ b/core/src/main/java/org/eigenbase/rel/AggregateCall.java
@@ -71,6 +71,23 @@ public class AggregateCall {
 
   //~ Methods ----------------------------------------------------------------
 
+  /** Creates an AggregateCall, inferring its type if {@code type} is null. */
+  public static AggregateCall create(SqlAggFunction aggFunction,
+      boolean distinct, List<Integer> argList, int groupCount, RelNode input,
+      RelDataType type, String name) {
+    if (type == null) {
+      final RelDataTypeFactory typeFactory =
+          input.getCluster().getTypeFactory();
+      final List<RelDataType> types =
+          SqlTypeUtil.projectTypes(input.getRowType(), argList);
+      final AggregateRelBase.AggCallBinding callBinding =
+          new AggregateRelBase.AggCallBinding(typeFactory, aggFunction, types,
+              groupCount);
+      type = aggFunction.inferReturnType(callBinding);
+    }
+    return new AggregateCall(aggFunction, distinct, argList, type, name);
+  }
+
   /**
    * Returns whether this AggregateCall is distinct, as in <code>
    * COUNT(DISTINCT empno)</code>.
@@ -203,24 +220,10 @@ public class AggregateCall {
     final SqlAggFunction sqlAgg = (SqlAggFunction) aggregation;
     // The return type of aggregate call need to be recomputed.
     // Since it might depend on the number of columns in GROUP BY.
-    RelDataType newReturnType;
-    if (oldGroupKeyCount == newGroupKeyCount) {
-      newReturnType = getType();
-    } else {
-      newReturnType = sqlAgg.inferReturnType(
-            new AggregateRelBase.AggCallBinding(
-                input.getCluster().getTypeFactory(),
-                sqlAgg,
-                SqlTypeUtil.projectTypes(input.getRowType(), aggArgs),
-                newGroupKeyCount));
-    }
-
-    return new AggregateCall(
-            aggregation,
-            isDistinct(),
-            aggArgs,
-            newReturnType,
-            getName());
+    final RelDataType newType =
+        oldGroupKeyCount == newGroupKeyCount ? type : null;
+    return create(sqlAgg, distinct, aggArgs, newGroupKeyCount, input, newType,
+        getName());
   }
 
   /** Creates a copy of this aggregate call, applying a mapping to its

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/rel/Aggregation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/Aggregation.java b/core/src/main/java/org/eigenbase/rel/Aggregation.java
index 52302f4..2af9369 100644
--- a/core/src/main/java/org/eigenbase/rel/Aggregation.java
+++ b/core/src/main/java/org/eigenbase/rel/Aggregation.java
@@ -25,6 +25,12 @@ import org.eigenbase.reltype.*;
  *
  * <p>It is used, via a {@link AggregateCall}, in an {@link AggregateRel}
  * relational operator.</p>
+ *
+ * @deprecated Use {@link org.eigenbase.sql.SqlAggFunction};
+ * after {@link org.eigenbase.util.Bug#upgrade calcite-0.9.1},
+ * {@link org.eigenbase.rel.AggregateCall} will require a {@code SqlAggFunction}
+ * and after {@link org.eigenbase.util.Bug#upgrade calcite-0.9.2}
+ * this interface will be removed.
  */
 public interface Aggregation {
   //~ Methods ----------------------------------------------------------------
@@ -34,6 +40,10 @@ public interface Aggregation {
    *
    * @param typeFactory Type factory to create the types
    * @return Array of parameter types
+   *
+   * @deprecated Use
+   * {@link org.eigenbase.sql.SqlAggFunction#getOperandTypeInference()}; will
+   * be removed after {@link org.eigenbase.util.Bug#upgrade calcite-0.9.2}.
    */
   List<RelDataType> getParameterTypes(RelDataTypeFactory typeFactory);
 
@@ -42,6 +52,10 @@ public interface Aggregation {
    *
    * @param typeFactory Type factory to create the type
    * @return Result type
+   *
+   * @deprecated Use
+   * {@link org.eigenbase.sql.SqlAggFunction#getReturnTypeInference()}; will
+   * be removed after {@link org.eigenbase.util.Bug#upgrade calcite-0.9.2}.
    */
   RelDataType getReturnType(RelDataTypeFactory typeFactory);
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java b/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java
index 7f866ab..3aeca5a 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java
@@ -21,7 +21,6 @@ import java.util.List;
 
 import org.eigenbase.rel.AggregateCall;
 import org.eigenbase.rel.AggregateRelBase;
-import org.eigenbase.rel.Aggregation;
 import org.eigenbase.rel.ProjectRelBase;
 import org.eigenbase.rel.RelNode;
 import org.eigenbase.relopt.RelOptCluster;
@@ -33,6 +32,7 @@ import org.eigenbase.relopt.RelOptTable;
 import org.eigenbase.relopt.RelOptUtil;
 import org.eigenbase.relopt.SubstitutionVisitor;
 import org.eigenbase.reltype.RelDataType;
+import org.eigenbase.sql.SqlAggFunction;
 import org.eigenbase.util.Pair;
 import org.eigenbase.util.mapping.AbstractSourceMapping;
 
@@ -40,7 +40,7 @@ import net.hydromatic.optiq.Table;
 import net.hydromatic.optiq.impl.StarTable;
 import net.hydromatic.optiq.jdbc.OptiqSchema;
 import net.hydromatic.optiq.materialize.Lattice;
-import net.hydromatic.optiq.materialize.MaterializationService;
+import net.hydromatic.optiq.materialize.TileKey;
 import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
 import net.hydromatic.optiq.prepare.RelOptTableImpl;
 import net.hydromatic.optiq.util.BitSets;
@@ -109,14 +109,14 @@ public class AggregateStarTableRule extends RelOptRule {
     final RelOptLattice lattice = call.getPlanner().getLattice(table);
     final List<Lattice.Measure> measures =
         lattice.lattice.toMeasures(aggregate.getAggCallList());
-    Pair<OptiqSchema.TableEntry, MaterializationService.TileKey> pair =
+    final Pair<OptiqSchema.TableEntry, TileKey> pair =
         lattice.getAggregate(call.getPlanner(), aggregate.getGroupSet(),
             measures);
     if (pair == null) {
       return;
     }
     final OptiqSchema.TableEntry tableEntry = pair.left;
-    final MaterializationService.TileKey tileKey = pair.right;
+    final TileKey tileKey = pair.right;
     final double rowCount = aggregate.getRows();
     final Table aggregateTable = tableEntry.getTable();
     final RelDataType aggregateTableRowType =
@@ -141,17 +141,18 @@ public class AggregateStarTableRule extends RelOptRule {
       }
       assert BitSets.contains(tileKey.dimensions, aggregate.getGroupSet());
       final List<AggregateCall> aggCalls = Lists.newArrayList();
+      BitSet groupSet = new BitSet();
+      for (int key : BitSets.toIter(aggregate.getGroupSet())) {
+        groupSet.set(BitSets.toList(tileKey.dimensions).indexOf(key));
+      }
       for (AggregateCall aggCall : aggregate.getAggCallList()) {
-        final AggregateCall copy = rollUp(aggCall, tileKey);
+        final AggregateCall copy =
+            rollUp(groupSet.cardinality(), rel, aggCall, tileKey);
         if (copy == null) {
           return;
         }
         aggCalls.add(copy);
       }
-      BitSet groupSet = new BitSet();
-      for (int key : BitSets.toIter(aggregate.getGroupSet())) {
-        groupSet.set(BitSets.toList(tileKey.dimensions).indexOf(key));
-      }
       rel = aggregate.copy(aggregate.getTraitSet(), rel, groupSet, aggCalls);
     } else if (!tileKey.measures.equals(measures)) {
       System.out.println("Using materialization "
@@ -181,10 +182,14 @@ public class AggregateStarTableRule extends RelOptRule {
     call.transformTo(rel);
   }
 
-  private static AggregateCall rollUp(AggregateCall aggregateCall,
-      MaterializationService.TileKey tileKey) {
-    final Aggregation aggregation = aggregateCall.getAggregation();
-    final Pair<Aggregation, List<Integer>> seek =
+  private static AggregateCall rollUp(int groupCount, RelNode input,
+      AggregateCall aggregateCall, TileKey tileKey) {
+    if (aggregateCall.isDistinct()) {
+      return null;
+    }
+    final SqlAggFunction aggregation =
+        (SqlAggFunction) aggregateCall.getAggregation();
+    final Pair<SqlAggFunction, List<Integer>> seek =
         Pair.of(aggregation, aggregateCall.getArgList());
     final int offset = tileKey.dimensions.cardinality();
     final ImmutableList<Lattice.Measure> measures = tileKey.measures;
@@ -194,12 +199,12 @@ public class AggregateStarTableRule extends RelOptRule {
     final int i = find(measures, seek);
   tryRoll:
     if (i >= 0) {
-      final Aggregation roll = SubstitutionVisitor.getRollup(aggregation);
+      final SqlAggFunction roll = SubstitutionVisitor.getRollup(aggregation);
       if (roll == null) {
         break tryRoll;
       }
-      return new AggregateCall(roll, false, ImmutableList.of(offset + i),
-          aggregateCall.type, aggregateCall.name);
+      return AggregateCall.create(roll, false, ImmutableList.of(offset + i),
+          groupCount, input, null, aggregateCall.name);
     }
 
     // Second, try to satisfy the aggregation based on group set columns.
@@ -213,8 +218,8 @@ public class AggregateStarTableRule extends RelOptRule {
         }
         newArgs.add(z);
       }
-      return new AggregateCall(aggregation, false, newArgs, aggregateCall.type,
-          aggregateCall.name);
+      return AggregateCall.create((SqlAggFunction) aggregation, false, newArgs,
+          groupCount, input, null, aggregateCall.name);
     }
 
     // No roll up possible.
@@ -222,7 +227,7 @@ public class AggregateStarTableRule extends RelOptRule {
   }
 
   private static int find(ImmutableList<Lattice.Measure> measures,
-      Pair<Aggregation, List<Integer>> seek) {
+      Pair<SqlAggFunction, List<Integer>> seek) {
     for (int i = 0; i < measures.size(); i++) {
       Lattice.Measure measure = measures.get(i);
       if (measure.agg.equals(seek.left)

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/rel/rules/PushAggregateThroughUnionRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/PushAggregateThroughUnionRule.java b/core/src/main/java/org/eigenbase/rel/rules/PushAggregateThroughUnionRule.java
index a48fb40..6d8796f 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/PushAggregateThroughUnionRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/PushAggregateThroughUnionRule.java
@@ -25,6 +25,11 @@ import org.eigenbase.reltype.*;
 import org.eigenbase.sql.SqlAggFunction;
 import org.eigenbase.sql.fun.*;
 
+import net.hydromatic.linq4j.Ord;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
 /**
  * PushAggregateThroughUnionRule implements the rule for pushing an
  * {@link AggregateRel} past a non-distinct {@link UnionRel}.
@@ -73,8 +78,7 @@ public class PushAggregateThroughUnionRule extends RelOptRule {
     RelOptCluster cluster = unionRel.getCluster();
 
     List<AggregateCall> transformedAggCalls =
-        transformAggCalls(
-            aggRel.getCluster().getTypeFactory(),
+        transformAggCalls(aggRel,
             aggRel.getGroupSet().cardinality(),
             aggRel.getAggCallList());
     if (transformedAggCalls == null) {
@@ -132,45 +136,34 @@ public class PushAggregateThroughUnionRule extends RelOptRule {
     call.transformTo(castRel);
   }
 
-  private List<AggregateCall> transformAggCalls(
-      RelDataTypeFactory typeFactory,
-      int nGroupCols,
+  private List<AggregateCall> transformAggCalls(RelNode input, int groupCount,
       List<AggregateCall> origCalls) {
-    List<AggregateCall> newCalls = new ArrayList<AggregateCall>();
-    int iInput = nGroupCols;
-    for (AggregateCall origCall : origCalls) {
+    final List<AggregateCall> newCalls = Lists.newArrayList();
+    for (Ord<AggregateCall> ord: Ord.zip(origCalls)) {
+      final AggregateCall origCall = ord.e;
       if (origCall.isDistinct()
           || !SUPPORTED_AGGREGATES.containsKey(origCall.getAggregation()
               .getClass())) {
         return null;
       }
-      Aggregation aggFun;
-      RelDataType aggType;
-      if (origCall.getAggregation().getName().equals("COUNT")) {
-        aggFun = new SqlSumEmptyIsZeroAggFunction(origCall.getType());
-        SqlAggFunction af = (SqlAggFunction) aggFun;
-        final AggregateRelBase.AggCallBinding binding =
-            new AggregateRelBase.AggCallBinding(typeFactory, af,
-                Collections.singletonList(origCall.getType()),
-                nGroupCols);
+      final SqlAggFunction aggFun;
+      final RelDataType aggType;
+      if (origCall.getAggregation() == SqlStdOperatorTable.COUNT) {
+        aggFun = SqlStdOperatorTable.SUM0;
         // count(any) is always not null, however nullability of sum might
         // depend on the number of columns in GROUP BY.
         // Here we use SUM0 since we are sure we will not face nullable
         // inputs nor we'll face empty set.
-        aggType = af.inferReturnType(binding);
+        aggType = null;
       } else {
-        aggFun = origCall.getAggregation();
+        aggFun = (SqlAggFunction) origCall.getAggregation();
         aggType = origCall.getType();
       }
       AggregateCall newCall =
-          new AggregateCall(
-              aggFun,
-              origCall.isDistinct(),
-              Collections.singletonList(iInput),
-              aggType,
-              origCall.getName());
+          AggregateCall.create(aggFun, origCall.isDistinct(),
+              ImmutableList.of(groupCount + ord.i), groupCount, input,
+              aggType, origCall.getName());
       newCalls.add(newCall);
-      ++iInput;
     }
     return newCalls;
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/rel/rules/ReduceAggregatesRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/ReduceAggregatesRule.java b/core/src/main/java/org/eigenbase/rel/rules/ReduceAggregatesRule.java
index 11f9c15..78a080a 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/ReduceAggregatesRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/ReduceAggregatesRule.java
@@ -151,6 +151,11 @@ public class ReduceAggregatesRule extends RelOptRule {
             oldAggRel.getRowType().getFieldNames());
 
     ruleCall.transformTo(projectRel);
+    // If we old AggRel(SUM(0)) transforms to new AggRel($SUM0($0)) both will
+    // have the same cost, but we prefer new. Before we set the importance of
+    // old to 0, we were getting different results between JDK 1.7 and 1.8
+    // because of some arbitrary orderings of rels within an equivalence set.
+    ruleCall.getPlanner().setImportance(oldAggRel, 0d);
   }
 
   private RexNode reduceAgg(
@@ -246,14 +251,14 @@ public class ReduceAggregatesRule extends RelOptRule {
             oldCall.getArgList(),
             sumType,
             null);
-    SqlAggFunction countAgg = SqlStdOperatorTable.COUNT;
-    RelDataType countType = countAgg.getReturnType(typeFactory);
     AggregateCall countCall =
-        new AggregateCall(
-            countAgg,
+        AggregateCall.create(
+            SqlStdOperatorTable.COUNT,
             oldCall.isDistinct(),
             oldCall.getArgList(),
-            countType,
+            oldAggRel.getGroupCount(),
+            oldAggRel.getChild(),
+            null,
             null);
 
     // NOTE:  these references are with respect to the output
@@ -295,25 +300,24 @@ public class ReduceAggregatesRule extends RelOptRule {
         getFieldType(
             oldAggRel.getChild(),
             arg);
-    RelDataType sumType =
+    final RelDataType sumType =
         typeFactory.createTypeWithNullability(
             argType, argType.isNullable());
-    SqlAggFunction sumZeroAgg = new SqlSumEmptyIsZeroAggFunction(sumType);
-    AggregateCall sumZeroCall =
+    final AggregateCall sumZeroCall =
         new AggregateCall(
-            sumZeroAgg,
+            SqlStdOperatorTable.SUM0,
             oldCall.isDistinct(),
             oldCall.getArgList(),
             sumType,
             null);
-    SqlAggFunction countAgg = SqlStdOperatorTable.COUNT;
-    RelDataType countType = countAgg.getReturnType(typeFactory);
-    AggregateCall countCall =
-        new AggregateCall(
-            countAgg,
+    final AggregateCall countCall =
+        AggregateCall.create(
+            SqlStdOperatorTable.COUNT,
             oldCall.isDistinct(),
             oldCall.getArgList(),
-            countType,
+            oldAggRel.getGroupCount(),
+            oldAggRel,
+            null,
             null);
 
     // NOTE:  these references are with respect to the output
@@ -420,14 +424,14 @@ public class ReduceAggregatesRule extends RelOptRule {
         rexBuilder.makeCall(
             SqlStdOperatorTable.MULTIPLY, sumArg, sumArg);
 
-    final SqlAggFunction countAgg = SqlStdOperatorTable.COUNT;
-    final RelDataType countType = countAgg.getReturnType(typeFactory);
     final AggregateCall countArgAggCall =
-        new AggregateCall(
-            countAgg,
+        AggregateCall.create(
+            SqlStdOperatorTable.COUNT,
             oldCall.isDistinct(),
             oldCall.getArgList(),
-            countType,
+            oldAggRel.getGroupCount(),
+            oldAggRel.getChild(),
+            null,
             null);
     final RexNode countArg =
         rexBuilder.addAggCall(

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java b/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
index a03d273..be7049a 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
@@ -26,6 +26,7 @@ import net.hydromatic.optiq.config.OptiqConnectionConfig;
 import net.hydromatic.optiq.jdbc.OptiqSchema;
 import net.hydromatic.optiq.materialize.Lattice;
 import net.hydromatic.optiq.materialize.MaterializationService;
+import net.hydromatic.optiq.materialize.TileKey;
 
 /**
  * Use of a lattice by the query optimizer.
@@ -69,8 +70,7 @@ public class RelOptLattice {
    * @param measureList Calls to aggregate functions
    * @return Materialized table
    */
-  public
-  Pair<OptiqSchema.TableEntry, MaterializationService.TileKey> getAggregate(
+  public Pair<OptiqSchema.TableEntry, TileKey> getAggregate(
       RelOptPlanner planner, BitSet groupSet,
       List<Lattice.Measure> measureList) {
     final OptiqConnectionConfig config =
@@ -81,7 +81,8 @@ public class RelOptLattice {
     final MaterializationService service = MaterializationService.instance();
     boolean create = lattice.auto && config.createMaterializations();
     final OptiqSchema schema = starRelOptTable.unwrap(OptiqSchema.class);
-    return service.defineTile(lattice, groupSet, measureList, schema, create);
+    return service.defineTile(lattice, groupSet, measureList, schema, create,
+        false);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
index 2f3bcb8..ec81f86 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
@@ -293,17 +293,13 @@ public abstract class RelOptUtil {
               true,
               SqlMinMaxAggFunction.MINMAX_COMPARABLE);
 
-      RelDataType returnType =
-          minFunction.inferReturnType(
-              new AggregateRelBase.AggCallBinding(
-                  typeFactory, minFunction, argTypes, 0));
-
       final AggregateCall aggCall =
-          new AggregateCall(
-              minFunction,
+          AggregateCall.create(minFunction,
               false,
               ImmutableList.of(0),
-              returnType,
+              0,
+              ret,
+              null,
               extraName);
 
       ret =
@@ -372,21 +368,17 @@ public abstract class RelOptUtil {
       final List<RelDataType> argTypes =
           ImmutableList.of(typeFactory.createSqlType(SqlTypeName.BOOLEAN));
 
-      SqlAggFunction minFunction =
+      final SqlAggFunction minFunction =
           new SqlMinMaxAggFunction(argTypes, true,
               SqlMinMaxAggFunction.MINMAX_COMPARABLE);
 
-      RelDataType returnType =
-          minFunction.inferReturnType(
-              new AggregateRelBase.AggCallBinding(
-                  typeFactory, minFunction, argTypes, projectedKeyCount));
-
       final AggregateCall aggCall =
-          new AggregateCall(
-              minFunction,
+          AggregateCall.create(minFunction,
               false,
               ImmutableList.of(projectedKeyCount),
-              returnType,
+              projectedKeyCount,
+              ret,
+              null,
               null);
 
       ret = new AggregateRel(

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/relopt/SubstitutionVisitor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/SubstitutionVisitor.java b/core/src/main/java/org/eigenbase/relopt/SubstitutionVisitor.java
index 3adac80..4d99b40 100644
--- a/core/src/main/java/org/eigenbase/relopt/SubstitutionVisitor.java
+++ b/core/src/main/java/org/eigenbase/relopt/SubstitutionVisitor.java
@@ -24,6 +24,7 @@ import org.eigenbase.rel.*;
 import org.eigenbase.rel.rules.RemoveTrivialProjectRule;
 import org.eigenbase.reltype.*;
 import org.eigenbase.rex.*;
+import org.eigenbase.sql.SqlAggFunction;
 import org.eigenbase.sql.SqlKind;
 import org.eigenbase.sql.fun.SqlStdOperatorTable;
 import org.eigenbase.sql.validate.SqlValidatorUtil;
@@ -1215,13 +1216,14 @@ public class SubstitutionVisitor {
     }
   }
 
-  public static Aggregation getRollup(Aggregation aggregation) {
+  public static SqlAggFunction getRollup(Aggregation aggregation) {
     if (aggregation == SqlStdOperatorTable.SUM
         || aggregation == SqlStdOperatorTable.MIN
-        || aggregation == SqlStdOperatorTable.MAX) {
-      return aggregation;
+        || aggregation == SqlStdOperatorTable.MAX
+        || aggregation == SqlStdOperatorTable.SUM0) {
+      return (SqlAggFunction) aggregation;
     } else if (aggregation == SqlStdOperatorTable.COUNT) {
-      return SqlStdOperatorTable.SUM;
+      return SqlStdOperatorTable.SUM0;
     } else {
       return null;
     }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/sql/fun/SqlStdOperatorTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/eigenbase/sql/fun/SqlStdOperatorTable.java
index 7f7bf73..5a75975 100644
--- a/core/src/main/java/org/eigenbase/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/eigenbase/sql/fun/SqlStdOperatorTable.java
@@ -775,7 +775,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
    * <code>SUM0</code> aggregate function.
    */
   public static final SqlAggFunction SUM0 =
-      new SqlSumEmptyIsZeroAggFunction(null);
+      new SqlSumEmptyIsZeroAggFunction();
 
   //-------------------------------------------------------------
   // WINDOW Rank Functions

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/main/java/org/eigenbase/sql/fun/SqlSumEmptyIsZeroAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/sql/fun/SqlSumEmptyIsZeroAggFunction.java b/core/src/main/java/org/eigenbase/sql/fun/SqlSumEmptyIsZeroAggFunction.java
index 92be32b..1cff679 100644
--- a/core/src/main/java/org/eigenbase/sql/fun/SqlSumEmptyIsZeroAggFunction.java
+++ b/core/src/main/java/org/eigenbase/sql/fun/SqlSumEmptyIsZeroAggFunction.java
@@ -31,35 +31,25 @@ import com.google.common.collect.ImmutableList;
  * Count</code> to implement <code>Sum</code>.
  */
 public class SqlSumEmptyIsZeroAggFunction extends SqlAggFunction {
-  //~ Instance fields --------------------------------------------------------
-
-  private final RelDataType type;
-
   //~ Constructors -----------------------------------------------------------
 
-  public SqlSumEmptyIsZeroAggFunction(RelDataType type) {
-    super(
-        "$SUM0",
+  SqlSumEmptyIsZeroAggFunction() {
+    super("$SUM0",
         SqlKind.OTHER_FUNCTION,
         ReturnTypes.ARG0,
         null,
         OperandTypes.NUMERIC,
         SqlFunctionCategory.NUMERIC);
-    this.type = type;
   }
 
   //~ Methods ----------------------------------------------------------------
 
   public List<RelDataType> getParameterTypes(RelDataTypeFactory typeFactory) {
-    return ImmutableList.of(type);
-  }
-
-  public RelDataType getType() {
-    return type;
+    return ImmutableList.of(typeFactory.createSqlType(SqlTypeName.ANY));
   }
 
   public RelDataType getReturnType(RelDataTypeFactory typeFactory) {
-    return type;
+    return typeFactory.createSqlType(SqlTypeName.ANY);
   }
 }
 


[9/9] git commit: [CALCITE-397] "SELECT DISTINCT *" on reflective schema gives ClassCastException at runtime

Posted by jh...@apache.org.
[CALCITE-397] "SELECT DISTINCT *" on reflective schema gives ClassCastException at runtime


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

Branch: refs/heads/master
Commit: 1f91bbf811d45d5c6832f23ba27972f2e304537b
Parents: 41215c2
Author: Julian Hyde <jh...@apache.org>
Authored: Sat Oct 25 12:08:33 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Sat Oct 25 12:08:33 2014 -0700

----------------------------------------------------------------------
 .../hydromatic/optiq/rules/java/JavaRowFormat.java  |  6 ++----
 .../net/hydromatic/optiq/rules/java/JavaRules.java  | 12 +++++-------
 .../net/hydromatic/optiq/rules/java/PhysType.java   |  4 ++++
 .../hydromatic/optiq/rules/java/PhysTypeImpl.java   | 16 ++++++++++++++--
 .../java/net/hydromatic/optiq/test/JdbcTest.java    |  8 ++++----
 5 files changed, 29 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/1f91bbf8/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRowFormat.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRowFormat.java b/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRowFormat.java
index 34cb92d..6773ff3 100644
--- a/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRowFormat.java
+++ b/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRowFormat.java
@@ -187,10 +187,8 @@ public enum JavaRowFormat {
           Object.class, stripCasts(expressions));
     }
 
-    @Override
-    public Expression comparer() {
-      return Expressions.call(
-          null, BuiltinMethod.ARRAY_COMPARER.method);
+    @Override public Expression comparer() {
+      return Expressions.call(BuiltinMethod.ARRAY_COMPARER.method);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/1f91bbf8/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java b/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
index f6a4f0d..1384e53 100644
--- a/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
+++ b/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
@@ -1338,9 +1338,9 @@ public class JavaRules {
         }
       }
       for (final AggImpState agg : aggs) {
-        results.add(agg.implementor.implementResult(
-            agg.context,
-            new AggResultContextImpl(resultBlock, agg.state)));
+        results.add(
+            agg.implementor.implementResult(agg.context,
+                new AggResultContextImpl(resultBlock, agg.state)));
       }
       resultBlock.add(physType.record(results));
       if (keyArity == 0) {
@@ -1369,7 +1369,7 @@ public class JavaRules {
             Expressions.return_(
                 null,
                 Expressions.call(
-                    childExp,
+                    inputPhysType.convertTo(childExp, physType),
                     BuiltinMethod.DISTINCT.method,
                     Expressions.<Expression>list()
                         .appendIfNotNull(physType.comparer()))));
@@ -1998,9 +1998,7 @@ public class JavaRules {
         final JavaTypeFactory typeFactory =
             (JavaTypeFactory) getCluster().getTypeFactory();
         PhysType physType =
-            PhysTypeImpl.of(
-                typeFactory,
-                table.getRowType(),
+            PhysTypeImpl.of(typeFactory, table.getRowType(),
                 JavaRowFormat.CUSTOM);
         List<Expression> expressionList = new ArrayList<Expression>();
         final PhysType childPhysType = result.physType;

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/1f91bbf8/core/src/main/java/net/hydromatic/optiq/rules/java/PhysType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/rules/java/PhysType.java b/core/src/main/java/net/hydromatic/optiq/rules/java/PhysType.java
index bf97532..51e60a0 100644
--- a/core/src/main/java/net/hydromatic/optiq/rules/java/PhysType.java
+++ b/core/src/main/java/net/hydromatic/optiq/rules/java/PhysType.java
@@ -164,6 +164,10 @@ public interface PhysType {
   /** Returns a copy of this type that allows nulls if {@code nullable} is
    * true. */
   PhysType makeNullable(boolean nullable);
+
+  /** Converts an enumerable of this physical type to an enumerable that uses a
+   * given physical type for its rows. */
+  Expression convertTo(Expression expression, PhysType targetPhysType);
 }
 
 // End PhysType.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/1f91bbf8/core/src/main/java/net/hydromatic/optiq/rules/java/PhysTypeImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/rules/java/PhysTypeImpl.java b/core/src/main/java/net/hydromatic/optiq/rules/java/PhysTypeImpl.java
index d307299..e30ce21 100644
--- a/core/src/main/java/net/hydromatic/optiq/rules/java/PhysTypeImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/rules/java/PhysTypeImpl.java
@@ -29,6 +29,7 @@ import org.eigenbase.reltype.RelDataType;
 import org.eigenbase.reltype.RelDataTypeFactory;
 import org.eigenbase.reltype.RelDataTypeField;
 import org.eigenbase.util.Pair;
+import org.eigenbase.util.Util;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
@@ -130,8 +131,7 @@ public class PhysTypeImpl implements PhysType {
       return Expressions.call(BuiltinMethod.IDENTITY_SELECTOR.method);
     default:
       return Expressions.lambda(Function1.class,
-          targetPhysType.record(fieldReferences(parameter, fields)),
-          parameter);
+          targetPhysType.record(fieldReferences(parameter, fields)), parameter);
     }
   }
 
@@ -178,6 +178,18 @@ public class PhysTypeImpl implements PhysType {
         Primitive.box(javaRowClass), format);
   }
 
+  public Expression convertTo(Expression exp, PhysType targetPhysType) {
+    final JavaRowFormat targetFormat = targetPhysType.getFormat();
+    if (format == targetFormat) {
+      return exp;
+    }
+    final ParameterExpression o_ =
+        Expressions.parameter(javaRowClass, "o");
+    final int fieldCount = rowType.getFieldCount();
+    return Expressions.call(exp, BuiltinMethod.SELECT.method,
+        generateSelector(o_, Util.range(fieldCount), targetFormat));
+  }
+
   public Pair<Expression, Expression> generateCollationKey(
       final List<RelFieldCollation> collations) {
     final Expression selector;

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/1f91bbf8/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java b/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
index c2c09cc..2dca18e 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
@@ -2932,16 +2932,16 @@ public class JdbcTest {
   }
 
   /** Test case for
-   * <a href="https://issues.apache.org/jira/browse/CALCITE-397">CALCITE-397</a>,
-   * "SELECT DISTINCT *" gives ClassCastException at runtime". */
-  @Ignore("CALCITE-397")
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-397">[CALCITE-397]
+   * "SELECT DISTINCT *" on reflective schema gives ClassCastException at
+   * runtime</a>. */
   @Test public void testSelectDistinctStar() {
     OptiqAssert.that()
         .with(OptiqAssert.Config.REGULAR)
         .query(
             "select distinct *\n"
             + "from \"hr\".\"emps\"\n")
-        .returnsCount(5)
+        .returnsCount(4)
         .planContains(".distinct(");
   }
 


[5/9] git commit: sqlline: Looking for class-path in inconsistent locations.

Posted by jh...@apache.org.
sqlline: Looking for class-path in inconsistent locations.


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

Branch: refs/heads/master
Commit: 667ca64606c88b3f2690fb6b03a4bc2a5a93713d
Parents: be79857
Author: Julian Hyde <jh...@apache.org>
Authored: Tue Oct 7 18:04:05 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Oct 24 09:44:30 2014 -0700

----------------------------------------------------------------------
 sqlline | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/667ca646/sqlline
----------------------------------------------------------------------
diff --git a/sqlline b/sqlline
index 3c0f4ce..4b13a0c 100755
--- a/sqlline
+++ b/sqlline
@@ -26,9 +26,10 @@ case $(uname -s) in
 (*) cygwin=;;
 esac
 
-# Build classpath on first call. (To force rebuild, remove .classpath.txt.)
+# Build classpath on first call.
+# (To force rebuild, remove target/fullclasspath.txt.)
 cd $(dirname $0)
-if [ ! -f .fullclasspath.txt ]; then
+if [ ! -f target/fullclasspath.txt ]; then
     mvn dependency:build-classpath -Dmdep.outputFile=target/classpath.txt
     awk -v RS=: -v ORS=: '{if(!m[$0]) {m[$0]=1; print}}' \
         target/classpath.txt \


[6/9] git commit: Update DiffRepository documentation.

Posted by jh...@apache.org.
Update DiffRepository documentation.


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

Branch: refs/heads/master
Commit: be79857df651036d6fd04ac3defe3202e6ec2ea8
Parents: 17f3e29
Author: Julian Hyde <jh...@apache.org>
Authored: Tue Oct 7 18:02:33 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Oct 24 09:44:30 2014 -0700

----------------------------------------------------------------------
 .../java/org/eigenbase/test/DiffRepository.java | 27 ++++++++++++++------
 .../org/eigenbase/test/RelOptRulesTest.java     | 17 +++++++++---
 2 files changed, 32 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/be79857d/core/src/test/java/org/eigenbase/test/DiffRepository.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/DiffRepository.java b/core/src/test/java/org/eigenbase/test/DiffRepository.java
index a80b436..f131833 100644
--- a/core/src/test/java/org/eigenbase/test/DiffRepository.java
+++ b/core/src/test/java/org/eigenbase/test/DiffRepository.java
@@ -57,7 +57,7 @@ import org.xml.sax.*;
  * </code></pre></blockquote>
  *
  * <p>There is an accompanying reference file named after the class,
- * <code>com/acme/test/MyTest.ref.xml</code>:</p>
+ * <code>src/test/resources/com/acme/test/MyTest.xml</code>:</p>
  *
  * <blockquote><pre><code>
  *
@@ -80,11 +80,22 @@ import org.xml.sax.*;
  *
  * </code></pre></blockquote>
  *
- * <p>If any of the testcases fails, a log file is generated, called <code>
- * com/acme/test/MyTest.log.xml</code> containing the actual output. The log
+ * <p>If any of the testcases fails, a log file is generated, called
+ * <code>target/surefire/com/acme/test/MyTest.xml</code>, containing the actual
+ * output.</p>
+ *
+ * <p>(Maven sometimes removes this file; if it is not present, run maven with
+ * an extra {@code -X} flag.
+ * See <a href="http://jira.codehaus.org/browse/SUREFIRE-846">SUREFIRE-846</a>
+ * for details.)</p>
+ *
+ * <p>The log
  * file is otherwise identical to the reference log, so once the log file has
  * been verified, it can simply be copied over to become the new reference
- * log.</p>
+ * log:</p>
+ *
+ * <blockquote><code>cp target/surefire/com/acme/test/MyTest.xml
+ * src/test/resources/com/acme/test/MyTest.xml</code></blockquote>
  *
  * <p>If a resource or testcase does not exist, <code>DiffRepository</code>
  * creates them in the log file. Because DiffRepository is so forgiving, it is
@@ -92,7 +103,8 @@ import org.xml.sax.*;
  *
  * <p>The {@link #lookup} method ensures that all test cases share the same
  * instance of the repository. This is important more than one one test case
- * fails. The shared instance ensures that the generated <code>.log.xml</code>
+ * fails. The shared instance ensures that the generated
+ * <code>target/surefire/com/acme/test/MyTest.xml</code>
  * file contains the actual for <em>both</em> test cases.
  */
 public class DiffRepository {
@@ -453,7 +465,7 @@ public class DiffRepository {
   }
 
   /**
-   * Flush the reference document to the file system.
+   * Flushes the reference document to the file system.
    */
   private void flushDoc() {
     FileWriter w = null;
@@ -463,8 +475,7 @@ public class DiffRepository {
       w = new FileWriter(logFile);
       write(doc, w);
     } catch (IOException e) {
-      throw Util.newInternal(
-          e,
+      throw Util.newInternal(e,
           "error while writing test reference log '" + logFile + "'");
     } finally {
       if (w != null) {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/be79857d/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java b/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
index 1d35d91..7c7472d 100644
--- a/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
@@ -100,14 +100,23 @@ import static org.junit.Assert.assertTrue;
  * examples. You'll have to come up with an SQL statement to which your rule
  * will apply in a meaningful way. See {@link SqlToRelTestBase} class comments
  * for details on the schema.
+ *
  * <li>Run the test. It should fail. Inspect the output in
- * RelOptRulesTest.log.xml; verify that the "planBefore" is the correct
+ * {@code target/surefire/.../RelOptRulesTest.xml}.
+ * (If you are running using maven and this file does not exist, add a
+ * {@code -X} flag to the maven command line.)
+ *
+ * <li>Verify that the "planBefore" is the correct
  * translation of your SQL, and that it contains the pattern on which your rule
- * is supposed to fire. If all is well, check out RelOptRulesTest.ref.xml and
- * replace it with the new .log.xml.
+ * is supposed to fire. If all is well, replace
+ * {@code src/test/resources/.../RelOptRulesTest.xml} and
+ * with the new {@code target/surefire/.../RelOptRulesTest.xml}.
+ *
  * <li>Run the test again. It should fail again, but this time it should contain
  * a "planAfter" entry for your rule. Verify that your rule applied its
- * transformation correctly, and then update the .ref.xml file again.
+ * transformation correctly, and then update the
+ * {@code src/test/resources/.../RelOptRulesTest.xml} file again.
+ *
  * <li>Run the test one last time; this time it should pass.
  * </ol>
  */


[4/9] git commit: HOWTO: Modify release instructions.

Posted by jh...@apache.org.
HOWTO: Modify release instructions.


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

Branch: refs/heads/master
Commit: 17f3e295e00c82adf2014b5bf26b6f3472529420
Parents: 9f73670
Author: Julian Hyde <jh...@apache.org>
Authored: Tue Oct 7 18:07:53 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Oct 24 09:44:20 2014 -0700

----------------------------------------------------------------------
 doc/HOWTO.md | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/17f3e295/doc/HOWTO.md
----------------------------------------------------------------------
diff --git a/doc/HOWTO.md b/doc/HOWTO.md
index cab8aff..ea46082 100644
--- a/doc/HOWTO.md
+++ b/doc/HOWTO.md
@@ -379,6 +379,9 @@ Verify the staged artifacts in the Nexus repository:
 * Enterprise &rarr; Staging
 * Staging tab &rarr; Name column &rarr; org.apache.calcite
 * Navigate through the artifact tree and make sure the .jar, .pom, .asc files are present
+* Press the 'Close' button to publish the repository at
+ https://repository.apache.org/content/repositories/orgapachecalcite-1000
+ (or a similar URL)
 
 Upload the artifacts to a staging area (in this case, your
 people.apache.org home directory):
@@ -461,7 +464,7 @@ Release vote on dev list
 
 ```
 To: dev@calcite.incubator.apache.org
-Subject: Release apache-calcite-X.Y.Z-incubating (release candidate N)
+Subject: [VOTE] Release apache-calcite-X.Y.Z-incubating (release candidate N)
 
 Hi all,
 
@@ -502,7 +505,11 @@ After vote finishes, send out the result:
 
 ```
 Subject: [RESULT] [VOTE] Release apache-calcite-X.Y.Z-incubating (release candidate N)
+<<<<<<< HEAD
 To: dev@calcite.incubator.apache.org
+=======
+To: dev@optiq.incubator.apache.org
+>>>>>>> 9f95c4f... HOWTO: Modify release instructions.
 
 Thanks to everyone who has tested the release candidate and given
 their comments and votes.
@@ -544,7 +551,7 @@ is open for 72 hours, or until the necessary number of votes (3 +1)
 is reached.
 
 [ ] +1 Release this package as Apache Calcite X.Y.Z incubating
-[ ] -1 Do not release this package because ...
+[ ] -1 Do not release this package because...
 
 Apache Calcite PPMC
 
@@ -559,7 +566,7 @@ No -1 votes
 http://mail-archives.apache.org/mod_mbox/incubator-calcite-dev/201408.mbox/MESSAGE-URI
 
 Artifacts:
-http://people.apache.org/~jhyde/calcite-X.Y.Z-incubating-rcN/
+http://people.apache.org/~jhyde/apache-calcite-X.Y.Z-incubating-rcN/
 
 ```
 


[2/9] git commit: [CALCITE-436] Simpler SPI to query Table

Posted by jh...@apache.org.
[CALCITE-436] Simpler SPI to query Table


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

Branch: refs/heads/master
Commit: 263640ff6c409d6a13225a01b0042afbc5226c2b
Parents: e4fcf2a
Author: Julian Hyde <jh...@apache.org>
Authored: Sat Oct 11 17:45:52 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Oct 24 09:25:49 2014 -0700

----------------------------------------------------------------------
 .../hydromatic/avatica/AvaticaConnection.java   |   1 -
 .../avatica/AvaticaPrepareResult.java           |   2 +
 .../net/hydromatic/optiq/BuiltinMethod.java     |   7 +
 .../net/hydromatic/optiq/FilterableTable.java   |  44 ++
 .../optiq/ProjectableFilterableTable.java       |  63 +++
 .../net/hydromatic/optiq/ScannableTable.java    |  31 ++
 .../main/java/net/hydromatic/optiq/Schemas.java |  49 ++-
 .../optiq/impl/interpreter/Interpreter.java     | 130 +++++-
 .../optiq/impl/interpreter/Nodes.java           | 116 ++++-
 .../optiq/impl/interpreter/ProjectNode.java     |   2 +-
 .../hydromatic/optiq/impl/interpreter/Row.java  |  29 +-
 .../optiq/impl/interpreter/ScanNode.java        |  61 ++-
 .../optiq/impl/interpreter/SortNode.java        |   7 +-
 .../net/hydromatic/optiq/jdbc/MetaImpl.java     |  23 +-
 .../optiq/jdbc/OptiqConnectionImpl.java         |  18 +-
 .../net/hydromatic/optiq/jdbc/OptiqPrepare.java |   7 +
 .../optiq/prepare/OptiqPrepareImpl.java         |  11 +-
 .../optiq/prepare/RelOptTableImpl.java          |  39 +-
 .../optiq/rules/java/EnumerableRel.java         |  33 ++
 .../rules/java/EnumerableRelImplementor.java    |  45 +-
 .../hydromatic/optiq/rules/java/JavaRules.java  | 123 +++++-
 .../hydromatic/optiq/runtime/Enumerables.java   |  30 +-
 .../optiq/runtime/EnumeratorCursor.java         |   2 +-
 .../net/hydromatic/optiq/tools/Programs.java    |   1 +
 .../metadata/RelMdPercentageOriginalRows.java   |   5 +
 .../eigenbase/rel/rules/FilterTableRule.java    | 168 +++++++
 .../eigenbase/rel/rules/ProjectTableRule.java   | 167 +++++++
 .../rel/rules/PushProjectPastFilterRule.java    |   2 +-
 .../rel/rules/PushProjectPastJoinRule.java      |   2 +-
 .../org/eigenbase/rel/rules/PushProjector.java  |  14 +-
 .../java/org/eigenbase/relopt/RelOptUtil.java   |   6 +-
 .../resource/EigenbaseNewResource.java          |   6 +
 .../main/java/org/eigenbase/rex/RexProgram.java |  18 +
 core/src/main/java/org/eigenbase/util/Bug.java  |  21 +-
 .../resource/EigenbaseResource.properties       |   2 +
 .../optiq/impl/generate/RangeTable.java         |  12 +-
 .../hydromatic/optiq/test/InterpreterTest.java  |  39 +-
 .../net/hydromatic/optiq/test/OptiqSuite.java   |   1 +
 .../optiq/test/ReflectiveSchemaTest.java        |   7 +-
 .../optiq/test/ScannableTableTest.java          | 433 +++++++++++++++++++
 40 files changed, 1635 insertions(+), 142 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/avatica/src/main/java/net/hydromatic/avatica/AvaticaConnection.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/net/hydromatic/avatica/AvaticaConnection.java b/avatica/src/main/java/net/hydromatic/avatica/AvaticaConnection.java
index 1814f09..411e250 100644
--- a/avatica/src/main/java/net/hydromatic/avatica/AvaticaConnection.java
+++ b/avatica/src/main/java/net/hydromatic/avatica/AvaticaConnection.java
@@ -434,7 +434,6 @@ public abstract class AvaticaConnection implements Connection {
       return statement.getParameterValues();
     }
   }
-
 }
 
 // End AvaticaConnection.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/avatica/src/main/java/net/hydromatic/avatica/AvaticaPrepareResult.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/net/hydromatic/avatica/AvaticaPrepareResult.java b/avatica/src/main/java/net/hydromatic/avatica/AvaticaPrepareResult.java
index 1cb3538..9ce74b2 100644
--- a/avatica/src/main/java/net/hydromatic/avatica/AvaticaPrepareResult.java
+++ b/avatica/src/main/java/net/hydromatic/avatica/AvaticaPrepareResult.java
@@ -17,6 +17,7 @@
 package net.hydromatic.avatica;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * Result of preparing a statement.
@@ -25,6 +26,7 @@ public interface AvaticaPrepareResult {
   List<ColumnMetaData> getColumnList();
   String getSql();
   List<AvaticaParameter> getParameterList();
+  Map<String, Object> getInternalParameters();
 }
 
 // End AvaticaPrepareResult.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/BuiltinMethod.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/BuiltinMethod.java b/core/src/main/java/net/hydromatic/optiq/BuiltinMethod.java
index 37063cf..9ed9b8f 100644
--- a/core/src/main/java/net/hydromatic/optiq/BuiltinMethod.java
+++ b/core/src/main/java/net/hydromatic/optiq/BuiltinMethod.java
@@ -22,6 +22,7 @@ import net.hydromatic.linq4j.expressions.Primitive;
 import net.hydromatic.linq4j.expressions.Types;
 import net.hydromatic.linq4j.function.*;
 
+import net.hydromatic.optiq.impl.interpreter.Row;
 import net.hydromatic.optiq.impl.java.ReflectiveSchema;
 import net.hydromatic.optiq.impl.jdbc.JdbcSchema;
 import net.hydromatic.optiq.runtime.*;
@@ -56,16 +57,22 @@ public enum BuiltinMethod {
   SCHEMA_GET_SUB_SCHEMA(Schema.class, "getSubSchema", String.class),
   SCHEMA_GET_TABLE(Schema.class, "getTable", String.class),
   SCHEMA_PLUS_UNWRAP(SchemaPlus.class, "unwrap", Class.class),
+  SCHEMAS_ENUMERABLE(Schemas.class, "enumerable", ScannableTable.class,
+      DataContext.class),
+  SCHEMAS_ENUMERABLE2(Schemas.class, "enumerable", FilterableTable.class,
+      DataContext.class),
   SCHEMAS_QUERYABLE(Schemas.class, "queryable", DataContext.class,
       SchemaPlus.class, Class.class, String.class),
   REFLECTIVE_SCHEMA_GET_TARGET(ReflectiveSchema.class, "getTarget"),
   DATA_CONTEXT_GET(DataContext.class, "get", String.class),
   DATA_CONTEXT_GET_ROOT_SCHEMA(DataContext.class, "getRootSchema"),
   JDBC_SCHEMA_DATA_SOURCE(JdbcSchema.class, "getDataSource"),
+  ROW_VALUE(Row.class, "getObject", int.class),
   RESULT_SET_ENUMERABLE_OF(ResultSetEnumerable.class, "of", DataSource.class,
       String.class, Function1.class),
   JOIN(ExtendedEnumerable.class, "join", Enumerable.class, Function1.class,
       Function1.class, Function2.class),
+  SLICE0(Enumerables.class, "slice0", Enumerable.class),
   SEMI_JOIN(Enumerables.class, "semiJoin", Enumerable.class, Enumerable.class,
       Function1.class, Function1.class),
   SELECT(ExtendedEnumerable.class, "select", Function1.class),

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/FilterableTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/FilterableTable.java b/core/src/main/java/net/hydromatic/optiq/FilterableTable.java
new file mode 100644
index 0000000..7bc3a26
--- /dev/null
+++ b/core/src/main/java/net/hydromatic/optiq/FilterableTable.java
@@ -0,0 +1,44 @@
+/*
+ * 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 net.hydromatic.optiq;
+
+import net.hydromatic.linq4j.Enumerable;
+
+import org.eigenbase.rex.RexNode;
+
+import java.util.List;
+
+/**
+ * Table that can be scanned, optionally applying supplied filter expressions,
+ * without creating an intermediate relational expression.
+ *
+ * @see ScannableTable
+ */
+public interface FilterableTable extends Table {
+  /** Returns an enumerator over the rows in this Table. Each row is represented
+   * as an array of its column values.
+   *
+   * <p>The list of filters is mutable.
+   * If the table can implement a particular filter, it should remove that
+   * filter from the list.
+   * If it cannot implement a filter, it should leave it in the list.
+   * Any filters remaining will be implemented by the consuming Calcite
+   * operator. */
+  Enumerable<Object[]> scan(DataContext root, List<RexNode> filters);
+}
+
+// End FilterableTable.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/ProjectableFilterableTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/ProjectableFilterableTable.java b/core/src/main/java/net/hydromatic/optiq/ProjectableFilterableTable.java
new file mode 100644
index 0000000..e713b53
--- /dev/null
+++ b/core/src/main/java/net/hydromatic/optiq/ProjectableFilterableTable.java
@@ -0,0 +1,63 @@
+/*
+ * 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 net.hydromatic.optiq;
+
+import net.hydromatic.linq4j.Enumerable;
+
+import org.eigenbase.rex.RexNode;
+
+import java.util.List;
+
+/**
+ * Table that can be scanned, optionally applying supplied filter expressions,
+ * and projecting a given list of columns,
+ * without creating an intermediate relational expression.
+ *
+ * <p>If you wish to write a table that can apply projects but not filters,
+ * simply decline all filters.</p>
+ *
+ * @see net.hydromatic.optiq.ScannableTable
+ * @see net.hydromatic.optiq.FilterableTable
+ */
+public interface ProjectableFilterableTable extends Table {
+  /** Returns an enumerable over the rows in this Table.
+   *
+   * <p>Each row is represented as an array of its column values.
+   *
+   * <p>The list of filters is mutable.
+   * If the table can implement a particular filter, it should remove that
+   * filter from the list.
+   * If it cannot implement a filter, it should leave it in the list.
+   * Any filters remaining will be implemented by the consuming Calcite
+   * operator.
+   *
+   * <p>The projects are zero-based.</p>
+   *
+   * @param root Execution context
+   * @param filters Mutable list of filters. The method should remove from the
+   *                list any filters that it cannot apply.
+   * @param projects List of projects. Each is the 0-based ordinal of the column
+   *                 to project.
+   * @return Enumerable over all rows that match the accepted filters, returning
+   * for each row an array of column values, one value for each ordinal in
+   * {@code projects}.
+   */
+  Enumerable<Object[]> scan(DataContext root, List<RexNode> filters,
+      int[] projects);
+}
+
+// End ProjectableFilterableTable.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/ScannableTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/ScannableTable.java b/core/src/main/java/net/hydromatic/optiq/ScannableTable.java
new file mode 100644
index 0000000..af76df9
--- /dev/null
+++ b/core/src/main/java/net/hydromatic/optiq/ScannableTable.java
@@ -0,0 +1,31 @@
+/*
+ * 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 net.hydromatic.optiq;
+
+import net.hydromatic.linq4j.Enumerable;
+
+/**
+ * Table that can be scanned without creating an intermediate relational
+ * expression.
+ */
+public interface ScannableTable extends Table {
+  /** Returns an enumerator over the rows in this Table. Each row is represented
+   * as an array of its column values. */
+  Enumerable<Object[]> scan(DataContext root);
+}
+
+// End ScannableTable.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/Schemas.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/Schemas.java b/core/src/main/java/net/hydromatic/optiq/Schemas.java
index 09167c3..6a7a16e 100644
--- a/core/src/main/java/net/hydromatic/optiq/Schemas.java
+++ b/core/src/main/java/net/hydromatic/optiq/Schemas.java
@@ -16,6 +16,7 @@
  */
 package net.hydromatic.optiq;
 
+import net.hydromatic.linq4j.Enumerable;
 import net.hydromatic.linq4j.QueryProvider;
 import net.hydromatic.linq4j.Queryable;
 import net.hydromatic.linq4j.expressions.*;
@@ -30,8 +31,10 @@ import net.hydromatic.optiq.materialize.Lattice;
 import org.eigenbase.reltype.RelDataType;
 import org.eigenbase.reltype.RelDataTypeFactory;
 import org.eigenbase.reltype.RelProtoDataType;
+import org.eigenbase.rex.RexNode;
 import org.eigenbase.sql.type.SqlTypeUtil;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 
@@ -152,6 +155,18 @@ public final class Schemas {
           expression(schema),
           BuiltinMethod.SCHEMA_GET_TABLE.method,
           Expressions.constant(tableName));
+      if (ScannableTable.class.isAssignableFrom(clazz)) {
+        return Expressions.call(
+            BuiltinMethod.SCHEMAS_ENUMERABLE.method,
+            Expressions.convert_(expression, ScannableTable.class),
+            DataContext.ROOT);
+      }
+      if (FilterableTable.class.isAssignableFrom(clazz)) {
+        return Expressions.call(
+            BuiltinMethod.SCHEMAS_ENUMERABLE2.method,
+            Expressions.convert_(expression, FilterableTable.class),
+            DataContext.ROOT);
+      }
     } else {
       expression = Expressions.call(
           BuiltinMethod.SCHEMAS_QUERYABLE.method,
@@ -160,8 +175,7 @@ public final class Schemas {
           Expressions.constant(elementType),
           Expressions.constant(tableName));
     }
-    return Types.castIfNecessary(
-        clazz, expression);
+    return Types.castIfNecessary(clazz, expression);
   }
 
   public static DataContext createDataContext(Connection connection) {
@@ -196,6 +210,37 @@ public final class Schemas {
     return table.asQueryable(root.getQueryProvider(), schema, tableName);
   }
 
+  /** Returns an {@link net.hydromatic.linq4j.Enumerable} over the rows of
+   * a given table, representing each row as an object array. */
+  public static Enumerable<Object[]> enumerable(final ScannableTable table,
+      final DataContext root) {
+    return table.scan(root);
+  }
+
+  /** Returns an {@link net.hydromatic.linq4j.Enumerable} over the rows of
+   * a given table, not applying any filters, representing each row as an object
+   * array. */
+  public static Enumerable<Object[]> enumerable(final FilterableTable table,
+      final DataContext root) {
+    return table.scan(root, ImmutableList.<RexNode>of());
+  }
+
+  /** Returns an {@link net.hydromatic.linq4j.Enumerable} over object arrays,
+   * given a fully-qualified table name which leads to a
+   * {@link ScannableTable}. */
+  public static Table table(DataContext root, String... names) {
+    SchemaPlus schema = root.getRootSchema();
+    final List<String> nameList = Arrays.asList(names);
+    for (Iterator<? extends String> iterator = nameList.iterator();;) {
+      String name = iterator.next();
+      if (iterator.hasNext()) {
+        schema = schema.getSubSchema(name);
+      } else {
+        return schema.getTable(name);
+      }
+    }
+  }
+
   /** Parses and validates a SQL query. For use within Calcite only. */
   public static OptiqPrepare.ParseResult parse(
       final OptiqConnection connection, final OptiqSchema schema,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Interpreter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Interpreter.java b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Interpreter.java
index 12ac66b..7607319 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Interpreter.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Interpreter.java
@@ -20,6 +20,7 @@ import net.hydromatic.linq4j.AbstractEnumerable;
 import net.hydromatic.linq4j.Enumerator;
 
 import net.hydromatic.optiq.DataContext;
+import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
 
 import org.eigenbase.rel.*;
 import org.eigenbase.rex.*;
@@ -28,8 +29,10 @@ import org.eigenbase.util.ReflectiveVisitDispatcher;
 import org.eigenbase.util.ReflectiveVisitor;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
+import java.math.BigDecimal;
 import java.util.*;
 
 /**
@@ -39,26 +42,26 @@ import java.util.*;
  * particular it holds working state while the data flow graph is being
  * assembled.</p>
  */
-public class Interpreter extends AbstractEnumerable<Row> {
+public class Interpreter extends AbstractEnumerable<Object[]> {
   final Map<RelNode, NodeInfo> nodes = Maps.newLinkedHashMap();
   private final DataContext dataContext;
   private final RelNode rootRel;
+  private final Map<RelNode, List<RelNode>> relInputs = Maps.newHashMap();
 
   public Interpreter(DataContext dataContext, RelNode rootRel) {
     this.dataContext = dataContext;
-    this.rootRel = rootRel;
     Compiler compiler = new Nodes.CoreCompiler(this);
-    compiler.visit(rootRel, 0, null);
+    this.rootRel = compiler.visitRoot(rootRel);
   }
 
-  public Enumerator<Row> enumerator() {
+  public Enumerator<Object[]> enumerator() {
     start();
     final ArrayDeque<Row> queue = nodes.get(rootRel).sink.list;
-    return new Enumerator<Row>() {
+    return new Enumerator<Object[]>() {
       Row row;
 
-      public Row current() {
-        return row;
+      public Object[] current() {
+        return row.getValues();
       }
 
       public boolean moveNext() {
@@ -108,19 +111,55 @@ public class Interpreter extends AbstractEnumerable<Row> {
       return new Scalar() {
         public Object execute(final Context context) {
           final List<Object> args;
-          final Comparable o0;
-          final Comparable o1;
+          Comparable o0;
+          Comparable o1;
           switch (call.getKind()) {
           case LESS_THAN:
-            args = lazyArgs(context);
-            o0 = (Comparable) args.get(0);
-            o1 = (Comparable) args.get(1);
-            return o0 == null || o1 == null ? null : o0.compareTo(o1) < 0;
+          case LESS_THAN_OR_EQUAL:
           case GREATER_THAN:
+          case GREATER_THAN_OR_EQUAL:
+          case EQUALS:
+          case NOT_EQUALS:
             args = lazyArgs(context);
             o0 = (Comparable) args.get(0);
+            if (o0 == null) {
+              return null;
+            }
             o1 = (Comparable) args.get(1);
-            return o0 == null || o1 == null ? null : o0.compareTo(o1) > 0;
+            if (o1 == null) {
+              return null;
+            }
+            if (o0 instanceof BigDecimal) {
+              if (o1 instanceof Double || o1 instanceof Float) {
+                o1 = new BigDecimal(((Number) o1).doubleValue());
+              } else {
+                o1 = new BigDecimal(((Number) o1).longValue());
+              }
+            }
+            if (o1 instanceof BigDecimal) {
+              if (o0 instanceof Double || o0 instanceof Float) {
+                o0 = new BigDecimal(((Number) o0).doubleValue());
+              } else {
+                o0 = new BigDecimal(((Number) o0).longValue());
+              }
+            }
+            final int c = o0.compareTo(o1);
+            switch (call.getKind()) {
+            case LESS_THAN:
+              return c < 0;
+            case LESS_THAN_OR_EQUAL:
+              return c <= 0;
+            case GREATER_THAN:
+              return c > 0;
+            case GREATER_THAN_OR_EQUAL:
+              return c >= 0;
+            case EQUALS:
+              return c == 0;
+            case NOT_EQUALS:
+              return c != 0;
+            default:
+              throw new AssertionError("unknown expression " + call);
+            }
           default:
             throw new AssertionError("unknown expression " + call);
           }
@@ -154,7 +193,7 @@ public class Interpreter extends AbstractEnumerable<Row> {
   }
 
   public Source source(RelNode rel, int ordinal) {
-    final RelNode input = rel.getInput(ordinal);
+    final RelNode input = getInput(rel, ordinal);
     final NodeInfo x = nodes.get(input);
     if (x == null) {
       throw new AssertionError("should be registered: " + rel);
@@ -162,6 +201,14 @@ public class Interpreter extends AbstractEnumerable<Row> {
     return new ListSource(x.sink);
   }
 
+  private RelNode getInput(RelNode rel, int ordinal) {
+    final List<RelNode> inputs = relInputs.get(rel);
+    if (inputs != null) {
+      return inputs.get(ordinal);
+    }
+    return rel.getInput(ordinal);
+  }
+
   public Sink sink(RelNode rel) {
     final ArrayDeque<Row> queue = new ArrayDeque<Row>(1);
     final ListSink sink = new ListSink(queue);
@@ -240,17 +287,60 @@ public class Interpreter extends AbstractEnumerable<Row> {
     private final ReflectiveVisitDispatcher<Compiler, RelNode> dispatcher =
         ReflectUtil.createDispatcher(Compiler.class, RelNode.class);
     protected final Interpreter interpreter;
+    protected RelNode rootRel;
+    protected RelNode rel;
     protected Node node;
 
+    private static final String REWRITE_METHOD_NAME = "rewrite";
     private static final String VISIT_METHOD_NAME = "visit";
 
     Compiler(Interpreter interpreter) {
       this.interpreter = interpreter;
     }
 
+    public RelNode visitRoot(RelNode p) {
+      rootRel = p;
+      visit(p, 0, null);
+      return rootRel;
+    }
+
     @Override public void visit(RelNode p, int ordinal, RelNode parent) {
+      for (;;) {
+        rel = null;
+        boolean found = dispatcher.invokeVisitor(this, p, REWRITE_METHOD_NAME);
+        if (!found) {
+          throw new AssertionError(
+              "interpreter: no implementation for rewrite");
+        }
+        if (rel == null) {
+          break;
+        }
+        if (OptiqPrepareImpl.DEBUG) {
+          System.out.println("Interpreter: rewrite " + p + " to " + rel);
+        }
+        p = rel;
+        if (parent != null) {
+          List<RelNode> inputs = interpreter.relInputs.get(parent);
+          if (inputs == null) {
+            inputs = Lists.newArrayList(parent.getInputs());
+            interpreter.relInputs.put(parent, inputs);
+          }
+          inputs.set(ordinal, p);
+        } else {
+          rootRel = p;
+        }
+      }
+
       // rewrite children first (from left to right)
-      super.visit(p, ordinal, parent);
+      final List<RelNode> inputs = interpreter.relInputs.get(p);
+      if (inputs != null) {
+        for (int i = 0; i < inputs.size(); i++) {
+          RelNode input = inputs.get(i);
+          visit(input, i, p);
+        }
+      } else {
+        p.childrenAccept(this);
+      }
 
       node = null;
       boolean found = dispatcher.invokeVisitor(this, p, VISIT_METHOD_NAME);
@@ -263,6 +353,14 @@ public class Interpreter extends AbstractEnumerable<Row> {
       assert nodeInfo != null;
       nodeInfo.node = node;
     }
+
+    /** Fallback rewrite method.
+     *
+     * <p>Overriding methods (each with a different sub-class of {@link RelNode}
+     * as its argument type) sets the {@link #rel} field if intends to
+     * rewrite. */
+    public void rewrite(RelNode r) {
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Nodes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Nodes.java b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Nodes.java
index c2de888..7c5b43b 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Nodes.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Nodes.java
@@ -16,7 +16,21 @@
  */
 package net.hydromatic.optiq.impl.interpreter;
 
+import net.hydromatic.optiq.FilterableTable;
+import net.hydromatic.optiq.ProjectableFilterableTable;
+
 import org.eigenbase.rel.*;
+import org.eigenbase.rel.rules.FilterTableRule;
+import org.eigenbase.relopt.RelOptCluster;
+import org.eigenbase.relopt.RelOptTable;
+import org.eigenbase.relopt.RelOptUtil;
+import org.eigenbase.relopt.RelTraitSet;
+import org.eigenbase.rex.RexNode;
+import org.eigenbase.util.ImmutableIntList;
+import org.eigenbase.util.Pair;
+import org.eigenbase.util.mapping.Mappings;
+
+import com.google.common.collect.ImmutableList;
 
 /**
  * Helper methods for {@link Node} and implementations for core relational
@@ -32,6 +46,71 @@ public class Nodes {
       super(interpreter);
     }
 
+    public void rewrite(ProjectRelBase project) {
+      RelNode input = project.getChild();
+      final Mappings.TargetMapping mapping = project.getMapping();
+      if (mapping == null) {
+        return;
+      }
+      RexNode condition;
+      if (input instanceof FilterRelBase) {
+        final FilterRelBase filter = (FilterRelBase) input;
+        condition = filter.getCondition();
+        input = filter.getChild();
+      } else {
+        condition = project.getCluster().getRexBuilder().makeLiteral(true);
+      }
+      if (input instanceof TableAccessRelBase) {
+        final TableAccessRelBase scan = (TableAccessRelBase) input;
+        final RelOptTable table = scan.getTable();
+        final ProjectableFilterableTable projectableFilterableTable =
+            table.unwrap(ProjectableFilterableTable.class);
+        if (projectableFilterableTable != null) {
+          final FilterTableRule.FilterSplit filterSplit =
+              FilterTableRule.FilterSplit.of(projectableFilterableTable,
+                  condition, interpreter.getDataContext());
+          rel = new FilterScanRel(project.getCluster(), project.getTraitSet(),
+              table, filterSplit.acceptedFilters,
+              ImmutableIntList.copyOf(Mappings.asList(mapping.inverse())));
+          rel = RelOptUtil.createFilter(rel, filterSplit.rejectedFilters);
+        }
+      }
+    }
+
+    public void rewrite(FilterRelBase filter) {
+      if (filter.getChild() instanceof TableAccessRelBase) {
+        final TableAccessRelBase scan = (TableAccessRelBase) filter.getChild();
+        final RelOptTable table = scan.getTable();
+        final ProjectableFilterableTable projectableFilterableTable =
+            table.unwrap(ProjectableFilterableTable.class);
+        if (projectableFilterableTable != null) {
+          final FilterTableRule.FilterSplit filterSplit =
+              FilterTableRule.FilterSplit.of(projectableFilterableTable,
+                  filter.getCondition(),
+                  interpreter.getDataContext());
+          if (!filterSplit.acceptedFilters.isEmpty()) {
+            rel = new FilterScanRel(scan.getCluster(), scan.getTraitSet(),
+                table, filterSplit.acceptedFilters, null);
+            rel = RelOptUtil.createFilter(rel, filterSplit.rejectedFilters);
+            return;
+          }
+        }
+        final FilterableTable filterableTable =
+            table.unwrap(FilterableTable.class);
+        if (filterableTable != null) {
+          final FilterTableRule.FilterSplit filterSplit =
+              FilterTableRule.FilterSplit.of(filterableTable,
+                  filter.getCondition(),
+                  interpreter.getDataContext());
+          if (!filterSplit.acceptedFilters.isEmpty()) {
+            rel = new FilterScanRel(scan.getCluster(), scan.getTraitSet(),
+                table, filterSplit.acceptedFilters, null);
+            rel = RelOptUtil.createFilter(rel, filterSplit.rejectedFilters);
+          }
+        }
+      }
+    }
+
     public void visit(FilterRelBase filter) {
       node = new FilterNode(interpreter, filter);
     }
@@ -40,18 +119,51 @@ public class Nodes {
       node = new ProjectNode(interpreter, project);
     }
 
+    /** Per {@link #rewrite(RelNode)}, writes to {@link #rel}.
+     *
+     * <p>We don't handle {@link CalcRelBase} directly. Expand to a
+     * {@link ProjectRelBase} on {@link FilterRelBase} (or just a
+     * {@link ProjectRelBase}). */
+    public void rewrite(CalcRelBase calc) {
+      final Pair<ImmutableList<RexNode>, ImmutableList<RexNode>> projectFilter =
+          calc.getProgram().split();
+      rel = calc.getChild();
+      rel = RelOptUtil.createFilter(rel, projectFilter.right);
+      rel = RelOptUtil.createProject(rel, projectFilter.left,
+          calc.getRowType().getFieldNames());
+    }
+
     public void visit(ValuesRelBase value) {
       node = new ValuesNode(interpreter, value);
     }
 
     public void visit(TableAccessRelBase scan) {
-      node = new ScanNode(interpreter, scan);
+      node = new ScanNode(interpreter, scan, ImmutableList.<RexNode>of(), null);
+    }
+
+    public void visit(FilterScanRel scan) {
+      node = new ScanNode(interpreter, scan, scan.filters, scan.projects);
     }
 
     public void visit(SortRel sort) {
       node = new SortNode(interpreter, sort);
     }
   }
+
+  /** Table scan that applies filters and optionally projects. Only used in an
+   * interpreter. */
+  public static class FilterScanRel extends TableAccessRelBase {
+    private final ImmutableList<RexNode> filters;
+    private final ImmutableIntList projects;
+
+    protected FilterScanRel(RelOptCluster cluster, RelTraitSet traits,
+        RelOptTable table, ImmutableList<RexNode> filters,
+        ImmutableIntList projects) {
+      super(cluster, traits, table);
+      this.filters = filters;
+      this.projects = projects;
+    }
+  }
 }
 
-// End Node.java
+// End Nodes.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ProjectNode.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ProjectNode.java b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ProjectNode.java
index 7193057..1a2d597 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ProjectNode.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ProjectNode.java
@@ -55,4 +55,4 @@ public class ProjectNode implements Node {
   }
 }
 
-// End FilterNode.java
+// End ProjectNode.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Row.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Row.java b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Row.java
index e410704..c84acc1 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Row.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/Row.java
@@ -24,10 +24,37 @@ import java.util.Arrays;
 public class Row {
   private final Object[] values;
 
-  public Row(Object[] values) {
+  /** Creates a Row. */
+  // must stay package-protected, because does not copy
+  Row(Object[] values) {
     this.values = values;
   }
 
+  /** Creates a Row.
+   *
+   * <p>Makes a defensive copy of the array, so the Row is immutable.
+   * (If you're worried about the extra copy, call {@link #of(Object)}.
+   * But the JIT probably avoids the copy.)
+   */
+  public static Row asCopy(Object... values) {
+    return new Row(values.clone());
+  }
+
+  /** Creates a Row with one column value. */
+  public static Row of(Object value0) {
+    return new Row(new Object[] {value0});
+  }
+
+  /** Creates a Row with two column values. */
+  public static Row of(Object value0, Object value1) {
+    return new Row(new Object[] {value0, value1});
+  }
+
+  /** Creates a Row with three column values. */
+  public static Row of(Object value0, Object value1, Object value2) {
+    return new Row(new Object[] {value0, value1, value2});
+  }
+
   @Override public int hashCode() {
     return Arrays.hashCode(values);
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ScanNode.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ScanNode.java b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ScanNode.java
index 085ffce..778b473 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ScanNode.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/ScanNode.java
@@ -22,14 +22,23 @@ import net.hydromatic.linq4j.Queryable;
 import net.hydromatic.linq4j.function.Function1;
 
 import net.hydromatic.optiq.DataContext;
+import net.hydromatic.optiq.FilterableTable;
+import net.hydromatic.optiq.ProjectableFilterableTable;
 import net.hydromatic.optiq.QueryableTable;
+import net.hydromatic.optiq.ScannableTable;
 import net.hydromatic.optiq.SchemaPlus;
 import net.hydromatic.optiq.Schemas;
+import net.hydromatic.optiq.runtime.Enumerables;
 
 import org.eigenbase.rel.TableAccessRelBase;
+import org.eigenbase.relopt.RelOptTable;
+import org.eigenbase.rex.RexNode;
+import org.eigenbase.util.ImmutableIntList;
 import org.eigenbase.util.Util;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
@@ -43,10 +52,15 @@ import java.util.List;
 public class ScanNode implements Node {
   private final Sink sink;
   private final TableAccessRelBase rel;
+  private final ImmutableList<RexNode> filters;
   private final DataContext root;
+  private final int[] projects;
 
-  public ScanNode(Interpreter interpreter, TableAccessRelBase rel) {
+  public ScanNode(Interpreter interpreter, TableAccessRelBase rel,
+      ImmutableList<RexNode> filters, ImmutableIntList projects) {
     this.rel = rel;
+    this.filters = Preconditions.checkNotNull(filters);
+    this.projects = projects == null ? null : projects.toIntArray();
     this.sink = interpreter.sink(rel);
     this.root = interpreter.getDataContext();
   }
@@ -62,23 +76,49 @@ public class ScanNode implements Node {
   }
 
   private Enumerable<Row> iterable() {
+    final RelOptTable table = rel.getTable();
+    final ProjectableFilterableTable pfTable =
+        table.unwrap(ProjectableFilterableTable.class);
+    if (pfTable != null) {
+      final List<RexNode> filters1 = Lists.newArrayList(filters);
+      final Enumerable<Object[]> enumerator =
+          pfTable.scan(root, filters1, projects);
+      assert filters1.isEmpty()
+          : "table could not handle a filter it earlier said it could";
+      return Enumerables.toRow(enumerator);
+    }
+    if (projects != null) {
+      throw new AssertionError("have projects, but table cannot handle them");
+    }
+    final FilterableTable filterableTable =
+        table.unwrap(FilterableTable.class);
+    if (filterableTable != null) {
+      final List<RexNode> filters1 = Lists.newArrayList(filters);
+      final Enumerable<Object[]> enumerator =
+          filterableTable.scan(root, filters1);
+      assert filters1.isEmpty()
+          : "table could not handle a filter it earlier said it could";
+      return Enumerables.toRow(enumerator);
+    }
+    if (!filters.isEmpty()) {
+      throw new AssertionError("have filters, but table cannot handle them");
+    }
     //noinspection unchecked
-    Enumerable<Row> iterable = rel.getTable().unwrap(Enumerable.class);
+    Enumerable<Row> iterable = table.unwrap(Enumerable.class);
     if (iterable != null) {
       return iterable;
     }
-    final QueryableTable queryableTable =
-        rel.getTable().unwrap(QueryableTable.class);
+    final QueryableTable queryableTable = table.unwrap(QueryableTable.class);
     if (queryableTable != null) {
       final Type elementType = queryableTable.getElementType();
       SchemaPlus schema = root.getRootSchema();
-      for (String name : Util.skipLast(rel.getTable().getQualifiedName())) {
+      for (String name : Util.skipLast(table.getQualifiedName())) {
         schema = schema.getSubSchema(name);
       }
       if (elementType instanceof Class) {
         //noinspection unchecked
         final Queryable<Object> queryable = Schemas.queryable(root,
-            (Class) elementType, rel.getTable().getQualifiedName());
+            (Class) elementType, table.getQualifiedName());
         ImmutableList.Builder<Field> fieldBuilder = ImmutableList.builder();
         Class type = (Class) elementType;
         for (Field field : type.getFields()) {
@@ -105,10 +145,15 @@ public class ScanNode implements Node {
             });
       } else {
         return Schemas.queryable(root, Row.class,
-            rel.getTable().getQualifiedName());
+            table.getQualifiedName());
       }
     }
-    throw new AssertionError("cannot convert table " + rel.getTable()
+    final ScannableTable scannableTable =
+        table.unwrap(ScannableTable.class);
+    if (scannableTable != null) {
+      return Enumerables.toRow(scannableTable.scan(root));
+    }
+    throw new AssertionError("cannot convert table " + table
         + " to iterable");
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/impl/interpreter/SortNode.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/SortNode.java b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/SortNode.java
index 1f324c3..7f0c8a6 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/interpreter/SortNode.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/interpreter/SortNode.java
@@ -25,6 +25,7 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
 
+import java.math.BigDecimal;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -48,11 +49,11 @@ public class SortNode implements Node {
     final int offset =
         rel.offset == null
             ? 0
-            : (Integer) ((RexLiteral) rel.offset).getValue();
+            : ((BigDecimal) ((RexLiteral) rel.offset).getValue()).intValue();
     final int fetch =
         rel.fetch == null
             ? -1
-            : (Integer) ((RexLiteral) rel.fetch).getValue();
+            : ((BigDecimal) ((RexLiteral) rel.fetch).getValue()).intValue();
     // In pure limit mode. No sort required.
     Row row;
   loop:
@@ -64,7 +65,7 @@ public class SortNode implements Node {
         }
       }
       if (fetch >= 0) {
-        for (int i = 0; i < offset && (row = source.receive()) != null; i++) {
+        for (int i = 0; i < fetch && (row = source.receive()) != null; i++) {
           sink.send(row);
         }
       } else {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/jdbc/MetaImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/jdbc/MetaImpl.java b/core/src/main/java/net/hydromatic/optiq/jdbc/MetaImpl.java
index 887d1ea..ce807b7 100644
--- a/core/src/main/java/net/hydromatic/optiq/jdbc/MetaImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/jdbc/MetaImpl.java
@@ -171,20 +171,20 @@ public class MetaImpl implements Meta {
   public static <E> ResultSet createEmptyResultSet(
       OptiqConnectionImpl connection,
       final Class<E> clazz) {
-    return createResultSet(
-        connection,
+    return createResultSet(connection, ImmutableMap.<String, Object>of(),
         fieldMetaData(clazz),
         new RecordEnumeratorCursor<E>(Linq4j.<E>emptyEnumerator(), clazz));
   }
 
   private static <E> ResultSet createResultSet(OptiqConnectionImpl connection,
+      final Map<String, Object> internalParameters,
       final ColumnMetaData.StructType structType,
       final Cursor cursor) {
     try {
       final AvaticaResultSet resultSet = connection.getFactory().newResultSet(
           connection.createStatement(),
           new OptiqPrepare.PrepareResult<E>("",
-              ImmutableList.<AvaticaParameter>of(), null,
+              ImmutableList.<AvaticaParameter>of(), internalParameters, null,
               structType, -1, null, Object.class) {
             @Override
             public Cursor createCursor(DataContext dataContext) {
@@ -203,7 +203,8 @@ public class MetaImpl implements Meta {
       final Enumerable<?> enumerable,
       final NamedFieldGetter columnGetter) {
     //noinspection unchecked
-    return createResultSet(connection, columnGetter.structType,
+    return createResultSet(connection, ImmutableMap.<String, Object>of(),
+        columnGetter.structType,
         columnGetter.cursor(((Enumerable) enumerable).enumerator()));
   }
 
@@ -603,10 +604,14 @@ public class MetaImpl implements Meta {
 
   public Cursor createCursor(AvaticaResultSet resultSet_) {
     OptiqResultSet resultSet = (OptiqResultSet) resultSet_;
-    final DataContext dataContext =
-        connection.createDataContext(
-            OptiqConnectionImpl.TROJAN.getParameterValues(
-                resultSet.getStatement()));
+    Map<String, Object> map = Maps.newLinkedHashMap();
+    final List<Object> parameterValues =
+        OptiqConnectionImpl.TROJAN.getParameterValues(resultSet.getStatement());
+    for (Ord<Object> o : Ord.zip(parameterValues)) {
+      map.put("?" + o.i, o.e);
+    }
+    map.putAll(resultSet.getPrepareResult().getInternalParameters());
+    final DataContext dataContext = connection.createDataContext(map);
     OptiqPrepare.PrepareResult prepareResult = resultSet.getPrepareResult();
     return prepareResult.createCursor(dataContext);
   }
@@ -623,7 +628,7 @@ public class MetaImpl implements Meta {
   @VisibleForTesting
   public static DataContext createDataContext(OptiqConnection connection) {
     return ((OptiqConnectionImpl) connection)
-        .createDataContext(ImmutableList.of());
+        .createDataContext(ImmutableMap.<String, Object>of());
   }
 
   /** A trojan-horse method, subject to change without notice. */

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
index a1c8f57..34ddd64 100644
--- a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
@@ -213,14 +213,14 @@ abstract class OptiqConnectionImpl
       OptiqPrepare.PrepareResult<T> enumerable =
           statement.prepare(queryable);
       final DataContext dataContext =
-          createDataContext(Collections.emptyList());
+          createDataContext(ImmutableMap.<String, Object>of());
       return enumerable.enumerator(dataContext);
     } catch (SQLException e) {
       throw new RuntimeException(e);
     }
   }
 
-  public DataContext createDataContext(List<Object> parameterValues) {
+  public DataContext createDataContext(Map<String, Object> parameterValues) {
     if (config().spark()) {
       return new SlimDataContext();
     }
@@ -286,7 +286,7 @@ abstract class OptiqConnectionImpl
     private final JavaTypeFactory typeFactory;
 
     DataContextImpl(OptiqConnectionImpl connection,
-        List<Object> parameterValues) {
+        Map<String, Object> parameters) {
       this.queryProvider = connection;
       this.typeFactory = connection.getTypeFactory();
       this.rootSchema = connection.rootSchema;
@@ -308,12 +308,12 @@ abstract class OptiqConnectionImpl
           .put(Variable.CURRENT_TIMESTAMP.camelName, time + currentOffset)
           .put(Variable.LOCAL_TIMESTAMP.camelName, time + localOffset)
           .put(Variable.TIME_ZONE.camelName, timeZone);
-      for (Ord<Object> value : Ord.zip(parameterValues)) {
-        Object e = value.e;
+      for (Map.Entry<String, Object> entry : parameters.entrySet()) {
+        Object e = entry.getValue();
         if (e == null) {
           e = AvaticaParameter.DUMMY_VALUE;
         }
-        builder.put("?" + value.i, e);
+        builder.put(entry.getKey(), e);
       }
       map = builder.build();
     }
@@ -376,8 +376,8 @@ abstract class OptiqConnectionImpl
     public List<String> getDefaultSchemaPath() {
       final String schemaName = connection.getSchema();
       return schemaName == null
-          ? Collections.<String>emptyList()
-          : Collections.singletonList(schemaName);
+          ? ImmutableList.<String>of()
+          : ImmutableList.of(schemaName);
     }
 
     public OptiqConnectionConfig config() {
@@ -385,7 +385,7 @@ abstract class OptiqConnectionImpl
     }
 
     public DataContext getDataContext() {
-      return connection.createDataContext(ImmutableList.of());
+      return connection.createDataContext(ImmutableMap.<String, Object>of());
     }
 
     public OptiqPrepare.SparkHandler spark() {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java
index f9f062c..2814546 100644
--- a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java
+++ b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java
@@ -221,6 +221,7 @@ public interface OptiqPrepare {
   public static class PrepareResult<T> implements AvaticaPrepareResult {
     public final String sql; // for debug
     public final List<AvaticaParameter> parameterList;
+    private final Map<String, Object> internalParameters;
     public final RelDataType rowType;
     public final ColumnMetaData.StructType structType;
     private final int maxRowCount;
@@ -229,6 +230,7 @@ public interface OptiqPrepare {
 
     public PrepareResult(String sql,
         List<AvaticaParameter> parameterList,
+        Map<String, Object> internalParameters,
         RelDataType rowType,
         ColumnMetaData.StructType structType,
         int maxRowCount,
@@ -237,6 +239,7 @@ public interface OptiqPrepare {
       super();
       this.sql = sql;
       this.parameterList = parameterList;
+      this.internalParameters = internalParameters;
       this.rowType = rowType;
       this.structType = structType;
       this.maxRowCount = maxRowCount;
@@ -262,6 +265,10 @@ public interface OptiqPrepare {
       return parameterList;
     }
 
+    public Map<String, Object> getInternalParameters() {
+      return internalParameters;
+    }
+
     public String getSql() {
       return sql;
     }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
index 1b8cd9e..130638e 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
@@ -128,6 +128,10 @@ public class OptiqPrepareImpl implements OptiqPrepare {
           COMMUTE
               ? CommutativeJoinRule.INSTANCE
               : MergeProjectRule.INSTANCE,
+          FilterTableRule.INSTANCE,
+          ProjectTableRule.INSTANCE,
+          ProjectTableRule.INSTANCE2,
+          PushProjectPastFilterRule.INSTANCE,
           PushFilterPastProjectRule.INSTANCE,
           PushFilterPastJoinRule.FILTER_ON_JOIN,
           RemoveDistinctAggregateRule.INSTANCE,
@@ -339,6 +343,7 @@ public class OptiqPrepareImpl implements OptiqPrepare {
     return new PrepareResult<T>(
         sql,
         ImmutableList.<AvaticaParameter>of(),
+        ImmutableMap.<String, Object>of(),
         x,
         getColumnMetaDataList(typeFactory, x, x, origins),
         -1,
@@ -453,6 +458,7 @@ public class OptiqPrepareImpl implements OptiqPrepare {
     return new PrepareResult<T>(
         sql,
         parameters,
+        preparingStmt.internalParameters,
         jdbcType,
         structType,
         maxRowCount,
@@ -622,7 +628,8 @@ public class OptiqPrepareImpl implements OptiqPrepare {
     protected final OptiqSchema schema;
     protected final RelDataTypeFactory typeFactory;
     private final EnumerableRel.Prefer prefer;
-
+    private final Map<String, Object> internalParameters =
+        Maps.newLinkedHashMap();
     private int expansionDepth;
     private SqlValidator sqlValidator;
 
@@ -706,7 +713,7 @@ public class OptiqPrepareImpl implements OptiqPrepare {
     @Override
     protected EnumerableRelImplementor getRelImplementor(
         RexBuilder rexBuilder) {
-      return new EnumerableRelImplementor(rexBuilder);
+      return new EnumerableRelImplementor(rexBuilder, internalParameters);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/prepare/RelOptTableImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/prepare/RelOptTableImpl.java b/core/src/main/java/net/hydromatic/optiq/prepare/RelOptTableImpl.java
index d5f47aa..21b3dbf 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/RelOptTableImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/RelOptTableImpl.java
@@ -18,10 +18,7 @@ package net.hydromatic.optiq.prepare;
 
 import net.hydromatic.linq4j.expressions.Expression;
 
-import net.hydromatic.optiq.QueryableTable;
-import net.hydromatic.optiq.Schemas;
-import net.hydromatic.optiq.Table;
-import net.hydromatic.optiq.TranslatableTable;
+import net.hydromatic.optiq.*;
 import net.hydromatic.optiq.jdbc.OptiqSchema;
 import net.hydromatic.optiq.rules.java.EnumerableConvention;
 import net.hydromatic.optiq.rules.java.JavaRules;
@@ -95,14 +92,25 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
   public static RelOptTableImpl create(RelOptSchema schema, RelDataType rowType,
       final OptiqSchema.TableEntry tableEntry, Double rowCount) {
     Function<Class, Expression> expressionFunction;
-    if (tableEntry.getTable() instanceof QueryableTable) {
-      final QueryableTable table = (QueryableTable) tableEntry.getTable();
+    final Table table = tableEntry.getTable();
+    if (table instanceof QueryableTable) {
+      final QueryableTable queryableTable = (QueryableTable) table;
       expressionFunction = new Function<Class, Expression>() {
         public Expression apply(Class clazz) {
-          return table.getExpression(tableEntry.schema.plus(),
+          return queryableTable.getExpression(tableEntry.schema.plus(),
               tableEntry.name, clazz);
         }
       };
+    } else if (table instanceof ScannableTable
+        || table instanceof FilterableTable
+        || table instanceof ProjectableFilterableTable) {
+      expressionFunction = new Function<Class, Expression>() {
+        public Expression apply(Class clazz) {
+          return Schemas.tableExpression(tableEntry.schema.plus(),
+              Object[].class, tableEntry.name,
+              table.getClass());
+        }
+      };
     } else {
       expressionFunction = new Function<Class, Expression>() {
         public Expression apply(Class input) {
@@ -111,7 +119,7 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
       };
     }
     return new RelOptTableImpl(schema, rowType, tableEntry.path(),
-      tableEntry.getTable(), expressionFunction, rowCount);
+        table, expressionFunction, rowCount);
   }
 
   public static RelOptTableImpl create(
@@ -170,9 +178,14 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
     }
     RelOptCluster cluster = context.getCluster();
     Class elementType = deduceElementType();
-    return new JavaRules.EnumerableTableAccessRel(
-        cluster, cluster.traitSetOf(EnumerableConvention.INSTANCE),
-        this, elementType);
+    final RelNode scan = new JavaRules.EnumerableTableAccessRel(cluster,
+        cluster.traitSetOf(EnumerableConvention.INSTANCE), this, elementType);
+    if (table instanceof FilterableTable
+        || table instanceof ProjectableFilterableTable) {
+      return new JavaRules.EnumerableInterpreterRel(cluster, scan.getTraitSet(),
+          scan, 1d);
+    }
+    return scan;
   }
 
   private Class deduceElementType() {
@@ -184,6 +197,10 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
       } else {
         return Object[].class;
       }
+    } else if (table instanceof ScannableTable
+        || table instanceof FilterableTable
+        || table instanceof ProjectableFilterableTable) {
+      return Object[].class;
     } else {
       return Object.class;
     }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRel.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRel.java b/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRel.java
index 73d5ad2..5a69057 100644
--- a/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRel.java
+++ b/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRel.java
@@ -18,7 +18,16 @@ package net.hydromatic.optiq.rules.java;
 
 import net.hydromatic.linq4j.expressions.BlockStatement;
 
+import org.eigenbase.rel.ProjectRelBase;
+import org.eigenbase.rel.RelFactories;
 import org.eigenbase.rel.RelNode;
+import org.eigenbase.relopt.RelOptCluster;
+import org.eigenbase.reltype.RelDataType;
+import org.eigenbase.rex.RexNode;
+import org.eigenbase.rex.RexUtil;
+import org.eigenbase.sql.validate.SqlValidatorUtil;
+
+import java.util.List;
 
 /**
  * A relational expression of one of the
@@ -27,6 +36,30 @@ import org.eigenbase.rel.RelNode;
  */
 public interface EnumerableRel
     extends RelNode {
+  RelFactories.FilterFactory FILTER_FACTORY =
+      new RelFactories.FilterFactory() {
+        public RelNode createFilter(RelNode child, RexNode condition) {
+          return new JavaRules.EnumerableFilterRel(child.getCluster(),
+              child.getTraitSet(), child, condition);
+        }
+      };
+
+  RelFactories.ProjectFactory PROJECT_FACTORY =
+      new RelFactories.ProjectFactory() {
+        public RelNode createProject(RelNode child,
+            List<? extends RexNode> exprs, List<String> fieldNames) {
+          final RelOptCluster cluster = child.getCluster();
+          final RelDataType rowType =
+              RexUtil.createStructType(cluster.getTypeFactory(), exprs,
+                  fieldNames == null ? null
+                      : SqlValidatorUtil.uniquify(fieldNames,
+                          SqlValidatorUtil.F_SUGGESTER));
+          return new JavaRules.EnumerableProjectRel(cluster,
+              child.getTraitSet(), child, exprs, rowType,
+              ProjectRelBase.Flags.BOXED);
+        }
+      };
+
   //~ Methods ----------------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRelImplementor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRelImplementor.java b/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRelImplementor.java
index 8d818d5..135582e 100644
--- a/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRelImplementor.java
+++ b/core/src/main/java/net/hydromatic/optiq/rules/java/EnumerableRelImplementor.java
@@ -25,6 +25,7 @@ import net.hydromatic.optiq.DataContext;
 import net.hydromatic.optiq.jdbc.JavaTypeFactoryImpl;
 import net.hydromatic.optiq.runtime.*;
 
+import org.eigenbase.rel.RelNode;
 import org.eigenbase.rex.RexBuilder;
 
 import com.google.common.collect.ImmutableList;
@@ -40,11 +41,12 @@ import java.util.*;
  * operators of {@link EnumerableConvention} calling convention.
  */
 public class EnumerableRelImplementor extends JavaRelImplementor {
-  public final Map<String, Queryable> map =
-      new LinkedHashMap<String, Queryable>();
+  public final Map<String, Object> map;
 
-  public EnumerableRelImplementor(RexBuilder rexBuilder) {
+  public EnumerableRelImplementor(RexBuilder rexBuilder,
+      Map<String, Object> internalParameters) {
     super(rexBuilder);
+    this.map = internalParameters;
   }
 
   public EnumerableRel.Result visitChild(
@@ -88,19 +90,13 @@ public class EnumerableRelImplementor extends JavaRelImplementor {
             BuiltinMethod.BINDABLE_BIND.method.getName(),
             Expressions.list(root0_),
             block));
-    memberDeclarations.add(
-        Expressions.methodDecl(
-            Modifier.PUBLIC,
-            Type.class,
-            BuiltinMethod.TYPED_GET_ELEMENT_TYPE.method.getName(),
-            Collections.<ParameterExpression>emptyList(),
-            Blocks.toFunctionBlock(
-                Expressions.return_(
-                    null,
-                    Expressions.constant(
-                        result.physType.getJavaRowType())))));
-    return Expressions.classDecl(
-        Modifier.PUBLIC,
+    memberDeclarations.add(Expressions.methodDecl(Modifier.PUBLIC,
+        Type.class,
+        BuiltinMethod.TYPED_GET_ELEMENT_TYPE.method.getName(),
+        Collections.<ParameterExpression>emptyList(),
+        Blocks.toFunctionBlock(Expressions.return_(null,
+            Expressions.constant(result.physType.getJavaRowType())))));
+    return Expressions.classDecl(Modifier.PUBLIC,
         "Baz",
         null,
         Collections.<Type>singletonList(Bindable.class),
@@ -355,9 +351,20 @@ public class EnumerableRelImplementor extends JavaRelImplementor {
   }
 
   public Expression register(Queryable queryable) {
-    String name = "v" + map.size();
-    map.put(name, queryable);
-    return Expressions.variable(queryable.getClass(), name);
+    return register(queryable, queryable.getClass());
+  }
+
+  public ParameterExpression register(Object o, Class clazz) {
+    final String name = "v" + map.size();
+    map.put(name, o);
+    return Expressions.variable(clazz, name);
+  }
+
+  public Expression stash(RelNode child, Class clazz) {
+    final ParameterExpression x = register(child, clazz);
+    final Expression e = Expressions.call(getRootExpression(),
+        BuiltinMethod.DATA_CONTEXT_GET.method, Expressions.constant(x.name));
+    return Expressions.convert_(e, clazz);
   }
 
   public EnumerableRel.Result result(PhysType physType, BlockStatement block) {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java b/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
index d9ead6c..f6a4f0d 100644
--- a/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
+++ b/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
@@ -22,7 +22,10 @@ import net.hydromatic.linq4j.expressions.Expression;
 import net.hydromatic.linq4j.function.*;
 
 import net.hydromatic.optiq.*;
+import net.hydromatic.optiq.impl.interpreter.Interpreter;
+import net.hydromatic.optiq.impl.interpreter.Row;
 import net.hydromatic.optiq.impl.java.JavaTypeFactory;
+import net.hydromatic.optiq.jdbc.JavaTypeFactoryImpl;
 import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
 import net.hydromatic.optiq.prepare.Prepare;
 import net.hydromatic.optiq.rules.java.impl.*;
@@ -508,38 +511,76 @@ public class JavaRules {
       this.elementType = elementType;
     }
 
-    private Expression getExpression() {
-      Expression expression = table.getExpression(Queryable.class);
+    private Expression getExpression(PhysType physType) {
+      final Expression expression = table.getExpression(Queryable.class);
+      final Expression expression2 = toEnumerable(expression);
+      assert Types.isAssignableFrom(Enumerable.class, expression2.getType());
+      Expression expression3 = toRows(physType, expression2);
+      return expression3;
+    }
+
+    private Expression toEnumerable(Expression expression) {
       final Type type = expression.getType();
       if (Types.isArray(type)) {
         if (Types.toClass(type).getComponentType().isPrimitive()) {
           expression =
-              Expressions.call(
-                  BuiltinMethod.AS_LIST.method,
-                  expression);
+              Expressions.call(BuiltinMethod.AS_LIST.method, expression);
         }
-        expression =
-            Expressions.call(
-                BuiltinMethod.AS_ENUMERABLE.method,
-                expression);
+        return Expressions.call(BuiltinMethod.AS_ENUMERABLE.method, expression);
       } else if (Types.isAssignableFrom(Iterable.class, type)
           && !Types.isAssignableFrom(Enumerable.class, type)) {
-        expression =
-            Expressions.call(
-                BuiltinMethod.AS_ENUMERABLE2.method,
-                expression);
+        return Expressions.call(BuiltinMethod.AS_ENUMERABLE2.method,
+            expression);
       } else if (Types.isAssignableFrom(Queryable.class, type)) {
         // Queryable extends Enumerable, but it's too "clever", so we call
         // Queryable.asEnumerable so that operations such as take(int) will be
         // evaluated directly.
-        expression =
-            Expressions.call(
-                expression,
-                BuiltinMethod.QUERYABLE_AS_ENUMERABLE.method);
+        return Expressions.call(expression,
+            BuiltinMethod.QUERYABLE_AS_ENUMERABLE.method);
       }
       return expression;
     }
 
+    private Expression toRows(PhysType physType, Expression expression) {
+      final ParameterExpression row_ =
+          Expressions.parameter(elementType, "row");
+      List<Expression> expressionList = new ArrayList<Expression>();
+      final int fieldCount = table.getRowType().getFieldCount();
+      if (elementType == Row.class) {
+        // Convert Enumerable<Row> to Enumerable<SyntheticRecord>
+        for (int i = 0; i < fieldCount; i++) {
+          expressionList.add(
+              RexToLixTranslator.convert(
+                  Expressions.call(row_,
+                      BuiltinMethod.ROW_VALUE.method,
+                      Expressions.constant(i)),
+                  physType.getJavaFieldType(i)));
+        }
+      } else if (elementType == Object[].class
+          && rowType.getFieldCount() == 1) {
+        // Convert Enumerable<Object[]> to Enumerable<SyntheticRecord>
+        for (int i = 0; i < fieldCount; i++) {
+          expressionList.add(
+              RexToLixTranslator.convert(
+                  Expressions.arrayIndex(row_, Expressions.constant(i)),
+                  physType.getJavaFieldType(i)));
+        }
+      } else if (elementType == Object.class) {
+        if (!(physType.getJavaRowType()
+            instanceof JavaTypeFactoryImpl.SyntheticRecordType)) {
+          return expression;
+        }
+        expressionList.add(
+            RexToLixTranslator.convert(row_, physType.getJavaFieldType(0)));
+      } else {
+        return expression;
+      }
+      return Expressions.call(expression,
+          BuiltinMethod.SELECT.method,
+          Expressions.lambda(Function1.class, physType.record(expressionList),
+              row_));
+    }
+
     private JavaRowFormat format() {
       if (Object[].class.isAssignableFrom(elementType)) {
         return JavaRowFormat.ARRAY;
@@ -564,11 +605,55 @@ public class JavaRules {
               implementor.getTypeFactory(),
               getRowType(),
               format());
-      final Expression expression = getExpression();
+      final Expression expression = getExpression(physType);
       return implementor.result(physType, Blocks.toBlock(expression));
     }
   }
 
+  /** Relational expression that executes its children using an interpreter.
+   *
+   * <p>Although quite a few kinds of {@link RelNode} can be interpreted,
+   * this is only created by default for {@link FilterableTable}
+   * and {@link ProjectableFilterableTable}.
+   */
+  public static class EnumerableInterpreterRel extends SingleRel
+      implements EnumerableRel {
+    private final double factor;
+
+    /** Creates an EnumerableInterpreterRel. */
+    public EnumerableInterpreterRel(RelOptCluster cluster,
+        RelTraitSet traitSet, RelNode input, double factor) {
+      super(cluster, traitSet, input);
+      this.factor = factor;
+    }
+
+    @Override public RelOptCost computeSelfCost(RelOptPlanner planner) {
+      return super.computeSelfCost(planner).multiplyBy(factor);
+    }
+
+    @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
+      return new EnumerableInterpreterRel(getCluster(), traitSet, sole(inputs),
+          factor);
+    }
+
+    public Result implement(EnumerableRelImplementor implementor, Prefer pref) {
+      final JavaTypeFactory typeFactory = implementor.getTypeFactory();
+      final BlockBuilder builder = new BlockBuilder();
+      final PhysType physType =
+          PhysTypeImpl.of(typeFactory, getRowType(), JavaRowFormat.ARRAY);
+      final Expression interpreter_ = builder.append("interpreter",
+          Expressions.new_(Interpreter.class,
+              implementor.getRootExpression(),
+              implementor.stash(getChild(), RelNode.class)));
+      final Expression sliced_ =
+          getRowType().getFieldCount() == 1
+              ? Expressions.call(BuiltinMethod.SLICE0.method, interpreter_)
+              : interpreter_;
+      builder.add(sliced_);
+      return implementor.result(physType, builder.toBlock());
+    }
+  }
+
   public static final EnumerableProjectRule ENUMERABLE_PROJECT_RULE =
       new EnumerableProjectRule();
 
@@ -616,7 +701,7 @@ public class JavaRules {
         RelOptCluster cluster,
         RelTraitSet traitSet,
         RelNode child,
-        List<RexNode> exps,
+        List<? extends RexNode> exps,
         RelDataType rowType,
         int flags) {
       super(cluster, traitSet, child, exps, rowType, flags);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/runtime/Enumerables.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/runtime/Enumerables.java b/core/src/main/java/net/hydromatic/optiq/runtime/Enumerables.java
index 00a5de8..92b14d6 100644
--- a/core/src/main/java/net/hydromatic/optiq/runtime/Enumerables.java
+++ b/core/src/main/java/net/hydromatic/optiq/runtime/Enumerables.java
@@ -23,6 +23,8 @@ import net.hydromatic.linq4j.function.EqualityComparer;
 import net.hydromatic.linq4j.function.Function1;
 import net.hydromatic.linq4j.function.Predicate1;
 
+import net.hydromatic.optiq.impl.interpreter.Row;
+
 import org.eigenbase.util.Bug;
 
 /**
@@ -33,9 +35,30 @@ import org.eigenbase.util.Bug;
  * Methods are subject to removal without notice.</p>
  */
 public class Enumerables {
+  private static final Function1<?, ?> SLICE =
+      new Function1<Object[], Object>() {
+        public Object apply(Object[] a0) {
+          return a0[0];
+        }
+      };
+
+  private static final Function1<Object[], Row> ARRAY_TO_ROW =
+      new Function1<Object[], Row>() {
+        public Row apply(Object[] a0) {
+          return Row.asCopy(a0);
+        }
+      };
+
   private Enumerables() {}
 
-  /**
+  /** Converts an enumerable over singleton arrays into the enumerable of their
+   * first elements. */
+  public static <E> Enumerable<E> slice0(Enumerable<E[]> enumerable) {
+    //noinspection unchecked
+    return enumerable.select((Function1<E[], E>) SLICE);
+  }
+
+   /**
    * Returns elements of {@code outer} for which there is a member of
    * {@code inner} with a matching key.
    */
@@ -117,6 +140,11 @@ public class Enumerables {
     };
   }
 
+  /** Converts an {@link Enumerable} over object arrays into an
+   * {@link Enumerable} over {@link Row} objects. */
+  public static Enumerable<Row> toRow(final Enumerable<Object[]> enumerator) {
+    return enumerator.select(ARRAY_TO_ROW);
+  }
 }
 
 // End Enumerables.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/runtime/EnumeratorCursor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/runtime/EnumeratorCursor.java b/core/src/main/java/net/hydromatic/optiq/runtime/EnumeratorCursor.java
index 30061e6..4ce8d1c 100644
--- a/core/src/main/java/net/hydromatic/optiq/runtime/EnumeratorCursor.java
+++ b/core/src/main/java/net/hydromatic/optiq/runtime/EnumeratorCursor.java
@@ -26,7 +26,7 @@ import net.hydromatic.linq4j.Enumerator;
  * For instance,
  * {@link net.hydromatic.optiq.rules.java.JavaRules.EnumerableCalcRel}
  * computes result just in {@code current()} method, thus it makes sense to
- * cache the result and make it available for all the accesors.
+ * cache the result and make it available for all the accessors.
  *
  * @param <T> Element type
  */

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/tools/Programs.java b/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
index a0fd46b..1364467 100644
--- a/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
+++ b/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
@@ -91,6 +91,7 @@ public class Programs {
               : MergeProjectRule.INSTANCE,
           AggregateStarTableRule.INSTANCE,
           AggregateStarTableRule.INSTANCE2,
+          FilterTableRule.INSTANCE,
           PushFilterPastProjectRule.INSTANCE,
           PushFilterPastJoinRule.FILTER_ON_JOIN,
           RemoveDistinctAggregateRule.INSTANCE,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/rel/metadata/RelMdPercentageOriginalRows.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/metadata/RelMdPercentageOriginalRows.java b/core/src/main/java/org/eigenbase/rel/metadata/RelMdPercentageOriginalRows.java
index 803c905..7b4a677 100644
--- a/core/src/main/java/org/eigenbase/rel/metadata/RelMdPercentageOriginalRows.java
+++ b/core/src/main/java/org/eigenbase/rel/metadata/RelMdPercentageOriginalRows.java
@@ -22,6 +22,7 @@ import org.eigenbase.rel.*;
 import org.eigenbase.relopt.*;
 
 import net.hydromatic.optiq.BuiltinMethod;
+import net.hydromatic.optiq.rules.java.JavaRules;
 
 import com.google.common.collect.ImmutableList;
 
@@ -153,6 +154,10 @@ public class RelMdPercentageOriginalRows {
     return cost;
   }
 
+  public RelOptCost getCumulativeCost(JavaRules.EnumerableInterpreterRel rel) {
+    return RelMetadataQuery.getNonCumulativeCost(rel);
+  }
+
   // Ditto for getNonCumulativeCost
   public RelOptCost getNonCumulativeCost(RelNode rel) {
     return rel.computeSelfCost(rel.getCluster().getPlanner());

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/rel/rules/FilterTableRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/FilterTableRule.java b/core/src/main/java/org/eigenbase/rel/rules/FilterTableRule.java
new file mode 100644
index 0000000..c168ef9
--- /dev/null
+++ b/core/src/main/java/org/eigenbase/rel/rules/FilterTableRule.java
@@ -0,0 +1,168 @@
+/*
+ * 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.eigenbase.rel.rules;
+
+import java.util.List;
+
+import org.eigenbase.rel.FilterRelBase;
+import org.eigenbase.rel.RelNode;
+import org.eigenbase.rel.TableAccessRelBase;
+import org.eigenbase.relopt.RelOptRule;
+import org.eigenbase.relopt.RelOptRuleCall;
+import org.eigenbase.relopt.RelOptTable;
+import org.eigenbase.relopt.RelOptUtil;
+import org.eigenbase.rex.*;
+
+import net.hydromatic.linq4j.Enumerable;
+
+import net.hydromatic.optiq.DataContext;
+import net.hydromatic.optiq.FilterableTable;
+import net.hydromatic.optiq.ProjectableFilterableTable;
+import net.hydromatic.optiq.rules.java.EnumerableRel;
+import net.hydromatic.optiq.rules.java.JavaRules;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import static org.eigenbase.util.Static.RESOURCE;
+
+/**
+ * Planner rule that pushes a filter into a scan of a {@link FilterableTable}
+ * or {@link net.hydromatic.optiq.ProjectableFilterableTable}.
+ */
+public class FilterTableRule extends RelOptRule {
+  private static final Predicate<TableAccessRelBase> PREDICATE =
+      new Predicate<TableAccessRelBase>() {
+        public boolean apply(TableAccessRelBase scan) {
+          // We can only push filters into a FilterableTable or
+          // ProjectableFilterableTable.
+          final RelOptTable table = scan.getTable();
+          return table.unwrap(FilterableTable.class) != null
+              || table.unwrap(ProjectableFilterableTable.class) != null;
+        }
+      };
+
+  public static final FilterTableRule INSTANCE = new FilterTableRule();
+
+  //~ Constructors -----------------------------------------------------------
+
+  /** Creates a FilterTableRule. */
+  private FilterTableRule() {
+    super(
+        operand(FilterRelBase.class,
+            operand(JavaRules.EnumerableInterpreterRel.class,
+                operand(TableAccessRelBase.class, null, PREDICATE, none()))));
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  // implement RelOptRule
+  public void onMatch(RelOptRuleCall call) {
+    final FilterRelBase filter = call.rel(0);
+    final JavaRules.EnumerableInterpreterRel interpreter = call.rel(1);
+    final TableAccessRelBase scan = call.rel(2);
+    final FilterableTable filterableTable =
+        scan.getTable().unwrap(FilterableTable.class);
+    final ProjectableFilterableTable projectableFilterableTable =
+        scan.getTable().unwrap(ProjectableFilterableTable.class);
+
+    final FilterSplit filterSplit;
+    if (filterableTable != null) {
+      filterSplit = FilterSplit.of(filterableTable, filter.getCondition(),
+          null);
+    } else if (projectableFilterableTable != null) {
+      filterSplit = FilterSplit.of(projectableFilterableTable,
+          filter.getCondition(), null);
+    } else {
+      throw new AssertionError(scan.getTable());
+    }
+
+    // It's worth using the ProjectableFilterableTable interface even if it
+    // refused all filters.
+    final RelNode newFilter =
+        RelOptUtil.createFilter(interpreter.getChild(),
+            filterSplit.acceptedFilters, EnumerableRel.FILTER_FACTORY);
+    final RelNode newInterpreter =
+        new JavaRules.EnumerableInterpreterRel(interpreter.getCluster(),
+            interpreter.getTraitSet(), newFilter, 0.15d);
+    final RelNode residue =
+        RelOptUtil.createFilter(newInterpreter, filterSplit.rejectedFilters);
+    call.transformTo(residue);
+  }
+
+  /** Splits a filter condition into parts that can and cannot be
+   * handled by a {@link FilterableTable} or
+   * {@link ProjectableFilterableTable}. */
+  public static class FilterSplit {
+    public final ImmutableList<RexNode> acceptedFilters;
+    public final ImmutableList<RexNode> rejectedFilters;
+
+    public FilterSplit(ImmutableList<RexNode> acceptedFilters,
+        ImmutableList<RexNode> rejectedFilters) {
+      this.acceptedFilters = acceptedFilters;
+      this.rejectedFilters = rejectedFilters;
+    }
+
+    public static FilterSplit of(FilterableTable table,
+        RexNode condition, DataContext dataContext) {
+      final List<RexNode> filters = Lists.newArrayList();
+      RelOptUtil.decomposeConjunction(condition, filters);
+      final List<RexNode> originalFilters = ImmutableList.copyOf(filters);
+
+      final Enumerable<Object[]> enumerable =
+          table.scan(dataContext, filters);
+      return rest(originalFilters, filters, enumerable);
+    }
+
+    public static FilterSplit of(ProjectableFilterableTable table,
+        RexNode condition, DataContext dataContext) {
+      final List<RexNode> filters = Lists.newArrayList();
+      RelOptUtil.decomposeConjunction(condition, filters);
+      final List<RexNode> originalFilters = ImmutableList.copyOf(filters);
+
+      final Enumerable<Object[]> enumerable =
+          table.scan(dataContext, filters, null);
+      return rest(originalFilters, filters, enumerable);
+    }
+
+    private static FilterSplit rest(List<RexNode> originalFilters,
+        List<RexNode> filters,
+        Enumerable<Object[]> enumerable) {
+      if (enumerable == null) {
+        throw RESOURCE.filterableTableScanReturnedNull().ex();
+      }
+      final ImmutableList.Builder<RexNode> accepted = ImmutableList.builder();
+      final ImmutableList.Builder<RexNode> rejected = ImmutableList.builder();
+      for (RexNode originalFilter : originalFilters) {
+        if (filters.contains(originalFilter)) {
+          rejected.add(originalFilter);
+        } else {
+          accepted.add(originalFilter);
+        }
+      }
+      for (RexNode node : filters) {
+        if (!originalFilters.contains(node)) {
+          throw RESOURCE.filterableTableInventedFilter(node.toString()).ex();
+        }
+      }
+      return new FilterSplit(accepted.build(), rejected.build());
+    }
+  }
+}
+
+// End FilterTableRule.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/263640ff/core/src/main/java/org/eigenbase/rel/rules/ProjectTableRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/ProjectTableRule.java b/core/src/main/java/org/eigenbase/rel/rules/ProjectTableRule.java
new file mode 100644
index 0000000..475edbb
--- /dev/null
+++ b/core/src/main/java/org/eigenbase/rel/rules/ProjectTableRule.java
@@ -0,0 +1,167 @@
+/*
+ * 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.eigenbase.rel.rules;
+
+import java.util.List;
+
+import org.eigenbase.rel.FilterRelBase;
+import org.eigenbase.rel.ProjectRelBase;
+import org.eigenbase.rel.RelNode;
+import org.eigenbase.rel.TableAccessRelBase;
+import org.eigenbase.relopt.RelOptRule;
+import org.eigenbase.relopt.RelOptRuleCall;
+import org.eigenbase.relopt.RelOptRuleOperand;
+import org.eigenbase.relopt.RelOptTable;
+import org.eigenbase.relopt.RelOptUtil;
+import org.eigenbase.rex.RexBuilder;
+import org.eigenbase.rex.RexInputRef;
+import org.eigenbase.rex.RexLocalRef;
+import org.eigenbase.rex.RexNode;
+import org.eigenbase.rex.RexProgram;
+import org.eigenbase.rex.RexShuttle;
+
+import net.hydromatic.optiq.ProjectableFilterableTable;
+import net.hydromatic.optiq.rules.java.EnumerableRel;
+import net.hydromatic.optiq.rules.java.JavaRules;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+
+/**
+ * Planner rule that pushes a project into a scan of a
+ * {@link net.hydromatic.optiq.ProjectableFilterableTable}.
+ *
+ * @see org.eigenbase.rel.rules.FilterTableRule
+ */
+public abstract class ProjectTableRule extends RelOptRule {
+  private static final Predicate<TableAccessRelBase> PREDICATE =
+      new Predicate<TableAccessRelBase>() {
+        public boolean apply(TableAccessRelBase scan) {
+          // We can only push projects into a ProjectableFilterableTable.
+          final RelOptTable table = scan.getTable();
+          return table.unwrap(ProjectableFilterableTable.class) != null;
+        }
+      };
+
+  public static final ProjectTableRule INSTANCE =
+      new ProjectTableRule(
+          operand(ProjectRelBase.class,
+              operand(JavaRules.EnumerableInterpreterRel.class,
+                  operand(TableAccessRelBase.class, null, PREDICATE, none()))),
+          "ProjectTableRule:basic") {
+        @Override public void onMatch(RelOptRuleCall call) {
+          final ProjectRelBase project = call.rel(0);
+          final JavaRules.EnumerableInterpreterRel interpreter = call.rel(1);
+          final TableAccessRelBase scan = call.rel(2);
+          final RelOptTable table = scan.getTable();
+          assert table.unwrap(ProjectableFilterableTable.class) != null;
+          apply(call, project, null, interpreter);
+        }
+      };
+
+  public static final ProjectTableRule INSTANCE2 =
+      new ProjectTableRule(
+          operand(ProjectRelBase.class,
+              operand(FilterRelBase.class,
+                  operand(JavaRules.EnumerableInterpreterRel.class,
+                      operand(TableAccessRelBase.class, null, PREDICATE,
+                          none())))),
+          "ProjectTableRule:filter") {
+        @Override public void onMatch(RelOptRuleCall call) {
+          final ProjectRelBase project = call.rel(0);
+          final FilterRelBase filter = call.rel(1);
+          final JavaRules.EnumerableInterpreterRel interpreter = call.rel(2);
+          final TableAccessRelBase scan = call.rel(3);
+          final RelOptTable table = scan.getTable();
+          assert table.unwrap(ProjectableFilterableTable.class) != null;
+          apply(call, project, filter, interpreter);
+        }
+      };
+
+  //~ Constructors -----------------------------------------------------------
+
+  /** Creates a FilterTableRule. */
+  private ProjectTableRule(RelOptRuleOperand operand, String description) {
+    super(operand, description);
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  protected void apply(RelOptRuleCall call, ProjectRelBase project,
+      FilterRelBase filter, JavaRules.EnumerableInterpreterRel interpreter) {
+    // Split the projects into column references and expressions on top of them.
+    // Creating a RexProgram is a convenient way to do this.
+    final RexBuilder rexBuilder = project.getCluster().getRexBuilder();
+    final RexProgram program = RexProgram.create(interpreter.getRowType(),
+        project.getProjects(), null, project.getRowType(), rexBuilder);
+    final List<Integer> projectOrdinals = Lists.newArrayList();
+    final List<RexNode> extraProjects;
+    if (program.getExprList().size()
+        == program.getInputRowType().getFieldCount()) {
+      // There are only field references, no non-trivial expressions.
+      for (RexLocalRef ref : program.getProjectList()) {
+        projectOrdinals.add(ref.getIndex());
+      }
+      extraProjects = null;
+    } else {
+      extraProjects = Lists.newArrayList();
+      RexShuttle shuttle = new RexShuttle() {
+        final List<RexInputRef> inputRefs = Lists.newArrayList();
+
+        @Override public RexNode visitInputRef(RexInputRef inputRef) {
+          final int source = inputRef.getIndex();
+          int target = projectOrdinals.indexOf(source);
+          final RexInputRef ref;
+          if (target < 0) {
+            target = projectOrdinals.size();
+            projectOrdinals.add(source);
+            ref = rexBuilder.makeInputRef(inputRef.getType(), target);
+            inputRefs.add(ref);
+          } else {
+            ref = inputRefs.get(target);
+          }
+          return ref;
+        }
+      };
+      for (RexNode node : project.getProjects()) {
+        extraProjects.add(node.accept(shuttle));
+      }
+    }
+
+    RelNode input = interpreter.getChild();
+    if (filter != null) {
+      input = RelOptUtil.createFilter(input, filter.getCondition(),
+          EnumerableRel.FILTER_FACTORY);
+    }
+    final RelNode newProject =
+        RelOptUtil.createProject(EnumerableRel.PROJECT_FACTORY, input,
+            projectOrdinals);
+    final RelNode newInterpreter =
+        new JavaRules.EnumerableInterpreterRel(interpreter.getCluster(),
+            interpreter.getTraitSet(), newProject, 0.15d);
+    final RelNode residue;
+    if (extraProjects != null) {
+      residue = RelOptUtil.createProject(newInterpreter, extraProjects,
+          project.getRowType().getFieldNames());
+    } else {
+      residue = newInterpreter;
+    }
+    call.transformTo(residue);
+  }
+}
+
+// End ProjectTableRule.java


[3/9] git commit: Re-order test suite, so that fast tests are run first.

Posted by jh...@apache.org.
Re-order test suite, so that fast tests are run first.


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

Branch: refs/heads/master
Commit: 9f7367002888a63b4e8dd280542edff7f2f3b396
Parents: 263640f
Author: Julian Hyde <jh...@apache.org>
Authored: Fri Oct 24 00:11:19 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Oct 24 09:26:02 2014 -0700

----------------------------------------------------------------------
 .../net/hydromatic/optiq/test/OptiqSuite.java    | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/9f736700/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java b/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
index 110eacc..8e0dd1a 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
@@ -66,43 +66,42 @@ import org.junit.runners.Suite;
     ModelTest.class,
     SqlValidatorFeatureTest.class,
     VolcanoPlannerTraitTest.class,
-    ScannableTableTest.class,
     InterpreterTest.class,
     VolcanoPlannerTest.class,
+    HepPlannerTest.class,
     SargTest.class,
-    SqlPrettyWriterTest.class,
     RelWriterTest.class,
     RexProgramTest.class,
+    RexTransformerTest.class,
     BinarySearchTest.class,
     EnumerablesTest.class,
+    ExceptionMessageTest.class,
 
     // medium tests (above 0.1s)
     SqlParserTest.class,
+    SqlPrettyWriterTest.class,
     SqlValidatorTest.class,
     SqlAdvisorTest.class,
-    RexTransformerTest.class,
     RelMetadataTest.class,
-    HepPlannerTest.class,
     RelOptRulesTest.class,
+    ScannableTableTest.class,
     RexExecutorTest.class,
-    MaterializationTest.class,
-    LatticeTest.class,
     SqlLimitsTest.class,
-    LinqFrontJdbcBackTest.class,
     JdbcFrontLinqBackTest.class,
     JdbcFrontJdbcBackTest.class,
     SqlToRelConverterTest.class,
     SqlOperatorTest.class,
-    RexTransformerTest.class,
     ChunkListTest.class,
     FrameworksTest.class,
-    PlannerTest.class,
-    ExceptionMessageTest.class,
 
     // slow tests (above 1s)
+    PlannerTest.class,
+    MaterializationTest.class,
     JdbcAdapterTest.class,
+    LinqFrontJdbcBackTest.class,
     JdbcFrontJdbcBackLinqMiddleTest.class,
     OptiqSqlOperatorTest.class,
+    LatticeTest.class,
     ReflectiveSchemaTest.class,
     JdbcTest.class,
 


[7/9] Various lattice improvements.

Posted by jh...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java b/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
index 0d7f8f2..e9c1ac4 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
@@ -16,6 +16,7 @@
  */
 package net.hydromatic.optiq.test;
 
+import net.hydromatic.optiq.materialize.MaterializationService;
 import net.hydromatic.optiq.runtime.Hook;
 
 import org.eigenbase.rel.RelNode;
@@ -25,11 +26,16 @@ import org.eigenbase.util.Util;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 import org.junit.Ignore;
 import org.junit.Test;
 
 import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -246,8 +252,7 @@ public class LatticeTest {
 
   /** Tests a model with pre-defined tiles. */
   @Test public void testLatticeWithPreDefinedTiles() {
-    foodmartModel(
-        " auto: false,\n"
+    foodmartModel(" auto: false,\n"
         + "  defaultMeasures: [ {\n"
         + "    agg: 'count'\n"
         + "  } ],\n"
@@ -255,8 +260,7 @@ public class LatticeTest {
         + "    dimensions: [ 'the_year', ['t', 'quarter'] ],\n"
         + "    measures: [ ]\n"
         + "  } ]\n")
-        .query(
-            "select distinct t.\"the_year\", t.\"quarter\"\n"
+        .query("select distinct t.\"the_year\", t.\"quarter\"\n"
             + "from \"foodmart\".\"sales_fact_1997\" as s\n"
             + "join \"foodmart\".\"time_by_day\" as t using (\"time_id\")\n")
       .enableMaterializations(true)
@@ -268,25 +272,8 @@ public class LatticeTest {
   /** A query that uses a pre-defined aggregate table, at the same
    *  granularity but fewer calls to aggregate functions. */
   @Test public void testLatticeWithPreDefinedTilesFewerMeasures() {
-    foodmartModel(
-        " auto: false,\n"
-        + "  defaultMeasures: [ {\n"
-        + "    agg: 'count'\n"
-        + "  } ],\n"
-        + "  tiles: [ {\n"
-        + "    dimensions: [ 'the_year', ['t', 'quarter'] ],\n"
-        + "    measures: [ {\n"
-        + "      agg: 'sum',\n"
-        + "      args: 'unit_sales'\n"
-        + "    }, {\n"
-        + "      agg: 'sum',\n"
-        + "      args: 'store_sales'\n"
-        + "    }, {\n"
-        + "      agg: 'count'\n"
-        + "    } ]\n"
-        + "  } ]\n")
-        .query(
-            "select t.\"the_year\", t.\"quarter\", count(*) as c\n"
+    foodmartModelWithOneTile()
+        .query("select t.\"the_year\", t.\"quarter\", count(*) as c\n"
             + "from \"foodmart\".\"sales_fact_1997\" as s\n"
             + "join \"foodmart\".\"time_by_day\" as t using (\"time_id\")\n"
             + "group by t.\"the_year\", t.\"quarter\"")
@@ -305,25 +292,8 @@ public class LatticeTest {
    * granularity. Includes a measure computed from a grouping column, a measure
    * based on COUNT rolled up using SUM, and an expression on a measure. */
   @Test public void testLatticeWithPreDefinedTilesRollUp() {
-    foodmartModel(
-        " auto: false,\n"
-        + "  defaultMeasures: [ {\n"
-        + "    agg: 'count'\n"
-        + "  } ],\n"
-        + "  tiles: [ {\n"
-        + "    dimensions: [ 'the_year', ['t', 'quarter'] ],\n"
-        + "    measures: [ {\n"
-        + "      agg: 'sum',\n"
-        + "      args: 'unit_sales'\n"
-        + "    }, {\n"
-        + "      agg: 'sum',\n"
-        + "      args: 'store_sales'\n"
-        + "    }, {\n"
-        + "      agg: 'count'\n"
-        + "    } ]\n"
-        + "  } ]\n")
-        .query(
-            "select t.\"the_year\",\n"
+    foodmartModelWithOneTile()
+        .query("select t.\"the_year\",\n"
             + "  count(*) as c,\n"
             + "  min(\"quarter\") as q,\n"
             + "  sum(\"unit_sales\") * 10 as us\n"
@@ -333,7 +303,7 @@ public class LatticeTest {
       .enableMaterializations(true)
       .explainContains(
           "EnumerableCalcRel(expr#0..3=[{inputs}], expr#4=[10], expr#5=[*($t3, $t4)], proj#0..2=[{exprs}], US=[$t5])\n"
-          + "  EnumerableAggregateRel(group=[{0}], agg#0=[$SUM0($2)], Q=[MIN($1)], agg#2=[$SUM0($4)])\n"
+          + "  EnumerableAggregateRel(group=[{0}], C=[$SUM0($2)], Q=[MIN($1)], agg#2=[$SUM0($4)])\n"
           + "    EnumerableTableAccessRel(table=[[adhoc, m{27, 31}")
       .returnsUnordered("the_year=1997; C=86837; Q=Q1; US=2667730.0000")
       .sameResultWithMaterializationsDisabled();
@@ -347,9 +317,12 @@ public class LatticeTest {
    * "Use optimization algorithm to suggest which tiles of a lattice to
    * materialize"</a>. */
   @Test public void testTileAlgorithm() {
+    MaterializationService.setThreadLocal();
+    MaterializationService.instance().clear();
     foodmartModel(
         " auto: false,\n"
         + "  algorithm: true,\n"
+        + "  algorithmMaxMillis: -1,\n"
         + "  rowCountEstimate: 86000,\n"
         + "  defaultMeasures: [ {\n"
         + "      agg: 'sum',\n"
@@ -369,8 +342,8 @@ public class LatticeTest {
             + "from \"foodmart\".\"sales_fact_1997\" as s\n"
             + "join \"foodmart\".\"time_by_day\" as t using (\"time_id\")\n")
         .enableMaterializations(true)
-        .explainContains("EnumerableAggregateRel(group=[{3, 4}])\n"
-            + "  EnumerableTableAccessRel(table=[[adhoc, m{7, 16, 25, 27, 31, 37}]])")
+        .explainContains("EnumerableAggregateRel(group=[{2, 3}])\n"
+            + "  EnumerableTableAccessRel(table=[[adhoc, m{16, 17, 27, 31}]])")
         .returnsUnordered("the_year=1997; quarter=Q1",
             "the_year=1997; quarter=Q2",
             "the_year=1997; quarter=Q3",
@@ -378,6 +351,102 @@ public class LatticeTest {
         .returnsCount(4);
   }
 
+  /** Tests a query that uses no columns from the fact table. */
+  @Test public void testGroupByEmpty() {
+    foodmartModel()
+        .query("select count(*) as c from \"foodmart\".\"sales_fact_1997\"")
+        .enableMaterializations(true)
+        .returnsUnordered("C=86837");
+  }
+
+  /** Calls {@link #testDistinctCount()} followed by
+   * {@link #testGroupByEmpty()}. */
+  @Test public void testGroupByEmptyWithPrelude() {
+    testDistinctCount();
+    testGroupByEmpty();
+  }
+
+  /** Tests a query that uses no dimension columns and one measure column. */
+  @Test public void testGroupByEmpty2() {
+    foodmartModel()
+        .query("select sum(\"unit_sales\") as s\n"
+            + "from \"foodmart\".\"sales_fact_1997\"")
+        .enableMaterializations(true)
+        .returnsUnordered("S=266773.0000");
+  }
+
+  /** Tests that two queries of the same dimensionality that use different
+   * measures can use the same materialization. */
+  @Test public void testGroupByEmpty3() {
+    final List<String> mats = Lists.newArrayList();
+    final Function<String, Void> handler =
+        new Function<String, Void>() {
+          public Void apply(String materializationName) {
+            mats.add(materializationName);
+            return null;
+          }
+        };
+    final OptiqAssert.AssertThat that = foodmartModel().pooled();
+    that.query("select sum(\"unit_sales\") as s, count(*) as c\n"
+            + "from \"foodmart\".\"sales_fact_1997\"")
+        .withHook(Hook.CREATE_MATERIALIZATION, handler)
+        .enableMaterializations(true)
+        .explainContains("EnumerableTableAccessRel(table=[[adhoc, m{}]])")
+        .returnsUnordered("S=266773.0000; C=86837");
+    assertThat(mats.toString(), mats.size(), equalTo(2));
+
+    // A similar query can use the same materialization.
+    that.query("select sum(\"unit_sales\") as s\n"
+        + "from \"foodmart\".\"sales_fact_1997\"")
+        .withHook(Hook.CREATE_MATERIALIZATION, handler)
+        .enableMaterializations(true)
+        .returnsUnordered("S=266773.0000");
+    assertThat(mats.toString(), mats.size(), equalTo(2));
+  }
+
+  /** Rolling up SUM. */
+  @Test public void testSum() {
+    foodmartModelWithOneTile()
+        .query("select sum(\"unit_sales\") as c\n"
+            + "from \"foodmart\".\"sales_fact_1997\"\n"
+            + "group by \"product_id\"\n"
+            + "order by 1 desc limit 1")
+        .enableMaterializations(true)
+        .returnsUnordered("C=267.0000");
+  }
+
+  /** Tests a distinct-count query.
+   *
+   * <p>We can't just roll up count(distinct ...) as we do count(...), but we
+   * can still use the aggregate table if we're smart. */
+  @Test public void testDistinctCount() {
+    foodmartModelWithOneTile()
+        .query("select count(distinct \"quarter\") as c\n"
+            + "from \"foodmart\".\"sales_fact_1997\"\n"
+            + "join \"foodmart\".\"time_by_day\" using (\"time_id\")\n"
+            + "group by \"the_year\"")
+        .enableMaterializations(true)
+        .explainContains("EnumerableCalcRel(expr#0..1=[{inputs}], C=[$t1])\n"
+            + "  EnumerableAggregateRel(group=[{0}], C=[COUNT($1)])\n"
+            + "    EnumerableCalcRel(expr#0..4=[{inputs}], proj#0..1=[{exprs}])\n"
+            + "      EnumerableTableAccessRel(table=[[adhoc, m{27, 31}]])")
+        .returnsUnordered("C=4");
+  }
+
+  @Test public void testDistinctCount2() {
+    foodmartModelWithOneTile()
+        .query("select count(distinct \"the_year\") as c\n"
+            + "from \"foodmart\".\"sales_fact_1997\"\n"
+            + "join \"foodmart\".\"time_by_day\" using (\"time_id\")\n"
+            + "group by \"the_year\"")
+        .enableMaterializations(true)
+        .explainContains("EnumerableCalcRel(expr#0..1=[{inputs}], C=[$t1])\n"
+            + "  EnumerableAggregateRel(group=[{0}], C=[COUNT($0)])\n"
+            + "    EnumerableAggregateRel(group=[{0}])\n"
+            + "      EnumerableTableAccessRel(table=[[adhoc, m{27, 31}]])")
+        .returnsUnordered("C=1");
+  }
+
   /** Runs all queries against the Foodmart schema, using a lattice.
    *
    * <p>Disabled for normal runs, because it is slow. */
@@ -407,23 +476,7 @@ public class LatticeTest {
     if (query == null) {
       return;
     }
-    foodmartModel(
-        " auto: false,\n"
-        + "  defaultMeasures: [ {\n"
-        + "    agg: 'count'\n"
-        + "  } ],\n"
-        + "  tiles: [ {\n"
-        + "    dimensions: [ 'the_year', ['t', 'quarter'] ],\n"
-        + "    measures: [ {\n"
-        + "      agg: 'sum',\n"
-        + "      args: 'unit_sales'\n"
-        + "    }, {\n"
-        + "      agg: 'sum',\n"
-        + "      args: 'store_sales'\n"
-        + "    }, {\n"
-        + "      agg: 'count'\n"
-        + "    } ]\n"
-        + "  } ]\n")
+    foodmartModelWithOneTile()
         .withSchema("foodmart")
         .query(query.sql)
       .sameResultWithMaterializationsDisabled();
@@ -467,6 +520,36 @@ public class LatticeTest {
         + "join \"foodmart\".\"product_class\" as \"pc\" on \"p\".\"product_class_id\" = \"pc\".\"product_class_id\"",
         extras);
   }
+
+  private OptiqAssert.AssertThat foodmartModelWithOneTile() {
+    return foodmartModel(
+        " auto: false,\n"
+        + "  defaultMeasures: [ {\n"
+        + "    agg: 'count'\n"
+        + "  } ],\n"
+        + "  tiles: [ {\n"
+        + "    dimensions: [ 'the_year', ['t', 'quarter'] ],\n"
+        + "    measures: [ {\n"
+        + "      agg: 'sum',\n"
+        + "      args: 'unit_sales'\n"
+        + "    }, {\n"
+        + "      agg: 'sum',\n"
+        + "      args: 'store_sales'\n"
+        + "    }, {\n"
+        + "      agg: 'count'\n"
+        + "    } ]\n"
+        + "  } ]\n");
+  }
+
+  // Just for debugging.
+  private static void runJdbc() throws SQLException {
+    final Connection connection = DriverManager.getConnection(
+        "jdbc:calcite:model=core/src/test/resources/mysql-foodmart-lattice-model.json");
+    final ResultSet resultSet = connection.createStatement()
+        .executeQuery("select * from \"adhoc\".\"m{27, 31}\"");
+    System.out.println(OptiqAssert.toString(resultSet));
+    connection.close();
+  }
 }
 
 // End LatticeTest.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/test/java/net/hydromatic/optiq/test/MaterializationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/MaterializationTest.java b/core/src/test/java/net/hydromatic/optiq/test/MaterializationTest.java
index 86bf601..3b6827b 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/MaterializationTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/MaterializationTest.java
@@ -247,7 +247,7 @@ public class MaterializationTest {
         JdbcTest.HR_MODEL,
         OptiqAssert.checkResultContains(
             "EnumerableCalcRel(expr#0..1=[{inputs}], expr#2=[1], expr#3=[+($t1, $t2)], C=[$t3], deptno=[$t0])\n"
-            + "  EnumerableAggregateRel(group=[{1}], agg#0=[SUM($2)])\n"
+            + "  EnumerableAggregateRel(group=[{1}], agg#0=[$SUM0($2)])\n"
             + "    EnumerableTableAccessRel(table=[[hr, m0]])"));
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java b/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java
index ec3aea4..d1ca838 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java
@@ -26,6 +26,7 @@ import org.junit.Test;
 import java.io.IOException;
 import java.util.*;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.*;
 
 /**
@@ -176,7 +177,7 @@ public class ModelTest {
         + "SemiMutableSchema");
   }
 
-  /** Tests a model containing a lattice. */
+  /** Tests a model containing a lattice and some views. */
   @Test public void testReadLattice() throws IOException {
     final ObjectMapper mapper = mapper();
     JsonRoot root = mapper.readValue(
@@ -201,12 +202,26 @@ public class ModelTest {
         + "               name: 'time_id'\n"
         + "             }\n"
         + "           ]\n"
+        + "         },\n"
+        + "         {\n"
+        + "           name: 'V',\n"
+        + "           type: 'view',\n"
+        + "           sql: 'values (1)'\n"
+        + "         },\n"
+        + "         {\n"
+        + "           name: 'V2',\n"
+        + "           type: 'view',\n"
+        + "           sql: [ 'values (1)', '(2)' ]\n"
         + "         }\n"
         + "       ],\n"
         + "       lattices: [\n"
         + "         {\n"
         + "           name: 'SalesStar',\n"
         + "           sql: 'select * from sales_fact_1997'\n"
+        + "         },\n"
+        + "         {\n"
+        + "           name: 'SalesStar2',\n"
+        + "           sql: [ 'select *', 'from sales_fact_1997' ]\n"
         + "         }\n"
         + "       ]\n"
         + "     }\n"
@@ -217,10 +232,55 @@ public class ModelTest {
     assertEquals(1, root.schemas.size());
     final JsonMapSchema schema = (JsonMapSchema) root.schemas.get(0);
     assertEquals("FoodMart", schema.name);
-    assertEquals(1, schema.lattices.size());
+    assertEquals(2, schema.lattices.size());
     final JsonLattice lattice0 = schema.lattices.get(0);
     assertEquals("SalesStar", lattice0.name);
-    assertEquals("select * from sales_fact_1997", lattice0.sql);
+    assertEquals("select * from sales_fact_1997", lattice0.getSql());
+    final JsonLattice lattice1 = schema.lattices.get(1);
+    assertEquals("SalesStar2", lattice1.name);
+    assertEquals("select *\nfrom sales_fact_1997\n", lattice1.getSql());
+    assertEquals(4, schema.tables.size());
+    final JsonTable table1 = schema.tables.get(1);
+    assertTrue(!(table1 instanceof JsonView));
+    final JsonTable table2 = schema.tables.get(2);
+    assertTrue(table2 instanceof JsonView);
+    assertThat(((JsonView) table2).getSql(), equalTo("values (1)"));
+    final JsonTable table3 = schema.tables.get(3);
+    assertTrue(table3 instanceof JsonView);
+    assertThat(((JsonView) table3).getSql(), equalTo("values (1)\n(2)\n"));
+  }
+
+  /** Tests a model with bad multi-line SQL. */
+  @Test public void testReadBadMultiLineSql() throws IOException {
+    final ObjectMapper mapper = mapper();
+    JsonRoot root = mapper.readValue(
+        "{\n"
+        + "  version: '1.0',\n"
+        + "   schemas: [\n"
+        + "     {\n"
+        + "       name: 'FoodMart',\n"
+        + "       tables: [\n"
+        + "         {\n"
+        + "           name: 'V',\n"
+        + "           type: 'view',\n"
+        + "           sql: [ 'values (1)', 2 ]\n"
+        + "         }\n"
+        + "       ]\n"
+        + "     }\n"
+        + "   ]\n"
+        + "}",
+        JsonRoot.class);
+    assertEquals(1, root.schemas.size());
+    final JsonMapSchema schema = (JsonMapSchema) root.schemas.get(0);
+    assertEquals(1, schema.tables.size());
+    final JsonView table1 = (JsonView) schema.tables.get(0);
+    try {
+      String s = table1.getSql();
+      fail("exprcted error, got " + s);
+    } catch (RuntimeException e) {
+      assertThat(e.getMessage(),
+          equalTo("each element of a string list must be a string; found: 2"));
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/core/src/test/resources/mysql-foodmart-lattice-model.json
----------------------------------------------------------------------
diff --git a/core/src/test/resources/mysql-foodmart-lattice-model.json b/core/src/test/resources/mysql-foodmart-lattice-model.json
new file mode 100644
index 0000000..09e574e
--- /dev/null
+++ b/core/src/test/resources/mysql-foodmart-lattice-model.json
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+{
+  version: '1.0',
+  defaultSchema: 'foodmart',
+  schemas: [ {
+    type: 'jdbc',
+    name: 'foodmart',
+    jdbcUser: 'foodmart',
+    jdbcPassword: 'foodmart',
+    jdbcUrl: 'jdbc:mysql://localhost',
+    jdbcCatalog: 'foodmart',
+    jdbcSchema: null
+  },
+  {
+    name: 'adhoc',
+    lattices: [ {
+      name: 'star',
+      sql: [
+        'select 1 from "foodmart"."sales_fact_1997" as "s"',
+        'join "foodmart"."product" as "p" using ("product_id")',
+        'join "foodmart"."time_by_day" as "t" using ("time_id")',
+        'join "foodmart"."product_class" as "pc" on "p"."product_class_id" = "pc"."product_class_id"'
+      ],
+      auto: false,
+      algorithm: true,
+      rowCountEstimate: 86837,
+      defaultMeasures: [ {
+        agg: 'count'
+      } ],
+      tiles: [ {
+        dimensions: [ 'the_year', ['t', 'quarter'] ],
+        measures: [ {
+          agg: 'sum',
+          args: 'unit_sales'
+        }, {
+          agg: 'sum',
+          args: 'store_sales'
+        }, {
+          agg: 'count'
+        } ]
+      } ]
+    } ]
+  } ]
+}

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/41215c2f/doc/MODEL.md
----------------------------------------------------------------------
diff --git a/doc/MODEL.md b/doc/MODEL.md
index 367e66b..e9febf2 100644
--- a/doc/MODEL.md
+++ b/doc/MODEL.md
@@ -91,7 +91,8 @@ Like base class <a href="#schema">Schema</a>, occurs within `root.schemas`.
 }
 ```
 
-`name`, `type`, `path`, `cache`, `materializations` inherited from <a href="#schema">Schema</a>.
+`name`, `type`, `path`, `cache`, `materializations` inherited from
+<a href="#schema">Schema</a>.
 
 `tables` (optional list of <a href="#table">Table</a> elements)
 defines the tables in this schema.
@@ -115,7 +116,8 @@ Like base class <a href="#schema">Schema</a>, occurs within `root.schemas`.
 }
 ```
 
-`name`, `type`, `path`, `cache`, `materializations` inherited from <a href="#schema">Schema</a>.
+`name`, `type`, `path`, `cache`, `materializations` inherited from
+<a href="#schema">Schema</a>.
 
 `factory` (required string) is the name of the factory class for this
 schema. Must implement interface `net.hydromatic.optiq.SchemaFactory`
@@ -141,33 +143,43 @@ Like base class <a href="#schema">Schema</a>, occurs within `root.schemas`.
 }
 ```
 
-`name`, `type`, `path`, `cache`, `materializations` inherited from <a href="#schema">Schema</a>.
+`name`, `type`, `path`, `cache`, `materializations` inherited from
+<a href="#schema">Schema</a>.
 
-`jdbcDriver` (optional string) is TODO.
+`jdbcDriver` (optional string) is the name of the JDBC driver class. It not
+specified, uses whichever class the JDBC DriverManager chooses.
 
-`jdbcUrl` (optional string) is TODO.
+`jdbcUrl` (optional string) is the JDBC connect string, for example
+"jdbc:mysql://localhost/foodmart".
 
-`jdbcUser` (optional string) is TODO.
+`jdbcUser` (optional string) is the JDBC user name.
 
-`jdbcPassword` (optional string) is TODO.
+`jdbcPassword` (optional string) is the JDBC password.
 
-`jdbcCatalog` (optional string) is TODO.
+`jdbcCatalog` (optional string) is the name of the initial catalog in the JDBC
+data source.
 
-`jdbcSchema` (optional string) is TODO.
+`jdbcSchema` (optional string) is the name of the initial schema in the JDBC
+data source.
 
 ### Materialization
 
 Occurs within `root.schemas.materializations`.
 
 ```json
-TODO
+{
+  view: 'V',
+  table: 'T',
+  sql: 'select deptno, count(*) as c, sum(sal) as s from emp group by deptno'
+}
 ```
 
 `view` (optional string) TODO
 
 `table` (optional string) TODO
 
-`sql` (optional string) TODO
+`sql` (optional string, or list of strings that will be concatenated as a
+ multi-line string) is the SQL definition of the materialization.
 
 ### Table
 
@@ -202,7 +214,8 @@ Like base class <a href="#table">Table</a>, occurs within `root.schemas.tables`.
 
 `name`, `type`, `columns` inherited from <a href="#table">Table</a>.
 
-`sql` (required string) is the SQL definition of the view.
+`sql` (required string, or list of strings that will be concatenated as a
+ multi-line string) is the SQL definition of the view.
 
 `path` (optional list) is the SQL path to resolve the query. If not
 specified, defaults to the current schema.
@@ -236,7 +249,9 @@ factory.
 Occurs within `root.schemas.tables.columns`.
 
 ```json
-TODO
+{
+  name: 'empno'
+}
 ```
 
 `name` (required string) is the name of this column.
@@ -246,11 +261,133 @@ TODO
 Occurs within `root.schemas.functions`.
 
 ```json
-TODO
+{
+  name: 'MY_PLUS',
+  className: 'com.example.functions.MyPlusFunction',
+  methodName: 'apply',
+  path: []
+}
 ```
 
 `name` (required string) is the name of this function.
 
-`className` (required string) is the name of the class that implements this function.
+`className` (required string) is the name of the class that implements this
+function.
+
+`methodName` (optional string) is the name of the method that implements this
+function.
 
 `path` (optional list of string) is the path for resolving this function.
+
+### Lattice
+
+Occurs within `root.schemas.lattices`.
+
+```json
+{
+  name: 'star',
+  sql: [
+    'select 1 from "foodmart"."sales_fact_1997" as "s"',
+    'join "foodmart"."product" as "p" using ("product_id")',
+    'join "foodmart"."time_by_day" as "t" using ("time_id")',
+    'join "foodmart"."product_class" as "pc" on "p"."product_class_id" = "pc"."product_class_id"'
+  ],
+  auto: false,
+  algorithm: true,
+  algorithmMaxMillis: 10000,
+  rowCountEstimate: 86837,
+  defaultMeasures: [ {
+    agg: 'count'
+  } ],
+  tiles: [ {
+    dimensions: [ 'the_year', ['t', 'quarter'] ],
+    measures: [ {
+      agg: 'sum',
+      args: 'unit_sales'
+    }, {
+      agg: 'sum',
+      args: 'store_sales'
+    }, {
+      agg: 'count'
+    } ]
+  } ]
+}
+```
+
+`name` (required string) is the name of this lattice.
+
+`sql` (required string, or list of strings that will be concatenated as a
+multi-line string) is the SQL statement that defines the fact table, dimension
+tables, and join paths for this lattice.
+
+`auto` (optional boolean, default true) is whether to materialize tiles on need
+as queries are executed.
+
+`algorithm` (optional boolean, default false) is whether to use an optimization
+algorithm to suggest and populate an initial set of tiles.
+
+`algorithmMaxMillis` (optional long, default -1, meaning no limit) is the
+maximum number of milliseconds for which to run the algorithm. After this point,
+takes the best result the algorithm has come up with so far.
+
+`rowCountEstimate` (optional double, default 1000.0) estimated number of rows in
+the star
+
+`tiles` (optional list of <a href="#tile">Tile</a> elements) is a list of
+materialized aggregates to create up front.
+
+`defaultMeasures`  (optional list of <a href="#measure">Measure</a> elements)
+is a list of measures that a tile should have by default.
+Any tile defined in `tiles` can still define its own measures, including
+measures not on this list. If not specified, the default list of measures is
+just 'count(*)':
+
+```json
+[ { name: 'count' } ]
+```
+
+### Tile
+
+Occurs within `root.schemas.lattices.tiles`.
+
+```json
+{
+  dimensions: [ 'the_year', ['t', 'quarter'] ],
+  measures: [ {
+    agg: 'sum',
+    args: 'unit_sales'
+  }, {
+    agg: 'sum',
+    args: 'store_sales'
+  }, {
+    agg: 'count'
+  } ]
+}
+```
+
+`dimensions` is a list of dimensions (columns from the star), like a `GROUP BY`
+clause. Each element is either a string (the unique label of the column within
+the star) or a string list (a column name qualified by a table name).
+
+`measures` (optional list of <a href="#measure">Measure</a> elements) is a list
+of aggregate functions applied to arguments. If not specified, uses the
+lattice's default measure list.
+
+### Measure
+
+Occurs within `root.schemas.lattices.defaultMeasures`
+and `root.schemas.lattices.tiles.measures`.
+
+```json
+{
+  agg: 'sum',
+  args: [ 'unit_sales' ]
+}
+```
+
+`agg` is the name of an aggregate function (usually 'count', 'sum', 'min',
+'max').
+
+`args` (optional) is a column label (string), or list of zero or more columns.
+If a list, each element is either a string (the unique label of the column
+within the star) or a string list (a column name qualified by a table name).