calcite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jh...@apache.org
Subject [1/2] incubator-calcite git commit: [CALCITE-493] Add EXTEND clause, for defining columns and their types at query/DML time
Date Wed, 10 Dec 2014 18:08:04 GMT
Repository: incubator-calcite
Updated Branches:
  refs/heads/master 155d24f1a -> a640e58cf


[CALCITE-493] Add EXTEND clause, for defining columns and their types at query/DML time


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

Branch: refs/heads/master
Commit: ecc5f4c2fff0a7a2a310b109b070d402c156f25a
Parents: 155d24f
Author: Julian Hyde <jhyde@apache.org>
Authored: Mon Dec 8 17:15:23 2014 -0800
Committer: Julian Hyde <jhyde@apache.org>
Committed: Wed Dec 10 08:38:18 2014 -0800

----------------------------------------------------------------------
 core/src/main/codegen/templates/Parser.jj       | 76 ++++++++++++++++++--
 .../calcite/plan/RelOptAbstractTable.java       |  5 ++
 .../org/apache/calcite/plan/RelOptTable.java    |  4 ++
 .../apache/calcite/prepare/RelOptTableImpl.java | 15 ++++
 .../apache/calcite/schema/ExtensibleTable.java  | 45 ++++++++++++
 .../org/apache/calcite/sql/SqlDataTypeSpec.java | 53 +++++++++++---
 .../java/org/apache/calcite/sql/SqlDelete.java  |  8 +--
 .../java/org/apache/calcite/sql/SqlInsert.java  |  6 +-
 .../java/org/apache/calcite/sql/SqlKind.java    |  7 +-
 .../java/org/apache/calcite/sql/SqlMerge.java   |  6 +-
 .../java/org/apache/calcite/sql/SqlUpdate.java  |  6 +-
 .../calcite/sql/fun/SqlStdOperatorTable.java    |  5 ++
 .../sql/validate/IdentifierNamespace.java       | 52 ++++++++++++--
 .../calcite/sql/validate/SqlValidatorImpl.java  | 46 +++++++-----
 .../calcite/sql/validate/SqlValidatorUtil.java  | 35 ++++-----
 .../calcite/sql/validate/TableNamespace.java    | 41 +++++++++--
 .../calcite/sql/parser/SqlParserTest.java       | 37 +++++++++-
 .../apache/calcite/sql/test/SqlAdvisorTest.java |  2 +
 .../apache/calcite/test/MockCatalogReader.java  | 48 ++++++++-----
 .../apache/calcite/test/RelOptRulesTest.java    |  2 +-
 .../calcite/test/SqlToRelConverterTest.java     | 42 +++++------
 .../apache/calcite/test/SqlToRelTestBase.java   | 14 +++-
 .../apache/calcite/test/SqlValidatorTest.java   |  9 +++
 .../calcite/test/SqlToRelConverterTest.xml      | 11 +++
 24 files changed, 460 insertions(+), 115 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/codegen/templates/Parser.jj
----------------------------------------------------------------------
diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 23c897e..4b28e3b 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -205,6 +205,12 @@ public class ${parser.class} extends SqlAbstractParserImpl
     {
         return SqlStmtEof();
     }
+
+    private SqlNode extend(SqlNode table, SqlNodeList extendList) {
+        return SqlStdOperatorTable.EXTEND.createCall(
+            table.getParserPosition().plus(extendList.getParserPosition()),
+            table, extendList);
+    }
 }
 
 PARSER_END(${parser.class})
@@ -1158,7 +1164,8 @@ SqlNode SqlInsert() :
  */
 SqlNode SqlDelete() :
 {
-    SqlIdentifier table;
+    SqlNode table;
+    SqlNodeList extendList = null;
     SqlIdentifier alias = null;
     SqlNode condition;
     SqlParserPos pos;
@@ -1172,6 +1179,12 @@ SqlNode SqlDelete() :
     {
 
     }
+    [
+        [ <EXTEND> ]
+        extendList = ExtendList() {
+            table = extend(table, extendList);
+        }
+    ]
     [ [ <AS> ] alias = SimpleIdentifier() ]
     condition = WhereOpt()
     {
@@ -1184,7 +1197,8 @@ SqlNode SqlDelete() :
  */
 SqlNode SqlUpdate() :
 {
-    SqlIdentifier table;
+    SqlNode table;
+    SqlNodeList extendList = null;
     SqlIdentifier alias = null;
     SqlNode condition;
     SqlNodeList sourceExpressionList;
@@ -1200,6 +1214,12 @@ SqlNode SqlUpdate() :
         targetColumnList = new SqlNodeList(pos);
         sourceExpressionList = new SqlNodeList(pos);
     }
+    [
+        [ <EXTEND> ]
+        extendList = ExtendList() {
+            table = extend(table, extendList);
+        }
+    ]
     [ [ <AS> ] alias = SimpleIdentifier() ]
     <SET> id = SimpleIdentifier()
     {
@@ -1233,7 +1253,8 @@ SqlNode SqlUpdate() :
  */
 SqlNode SqlMerge() :
 {
-    SqlIdentifier table;
+    SqlNode table;
+    SqlNodeList extendList = null;
     SqlIdentifier alias = null;
     SqlNode sourceTableRef;
     SqlNode condition;
@@ -1246,6 +1267,12 @@ SqlNode SqlMerge() :
     {
         mergePos = getPos();
     }
+    [
+        [ <EXTEND> ]
+        extendList = ExtendList() {
+            table = extend(table, extendList);
+        }
+    ]
     [ [ <AS> ] alias = SimpleIdentifier() ]
 
     <USING> sourceTableRef = TableRef()
@@ -1265,7 +1292,7 @@ SqlNode SqlMerge() :
     }
 }
 
-SqlUpdate WhenMatchedClause(SqlIdentifier table, SqlIdentifier alias) :
+SqlUpdate WhenMatchedClause(SqlNode table, SqlIdentifier alias) :
 {
     SqlIdentifier id;
     SqlParserPos pos;
@@ -1303,7 +1330,7 @@ SqlUpdate WhenMatchedClause(SqlIdentifier table, SqlIdentifier alias) :
     }
 }
 
-SqlInsert WhenNotMatchedClause(SqlIdentifier table) :
+SqlInsert WhenNotMatchedClause(SqlNode table) :
 {
     SqlParserPos pos, insertPos;
     List keywords = new ArrayList();
@@ -1592,6 +1619,7 @@ SqlNode TableRef() :
 {
     SqlNode tableRef;
     SqlNode over;
+    SqlNodeList extendList = null;
     String alias;
     SqlParserPos pos;
     SqlNodeList args;
@@ -1606,6 +1634,12 @@ SqlNode TableRef() :
     (
         LOOKAHEAD(2)
         tableRef = CompoundIdentifier()
+        [
+            [ <EXTEND> ]
+            extendList = ExtendList() {
+                tableRef = extend(tableRef, extendList);
+            }
+        ]
         over = TableOverOpt()
         {
             if (over != null) {
@@ -1730,6 +1764,37 @@ SqlNode TableRef() :
     }
 }
 
+SqlNodeList ExtendList() :
+{
+    SqlParserPos pos;
+    List<SqlNode> list = Lists.newArrayList();
+}
+{
+    <LPAREN> { pos = getPos(); }
+    ColumnType(list)
+    (
+        <COMMA> ColumnType(list)
+    )*
+    <RPAREN> {
+        return new SqlNodeList(list, pos.plus(getPos()));
+    }
+}
+
+void ColumnType(List<SqlNode> list) :
+{
+    SqlIdentifier name;
+    SqlDataTypeSpec type;
+}
+{
+    name = SimpleIdentifier()
+    type = DataType()
+    [ <NOT> <NULL> { type = type.withNullable(false); } ]
+    {
+        list.add(name);
+        list.add(type);
+    }
+}
+
 SqlNode TableFunctionCall(SqlParserPos pos) :
 {
     SqlNode call;
@@ -4631,6 +4696,7 @@ SqlPostfixOperator PostfixRowOperator() :
     | < EXP: "EXP" >
     | < EXPLAIN: "EXPLAIN" >
     | < EXTERNAL: "EXTERNAL" >
+    | < EXTEND: "EXTEND" >
     | < EXTRACT: "EXTRACT" >
     | < FALSE: "FALSE" >
     | < FETCH: "FETCH" >

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/java/org/apache/calcite/plan/RelOptAbstractTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptAbstractTable.java b/core/src/main/java/org/apache/calcite/plan/RelOptAbstractTable.java
index 2c3099d..f672ef3 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptAbstractTable.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptAbstractTable.java
@@ -21,6 +21,7 @@ import org.apache.calcite.rel.RelCollation;
 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.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
@@ -94,6 +95,10 @@ public abstract class RelOptAbstractTable implements RelOptTable {
   public Expression getExpression(Class clazz) {
     throw new UnsupportedOperationException();
   }
+
+  public RelOptTable extend(List<RelDataTypeField> extendedFields) {
+    throw new UnsupportedOperationException();
+  }
 }
 
 // End RelOptAbstractTable.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/java/org/apache/calcite/plan/RelOptTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptTable.java b/core/src/main/java/org/apache/calcite/plan/RelOptTable.java
index c628f71..7f069e0 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptTable.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptTable.java
@@ -20,6 +20,7 @@ import org.apache.calcite.linq4j.tree.Expression;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import java.util.List;
@@ -96,6 +97,9 @@ public interface RelOptTable {
    */
   Expression getExpression(Class clazz);
 
+  /** Returns a table with the given extra fields. */
+  RelOptTable extend(List<RelDataTypeField> extendedFields);
+
   /** Can expand a view into relational expressions. */
   interface ViewExpander {
     RelNode expandView(

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/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 68e0dcf..f91c45f 100644
--- a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
@@ -23,9 +23,12 @@ import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.linq4j.tree.Expression;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.schema.ExtensibleTable;
 import org.apache.calcite.schema.FilterableTable;
 import org.apache.calcite.schema.ProjectableFilterableTable;
 import org.apache.calcite.schema.QueryableTable;
@@ -160,6 +163,18 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
     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
+  }
+
   public double getRowCount() {
     if (rowCount != null) {
       return rowCount;

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/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
new file mode 100644
index 0000000..7469cd7
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import org.apache.calcite.rel.type.RelDataTypeField;
+
+import java.util.List;
+
+/**
+ * Table whose row type can be extended to include extra fields.
+ *
+ * <p>In some storage systems, especially those with "late schema", there may
+ * exist columns that have values in the table but which are not declared in
+ * the table schema. However, a particular query may wish to reference these
+ * columns as if they were defined in the schema. Calling the {@link #extend}
+ * method creates a temporarily extended table schema.
+ *
+ * <p>If the table implements extended interfaces such as
+ * {@link org.apache.calcite.schema.ScannableTable},
+ * {@link org.apache.calcite.schema.FilterableTable} or
+ * {@link org.apache.calcite.schema.ProjectableFilterableTable}, you may wish
+ * to make the table returned from {@link #extend} implement these interfaces
+ * as well.
+ */
+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);
+}
+
+// End ExtensibleTable.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java b/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java
index e366204..fd4f22c 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java
@@ -18,6 +18,7 @@ package org.apache.calcite.sql;
 
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.runtime.SqlFunctions;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
@@ -61,10 +62,17 @@ public class SqlDataTypeSpec extends SqlNode {
   private final String charSetName;
   private final TimeZone timeZone;
 
+  /** Whether data type is allows nulls.
+   *
+   * <p>Nullable is nullable! Null means "not specified". E.g.
+   * {@code CAST(x AS INTEGER)} preserves has the same nullability as {@code x}.
+   */
+  private Boolean nullable;
+
   //~ Constructors -----------------------------------------------------------
 
   /**
-   * Creates a type specification.
+   * Creates a type specification representing a regular, non-collection type.
    */
   public SqlDataTypeSpec(
       final SqlIdentifier typeName,
@@ -73,13 +81,7 @@ public class SqlDataTypeSpec extends SqlNode {
       String charSetName,
       TimeZone timeZone,
       SqlParserPos pos) {
-    super(pos);
-    this.collectionsTypeName = null;
-    this.typeName = typeName;
-    this.scale = scale;
-    this.precision = precision;
-    this.charSetName = charSetName;
-    this.timeZone = timeZone;
+    this(null, typeName, precision, scale, charSetName, timeZone, null, pos);
   }
 
   /**
@@ -92,13 +94,30 @@ public class SqlDataTypeSpec extends SqlNode {
       int scale,
       String charSetName,
       SqlParserPos pos) {
+    this(collectionsTypeName, typeName, precision, scale, charSetName, null,
+        null, pos);
+  }
+
+  /**
+   * Creates a type specification.
+   */
+  public SqlDataTypeSpec(
+      SqlIdentifier collectionsTypeName,
+      SqlIdentifier typeName,
+      int precision,
+      int scale,
+      String charSetName,
+      TimeZone timeZone,
+      Boolean nullable,
+      SqlParserPos pos) {
     super(pos);
     this.collectionsTypeName = collectionsTypeName;
     this.typeName = typeName;
-    this.scale = scale;
     this.precision = precision;
+    this.scale = scale;
     this.charSetName = charSetName;
-    this.timeZone = null;
+    this.timeZone = timeZone;
+    this.nullable = nullable;
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -139,6 +158,16 @@ public class SqlDataTypeSpec extends SqlNode {
     return timeZone;
   }
 
+  /** Returns a copy of this data type specification with a given
+   * nullability. */
+  public SqlDataTypeSpec withNullable(Boolean nullable) {
+    if (SqlFunctions.eq(nullable, this.nullable)) {
+      return this;
+    }
+    return new SqlDataTypeSpec(collectionsTypeName, typeName, precision, scale,
+        charSetName, timeZone, nullable, getParserPosition());
+  }
+
   /**
    * Returns a new SqlDataTypeSpec corresponding to the component type if the
    * type spec is a collections type spec.<br>
@@ -326,6 +355,10 @@ public class SqlDataTypeSpec extends SqlNode {
       }
     }
 
+    if (nullable != null) {
+      type = typeFactory.createTypeWithNullability(type, nullable);
+    }
+
     return type;
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/java/org/apache/calcite/sql/SqlDelete.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDelete.java b/core/src/main/java/org/apache/calcite/sql/SqlDelete.java
index 5401e2b..ed41657 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlDelete.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlDelete.java
@@ -32,7 +32,7 @@ public class SqlDelete extends SqlCall {
   public static final SqlSpecialOperator OPERATOR =
       new SqlSpecialOperator("DELETE", SqlKind.DELETE);
 
-  SqlIdentifier targetTable;
+  SqlNode targetTable;
   SqlNode condition;
   SqlSelect sourceSelect;
   SqlIdentifier alias;
@@ -41,7 +41,7 @@ public class SqlDelete extends SqlCall {
 
   public SqlDelete(
       SqlParserPos pos,
-      SqlIdentifier targetTable,
+      SqlNode targetTable,
       SqlNode condition,
       SqlSelect sourceSelect,
       SqlIdentifier alias) {
@@ -69,7 +69,7 @@ public class SqlDelete extends SqlCall {
   @Override public void setOperand(int i, SqlNode operand) {
     switch (i) {
     case 0:
-      targetTable = (SqlIdentifier) operand;
+      targetTable = operand;
       break;
     case 1:
       condition = operand;
@@ -88,7 +88,7 @@ public class SqlDelete extends SqlCall {
   /**
    * @return the identifier for the target table of the deletion
    */
-  public SqlIdentifier getTargetTable() {
+  public SqlNode getTargetTable() {
     return targetTable;
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/java/org/apache/calcite/sql/SqlInsert.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlInsert.java b/core/src/main/java/org/apache/calcite/sql/SqlInsert.java
index 71132b0..43861a9 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlInsert.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlInsert.java
@@ -32,7 +32,7 @@ public class SqlInsert extends SqlCall {
       new SqlSpecialOperator("INSERT", SqlKind.INSERT);
 
   SqlNodeList keywords;
-  SqlIdentifier targetTable;
+  SqlNode targetTable;
   SqlNode source;
   SqlNodeList columnList;
 
@@ -40,7 +40,7 @@ public class SqlInsert extends SqlCall {
 
   public SqlInsert(SqlParserPos pos,
       SqlNodeList keywords,
-      SqlIdentifier targetTable,
+      SqlNode targetTable,
       SqlNode source,
       SqlNodeList columnList) {
     super(pos);
@@ -87,7 +87,7 @@ public class SqlInsert extends SqlCall {
   /**
    * @return the identifier for the target table of the insertion
    */
-  public SqlIdentifier getTargetTable() {
+  public SqlNode getTargetTable() {
     return targetTable;
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/java/org/apache/calcite/sql/SqlKind.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index 359f113..4499055 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -565,6 +565,10 @@ public enum SqlKind {
    */
   REINTERPRET,
 
+  /** The internal {@code EXTEND} operator that qualifies a table name in the
+   * {@code FROM} clause. */
+  EXTEND,
+
   /** The internal {@code CUBE} operator that occurs within a {@code GROUP BY}
    * clause. */
   CUBE,
@@ -624,7 +628,8 @@ public enum SqlKind {
   public static final Set<SqlKind> EXPRESSION =
       EnumSet.complementOf(
           EnumSet.of(
-              AS, DESCENDING, SELECT, JOIN, OTHER_FUNCTION, CAST, TRIM,
+              AS, DESCENDING, CUBE, ROLLUP, GROUPING_SETS, EXTEND,
+              SELECT, JOIN, OTHER_FUNCTION, CAST, TRIM,
               LITERAL_CHAIN, JDBC_FN, PRECEDING, FOLLOWING, ORDER_BY,
               NULLS_FIRST, NULLS_LAST, COLLECTION_TABLE, TABLESAMPLE,
               WITH, WITH_ITEM));

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/java/org/apache/calcite/sql/SqlMerge.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlMerge.java b/core/src/main/java/org/apache/calcite/sql/SqlMerge.java
index 59317e3..77be4e7 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlMerge.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlMerge.java
@@ -34,7 +34,7 @@ public class SqlMerge extends SqlCall {
   public static final SqlSpecialOperator OPERATOR =
       new SqlSpecialOperator("MERGE", SqlKind.MERGE);
 
-  SqlIdentifier targetTable;
+  SqlNode targetTable;
   SqlNode condition;
   SqlNode source;
   SqlUpdate updateCall;
@@ -45,7 +45,7 @@ public class SqlMerge extends SqlCall {
   //~ Constructors -----------------------------------------------------------
 
   public SqlMerge(SqlParserPos pos,
-      SqlIdentifier targetTable,
+      SqlNode targetTable,
       SqlNode condition,
       SqlNode source,
       SqlUpdate updateCall,
@@ -108,7 +108,7 @@ public class SqlMerge extends SqlCall {
   /**
    * @return the identifier for the target table of the merge
    */
-  public SqlIdentifier getTargetTable() {
+  public SqlNode getTargetTable() {
     return targetTable;
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/java/org/apache/calcite/sql/SqlUpdate.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUpdate.java b/core/src/main/java/org/apache/calcite/sql/SqlUpdate.java
index 6773aa8..23afb22 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlUpdate.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlUpdate.java
@@ -33,7 +33,7 @@ public class SqlUpdate extends SqlCall {
   public static final SqlSpecialOperator OPERATOR =
       new SqlSpecialOperator("UPDATE", SqlKind.UPDATE);
 
-  SqlIdentifier targetTable;
+  SqlNode targetTable;
   SqlNodeList targetColumnList;
   SqlNodeList sourceExpressionList;
   SqlNode condition;
@@ -43,7 +43,7 @@ public class SqlUpdate extends SqlCall {
   //~ Constructors -----------------------------------------------------------
 
   public SqlUpdate(SqlParserPos pos,
-      SqlIdentifier targetTable,
+      SqlNode targetTable,
       SqlNodeList targetColumnList,
       SqlNodeList sourceExpressionList,
       SqlNode condition,
@@ -102,7 +102,7 @@ public class SqlUpdate extends SqlCall {
   /**
    * @return the identifier for the target table of the update
    */
-  public SqlIdentifier getTargetTable() {
+  public SqlNode getTargetTable() {
     return targetTable;
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index b260312..427ce95 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -177,6 +177,11 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
   public static final SqlGroupingIdFunction GROUPING_ID =
       new SqlGroupingIdFunction();
 
+  /** {@code EXTEND} operator to add columns to a table's schema, as in
+   * {@code SELECT ... FROM emp EXTEND (horoscope VARCHAR(100))}. */
+  public static final SqlInternalOperator EXTEND =
+      new SqlInternalOperator("EXTEND", SqlKind.EXTEND);
+
   /**
    * String concatenation operator, '<code>||</code>'.
    */

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/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 8e08504..89d4132 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,16 +18,23 @@ 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;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.Pair;
 
 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;
 
 import static org.apache.calcite.util.Static.RESOURCE;
 
@@ -40,6 +47,7 @@ public class IdentifierNamespace extends AbstractNamespace {
 
   private final SqlIdentifier id;
   private final SqlValidatorScope parentScope;
+  private final SqlNodeList extendList;
 
   /**
    * The underlying namespace. Often a {@link TableNamespace}.
@@ -58,22 +66,40 @@ public class IdentifierNamespace extends AbstractNamespace {
    * Creates an IdentifierNamespace.
    *
    * @param validator     Validator
-   * @param id            Identifier node
+   * @param id            Identifier node (or "identifier EXTEND column-list")
+   * @param extendList    Extension columns, or null
    * @param enclosingNode Enclosing node
    * @param parentScope   Parent scope which this namespace turns to in order to
-   *                      resolve objects
    */
   IdentifierNamespace(SqlValidatorImpl validator, SqlIdentifier id,
-      SqlNode enclosingNode, SqlValidatorScope parentScope) {
+      @Nullable SqlNodeList extendList, SqlNode enclosingNode,
+      SqlValidatorScope parentScope) {
     super(validator, enclosingNode);
     this.id = id;
+    this.extendList = extendList;
     this.parentScope = parentScope;
     assert parentScope != null;
-    assert id != null;
+  }
+
+  IdentifierNamespace(SqlValidatorImpl validator, SqlNode node,
+      SqlNode enclosingNode, SqlValidatorScope parentScope) {
+    this(validator, split(node).left, split(node).right, enclosingNode,
+        parentScope);
   }
 
   //~ Methods ----------------------------------------------------------------
 
+  protected static Pair<SqlIdentifier, SqlNodeList> split(SqlNode node) {
+    switch (node.getKind()) {
+    case EXTEND:
+      final SqlCall call = (SqlCall) node;
+      return Pair.of((SqlIdentifier) call.getOperandList().get(0),
+          (SqlNodeList) call.getOperandList().get(1));
+    default:
+      return Pair.of((SqlIdentifier) node, null);
+    }
+  }
+
   public RelDataType validateImpl() {
     resolvedNamespace = parentScope.getTableNamespace(id.names);
     if (resolvedNamespace == null) {
@@ -110,6 +136,24 @@ 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);
+      rowType = resolvedNamespace.getRowType();
+    }
+
     // Build a list of monotonic expressions.
     final ImmutableList.Builder<Pair<SqlNode, SqlMonotonicity>> builder =
         ImmutableList.builder();

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/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 a02d86a..5e97cec 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
@@ -80,6 +80,7 @@ import org.apache.calcite.util.Util;
 import org.apache.calcite.util.trace.CalciteTrace;
 
 import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -1195,9 +1196,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               selfJoinCond,
               condition);
     }
-    SqlIdentifier target =
-        (SqlIdentifier) updateCall.getTargetTable().clone(
-            SqlParserPos.ZERO);
+    SqlNode target =
+        updateCall.getTargetTable().clone(SqlParserPos.ZERO);
 
     // For the source, we need to anonymize the fields, so
     // that for a statement like UPDATE T SET I = I + 1,
@@ -1248,7 +1248,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
    * @return expression for unique identifier, or null to prevent conversion
    */
   protected SqlNode getSelfJoinExprForUpdate(
-      SqlIdentifier table,
+      SqlNode table,
       String alias) {
     return null;
   }
@@ -1707,6 +1707,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
    * @param enclosingNode Outermost node for namespace, including decorations
    *                      such as alias and sample clause
    * @param alias         Alias
+   * @param extendList    Definitions of extended columns
    * @param forceNullable Whether to force the type of namespace to be
    *                      nullable because it is in an outer join
    * @return registered node, usually the same as {@code node}
@@ -1717,6 +1718,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
       final SqlNode node,
       SqlNode enclosingNode,
       String alias,
+      SqlNodeList extendList,
       boolean forceNullable) {
     final SqlKind kind = node.getKind();
 
@@ -1783,6 +1785,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               expr,
               enclosingNode,
               alias,
+              extendList,
               forceNullable);
       if (newExpr != expr) {
         call.setOperand(0, newExpr);
@@ -1809,6 +1812,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               expr,
               enclosingNode,
               alias,
+              extendList,
               forceNullable);
       if (newExpr != expr) {
         call.setOperand(0, newExpr);
@@ -1844,6 +1848,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               left,
               left,
               null,
+              null,
               forceLeftNullable);
       if (newLeft != left) {
         join.setLeft(newLeft);
@@ -1861,6 +1866,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               right,
               right,
               null,
+              null,
               forceRightNullable);
       if (newRight != right) {
         join.setRight(newRight);
@@ -1873,9 +1879,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
       final SqlIdentifier id = (SqlIdentifier) node;
       final IdentifierNamespace newNs =
           new IdentifierNamespace(
-              this,
-              id,
-              enclosingNode,
+              this, id, extendList, enclosingNode,
               parentScope);
       registerNamespace(usingScope, alias, newNs, forceNullable);
       return newNode;
@@ -1887,6 +1891,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
           ((SqlCall) node).operand(0),
           enclosingNode,
           alias,
+          extendList,
           forceNullable);
 
     case COLLECTION_TABLE:
@@ -1899,6 +1904,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               operand,
               enclosingNode,
               alias,
+              extendList,
               forceNullable);
       if (newOperand != operand) {
         call.setOperand(0, newOperand);
@@ -1940,6 +1946,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               operand,
               enclosingNode,
               alias,
+              extendList,
               forceNullable);
       if (newOperand != operand) {
         call.setOperand(0, newOperand);
@@ -1955,6 +1962,16 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
       return newNode;
 
+    case EXTEND:
+      final SqlCall extend = (SqlCall) node;
+      return registerFrom(parentScope,
+          usingScope,
+          extend.getOperandList().get(0),
+          extend,
+          alias,
+          (SqlNodeList) extend.getOperandList().get(1),
+          forceNullable);
+
     default:
       throw Util.unexpected(kind);
     }
@@ -2091,6 +2108,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               from,
               from,
               null,
+              null,
               false);
       if (newFrom != from) {
         select.setFrom(newFrom);
@@ -3889,7 +3907,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
    * Common base class for DML statement namespaces.
    */
   public static class DmlNamespace extends IdentifierNamespace {
-    protected DmlNamespace(SqlValidatorImpl validator, SqlIdentifier id,
+    protected DmlNamespace(SqlValidatorImpl validator, SqlNode id,
         SqlNode enclosingNode, SqlValidatorScope parentScope) {
       super(validator, id, enclosingNode, parentScope);
     }
@@ -3904,8 +3922,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     public InsertNamespace(SqlValidatorImpl validator, SqlInsert node,
         SqlNode enclosingNode, SqlValidatorScope parentScope) {
       super(validator, node.getTargetTable(), enclosingNode, parentScope);
-      this.node = node;
-      assert node != null;
+      this.node = Preconditions.checkNotNull(node);
     }
 
     public SqlInsert getNode() {
@@ -3922,8 +3939,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     public UpdateNamespace(SqlValidatorImpl validator, SqlUpdate node,
         SqlNode enclosingNode, SqlValidatorScope parentScope) {
       super(validator, node.getTargetTable(), enclosingNode, parentScope);
-      this.node = node;
-      assert node != null;
+      this.node = Preconditions.checkNotNull(node);
     }
 
     public SqlUpdate getNode() {
@@ -3940,8 +3956,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     public DeleteNamespace(SqlValidatorImpl validator, SqlDelete node,
         SqlNode enclosingNode, SqlValidatorScope parentScope) {
       super(validator, node.getTargetTable(), enclosingNode, parentScope);
-      this.node = node;
-      assert node != null;
+      this.node = Preconditions.checkNotNull(node);
     }
 
     public SqlDelete getNode() {
@@ -3958,8 +3973,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     public MergeNamespace(SqlValidatorImpl validator, SqlMerge node,
         SqlNode enclosingNode, SqlValidatorScope parentScope) {
       super(validator, node.getTargetTable(), enclosingNode, parentScope);
-      this.node = node;
-      assert node != null;
+      this.node = Preconditions.checkNotNull(node);
     }
 
     public SqlMerge getNode() {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/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 3f694e4..2ae4af5 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
@@ -79,25 +79,26 @@ public class SqlValidatorUtil {
       Prepare.CatalogReader catalogReader,
       String datasetName,
       boolean[] usedDataset) {
-    if (namespace.isWrapperFor(TableNamespace.class)) {
-      TableNamespace tableNamespace =
-          namespace.unwrap(TableNamespace.class);
-      final List<String> names = tableNamespace.getTable().getQualifiedName();
-      if ((datasetName != null)
-          && (catalogReader instanceof RelOptSchemaWithSampling)) {
-        return ((RelOptSchemaWithSampling) catalogReader)
-            .getTableForMember(
-                names,
-                datasetName,
-                usedDataset);
-      } else {
-        // Schema does not support substitution. Ignore the dataset,
-        // if any.
-        return catalogReader.getTableForMember(names);
-      }
-    } else {
+    if (!namespace.isWrapperFor(TableNamespace.class)) {
       return null;
     }
+    final TableNamespace tableNamespace =
+        namespace.unwrap(TableNamespace.class);
+    final List<String> names = tableNamespace.getTable().getQualifiedName();
+    RelOptTable table;
+    if (datasetName != null
+        && catalogReader instanceof RelOptSchemaWithSampling) {
+      final RelOptSchemaWithSampling reader =
+          (RelOptSchemaWithSampling) catalogReader;
+      table = reader.getTableForMember(names, datasetName, usedDataset);
+    } else {
+      // Schema does not support substitution. Ignore the data set, if any.
+      table = catalogReader.getTableForMember(names);
+    }
+    if (!tableNamespace.extendedFields.isEmpty()) {
+      table = table.extend(tableNamespace.extendedFields);
+    }
+    return table;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/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 1ec155e..4c70157 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
@@ -17,21 +17,42 @@
 package org.apache.calcite.sql.validate;
 
 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.sql.SqlNode;
 
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import java.util.List;
+
 /** Namespace based on a table from the catalog. */
 class TableNamespace extends AbstractNamespace {
   private final SqlValidatorTable table;
+  public final ImmutableList<RelDataTypeField> extendedFields;
 
   /** Creates a TableNamespace. */
-  TableNamespace(SqlValidatorImpl validator, SqlValidatorTable table) {
+  TableNamespace(SqlValidatorImpl validator, SqlValidatorTable table,
+      ImmutableList<RelDataTypeField> fields) {
     super(validator, null);
-    this.table = table;
-    assert table != null;
+    this.table = Preconditions.checkNotNull(table);
+    this.extendedFields = fields;
+  }
+
+  public TableNamespace(SqlValidatorImpl validator, SqlValidatorTable table) {
+    this(validator, table, ImmutableList.<RelDataTypeField>of());
   }
 
   protected RelDataType validateImpl() {
-    return table.getRowType();
+    if (extendedFields.isEmpty()) {
+      return table.getRowType();
+    }
+    final RelDataTypeFactory.FieldInfoBuilder builder =
+        validator.getTypeFactory().builder();
+    builder.addAll(table.getRowType().getFieldList());
+    builder.addAll(extendedFields);
+    return builder.build();
   }
 
   public SqlNode getNode() {
@@ -42,6 +63,18 @@ class TableNamespace extends AbstractNamespace {
   @Override public SqlValidatorTable getTable() {
     return table;
   }
+
+  /** Creates a TableNamespace based on the same table as this one, but with
+   * extended fields.
+   *
+   * <p>Extended fields are "hidden" or undeclared fields that may nevertheless
+   * 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) {
+    return new TableNamespace(validator, table,
+        ImmutableList.copyOf(
+            Iterables.concat(this.extendedFields, extendedFields)));
+  }
 }
 
 // End TableNamespace.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 64b8f4c..d9a5e0f 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -792,7 +792,7 @@ public class SqlParserTest {
         + "order by a")
         .ok("SELECT `DEPTNO`\n"
             + "FROM `EMP`\n"
-            + "GROUP BY (GROUPING_SETS(`DEPTNO`, (GROUPING_SETS(`E`, `D`)),, (CUBE(`X`, `Y`)), (ROLLUP(`P`, `Q`))))\n"
+            + "GROUP BY (GROUPING_SETS(`DEPTNO`, GROUPING_SETS(`E`, `D`),, CUBE(`X`, `Y`), ROLLUP(`P`, `Q`)))\n"
             + "ORDER BY `A`");
 
     sql("select deptno from emp\n"
@@ -1911,6 +1911,41 @@ public class SqlParserTest {
         "(?s).*Encountered \"\\( \\)\" at .*");
   }
 
+  /** Test case for [CALCITE-493]. */
+  @Test public void testTableExtend() {
+    sql("select * from emp extend (x int, y varchar(10) not null)")
+        .ok("SELECT *\n"
+            + "FROM (EXTEND(`EMP`, `X`, INTEGER, `Y`, VARCHAR(10)))");
+    sql("select * from emp extend (x int, y varchar(10) not null) where true")
+        .ok("SELECT *\n"
+            + "FROM (EXTEND(`EMP`, `X`, INTEGER, `Y`, VARCHAR(10)))\n"
+            + "WHERE TRUE");
+    // with table alias
+    sql("select * from emp extend (x int, y varchar(10) not null) as t")
+        .ok("SELECT *\n"
+            + "FROM (EXTEND(`EMP`, `X`, INTEGER, `Y`, VARCHAR(10))) AS `T`");
+    // as previous, without AS
+    sql("select * from emp extend (x int, y varchar(10) not null) t")
+        .ok("SELECT *\n"
+            + "FROM (EXTEND(`EMP`, `X`, INTEGER, `Y`, VARCHAR(10))) AS `T`");
+    // with table alias and column alias list
+    sql("select * from emp extend (x int, y varchar(10) not null) as t(a, b)")
+        .ok("SELECT *\n"
+            + "FROM (EXTEND(`EMP`, `X`, INTEGER, `Y`, VARCHAR(10))) AS `T` (`A`, `B`)");
+    // as previous, without AS
+    sql("select * from emp extend (x int, y varchar(10) not null) t(a, b)")
+        .ok("SELECT *\n"
+            + "FROM (EXTEND(`EMP`, `X`, INTEGER, `Y`, VARCHAR(10))) AS `T` (`A`, `B`)");
+    // omit EXTEND
+    sql("select * from emp (x int, y varchar(10) not null) t(a, b)")
+        .ok("SELECT *\n"
+            + "FROM (EXTEND(`EMP`, `X`, INTEGER, `Y`, VARCHAR(10))) AS `T` (`A`, `B`)");
+    sql("select * from emp (x int, y varchar(10) not null) where x = y")
+        .ok("SELECT *\n"
+            + "FROM (EXTEND(`EMP`, `X`, INTEGER, `Y`, VARCHAR(10)))\n"
+            + "WHERE (`X` = `Y`)");
+  }
+
   @Test public void testExplicitTable() {
     check("table emp", "(TABLE `EMP`)");
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/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 f1bf067..bd88a5c 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
@@ -271,6 +271,8 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
           "KEYWORD(UNION)",
           "KEYWORD(FULL)",
           "KEYWORD(ORDER)",
+          "KEYWORD(()",
+          "KEYWORD(EXTEND)",
           "KEYWORD(AS)",
           "KEYWORD(USING)",
           "KEYWORD(RIGHT)",

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/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 f915e1b..f070f3d 100644
--- a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
+++ b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
@@ -19,6 +19,7 @@ package org.apache.calcite.test;
 import org.apache.calcite.linq4j.tree.Expression;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollationImpl;
@@ -46,6 +47,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
 
 import java.util.ArrayList;
@@ -68,7 +70,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
   public static final Ordering<Iterable<String>>
   CASE_INSENSITIVE_LIST_COMPARATOR =
-      Ordering.<String>from(String.CASE_INSENSITIVE_ORDER).lexicographical();
+      Ordering.from(String.CASE_INSENSITIVE_ORDER).lexicographical();
 
   //~ Instance fields --------------------------------------------------------
 
@@ -139,7 +141,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     registerSchema(salesSchema);
 
     // Register "EMP" table.
-    MockTable empTable = new MockTable(this, salesSchema, "EMP");
+    MockTable empTable = MockTable.create(this, salesSchema, "EMP");
     empTable.addColumn("EMPNO", intType);
     empTable.addColumn("ENAME", varchar20Type);
     empTable.addColumn("JOB", varchar10Type);
@@ -152,13 +154,13 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     registerTable(empTable);
 
     // Register "DEPT" table.
-    MockTable deptTable = new MockTable(this, salesSchema, "DEPT");
+    MockTable deptTable = MockTable.create(this, salesSchema, "DEPT");
     deptTable.addColumn("DEPTNO", intType);
     deptTable.addColumn("NAME", varchar10Type);
     registerTable(deptTable);
 
     // Register "BONUS" table.
-    MockTable bonusTable = new MockTable(this, salesSchema, "BONUS");
+    MockTable bonusTable = MockTable.create(this, salesSchema, "BONUS");
     bonusTable.addColumn("ENAME", varchar20Type);
     bonusTable.addColumn("JOB", varchar10Type);
     bonusTable.addColumn("SAL", intType);
@@ -166,7 +168,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     registerTable(bonusTable);
 
     // Register "SALGRADE" table.
-    MockTable salgradeTable = new MockTable(this, salesSchema, "SALGRADE");
+    MockTable salgradeTable = MockTable.create(this, salesSchema, "SALGRADE");
     salgradeTable.addColumn("GRADE", intType);
     salgradeTable.addColumn("LOSAL", intType);
     salgradeTable.addColumn("HISAL", intType);
@@ -174,7 +176,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
     // Register "EMP_ADDRESS" table
     MockTable contactAddressTable =
-        new MockTable(this, salesSchema, "EMP_ADDRESS");
+        MockTable.create(this, salesSchema, "EMP_ADDRESS");
     contactAddressTable.addColumn("EMPNO", intType);
     contactAddressTable.addColumn("HOME_ADDRESS", addressType);
     contactAddressTable.addColumn("MAILING_ADDRESS", addressType);
@@ -185,7 +187,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     registerSchema(customerSchema);
 
     // Register "CONTACT" table.
-    MockTable contactTable = new MockTable(this, customerSchema, "CONTACT");
+    MockTable contactTable = MockTable.create(this, customerSchema, "CONTACT");
     contactTable.addColumn("CONTACTNO", intType);
     contactTable.addColumn("FNAME", varchar10Type);
     contactTable.addColumn("LNAME", varchar10Type);
@@ -194,7 +196,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     registerTable(contactTable);
 
     // Register "ACCOUNT" table.
-    MockTable accountTable = new MockTable(this, customerSchema, "ACCOUNT");
+    MockTable accountTable = MockTable.create(this, customerSchema, "ACCOUNT");
     accountTable.addColumn("ACCTNO", intType);
     accountTable.addColumn("TYPE", varchar20Type);
     accountTable.addColumn("BALANCE", intType);
@@ -362,20 +364,25 @@ public class MockCatalogReader implements Prepare.CatalogReader {
    */
   public static class MockTable implements Prepare.PreparingTable {
     private final MockCatalogReader catalogReader;
-    private final List<Pair<String, RelDataType>> columnList =
-        new ArrayList<Pair<String, RelDataType>>();
+    private final List<Map.Entry<String, RelDataType>> columnList =
+        Lists.newArrayList();
     private RelDataType rowType;
     private List<RelCollation> collationList;
     private final List<String> names;
 
-    public MockTable(
-        MockCatalogReader catalogReader,
-        MockSchema schema,
-        String name) {
+    public MockTable(MockCatalogReader catalogReader, String catalogName,
+        String schemaName, String name) {
       this.catalogReader = catalogReader;
-      this.names =
-          ImmutableList.of(schema.getCatalogName(), schema.name, name);
+      this.names = ImmutableList.of(catalogName, schemaName, name);
+    }
+
+    public static MockTable create(MockCatalogReader catalogReader,
+        MockSchema schema, String name) {
+      MockTable table =
+          new MockTable(catalogReader, schema.getCatalogName(), schema.name,
+              name);
       schema.addTable(name);
+      return table;
     }
 
     public <T> T unwrap(Class<T> clazz) {
@@ -437,6 +444,15 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     public void addColumn(String name, RelDataType type) {
       columnList.add(Pair.of(name, type));
     }
+
+    public RelOptTable extend(List<RelDataTypeField> extendedFields) {
+      final MockTable table = new MockTable(catalogReader, names.get(0),
+          names.get(1), names.get(2));
+      table.columnList.addAll(columnList);
+      table.columnList.addAll(extendedFields);
+      table.onRegister(catalogReader.typeFactory);
+      return table;
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index 0147413..74235e8 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -607,7 +607,7 @@ public class RelOptRulesTest extends RelOptTestBase {
                     typeFactory.createSqlType(SqlTypeName.INTEGER);
                 for (int i = 0; i < 10; i++) {
                   String t = String.valueOf((char) ('A' + i));
-                  MockTable table = new MockTable(this, schema, t);
+                  MockTable table = MockTable.create(this, schema, t);
                   table.addColumn(t, intType);
                   registerTable(table);
                 }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 15f09ee..76e18ce 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -616,6 +616,11 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
         "${plan}");
   }
 
+  @Test public void testTableExtend() {
+    sql("select * from dept extend (x varchar(5) not null)")
+        .convertsTo("${plan}");
+  }
+
   @Test public void testExplicitTable() {
     check(
         "table emp",
@@ -678,8 +683,7 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
 
   @Test public void testCollectionTableWithCursorParam() {
     tester.withDecorrelation(false).assertConvertsTo(
-        "select * from table(dedup("
-            + "cursor(select ename from emp),"
+        "select * from table(dedup(" + "cursor(select ename from emp),"
             + " cursor(select name from dept), 'NAME'))",
         "${plan}");
   }
@@ -691,9 +695,7 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
   }
 
   @Test public void testUnnestSubquery() {
-    check(
-        "select*from unnest(multiset(select*from dept))",
-        "${plan}");
+    check("select*from unnest(multiset(select*from dept))", "${plan}");
   }
 
   @Test public void testMultisetSubquery() {
@@ -761,12 +763,10 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
 
   @Test public void testInValueListLong() {
     // Go over the default threshold of 20 to force a subquery.
-    check(
-        "select empno from emp where deptno in"
+    check("select empno from emp where deptno in"
             + " (10, 20, 30, 40, 50, 60, 70, 80, 90, 100"
             + ", 110, 120, 130, 140, 150, 160, 170, 180, 190"
-            + ", 200, 210, 220, 230)",
-        "${plan}");
+            + ", 200, 210, 220, 230)", "${plan}");
   }
 
   @Test public void testInUncorrelatedSubquery() {
@@ -839,15 +839,11 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
   }
 
   @Test public void testElement() {
-    check(
-        "select element(multiset[5]) from emp",
-        "${plan}");
+    check("select element(multiset[5]) from emp", "${plan}");
   }
 
   @Test public void testElementInValues() {
-    check(
-        "values element(multiset[5])",
-        "${plan}");
+    check("values element(multiset[5])", "${plan}");
   }
 
   @Test public void testUnionAll() {
@@ -1028,19 +1024,13 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
     rel.explain(planWriter);
     pw.flush();
     TestUtil.assertEqualsVerbose(
-        "<RelNode type=\"LogicalProject\">\n"
-            + "\t<Property name=\"EXPR$0\">\n"
-            + "\t\t+(1, 2)\t</Property>\n"
-            + "\t<Property name=\"EXPR$1\">\n"
-            + "\t\t3\t</Property>\n"
-            + "\t<Inputs>\n"
+        "<RelNode type=\"LogicalProject\">\n" + "\t<Property name=\"EXPR$0\">\n"
+            + "\t\t+(1, 2)\t</Property>\n" + "\t<Property name=\"EXPR$1\">\n"
+            + "\t\t3\t</Property>\n" + "\t<Inputs>\n"
             + "\t\t<RelNode type=\"LogicalValues\">\n"
             + "\t\t\t<Property name=\"tuples\">\n"
-            + "\t\t\t\t[{ true }]\t\t\t</Property>\n"
-            + "\t\t\t<Inputs/>\n"
-            + "\t\t</RelNode>\n"
-            + "\t</Inputs>\n"
-            + "</RelNode>\n",
+            + "\t\t\t\t[{ true }]\t\t\t</Property>\n" + "\t\t\t<Inputs/>\n"
+            + "\t\t</RelNode>\n" + "\t</Inputs>\n" + "</RelNode>\n",
         Util.toLinux(sw.toString()));
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
index 1d88316..32e2dbe 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
@@ -299,7 +299,7 @@ public abstract class SqlToRelTestBase {
     public void registerRules(RelOptPlanner planner) throws Exception {
     }
 
-      /** Mock column set. */
+    /** Mock column set. */
     protected class MockColumnSet implements RelOptTable {
       private final List<String> names;
       private final RelDataType rowType;
@@ -360,6 +360,14 @@ public abstract class SqlToRelTestBase {
       public Expression getExpression(Class clazz) {
         return null;
       }
+
+      public RelOptTable extend(List<RelDataTypeField> extendedFields) {
+        final RelDataType extendedRowType = typeFactory.builder()
+            .addAll(rowType.getFieldList())
+            .addAll(extendedFields)
+            .build();
+        return new MockColumnSet(names, extendedRowType, collationList);
+      }
     }
   }
 
@@ -382,6 +390,10 @@ public abstract class SqlToRelTestBase {
       return parent.getExpression(clazz);
     }
 
+    public RelOptTable extend(List<RelDataTypeField> extendedFields) {
+      return parent.extend(extendedFields);
+    }
+
     public List<String> getQualifiedName() {
       return parent.getQualifiedName();
     }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 1cee06d..c735639 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -6302,6 +6302,15 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
     check("SELECT DISTINCT 5, 10+5, 'string' from emp");
   }
 
+  @Test public void testTableExtend() {
+    checkResultType("select * from dept extend (x int not null)",
+        "RecordType(INTEGER NOT NULL DEPTNO, VARCHAR(10) NOT NULL NAME, INTEGER NOT NULL X) NOT NULL");
+    checkResultType("select deptno + x as z\n"
+        + "from dept extend (x int not null) as x\n"
+        + "where x > 10",
+        "RecordType(INTEGER NOT NULL Z) NOT NULL");
+  }
+
   @Test public void testExplicitTable() {
     final String empRecordType =
         "RecordType(INTEGER NOT NULL EMPNO,"

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/ecc5f4c2/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index adefd68..4958495 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -2215,4 +2215,15 @@ LogicalProject(DEPTNO=[$1], EXPR$1=[CASE($3, 1, 0)], EXPR$2=[$4], EXPR$3=[CASE($
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testTableExtend">
+        <Resource name="sql">
+            <![CDATA[select * from dept extend (x varchar(5) not null)]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], X=[$2])
+  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
 </Root>


Mime
View raw message