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/09/05 03:39:39 UTC

[1/2] [OPTIQ-402] Lattice should create materializations on demand

Repository: incubator-optiq
Updated Branches:
  refs/heads/master 32e0b6afb -> c3bf95bb5


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 2c473c9..e1cacd2 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
@@ -16,14 +16,23 @@
  */
 package net.hydromatic.optiq.test;
 
+import net.hydromatic.linq4j.function.Function1;
+
+import net.hydromatic.optiq.runtime.Hook;
+
+import org.eigenbase.rel.RelNode;
+import org.eigenbase.relopt.RelOptUtil;
 import org.eigenbase.util.TestUtil;
 import org.eigenbase.util.Util;
 
+import com.google.common.base.Function;
+
 import org.junit.Test;
 
 import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertThat;
@@ -123,8 +132,8 @@ public class LatticeTest {
   @Test public void testLatticeInvalidSql() {
     modelWithLattice("star",
         "select 1 from \"foodmart\".\"sales_fact_1997\" as s\n"
-        + "join \"foodmart\".\"product\" as p using (\"product_id\")\n"
-        + "join \"foodmart\".\"time_by_day\" as t on s.\"product_id\" = 100")
+            + "join \"foodmart\".\"product\" as p using (\"product_id\")\n"
+            + "join \"foodmart\".\"time_by_day\" as t on s.\"product_id\" = 100")
         .connectThrows("only equi-join of columns allowed: 100");
   }
 
@@ -140,39 +149,94 @@ public class LatticeTest {
   /** When a lattice is registered, there is a table with the same name.
    * It can be used for explain, but not for queries. */
   @Test public void testLatticeStarTable() {
+    final AtomicInteger counter = new AtomicInteger();
     try {
       foodmartModel()
           .query("select count(*) from \"adhoc\".\"star\"")
-          .convertContains(
-              "AggregateRel(group=[{}], EXPR$0=[COUNT()])\n"
-              + "  ProjectRel(DUMMY=[0])\n"
-              + "    StarTableScan(table=[[adhoc, star]])\n");
+          .convertMatches(
+              OptiqAssert.checkRel(
+                  "AggregateRel(group=[{}], EXPR$0=[COUNT()])\n"
+                  + "  ProjectRel(DUMMY=[0])\n"
+                  + "    StarTableScan(table=[[adhoc, star]])\n",
+                  counter));
     } catch (RuntimeException e) {
       assertThat(Util.getStackTrace(e), containsString("CannotPlanException"));
     }
+    assertThat(counter.get(), equalTo(1));
   }
 
   /** Tests that a 2-way join query can be mapped 4-way join lattice. */
   @Test public void testLatticeRecognizeJoin() {
     final AtomicInteger counter = new AtomicInteger();
     foodmartModel()
-      .query(
-          "select s.\"unit_sales\", p.\"brand_name\"\n"
-          + "from \"foodmart\".\"sales_fact_1997\" as s\n"
-          + "join \"foodmart\".\"product\" as p using (\"product_id\")\n")
+        .query(
+            "select s.\"unit_sales\", p.\"brand_name\"\n"
+            + "from \"foodmart\".\"sales_fact_1997\" as s\n"
+            + "join \"foodmart\".\"product\" as p using (\"product_id\")\n")
+        .enableMaterializations(true)
         .substitutionMatches(
             OptiqAssert.checkRel(
-                "ProjectRel(unit_sales=[$1], brand_name=[$3])\n"
-                + "  JoinRel(condition=[=($0, $2)], joinType=[inner])\n"
-                + "    ProjectRel(product_id=[$0], unit_sales=[$7])\n"
-                + "      ProjectRel($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[$4], $f5=[$5], $f6=[$6], $f7=[$7])\n"
-                + "        TableAccessRel(table=[[adhoc, star]])\n"
-                + "    ProjectRel(product_id=[$1], brand_name=[$2])\n"
-                + "      JdbcTableScan(table=[[foodmart, product]])\n",
+                "ProjectRel(unit_sales=[$7], brand_name=[$10])\n"
+                + "  ProjectRel($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[$4], $f5=[$5], $f6=[$6], $f7=[$7], $f8=[$8], $f9=[$9], $f10=[$10], $f11=[$11], $f12=[$12], $f13=[$13], $f14=[$14], $f15=[$15], $f16=[$16], $f17=[$17], $f18=[$18], $f19=[$19], $f20=[$20], $f21=[$21], $f22=[$22])\n"
+                + "    TableAccessRel(table=[[adhoc, star]])\n",
                 counter));
     assertThat(counter.intValue(), equalTo(1));
   }
 
+  /** Tests an aggregate on a 2-way join query can use an aggregate table. */
+  @Test public void testLatticeRecognizeGroupJoin() {
+    final AtomicInteger counter = new AtomicInteger();
+    OptiqAssert.AssertQuery that = foodmartModel()
+        .query(
+            "select distinct p.\"brand_name\", s.\"customer_id\"\n"
+            + "from \"foodmart\".\"sales_fact_1997\" as s\n"
+            + "join \"foodmart\".\"product\" as p using (\"product_id\")\n")
+        .enableMaterializations(true)
+        .substitutionMatches(
+            new Function1<RelNode, Void>() {
+              public Void apply(RelNode relNode) {
+                counter.incrementAndGet();
+                String s = RelOptUtil.toString(relNode);
+                assertThat(s,
+                    anyOf(
+                        containsString(
+                            "AggregateRel(group=[{0, 1}])\n"
+                            + "  ProjectRel(brand_name=[$10], customer_id=[$2])\n"
+                            + "    ProjectRel($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[$4], $f5=[$5], $f6=[$6], $f7=[$7], $f8=[$8], $f9=[$9], $f10=[$10], $f11=[$11], $f12=[$12], $f13=[$13], $f14=[$14], $f15=[$15], $f16=[$16], $f17=[$17], $f18=[$18], $f19=[$19], $f20=[$20], $f21=[$21], $f22=[$22])\n"
+                            + "      TableAccessRel(table=[[adhoc, star]])\n"),
+                        containsString(
+                            "AggregateRel(group=[{0, 1}])\n"
+                            + "  ProjectRel(customer_id=[$2], brand_name=[$10])\n"
+                            + "    ProjectRel($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[$4], $f5=[$5], $f6=[$6], $f7=[$7], $f8=[$8], $f9=[$9], $f10=[$10], $f11=[$11], $f12=[$12], $f13=[$13], $f14=[$14], $f15=[$15], $f16=[$16], $f17=[$17], $f18=[$18], $f19=[$19], $f20=[$20], $f21=[$21], $f22=[$22])\n"
+                            + "      TableAccessRel(table=[[adhoc, star]])\n")));
+                return null;
+              }
+            });
+    assertThat(counter.intValue(), equalTo(2));
+    that.explainContains(
+        "EnumerableCalcRel(expr#0..1=[{inputs}], $f0=[$t1], $f1=[$t0])\n"
+        + "  EnumerableTableAccessRel(table=[[adhoc, m{2, 10}]])")
+        .returnsCount(69203);
+
+    // Run the same query again and see whether it uses the same
+    // materialization.
+    that.withHook(
+        Hook.CREATE_MATERIALIZATION,
+        new Function<String, Void>() {
+          public Void apply(String materializationName) {
+            counter.incrementAndGet();
+            return null;
+          }
+        })
+        .returnsCount(69203);
+
+    // Ideally the counter would stay at 2. It increments to 3 because
+    // OptiqAssert.AssertQuery creates a new schema for every request,
+    // and therefore cannot re-use lattices or materializations from the
+    // previous request.
+    assertThat(counter.intValue(), equalTo(3));
+  }
+
   private OptiqAssert.AssertThat foodmartModel() {
     return modelWithLattice("star",
         "select 1 from \"foodmart\".\"sales_fact_1997\" as s\n"

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java b/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
index b2fe555..4cc87ce 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
@@ -274,11 +274,10 @@ public class OptiqAssert {
     return new Function1<ResultSet, Void>() {
       public Void apply(ResultSet resultSet) {
         try {
-          final List<String> expectedList = new ArrayList<String>();
-          Collections.addAll(expectedList, lines);
+          final List<String> expectedList = Lists.newArrayList(lines);
           Collections.sort(expectedList);
 
-          final List<String> actualList = new ArrayList<String>();
+          final List<String> actualList = Lists.newArrayList();
           OptiqAssert.toStringList(resultSet, actualList);
           Collections.sort(actualList);
 
@@ -374,6 +373,9 @@ public class OptiqAssert {
       ((OptiqConnection) connection).getProperties().setProperty(
           OptiqConnectionProperty.MATERIALIZATIONS_ENABLED.camelName(),
           Boolean.toString(materializationsEnabled));
+      ((OptiqConnection) connection).getProperties().setProperty(
+          OptiqConnectionProperty.CREATE_MATERIALIZATIONS.camelName(),
+          Boolean.toString(materializationsEnabled));
       for (Pair<Hook, Function> hook : hooks) {
         closeableList.add(hook.left.addThread(hook.right));
       }
@@ -446,6 +448,9 @@ public class OptiqAssert {
       ((OptiqConnection) connection).getProperties().setProperty(
           OptiqConnectionProperty.MATERIALIZATIONS_ENABLED.camelName(),
           Boolean.toString(materializationsEnabled));
+      ((OptiqConnection) connection).getProperties().setProperty(
+          OptiqConnectionProperty.CREATE_MATERIALIZATIONS.camelName(),
+          Boolean.toString(materializationsEnabled));
       PreparedStatement statement = connection.prepareStatement(sql);
       statement.close();
       connection.close();

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java b/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java
index 1d46a40..102309b 100644
--- a/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java
+++ b/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java
@@ -58,14 +58,6 @@ public class MockRelOptPlanner extends AbstractRelOptPlanner {
     return root;
   }
 
-  public void addMaterialization(RelOptMaterialization materialization) {
-    // ignore - this planner does not support materializations
-  }
-
-  public void addLattice(RelOptLattice lattice) {
-    // ignore - this planner does not support lattices
-  }
-
   @Override public void clear() {
     super.clear();
     this.rule = null;

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 1bd4708..31a9906 100644
--- a/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
@@ -25,6 +25,7 @@ import org.eigenbase.rel.metadata.ChainedRelMetadataProvider;
 import org.eigenbase.rel.metadata.DefaultRelMetadataProvider;
 import org.eigenbase.rel.metadata.RelMetadataProvider;
 import org.eigenbase.rel.rules.AddRedundantSemiJoinRule;
+import org.eigenbase.rel.rules.AggregateProjectMergeRule;
 import org.eigenbase.rel.rules.CoerceInputsRule;
 import org.eigenbase.rel.rules.ConvertMultiJoinRule;
 import org.eigenbase.rel.rules.ExtractJoinFilterRule;
@@ -871,6 +872,17 @@ public class RelOptRulesTest extends RelOptTestBase {
     basePullConstantTroughAggregate();
   }
 
+  @Test public void testAggregateProjectMerge() throws Exception {
+    HepProgram program = new HepProgramBuilder()
+        .addRuleInstance(AggregateProjectMergeRule.INSTANCE)
+        .build();
+    checkPlanning(program,
+        "select x, sum(z), y from (\n"
+        + "  select deptno as x, empno as y, sal as z, sal * 2 as zz\n"
+        + "  from emp)\n"
+        + "group by x, y");
+  }
+
   public void transitiveInference() throws Exception {
     final DiffRepository diffRepos = getDiffRepos();
     String sql = diffRepos.expand(null, "${sql}");

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml b/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
index 0232bf9..e96dfb1 100644
--- a/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
@@ -2133,4 +2133,29 @@ ProjectRel(DEPTNO=[$0])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testAggregateProjectMerge">
+        <Resource name="sql">
+            <![CDATA[select x, sum(z), y from (
+  select deptno as x, empno as y, sal as z, sal * 2 as zz
+  from emp)
+group by x, y]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+ProjectRel(X=[$0], EXPR$1=[$2], Y=[$1])
+  AggregateRel(group=[{0, 1}], EXPR$1=[SUM($2)])
+    ProjectRel(X=[$0], Y=[$1], Z=[$2])
+      ProjectRel(X=[$7], Y=[$0], Z=[$5], ZZ=[*($5, 2)])
+        TableAccessRel(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+ProjectRel(X=[$0], EXPR$1=[$2], Y=[$1])
+  ProjectRel($f0=[$1], $f1=[$0], $f2=[$2])
+    AggregateRel(group=[{0, 7}], EXPR$1=[SUM($5)])
+      TableAccessRel(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
 </Root>

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/mongodb/src/test/java/net/hydromatic/optiq/test/MongoAdapterTest.java
----------------------------------------------------------------------
diff --git a/mongodb/src/test/java/net/hydromatic/optiq/test/MongoAdapterTest.java b/mongodb/src/test/java/net/hydromatic/optiq/test/MongoAdapterTest.java
index edc098d..bb32774 100644
--- a/mongodb/src/test/java/net/hydromatic/optiq/test/MongoAdapterTest.java
+++ b/mongodb/src/test/java/net/hydromatic/optiq/test/MongoAdapterTest.java
@@ -23,6 +23,7 @@ import org.eigenbase.util.*;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 
 import org.junit.*;
 
@@ -133,16 +134,17 @@ public class MongoAdapterTest {
     };
   }
 
+  /** Similar to {@link OptiqAssert#checkResultUnordered}, but filters strings
+   * before comparing them. */
   static Function1<ResultSet, Void> checkResultUnordered(
       final String... lines) {
     return new Function1<ResultSet, Void>() {
       public Void apply(ResultSet resultSet) {
         try {
-          final List<String> expectedList = new ArrayList<String>();
-          Collections.addAll(expectedList, lines);
+          final List<String> expectedList = Lists.newArrayList(lines);
           Collections.sort(expectedList);
 
-          final List<String> actualList = new ArrayList<String>();
+          final List<String> actualList = Lists.newArrayList();
           OptiqAssert.toStringList(resultSet, actualList);
           for (int i = 0; i < actualList.size(); i++) {
             String s = actualList.get(i);


[2/2] git commit: [OPTIQ-402] Lattice should create materializations on demand

Posted by jh...@apache.org.
[OPTIQ-402] Lattice should create materializations on demand


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

Branch: refs/heads/master
Commit: c3bf95bb539ae78ffba1db82791b660118ca167f
Parents: 32e0b6a
Author: Julian Hyde <jh...@apache.org>
Authored: Tue Sep 2 12:27:25 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Sep 4 17:59:34 2014 -0700

----------------------------------------------------------------------
 .../main/java/net/hydromatic/optiq/Schemas.java |  51 ++++++--
 .../optiq/config/OptiqConnectionConfig.java     |  11 ++
 .../optiq/config/OptiqConnectionConfigImpl.java |  87 +++++++++++++
 .../optiq/config/OptiqConnectionProperty.java   |   5 +-
 .../net/hydromatic/optiq/impl/StarTable.java    |  21 ++--
 .../optiq/impl/jdbc/JdbcImplementor.java        |   2 +-
 .../optiq/jdbc/OptiqConnectionImpl.java         |  51 +-------
 .../hydromatic/optiq/materialize/Lattice.java   |  66 +++++++++-
 .../optiq/materialize/MaterializationActor.java |  34 ++++-
 .../materialize/MaterializationService.java     |  18 ++-
 .../optiq/prepare/OptiqCatalogReader.java       |   2 +-
 .../optiq/prepare/OptiqMaterializer.java        |   2 +-
 .../optiq/prepare/OptiqPrepareImpl.java         |   8 +-
 .../net/hydromatic/optiq/prepare/Prepare.java   |   2 +-
 .../optiq/prepare/QueryableRelBuilder.java      |   2 +-
 .../optiq/prepare/RelOptTableImpl.java          |  35 ++++--
 .../hydromatic/optiq/rules/java/JavaRules.java  |   4 +-
 .../java/net/hydromatic/optiq/runtime/Hook.java |   3 +
 .../net/hydromatic/optiq/tools/Programs.java    |  17 +--
 .../java/org/eigenbase/rel/AggregateCall.java   |  10 ++
 .../rel/rules/AggregateProjectMergeRule.java    | 123 +++++++++++++++++++
 .../rel/rules/AggregateStarTableRule.java       | 115 +++++++++++++++++
 .../rules/PushAggregateThroughUnionRule.java    |   2 +-
 .../eigenbase/relopt/AbstractRelOptPlanner.java |  19 ++-
 .../java/org/eigenbase/relopt/Contexts.java     |  65 ++++++++++
 .../org/eigenbase/relopt/RelOptLattice.java     | 110 ++++++++++++++++-
 .../eigenbase/relopt/RelOptMaterialization.java |  48 +++++---
 .../org/eigenbase/relopt/RelOptPlanner.java     |   8 +-
 .../eigenbase/relopt/SubstitutionVisitor.java   |   8 +-
 .../org/eigenbase/relopt/hep/HepPlanner.java    |  18 +--
 .../relopt/volcano/VolcanoPlanner.java          |  39 ++++--
 .../main/java/org/eigenbase/rex/RexBuilder.java |   4 +-
 .../main/java/org/eigenbase/sql/SqlDialect.java |   1 +
 .../org/eigenbase/sql2rel/RelFieldTrimmer.java  |   7 +-
 .../main/java/org/eigenbase/util/XmlOutput.java |   5 +-
 .../net/hydromatic/optiq/test/JdbcTest.java     |  28 ++++-
 .../net/hydromatic/optiq/test/LatticeTest.java  |  98 ++++++++++++---
 .../net/hydromatic/optiq/test/OptiqAssert.java  |  11 +-
 .../org/eigenbase/test/MockRelOptPlanner.java   |   8 --
 .../org/eigenbase/test/RelOptRulesTest.java     |  12 ++
 .../org/eigenbase/test/RelOptRulesTest.xml      |  25 ++++
 .../hydromatic/optiq/test/MongoAdapterTest.java |   8 +-
 42 files changed, 993 insertions(+), 200 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 3f279c5..4d9a426 100644
--- a/core/src/main/java/net/hydromatic/optiq/Schemas.java
+++ b/core/src/main/java/net/hydromatic/optiq/Schemas.java
@@ -21,6 +21,8 @@ import net.hydromatic.linq4j.Queryable;
 import net.hydromatic.linq4j.expressions.*;
 
 import net.hydromatic.optiq.config.OptiqConnectionConfig;
+import net.hydromatic.optiq.config.OptiqConnectionConfigImpl;
+import net.hydromatic.optiq.config.OptiqConnectionProperty;
 import net.hydromatic.optiq.impl.java.JavaTypeFactory;
 import net.hydromatic.optiq.jdbc.*;
 import net.hydromatic.optiq.materialize.Lattice;
@@ -191,7 +193,8 @@ public final class Schemas {
       final List<String> schemaPath, final String sql) {
     final OptiqPrepare prepare = OptiqPrepare.DEFAULT_FACTORY.apply();
     final OptiqPrepare.Context context =
-        makeContext(connection, schema, schemaPath);
+        makeContext(connection, schema, schemaPath,
+            ImmutableMap.<OptiqConnectionProperty, String>of());
     OptiqPrepare.Dummy.push(context);
     try {
       return prepare.parse(context, sql);
@@ -207,7 +210,8 @@ public final class Schemas {
       final List<String> schemaPath, final String sql) {
     final OptiqPrepare prepare = OptiqPrepare.DEFAULT_FACTORY.apply();
     final OptiqPrepare.Context context =
-        makeContext(connection, schema, schemaPath);
+        makeContext(connection, schema, schemaPath,
+            ImmutableMap.<OptiqConnectionProperty, String>of());
     OptiqPrepare.Dummy.push(context);
     try {
       return prepare.convert(context, sql);
@@ -219,26 +223,46 @@ public final class Schemas {
   /** Prepares a SQL query for execution. For use within Optiq only. */
   public static OptiqPrepare.PrepareResult<Object> prepare(
       final OptiqConnection connection, final OptiqSchema schema,
-      final List<String> schemaPath, final String sql) {
+      final List<String> schemaPath, final String sql,
+      final ImmutableMap<OptiqConnectionProperty, String> map) {
     final OptiqPrepare prepare = OptiqPrepare.DEFAULT_FACTORY.apply();
-    return prepare.prepareSql(
-        makeContext(connection, schema, schemaPath), sql, null, Object[].class,
-        -1);
+    final OptiqPrepare.Context context =
+        makeContext(connection, schema, schemaPath, map);
+    OptiqPrepare.Dummy.push(context);
+    try {
+      return prepare.prepareSql(context, sql, null, Object[].class, -1);
+    } finally {
+      OptiqPrepare.Dummy.pop(context);
+    }
   }
 
-  private static OptiqPrepare.Context makeContext(
+  public static OptiqPrepare.Context makeContext(
       final OptiqConnection connection, final OptiqSchema schema,
-      final List<String> schemaPath) {
+      final List<String> schemaPath,
+      final ImmutableMap<OptiqConnectionProperty, String> propValues) {
     if (connection == null) {
       final OptiqPrepare.Context context0 = OptiqPrepare.Dummy.peek();
-      return makeContext(context0.config(), context0.getTypeFactory(),
+      final OptiqConnectionConfig config =
+          mutate(context0.config(), propValues);
+      return makeContext(config, context0.getTypeFactory(),
           context0.getDataContext(), schema, schemaPath);
     } else {
-      return makeContext(connection.config(), connection.getTypeFactory(),
+      final OptiqConnectionConfig config =
+          mutate(connection.config(), propValues);
+      return makeContext(config, connection.getTypeFactory(),
           createDataContext(connection), schema, schemaPath);
     }
   }
 
+  private static OptiqConnectionConfig mutate(OptiqConnectionConfig config,
+      ImmutableMap<OptiqConnectionProperty, String> propValues) {
+    for (Map.Entry<OptiqConnectionProperty, String> e : propValues.entrySet()) {
+      config =
+          ((OptiqConnectionConfigImpl) config).set(e.getKey(), e.getValue());
+    }
+    return config;
+  }
+
   private static OptiqPrepare.Context makeContext(
       final OptiqConnectionConfig connectionConfig,
       final JavaTypeFactory typeFactory,
@@ -334,6 +358,13 @@ public final class Schemas {
     }
   }
 
+  public static OptiqSchema subSchema(OptiqSchema schema, List<String> names) {
+    for (String string : names) {
+      schema = schema.getSubSchema(string, false);
+    }
+    return schema;
+  }
+
   /** 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-optiq/blob/c3bf95bb/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionConfig.java b/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionConfig.java
index 33bb4a9..a8a49ab 100644
--- a/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionConfig.java
+++ b/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionConfig.java
@@ -24,14 +24,25 @@ import net.hydromatic.avatica.Quoting;
  * a method for every property. At some point there will be similar config
  * classes for system and statement properties. */
 public interface OptiqConnectionConfig extends ConnectionConfig {
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#AUTO_TEMP */
   boolean autoTemp();
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#MATERIALIZATIONS_ENABLED */
   boolean materializationsEnabled();
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#CREATE_MATERIALIZATIONS */
+  boolean createMaterializations();
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#MODEL */
   String model();
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#LEX */
   Lex lex();
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#QUOTING */
   Quoting quoting();
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#UNQUOTED_CASING */
   Casing unquotedCasing();
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#QUOTED_CASING */
   Casing quotedCasing();
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#CASE_SENSITIVE */
   boolean caseSensitive();
+  /** @see net.hydromatic.optiq.config.OptiqConnectionProperty#SPARK */
   boolean spark();
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionConfigImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionConfigImpl.java b/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionConfigImpl.java
new file mode 100644
index 0000000..719f33b
--- /dev/null
+++ b/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionConfigImpl.java
@@ -0,0 +1,87 @@
+/*
+ * 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.config;
+
+import net.hydromatic.avatica.Casing;
+import net.hydromatic.avatica.ConnectionConfigImpl;
+import net.hydromatic.avatica.Quoting;
+
+import java.util.Properties;
+
+/** Implementation of {@link OptiqConnectionConfig}. */
+public class OptiqConnectionConfigImpl extends ConnectionConfigImpl
+    implements OptiqConnectionConfig {
+  public OptiqConnectionConfigImpl(Properties properties) {
+    super(properties);
+  }
+
+  /** Returns a copy of this configuration with one property changed. */
+  public OptiqConnectionConfigImpl set(OptiqConnectionProperty property,
+      String value) {
+    final Properties properties1 = new Properties(properties);
+    properties1.setProperty(property.camelName(), value);
+    return new OptiqConnectionConfigImpl(properties1);
+  }
+
+  public boolean autoTemp() {
+    return OptiqConnectionProperty.AUTO_TEMP.wrap(properties).getBoolean();
+  }
+
+  public boolean materializationsEnabled() {
+    return OptiqConnectionProperty.MATERIALIZATIONS_ENABLED.wrap(properties)
+        .getBoolean();
+  }
+
+  public boolean createMaterializations() {
+    return OptiqConnectionProperty.CREATE_MATERIALIZATIONS.wrap(properties)
+        .getBoolean();
+  }
+
+  public String model() {
+    return OptiqConnectionProperty.MODEL.wrap(properties).getString();
+  }
+
+  public Lex lex() {
+    return OptiqConnectionProperty.LEX.wrap(properties).getEnum(Lex.class);
+  }
+
+  public Quoting quoting() {
+    return OptiqConnectionProperty.QUOTING.wrap(properties)
+        .getEnum(Quoting.class, lex().quoting);
+  }
+
+  public Casing unquotedCasing() {
+    return OptiqConnectionProperty.UNQUOTED_CASING.wrap(properties)
+        .getEnum(Casing.class, lex().unquotedCasing);
+  }
+
+  public Casing quotedCasing() {
+    return OptiqConnectionProperty.QUOTED_CASING.wrap(properties)
+        .getEnum(Casing.class, lex().quotedCasing);
+  }
+
+  public boolean caseSensitive() {
+    return OptiqConnectionProperty.CASE_SENSITIVE.wrap(properties)
+        .getBoolean(lex().caseSensitive);
+  }
+
+  public boolean spark() {
+    return OptiqConnectionProperty.SPARK.wrap(properties).getBoolean();
+  }
+}
+
+// End OptiqConnectionConfigImpl.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionProperty.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionProperty.java b/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionProperty.java
index 7c357a6..ba3089c 100644
--- a/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionProperty.java
+++ b/core/src/main/java/net/hydromatic/optiq/config/OptiqConnectionProperty.java
@@ -31,9 +31,12 @@ public enum OptiqConnectionProperty implements ConnectionProperty {
   /** Whether to store query results in temporary tables. */
   AUTO_TEMP("autoTemp", Type.BOOLEAN, false),
 
-  /** Whether materializations are enabled. */
+  /** Whether Optiq should use materializations. */
   MATERIALIZATIONS_ENABLED("materializationsEnabled", Type.BOOLEAN, true),
 
+  /** Whether Optiq should create materializations. */
+  CREATE_MATERIALIZATIONS("createMaterializations", Type.BOOLEAN, true),
+
   /** URI of the model. */
   MODEL("model", Type.STRING, null),
 

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 1e8b2a3..37d3dc9 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
@@ -18,6 +18,7 @@ package net.hydromatic.optiq.impl;
 
 import net.hydromatic.optiq.Table;
 import net.hydromatic.optiq.TranslatableTable;
+import net.hydromatic.optiq.materialize.Lattice;
 
 import org.eigenbase.rel.*;
 import org.eigenbase.relopt.*;
@@ -27,6 +28,7 @@ import org.eigenbase.sql.validate.SqlValidatorUtil;
 import org.eigenbase.util.ImmutableIntList;
 import org.eigenbase.util.Pair;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
 import java.util.ArrayList;
@@ -44,6 +46,8 @@ import java.util.List;
  * the materialization are mapped onto the same star table.</p>
  */
 public class StarTable extends AbstractTable implements TranslatableTable {
+  public final Lattice lattice;
+
   // TODO: we'll also need a list of join conditions between tables. For now
   //  we assume that join conditions match
   public final ImmutableList<Table> tables;
@@ -52,14 +56,14 @@ public class StarTable extends AbstractTable implements TranslatableTable {
   public ImmutableIntList fieldCounts;
 
   /** Creates a StarTable. */
-  public StarTable(List<Table> tables) {
-    super();
-    this.tables = ImmutableList.copyOf(tables);
+  private StarTable(Lattice lattice, ImmutableList<Table> tables) {
+    this.lattice = Preconditions.checkNotNull(lattice);
+    this.tables = tables;
   }
 
   /** Creates a StarTable and registers it in a schema. */
-  public static StarTable of(List<Table> tables) {
-    return new StarTable(tables);
+  public static StarTable of(Lattice lattice, List<Table> tables) {
+    return new StarTable(lattice, ImmutableList.copyOf(tables));
   }
 
   public RelDataType getRowType(RelDataTypeFactory typeFactory) {
@@ -87,9 +91,8 @@ public class StarTable extends AbstractTable implements TranslatableTable {
   }
 
   public StarTable add(Table table) {
-    final List<Table> tables1 = new ArrayList<Table>(tables);
-    tables1.add(table);
-    return of(tables1);
+    return of(lattice,
+        ImmutableList.<Table>builder().addAll(tables).add(table).build());
   }
 
   /** Returns the column offset of the first column of {@code table} in this
@@ -115,7 +118,7 @@ public class StarTable extends AbstractTable implements TranslatableTable {
    *
    * <p>It has infinite cost.
    */
-  private static class StarTableScan extends TableAccessRelBase {
+  public static class StarTableScan extends TableAccessRelBase {
     public StarTableScan(RelOptCluster cluster, RelOptTable relOptTable) {
       super(cluster, cluster.traitSetOf(Convention.NONE), relOptTable);
     }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 cf38b6f..c4340ae 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
@@ -377,7 +377,7 @@ public class JdbcImplementor {
         select = asSelect();
         clauseList.addAll(this.clauses);
       }
-      Collections.addAll(clauseList, clauses);
+      clauseList.appendAll(clauses);
       Context newContext;
       final SqlNodeList selectList = select.getSelectList();
       if (selectList != null) {

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 2d640b0..b854dca 100644
--- a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqConnectionImpl.java
@@ -24,9 +24,8 @@ import net.hydromatic.linq4j.expressions.Expressions;
 import net.hydromatic.linq4j.function.Function0;
 
 import net.hydromatic.optiq.*;
-import net.hydromatic.optiq.config.Lex;
 import net.hydromatic.optiq.config.OptiqConnectionConfig;
-import net.hydromatic.optiq.config.OptiqConnectionProperty;
+import net.hydromatic.optiq.config.OptiqConnectionConfigImpl;
 import net.hydromatic.optiq.impl.AbstractSchema;
 import net.hydromatic.optiq.impl.java.JavaTypeFactory;
 import net.hydromatic.optiq.prepare.OptiqCatalogReader;
@@ -394,54 +393,6 @@ abstract class OptiqConnectionImpl
     }
   }
 
-  /** Implementation of {@link OptiqConnectionConfig}. */
-  private static class OptiqConnectionConfigImpl extends ConnectionConfigImpl
-      implements OptiqConnectionConfig {
-    public OptiqConnectionConfigImpl(Properties properties) {
-      super(properties);
-    }
-
-    public boolean autoTemp() {
-      return OptiqConnectionProperty.AUTO_TEMP.wrap(properties).getBoolean();
-    }
-
-    public boolean materializationsEnabled() {
-      return OptiqConnectionProperty.MATERIALIZATIONS_ENABLED.wrap(properties)
-          .getBoolean();
-    }
-
-    public String model() {
-      return OptiqConnectionProperty.MODEL.wrap(properties).getString();
-    }
-
-    public Lex lex() {
-      return OptiqConnectionProperty.LEX.wrap(properties).getEnum(Lex.class);
-    }
-
-    public Quoting quoting() {
-      return OptiqConnectionProperty.QUOTING.wrap(properties)
-          .getEnum(Quoting.class, lex().quoting);
-    }
-
-    public Casing unquotedCasing() {
-      return OptiqConnectionProperty.UNQUOTED_CASING.wrap(properties)
-          .getEnum(Casing.class, lex().unquotedCasing);
-    }
-
-    public Casing quotedCasing() {
-      return OptiqConnectionProperty.QUOTED_CASING.wrap(properties)
-          .getEnum(Casing.class, lex().quotedCasing);
-    }
-
-    public boolean caseSensitive() {
-      return OptiqConnectionProperty.CASE_SENSITIVE.wrap(properties)
-          .getBoolean(lex().caseSensitive);
-    }
-
-    public boolean spark() {
-      return OptiqConnectionProperty.SPARK.wrap(properties).getBoolean();
-    }
-  }
 }
 
 // End OptiqConnectionImpl.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 984b157..89b9565 100644
--- a/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java
+++ b/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java
@@ -27,6 +27,12 @@ import net.hydromatic.optiq.util.graph.*;
 import org.eigenbase.rel.*;
 import org.eigenbase.relopt.RelOptUtil;
 import org.eigenbase.rex.*;
+import org.eigenbase.sql.SqlJoin;
+import org.eigenbase.sql.SqlKind;
+import org.eigenbase.sql.SqlNode;
+import org.eigenbase.sql.SqlSelect;
+import org.eigenbase.sql.SqlUtil;
+import org.eigenbase.sql.validate.SqlValidatorUtil;
 import org.eigenbase.util.mapping.IntPair;
 
 import com.google.common.base.Preconditions;
@@ -40,6 +46,7 @@ import java.util.*;
  */
 public class Lattice {
   public final ImmutableList<Node> nodes;
+  public final ImmutableList<List<String>> columns;
 
   private Lattice(List<Node> nodes) {
     this.nodes = ImmutableList.copyOf(nodes);
@@ -54,6 +61,16 @@ public class Lattice {
         assert nodes.subList(0, i).contains(node.parent);
       }
     }
+
+    final ImmutableList.Builder<List<String>> builder = ImmutableList.builder();
+    for (Node node : nodes) {
+      if (node.scan != null) {
+        for (String name : node.scan.getRowType().getFieldNames()) {
+          builder.add(ImmutableList.of(node.alias, name));
+        }
+      }
+    }
+    columns = builder.build();
   }
 
   /** Creates a Lattice. */
@@ -67,6 +84,10 @@ public class Lattice {
     List<int[][]> tempLinks = Lists.newArrayList();
     populate(relNodes, tempLinks, parsed.relNode);
 
+    // Get aliases.
+    List<String> aliases = Lists.newArrayList();
+    populateAliases(((SqlSelect) parsed.sqlNode).getFrom(), aliases, null);
+
     // Build a graph.
     final DirectedGraph<RelNode, Edge> graph =
         DefaultDirectedGraph.create(Edge.FACTORY);
@@ -88,32 +109,52 @@ public class Lattice {
     List<Node> nodes = Lists.newArrayList();
     Node previous = null;
     final Map<RelNode, Node> map = Maps.newIdentityHashMap();
+    int previousColumn = 0;
     for (RelNode relNode : TopologicalOrderIterator.of(graph)) {
       final List<Edge> edges = graph.getInwardEdges(relNode);
       Node node;
+      final int column = previousColumn + relNode.getRowType().getFieldCount();
       if (previous == null) {
         if (!edges.isEmpty()) {
           throw new RuntimeException("root node must not have relationships: "
               + relNode);
         }
-        node = new Node((TableAccessRelBase) relNode, null, null);
+        node = new Node((TableAccessRelBase) relNode, null, null,
+            previousColumn, column, aliases.get(nodes.size()));
       } else {
         if (edges.size() != 1) {
           throw new RuntimeException(
               "child node must have precisely one parent: " + relNode);
         }
         final Edge edge = edges.get(0);
-        node =
-            new Node((TableAccessRelBase) relNode, map.get(edge.getSource()),
-                edge.pairs);
+        node = new Node((TableAccessRelBase) relNode, map.get(edge.getSource()),
+            edge.pairs, previousColumn, column, aliases.get(nodes.size()));
       }
       nodes.add(node);
       map.put(relNode, node);
       previous = node;
+      previousColumn = column;
     }
     return new Lattice(nodes);
   }
 
+  private static void populateAliases(SqlNode from, List<String> aliases,
+      String current) {
+    if (from instanceof SqlJoin) {
+      SqlJoin join = (SqlJoin) from;
+      populateAliases(join.getLeft(), aliases, null);
+      populateAliases(join.getRight(), aliases, null);
+    } else if (from.getKind() == SqlKind.AS) {
+      populateAliases(SqlUtil.stripAs(from), aliases,
+          SqlValidatorUtil.getAlias(from, -1));
+    } else {
+      if (current == null) {
+        current = SqlValidatorUtil.getAlias(from, -1);
+      }
+      aliases.add(current);
+    }
+  }
+
   private static boolean populate(List<RelNode> nodes, List<int[][]> tempLinks,
       RelNode rel) {
     if (nodes.isEmpty() && rel instanceof ProjectRel) {
@@ -177,7 +218,11 @@ public class Lattice {
     for (Node node : nodes) {
       tables.add(node.scan.getTable().unwrap(Table.class));
     }
-    return new StarTable(tables);
+    return StarTable.of(this, tables);
+  }
+
+  public List<String> getColumn(int i) {
+    return columns.get(i);
   }
 
   /** Source relation of a lattice.
@@ -189,12 +234,21 @@ public class Lattice {
     public final TableAccessRelBase scan;
     public final Node parent;
     public final ImmutableList<IntPair> link;
+    public final int startCol;
+    public final int endCol;
+    public final String alias;
 
-    public Node(TableAccessRelBase scan, Node parent, List<IntPair> link) {
+    public Node(TableAccessRelBase scan, Node parent, List<IntPair> link,
+        int startCol, int endCol, String alias) {
       this.scan = Preconditions.checkNotNull(scan);
       this.parent = parent;
       this.link = link == null ? null : ImmutableList.copyOf(link);
       assert (parent == null) == (link == null);
+      assert startCol >= 0;
+      assert endCol > startCol;
+      this.startCol = startCol;
+      this.endCol = endCol;
+      this.alias = alias;
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 80b2b6a..9ac009f 100644
--- a/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationActor.java
+++ b/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationActor.java
@@ -20,6 +20,9 @@ import net.hydromatic.optiq.jdbc.OptiqRootSchema;
 import net.hydromatic.optiq.jdbc.OptiqSchema;
 
 import org.eigenbase.reltype.RelDataType;
+import org.eigenbase.util.Util;
+
+import com.google.common.collect.Maps;
 
 import java.util.*;
 
@@ -30,8 +33,9 @@ class MaterializationActor {
   // Not an actor yet -- TODO make members private and add request/response
   // queues
 
-  final Map<MaterializationKey, Materialization> keyMap =
-      new HashMap<MaterializationKey, Materialization>();
+  final Map<MaterializationKey, Materialization> keyMap = Maps.newHashMap();
+
+  final Map<QueryKey, MaterializationKey> keyBySql = Maps.newHashMap();
 
   /** A query materialized in a table, so that reading from the table gives the
    * same results as executing the query. */
@@ -65,6 +69,32 @@ class MaterializationActor {
       this.rowType = rowType;
     }
   }
+
+  /** A materialization can be re-used if it is the same SQL, on the same
+   * schema, with the same path for resolving functions. */
+  static class QueryKey {
+    final String sql;
+    final OptiqSchema schema;
+    final List<String> path;
+
+    QueryKey(String sql, OptiqSchema schema, List<String> path) {
+      this.sql = sql;
+      this.schema = schema;
+      this.path = path;
+    }
+
+    @Override public boolean equals(Object obj) {
+      return obj == this
+          || obj instanceof QueryKey
+          && sql.equals(((QueryKey) obj).sql)
+          && schema.equals(((QueryKey) obj).schema)
+          && path.equals(((QueryKey) obj).path);
+    }
+
+    @Override public int hashCode() {
+      return Util.hashV(sql, schema, path);
+    }
+  }
 }
 
 // End MaterializationActor.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 ec53fca..2bd3973 100644
--- a/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationService.java
+++ b/core/src/main/java/net/hydromatic/optiq/materialize/MaterializationService.java
@@ -24,15 +24,19 @@ import net.hydromatic.linq4j.function.Function1;
 import net.hydromatic.linq4j.function.Functions;
 
 import net.hydromatic.optiq.*;
+import net.hydromatic.optiq.config.OptiqConnectionProperty;
 import net.hydromatic.optiq.impl.clone.CloneSchema;
 import net.hydromatic.optiq.impl.java.JavaTypeFactory;
 import net.hydromatic.optiq.jdbc.*;
 import net.hydromatic.optiq.prepare.Prepare;
+import net.hydromatic.optiq.runtime.Hook;
 
 import org.eigenbase.reltype.RelDataType;
 import org.eigenbase.reltype.RelDataTypeImpl;
 import org.eigenbase.util.Pair;
 
+import com.google.common.collect.ImmutableMap;
+
 import java.lang.reflect.Type;
 import java.util.*;
 
@@ -61,6 +65,13 @@ public class MaterializationService {
   /** Defines a new materialization. Returns its key. */
   public MaterializationKey defineMaterialization(final OptiqSchema schema,
       String viewSql, List<String> viewSchemaPath, String tableName) {
+    final MaterializationActor.QueryKey queryKey =
+        new MaterializationActor.QueryKey(viewSql, schema, viewSchemaPath);
+    final MaterializationKey existingKey = actor.keyBySql.get(queryKey);
+    if (existingKey != null) {
+      return existingKey;
+    }
+
     final OptiqConnection connection =
         MetaImpl.connect(schema.root(), null);
     final MaterializationKey key = new MaterializationKey();
@@ -71,8 +82,11 @@ public class MaterializationService {
       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);
+            Schemas.prepare(connection, schema, viewSchemaPath, viewSql, map);
         rowType = prepareResult.rowType;
         final JavaTypeFactory typeFactory = connection.getTypeFactory();
         materializedTable =
@@ -112,6 +126,7 @@ public class MaterializationService {
         schema.add(tableName, materializedTable);
       }
       tableEntry = schema.add(tableName, materializedTable);
+      Hook.CREATE_MATERIALIZATION.run(tableName);
     } else {
       tableEntry = null;
     }
@@ -125,6 +140,7 @@ public class MaterializationService {
         new MaterializationActor.Materialization(key, schema.root(),
             tableEntry, viewSql, rowType);
     actor.keyMap.put(materialization.key, materialization);
+    actor.keyBySql.put(queryKey, materialization.key);
     return key;
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/net/hydromatic/optiq/prepare/OptiqCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqCatalogReader.java b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqCatalogReader.java
index a74990e..a000b3a 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqCatalogReader.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqCatalogReader.java
@@ -92,7 +92,7 @@ public class OptiqCatalogReader implements Prepare.CatalogReader,
       final Table table = pair.getValue();
       final String name2 = pair.getKey();
       return RelOptTableImpl.create(this, table.getRowType(typeFactory),
-          schema.add(name2, table));
+          schema.add(name2, table), null);
     }
     return null;
   }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java
index ba2ae80..4e6892f 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java
@@ -110,7 +110,7 @@ class OptiqMaterializer extends OptiqPrepareImpl.OptiqPreparingStmt {
       assert table instanceof StarTable;
       RelOptTableImpl starRelOptTable =
           RelOptTableImpl.create(catalogReader, table.getRowType(typeFactory),
-              starTable);
+              starTable, null);
       final RelNode rel3 =
           RelOptMaterialization.tryUseStar(rel2, starRelOptTable);
       if (rel3 != null) {

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 4c19268..a34d80c 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
@@ -122,6 +122,8 @@ public class OptiqPrepareImpl implements OptiqPrepare {
           JavaRules.ENUMERABLE_ONE_ROW_RULE,
           JavaRules.ENUMERABLE_EMPTY_RULE,
           JavaRules.ENUMERABLE_TABLE_FUNCTION_RULE,
+          AggregateStarTableRule.INSTANCE,
+          AggregateStarTableRule.INSTANCE2,
           TableAccessRule.INSTANCE,
           COMMUTE
               ? CommutativeJoinRule.INSTANCE
@@ -227,9 +229,13 @@ public class OptiqPrepareImpl implements OptiqPrepare {
 
   /** Creates a query planner and initializes it with a default set of
    * rules. */
-  protected RelOptPlanner createPlanner(OptiqPrepare.Context prepareContext,
+  protected RelOptPlanner createPlanner(
+      final OptiqPrepare.Context prepareContext,
       org.eigenbase.relopt.Context externalContext,
       RelOptCostFactory costFactory) {
+    if (externalContext == null) {
+      externalContext = Contexts.withConfig(prepareContext.config());
+    }
     final VolcanoPlanner planner =
         new VolcanoPlanner(costFactory, externalContext);
     planner.addRelTraitDef(ConventionTraitDef.INSTANCE);

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java b/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
index 8f17eb7..310c219 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
@@ -124,7 +124,7 @@ public abstract class Prepare {
       final JavaTypeFactory typeFactory = context.getTypeFactory();
       final RelOptTableImpl starRelOptTable =
           RelOptTableImpl.create(catalogReader,
-              starTable.getTable().getRowType(typeFactory), starTable);
+              starTable.getTable().getRowType(typeFactory), starTable, null);
       planner.addLattice(
           new RelOptLattice(lattice.getLattice(), starRelOptTable));
     }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/net/hydromatic/optiq/prepare/QueryableRelBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/prepare/QueryableRelBuilder.java b/core/src/main/java/net/hydromatic/optiq/prepare/QueryableRelBuilder.java
index e31dc3e..d410b92 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/QueryableRelBuilder.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/QueryableRelBuilder.java
@@ -77,7 +77,7 @@ class QueryableRelBuilder<T> implements QueryableFactory<T> {
               .add(tableQueryable.tableName, tableQueryable.table);
       final RelOptTableImpl relOptTable =
           RelOptTableImpl.create(null, table.getRowType(translator.typeFactory),
-              tableEntry);
+              tableEntry, null);
       if (table instanceof TranslatableTable) {
         return ((TranslatableTable) table).toRel(translator, relOptTable);
       } else {

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 254565b..d5f47aa 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/RelOptTableImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/RelOptTableImpl.java
@@ -19,6 +19,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.jdbc.OptiqSchema;
@@ -32,6 +33,7 @@ import org.eigenbase.relopt.RelOptSchema;
 import org.eigenbase.reltype.RelDataType;
 import org.eigenbase.sql.SqlAccessType;
 import org.eigenbase.sql.validate.SqlMonotonicity;
+import org.eigenbase.util.Util;
 
 import com.google.common.base.Function;
 import com.google.common.base.Functions;
@@ -52,17 +54,28 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
   private final Function<Class, Expression> expressionFunction;
   private final ImmutableList<String> names;
 
+  /** Estimate for the row count, or null.
+   *
+   * <p>If not null, overrides the estimate from the actual table.
+   *
+   * <p>Useful when a table that contains a materialized query result is being
+   * used to replace a query expression that wildly underestimates the row
+   * count. Now the materialized table can tell the same lie. */
+  private final Double rowCount;
+
   private RelOptTableImpl(
       RelOptSchema schema,
       RelDataType rowType,
       List<String> names,
       Table table,
-      Function<Class, Expression> expressionFunction) {
+      Function<Class, Expression> expressionFunction,
+      Double rowCount) {
     this.schema = schema;
     this.rowType = rowType;
     this.names = ImmutableList.copyOf(names);
     this.table = table; // may be null
     this.expressionFunction = expressionFunction;
+    this.rowCount = rowCount;
     assert expressionFunction != null;
     assert rowType != null;
   }
@@ -76,13 +89,11 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
     final Function<Class, Expression> expressionFunction =
         (Function) Functions.constant(expression);
     return new RelOptTableImpl(schema, rowType, names, null,
-        expressionFunction);
+        expressionFunction, null);
   }
 
-  public static RelOptTableImpl create(
-      RelOptSchema schema,
-      RelDataType rowType,
-      final OptiqSchema.TableEntry tableEntry) {
+  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();
@@ -100,7 +111,7 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
       };
     }
     return new RelOptTableImpl(schema, rowType, tableEntry.path(),
-      tableEntry.getTable(), expressionFunction);
+      tableEntry.getTable(), expressionFunction, rowCount);
   }
 
   public static RelOptTableImpl create(
@@ -114,7 +125,7 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
           }
         };
     return new RelOptTableImpl(schema, rowType, ImmutableList.<String>of(),
-        table, expressionFunction);
+        table, expressionFunction, null);
   }
 
   public <T> T unwrap(Class<T> clazz) {
@@ -124,6 +135,11 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
     if (clazz.isInstance(table)) {
       return clazz.cast(table);
     }
+    if (clazz == OptiqSchema.class) {
+      return clazz.cast(
+          Schemas.subSchema(((OptiqCatalogReader) schema).rootSchema,
+              Util.skipLast(getQualifiedName())));
+    }
     return null;
   }
 
@@ -132,6 +148,9 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
   }
 
   public double getRowCount() {
+    if (rowCount != null) {
+      return rowCount;
+    }
     if (table != null) {
       final Double rowCount = table.getStatistic().getRowCount();
       if (rowCount != null) {

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 16ebfcd..35d3f9f 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
@@ -1281,7 +1281,9 @@ public class JavaRules {
                         Expressions.call(accumulatorInitializer, "apply"),
                         accumulatorAdder,
                         resultSelector))));
-      } else if (aggCalls.isEmpty()) {
+      } else if (aggCalls.isEmpty()
+          && groupSet.equals(
+              BitSets.range(child.getRowType().getFieldCount()))) {
         builder.add(
             Expressions.return_(
                 null,

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java b/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
index c8c7a80..b82aa3d 100644
--- a/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
+++ b/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
@@ -56,6 +56,9 @@ public enum Hook {
   /** Called to create a Program to optimize the statement. */
   PROGRAM,
 
+  /** Called when materialization is created. */
+  CREATE_MATERIALIZATION,
+
   /** Called with a query that has been generated to send to a back-end system.
    * The query might be a SQL string (for the JDBC adapter), a list of Mongo
    * pipeline expressions (for the MongoDB adapter), et cetera. */

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 945334c..cfa2cdb 100644
--- a/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
+++ b/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
@@ -89,6 +89,8 @@ public class Programs {
           OptiqPrepareImpl.COMMUTE
               ? CommutativeJoinRule.INSTANCE
               : MergeProjectRule.INSTANCE,
+          AggregateStarTableRule.INSTANCE,
+          AggregateStarTableRule.INSTANCE2,
           PushFilterPastProjectRule.INSTANCE,
           PushFilterPastJoinRule.FILTER_ON_JOIN,
           RemoveDistinctAggregateRule.INSTANCE,
@@ -150,14 +152,14 @@ public class Programs {
         final HepPlanner hepPlanner = new HepPlanner(hepProgram,
             null, noDag, null, RelOptCostImpl.FACTORY);
 
+        List<RelMetadataProvider> list = Lists.newArrayList();
         if (metadataProvider != null) {
-          List<RelMetadataProvider> list = Lists.newArrayList();
           list.add(metadataProvider);
-          hepPlanner.registerMetadataProviders(list);
-          RelMetadataProvider plannerChain =
-              ChainedRelMetadataProvider.of(list);
-          rel.getCluster().setMetadataProvider(plannerChain);
         }
+        hepPlanner.registerMetadataProviders(list);
+        RelMetadataProvider plannerChain =
+            ChainedRelMetadataProvider.of(list);
+        rel.getCluster().setMetadataProvider(plannerChain);
 
         hepPlanner.setRoot(rel);
         return hepPlanner.findBestExp();
@@ -186,13 +188,14 @@ public class Programs {
               .addMatchOrder(HepMatchOrder.BOTTOM_UP)
               .addRuleInstance(ConvertMultiJoinRule.INSTANCE)
               .build();
-          final Program program1 = of(hep, false, null);
+          final Program program1 =
+              of(hep, false, new DefaultRelMetadataProvider());
 
           // Create a program that contains a rule to expand a MultiJoinRel
           // into heuristically ordered joins.
           // We use the rule set passed in, but remove SwapJoinRule and
           // PushJoinThroughJoinRule, because they cause exhaustive search.
-          final List<RelOptRule> list = new ArrayList<RelOptRule>(rules);
+          final List<RelOptRule> list = Lists.newArrayList(rules);
           list.removeAll(
               ImmutableList.of(SwapJoinRule.INSTANCE,
                   CommutativeJoinRule.INSTANCE,

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 1732548..93a69ef 100644
--- a/core/src/main/java/org/eigenbase/rel/AggregateCall.java
+++ b/core/src/main/java/org/eigenbase/rel/AggregateCall.java
@@ -178,6 +178,16 @@ public class AggregateCall {
   }
 
   /**
+   * Creates an equivalent AggregateCall with new argument ordinals.
+   *
+   * @param args Arguments
+   * @return AggregateCall that suits new inputs and GROUP BY columns
+   */
+  public AggregateCall copy(List<Integer> args) {
+    return new AggregateCall(aggregation, distinct, args, type, name);
+  }
+
+  /**
    * Creates equivalent AggregateCall that is adapted to a new input types
    * and/or number of columns in GROUP BY.
    *

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/rel/rules/AggregateProjectMergeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/AggregateProjectMergeRule.java b/core/src/main/java/org/eigenbase/rel/rules/AggregateProjectMergeRule.java
new file mode 100644
index 0000000..c97a8c9
--- /dev/null
+++ b/core/src/main/java/org/eigenbase/rel/rules/AggregateProjectMergeRule.java
@@ -0,0 +1,123 @@
+/*
+ * 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.BitSet;
+import java.util.List;
+
+import org.eigenbase.rel.AggregateCall;
+import org.eigenbase.rel.AggregateRelBase;
+import org.eigenbase.rel.ProjectRelBase;
+import org.eigenbase.rel.RelFactories;
+import org.eigenbase.rel.RelNode;
+import org.eigenbase.relopt.RelOptRule;
+import org.eigenbase.relopt.RelOptRuleCall;
+import org.eigenbase.relopt.RelOptUtil;
+import org.eigenbase.rex.RexInputRef;
+import org.eigenbase.rex.RexNode;
+
+import net.hydromatic.optiq.util.BitSets;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+/**
+ * Planner rule that recognizes a {@link org.eigenbase.rel.AggregateRelBase}
+ * on top of a {@link org.eigenbase.rel.ProjectRelBase} and if possible
+ * aggregate through the project or removes the project.
+ *
+ * <p>This is only possible when the grouping expressions and arguments to
+ * the aggregate functions are field references (i.e. not expressions).
+ *
+ * <p>In some cases, this rule has the effect of trimming: the aggregate will
+ * use fewer columns than the project did.
+ */
+public class AggregateProjectMergeRule extends RelOptRule {
+  public static final AggregateProjectMergeRule INSTANCE =
+      new AggregateProjectMergeRule();
+
+  /** Private constructor. */
+  private AggregateProjectMergeRule() {
+    super(
+        operand(AggregateRelBase.class,
+            operand(ProjectRelBase.class, any())));
+  }
+
+  public void onMatch(RelOptRuleCall call) {
+    final AggregateRelBase aggregate = call.rel(0);
+    final ProjectRelBase project = call.rel(1);
+    RelNode x = apply(aggregate, project);
+    if (x != null) {
+      call.transformTo(x);
+    }
+  }
+
+  public static RelNode apply(AggregateRelBase aggregate,
+      ProjectRelBase project) {
+    final List<Integer> newKeys = Lists.newArrayList();
+    for (int key : BitSets.toIter(aggregate.getGroupSet())) {
+      final RexNode rex = project.getProjects().get(key);
+      if (rex instanceof RexInputRef) {
+        newKeys.add(((RexInputRef) rex).getIndex());
+      } else {
+        // Cannot handle "GROUP BY expression"
+        return null;
+      }
+    }
+
+    final ImmutableList.Builder<AggregateCall> aggCalls =
+        ImmutableList.builder();
+    for (AggregateCall aggregateCall : aggregate.getAggCallList()) {
+      final ImmutableList.Builder<Integer> newArgs = ImmutableList.builder();
+      for (int arg : aggregateCall.getArgList()) {
+        final RexNode rex = project.getProjects().get(arg);
+        if (rex instanceof RexInputRef) {
+          newArgs.add(((RexInputRef) rex).getIndex());
+        } else {
+          // Cannot handle "AGG(expression)"
+          return null;
+        }
+      }
+      aggCalls.add(aggregateCall.copy(newArgs.build()));
+    }
+
+    final BitSet newGroupSet = BitSets.of(newKeys);
+    final AggregateRelBase newAggregate =
+        aggregate.copy(aggregate.getTraitSet(), project.getChild(), newGroupSet,
+            aggCalls.build());
+
+    // Add a project if the group set is not in the same order or
+    // contains duplicates.
+    RelNode rel = newAggregate;
+    if (!BitSets.toList(newGroupSet).equals(newKeys)) {
+      final List<Integer> posList = Lists.newArrayList();
+      for (int newKey : newKeys) {
+        posList.add(BitSets.toList(newGroupSet).indexOf(newKey));
+      }
+      for (int i = newAggregate.getGroupSet().cardinality();
+           i < newAggregate.getRowType().getFieldCount(); i++) {
+        posList.add(i);
+      }
+      rel = RelOptUtil.createProject(RelFactories.DEFAULT_PROJECT_FACTORY,
+          rel, posList);
+    }
+
+    return rel;
+  }
+}
+
+// End AggregateProjectMergeRule.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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
new file mode 100644
index 0000000..2edadfe
--- /dev/null
+++ b/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java
@@ -0,0 +1,115 @@
+/*
+ * 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 org.eigenbase.rel.AggregateRelBase;
+import org.eigenbase.rel.ProjectRelBase;
+import org.eigenbase.rel.RelNode;
+import org.eigenbase.relopt.RelOptCluster;
+import org.eigenbase.relopt.RelOptLattice;
+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 net.hydromatic.optiq.impl.StarTable;
+import net.hydromatic.optiq.jdbc.OptiqSchema;
+import net.hydromatic.optiq.prepare.RelOptTableImpl;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Planner rule that matches an {@link org.eigenbase.rel.AggregateRelBase} on
+ * top of a {@link net.hydromatic.optiq.impl.StarTable.StarTableScan}.
+ *
+ * <p>This pattern indicates that an aggregate table may exist. The rule asks
+ * the star table for an aggregate table at the required level of aggregation.
+ */
+public class AggregateStarTableRule extends RelOptRule {
+  public static final AggregateStarTableRule INSTANCE =
+      new AggregateStarTableRule(
+          operand(AggregateRelBase.class,
+              some(operand(StarTable.StarTableScan.class, none()))),
+          "AggregateStarTableRule");
+
+  public static final AggregateStarTableRule INSTANCE2 =
+      new AggregateStarTableRule(
+          operand(AggregateRelBase.class,
+              operand(ProjectRelBase.class,
+                  operand(StarTable.StarTableScan.class, none()))),
+          "AggregateStarTableRule:project") {
+        @Override
+        public void onMatch(RelOptRuleCall call) {
+          final AggregateRelBase aggregate = call.rel(0);
+          final ProjectRelBase project = call.rel(1);
+          final StarTable.StarTableScan scan = call.rel(2);
+          final RelNode rel =
+              AggregateProjectMergeRule.apply(aggregate, project);
+          final AggregateRelBase aggregate2;
+          final ProjectRelBase project2;
+          if (rel instanceof AggregateRelBase) {
+            project2 = null;
+            aggregate2 = (AggregateRelBase) rel;
+          } else if (rel instanceof ProjectRelBase) {
+            project2 = (ProjectRelBase) rel;
+            aggregate2 = (AggregateRelBase) project2.getChild();
+          } else {
+            return;
+          }
+          apply(call, project2, aggregate2, scan);
+        }
+      };
+
+  private AggregateStarTableRule(RelOptRuleOperand operand,
+      String description) {
+    super(operand, description);
+  }
+
+  @Override
+  public void onMatch(RelOptRuleCall call) {
+    final AggregateRelBase aggregate = call.rel(0);
+    final StarTable.StarTableScan scan = call.rel(1);
+    apply(call, null, aggregate, scan);
+  }
+
+  protected void apply(RelOptRuleCall call, ProjectRelBase postProject,
+      AggregateRelBase aggregate, StarTable.StarTableScan scan) {
+    final RelOptCluster cluster = scan.getCluster();
+    final RelOptTable table = scan.getTable();
+    final RelOptLattice lattice = call.getPlanner().getLattice(table);
+    OptiqSchema.TableEntry aggregateTable =
+        lattice.getAggregate(call.getPlanner(), aggregate.getGroupSet(),
+            aggregate.getAggCallList());
+    if (aggregateTable == null) {
+      return;
+    }
+    System.out.println(aggregateTable);
+    final double rowCount = aggregate.getRows();
+    final RelOptTable aggregateRelOptTable =
+        RelOptTableImpl.create(table.getRelOptSchema(),
+            aggregateTable.getTable().getRowType(cluster.getTypeFactory()),
+            aggregateTable, rowCount);
+    RelNode rel = aggregateRelOptTable.toRel(RelOptUtil.getContext(cluster));
+    if (postProject != null) {
+      rel = postProject.copy(postProject.getTraitSet(), ImmutableList.of(rel));
+    }
+    call.transformTo(rel);
+  }
+}
+
+// End AggregateStarTableRule.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 a225a77..a48fb40 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/PushAggregateThroughUnionRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/PushAggregateThroughUnionRule.java
@@ -151,7 +151,7 @@ public class PushAggregateThroughUnionRule extends RelOptRule {
         SqlAggFunction af = (SqlAggFunction) aggFun;
         final AggregateRelBase.AggCallBinding binding =
             new AggregateRelBase.AggCallBinding(typeFactory, af,
-            Collections.singletonList(origCall.getType()),
+                Collections.singletonList(origCall.getType()),
                 nGroupCols);
         // count(any) is always not null, however nullability of sum might
         // depend on the number of columns in GROUP BY.

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/relopt/AbstractRelOptPlanner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/AbstractRelOptPlanner.java b/core/src/main/java/org/eigenbase/relopt/AbstractRelOptPlanner.java
index 0a76545..9823b74 100644
--- a/core/src/main/java/org/eigenbase/relopt/AbstractRelOptPlanner.java
+++ b/core/src/main/java/org/eigenbase/relopt/AbstractRelOptPlanner.java
@@ -63,7 +63,8 @@ public abstract class AbstractRelOptPlanner implements RelOptPlanner {
 
   private final Set<RelTrait> traits = new HashSet<RelTrait>();
 
-  private final Context context;
+  /** External context. Never null. */
+  protected final Context context;
 
   private Executor executor;
 
@@ -76,6 +77,9 @@ public abstract class AbstractRelOptPlanner implements RelOptPlanner {
       Context context) {
     assert costFactory != null;
     this.costFactory = costFactory;
+    if (context == null) {
+      context = Contexts.empty();
+    }
     this.context = context;
 
     // In case no one calls setCancelFlag, set up a
@@ -190,6 +194,19 @@ public abstract class AbstractRelOptPlanner implements RelOptPlanner {
     return this;
   }
 
+  public void addMaterialization(RelOptMaterialization materialization) {
+    // ignore - this planner does not support materializations
+  }
+
+  public void addLattice(RelOptLattice lattice) {
+    // ignore - this planner does not support lattices
+  }
+
+  public RelOptLattice getLattice(RelOptTable table) {
+    // this planner does not support lattices
+    return null;
+  }
+
   // implement RelOptPlanner
   public void registerSchema(RelOptSchema schema) {
   }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/relopt/Contexts.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/Contexts.java b/core/src/main/java/org/eigenbase/relopt/Contexts.java
new file mode 100644
index 0000000..54dddaa
--- /dev/null
+++ b/core/src/main/java/org/eigenbase/relopt/Contexts.java
@@ -0,0 +1,65 @@
+/*
+ * 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.relopt;
+
+import net.hydromatic.optiq.config.OptiqConnectionConfig;
+
+/**
+ * Utilities for {@link Context}.
+ */
+public class Contexts {
+  public static final EmptyContext EMPTY_CONTEXT = new EmptyContext();
+
+  private Contexts() {}
+
+  /** Returns a context that contains a
+   * {@link net.hydromatic.optiq.config.OptiqConnectionConfig}. */
+  public static Context withConfig(OptiqConnectionConfig config) {
+    return new ConfigContext(config);
+  }
+
+  /** Returns a context that returns null for all inquiries. */
+  public static Context empty() {
+    return EMPTY_CONTEXT;
+  }
+
+  /** Context that contains a
+   * {@link net.hydromatic.optiq.config.OptiqConnectionConfig}. */
+  private static class ConfigContext implements Context {
+    private OptiqConnectionConfig config;
+
+    public ConfigContext(OptiqConnectionConfig config) {
+      this.config = config;
+    }
+
+    public <T> T unwrap(Class<T> clazz) {
+      if (clazz.isInstance(config)) {
+        return clazz.cast(config);
+      }
+      return null;
+    }
+  }
+
+  /** Empty context. */
+  static class EmptyContext implements Context {
+    public <T> T unwrap(Class<T> clazz) {
+      return null;
+    }
+  }
+}
+
+// End Contexts.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 b85234e..a41202b 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
@@ -16,11 +16,22 @@
  */
 package org.eigenbase.relopt;
 
+import java.util.BitSet;
 import java.util.List;
 
+import org.eigenbase.rel.AggregateCall;
 import org.eigenbase.rel.RelNode;
+import org.eigenbase.reltype.RelDataTypeField;
+import org.eigenbase.sql.SqlDialect;
+import org.eigenbase.util.mapping.IntPair;
 
+import net.hydromatic.optiq.config.OptiqConnectionConfig;
+import net.hydromatic.optiq.jdbc.OptiqSchema;
 import net.hydromatic.optiq.materialize.Lattice;
+import net.hydromatic.optiq.materialize.MaterializationKey;
+import net.hydromatic.optiq.materialize.MaterializationService;
+import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
+import net.hydromatic.optiq.util.BitSets;
 
 import com.google.common.collect.Lists;
 
@@ -29,9 +40,7 @@ import com.google.common.collect.Lists;
  */
 public class RelOptLattice {
   private final Lattice lattice;
-  private final RelOptTable starRelOptTable;
-  private final List<RelOptMaterialization> materializations =
-      Lists.newArrayList();
+  public final RelOptTable starRelOptTable;
 
   public RelOptLattice(Lattice lattice, RelOptTable starRelOptTable) {
     this.lattice = lattice;
@@ -52,6 +61,101 @@ public class RelOptLattice {
   public RelNode rewrite(RelNode node) {
     return RelOptMaterialization.tryUseStar(node, starRelOptTable);
   }
+
+  /** Retrieves a materialized table that will satisfy an aggregate query on
+   * the star table.
+   *
+   * <p>The current implementation creates a materialization and populates it.
+   *
+   * <p>Future implementations might return materializations at a different
+   * level of aggregation, from which the desired result can be obtained by
+   * rolling up.
+   *
+   * @param planner Current planner
+   * @param groupSet Grouping key
+   * @param aggCallList Aggregate functions
+   * @return Materialized table
+   */
+  public OptiqSchema.TableEntry getAggregate(RelOptPlanner planner,
+      BitSet groupSet, List<AggregateCall> aggCallList) {
+    final OptiqConnectionConfig config =
+        planner.getContext().unwrap(OptiqConnectionConfig.class);
+    if (config == null || !config.createMaterializations()) {
+      return null;
+    }
+    String sql = sql(starRelOptTable, groupSet, aggCallList);
+    final MaterializationService service = MaterializationService.instance();
+    final OptiqSchema schema = starRelOptTable.unwrap(OptiqSchema.class);
+    final MaterializationKey materializationKey = service
+        .defineMaterialization(schema, sql, schema.path(null), "m" + groupSet);
+    return service.checkValid(materializationKey);
+  }
+
+  private String sql(RelOptTable starRelOptTable, BitSet groupSet,
+      List<AggregateCall> aggCallList) {
+    BitSet columns = (BitSet) groupSet.clone();
+    for (AggregateCall call : aggCallList) {
+      for (int arg : call.getArgList()) {
+        columns.set(arg);
+      }
+    }
+    // Figure out which nodes are needed. Use a node if its columns are used
+    // or if has a child whose columns are used.
+    List<Lattice.Node> usedNodes = Lists.newArrayList();
+    for (Lattice.Node node : lattice.nodes) {
+      if (BitSets.range(node.startCol, node.endCol).intersects(columns)) {
+        use(usedNodes, node);
+      }
+    }
+    final SqlDialect dialect = SqlDialect.DatabaseProduct.OPTIQ.getDialect();
+    final StringBuilder buf = new StringBuilder();
+    buf.append("SELECT DISTINCT ");
+    int k = 0;
+    for (int i : BitSets.toIter(groupSet)) {
+      final RelDataTypeField field =
+          starRelOptTable.getRowType().getFieldList().get(i);
+      if (k++ > 0) {
+        buf.append(", ");
+      }
+      dialect.quoteIdentifier(buf, field.getName());
+    }
+    buf.append("\nFROM ");
+    for (Lattice.Node node : usedNodes) {
+      if (node.parent != null) {
+        buf.append("\nJOIN ");
+      }
+      dialect.quoteIdentifier(buf, node.scan.getTable().getQualifiedName());
+      buf.append(" AS ");
+      dialect.quoteIdentifier(buf, node.alias);
+      if (node.parent != null) {
+        buf.append(" ON ");
+        k = 0;
+        for (IntPair pair : node.link) {
+          if (k++ > 0) {
+            buf.append(" AND ");
+          }
+          dialect.quoteIdentifier(buf,
+              lattice.getColumn(node.parent.startCol + pair.source));
+          buf.append(" = ");
+          dialect.quoteIdentifier(buf,
+              lattice.getColumn(node.startCol + pair.target));
+        }
+      }
+    }
+    if (OptiqPrepareImpl.DEBUG) {
+      System.out.println("Lattice SQL:\n" + buf);
+    }
+    return buf.toString();
+  }
+
+  private void use(List<Lattice.Node> usedNodes, Lattice.Node node) {
+    if (!usedNodes.contains(node)) {
+      if (node.parent != null) {
+        use(usedNodes, node.parent);
+      }
+      usedNodes.add(node);
+    }
+  }
 }
 
 // End RelOptLattice.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java b/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java
index 74939b6..c3bf2a2 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java
@@ -17,15 +17,20 @@
 package org.eigenbase.relopt;
 
 import org.eigenbase.rel.*;
+import org.eigenbase.rel.metadata.DefaultRelMetadataProvider;
+import org.eigenbase.rel.rules.MergeProjectRule;
 import org.eigenbase.rel.rules.PullUpProjectsAboveJoinRule;
-import org.eigenbase.relopt.hep.HepPlanner;
-import org.eigenbase.relopt.hep.HepProgram;
 import org.eigenbase.sql.SqlExplainLevel;
 import org.eigenbase.util.Util;
 import org.eigenbase.util.mapping.Mappings;
 
 import net.hydromatic.optiq.Table;
 import net.hydromatic.optiq.impl.StarTable;
+import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
+import net.hydromatic.optiq.tools.Program;
+import net.hydromatic.optiq.tools.Programs;
+
+import com.google.common.collect.ImmutableList;
 
 /**
  * Records that a particular query is materialized by a particular table.
@@ -76,8 +81,10 @@ public class RelOptMaterialization {
                       starRelOptTable.getRowType().getFieldCount(),
                       0, 0, relOptTable.getRowType().getFieldCount());
 
-              return RelOptUtil.createProject(
-                  new TableAccessRel(scan.getCluster(), starRelOptTable),
+              final RelOptCluster cluster = scan.getCluster();
+              final RelNode scan2 =
+                  starRelOptTable.toRel(RelOptUtil.getContext(cluster));
+              return RelOptUtil.createProject(scan2,
                   Mappings.asList(mapping.inverse()));
             }
             return scan;
@@ -182,21 +189,24 @@ public class RelOptMaterialization {
    * as close to leaves as possible.
    */
   public static RelNode toLeafJoinForm(RelNode rel) {
-    HepProgram program = HepProgram.builder()
-        .addRuleInstance(PullUpProjectsAboveJoinRule.RIGHT_PROJECT)
-        .addRuleInstance(PullUpProjectsAboveJoinRule.LEFT_PROJECT)
-        .build();
-    final HepPlanner planner =
-        new HepPlanner(program, //
-            rel.getCluster().getPlanner().getContext());
-    planner.setRoot(rel);
-    System.out.println(
-        RelOptUtil.dumpPlan(
-            "before", rel, false, SqlExplainLevel.DIGEST_ATTRIBUTES));
-    final RelNode rel2 = planner.findBestExp();
-    System.out.println(
-        RelOptUtil.dumpPlan(
-            "after", rel2, false, SqlExplainLevel.DIGEST_ATTRIBUTES));
+    final Program program = Programs.hep(
+        ImmutableList.of(
+            PullUpProjectsAboveJoinRule.RIGHT_PROJECT,
+            PullUpProjectsAboveJoinRule.LEFT_PROJECT,
+            MergeProjectRule.INSTANCE),
+        false,
+        new DefaultRelMetadataProvider());
+    if (OptiqPrepareImpl.DEBUG) {
+      System.out.println(
+          RelOptUtil.dumpPlan(
+              "before", rel, false, SqlExplainLevel.DIGEST_ATTRIBUTES));
+    }
+    final RelNode rel2 = program.run(null, rel, null);
+    if (OptiqPrepareImpl.DEBUG) {
+      System.out.println(
+          RelOptUtil.dumpPlan(
+              "after", rel2, false, SqlExplainLevel.DIGEST_ATTRIBUTES));
+    }
     return rel2;
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java b/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java
index 4c9f3c8..5f0048a 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java
@@ -100,7 +100,8 @@ public interface RelOptPlanner {
   /**
    * Provides the Context created when this planner was constructed.
    *
-   * @return Either null or an externally defined context.
+   * @return Never null; either an externally defined context, or a dummy
+   * context that returns null for each requested interface
    */
   Context getContext();
 
@@ -164,6 +165,11 @@ public interface RelOptPlanner {
   void addLattice(RelOptLattice lattice);
 
   /**
+   * Retrieves a lattice, given its star table.
+   */
+  RelOptLattice getLattice(RelOptTable table);
+
+  /**
    * Finds the most efficient expression to implement this query.
    *
    * @throws CannotPlanException if cannot find a plan

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 9dfd5c6..adac55a 100644
--- a/core/src/main/java/org/eigenbase/relopt/SubstitutionVisitor.java
+++ b/core/src/main/java/org/eigenbase/relopt/SubstitutionVisitor.java
@@ -1120,9 +1120,7 @@ public class SubstitutionVisitor {
     return Lists.transform(aggCallList,
         new Function<AggregateCall, AggregateCall>() {
           public AggregateCall apply(AggregateCall call) {
-            return new AggregateCall(call.getAggregation(), call.isDistinct(),
-                Mappings.apply2(mapping, call.getArgList()), call.getType(),
-                call.name);
+            return call.copy(Mappings.apply2(mapping, call.getArgList()));
           }
         });
   }
@@ -1791,9 +1789,7 @@ public class SubstitutionVisitor {
       }
     }
 
-    /** Based on
-     * {@link RemoveTrivialProjectRule#strip(org.eigenbase.rel.ProjectRelBase)}.
-     */
+    /** Based on {@link RemoveTrivialProjectRule#strip}. */
     public static MutableRel strip(MutableProject project) {
       return isTrivial(project) ? project.getChild() : project;
     }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java b/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
index 05b09c2..ab94fe7 100644
--- a/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
+++ b/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
@@ -128,14 +128,6 @@ public class HepPlanner extends AbstractRelOptPlanner {
     return root;
   }
 
-  public void addMaterialization(RelOptMaterialization materialization) {
-    // ignore - this planner does not support materializations
-  }
-
-  public void addLattice(RelOptLattice lattice) {
-    // ignore - this planner does not support lattices
-  }
-
   // implement RelOptPlanner
   public boolean addRule(RelOptRule rule) {
     boolean added = allRules.add(rule);
@@ -933,15 +925,9 @@ public class HepPlanner extends AbstractRelOptPlanner {
 
     assertNoCycles();
 
-    Iterator<HepRelVertex> bfsIter =
-        new BreadthFirstIterator<HepRelVertex, DefaultEdge>(
-            graph,
-            root);
-
-    StringBuilder sb = new StringBuilder();
+    final StringBuilder sb = new StringBuilder();
     sb.append("\nBreadth-first from root:  {\n");
-    while (bfsIter.hasNext()) {
-      HepRelVertex vertex = bfsIter.next();
+    for (HepRelVertex vertex : BreadthFirstIterator.of(graph, root)) {
       sb.append("    ")
           .append(vertex)
           .append(" = ");

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java b/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
index e8b740b..23f37c0 100644
--- a/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
+++ b/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
@@ -33,12 +33,15 @@ import org.eigenbase.util.*;
 
 import net.hydromatic.linq4j.expressions.Expressions;
 
+import net.hydromatic.optiq.config.OptiqConnectionConfig;
 import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
 import net.hydromatic.optiq.runtime.Hook;
 import net.hydromatic.optiq.runtime.Spaces;
 import net.hydromatic.optiq.util.graph.*;
 
 import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 import com.google.common.collect.*;
 
 import static org.eigenbase.util.Stacks.*;
@@ -190,7 +193,9 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
   private final List<RelOptMaterialization> materializations =
       Lists.newArrayList();
 
-  private final List<RelOptLattice> lattices = Lists.newArrayList();
+  /** Map of lattices by the qualified name of their star table. */
+  private final Map<List<String>, RelOptLattice> latticeByName =
+      Maps.newLinkedHashMap();
 
   final Map<RelNode, Provenance> provenanceMap =
       new HashMap<RelNode, Provenance>();
@@ -268,12 +273,17 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
     return root;
   }
 
-  public void addMaterialization(RelOptMaterialization materialization) {
+  @Override public void addMaterialization(
+      RelOptMaterialization materialization) {
     materializations.add(materialization);
   }
 
-  public void addLattice(RelOptLattice lattice) {
-    lattices.add(lattice);
+  @Override public void addLattice(RelOptLattice lattice) {
+    latticeByName.put(lattice.starRelOptTable.getQualifiedName(), lattice);
+  }
+
+  @Override public RelOptLattice getLattice(RelOptTable table) {
+    return latticeByName.get(table.getQualifiedName());
   }
 
   private void useLattice(RelOptLattice lattice, RelNode rel) {
@@ -321,8 +331,7 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
             .addRuleInstance(MergeProjectRule.INSTANCE)
             .build();
 
-    final HepPlanner hepPlanner = new HepPlanner(program, //
-        getContext());
+    final HepPlanner hepPlanner = new HepPlanner(program, getContext());
     hepPlanner.setRoot(target);
     target = hepPlanner.findBestExp();
 
@@ -334,6 +343,13 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
   }
 
   private void useApplicableMaterializations() {
+    // Avoid using materializations while populating materializations!
+    final OptiqConnectionConfig config =
+        context.unwrap(OptiqConnectionConfig.class);
+    if (config == null || !config.materializationsEnabled()) {
+      return;
+    }
+
     // Given materializations:
     //   T = Emps Join Depts
     //   T2 = T Group by C1
@@ -376,9 +392,16 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
     final List<Pair<RelOptLattice, RelNode>> latticeUses = Lists.newArrayList();
     final Set<List<String>> queryTableNames =
         Sets.newHashSet(Iterables.transform(queryTables, GET_QUALIFIED_NAME));
-    for (RelOptLattice lattice : lattices) {
+    // Remember leaf-join form of root so we convert at most once.
+    final Supplier<RelNode> leafJoinRoot = Suppliers.memoize(
+        new Supplier<RelNode>() {
+          public RelNode get() {
+            return RelOptMaterialization.toLeafJoinForm(originalRoot);
+          }
+        });
+    for (RelOptLattice lattice : latticeByName.values()) {
       if (queryTableNames.contains(lattice.rootTable().getQualifiedName())) {
-        RelNode rel2 = lattice.rewrite(originalRoot);
+        RelNode rel2 = lattice.rewrite(leafJoinRoot.get());
         if (rel2 != null) {
           latticeUses.add(Pair.of(lattice, rel2));
         }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/rex/RexBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rex/RexBuilder.java b/core/src/main/java/org/eigenbase/rex/RexBuilder.java
index 4f569ac..d393a16 100644
--- a/core/src/main/java/org/eigenbase/rex/RexBuilder.java
+++ b/core/src/main/java/org/eigenbase/rex/RexBuilder.java
@@ -258,9 +258,7 @@ public class RexBuilder {
       final List<Integer> args = aggCall.getArgList();
       final List<Integer> nullableArgs = nullableArgs(args, aggArgTypes);
       if (!nullableArgs.equals(args)) {
-        aggCall = new AggregateCall(aggCall.getAggregation(),
-            aggCall.isDistinct(), nullableArgs,
-            aggCall.getType(), aggCall.getName());
+        aggCall = aggCall.copy(nullableArgs);
       }
     }
     RexNode rex = aggCallMapping.get(aggCall);

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/sql/SqlDialect.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/sql/SqlDialect.java b/core/src/main/java/org/eigenbase/sql/SqlDialect.java
index db0aac6..5c4d9fc 100644
--- a/core/src/main/java/org/eigenbase/sql/SqlDialect.java
+++ b/core/src/main/java/org/eigenbase/sql/SqlDialect.java
@@ -483,6 +483,7 @@ public class SqlDialect {
     INFORMIX("Informix", null),
     INGRES("Ingres", null),
     LUCIDDB("LucidDB", "\""),
+    OPTIQ("Apache Optiq", "\""),
     INTERBASE("Interbase", null),
     PHOENIX("Phoenix", "\""),
     POSTGRESQL("PostgreSQL", "\""),

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java b/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java
index b0e3ccd..39cb8b4 100644
--- a/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java
@@ -783,12 +783,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     for (AggregateCall aggCall : aggregate.getAggCallList()) {
       if (fieldsUsed.get(j)) {
         AggregateCall newAggCall =
-            new AggregateCall(
-                aggCall.getAggregation(),
-                aggCall.isDistinct(),
-                Mappings.apply2(inputMapping, aggCall.getArgList()),
-                aggCall.getType(),
-                aggCall.getName());
+            aggCall.copy(Mappings.apply2(inputMapping, aggCall.getArgList()));
         if (newAggCall.equals(aggCall)) {
           newAggCall = aggCall; // immutable -> canonize to save space
         }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/core/src/main/java/org/eigenbase/util/XmlOutput.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/util/XmlOutput.java b/core/src/main/java/org/eigenbase/util/XmlOutput.java
index d3389e5..cd529e8 100644
--- a/core/src/main/java/org/eigenbase/util/XmlOutput.java
+++ b/core/src/main/java/org/eigenbase/util/XmlOutput.java
@@ -19,6 +19,8 @@ package org.eigenbase.util;
 import java.io.*;
 import java.util.*;
 
+import com.google.common.collect.Lists;
+
 /**
  * Streaming XML output.
  *
@@ -612,8 +614,7 @@ public class XmlOutput {
     public StringEscaper getMutableClone() {
       StringEscaper clone = clone();
       if (clone.translationVector == null) {
-        clone.translationVector = new ArrayList<String>();
-        Collections.addAll(clone.translationVector, clone.translationTable);
+        clone.translationVector = Lists.newArrayList(clone.translationTable);
         clone.translationTable = null;
       }
       return clone;

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/c3bf95bb/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 9c624d5..471f973 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
@@ -2902,7 +2902,20 @@ public class JdbcTest {
             + "from \"hr\".\"emps\"\n")
         .returnsUnordered(
             "deptno=10",
-            "deptno=20")
+            "deptno=20");
+  }
+
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/OPTIQ-397">OPTIQ-397</a>,
+   * "SELECT DISTINCT *" gives ClassCastException at runtime". */
+  @Ignore("OPTIQ-397")
+  @Test public void testSelectDistinctStar() {
+    OptiqAssert.that()
+        .with(OptiqAssert.Config.REGULAR)
+        .query(
+            "select distinct *\n"
+            + "from \"hr\".\"emps\"\n")
+        .returnsCount(5)
         .planContains(".distinct(");
   }
 
@@ -2931,7 +2944,18 @@ public class JdbcTest {
             + "group by \"deptno\"")
         .returnsUnordered(
             "deptno=10",
-            "deptno=20")
+            "deptno=20");
+  }
+
+  /** Same result (and plan) as {@link #testSelectDistinct}. */
+  @Test public void testGroupByNoAggregatesAllColumns() {
+    OptiqAssert.that()
+        .with(OptiqAssert.Config.REGULAR)
+        .query(
+            "select \"deptno\"\n"
+            + "from \"hr\".\"emps\"\n"
+            + "group by \"deptno\", \"empid\", \"name\", \"salary\", \"commission\"")
+        .returnsCount(4)
         .planContains(".distinct(");
   }