[CALCITE-2441] RelBuilder.scan should expand TranslatableTable and views

Add class ViewExpanders, which has utility methods for creating
ToRelContext and ViewExpander.


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

Branch: refs/heads/master
Commit: 9c26a9e74dafadb27ea363d82cfe1105d5dd3329
Parents: 332ffb4
Author: Julian Hyde <[email protected]>
Authored: Thu Oct 25 16:50:44 2018 -0700
Committer: Julian Hyde <[email protected]>
Committed: Wed Oct 31 12:01:28 2018 -0700

----------------------------------------------------------------------
 .../org/apache/calcite/jdbc/CalciteSchema.java  |   1 -
 .../calcite/plan/RelOptMaterialization.java     |   2 +-
 .../org/apache/calcite/plan/RelOptUtil.java     |  18 +--
 .../org/apache/calcite/plan/ViewExpanders.java  |  67 +++++++++++
 .../calcite/prepare/LixToRelTranslator.java     |  20 ++--
 .../org/apache/calcite/prepare/PlannerImpl.java |  95 +++++++++-------
 .../org/apache/calcite/prepare/Prepare.java     |   8 +-
 .../calcite/prepare/QueryableRelBuilder.java    |   3 +-
 .../apache/calcite/rel/core/RelFactories.java   |  44 ++++++++
 .../rel/rules/AggregateStarTableRule.java       |   4 +-
 .../rel/rules/LoptSemiJoinOptimizer.java        |   3 +-
 .../apache/calcite/rel/rules/TableScanRule.java |   4 +-
 .../apache/calcite/schema/impl/ViewTable.java   |  25 +++-
 .../apache/calcite/sql2rel/RelDecorrelator.java |   4 -
 .../calcite/sql2rel/SqlToRelConverter.java      |  16 +--
 .../apache/calcite/tools/FrameworkConfig.java   |   6 +
 .../org/apache/calcite/tools/Frameworks.java    |  18 ++-
 .../org/apache/calcite/tools/RelBuilder.java    |   1 +
 .../org/apache/calcite/test/CalciteAssert.java  |   5 +
 .../org/apache/calcite/test/RelBuilderTest.java | 113 ++++++++++++++-----
 .../org/apache/calcite/tools/PlannerTest.java   |  40 +++++++
 21 files changed, 364 insertions(+), 133 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java 
b/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
index 8e4c897..fa25f69 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
@@ -730,7 +730,6 @@ public abstract class CalciteSchema {
     public TableEntryImpl(CalciteSchema schema, String name, Table table,
         ImmutableList<String> sqls) {
       super(schema, name, sqls);
-      assert table != null;
       this.table = Objects.requireNonNull(table);
     }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
index e89c3f0..fea1cba 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
@@ -104,7 +104,7 @@ public class RelOptMaterialization {
 
               final RelOptCluster cluster = scan.getCluster();
               final RelNode scan2 =
-                  starRelOptTable.toRel(RelOptUtil.getContext(cluster));
+                  starRelOptTable.toRel(ViewExpanders.simpleContext(cluster));
               return RelOptUtil.createProject(scan2,
                   Mappings.asList(mapping.inverse()));
             }

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index b5063d1..cda1e66 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -20,7 +20,6 @@ import org.apache.calcite.avatica.AvaticaConnection;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.rel.RelHomogeneousShuttle;
 import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.RelRoot;
 import org.apache.calcite.rel.RelShuttle;
 import org.apache.calcite.rel.RelVisitor;
 import org.apache.calcite.rel.RelWriter;
@@ -2791,20 +2790,9 @@ public abstract class RelOptUtil {
     return query;
   }
 
-  /** Returns a simple
-   * {@link org.apache.calcite.plan.RelOptTable.ToRelContext}. */
-  public static RelOptTable.ToRelContext getContext(
-      final RelOptCluster cluster) {
-    return new RelOptTable.ToRelContext() {
-      public RelOptCluster getCluster() {
-        return cluster;
-      }
-
-      public RelRoot expandView(RelDataType rowType, String queryString,
-          List<String> schemaPath, List<String> viewPath) {
-        throw new UnsupportedOperationException();
-      }
-    };
+  @Deprecated // to be removed before 2.0
+  public static RelOptTable.ToRelContext getContext(RelOptCluster cluster) {
+    return ViewExpanders.simpleContext(cluster);
   }
 
   /** Returns the number of {@link org.apache.calcite.rel.core.Join} nodes in a

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/plan/ViewExpanders.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/ViewExpanders.java 
b/core/src/main/java/org/apache/calcite/plan/ViewExpanders.java
new file mode 100644
index 0000000..eea2e33
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/plan/ViewExpanders.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.plan;
+
+import org.apache.calcite.rel.RelRoot;
+import org.apache.calcite.rel.type.RelDataType;
+
+import java.util.List;
+import javax.annotation.Nonnull;
+
+/**
+ * Utilities for {@link RelOptTable.ViewExpander} and
+ * {@link RelOptTable.ToRelContext}.
+ */
+@Nonnull
+public abstract class ViewExpanders {
+  private ViewExpanders() {}
+
+  /** Converts a {@code ViewExpander} to a {@code ToRelContext}. */
+  public static RelOptTable.ToRelContext toRelContext(
+      RelOptTable.ViewExpander viewExpander, RelOptCluster cluster) {
+    if (viewExpander instanceof RelOptTable.ToRelContext) {
+      return (RelOptTable.ToRelContext) viewExpander;
+    }
+    return new RelOptTable.ToRelContext() {
+      public RelOptCluster getCluster() {
+        return cluster;
+      }
+
+      public RelRoot expandView(RelDataType rowType, String queryString,
+          List<String> schemaPath, List<String> viewPath) {
+        return viewExpander.expandView(rowType, queryString, schemaPath,
+            viewPath);
+      }
+    };
+  }
+
+  /** Creates a simple {@code ToRelContext} that cannot expand views. */
+  public static RelOptTable.ToRelContext simpleContext(RelOptCluster cluster) {
+    return new RelOptTable.ToRelContext() {
+      public RelOptCluster getCluster() {
+        return cluster;
+      }
+
+      public RelRoot expandView(RelDataType rowType, String queryString,
+          List<String> schemaPath, List<String> viewPath) {
+        throw new UnsupportedOperationException();
+      }
+    };
+  }
+}
+
+// End ViewExpanders.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java 
b/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
index 6994550..8a05671 100644
--- a/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
+++ b/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
@@ -27,12 +27,11 @@ import org.apache.calcite.linq4j.tree.NewExpression;
 import org.apache.calcite.linq4j.tree.Types;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.ViewExpanders;
 import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.RelRoot;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.logical.LogicalTableScan;
-import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.BuiltInMethod;
@@ -49,7 +48,7 @@ import java.util.List;
  *
  * @see QueryableRelBuilder
  */
-class LixToRelTranslator implements RelOptTable.ToRelContext {
+class LixToRelTranslator {
   final RelOptCluster cluster;
   private final Prepare preparingStmt;
   final JavaTypeFactory typeFactory;
@@ -60,13 +59,14 @@ class LixToRelTranslator implements 
RelOptTable.ToRelContext {
     this.typeFactory = (JavaTypeFactory) cluster.getTypeFactory();
   }
 
-  public RelOptCluster getCluster() {
-    return cluster;
-  }
-
-  public RelRoot expandView(RelDataType rowType, String queryString,
-      List<String> schemaPath, List<String> viewPath) {
-    return preparingStmt.expandView(rowType, queryString, schemaPath, 
viewPath);
+  RelOptTable.ToRelContext toRelContext() {
+    if (preparingStmt instanceof RelOptTable.ViewExpander) {
+      final RelOptTable.ViewExpander viewExpander =
+          (RelOptTable.ViewExpander) this.preparingStmt;
+      return ViewExpanders.toRelContext(viewExpander, cluster);
+    } else {
+      return ViewExpanders.simpleContext(cluster);
+    }
   }
 
   public <T> RelNode translate(Queryable<T> queryable) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java 
b/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java
index 6f746ab..9269155 100644
--- a/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java
@@ -60,7 +60,7 @@ import java.util.List;
 import java.util.Properties;
 
 /** Implementation of {@link org.apache.calcite.tools.Planner}. */
-public class PlannerImpl implements Planner {
+public class PlannerImpl implements Planner, ViewExpander {
   private final SqlOperatorTable operatorTable;
   private final ImmutableList<Program> programs;
   private final FrameworkConfig config;
@@ -229,7 +229,7 @@ public class PlannerImpl implements Planner {
         .withConvertTableAccess(false)
         .build();
     final SqlToRelConverter sqlToRelConverter =
-        new SqlToRelConverter(new ViewExpanderImpl(), validator,
+        new SqlToRelConverter(this, validator,
             createCatalogReader(), cluster, convertletTable, config);
     root =
         sqlToRelConverter.convertQuery(validatedSqlNode, false, true);
@@ -242,50 +242,63 @@ public class PlannerImpl implements Planner {
     return root;
   }
 
-  /** Implements {@link org.apache.calcite.plan.RelOptTable.ViewExpander}
-   * interface for {@link org.apache.calcite.tools.Planner}. */
+  /** @deprecated Now {@link PlannerImpl} implements {@link ViewExpander}
+   * directly. */
+  @Deprecated
   public class ViewExpanderImpl implements ViewExpander {
-    @Override public RelRoot expandView(RelDataType rowType, String 
queryString,
-      List<String> schemaPath, List<String> viewPath) {
-      SqlParser parser = SqlParser.create(queryString, parserConfig);
-      SqlNode sqlNode;
-      try {
-        sqlNode = parser.parseQuery();
-      } catch (SqlParseException e) {
-        throw new RuntimeException("parse failed", e);
-      }
+    ViewExpanderImpl() {
+    }
 
-      final SqlConformance conformance = conformance();
-      final CalciteCatalogReader catalogReader =
-          createCatalogReader().withSchemaPath(schemaPath);
-      final SqlValidator validator =
-          new CalciteSqlValidator(operatorTable, catalogReader, typeFactory,
-              conformance);
-      validator.setIdentifierExpansion(true);
-
-      final RexBuilder rexBuilder = createRexBuilder();
-      final RelOptCluster cluster = RelOptCluster.create(planner, rexBuilder);
-      final SqlToRelConverter.Config config = SqlToRelConverter
-          .configBuilder()
-          .withConfig(sqlToRelConverterConfig)
-          .withTrimUnusedFields(false)
-          .withConvertTableAccess(false)
-          .build();
-      final SqlToRelConverter sqlToRelConverter =
-          new SqlToRelConverter(new ViewExpanderImpl(), validator,
-              catalogReader, cluster, convertletTable, config);
-
-      root = sqlToRelConverter.convertQuery(sqlNode, true, false);
-      root = root.withRel(sqlToRelConverter.flattenTypes(root.rel, true));
-      final RelBuilder relBuilder =
-          config.getRelBuilderFactory().create(cluster, null);
-      root = root.withRel(
-          RelDecorrelator.decorrelateQuery(root.rel, relBuilder));
-
-      return PlannerImpl.this.root;
+    public RelRoot expandView(RelDataType rowType, String queryString,
+        List<String> schemaPath, List<String> viewPath) {
+      return PlannerImpl.this.expandView(rowType, queryString, schemaPath,
+          viewPath);
     }
   }
 
+  @Override public RelRoot expandView(RelDataType rowType, String queryString,
+      List<String> schemaPath, List<String> viewPath) {
+    if (planner == null) {
+      ready();
+    }
+    SqlParser parser = SqlParser.create(queryString, parserConfig);
+    SqlNode sqlNode;
+    try {
+      sqlNode = parser.parseQuery();
+    } catch (SqlParseException e) {
+      throw new RuntimeException("parse failed", e);
+    }
+
+    final SqlConformance conformance = conformance();
+    final CalciteCatalogReader catalogReader =
+        createCatalogReader().withSchemaPath(schemaPath);
+    final SqlValidator validator =
+        new CalciteSqlValidator(operatorTable, catalogReader, typeFactory,
+            conformance);
+    validator.setIdentifierExpansion(true);
+
+    final RexBuilder rexBuilder = createRexBuilder();
+    final RelOptCluster cluster = RelOptCluster.create(planner, rexBuilder);
+    final SqlToRelConverter.Config config = SqlToRelConverter
+        .configBuilder()
+        .withConfig(sqlToRelConverterConfig)
+        .withTrimUnusedFields(false)
+        .withConvertTableAccess(false)
+        .build();
+    final SqlToRelConverter sqlToRelConverter =
+        new SqlToRelConverter(this, validator,
+            catalogReader, cluster, convertletTable, config);
+
+    final RelRoot root =
+        sqlToRelConverter.convertQuery(sqlNode, true, false);
+    final RelRoot root2 =
+        root.withRel(sqlToRelConverter.flattenTypes(root.rel, true));
+    final RelBuilder relBuilder =
+        config.getRelBuilderFactory().create(cluster, null);
+    return root2.withRel(
+        RelDecorrelator.decorrelateQuery(root.rel, relBuilder));
+  }
+
   // CalciteCatalogReader is stateless; no need to store one
   private CalciteCatalogReader createCatalogReader() {
     final SchemaPlus rootSchema = rootSchema(defaultSchema);

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/prepare/Prepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/Prepare.java 
b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
index 8d1a55b..226a27f 100644
--- a/core/src/main/java/org/apache/calcite/prepare/Prepare.java
+++ b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
@@ -31,6 +31,7 @@ import org.apache.calcite.plan.RelOptSchema;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.plan.ViewExpanders;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelRoot;
@@ -175,7 +176,7 @@ public abstract class Prepare {
         if (node instanceof TableScan) {
           final RelOptCluster cluster = node.getCluster();
           final RelOptTable.ToRelContext context =
-              RelOptUtil.getContext(cluster);
+              ViewExpanders.simpleContext(cluster);
           final RelNode r = node.getTable().toRel(context);
           planner.registerClass(r);
         }
@@ -398,11 +399,6 @@ public abstract class Prepare {
     return THREAD_TRIM.get() || RelOptUtil.countJoins(rootRel) < 2;
   }
 
-  public RelRoot expandView(RelDataType rowType, String queryString,
-      List<String> schemaPath, List<String> viewPath) {
-    throw new UnsupportedOperationException();
-  }
-
   protected abstract void init(Class runtimeContextClass);
 
   protected abstract SqlValidator getSqlValidator();

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java 
b/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
index 5edf4cc..1269e9d 100644
--- a/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
@@ -99,7 +99,8 @@ class QueryableRelBuilder<T> implements QueryableFactory<T> {
           RelOptTableImpl.create(null, 
table.getRowType(translator.typeFactory),
               tableEntry, null);
       if (table instanceof TranslatableTable) {
-        return ((TranslatableTable) table).toRel(translator, relOptTable);
+        return ((TranslatableTable) table).toRel(translator.toRelContext(),
+            relOptTable);
       } else {
         return LogicalTableScan.create(translator.cluster, relOptTable);
       }

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java 
b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
index 5870f34..8baef30 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
@@ -20,6 +20,7 @@ import org.apache.calcite.plan.Contexts;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.plan.ViewExpanders;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.logical.LogicalAggregate;
@@ -37,6 +38,7 @@ import org.apache.calcite.rel.logical.LogicalValues;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.schema.TranslatableTable;
 import org.apache.calcite.sql.SemiJoinType;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.tools.RelBuilder;
@@ -49,6 +51,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
+import javax.annotation.Nonnull;
 
 /**
  * Contains factory interface and default implementation for creating various
@@ -390,6 +393,47 @@ public class RelFactories {
   }
 
   /**
+   * Creates a {@link TableScanFactory} that can expand
+   * {@link TranslatableTable} instances, but explodes on views.
+   *
+   * @param tableScanFactory Factory for non-translatable tables
+   * @return Table scan factory
+   */
+  @Nonnull public static TableScanFactory expandingScanFactory(
+      @Nonnull TableScanFactory tableScanFactory) {
+    return expandingScanFactory(
+        (rowType, queryString, schemaPath, viewPath) -> {
+          throw new UnsupportedOperationException("cannot expand view");
+        },
+        tableScanFactory);
+  }
+
+  /**
+   * Creates a {@link TableScanFactory} that uses a
+   * {@link org.apache.calcite.plan.RelOptTable.ViewExpander} to handle
+   * {@link TranslatableTable} instances, and falls back to a default
+   * factory for other tables.
+   *
+   * @param viewExpander View expander
+   * @param tableScanFactory Factory for non-translatable tables
+   * @return Table scan factory
+   */
+  @Nonnull public static TableScanFactory expandingScanFactory(
+      @Nonnull RelOptTable.ViewExpander viewExpander,
+      @Nonnull TableScanFactory tableScanFactory) {
+    return (cluster, table) -> {
+      final TranslatableTable translatableTable =
+          table.unwrap(TranslatableTable.class);
+      if (translatableTable != null) {
+        final RelOptTable.ToRelContext toRelContext =
+            ViewExpanders.toRelContext(viewExpander, cluster);
+        return translatableTable.toRel(toRelContext, table);
+      }
+      return tableScanFactory.createScan(cluster, table);
+    };
+  }
+
+  /**
    * Can create a {@link Match} of
    * the appropriate type for a rule's calling convention.
    */

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
index f1b6d6e..e974763 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
@@ -27,8 +27,8 @@ import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptRuleOperand;
 import org.apache.calcite.plan.RelOptTable;
-import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.SubstitutionVisitor;
+import org.apache.calcite.plan.ViewExpanders;
 import org.apache.calcite.prepare.CalcitePrepareImpl;
 import org.apache.calcite.prepare.RelOptTableImpl;
 import org.apache.calcite.rel.RelNode;
@@ -148,7 +148,7 @@ public class AggregateStarTableRule extends RelOptRule {
             aggregateTableRowType,
             tableEntry,
             rowCount);
-    
relBuilder.push(aggregateRelOptTable.toRel(RelOptUtil.getContext(cluster)));
+    
relBuilder.push(aggregateRelOptTable.toRel(ViewExpanders.simpleContext(cluster)));
     if (tileKey == null) {
       if (CalcitePrepareImpl.DEBUG) {
         System.out.println("Using materialization "

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java 
b/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
index 05196d4..7d322fc 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
@@ -19,6 +19,7 @@ package org.apache.calcite.rel.rules;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.ViewExpanders;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.JoinInfo;
 import org.apache.calcite.rel.core.SemiJoin;
@@ -281,7 +282,7 @@ public class LoptSemiJoinOptimizer {
     final List<Integer> bestKeyOrder = new ArrayList<>();
     LcsTableScan tmpFactRel =
         (LcsTableScan) factTable.toRel(
-            RelOptUtil.getContext(factRel.getCluster()));
+            ViewExpanders.simpleContext(factRel.getCluster()));
 
     LcsIndexOptimizer indexOptimizer = new LcsIndexOptimizer(tmpFactRel);
     FemLocalIndex bestIndex =

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/rel/rules/TableScanRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/TableScanRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/TableScanRule.java
index 851c7c3..24dd9ff 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/TableScanRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/TableScanRule.java
@@ -19,7 +19,7 @@ package org.apache.calcite.rel.rules;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptTable;
-import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.ViewExpanders;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.logical.LogicalTableScan;
@@ -53,7 +53,7 @@ public class TableScanRule extends RelOptRule {
     final LogicalTableScan oldRel = call.rel(0);
     RelNode newRel =
         oldRel.getTable().toRel(
-            RelOptUtil.getContext(oldRel.getCluster()));
+            ViewExpanders.simpleContext(oldRel.getCluster()));
     call.transformTo(newRel);
   }
 }

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java 
b/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java
index cb23cd1..0c21e64 100644
--- a/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java
+++ b/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java
@@ -24,6 +24,8 @@ import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelRoot;
+import org.apache.calcite.rel.RelShuttleImpl;
+import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelProtoDataType;
@@ -118,13 +120,26 @@ public class ViewTable
     return expandView(context, relOptTable.getRowType(), viewSql).rel;
   }
 
-  private RelRoot expandView(RelOptTable.ToRelContext preparingStmt,
+  private RelRoot expandView(RelOptTable.ToRelContext context,
       RelDataType rowType, String queryString) {
     try {
-      RelRoot root = preparingStmt.expandView(rowType, queryString, 
schemaPath, viewPath);
-
-      root = root.withRel(RelOptUtil.createCastRel(root.rel, rowType, true));
-      return root;
+      final RelRoot root =
+          context.expandView(rowType, queryString, schemaPath, viewPath);
+      final RelNode rel = RelOptUtil.createCastRel(root.rel, rowType, true);
+      // Expand any views
+      final RelNode rel2 = rel.accept(
+          new RelShuttleImpl() {
+            @Override public RelNode visit(TableScan scan) {
+              final RelOptTable table = scan.getTable();
+              final TranslatableTable translatableTable =
+                  table.unwrap(TranslatableTable.class);
+              if (translatableTable != null) {
+                return translatableTable.toRel(context, table);
+              }
+              return super.visit(scan);
+            }
+          });
+      return root.withRel(rel2);
     } catch (Exception e) {
       throw new RuntimeException("Error while parsing view definition: "
           + queryString, e);

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java 
b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
index e694d19..10cbfa1 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -101,11 +101,9 @@ import com.google.common.collect.SortedSetMultimap;
 import org.slf4j.Logger;
 
 import java.math.BigDecimal;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -2673,8 +2671,6 @@ public class RelDecorrelator implements ReflectiveVisitor 
{
     final Holder<Integer> offset = Holder.of(0);
     int corrIdGenerator = 0;
 
-    final Deque<RelNode> stack = new ArrayDeque<>();
-
     /** Creates a CorelMap by iterating over a {@link RelNode} tree. */
     CorelMap build(RelNode... rels) {
       for (RelNode rel : rels) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java 
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 6c8cb15..9750664 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -25,6 +25,7 @@ import org.apache.calcite.plan.RelOptSamplingParameters;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.plan.ViewExpanders;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.prepare.RelOptTableImpl;
 import org.apache.calcite.rel.RelCollation;
@@ -3207,20 +3208,7 @@ public class SqlToRelConverter {
   }
 
   private RelOptTable.ToRelContext createToRelContext() {
-    return new RelOptTable.ToRelContext() {
-      public RelOptCluster getCluster() {
-        return cluster;
-      }
-
-      @Override public RelRoot expandView(
-          RelDataType rowType,
-          String queryString,
-          List<String> schemaPath,
-          List<String> viewPath) {
-        return viewExpander.expandView(rowType, queryString, schemaPath, 
viewPath);
-      }
-
-    };
+    return ViewExpanders.toRelContext(viewExpander, cluster);
   }
 
   public RelNode toRel(final RelOptTable table) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/tools/FrameworkConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/FrameworkConfig.java 
b/core/src/main/java/org/apache/calcite/tools/FrameworkConfig.java
index ace0226..e4a62dd 100644
--- a/core/src/main/java/org/apache/calcite/tools/FrameworkConfig.java
+++ b/core/src/main/java/org/apache/calcite/tools/FrameworkConfig.java
@@ -19,6 +19,7 @@ package org.apache.calcite.tools;
 import org.apache.calcite.materialize.SqlStatisticProvider;
 import org.apache.calcite.plan.Context;
 import org.apache.calcite.plan.RelOptCostFactory;
+import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelTraitDef;
 import org.apache.calcite.rel.type.RelDataTypeSystem;
 import org.apache.calcite.rex.RexExecutor;
@@ -133,6 +134,11 @@ public interface FrameworkConfig {
    * direction of relationships.
    */
   SqlStatisticProvider getStatisticProvider();
+
+  /**
+   * Returns a view expander.
+   */
+  RelOptTable.ViewExpander getViewExpander();
 }
 
 // End FrameworkConfig.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/tools/Frameworks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/Frameworks.java 
b/core/src/main/java/org/apache/calcite/tools/Frameworks.java
index e5d83fb..49cd1ac 100644
--- a/core/src/main/java/org/apache/calcite/tools/Frameworks.java
+++ b/core/src/main/java/org/apache/calcite/tools/Frameworks.java
@@ -24,6 +24,7 @@ import org.apache.calcite.plan.Context;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCostFactory;
 import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelTraitDef;
 import org.apache.calcite.prepare.CalcitePrepareImpl;
 import org.apache.calcite.prepare.PlannerImpl;
@@ -202,6 +203,7 @@ public class Frameworks {
     private RelDataTypeSystem typeSystem;
     private boolean evolveLattice;
     private SqlStatisticProvider statisticProvider;
+    private RelOptTable.ViewExpander viewExpander;
 
     /** Creates a ConfigBuilder, initializing to defaults. */
     private ConfigBuilder() {
@@ -236,7 +238,7 @@ public class Frameworks {
       return new StdFrameworkConfig(context, convertletTable, operatorTable,
           programs, traitDefs, parserConfig, sqlToRelConverterConfig,
           defaultSchema, costFactory, typeSystem, executor, evolveLattice,
-          statisticProvider);
+          statisticProvider, viewExpander);
     }
 
     public ConfigBuilder context(Context c) {
@@ -329,6 +331,11 @@ public class Frameworks {
       this.statisticProvider = Objects.requireNonNull(statisticProvider);
       return this;
     }
+
+    public ConfigBuilder viewExpander(RelOptTable.ViewExpander viewExpander) {
+      this.viewExpander = viewExpander;
+      return this;
+    }
   }
 
   /**
@@ -349,6 +356,7 @@ public class Frameworks {
     private final RexExecutor executor;
     private final boolean evolveLattice;
     private final SqlStatisticProvider statisticProvider;
+    private final RelOptTable.ViewExpander viewExpander;
 
     StdFrameworkConfig(Context context,
         SqlRexConvertletTable convertletTable,
@@ -362,7 +370,8 @@ public class Frameworks {
         RelDataTypeSystem typeSystem,
         RexExecutor executor,
         boolean evolveLattice,
-        SqlStatisticProvider statisticProvider) {
+        SqlStatisticProvider statisticProvider,
+        RelOptTable.ViewExpander viewExpander) {
       this.context = context;
       this.convertletTable = convertletTable;
       this.operatorTable = operatorTable;
@@ -376,6 +385,7 @@ public class Frameworks {
       this.executor = executor;
       this.evolveLattice = evolveLattice;
       this.statisticProvider = statisticProvider;
+      this.viewExpander = viewExpander;
     }
 
     public SqlParser.Config getParserConfig() {
@@ -429,6 +439,10 @@ public class Frameworks {
     public SqlStatisticProvider getStatisticProvider() {
       return statisticProvider;
     }
+
+    public RelOptTable.ViewExpander getViewExpander() {
+      return viewExpander;
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java 
b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index d13df3c..d2b5d5d 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -933,6 +933,7 @@ public class RelBuilder {
     }
     final RelNode scan = scanFactory.createScan(cluster, relOptTable);
     push(scan);
+    rename(relOptTable.getRowType().getFieldNames());
     return this;
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java 
b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index d6a0efd..4519d04 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -768,6 +768,11 @@ public class CalciteAssert {
                   + "    (40, 'Empty')) as t(deptno, dname)",
               ImmutableList.of(), ImmutableList.of("POST", "DEPT"),
               null));
+      post.add("DEPT30",
+          ViewTable.viewMacro(post,
+              "select * from dept where deptno = 30",
+              ImmutableList.of("POST"), ImmutableList.of("POST", "DEPT30"),
+              null));
       post.add("EMPS",
           ViewTable.viewMacro(post,
               "select * from (values\n"

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java 
b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 880a1a9..3624204 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -17,12 +17,15 @@
 package org.apache.calcite.test;
 
 import org.apache.calcite.jdbc.CalciteConnection;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelTraitDef;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.core.Correlate;
 import org.apache.calcite.rel.core.Exchange;
 import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.core.TableFunctionScan;
 import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rel.core.Window;
@@ -56,13 +59,14 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 
-import org.junit.Ignore;
+import org.hamcrest.Matcher;
 import org.junit.Test;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -122,6 +126,30 @@ public class RelBuilderTest {
         .programs(Programs.heuristicJoinOrder(Programs.RULE_SET, true, 2));
   }
 
+  /** Creates a config builder that will contain a view, "MYVIEW", and also
+   * the SCOTT JDBC schema, whose tables implement
+   * {@link org.apache.calcite.schema.TranslatableTable}. */
+  static Frameworks.ConfigBuilder expandingConfig(Connection connection)
+      throws SQLException {
+    final CalciteConnection calciteConnection =
+        connection.unwrap(CalciteConnection.class);
+    final SchemaPlus root = calciteConnection.getRootSchema();
+    CalciteAssert.SchemaSpec spec = CalciteAssert.SchemaSpec.SCOTT;
+    CalciteAssert.addSchema(root, spec);
+    final String viewSql =
+        String.format(Locale.ROOT, "select * from \"%s\".\"%s\" where 1=1",
+            spec.schemaName, "EMP");
+
+    // create view
+    ViewTableMacro macro = ViewTable.viewMacro(root, viewSql,
+        Collections.singletonList("test"), Arrays.asList("test", "view"), 
false);
+
+    // register view (in root schema)
+    root.add("MYVIEW", macro);
+
+    return Frameworks.newConfigBuilder().defaultSchema(root);
+  }
+
   @Test public void testScan() {
     // Equivalent SQL:
     //   SELECT *
@@ -2189,38 +2217,67 @@ public class RelBuilderTest {
    *
    * <p>This test currently fails (thus ignored).
    */
-  @Ignore("https://issues.apache.org/jira/browse/CALCITE-2441";)
-  @Test public void testExpandViewInRelBuilder() throws Exception {
-    final Connection connection = DriverManager.getConnection("jdbc:calcite:");
-    final SchemaPlus root = 
connection.unwrap(CalciteConnection.class).getRootSchema();
-    CalciteAssert.SchemaSpec spec = CalciteAssert.SchemaSpec.SCOTT;
-    CalciteAssert.addSchema(root, spec);
-    final String viewSql =
-        String.format(Locale.ROOT, "select * from \"%s\".\"%s\" where 1=1",
-            spec.schemaName, "EMP");
-
-    // create view
-    ViewTableMacro macro = ViewTable.viewMacro(root, viewSql,
-        Collections.singletonList("test"), Arrays.asList("test", "view"), 
false);
-
-    // register view (in root schema)
-    root.add("MYVIEW", macro);
-
-    FrameworkConfig config = 
Frameworks.newConfigBuilder().defaultSchema(root).build();
-    RelNode node = RelBuilder.create(config).scan("MYVIEW").build();
-
-    int count = 0;
-    try (PreparedStatement statement =
-             connection.unwrap(RelRunner.class).prepare(node);
-        ResultSet resultSet = statement.executeQuery()) {
-      while (resultSet.next()) {
-        count++;
+  @Test public void testExpandViewInRelBuilder() throws SQLException {
+    try (Connection connection = DriverManager.getConnection("jdbc:calcite:")) 
{
+      final Frameworks.ConfigBuilder configBuilder =
+          expandingConfig(connection);
+      final RelOptTable.ViewExpander viewExpander =
+          (RelOptTable.ViewExpander) 
Frameworks.getPlanner(configBuilder.build());
+      final RelFactories.TableScanFactory tableScanFactory =
+          RelFactories.expandingScanFactory(viewExpander,
+              RelFactories.DEFAULT_TABLE_SCAN_FACTORY);
+      configBuilder.context(Contexts.of(tableScanFactory));
+      final RelBuilder builder = RelBuilder.create(configBuilder.build());
+      RelNode node = builder.scan("MYVIEW").build();
+
+      int count = 0;
+      try (PreparedStatement statement =
+               connection.unwrap(RelRunner.class).prepare(node);
+           ResultSet resultSet = statement.executeQuery()) {
+        while (resultSet.next()) {
+          count++;
+        }
       }
+
+      assertTrue(count > 1);
     }
+  }
 
-    assertTrue(count > 1);
+  @Test public void testExpandTable() throws SQLException {
+    final RelOptTable.ViewExpander viewExpander =
+        (rowType, queryString, schemaPath, viewPath) -> null;
+    final RelFactories.TableScanFactory tableScanFactory =
+        RelFactories.expandingScanFactory(viewExpander,
+            RelFactories.DEFAULT_TABLE_SCAN_FACTORY);
+    try (Connection connection = DriverManager.getConnection("jdbc:calcite:")) 
{
+      // First, use a non-expanding RelBuilder. Plan contains LogicalTableScan.
+      final Frameworks.ConfigBuilder configBuilder =
+          expandingConfig(connection);
+      final RelBuilder builder = RelBuilder.create(configBuilder.build());
+      final String expected = "LogicalFilter(condition=[>($2, 10)])\n"
+          + "  LogicalTableScan(table=[[JDBC_SCOTT, EMP]])\n";
+      checkExpandTable(builder, hasTree(expected));
+
+      // Next, use an expanding RelBuilder. Plan contains JdbcTableScan,
+      // because RelBuilder.scan has called RelOptTable.toRel.
+      final FrameworkConfig config = configBuilder
+          .context(Contexts.of(tableScanFactory)).build();
+      final RelBuilder builder2 = RelBuilder.create(config);
+      final String expected2 = "LogicalFilter(condition=[>($2, 10)])\n"
+          + "  JdbcTableScan(table=[[JDBC_SCOTT, EMP]])\n";
+      checkExpandTable(builder2, hasTree(expected2));
+    }
   }
 
+  private void checkExpandTable(RelBuilder builder, Matcher<RelNode> matcher) {
+    final RelNode root =
+        builder.scan("JDBC_SCOTT", "EMP")
+            .filter(
+                builder.call(SqlStdOperatorTable.GREATER_THAN, 
builder.field(2),
+                    builder.literal(10)))
+            .build();
+    assertThat(root, matcher);
+  }
 }
 
 // End RelBuilderTest.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/9c26a9e7/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java 
b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
index 8931458..e0ed1c1 100644
--- a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
@@ -77,6 +77,7 @@ import org.apache.calcite.util.Util;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 
+import org.hamcrest.Matcher;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -88,6 +89,7 @@ import static org.apache.calcite.plan.RelOptRule.operand;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
@@ -1132,6 +1134,44 @@ public class PlannerTest {
     Planner planner = getPlanner(null, Programs.of(ruleSet));
     planner.close();
   }
+
+  @Test public void testView() throws Exception {
+    final String sql = "select * FROM dept";
+    final String expected = "LogicalProject(DEPTNO=[$0], DNAME=[$1])\n"
+        + "  LogicalValues(type=[RecordType(INTEGER DEPTNO, CHAR(11) DNAME)], "
+        + "tuples=[[{ 10, 'Sales      ' },"
+        + " { 20, 'Marketing  ' },"
+        + " { 30, 'Engineering' },"
+        + " { 40, 'Empty      ' }]])\n";
+    checkView(sql, is(expected));
+  }
+
+  @Test public void testViewOnView() throws Exception {
+    final String sql = "select * FROM dept30";
+    final String expected = "LogicalProject(DEPTNO=[$0], DNAME=[$1])\n"
+        + "  LogicalFilter(condition=[=($0, 30)])\n"
+        + "    LogicalProject(DEPTNO=[$0], DNAME=[$1])\n"
+        + "      LogicalValues(type=[RecordType(INTEGER DEPTNO, CHAR(11) 
DNAME)], "
+        + "tuples=[[{ 10, 'Sales      ' },"
+        + " { 20, 'Marketing  ' },"
+        + " { 30, 'Engineering' },"
+        + " { 40, 'Empty      ' }]])\n";
+    checkView(sql, is(expected));
+  }
+
+  private void checkView(String sql, Matcher<String> matcher)
+      throws SqlParseException, ValidationException, RelConversionException {
+    final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    final FrameworkConfig config = Frameworks.newConfigBuilder()
+        .defaultSchema(
+            CalciteAssert.addSchema(rootSchema, CalciteAssert.SchemaSpec.POST))
+        .build();
+    final Planner planner = Frameworks.getPlanner(config);
+    SqlNode parse = planner.parse(sql);
+    final SqlNode validate = planner.validate(parse);
+    final RelRoot root = planner.rel(validate);
+    assertThat(toString(root.rel), matcher);
+  }
 }
 
 // End PlannerTest.java

Reply via email to