[CALCITE-1666] Support for modifiable views with extended columns (Kevin Liew)

[CALCITE-504] Support EXTEND clause (to declare extra columns) in CREATE VIEW

Close apache/calcite#386


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

Branch: refs/heads/master
Commit: e0a1f7d3069f0eefeb8bdd411a250cd1fb0532f3
Parents: 598cf64
Author: kliewkliew <[email protected]>
Authored: Wed Mar 1 23:43:18 2017 -0800
Committer: Julian Hyde <[email protected]>
Committed: Tue Mar 14 21:42:13 2017 -0700

----------------------------------------------------------------------
 .../org/apache/calcite/jdbc/CalcitePrepare.java |   6 +-
 .../calcite/prepare/CalciteCatalogReader.java   |   7 +
 .../calcite/prepare/CalcitePrepareImpl.java     |  25 +-
 .../org/apache/calcite/prepare/Prepare.java     |  26 ++
 .../apache/calcite/prepare/RelOptTableImpl.java |  17 +-
 .../calcite/rel/type/RelDataTypeField.java      |  23 ++
 .../apache/calcite/runtime/CalciteResource.java |   6 +
 .../apache/calcite/schema/ExtensibleTable.java  |   5 +
 .../java/org/apache/calcite/schema/Schemas.java |   5 +-
 .../schema/impl/ModifiableViewTable.java        | 149 ++++++++++
 .../apache/calcite/schema/impl/ViewTable.java   |  95 -------
 .../calcite/schema/impl/ViewTableMacro.java     | 108 ++++++++
 .../DelegatingSqlValidatorCatalogReader.java    |   4 +
 .../apache/calcite/sql/validate/EmptyScope.java |   9 +-
 .../sql/validate/IdentifierNamespace.java       |  19 +-
 .../sql/validate/SqlValidatorCatalogReader.java |   3 +-
 .../calcite/sql/validate/SqlValidatorImpl.java  | 192 ++++++++++++-
 .../calcite/sql/validate/SqlValidatorUtil.java  |  98 +++++++
 .../calcite/sql/validate/TableNamespace.java    |  14 +
 .../NullInitializerExpressionFactory.java       |   2 +-
 .../calcite/sql2rel/SqlToRelConverter.java      |  68 +++--
 .../calcite/runtime/CalciteResource.properties  |   2 +
 .../apache/calcite/sql/test/SqlAdvisorTest.java |   2 +
 .../java/org/apache/calcite/test/JdbcTest.java  |  16 +-
 .../apache/calcite/test/MockCatalogReader.java  | 272 +++++++++++++++++--
 .../calcite/test/SqlToRelConverterTest.java     |  69 +++++
 .../apache/calcite/test/SqlValidatorTest.java   |  92 +++++++
 .../calcite/test/SqlToRelConverterTest.xml      | 154 +++++++++++
 28 files changed, 1291 insertions(+), 197 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java 
b/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java
index 31b6b70..6d2a8ac 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java
@@ -46,6 +46,7 @@ import org.apache.calcite.util.ImmutableIntList;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
 import java.lang.reflect.InvocationTargetException;
@@ -270,17 +271,20 @@ public interface CalcitePrepare {
     public final ImmutableList<String> tablePath;
     public final RexNode constraint;
     public final ImmutableIntList columnMapping;
+    public final boolean modifiable;
 
     public AnalyzeViewResult(CalcitePrepareImpl prepare,
         SqlValidator validator, String sql, SqlNode sqlNode,
         RelDataType rowType, RelRoot root, Table table,
         ImmutableList<String> tablePath, RexNode constraint,
-        ImmutableIntList columnMapping) {
+        ImmutableIntList columnMapping, boolean modifiable) {
       super(prepare, validator, sql, sqlNode, rowType, root);
       this.table = table;
       this.tablePath = tablePath;
       this.constraint = constraint;
       this.columnMapping = columnMapping;
+      this.modifiable = modifiable;
+      Preconditions.checkArgument(modifiable == (table != null));
     }
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java 
b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
index b03f44b..ca68904 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
@@ -404,6 +404,13 @@ public class CalciteCatalogReader implements 
Prepare.CatalogReader {
   public SqlNameMatcher nameMatcher() {
     return nameMatcher;
   }
+
+  @Override public <C> C unwrap(Class<C> aClass) {
+    if (aClass.isInstance(this)) {
+      return aClass.cast(this);
+    }
+    return null;
+  }
 }
 
 // End CalciteCatalogReader.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java 
b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
index 7f43801..9858b48 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -358,7 +358,7 @@ public class CalcitePrepareImpl implements CalcitePrepare {
       }
       return new AnalyzeViewResult(this, validator, sql, sqlNode,
           validator.getValidatedNodeType(sqlNode), root, null, null, null,
-          null);
+          null, false);
     }
     final RelOptTable targetRelTable = scan.getTable();
     final RelDataType targetRowType = targetRelTable.getRowType();
@@ -384,7 +384,7 @@ public class CalcitePrepareImpl implements CalcitePrepare {
             }
             return new AnalyzeViewResult(this, validator, sql, sqlNode,
                 validator.getValidatedNodeType(sqlNode), root, null, null, 
null,
-                null);
+                null, false);
           }
           projectMap.put(index, rexBuilder.makeInputRef(viewRel, node.i));
           columnMapping.add(index);
@@ -400,7 +400,20 @@ public class CalcitePrepareImpl implements CalcitePrepare {
       constraint = rexBuilder.makeLiteral(true);
     }
     final List<RexNode> filters = new ArrayList<>();
+    // If we put a constraint in projectMap above, then filters will not be 
empty despite
+    // being a modifiable view.
+    final List<RexNode> filters2 = new ArrayList<>();
+    boolean retry = false;
     RelOptUtil.inferViewPredicates(projectMap, filters, constraint);
+    if (fail && !filters.isEmpty()) {
+      final Map<Integer, RexNode> projectMap2 = new HashMap<>();
+      RelOptUtil.inferViewPredicates(projectMap2, filters2, constraint);
+      if (!filters2.isEmpty()) {
+        throw validator.newValidationError(sqlNode,
+            RESOURCE.modifiableViewMustHaveOnlyEqualityPredicates());
+      }
+      retry = true;
+    }
 
     // Check that all columns that are not projected have a constant value
     for (RelDataTypeField field : targetRowType.getFieldList()) {
@@ -423,13 +436,15 @@ public class CalcitePrepareImpl implements CalcitePrepare 
{
       }
       return new AnalyzeViewResult(this, validator, sql, sqlNode,
           validator.getValidatedNodeType(sqlNode), root, null, null, null,
-          null);
+          null, false);
     }
 
+    final boolean modifiable = filters.isEmpty() || retry && 
filters2.isEmpty();
     return new AnalyzeViewResult(this, validator, sql, sqlNode,
-        validator.getValidatedNodeType(sqlNode), root, table,
+        validator.getValidatedNodeType(sqlNode), root, modifiable ? table : 
null,
         ImmutableList.copyOf(tablePath),
-        constraint, ImmutableIntList.copyOf(columnMapping));
+        constraint, ImmutableIntList.copyOf(columnMapping),
+        modifiable);
   }
 
   @Override public void executeDdl(Context context, SqlNode node) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 26a6bff..81102b3 100644
--- a/core/src/main/java/org/apache/calcite/prepare/Prepare.java
+++ b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
@@ -35,12 +35,15 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelRoot;
 import org.apache.calcite.rel.logical.LogicalTableModify;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexExecutorImpl;
 import org.apache.calcite.runtime.Bindable;
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.runtime.Typed;
+import org.apache.calcite.schema.ExtensibleTable;
 import org.apache.calcite.schema.Table;
 import org.apache.calcite.schema.Wrapper;
+import org.apache.calcite.schema.impl.ModifiableViewTable;
 import org.apache.calcite.schema.impl.StarTable;
 import org.apache.calcite.sql.SqlExplain;
 import org.apache.calcite.sql.SqlExplainFormat;
@@ -418,6 +421,29 @@ public abstract class Prepare {
       }
       return !rowType.getFieldList().get(ordinal).getType().isNullable();
     }
+
+    public RelOptTable extend(List<RelDataTypeField> extendedFields) {
+      final Table table = unwrap(Table.class);
+      if (table instanceof ExtensibleTable) {
+        return extend((ExtensibleTable) table, extendedFields);
+      } else if (table instanceof ModifiableViewTable) {
+        final Table underlying = ((Wrapper) table).unwrap(Table.class);
+        if (underlying instanceof ExtensibleTable) {
+          return extend((ExtensibleTable) underlying, extendedFields);
+        }
+      }
+      throw new RuntimeException("Cannot extend " + table);
+    }
+
+    private RelOptTable extend(ExtensibleTable table,
+        List<RelDataTypeField> extendedFields) {
+      final Table extendedTable = table.extend(extendedFields);
+      return extend(extendedTable);
+    }
+
+    /** Implementation-specific code to instantiate a new {@link RelOptTable}
+     * based on a {@link Table} that has been extended. */
+    protected abstract RelOptTable extend(Table extendedTable);
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java 
b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
index cddee2b..ccc25d8 100644
--- a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
@@ -30,10 +30,8 @@ import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.logical.LogicalTableScan;
 import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rel.type.RelRecordType;
 import org.apache.calcite.runtime.Hook;
-import org.apache.calcite.schema.ExtensibleTable;
 import org.apache.calcite.schema.FilterableTable;
 import org.apache.calcite.schema.ModifiableTable;
 import org.apache.calcite.schema.Path;
@@ -200,16 +198,11 @@ public class RelOptTableImpl extends 
Prepare.AbstractPreparingTable {
     return expressionFunction.apply(clazz);
   }
 
-  public RelOptTable extend(List<RelDataTypeField> extendedFields) {
-    if (table instanceof ExtensibleTable) {
-      final Table extendedTable =
-          ((ExtensibleTable) table).extend(extendedFields);
-      final RelDataType extendedRowType =
-          extendedTable.getRowType(schema.getTypeFactory());
-      return new RelOptTableImpl(schema, extendedRowType, names, extendedTable,
-          expressionFunction, rowCount);
-    }
-    throw new RuntimeException("Cannot extend " + table); // TODO: user error
+  @Override protected RelOptTable extend(Table extendedTable) {
+    final RelDataType extendedRowType =
+        extendedTable.getRowType(getRelOptSchema().getTypeFactory());
+    return new RelOptTableImpl(getRelOptSchema(), extendedRowType, 
getQualifiedName(),
+        extendedTable, expressionFunction, getRowCount());
   }
 
   @Override public boolean equals(Object obj) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java
index 293de27..36acc1b 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java
@@ -16,6 +16,8 @@
  */
 package org.apache.calcite.rel.type;
 
+import com.google.common.base.Function;
+
 import java.util.Map;
 
 /**
@@ -28,6 +30,27 @@ import java.util.Map;
  * and {@link #getValue()} must be equivalent to {@link #getType()}.
  */
 public interface RelDataTypeField extends Map.Entry<String, RelDataType> {
+
+  /**
+   * Function to transform a set of {@link RelDataTypeField} to
+   * a set of {@link Integer} of the field keys.
+   */
+  class ToFieldIndex implements Function<RelDataTypeField, Integer> {
+    @Override public Integer apply(RelDataTypeField o) {
+      return o.getIndex();
+    }
+  }
+
+  /**
+   * Function to transform a set of {@link RelDataTypeField} to
+   * a set of {@link String} of the field names.
+   */
+  class ToFieldName implements Function<RelDataTypeField, String> {
+    @Override public String apply(RelDataTypeField o) {
+      return o.getName();
+    }
+  }
+
   //~ Methods ----------------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java 
b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index 7bc99eb..40774d0 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -636,12 +636,18 @@ public interface CalciteResource {
   @BaseMessage("Modifiable view must be based on a single table")
   ExInst<SqlValidatorException> modifiableViewMustBeBasedOnSingleTable();
 
+  @BaseMessage("Modifiable view must be predicated only on equality 
expressions")
+  ExInst<SqlValidatorException> modifiableViewMustHaveOnlyEqualityPredicates();
+
   @BaseMessage("View is not modifiable. More than one expression maps to 
column ''{0}'' of base table ''{1}''")
   ExInst<SqlValidatorException> moreThanOneMappedColumn(String columnName, 
String tableName);
 
   @BaseMessage("View is not modifiable. No value is supplied for NOT NULL 
column ''{0}'' of base table ''{1}''")
   ExInst<SqlValidatorException> noValueSuppliedForViewColumn(String 
columnName, String tableName);
 
+  @BaseMessage("Modifiable view constraint is not satisfied for column ''{0}'' 
of base table ''{1}''")
+  ExInst<SqlValidatorException> viewConstraintNotSatisfied(String columnName, 
String tableName);
+
   @BaseMessage("Not a record type. The ''*'' operator requires a record")
   ExInst<SqlValidatorException> starRequiresRecordType();
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java 
b/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java
index 7469cd7..1127adb 100644
--- a/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java
+++ b/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java
@@ -40,6 +40,11 @@ public interface ExtensibleTable extends Table {
   /** Returns a table that has the row type of this table plus the given
    * fields. */
   Table extend(List<RelDataTypeField> fields);
+
+  /** Returns the starting offset of the first extended column, which may 
differ
+   * from the field count when the table stores metadata columns that are not
+   * counted in the row-type field count. */
+  int getExtendedColumnOffset();
 }
 
 // End ExtensibleTable.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/schema/Schemas.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/Schemas.java 
b/core/src/main/java/org/apache/calcite/schema/Schemas.java
index 6c8ab1d..86275c4 100644
--- a/core/src/main/java/org/apache/calcite/schema/Schemas.java
+++ b/core/src/main/java/org/apache/calcite/schema/Schemas.java
@@ -504,11 +504,14 @@ public final class Schemas {
   public static Path path(CalciteSchema rootSchema, Iterable<String> names) {
     final ImmutableList.Builder<Pair<String, Schema>> builder =
         ImmutableList.builder();
-    Schema schema = rootSchema.schema;
+    Schema schema = rootSchema.plus();
     final Iterator<String> iterator = names.iterator();
     if (!iterator.hasNext()) {
       return PathImpl.EMPTY;
     }
+    if (!rootSchema.name.isEmpty()) {
+      assert rootSchema.name.equals(iterator.next());
+    }
     for (;;) {
       final String name = iterator.next();
       builder.add(Pair.of(name, schema));

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/schema/impl/ModifiableViewTable.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/schema/impl/ModifiableViewTable.java 
b/core/src/main/java/org/apache/calcite/schema/impl/ModifiableViewTable.java
new file mode 100644
index 0000000..c006e68
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/schema/impl/ModifiableViewTable.java
@@ -0,0 +1,149 @@
+/*
+ * 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.schema.impl;
+
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelProtoDataType;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.schema.ModifiableView;
+import org.apache.calcite.schema.Path;
+import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.Wrapper;
+import org.apache.calcite.sql.SqlFunction;
+import org.apache.calcite.sql2rel.InitializerExpressionFactory;
+import org.apache.calcite.sql2rel.NullInitializerExpressionFactory;
+import org.apache.calcite.util.ImmutableIntList;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/** Extension to {@link ViewTable} that is modifiable. */
+public class ModifiableViewTable extends ViewTable
+    implements ModifiableView, Wrapper {
+  private final Table table;
+  private final Path tablePath;
+  private final RexNode constraint;
+  private final ImmutableIntList columnMapping;
+  private final InitializerExpressionFactory initializerExpressionFactory;
+
+  /** Creates a ModifiableViewTable. */
+  public ModifiableViewTable(Type elementType, RelProtoDataType rowType,
+      String viewSql, List<String> schemaPath, List<String> viewPath,
+      Table table, Path tablePath, RexNode constraint,
+      ImmutableIntList columnMapping, RelDataTypeFactory typeFactory) {
+    super(elementType, rowType, viewSql, schemaPath, viewPath);
+    this.table = table;
+    this.tablePath = tablePath;
+    this.constraint = constraint;
+    this.columnMapping = columnMapping;
+    this.initializerExpressionFactory =
+        new ModifiableViewTableInitializerExpressionFactory(typeFactory);
+  }
+
+  public RexNode getConstraint(RexBuilder rexBuilder,
+      RelDataType tableRowType) {
+    return rexBuilder.copy(constraint);
+  }
+
+  public ImmutableIntList getColumnMapping() {
+    return columnMapping;
+  }
+
+  public Table getTable() {
+    return table;
+  }
+
+  public Path getTablePath() {
+    return tablePath;
+  }
+
+  @Override public <C> C unwrap(Class<C> aClass) {
+    if (aClass.isInstance(initializerExpressionFactory)) {
+      return aClass.cast(initializerExpressionFactory);
+    } else if (aClass.isInstance(table)) {
+      return aClass.cast(table);
+    }
+    return null;
+  }
+
+  /**
+   * Initializes columns based on the view constraint.
+   */
+  private class ModifiableViewTableInitializerExpressionFactory
+      extends NullInitializerExpressionFactory {
+    private final ImmutableMap<Integer, RexNode> projectMap;
+
+    private ModifiableViewTableInitializerExpressionFactory(RelDataTypeFactory 
typeFactory) {
+      super(typeFactory);
+      final Map<Integer, RexNode> projectMap = Maps.newHashMap();
+      final List<RexNode> filters = new ArrayList<>();
+      RelOptUtil.inferViewPredicates(projectMap, filters, constraint);
+      assert filters.isEmpty();
+      this.projectMap = ImmutableMap.copyOf(projectMap);
+    }
+
+    @Override public boolean isGeneratedAlways(RelOptTable table, int iColumn) 
{
+      assert table.unwrap(ModifiableViewTable.class) != null;
+      return false;
+    }
+
+    @Override public RexNode newColumnDefaultValue(RelOptTable table, int 
iColumn) {
+      final ModifiableViewTable viewTable = 
table.unwrap(ModifiableViewTable.class);
+      final RelDataType viewType =
+          viewTable.getRowType(rexBuilder.getTypeFactory());
+      final RelDataType iType = viewType.getFieldList().get(iColumn).getType();
+
+      // Use the view constraint to generate the default value if the column 
is constrained.
+      final int mappedOrdinal = viewTable.columnMapping.get(iColumn);
+      final RexNode viewConstraint = projectMap.get(mappedOrdinal);
+      if (viewConstraint != null) {
+        return rexBuilder.ensureType(iType, viewConstraint, true);
+      }
+
+      // Otherwise use the default value of the underlying table.
+      final Table schemaTable = viewTable.unwrap(Table.class);
+      if (schemaTable instanceof Wrapper) {
+        final InitializerExpressionFactory initializerExpressionFactory =
+            ((Wrapper) schemaTable).unwrap(InitializerExpressionFactory.class);
+        if (initializerExpressionFactory != null) {
+          final RexNode tableConstraint =
+              initializerExpressionFactory.newColumnDefaultValue(table, 
iColumn);
+          return rexBuilder.ensureType(iType, tableConstraint, true);
+        }
+      }
+
+      // Otherwise Sql type of NULL.
+      return super.newColumnDefaultValue(table, iColumn);
+    }
+
+    @Override public RexNode newAttributeInitializer(RelDataType type,
+        SqlFunction constructor, int iAttribute, List<RexNode> 
constructorArgs) {
+      throw new UnsupportedOperationException("Not implemented - unknown 
requirements");
+    }
+  }
+}
+
+// End ModifiableViewTable.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 507db2f..65a5b9f 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
@@ -17,8 +17,6 @@
 package org.apache.calcite.schema.impl;
 
 import org.apache.calcite.adapter.java.AbstractQueryableTable;
-import org.apache.calcite.adapter.java.JavaTypeFactory;
-import org.apache.calcite.jdbc.CalcitePrepare;
 import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.linq4j.QueryProvider;
 import org.apache.calcite.linq4j.Queryable;
@@ -28,25 +26,14 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelRoot;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.rel.type.RelDataTypeImpl;
 import org.apache.calcite.rel.type.RelProtoDataType;
-import org.apache.calcite.rex.RexBuilder;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.schema.FunctionParameter;
-import org.apache.calcite.schema.ModifiableView;
-import org.apache.calcite.schema.Path;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
-import org.apache.calcite.schema.Schemas;
-import org.apache.calcite.schema.Table;
-import org.apache.calcite.schema.TableMacro;
 import org.apache.calcite.schema.TranslatableTable;
-import org.apache.calcite.util.ImmutableIntList;
 
 import com.google.common.collect.ImmutableList;
 
 import java.lang.reflect.Type;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -138,88 +125,6 @@ public class ViewTable
           + queryString, e);
     }
   }
-
-  /** Table function that implements a view. It returns the operator
-   * tree of the view's SQL query. */
-  static class ViewTableMacro implements TableMacro {
-    protected final String viewSql;
-    protected final CalciteSchema schema;
-    private final Boolean modifiable;
-    /** Typically null. If specified, overrides the path of the schema as the
-     * context for validating {@code viewSql}. */
-    protected final List<String> schemaPath;
-    protected final List<String> viewPath;
-
-    ViewTableMacro(CalciteSchema schema, String viewSql, List<String> 
schemaPath,
-        List<String> viewPath, Boolean modifiable) {
-      this.viewSql = viewSql;
-      this.schema = schema;
-      this.viewPath = viewPath == null ? null : ImmutableList.copyOf(viewPath);
-      this.modifiable = modifiable;
-      this.schemaPath =
-          schemaPath == null ? null : ImmutableList.copyOf(schemaPath);
-    }
-
-    public List<FunctionParameter> getParameters() {
-      return Collections.emptyList();
-    }
-
-    public TranslatableTable apply(List<Object> arguments) {
-      CalcitePrepare.AnalyzeViewResult parsed =
-          Schemas.analyzeView(MaterializedViewTable.MATERIALIZATION_CONNECTION,
-              schema, schemaPath, viewSql, modifiable != null && modifiable);
-      final List<String> schemaPath1 =
-          schemaPath != null ? schemaPath : schema.path(null);
-      final JavaTypeFactory typeFactory = (JavaTypeFactory) parsed.typeFactory;
-      final Type elementType = typeFactory.getJavaClass(parsed.rowType);
-      if ((modifiable == null || modifiable) && parsed.table != null) {
-        return new ModifiableViewTable(elementType,
-            RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath1, 
viewPath,
-            parsed.table, Schemas.path(schema.root(), parsed.tablePath),
-            parsed.constraint, parsed.columnMapping);
-      } else {
-        return new ViewTable(elementType,
-            RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath1, 
viewPath);
-      }
-    }
-  }
-
-  /** Extension to {@link ViewTable} that is modifiable. */
-  static class ModifiableViewTable extends ViewTable
-      implements ModifiableView {
-    private final Table table;
-    private final Path tablePath;
-    private final RexNode constraint;
-    private final ImmutableIntList columnMapping;
-
-    public ModifiableViewTable(Type elementType, RelProtoDataType rowType,
-        String viewSql, List<String> schemaPath, List<String> viewPath,
-        Table table, Path tablePath, RexNode constraint,
-        ImmutableIntList columnMapping) {
-      super(elementType, rowType, viewSql, schemaPath, viewPath);
-      this.table = table;
-      this.tablePath = tablePath;
-      this.constraint = constraint;
-      this.columnMapping = columnMapping;
-    }
-
-    public RexNode getConstraint(RexBuilder rexBuilder,
-        RelDataType tableRowType) {
-      return rexBuilder.copy(constraint);
-    }
-
-    public ImmutableIntList getColumnMapping() {
-      return columnMapping;
-    }
-
-    public Table getTable() {
-      return table;
-    }
-
-    public Path getTablePath() {
-      return tablePath;
-    }
-  }
 }
 
 // End ViewTable.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/schema/impl/ViewTableMacro.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/schema/impl/ViewTableMacro.java 
b/core/src/main/java/org/apache/calcite/schema/impl/ViewTableMacro.java
new file mode 100644
index 0000000..e7e044a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/schema/impl/ViewTableMacro.java
@@ -0,0 +1,108 @@
+/*
+ * 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.schema.impl;
+
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.jdbc.CalcitePrepare;
+import org.apache.calcite.jdbc.CalciteSchema;
+import org.apache.calcite.rel.type.RelDataTypeImpl;
+import org.apache.calcite.schema.FunctionParameter;
+import org.apache.calcite.schema.Schemas;
+import org.apache.calcite.schema.TableMacro;
+import org.apache.calcite.schema.TranslatableTable;
+
+import com.google.common.collect.ImmutableList;
+
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.List;
+
+/** Table function that implements a view. It returns the operator
+ * tree of the view's SQL query. */
+public class ViewTableMacro implements TableMacro {
+  protected final String viewSql;
+  protected final CalciteSchema schema;
+  private final Boolean modifiable;
+  /** Typically null. If specified, overrides the path of the schema as the
+   * context for validating {@code viewSql}. */
+  protected final List<String> schemaPath;
+  protected final List<String> viewPath;
+
+  /**
+   * Creates a ViewTableMacro.
+   *
+   * @param schema     Root schema
+   * @param viewSql    SQL defining the view
+   * @param schemaPath Schema path relative to the root schema
+   * @param viewPath   View path relative to the schema path
+   * @param modifiable Request that a view is modifiable (dependent on analysis
+   *                   of {@code viewSql})
+   */
+  public ViewTableMacro(CalciteSchema schema, String viewSql,
+      List<String> schemaPath, List<String> viewPath, Boolean modifiable) {
+    this.viewSql = viewSql;
+    this.schema = schema;
+    this.viewPath = viewPath == null ? null : ImmutableList.copyOf(viewPath);
+    this.modifiable = modifiable;
+    this.schemaPath =
+        schemaPath == null ? null : ImmutableList.copyOf(schemaPath);
+  }
+
+  public List<FunctionParameter> getParameters() {
+    return Collections.emptyList();
+  }
+
+  public TranslatableTable apply(List<Object> arguments) {
+    CalcitePrepare.AnalyzeViewResult parsed =
+        Schemas.analyzeView(MaterializedViewTable.MATERIALIZATION_CONNECTION,
+            schema, schemaPath, viewSql, modifiable != null && modifiable);
+    final List<String> schemaPath1 =
+        schemaPath != null ? schemaPath : schema.path(null);
+    if ((modifiable == null || modifiable)
+        && parsed.modifiable
+        && parsed.table != null) {
+      return modifiableViewTable(parsed, viewSql, schemaPath1, viewPath, 
schema);
+    } else {
+      return viewTable(parsed, viewSql, schemaPath1, viewPath);
+    }
+  }
+
+  /** Allows a sub-class to return an extension of {@link ModifiableViewTable}
+   * by overriding this method. */
+  protected ModifiableViewTable 
modifiableViewTable(CalcitePrepare.AnalyzeViewResult parsed,
+      String viewSql, List<String> schemaPath, List<String> viewPath,
+      CalciteSchema schema) {
+    final JavaTypeFactory typeFactory = (JavaTypeFactory) parsed.typeFactory;
+    final Type elementType = typeFactory.getJavaClass(parsed.rowType);
+    return new ModifiableViewTable(elementType,
+        RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath, viewPath,
+        parsed.table, Schemas.path(schema.root(), parsed.tablePath),
+        parsed.constraint, parsed.columnMapping, parsed.typeFactory);
+  }
+
+  /** Allows a sub-class to return an extension of {@link ViewTable} by
+   * overriding this method. */
+  protected ViewTable viewTable(CalcitePrepare.AnalyzeViewResult parsed,
+      String viewSql, List<String> schemaPath, List<String> viewPath) {
+    final JavaTypeFactory typeFactory = (JavaTypeFactory) parsed.typeFactory;
+    final Type elementType = typeFactory.getJavaClass(parsed.rowType);
+    return new ViewTable(elementType,
+        RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath, viewPath);
+  }
+}
+
+// End ViewTableMacro.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java
 
b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java
index 244afb5..6a1ad8a 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java
@@ -55,6 +55,10 @@ public abstract class DelegatingSqlValidatorCatalogReader
   public List<List<String>> getSchemaPaths() {
     return catalogReader.getSchemaPaths();
   }
+
+  @Override public <C> C unwrap(Class<C> aClass) {
+    return catalogReader.unwrap(aClass);
+  }
 }
 
 // End DelegatingSqlValidatorCatalogReader.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java 
b/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
index 0dc9131..bafa309 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.sql.validate;
 
 import org.apache.calcite.jdbc.CalciteSchema;
+import org.apache.calcite.plan.RelOptSchema;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.prepare.RelOptTableImpl;
 import org.apache.calcite.rel.type.RelDataType;
@@ -154,9 +155,11 @@ class EmptyScope implements SqlValidatorScope {
           table2 = ((Wrapper) table).unwrap(Prepare.PreparingTable.class);
         }
         if (table2 == null) {
-          table2 = RelOptTableImpl.create(null,
-              table.getRowType(validator.typeFactory), schema.add(name2, 
table),
-              null);
+          final RelOptSchema relOptSchema =
+              validator.catalogReader.unwrap(RelOptSchema.class);
+          final RelDataType rowType = table.getRowType(validator.typeFactory);
+          table2 = RelOptTableImpl.create(relOptSchema, rowType,
+              schema.add(name2, table), null);
         }
         namespace = new TableNamespace(validator, table2);
         resolved.found(namespace, false, this, path, remainingNames);

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java 
b/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
index f5ffde3..63247e7 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
@@ -18,9 +18,7 @@ package org.apache.calcite.sql.validate;
 
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
 import org.apache.calcite.sql.SqlCall;
-import org.apache.calcite.sql.SqlDataTypeSpec;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
@@ -29,11 +27,9 @@ import org.apache.calcite.util.Pair;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import javax.annotation.Nullable;
 
@@ -199,20 +195,13 @@ public class IdentifierNamespace extends 
AbstractNamespace {
     RelDataType rowType = resolvedNamespace.getRowType();
 
     if (extendList != null) {
-      final List<RelDataTypeField> fields = Lists.newArrayList();
-      final Iterator<SqlNode> extendIterator = extendList.iterator();
-      while (extendIterator.hasNext()) {
-        SqlIdentifier id = (SqlIdentifier) extendIterator.next();
-        SqlDataTypeSpec type = (SqlDataTypeSpec) extendIterator.next();
-        fields.add(
-            new RelDataTypeFieldImpl(id.getSimple(), fields.size(),
-                type.deriveType(validator)));
-      }
-
       if (!(resolvedNamespace instanceof TableNamespace)) {
         throw new RuntimeException("cannot convert");
       }
-      resolvedNamespace = ((TableNamespace) resolvedNamespace).extend(fields);
+      final List<RelDataTypeField> extendedFields =
+          SqlValidatorUtil.getExtendedColumns(validator, getTable(), 
extendList);
+      resolvedNamespace =
+          ((TableNamespace) resolvedNamespace).extend(extendedFields);
       rowType = resolvedNamespace.getRowType();
     }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
index a71f8f4..be204e2 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
@@ -19,6 +19,7 @@ package org.apache.calcite.sql.validate;
 import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.schema.Wrapper;
 import org.apache.calcite.sql.SqlIdentifier;
 
 import java.util.List;
@@ -32,7 +33,7 @@ import java.util.List;
  * implement the repository. It is also possible to construct mock
  * implementations of this interface for testing purposes.
  */
-public interface SqlValidatorCatalogReader {
+public interface SqlValidatorCatalogReader extends Wrapper {
   //~ Methods ----------------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index f9045a2..ad2d4c1 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -19,17 +19,25 @@ package org.apache.calcite.sql.validate;
 import org.apache.calcite.config.NullCollation;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.type.DynamicRecordType;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rel.type.RelDataTypeSystem;
 import org.apache.calcite.rel.type.RelRecordType;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexSqlStandardConvertletTable;
+import org.apache.calcite.rex.RexToSqlNodeConverter;
+import org.apache.calcite.rex.RexToSqlNodeConverterImpl;
 import org.apache.calcite.runtime.CalciteContextException;
 import org.apache.calcite.runtime.CalciteException;
 import org.apache.calcite.runtime.Feature;
 import org.apache.calcite.runtime.Resources;
 import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.impl.ModifiableViewTable;
 import org.apache.calcite.sql.JoinConditionType;
 import org.apache.calcite.sql.JoinType;
 import org.apache.calcite.sql.SqlAccessEnum;
@@ -80,6 +88,7 @@ import org.apache.calcite.sql.util.SqlShuttle;
 import org.apache.calcite.sql.util.SqlVisitor;
 import org.apache.calcite.util.BitString;
 import org.apache.calcite.util.Bug;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableNullableList;
 import org.apache.calcite.util.Litmus;
 import org.apache.calcite.util.Pair;
@@ -94,6 +103,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
 import org.slf4j.Logger;
@@ -983,21 +993,40 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
       SqlValidatorScope scope) {
     if (node instanceof SqlIdentifier && scope instanceof DelegatingScope) {
       final SqlIdentifier id = (SqlIdentifier) node;
-      final SqlValidatorScope parentScope =
-          ((DelegatingScope) scope).getParent();
-      if (id.isSimple()) {
-        final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
-        final SqlValidatorScope.ResolvedImpl resolved =
-            new SqlValidatorScope.ResolvedImpl();
-        parentScope.resolve(id.names, nameMatcher, false, resolved);
-        if (resolved.count() == 1) {
-          return resolved.only().namespace;
+      final DelegatingScope idScope = (DelegatingScope) ((DelegatingScope) 
scope).getParent();
+      return getNamespace(id, idScope);
+    } else if (node instanceof SqlCall) {
+      // Handle extended identifiers.
+      final SqlCall sqlCall = (SqlCall) node;
+      final SqlKind sqlKind = sqlCall.getOperator().getKind();
+      if (sqlKind.equals(SqlKind.EXTEND)) {
+        final SqlIdentifier id = (SqlIdentifier) 
sqlCall.getOperandList().get(0);
+        final DelegatingScope idScope = (DelegatingScope) scope;
+        return getNamespace(id, idScope);
+      } else {
+        final SqlNode nested = sqlCall.getOperandList().get(0);
+        if (sqlKind.equals(SqlKind.AS)
+            && nested.getKind().equals(SqlKind.EXTEND)) {
+          return getNamespace(nested, scope);
         }
       }
     }
     return getNamespace(node);
   }
 
+  private SqlValidatorNamespace getNamespace(SqlIdentifier id, DelegatingScope 
scope) {
+    if (id.isSimple()) {
+      final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
+      final SqlValidatorScope.ResolvedImpl resolved =
+          new SqlValidatorScope.ResolvedImpl();
+      scope.resolve(id.names, nameMatcher, false, resolved);
+      if (resolved.count() == 1) {
+        return resolved.only().namespace;
+      }
+    }
+    return getNamespace(id);
+  }
+
   public SqlValidatorNamespace getNamespace(SqlNode node) {
     switch (node.getKind()) {
     case AS:
@@ -2053,6 +2082,9 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
         tableScope = new TableScope(parentScope, node);
       }
       tableScope.addChild(newNs, alias, forceNullable);
+      if (extendList != null && extendList.size() != 0) {
+        return enclosingNode;
+      }
       return newNode;
 
     case LATERAL:
@@ -3832,9 +3864,149 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
 
     checkTypeAssignment(logicalSourceRowType, logicalTargetRowType, insert);
 
+    checkConstraint(table, source, logicalTargetRowType);
+
     validateAccess(insert.getTargetTable(), table, SqlAccessEnum.INSERT);
   }
 
+  /**
+   * Validates insert values against the constraint of a modifiable view.
+   *
+   * @param validatorTable Table that may wrap a ModifiableViewTable
+   * @param source        The values being inserted
+   * @param targetRowType The target type for the view
+   */
+  private void checkConstraint(
+      SqlValidatorTable validatorTable,
+      SqlNode source,
+      RelDataType targetRowType) {
+    final ModifiableViewTable modifiableViewTable =
+        validatorTable.unwrap(ModifiableViewTable.class);
+    if (modifiableViewTable != null && source instanceof SqlCall) {
+      final Table table = modifiableViewTable.unwrap(Table.class);
+      final RelDataType tableRowType = table.getRowType(typeFactory);
+      final List<RelDataTypeField> tableFields = tableRowType.getFieldList();
+
+      // Get the mapping from column indexes of the underlying table
+      // to the target columns and view constraints.
+      final Map<Integer, RelDataTypeField> tableIndexToTargetField =
+          SqlValidatorUtil.getIndexToFieldMap(tableFields, targetRowType);
+      final Map<Integer, RexNode> projectMap =
+          getConstraintForModifiableView(modifiableViewTable, targetRowType);
+
+      // Determine columns (indexed to the underlying table) that need
+      // to be validated against the view constraint.
+      final ImmutableBitSet targetColumns =
+          ImmutableBitSet.of(tableIndexToTargetField.keySet());
+      final ImmutableBitSet constrainedColumns =
+          ImmutableBitSet.of(projectMap.keySet());
+      final ImmutableBitSet constrainedTargetColumns =
+          targetColumns.intersect(constrainedColumns);
+
+      // Validate insert values against the view constraint.
+      final List<SqlNode> values = ((SqlCall) source).getOperandList();
+      for (final int colIndex : constrainedTargetColumns.asList()) {
+        final String colName = tableFields.get(colIndex).getName();
+        final RelDataTypeField targetField = 
tableIndexToTargetField.get(colIndex);
+        for (SqlNode row : values) {
+          final SqlCall call = (SqlCall) row;
+          final SqlNode sourceValue = call.operand(targetField.getIndex());
+          checkConstraint(validatorTable, colName, sourceValue, 
projectMap.get(colIndex));
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns a mapping of the column ordinal in the underlying table to a 
column
+   * constraint of the modifiable view.
+   *
+   * @param modifiableViewTable The modifiable view which has a constraint
+   * @param targetRowType       The target type
+   */
+  private Map<Integer, RexNode> getConstraintForModifiableView(
+      ModifiableViewTable modifiableViewTable, RelDataType targetRowType) {
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    final RexNode constraint =
+        modifiableViewTable.getConstraint(rexBuilder, targetRowType);
+    final Map<Integer, RexNode> projectMap = Maps.newHashMap();
+    final List<RexNode> filters = new ArrayList<>();
+    RelOptUtil.inferViewPredicates(projectMap, filters, constraint);
+    assert filters.isEmpty();
+    return projectMap;
+  }
+
+  /**
+   * Validates updates against the constraint of a modifiable view.
+   *
+   * @param validatorTable A {@link SqlValidatorTable} that may wrap a
+   *                       ModifiableViewTable
+   * @param update         The UPDATE parse tree node
+   * @param targetRowType  The target type
+   */
+  private void checkConstraint(
+      SqlValidatorTable validatorTable,
+      SqlUpdate update,
+      RelDataType targetRowType) {
+    final ModifiableViewTable modifiableViewTable =
+        validatorTable.unwrap(ModifiableViewTable.class);
+    if (modifiableViewTable != null) {
+      final Table table = modifiableViewTable.unwrap(Table.class);
+      final RelDataType tableRowType = table.getRowType(typeFactory);
+
+      final Map<Integer, RexNode> projectMap =
+          getConstraintForModifiableView(modifiableViewTable, targetRowType);
+      final Map<String, Integer> nameToIndex =
+          SqlValidatorUtil.mapNameToIndex(tableRowType.getFieldList());
+
+      // Validate update values against the view constraint.
+      for (final Pair<SqlNode, SqlNode> column : 
Pair.zip(update.getTargetColumnList().getList(),
+          update.getSourceExpressionList().getList())) {
+        final String columnName = ((SqlIdentifier) column.left).getSimple();
+        final Integer columnIndex = nameToIndex.get(columnName);
+        if (projectMap.containsKey(columnIndex)) {
+          final RexNode columnConstraint = projectMap.get(columnIndex);
+          checkConstraint(validatorTable, columnName, column.right, 
columnConstraint);
+        }
+      }
+    }
+  }
+
+  /**
+   * Ensures that a source value does not violate the constraint of the target 
column.
+   *
+   * @param table            The SqlValidatorTable which wraps a 
ModifiableViewTable.
+   * @param columnName       The target column name.
+   * @param sourceValue      The insert value being validated.
+   * @param targetConstraint The constraint applied to sourceValue for 
validation.
+   */
+  private void checkConstraint(
+      SqlValidatorTable table, String columnName,
+      SqlNode sourceValue, RexNode targetConstraint) {
+    if (!(sourceValue instanceof SqlLiteral)) {
+      // We cannot guarantee that the value satisfies the constraint.
+      throw newValidationError(sourceValue,
+          RESOURCE.viewConstraintNotSatisfied(
+              columnName, Util.last(table.getQualifiedName())));
+    }
+    final SqlLiteral insertValue = (SqlLiteral) sourceValue;
+    final RexLiteral columnConstraint = (RexLiteral) targetConstraint;
+
+    final RexSqlStandardConvertletTable convertletTable =
+        new RexSqlStandardConvertletTable();
+    final RexToSqlNodeConverter sqlNodeToRexConverter =
+        new RexToSqlNodeConverterImpl(convertletTable);
+    final SqlLiteral constraintValue =
+        (SqlLiteral) sqlNodeToRexConverter.convertLiteral(columnConstraint);
+
+    if (!insertValue.equals(constraintValue)) {
+      // The value does not satisfy the constraint.
+      throw newValidationError(sourceValue,
+          RESOURCE.viewConstraintNotSatisfied(
+              columnName, Util.last(table.getQualifiedName())));
+    }
+  }
+
   private void checkFieldCount(
       SqlNode node,
       SqlValidatorTable table,
@@ -4009,6 +4181,8 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     RelDataType sourceRowType = getNamespace(select).getRowType();
     checkTypeAssignment(sourceRowType, targetRowType, call);
 
+    checkConstraint(table, call, targetRowType);
+
     validateAccess(call.getTargetTable(), table, SqlAccessEnum.UPDATE);
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
index c643a83..a393d84 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
@@ -25,7 +25,9 @@ import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
 import org.apache.calcite.schema.CustomColumnResolvingTable;
+import org.apache.calcite.schema.ExtensibleTable;
 import org.apache.calcite.schema.Table;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDataTypeSpec;
@@ -47,6 +49,7 @@ import org.apache.calcite.util.Util;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
@@ -109,6 +112,101 @@ public class SqlValidatorUtil {
     return table;
   }
 
+  /**
+   * Gets a list of extended columns with field indices to the underlying 
table.
+   */
+  public static List<RelDataTypeField> getExtendedColumns(
+      SqlValidator validator, SqlValidatorTable table, SqlNodeList 
extendedColumns) {
+    final ImmutableList.Builder<RelDataTypeField> extendedFields =
+        ImmutableList.builder();
+    final ExtensibleTable extTable = table.unwrap(ExtensibleTable.class);
+    int extendedFieldOffset =
+        extTable == null
+            ? table.getRowType().getFieldCount()
+            : extTable.getExtendedColumnOffset();
+    for (final Pair<SqlIdentifier, SqlDataTypeSpec> pair : 
pairs(extendedColumns)) {
+      final SqlIdentifier identifier = pair.left;
+      final SqlDataTypeSpec type = pair.right;
+      extendedFields.add(
+          new RelDataTypeFieldImpl(identifier.getSimple(),
+              extendedFieldOffset++,
+              type.deriveType(validator)));
+    }
+    return extendedFields.build();
+  }
+
+  /** Converts a list of extended columns
+   * (of the form [name0, type0, name1, type1, ...])
+   * into a list of (name, type) pairs. */
+  private static List<Pair<SqlIdentifier, SqlDataTypeSpec>> pairs(
+      SqlNodeList extendedColumns) {
+    final List list = extendedColumns.getList();
+    //noinspection unchecked
+    return Pair.zip(Util.quotientList(list, 2, 0),
+        Util.quotientList(list, 2, 1));
+  }
+
+  /**
+   * Gets a map of indexes from the source to fields in the target for the
+   * intersecting set of source and target fields.
+   *
+   * @param sourceFields The source of column names that determine indexes
+   * @param targetFields The target fields to be indexed
+   */
+  public static ImmutableMap<Integer, RelDataTypeField> getIndexToFieldMap(
+      List<RelDataTypeField> sourceFields,
+      RelDataType targetFields) {
+    final ImmutableMap.Builder<Integer, RelDataTypeField> output =
+        ImmutableMap.builder();
+    for (final RelDataTypeField source : sourceFields) {
+      final RelDataTypeField target = targetFields.getField(source.getName(), 
true, false);
+      if (target != null) {
+        output.put(source.getIndex(), target);
+      }
+    }
+    return output.build();
+  }
+
+  /**
+   * Gets the bit-set to the column ordinals in the source for columns that 
intersect in the target.
+   * @param sourceRowType The source upon which to ordinate the bit set.
+   * @param targetRowType The target to overlay on the source to create the 
bit set.
+   */
+  public static ImmutableBitSet getOrdinalBitSet(
+      RelDataType sourceRowType, RelDataType targetRowType) {
+    Map<Integer, RelDataTypeField> indexToField =
+        getIndexToFieldMap(sourceRowType.getFieldList(), targetRowType);
+    return getOrdinalBitSet(sourceRowType, indexToField);
+  }
+
+  /**
+   * Gets the bit-set to the column ordinals in the source for columns that
+   * intersect in the target.
+   *
+   * @param sourceRowType The source upon which to ordinate the bit set.
+   * @param indexToField  The map of ordinals to target fields.
+   */
+  public static ImmutableBitSet getOrdinalBitSet(
+      RelDataType sourceRowType,
+      Map<Integer, RelDataTypeField> indexToField) {
+    ImmutableBitSet source = ImmutableBitSet.of(
+        Lists.transform(
+            sourceRowType.getFieldList(),
+            new RelDataTypeField.ToFieldIndex()));
+    ImmutableBitSet target =
+        ImmutableBitSet.of(indexToField.keySet());
+    return source.intersect(target);
+  }
+
+  /** Returns a map from field names to indexes. */
+  static Map<String, Integer> mapNameToIndex(List<RelDataTypeField> fields) {
+    ImmutableMap.Builder<String, Integer> output = ImmutableMap.builder();
+    for (RelDataTypeField field : fields) {
+      output.put(field.getName(), field.getIndex());
+    }
+    return output.build();
+  }
+
   @Deprecated // to be removed before 2.0
   public static RelDataTypeField lookupField(boolean caseSensitive,
       final RelDataType rowType, String columnName) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java 
b/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java
index d354c5d..a8c6476 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java
@@ -16,9 +16,12 @@
  */
 package org.apache.calcite.sql.validate;
 
+import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.schema.ExtensibleTable;
+import org.apache.calcite.schema.Table;
 import org.apache.calcite.sql.SqlNode;
 
 import com.google.common.base.Preconditions;
@@ -76,6 +79,17 @@ class TableNamespace extends AbstractNamespace {
    * be present if you ask for them. Phoenix uses them, for instance, to access
    * rarely used fields in the underlying HBase table. */
   public TableNamespace extend(List<RelDataTypeField> extendedFields) {
+    final Table schemaTable = table.unwrap(Table.class);
+    if (schemaTable != null
+        && table instanceof RelOptTable
+        && schemaTable instanceof ExtensibleTable) {
+      final SqlValidatorTable validatorTable =
+          ((RelOptTable) table).extend(ImmutableList.copyOf(
+              Iterables.concat(this.extendedFields, extendedFields)))
+          .unwrap(SqlValidatorTable.class);
+      return new TableNamespace(
+          validator, validatorTable, ImmutableList.<RelDataTypeField>of());
+    }
     return new TableNamespace(validator, table,
         ImmutableList.copyOf(
             Iterables.concat(this.extendedFields, extendedFields)));

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java
 
b/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java
index 83ecdf3..f0acd94 100644
--- 
a/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java
+++ 
b/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java
@@ -29,7 +29,7 @@ import java.util.List;
  * An implementation of {@link InitializerExpressionFactory} that always 
supplies NULL.
  */
 public class NullInitializerExpressionFactory implements 
InitializerExpressionFactory {
-  private final RexBuilder rexBuilder;
+  protected final RexBuilder rexBuilder;
 
   public NullInitializerExpressionFactory(RelDataTypeFactory typeFactory) {
     this.rexBuilder = new RexBuilder(typeFactory);

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 e832f19..06d593b 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -1948,31 +1948,14 @@ public class SqlToRelConverter {
       return;
 
     case IDENTIFIER:
-      final SqlValidatorNamespace fromNamespace =
-          validator.getNamespace(from).resolve();
-      if (fromNamespace.getNode() != null) {
-        convertFrom(bb, fromNamespace.getNode());
-        return;
-      }
-      final String datasetName =
-          datasetStack.isEmpty() ? null : datasetStack.peek();
-      boolean[] usedDataset = {false};
-      RelOptTable table =
-          SqlValidatorUtil.getRelOptTable(
-              fromNamespace,
-              catalogReader,
-              datasetName,
-              usedDataset);
-      final RelNode tableRel;
-      if (config.isConvertTableAccess()) {
-        tableRel = toRel(table);
-      } else {
-        tableRel = LogicalTableScan.create(cluster, table);
-      }
-      bb.setRoot(tableRel, true);
-      if (usedDataset[0]) {
-        bb.setDataset(datasetName);
-      }
+      convertIdentifier(bb, (SqlIdentifier) from, null);
+      return;
+
+    case EXTEND:
+      call = (SqlCall) from;
+      SqlIdentifier id = (SqlIdentifier) call.getOperandList().get(0);
+      SqlNodeList extendedColumns = (SqlNodeList) call.getOperandList().get(1);
+      convertIdentifier(bb, id, extendedColumns);
       return;
 
     case JOIN:
@@ -2149,6 +2132,41 @@ public class SqlToRelConverter {
     bb.setRoot(rel, false);
   }
 
+  private void convertIdentifier(Blackboard bb, SqlIdentifier id,
+      SqlNodeList extendedColumns) {
+    final SqlValidatorNamespace fromNamespace =
+        validator.getNamespace(id).resolve();
+    if (fromNamespace.getNode() != null) {
+      convertFrom(bb, fromNamespace.getNode());
+      return;
+    }
+    final String datasetName =
+        datasetStack.isEmpty() ? null : datasetStack.peek();
+    final boolean[] usedDataset = {false};
+    RelOptTable table =
+        SqlValidatorUtil.getRelOptTable(fromNamespace, catalogReader,
+            datasetName, usedDataset);
+    if (extendedColumns != null && extendedColumns.size() > 0) {
+      assert table != null;
+      final SqlValidatorTable validatorTable =
+          table.unwrap(SqlValidatorTable.class);
+      final List<RelDataTypeField> extendedFields =
+          SqlValidatorUtil.getExtendedColumns(validator, validatorTable,
+              extendedColumns);
+      table = table.extend(extendedFields);
+    }
+    final RelNode tableRel;
+    if (config.isConvertTableAccess()) {
+      tableRel = toRel(table);
+    } else {
+      tableRel = LogicalTableScan.create(cluster, table);
+    }
+    bb.setRoot(tableRel, true);
+    if (usedDataset[0]) {
+      bb.setDataset(datasetName);
+    }
+  }
+
   protected void convertCollectionTable(
       Blackboard bb,
       SqlCall call) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
----------------------------------------------------------------------
diff --git 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 75cbe07..4fb7c9a 100644
--- 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -206,8 +206,10 @@ StreamMustOrderByMonotonic=Streaming ORDER BY must start 
with monotonic expressi
 StreamSetOpInconsistentInputs=Set operator cannot combine streaming and 
non-streaming inputs
 CannotStreamValues=Cannot stream VALUES
 ModifiableViewMustBeBasedOnSingleTable=Modifiable view must be based on a 
single table
+ModifiableViewMustHaveOnlyEqualityPredicates=Modifiable view must be 
predicated only on equality expressions
 MoreThanOneMappedColumn=View is not modifiable. More than one expression maps 
to column ''{0}'' of base table ''{1}''
 NoValueSuppliedForViewColumn=View is not modifiable. No value is supplied for 
NOT NULL column ''{0}'' of base table ''{1}''
+ViewConstraintNotSatisfied=Modifiable view constraint is not satisfied for 
column ''{0}'' of base table ''{1}''
 StarRequiresRecordType=Not a record type. The ''*'' operator requires a record
 FilterMustBeBoolean=FILTER expression must be of type BOOLEAN
 CannotStreamResultsForNonStreamingInputs=Cannot stream results of a query with 
no streaming inputs: ''{0}''. At least one input should be convertible to a 
stream

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java 
b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index 24aae33..5f48a24 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -72,6 +72,8 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
           "TABLE(CATALOG.SALES.EMPDEFAULTS)",
           "TABLE(CATALOG.SALES.EMPNULLABLES)",
           "TABLE(CATALOG.SALES.EMP_B)",
+          "TABLE(CATALOG.SALES.EMP_MODIFIABLEVIEW)",
+          "TABLE(CATALOG.SALES.EMP_MODIFIABLEVIEW2)",
           "TABLE(CATALOG.SALES.EMP_20)",
           "TABLE(CATALOG.SALES.EMPNULLABLES_20)",
           "TABLE(CATALOG.SALES.EMP_ADDRESS)",

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java 
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 2fcbb0a..8138ebf 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -348,8 +348,7 @@ public class JdbcTest {
           true)
           .query("select \"name\" from \"adhoc\".V order by \"name\"")
           .throws_(
-              "View is not modifiable. No value is supplied for NOT NULL "
-                  + "column 'deptno' of base table 'MUTABLE_EMPLOYEES'");
+              "Modifiable view must be predicated only on equality 
expressions");
 
       // Deduce "deptno = 10" from the constraint, and add a further
       // condition "deptno < 20 OR commission > 1000".
@@ -358,10 +357,15 @@ public class JdbcTest {
               + "where \"deptno\" = 10 AND (\"deptno\" < 20 OR \"commission\" 
> 1000)",
           true)
           .query("insert into \"adhoc\".v values ('n',1,2)")
-          .explainContains(""
-              + "EnumerableTableModify(table=[[adhoc, MUTABLE_EMPLOYEES]], 
operation=[INSERT], flattened=[false])\n"
-              + "  EnumerableCalc(expr#0..2=[{inputs}], 
expr#3=[CAST($t1):JavaType(int) NOT NULL], expr#4=[10], 
expr#5=[CAST($t0):JavaType(class java.lang.String)], 
expr#6=[CAST($t2):JavaType(float) NOT NULL], expr#7=[null], expr#8=[20], 
expr#9=[<($t4, $t8)], expr#10=[1000], expr#11=[>($t7, $t10)], expr#12=[OR($t9, 
$t11)], empid=[$t3], deptno=[$t4], name=[$t5], salary=[$t6], commission=[$t7], 
$condition=[$t12])\n"
-              + "    EnumerableValues(tuples=[[{ 'n', 1, 2 }]])");
+          .throws_(
+              "Modifiable view must be predicated only on equality 
expressions");
+      modelWithView("select \"name\", \"empid\" as e, \"salary\" "
+              + "from \"MUTABLE_EMPLOYEES\"\n"
+              + "where \"deptno\" = 10 AND (\"deptno\" > 20 AND \"commission\" 
> 1000)",
+          true)
+          .query("insert into \"adhoc\".v values ('n',1,2)")
+          .throws_(
+              "Modifiable view must be predicated only on equality 
expressions");
 
       modelWithView(
           "select \"name\", \"empid\" as e, \"salary\" "

http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java 
b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
index 420354d..f7802a7 100644
--- a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
+++ b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
@@ -16,6 +16,8 @@
  */
 package org.apache.calcite.test;
 
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.jdbc.CalcitePrepare;
 import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.linq4j.QueryProvider;
@@ -51,6 +53,7 @@ import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.schema.CustomColumnResolvingTable;
+import org.apache.calcite.schema.ExtensibleTable;
 import org.apache.calcite.schema.Path;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
@@ -58,8 +61,12 @@ import org.apache.calcite.schema.Schemas;
 import org.apache.calcite.schema.Statistic;
 import org.apache.calcite.schema.StreamableTable;
 import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.TableMacro;
+import org.apache.calcite.schema.TranslatableTable;
 import org.apache.calcite.schema.Wrapper;
 import org.apache.calcite.schema.impl.AbstractSchema;
+import org.apache.calcite.schema.impl.ModifiableViewTable;
+import org.apache.calcite.schema.impl.ViewTableMacro;
 import org.apache.calcite.sql.SqlAccessType;
 import org.apache.calcite.sql.SqlCollation;
 import org.apache.calcite.sql.SqlFunction;
@@ -83,6 +90,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -192,9 +200,7 @@ public class MockCatalogReader extends CalciteCatalogReader 
{
     // Register "EMPDEFAULTS" table with default values for some columns.
     final InitializerExpressionFactory empInitializerExpressionFactory =
         new NullInitializerExpressionFactory(typeFactory) {
-          @Override public RexNode newColumnDefaultValue(RelOptTable table,
-              int iColumn) {
-            final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+          @Override public RexNode newColumnDefaultValue(RelOptTable table, 
int iColumn) {
             switch (iColumn) {
             case 0:
               return rexBuilder.makeExactLiteral(new BigDecimal(123),
@@ -483,6 +489,8 @@ public class MockCatalogReader extends CalciteCatalogReader 
{
     //   SELECT *
     //   FROM T
     //   WHERE F0.C0 = 10
+    // This table uses MockViewTable which does not populate the constrained 
columns with default
+    // values on INSERT.
     final ImmutableIntList m1 = ImmutableIntList.of(0, 1, 2, 3, 4, 5, 6, 7, 8);
     MockTable struct10View =
         new MockViewTable(this, structTypeSchema.getCatalogName(),
@@ -504,6 +512,42 @@ public class MockCatalogReader extends 
CalciteCatalogReader {
       struct10View.addColumn(column.getName(), column.type);
     }
     registerTable(struct10View);
+
+    return init2(salesSchema);
+  }
+
+  private MockCatalogReader init2(MockSchema salesSchema) {
+    // Same as "EMP_20" except it uses ModifiableViewTable which populates
+    // constrained columns with default values on INSERT and has a single 
constraint on DEPTNO.
+    List<String> empModifiableViewNames = ImmutableList.of(
+        salesSchema.getCatalogName(), salesSchema.name, "EMP_MODIFIABLEVIEW");
+    TableMacro empModifiableViewMacro = 
MockModifiableViewRelOptTable.viewMacro(rootSchema,
+        "select EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, SLACKER from 
EMPDEFAULTS"
+            + " where DEPTNO = 20", empModifiableViewNames.subList(0, 2),
+        ImmutableList.of(empModifiableViewNames.get(2)), true);
+    TranslatableTable empModifiableView = 
empModifiableViewMacro.apply(ImmutableList.of());
+    MockModifiableViewRelOptTable mockEmpViewTable = 
MockModifiableViewRelOptTable.create(
+        (MockModifiableViewRelOptTable.MockModifiableViewTable) 
empModifiableView, this,
+        empModifiableViewNames.get(0), empModifiableViewNames.get(1),
+        empModifiableViewNames.get(2), false, 20, null);
+    registerTable(mockEmpViewTable);
+
+    // Same as "EMP_MODIFIABLEVIEW" except that all columns are in the view, 
columns are reordered,
+    // and there is an `extra` extended column.
+    List<String> empModifiableViewNames2 = ImmutableList.of(
+        salesSchema.getCatalogName(), salesSchema.name, "EMP_MODIFIABLEVIEW2");
+    TableMacro empModifiableViewMacro2 = 
MockModifiableViewRelOptTable.viewMacro(rootSchema,
+        "select ENAME, EMPNO, JOB, DEPTNO, SLACKER, SAL, EXTRA, HIREDATE, MGR, 
COMM"
+            + " from EMPDEFAULTS extend (EXTRA boolean)"
+            + " where DEPTNO = 20", empModifiableViewNames2.subList(0, 2),
+        ImmutableList.of(empModifiableViewNames.get(2)), true);
+    TranslatableTable empModifiableView2 = 
empModifiableViewMacro2.apply(ImmutableList.of());
+    MockModifiableViewRelOptTable mockEmpViewTable2 = 
MockModifiableViewRelOptTable.create(
+        (MockModifiableViewRelOptTable.MockModifiableViewTable) 
empModifiableView2, this,
+        empModifiableViewNames2.get(0), empModifiableViewNames2.get(1),
+        empModifiableViewNames2.get(2), false, 20, null);
+    registerTable(mockEmpViewTable2);
+
     return this;
   }
 
@@ -511,22 +555,27 @@ public class MockCatalogReader extends 
CalciteCatalogReader {
 
   protected void registerTable(final MockTable table) {
     table.onRegister(typeFactory);
-    assert table.names.get(0).equals(DEFAULT_CATALOG);
-    final CalciteSchema schema =
-        rootSchema.getSubSchema(table.names.get(1), true);
     final WrapperTable wrapperTable = new WrapperTable(table);
     if (table.stream) {
-      schema.add(table.names.get(2),
+      registerTable(table.names,
           new StreamableWrapperTable(table) {
             public Table stream() {
               return wrapperTable;
             }
           });
     } else {
-      schema.add(table.names.get(2), wrapperTable);
+      registerTable(table.names, wrapperTable);
     }
   }
 
+  private void registerTable(final List<String> names, final Table table) {
+    assert names.get(0).equals(DEFAULT_CATALOG);
+    final String schemaName = names.get(1);
+    final String tableName = names.get(2);
+    final CalciteSchema schema = rootSchema.getSubSchema(schemaName, true);
+    schema.add(tableName, table);
+  }
+
   protected void registerSchema(MockSchema schema) {
     rootSchema.add(schema.name, new AbstractSchema());
   }
@@ -598,34 +647,62 @@ public class MockCatalogReader extends 
CalciteCatalogReader {
    */
   public static class MockTable extends Prepare.AbstractPreparingTable {
     protected final MockCatalogReader catalogReader;
-    private final boolean stream;
-    private final double rowCount;
+    protected final boolean stream;
+    protected final double rowCount;
     protected final List<Map.Entry<String, RelDataType>> columnList =
         new ArrayList<>();
     protected final List<Integer> keyList = new ArrayList<>();
     protected RelDataType rowType;
-    private List<RelCollation> collationList;
+    protected List<RelCollation> collationList;
     protected final List<String> names;
-    private final Set<String> monotonicColumnSet = Sets.newHashSet();
-    private StructKind kind = StructKind.FULLY_QUALIFIED;
+    protected final Set<String> monotonicColumnSet = Sets.newHashSet();
+    protected StructKind kind = StructKind.FULLY_QUALIFIED;
     protected final ColumnResolver resolver;
-    private final InitializerExpressionFactory initializerFactory;
+    protected final InitializerExpressionFactory initializerFactory;
 
     public MockTable(MockCatalogReader catalogReader, String catalogName,
         String schemaName, String name, boolean stream, double rowCount,
         ColumnResolver resolver,
         InitializerExpressionFactory initializerFactory) {
+      this(catalogReader, ImmutableList.of(catalogName, schemaName, name), 
stream, rowCount,
+          resolver, initializerFactory);
+    }
+
+    private MockTable(MockCatalogReader catalogReader, List<String> names, 
boolean stream,
+        double rowCount, ColumnResolver resolver, InitializerExpressionFactory 
initializerFactory) {
       this.catalogReader = catalogReader;
       this.stream = stream;
       this.rowCount = rowCount;
-      this.names = ImmutableList.of(catalogName, schemaName, name);
+      this.names = names;
       this.resolver = resolver;
       this.initializerFactory = initializerFactory;
     }
 
+    /**
+     * Copy constructor.
+     */
+    protected MockTable(MockCatalogReader catalogReader, boolean stream, 
double rowCount,
+        List<Map.Entry<String, RelDataType>> columnList, List<Integer> keyList,
+        RelDataType rowType, List<RelCollation> collationList, List<String> 
names,
+        Set<String> monotonicColumnSet, StructKind kind, ColumnResolver 
resolver,
+        InitializerExpressionFactory initializerFactory) {
+      this.catalogReader = catalogReader;
+      this.stream = stream;
+      this.rowCount = rowCount;
+      this.rowType = rowType;
+      this.collationList = collationList;
+      this.names = names;
+      this.kind = kind;
+      this.resolver = resolver;
+      this.initializerFactory = initializerFactory;
+      for (String name : monotonicColumnSet) {
+        addMonotonic(name);
+      }
+    }
+
     /** Implementation of AbstractModifiableTable. */
     private class ModifiableTable extends JdbcTest.AbstractModifiableTable
-        implements Wrapper {
+        implements ExtensibleTable, Wrapper {
       protected ModifiableTable(String tableName) {
         super(tableName);
       }
@@ -657,9 +734,35 @@ public class MockCatalogReader extends 
CalciteCatalogReader {
       @Override public <C> C unwrap(Class<C> aClass) {
         if (aClass.isInstance(initializerFactory)) {
           return aClass.cast(initializerFactory);
+        } else if (aClass.isInstance(MockTable.this)) {
+          return aClass.cast(MockTable.this);
         }
         return null;
       }
+
+      @Override public Table extend(final List<RelDataTypeField> fields) {
+        return new ModifiableTable(Util.last(names)) {
+          @Override public RelDataType getRowType(RelDataTypeFactory 
typeFactory) {
+            ImmutableList<RelDataTypeField> allFields = ImmutableList.copyOf(
+                Iterables.concat(
+                    rowType.getFieldList(),
+                    fields));
+            return typeFactory.createStructType(allFields);
+          }
+        };
+      }
+
+      @Override public int getExtendedColumnOffset() {
+        return rowType.getFieldCount();
+      }
+    }
+
+    @Override protected RelOptTable extend(final Table extendedTable) {
+      return new MockTable(catalogReader, names, stream, rowCount, resolver, 
initializerFactory) {
+        @Override public RelDataType getRowType() {
+          return extendedTable.getRowType(catalogReader.typeFactory);
+        }
+      };
     }
 
     /**
@@ -677,13 +780,6 @@ public class MockCatalogReader extends 
CalciteCatalogReader {
           RelDataType rowType, RelDataTypeFactory typeFactory, List<String> 
names) {
         return resolver.resolveColumn(rowType, typeFactory, names);
       }
-
-      @Override public <C> C unwrap(Class<C> aClass) {
-        if (aClass.isInstance(initializerFactory)) {
-          return aClass.cast(initializerFactory);
-        }
-        return null;
-      }
     }
 
     public static MockTable create(MockCatalogReader catalogReader,
@@ -815,6 +911,136 @@ public class MockCatalogReader extends 
CalciteCatalogReader {
   }
 
   /**
+   * Alternative to MockViewTable that exercises code paths in 
ModifiableViewTable
+   * and ModifiableViewTableInitializerExpressionFactory.
+   */
+  public static class MockModifiableViewRelOptTable extends MockTable {
+    private final MockModifiableViewTable modifiableViewTable;
+
+    private MockModifiableViewRelOptTable(MockModifiableViewTable 
modifiableViewTable,
+        MockCatalogReader catalogReader, String catalogName, String 
schemaName, String name,
+        boolean stream, double rowCount, ColumnResolver resolver,
+        InitializerExpressionFactory initializerExpressionFactory) {
+      super(catalogReader, ImmutableList.of(catalogName, schemaName, name), 
stream, rowCount,
+          resolver, initializerExpressionFactory);
+      this.modifiableViewTable = modifiableViewTable;
+    }
+
+    /**
+     * Copy constructor.
+     */
+    private MockModifiableViewRelOptTable(MockModifiableViewTable 
modifiableViewTable,
+        MockCatalogReader catalogReader, boolean stream, double rowCount,
+        List<Map.Entry<String, RelDataType>> columnList, List<Integer> keyList,
+        RelDataType rowType, List<RelCollation> collationList, List<String> 
names,
+        Set<String> monotonicColumnSet, StructKind kind, ColumnResolver 
resolver,
+        InitializerExpressionFactory initializerFactory) {
+      super(catalogReader, stream, rowCount, columnList, keyList, rowType, 
collationList, names,
+          monotonicColumnSet, kind, resolver, initializerFactory);
+      this.modifiableViewTable = modifiableViewTable;
+    }
+
+    public static MockModifiableViewRelOptTable create(MockModifiableViewTable 
modifiableViewTable,
+        MockCatalogReader catalogReader, String catalogName, String 
schemaName, String name,
+        boolean stream, double rowCount, ColumnResolver resolver) {
+      final Table underlying = modifiableViewTable.unwrap(Table.class);
+      final InitializerExpressionFactory maybeInitializerExpressionFactory =
+          underlying != null && underlying instanceof Wrapper
+              ? ((Wrapper) 
underlying).unwrap(InitializerExpressionFactory.class)
+              : new 
NullInitializerExpressionFactory(catalogReader.typeFactory);
+      final InitializerExpressionFactory initializerExpressionFactory =
+          maybeInitializerExpressionFactory == null
+              ? new NullInitializerExpressionFactory(catalogReader.typeFactory)
+              : maybeInitializerExpressionFactory;
+      return new MockModifiableViewRelOptTable(modifiableViewTable, 
catalogReader, catalogName,
+          schemaName, name, stream, rowCount, resolver, 
initializerExpressionFactory);
+    }
+
+    public static MockViewTableMacro viewMacro(CalciteSchema schema, String 
viewSql,
+        List<String> schemaPath, List<String> viewPath, Boolean modifiable) {
+      return new MockViewTableMacro(schema, viewSql, schemaPath, viewPath, 
modifiable);
+    }
+
+    @Override public RelDataType getRowType() {
+      return modifiableViewTable.getRowType(catalogReader.typeFactory);
+    }
+
+    @Override public RelOptTable extend(List<RelDataTypeField> extendedFields) 
{
+      final ExtensibleTable table = 
modifiableViewTable.unwrap(ExtensibleTable.class);
+      final Table extendedTable = table.extend(extendedFields);
+      final MockModifiableViewTable mockModifiableViewTableExtended =
+          new MockModifiableViewTable(modifiableViewTable.elementType,
+              
RelDataTypeImpl.proto(extendedTable.getRowType(modifiableViewTable.typeFactory)),
+              modifiableViewTable.viewSql, modifiableViewTable.schemaPath,
+              modifiableViewTable.viewPath, extendedTable, 
modifiableViewTable.tablePath,
+              modifiableViewTable.constraint, 
modifiableViewTable.columnMapping,
+              modifiableViewTable.typeFactory);
+      return new 
MockModifiableViewRelOptTable(mockModifiableViewTableExtended, catalogReader,
+          stream, rowCount, columnList, keyList, rowType, collationList, 
names, monotonicColumnSet,
+          kind, resolver, initializerFactory);
+    }
+
+    @Override public <T> T unwrap(Class<T> clazz) {
+      if (clazz.isInstance(modifiableViewTable)) {
+        return clazz.cast(modifiableViewTable);
+      }
+      return super.unwrap(clazz);
+    }
+
+    /**
+     * A TableMacro that creates mock ModifiableViewTable.
+     */
+    public static class MockViewTableMacro extends ViewTableMacro {
+      MockViewTableMacro(CalciteSchema schema, String viewSql, List<String> 
schemaPath,
+          List<String> viewPath, Boolean modifiable) {
+        super(schema, viewSql, schemaPath, viewPath, modifiable);
+      }
+
+      @Override protected ModifiableViewTable modifiableViewTable(
+          CalcitePrepare.AnalyzeViewResult parsed, String viewSql,
+          List<String> schemaPath, List<String> viewPath, CalciteSchema 
schema) {
+        final JavaTypeFactory typeFactory = (JavaTypeFactory) 
parsed.typeFactory;
+        final Type elementType = typeFactory.getJavaClass(parsed.rowType);
+        return new MockModifiableViewTable(elementType,
+            RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath, 
viewPath,
+            parsed.table, Schemas.path(schema.root(), parsed.tablePath),
+            parsed.constraint, parsed.columnMapping, parsed.typeFactory);
+      }
+    }
+
+    /**
+     * A mock of ModifiableViewTable that can unwrap a mock RelOptTable.
+     */
+    private static class MockModifiableViewTable extends ModifiableViewTable {
+      private final Type elementType;
+      private final String viewSql;
+      private final List<String> schemaPath;
+      private final List<String> viewPath;
+      private final Path tablePath;
+      private final RexNode constraint;
+      private final ImmutableIntList columnMapping;
+      private final RelDataTypeFactory typeFactory;
+
+      MockModifiableViewTable(Type elementType, RelProtoDataType rowType,
+          String viewSql, List<String> schemaPath, List<String> viewPath,
+          Table table, Path tablePath, RexNode constraint,
+          ImmutableIntList columnMapping, RelDataTypeFactory typeFactory) {
+        super(elementType, rowType, viewSql, schemaPath, viewPath, table,
+            tablePath, constraint, columnMapping, typeFactory);
+        this.elementType = elementType;
+        this.viewSql = viewSql;
+        this.schemaPath = schemaPath;
+        this.viewPath = viewPath;
+        this.tablePath = tablePath;
+        this.constraint = constraint;
+        this.columnMapping = columnMapping;
+        this.typeFactory = typeFactory;
+      }
+    }
+
+  }
+
+  /**
    * Mock implementation of
    * {@link org.apache.calcite.prepare.Prepare.PreparingTable} for views.
    */

Reply via email to