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

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

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);
   }
 }