You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2016/01/21 23:38:55 UTC

[18/50] [abbrv] calcite git commit: [CALCITE-854] Implement UNNEST ... WITH ORDINALITY

[CALCITE-854] Implement UNNEST ... WITH ORDINALITY

Also allow UNNEST applied to ARRAY types, and lay groundwork for UNNEST with more than one argument.

Improve the error location for the "List of column aliases must have same degree as table" message.


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

Branch: refs/heads/branch-release
Commit: 4762b889699f5fb963749dca8b3106c17f44f2a9
Parents: 8bc5f85
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Aug 27 16:17:21 2015 -0600
Committer: Julian Hyde <jh...@apache.org>
Committed: Sun Jan 10 00:51:24 2016 -0800

----------------------------------------------------------------------
 core/src/main/codegen/templates/Parser.jj       |  11 +-
 .../adapter/enumerable/EnumerableUncollect.java |  81 +++++++++++++--
 .../enumerable/EnumerableUncollectRule.java     |   9 +-
 .../calcite/adapter/enumerable/PhysType.java    |   6 ++
 .../adapter/enumerable/PhysTypeImpl.java        |  22 ++++
 .../calcite/prepare/CalcitePrepareImpl.java     |   5 +-
 .../org/apache/calcite/rel/core/Uncollect.java  | 102 +++++++++++++------
 .../calcite/rel/rules/ProjectMergeRule.java     |   2 +-
 .../apache/calcite/sql/SqlUnnestOperator.java   |  35 ++++++-
 .../sql/fun/SqlArrayQueryConstructor.java       |   2 +-
 .../sql/fun/SqlArrayValueConstructor.java       |   2 +-
 .../sql/fun/SqlMultisetQueryConstructor.java    |   2 +-
 .../calcite/sql/fun/SqlStdOperatorTable.java    |  10 +-
 .../calcite/sql/validate/AbstractNamespace.java |  15 ++-
 .../calcite/sql/validate/AliasNamespace.java    |  12 ++-
 .../calcite/sql/validate/UnnestNamespace.java   |  11 +-
 .../calcite/sql2rel/SqlToRelConverter.java      |  33 +++---
 .../calcite/sql/parser/SqlParserTest.java       |  14 +++
 .../java/org/apache/calcite/test/JdbcTest.java  |  11 +-
 .../calcite/test/SqlToRelConverterTest.java     |  10 ++
 .../apache/calcite/test/SqlValidatorTest.java   |  72 ++++++++++++-
 .../calcite/test/SqlToRelConverterTest.xml      |  28 +++++
 site/_docs/reference.md                         |   2 +-
 23 files changed, 409 insertions(+), 88 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/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 fe509af..f41814a 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -76,6 +76,7 @@ import org.apache.calcite.sql.SqlSelectKeyword;
 import org.apache.calcite.sql.SqlSetOption;
 import org.apache.calcite.sql.SqlTimeLiteral;
 import org.apache.calcite.sql.SqlTimestampLiteral;
+import org.apache.calcite.sql.SqlUnnestOperator;
 import org.apache.calcite.sql.SqlUpdate;
 import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.SqlWindow;
@@ -1675,6 +1676,7 @@ SqlNode TableRef() :
     boolean isRepeatable = false;
     int repeatableSeed = 0;
     SqlNodeList columnAliasList = null;
+    SqlUnnestOperator unnestOp = SqlStdOperatorTable.UNNEST;
 }
 {
     (
@@ -1713,10 +1715,13 @@ SqlNode TableRef() :
     |
         <UNNEST> { pos = getPos(); }
         args = ParenthesizedQueryOrCommaList(ExprContext.ACCEPT_SUBQUERY)
+        [
+            <WITH> <ORDINALITY> {
+                unnestOp = SqlStdOperatorTable.UNNEST_WITH_ORDINALITY;
+            }
+        ]
         {
-            tableRef =
-                SqlStdOperatorTable.UNNEST.createCall(
-                    pos.plus(getPos()), args.toArray());
+            tableRef = unnestOp.createCall(pos.plus(getPos()), args.toArray());
         }
     |
         <TABLE> { pos = getPos(); } <LPAREN>

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollect.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollect.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollect.java
index 861a4dd..20bbaf2 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollect.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollect.java
@@ -16,30 +16,60 @@
  */
 package org.apache.calcite.adapter.enumerable;
 
-import org.apache.calcite.adapter.java.JavaTypeFactory;
 import org.apache.calcite.linq4j.tree.BlockBuilder;
 import org.apache.calcite.linq4j.tree.Expression;
 import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.ParameterExpression;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Uncollect;
-import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.ImmutableIntList;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
 
 /** Implementation of {@link org.apache.calcite.rel.core.Uncollect} in
  * {@link org.apache.calcite.adapter.enumerable.EnumerableConvention enumerable calling convention}. */
 public class EnumerableUncollect extends Uncollect implements EnumerableRel {
+  @Deprecated // to be removed before 2.0
   public EnumerableUncollect(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode child) {
-    super(cluster, traitSet, child);
+    this(cluster, traitSet, child, false);
+  }
+
+  /** Creates an EnumerableUncollect.
+   *
+   * <p>Use {@link #create} unless you know what you're doing. */
+  public EnumerableUncollect(RelOptCluster cluster, RelTraitSet traitSet,
+      RelNode child, boolean withOrdinality) {
+    super(cluster, traitSet, child, withOrdinality);
     assert getConvention() instanceof EnumerableConvention;
     assert getConvention() == child.getConvention();
   }
 
+  /**
+   * Creates an EnumerableUncollect.
+   *
+   * <p>Each field of the input relational expression must be an array or
+   * multiset.
+   *
+   * @param traitSet Trait set
+   * @param input    Input relational expression
+   * @param withOrdinality Whether output should contain an ORDINALITY column
+   */
+  public static EnumerableUncollect create(RelTraitSet traitSet, RelNode input,
+      boolean withOrdinality) {
+    final RelOptCluster cluster = input.getCluster();
+    return new EnumerableUncollect(cluster, traitSet, input, withOrdinality);
+  }
+
   @Override public EnumerableUncollect copy(RelTraitSet traitSet,
       RelNode newInput) {
-    return new EnumerableUncollect(getCluster(), traitSet, newInput);
+    return new EnumerableUncollect(getCluster(), traitSet, newInput,
+        withOrdinality);
   }
 
   public Result implement(EnumerableRelImplementor implementor, Prefer pref) {
@@ -52,19 +82,54 @@ public class EnumerableUncollect extends Uncollect implements EnumerableRel {
             getRowType(),
             JavaRowFormat.LIST);
 
-    final JavaTypeFactory typeFactory = implementor.getTypeFactory();
-    RelDataType inputRowType = child.getRowType();
-
     // final Enumerable<List<Employee>> child = <<child adapter>>;
     // return child.selectMany(LIST_TO_ENUMERABLE);
     final Expression child_ =
         builder.append(
             "child", result.block);
+    final Expression lambda;
+    if (withOrdinality) {
+      final BlockBuilder builder2 = new BlockBuilder();
+      final ParameterExpression o_ = Expressions.parameter(Modifier.FINAL,
+          result.physType.getJavaRowType(),
+          "o");
+      final Expression list_ = builder2.append("list",
+          Expressions.new_(ArrayList.class));
+      final ParameterExpression i_ = Expressions.parameter(int.class, "i");
+      final BlockBuilder builder3 = new BlockBuilder();
+      final Expression v_ =
+          builder3.append("v",
+              Expressions.call(o_, BuiltInMethod.LIST_GET.method, i_));
+      final List<Expression> expressions = new ArrayList<>();
+      final PhysType componentPhysType = result.physType.component(0);
+      final int fieldCount = componentPhysType.getRowType().getFieldCount();
+      expressions.addAll(
+          componentPhysType.accessors(v_,
+              ImmutableIntList.identity(fieldCount)));
+      expressions.add(Expressions.add(i_, Expressions.constant(1)));
+      builder3.add(
+          Expressions.statement(
+              Expressions.call(list_, BuiltInMethod.COLLECTION_ADD.method,
+                  physType.record(expressions))));
+      builder2.add(
+          Expressions.for_(
+              Expressions.declare(0, i_, Expressions.constant(0)),
+              Expressions.lessThan(i_,
+                  Expressions.call(o_, BuiltInMethod.COLLECTION_SIZE.method)),
+              Expressions.postIncrementAssign(i_),
+              builder3.toBlock()));
+      builder2.add(
+          Expressions.return_(null,
+              Expressions.call(BuiltInMethod.AS_ENUMERABLE2.method, list_)));
+      lambda = Expressions.lambda(builder2.toBlock(), o_);
+    } else {
+      lambda = Expressions.call(BuiltInMethod.LIST_TO_ENUMERABLE.method);
+    }
     builder.add(
         Expressions.return_(null,
             Expressions.call(child_,
                 BuiltInMethod.SELECT_MANY.method,
-                Expressions.call(BuiltInMethod.LIST_TO_ENUMERABLE.method))));
+                lambda)));
     return implementor.result(physType, builder.toBlock());
   }
 }

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollectRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollectRule.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollectRule.java
index 797bbc2..2687b1e 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollectRule.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollectRule.java
@@ -37,11 +37,10 @@ class EnumerableUncollectRule extends ConverterRule {
     final RelTraitSet traitSet =
         uncollect.getTraitSet().replace(EnumerableConvention.INSTANCE);
     final RelNode input = uncollect.getInput();
-    return new EnumerableUncollect(
-        rel.getCluster(),
-        traitSet,
-        convert(input,
-            input.getTraitSet().replace(EnumerableConvention.INSTANCE)));
+    final RelNode newInput = convert(input,
+        input.getTraitSet().replace(EnumerableConvention.INSTANCE));
+    return EnumerableUncollect.create(traitSet, newInput,
+        uncollect.withOrdinality);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysType.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysType.java
index dd6d2b2..0cf1dfe 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysType.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysType.java
@@ -48,6 +48,12 @@ public interface PhysType {
    * field type is {@code Object} even if the field is not nullable.</p> */
   Type getJavaFieldType(int field);
 
+  /** Returns the physical type of a field. */
+  PhysType field(int ordinal);
+
+  /** Returns the physical type of a given field's component type. */
+  PhysType component(int field);
+
   /** Returns the SQL row type. */
   RelDataType getRowType();
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java
index 918833e..2c0c491 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java
@@ -32,6 +32,7 @@ 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.runtime.Utilities;
+import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.Pair;
@@ -485,6 +486,27 @@ public class PhysTypeImpl implements PhysType {
     return format.javaFieldClass(typeFactory, rowType, index);
   }
 
+  public PhysType component(int fieldOrdinal) {
+    final RelDataTypeField field = rowType.getFieldList().get(fieldOrdinal);
+    return PhysTypeImpl.of(typeFactory,
+        toStruct(field.getType().getComponentType()), format, false);
+  }
+
+  public PhysType field(int ordinal) {
+    final RelDataTypeField field = rowType.getFieldList().get(ordinal);
+    final RelDataType type = field.getType();
+    return PhysTypeImpl.of(typeFactory, toStruct(type), format, false);
+  }
+
+  private RelDataType toStruct(RelDataType type) {
+    if (type.isStruct()) {
+      return type;
+    }
+    return typeFactory.builder()
+        .add(SqlUtil.deriveAliasFromOrdinal(0), type)
+        .build();
+  }
+
   public Expression comparer() {
     return format.comparer();
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/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 b3eb693..dc17ddd 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -114,6 +114,7 @@ import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlOperatorTable;
+import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParseException;
 import org.apache.calcite.sql.parser.SqlParser;
@@ -598,7 +599,9 @@ public class CalcitePrepareImpl implements CalcitePrepare {
   private <T> CalciteSignature<T> simplePrepare(Context context, String sql) {
     final JavaTypeFactory typeFactory = context.getTypeFactory();
     final RelDataType x =
-        typeFactory.builder().add("EXPR$0", SqlTypeName.INTEGER).build();
+        typeFactory.builder()
+            .add(SqlUtil.deriveAliasFromOrdinal(0), SqlTypeName.INTEGER)
+            .build();
     @SuppressWarnings("unchecked")
     final List<T> list = (List) ImmutableList.of(1);
     final List<String> origin = null;

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/rel/core/Uncollect.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Uncollect.java b/core/src/main/java/org/apache/calcite/rel/core/Uncollect.java
index 2e76332..2d46929 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Uncollect.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Uncollect.java
@@ -21,37 +21,46 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.SingleRel;
 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.SqlUnnestOperator;
 import org.apache.calcite.sql.SqlUtil;
+import org.apache.calcite.sql.type.SqlTypeName;
 
 import java.util.List;
 
 /**
- * Relational expression that unnests its input's sole column into a
- * relation.
+ * Relational expression that unnests its input's columns into a relation.
+ *
+ * <p>The input may have multiple columns, but each must be a multiset or
+ * array. If {@code withOrdinality}, the output contains an extra
+ * {@code ORDINALITY} column.
  *
  * <p>Like its inverse operation {@link Collect}, Uncollect is generally
  * invoked in a nested loop, driven by
  * {@link org.apache.calcite.rel.logical.LogicalCorrelate} or similar.
  */
 public class Uncollect extends SingleRel {
+  public final boolean withOrdinality;
+
   //~ Constructors -----------------------------------------------------------
 
-  /**
-   * Creates an Uncollect.
-   *
-   * <p>The row type of the child relational expression must contain precisely
-   * one column, that column must be a multiset of records.
-   *
-   * @param cluster Cluster the relational expression belongs to
-   * @param traitSet Traits
-   * @param child   Child relational expression
-   */
+  @Deprecated // to be removed before 2.0
   public Uncollect(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode child) {
-    super(cluster, traitSet, child);
+    this(cluster, traitSet, child, false);
+  }
+
+  /** Creates an Uncollect.
+   *
+   * <p>Use {@link #create} unless you know what you're doing. */
+  public Uncollect(RelOptCluster cluster, RelTraitSet traitSet, RelNode input,
+      boolean withOrdinality) {
+    super(cluster, traitSet, input);
+    this.withOrdinality = withOrdinality;
     assert deriveRowType() != null : "invalid child rowtype";
   }
 
@@ -59,11 +68,33 @@ public class Uncollect extends SingleRel {
    * Creates an Uncollect by parsing serialized output.
    */
   public Uncollect(RelInput input) {
-    this(input.getCluster(), input.getTraitSet(), input.getInput());
+    this(input.getCluster(), input.getTraitSet(), input.getInput(),
+        input.getBoolean("withOrdinality", false));
+  }
+
+  /**
+   * Creates an Uncollect.
+   *
+   * <p>Each field of the input relational expression must be an array or
+   * multiset.
+   *
+   * @param traitSet Trait set
+   * @param input    Input relational expression
+   * @param withOrdinality Whether output should contain an ORDINALITY column
+   */
+  public static Uncollect create(RelTraitSet traitSet, RelNode input,
+      boolean withOrdinality) {
+    final RelOptCluster cluster = input.getCluster();
+    return new Uncollect(cluster, traitSet, input, withOrdinality);
   }
 
   //~ Methods ----------------------------------------------------------------
 
+  @Override public RelWriter explainTerms(RelWriter pw) {
+    return super.explainTerms(pw)
+        .itemIf("withOrdinality", withOrdinality, withOrdinality);
+  }
+
   @Override public final RelNode copy(RelTraitSet traitSet,
       List<RelNode> inputs) {
     return copy(traitSet, sole(inputs));
@@ -71,35 +102,44 @@ public class Uncollect extends SingleRel {
 
   public RelNode copy(RelTraitSet traitSet, RelNode input) {
     assert traitSet.containsIfApplicable(Convention.NONE);
-    return new Uncollect(getCluster(), traitSet, input);
+    return new Uncollect(getCluster(), traitSet, input, withOrdinality);
   }
 
   protected RelDataType deriveRowType() {
-    return deriveUncollectRowType(getInput());
+    return deriveUncollectRowType(input, withOrdinality);
   }
 
   /**
    * Returns the row type returned by applying the 'UNNEST' operation to a
-   * relational expression. The relational expression must have precisely one
-   * column, whose type must be a multiset of structs. The return type is the
-   * type of that column.
+   * relational expression.
+   *
+   * <p>Each column in the relational expression must be a multiset of structs
+   * or an array. The return type is the type of that column, plus an ORDINALITY
+   * column if {@code withOrdinality}.
    */
-  public static RelDataType deriveUncollectRowType(RelNode rel) {
+  public static RelDataType deriveUncollectRowType(RelNode rel,
+      boolean withOrdinality) {
     RelDataType inputType = rel.getRowType();
     assert inputType.isStruct() : inputType + " is not a struct";
     final List<RelDataTypeField> fields = inputType.getFieldList();
-    assert 1 == fields.size() : "expected 1 field";
-    RelDataType ret = fields.get(0).getType().getComponentType();
-    assert null != ret;
-    if (!ret.isStruct()) {
-      // Element type is not a record. It may be a scalar type, say
-      // "INTEGER". Wrap it in a struct type.
-      ret =
-          rel.getCluster().getTypeFactory().builder()
-              .add(SqlUtil.deriveAliasFromOrdinal(0), ret)
-              .build();
+    final RelDataTypeFactory.FieldInfoBuilder builder =
+        rel.getCluster().getTypeFactory().builder();
+    for (RelDataTypeField field : fields) {
+      RelDataType ret = field.getType().getComponentType();
+      assert null != ret;
+      if (ret.isStruct()) {
+        builder.addAll(ret.getFieldList());
+      } else {
+        // Element type is not a record. It may be a scalar type, say
+        // "INTEGER". Wrap it in a struct type.
+        builder.add(SqlUtil.deriveAliasFromOrdinal(field.getIndex()), ret);
+      }
+    }
+    if (withOrdinality) {
+      builder.add(SqlUnnestOperator.ORDINALITY_COLUMN_NAME,
+          SqlTypeName.INTEGER);
     }
-    return ret;
+    return builder.build();
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
index 9a4e849..1a67787 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
@@ -24,9 +24,9 @@ import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.core.RelFactories.ProjectFactory;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.tools.RelBuilderFactory;
-import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.util.Permutation;
 
 import java.util.List;

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java
index 54cbb99..096f361 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java
@@ -17,17 +17,26 @@
 package org.apache.calcite.sql;
 
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.sql.type.ArraySqlType;
 import org.apache.calcite.sql.type.MultisetSqlType;
 import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.SqlTypeName;
 
 /**
  * The <code>UNNEST</code> operator.
  */
 public class SqlUnnestOperator extends SqlFunctionalOperator {
+  /** Whether {@code WITH ORDINALITY} was specified.
+   *
+   * <p>If so, the returned records include a column {@code ORDINALITY}. */
+  public final boolean withOrdinality;
+
+  public static final String ORDINALITY_COLUMN_NAME = "ORDINALITY";
+
   //~ Constructors -----------------------------------------------------------
 
-  public SqlUnnestOperator() {
+  public SqlUnnestOperator(boolean withOrdinality) {
     super(
         "UNNEST",
         SqlKind.UNNEST,
@@ -36,6 +45,7 @@ public class SqlUnnestOperator extends SqlFunctionalOperator {
         null,
         null,
         OperandTypes.SCALAR_OR_RECORD_COLLECTION);
+    this.withOrdinality = withOrdinality;
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -47,7 +57,28 @@ public class SqlUnnestOperator extends SqlFunctionalOperator {
       type = type.getFieldList().get(0).getType();
     }
     assert type instanceof ArraySqlType || type instanceof MultisetSqlType;
-    return type.getComponentType();
+    if (withOrdinality) {
+      final RelDataTypeFactory.FieldInfoBuilder builder =
+          opBinding.getTypeFactory().builder();
+      if (type.getComponentType().isStruct()) {
+        builder.addAll(type.getComponentType().getFieldList());
+      } else {
+        builder.add(SqlUtil.deriveAliasFromOrdinal(0), type.getComponentType());
+      }
+      return builder
+          .add(ORDINALITY_COLUMN_NAME, SqlTypeName.INTEGER)
+          .build();
+    } else {
+      return type.getComponentType();
+    }
+  }
+
+  @Override public void unparse(SqlWriter writer, SqlCall call, int leftPrec,
+      int rightPrec) {
+    super.unparse(writer, call, leftPrec, rightPrec);
+    if (withOrdinality) {
+      writer.keyword("WITH ORDINALITY");
+    }
   }
 
   public boolean argumentMustBeScalar(int ordinal) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayQueryConstructor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayQueryConstructor.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayQueryConstructor.java
index 2ea6b0a..640f742 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayQueryConstructor.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayQueryConstructor.java
@@ -26,7 +26,7 @@ public class SqlArrayQueryConstructor extends SqlMultisetQueryConstructor {
   //~ Constructors -----------------------------------------------------------
 
   public SqlArrayQueryConstructor() {
-    super("ARRAY", SqlKind.MAP_QUERY_CONSTRUCTOR);
+    super("ARRAY", SqlKind.ARRAY_QUERY_CONSTRUCTOR);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayValueConstructor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayValueConstructor.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayValueConstructor.java
index b51c18b..5fc55fe 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayValueConstructor.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlArrayValueConstructor.java
@@ -22,7 +22,7 @@ import org.apache.calcite.sql.SqlOperatorBinding;
 import org.apache.calcite.sql.type.SqlTypeUtil;
 
 /**
- * Definition of the SQL:2003 standard ARRAY constructor, <code>MULTISET
+ * Definition of the SQL:2003 standard ARRAY constructor, <code>ARRAY
  * [&lt;expr&gt;, ...]</code>.
  */
 public class SqlArrayValueConstructor extends SqlMultisetValueConstructor {

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetQueryConstructor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetQueryConstructor.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetQueryConstructor.java
index 76b09fe..9c58a30 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetQueryConstructor.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetQueryConstructor.java
@@ -122,7 +122,7 @@ public class SqlMultisetQueryConstructor extends SqlSpecialOperator {
       SqlCall call,
       int leftPrec,
       int rightPrec) {
-    writer.keyword("MULTISET");
+    writer.keyword(getName());
     final SqlWriter.Frame frame = writer.startList("(", ")");
     assert call.operandCount() == 1;
     call.operand(0).unparse(writer, leftPrec, rightPrec);

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/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 ec26b7c..6aa6306 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
@@ -949,8 +949,14 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
   /**
    * The <code>UNNEST</code> operator.
    */
-  public static final SqlSpecialOperator UNNEST =
-      new SqlUnnestOperator();
+  public static final SqlUnnestOperator UNNEST =
+      new SqlUnnestOperator(false);
+
+  /**
+   * The <code>UNNEST WITH ORDINALITY</code> operator.
+   */
+  public static final SqlUnnestOperator UNNEST_WITH_ORDINALITY =
+      new SqlUnnestOperator(true);
 
   /**
    * The <code>LATERAL</code> operator.

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java
index 9bbb11f..c0c1971 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java
@@ -202,10 +202,7 @@ abstract class AbstractNamespace implements SqlValidatorNamespace {
       return type;
     }
     final RelDataTypeFactory typeFactory = validator.getTypeFactory();
-    final RelDataType structType =
-        typeFactory.builder()
-            .add(validator.deriveAlias(getNode(), 0), componentType)
-            .build();
+    final RelDataType structType = toStruct(componentType, getNode());
     final RelDataType collectionType;
     switch (type.getSqlTypeName()) {
     case ARRAY:
@@ -220,6 +217,16 @@ abstract class AbstractNamespace implements SqlValidatorNamespace {
     return typeFactory.createTypeWithNullability(collectionType,
         type.isNullable());
   }
+
+  /** Converts a type to a struct if it is not already. */
+  protected RelDataType toStruct(RelDataType type, SqlNode unnest) {
+    if (type.isStruct()) {
+      return type;
+    }
+    return validator.getTypeFactory().builder()
+        .add(validator.deriveAlias(unnest, 0), type)
+        .build();
+  }
 }
 
 // End AbstractNamespace.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java
index c109dcf..d425ceb 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java
@@ -21,7 +21,9 @@ import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.Util;
 
 import java.util.ArrayList;
@@ -67,7 +69,8 @@ public class AliasNamespace extends AbstractNamespace {
     final SqlValidatorNamespace childNs =
         validator.getNamespace(operands.get(0));
     final RelDataType rowType = childNs.getRowTypeSansSystemColumns();
-    for (final SqlNode operand : Util.skip(operands, 2)) {
+    final List<SqlNode> columnNames = Util.skip(operands, 2);
+    for (final SqlNode operand : columnNames) {
       String name = ((SqlIdentifier) operand).getSimple();
       if (nameList.contains(name)) {
         throw validator.newValidationError(operand,
@@ -76,8 +79,11 @@ public class AliasNamespace extends AbstractNamespace {
       nameList.add(name);
     }
     if (nameList.size() != rowType.getFieldCount()) {
-      // Position error at first name in list.
-      throw validator.newValidationError(operands.get(2),
+      // Position error over all column names
+      final SqlNode node = operands.size() == 3
+          ? operands.get(2)
+          : new SqlNodeList(columnNames, SqlParserPos.sum(columnNames));
+      throw validator.newValidationError(node,
           RESOURCE.aliasListDegree(rowType.getFieldCount(), getString(rowType),
               nameList.size()));
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/core/src/main/java/org/apache/calcite/sql/validate/UnnestNamespace.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/UnnestNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/UnnestNamespace.java
index f5829f3..68c1108 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/UnnestNamespace.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/UnnestNamespace.java
@@ -19,7 +19,7 @@ package org.apache.calcite.sql.validate;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.SqlUnnestOperator;
 import org.apache.calcite.sql.type.MultisetSqlType;
 
 /**
@@ -40,7 +40,7 @@ class UnnestNamespace extends AbstractNamespace {
       SqlNode enclosingNode) {
     super(validator, enclosingNode);
     assert scope != null;
-    assert unnest.getOperator() == SqlStdOperatorTable.UNNEST;
+    assert unnest.getOperator() instanceof SqlUnnestOperator;
     this.unnest = unnest;
     this.scope = scope;
   }
@@ -53,12 +53,7 @@ class UnnestNamespace extends AbstractNamespace {
     RelDataType type =
         unnest.getOperator().validateOperands(validator, scope, unnest);
 
-    if (type.isStruct()) {
-      return type;
-    }
-    return validator.getTypeFactory().builder()
-        .add(validator.deriveAlias(unnest, 0), type)
-        .build();
+    return toStruct(type, unnest);
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/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 e4e5edf..fe40346 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -112,6 +112,7 @@ import org.apache.calcite.sql.SqlSampleSpec;
 import org.apache.calcite.sql.SqlSelect;
 import org.apache.calcite.sql.SqlSelectKeyword;
 import org.apache.calcite.sql.SqlSetOperator;
+import org.apache.calcite.sql.SqlUnnestOperator;
 import org.apache.calcite.sql.SqlUpdate;
 import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.SqlValuesOperator;
@@ -1013,6 +1014,7 @@ public class SqlToRelConverter {
 
     case MULTISET_QUERY_CONSTRUCTOR:
     case MULTISET_VALUE_CONSTRUCTOR:
+    case ARRAY_QUERY_CONSTRUCTOR:
       rel = convertMultisets(ImmutableList.of(subQuery.node), bb);
       subQuery.expr = bb.register(rel, JoinRelType.INNER);
       return;
@@ -1685,6 +1687,7 @@ public class SqlToRelConverter {
     case SELECT:
     case MULTISET_QUERY_CONSTRUCTOR:
     case MULTISET_VALUE_CONSTRUCTOR:
+    case ARRAY_QUERY_CONSTRUCTOR:
     case CURSOR:
     case SCALAR_QUERY:
       if (!registerOnlyScalarSubqueries
@@ -1886,7 +1889,7 @@ public class SqlToRelConverter {
   protected void convertFrom(
       Blackboard bb,
       SqlNode from) {
-    SqlCall call;
+    final SqlCall call;
     final SqlNode[] operands;
     switch (from.getKind()) {
     case AS:
@@ -2026,7 +2029,9 @@ public class SqlToRelConverter {
       return;
 
     case UNNEST:
-      final SqlNode node = ((SqlCall) from).operand(0);
+      call = (SqlCall) from;
+      final SqlNode node = call.operand(0);
+      final SqlUnnestOperator operator = (SqlUnnestOperator) call.getOperator();
       replaceSubqueries(bb, node, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN);
       final RelNode childRel =
           RelOptUtil.createProject(
@@ -2037,7 +2042,7 @@ public class SqlToRelConverter {
 
       Uncollect uncollect =
           new Uncollect(cluster, cluster.traitSetOf(Convention.NONE),
-              childRel);
+              childRel, operator.withOrdinality);
       bb.setRoot(uncollect, true);
       return;
 
@@ -2046,8 +2051,8 @@ public class SqlToRelConverter {
 
       // Dig out real call; TABLE() wrapper is just syntactic.
       assert call.getOperandList().size() == 1;
-      call = call.operand(0);
-      convertCollectionTable(bb, call);
+      final SqlCall call2 = call.operand(0);
+      convertCollectionTable(bb, call2);
       return;
 
     default:
@@ -3348,14 +3353,10 @@ public class SqlToRelConverter {
       }
 
       final SqlCall call = (SqlCall) operand;
-      final SqlOperator op = call.getOperator();
-      if ((op != SqlStdOperatorTable.MULTISET_VALUE)
-          && (op != SqlStdOperatorTable.MULTISET_QUERY)) {
-        lastList.add(operand);
-        continue;
-      }
       final RelNode input;
-      if (op == SqlStdOperatorTable.MULTISET_VALUE) {
+      switch (call.getKind()) {
+      case MULTISET_VALUE_CONSTRUCTOR:
+      case ARRAY_VALUE_CONSTRUCTOR:
         final SqlNodeList list =
             new SqlNodeList(call.getOperandList(), call.getParserPosition());
         CollectNamespace nss =
@@ -3376,9 +3377,15 @@ public class SqlToRelConverter {
             list,
             multisetType.getComponentType());
         input = convertQueryOrInList(usedBb, list, null);
-      } else {
+        break;
+      case MULTISET_QUERY_CONSTRUCTOR:
+      case ARRAY_QUERY_CONSTRUCTOR:
         final RelRoot root = convertQuery(call.operand(0), false, true);
         input = root.rel;
+        break;
+      default:
+        lastList.add(operand);
+        continue;
       }
 
       if (lastList.size() > 0) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/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 c1c8f3b..31dbd58 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
@@ -5541,6 +5541,20 @@ public class SqlParserTest {
         "(?s)Encountered \"unnest\" at.*");
   }
 
+  @Test public void testUnnestWithOrdinality() {
+    sql("select * from unnest(x) with ordinality")
+        .ok("SELECT *\n"
+            + "FROM (UNNEST(`X`) WITH ORDINALITY)");
+    sql("select*from unnest(x) with ordinality AS T")
+        .ok("SELECT *\n"
+            + "FROM (UNNEST(`X`) WITH ORDINALITY) AS `T`");
+    sql("select*from unnest(x) with ordinality AS T(c, o)")
+        .ok("SELECT *\n"
+            + "FROM (UNNEST(`X`) WITH ORDINALITY) AS `T` (`C`, `O`)");
+    sql("select*from unnest(x) as T ^with^ ordinality")
+        .fails("(?s)Encountered \"with\" at .*");
+  }
+
   @Test public void testParensInFrom() {
     // UNNEST may not occur within parentheses.
     // FIXME should fail at "unnest"

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/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 3ee40e8..b64cba4 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -2176,11 +2176,18 @@ public class JdbcTest {
         .returnsUnordered("A=[{10}, {20}, {10}, {10}]");
   }
 
-  @Ignore("unnest does not apply to array. should it?")
   @Test public void testUnnestArray() {
     CalciteAssert.that()
         .query("select*from unnest(array[1,2])")
-        .returnsUnordered("xx");
+        .returnsUnordered("EXPR$0=1",
+            "EXPR$0=2");
+  }
+
+  @Test public void testUnnestArrayWithOrdinality() {
+    CalciteAssert.that()
+        .query("select*from unnest(array[10,20]) with ordinality as t(i, o)")
+        .returnsUnordered("I=10; O=1",
+            "I=20; O=2");
   }
 
   @Test public void testUnnestMultiset() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/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 1c67bc8..c24e6af 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -734,6 +734,16 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
     check("select*from unnest(multiset(select*from dept))", "${plan}");
   }
 
+  @Test public void testUnnestArray() {
+    sql("select*from unnest(array(select*from dept))")
+        .convertsTo("${plan}");
+  }
+
+  @Test public void testUnnestWithOrdinality() {
+    sql("select*from unnest(array(select*from dept)) with ordinality")
+        .convertsTo("${plan}");
+  }
+
   @Test public void testMultisetSubquery() {
     check(
         "select multiset(select deptno from dept) from (values(true))",

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/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 64d1c56..2f4f60e 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -4606,7 +4606,7 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
     checkFails(
         "select d.^deptno^ from dept as d(a, b)",
         "(?s).*Column 'DEPTNO' not found in table 'D'.*");
-    checkFails("select 1 from dept as d(^a^, b, c)",
+    checkFails("select 1 from dept as d(^a, b, c^)",
         "(?s).*List of column aliases must have same degree as table; "
             + "table has 2 columns \\('DEPTNO', 'NAME'\\), "
             + "whereas alias list has 3 columns.*");
@@ -6501,6 +6501,76 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         "Column 'C1' not found in any table");
   }
 
+  @Test public void testUnnestArray() {
+    checkColumnType("select*from unnest(array[1])", "INTEGER NOT NULL");
+    checkColumnType("select*from unnest(array[1, 2])", "INTEGER NOT NULL");
+    checkColumnType(
+        "select*from unnest(array[321.3, 2.33])",
+        "DECIMAL(5, 2) NOT NULL");
+    checkColumnType(
+        "select*from unnest(array[321.3, 4.23e0])",
+        "DOUBLE NOT NULL");
+    checkColumnType(
+        "select*from unnest(array[43.2e1, cast(null as decimal(4,2))])",
+        "DOUBLE");
+    checkColumnType(
+        "select*from unnest(array[1, 2.3, 1])",
+        "DECIMAL(11, 1) NOT NULL");
+    checkColumnType(
+        "select*from unnest(array['1','22','333'])",
+        "CHAR(3) NOT NULL");
+    checkColumnType(
+        "select*from unnest(array['1','22','333','22'])",
+        "CHAR(3) NOT NULL");
+    checkFails(
+        "select*from ^unnest(1)^",
+        "(?s).*Cannot apply 'UNNEST' to arguments of type 'UNNEST.<INTEGER>.'.*");
+    check("select*from unnest(array(select*from dept))");
+    check("select c from unnest(array(select deptno from dept)) as t(c)");
+    checkFails("select c from unnest(array(select * from dept)) as t(^c^)",
+        "List of column aliases must have same degree as table; table has 2 columns \\('DEPTNO', 'NAME'\\), whereas alias list has 1 columns");
+    checkFails(
+        "select ^c1^ from unnest(array(select name from dept)) as t(c)",
+        "Column 'C1' not found in any table");
+  }
+
+  @Test public void testUnnestWithOrdinality() {
+    checkResultType("select*from unnest(array[1, 2]) with ordinality",
+        "RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL ORDINALITY) NOT NULL");
+    checkResultType(
+        "select*from unnest(array[43.2e1, cast(null as decimal(4,2))]) with ordinality",
+        "RecordType(DOUBLE EXPR$0, INTEGER NOT NULL ORDINALITY) NOT NULL");
+    checkFails(
+        "select*from ^unnest(1) with ordinality^",
+        "(?s).*Cannot apply 'UNNEST' to arguments of type 'UNNEST.<INTEGER>.'.*");
+    check("select deptno\n"
+        + "from unnest(array(select*from dept)) with ordinality\n"
+        + "where ordinality < 5");
+    checkFails("select c from unnest(\n"
+        + "  array(select deptno from dept)) with ordinality as t(^c^)",
+        "List of column aliases must have same degree as table; table has 2 "
+        + "columns \\('DEPTNO', 'ORDINALITY'\\), "
+        + "whereas alias list has 1 columns");
+    check("select c from unnest(\n"
+        + "  array(select deptno from dept)) with ordinality as t(c, d)");
+    checkFails("select c from unnest(\n"
+        + "  array(select deptno from dept)) with ordinality as t(^c, d, e^)",
+        "List of column aliases must have same degree as table; table has 2 "
+        + "columns \\('DEPTNO', 'ORDINALITY'\\), "
+        + "whereas alias list has 3 columns");
+    checkFails("select c\n"
+        + "from unnest(array(select * from dept)) with ordinality as t(^c, d, e, f^)",
+        "List of column aliases must have same degree as table; table has 3 "
+        + "columns \\('DEPTNO', 'NAME', 'ORDINALITY'\\), "
+        + "whereas alias list has 4 columns");
+    checkFails(
+        "select ^name^ from unnest(array(select name from dept)) with ordinality as t(c, o)",
+        "Column 'NAME' not found in any table");
+    checkFails(
+        "select ^ordinality^ from unnest(array(select name from dept)) with ordinality as t(c, o)",
+        "Column 'ORDINALITY' not found in any table");
+  }
+
   @Test public void testCorrelationJoin() {
     check("select *,"
         + "         multiset(select * from emp where deptno=dept.deptno) "

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/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 c46d493..553f275 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -145,6 +145,34 @@ LogicalProject(DEPTNO=[$0], NAME=[$1])
             <![CDATA[select*from unnest(multiset(select*from dept))]]>
         </Resource>
     </TestCase>
+    <TestCase name="testUnnestArray">
+        <Resource name="sql">
+            <![CDATA[select*from unnest(array(select*from dept))]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  Uncollect
+    Collect(field=[EXPR$0])
+      LogicalProject(DEPTNO=[$0], NAME=[$1])
+        LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testUnnestWithOrdinality">
+        <Resource name="sql">
+            <![CDATA[select*from unnest(array(select*from dept)) with ordinality]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], ORDINALITY=[$2])
+  Uncollect(withOrdinality=[true])
+    Collect(field=[EXPR$0])
+      LogicalProject(DEPTNO=[$0], NAME=[$1])
+        LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
     <TestCase name="testMultisetSubquery">
         <Resource name="plan">
             <![CDATA[

http://git-wip-us.apache.org/repos/asf/calcite/blob/4762b889/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 7f267bd..dbf2974 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -128,7 +128,7 @@ tablePrimary:
       [ TABLE ] [ [ catalogName . ] schemaName . ] tableName
   |   '(' query ')'
   |   values
-  |   UNNEST '(' expression ')'
+  |   UNNEST '(' expression ')' [ WITH ORDINALITY ]
   |   TABLE '(' [ SPECIFIC ] functionName '(' expression [, expression ]* ')' ')'
 
 values: