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/05/31 17:28:51 UTC

[2/2] calcite git commit: [CALCITE-1250] UNNEST applied to MAP data type (Johannes Schulte)

[CALCITE-1250] UNNEST applied to MAP data type (Johannes Schulte)

Implicit column names are KEY and VALUE.

Make UNNEST work with multiple arguments of different types.

Close apache/calcite#235


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

Branch: refs/heads/master
Commit: d757201f155bd0c900725b3ce2776899668fce4d
Parents: 2f937c1
Author: baunz <jo...@gmail.com>
Authored: Thu May 19 23:02:34 2016 +0200
Committer: Julian Hyde <jh...@apache.org>
Committed: Tue May 31 00:01:23 2016 -0700

----------------------------------------------------------------------
 .../adapter/enumerable/EnumerableUncollect.java | 27 +++++--
 .../org/apache/calcite/rel/core/Uncollect.java  | 20 +++--
 .../apache/calcite/runtime/SqlFunctions.java    | 80 ++++++++++++++------
 .../apache/calcite/sql/SqlUnnestOperator.java   | 26 +++++--
 .../apache/calcite/sql/type/OperandTypes.java   |  3 +
 .../org/apache/calcite/util/BuiltInMethod.java  | 36 ++++-----
 .../java/org/apache/calcite/test/JdbcTest.java  | 45 +++++++++++
 .../apache/calcite/test/SqlValidatorTest.java   |  5 ++
 8 files changed, 184 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/d757201f/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 0dfa57f..def8e24 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
@@ -25,6 +25,8 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Uncollect;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.runtime.SqlFunctions.FlatProductInputType;
+import org.apache.calcite.sql.type.MapSqlType;
 import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.IntList;
 
@@ -87,20 +89,33 @@ public class EnumerableUncollect extends Uncollect implements EnumerableRel {
     final Expression child_ =
         builder.append(
             "child", result.block);
+
     final List<Integer> fieldCounts = new ArrayList<>();
+    final List<FlatProductInputType> inputTypes = new ArrayList<>();
+
     for (RelDataTypeField field : child.getRowType().getFieldList()) {
       final RelDataType type = field.getType();
-      final RelDataType elementType = type.getComponentType();
-      if (elementType.isStruct()) {
-        fieldCounts.add(elementType.getFieldCount());
+      if (type instanceof MapSqlType) {
+        fieldCounts.add(2);
+        inputTypes.add(FlatProductInputType.MAP);
       } else {
-        fieldCounts.add(-1);
+        final RelDataType elementType = type.getComponentType();
+        if (elementType.isStruct()) {
+          fieldCounts.add(elementType.getFieldCount());
+          inputTypes.add(FlatProductInputType.LIST);
+        } else {
+          fieldCounts.add(-1);
+          inputTypes.add(FlatProductInputType.SCALAR);
+        }
       }
     }
+
     final Expression lambda =
         Expressions.call(BuiltInMethod.FLAT_PRODUCT.method,
             Expressions.constant(IntList.toArray(fieldCounts)),
-            Expressions.constant(withOrdinality));
+            Expressions.constant(withOrdinality),
+            Expressions.constant(
+                inputTypes.toArray(new FlatProductInputType[inputTypes.size()])));
     builder.add(
         Expressions.return_(null,
             Expressions.call(child_,
@@ -108,6 +123,8 @@ public class EnumerableUncollect extends Uncollect implements EnumerableRel {
                 lambda)));
     return implementor.result(physType, builder.toBlock());
   }
+
 }
 
+
 // End EnumerableUncollect.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d757201f/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 2d46929..d391f93 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
@@ -28,6 +28,7 @@ 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.MapSqlType;
 import org.apache.calcite.sql.type.SqlTypeName;
 
 import java.util.List;
@@ -125,14 +126,19 @@ public class Uncollect extends SingleRel {
     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());
+      if (field.getType() instanceof MapSqlType) {
+        builder.add(SqlUnnestOperator.MAP_KEY_COLUMN_NAME, field.getType().getKeyType());
+        builder.add(SqlUnnestOperator.MAP_VALUE_COLUMN_NAME, field.getType().getValueType());
       } 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);
+        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) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/d757201f/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 7e2d84d..e8da557 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -29,6 +29,7 @@ import org.apache.calcite.linq4j.function.Deterministic;
 import org.apache.calcite.linq4j.function.Function1;
 import org.apache.calcite.linq4j.function.NonDeterministic;
 import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.runtime.FlatLists.ComparableList;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -42,6 +43,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.TimeZone;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.regex.Pattern;
@@ -1489,52 +1491,78 @@ public class SqlFunctions {
     }
   }
 
-  public static <E extends Comparable>
-  Function1<Object, Enumerable<FlatLists.ComparableList<E>>>
-  flatProduct(final int[] fieldCounts, final boolean withOrdinality) {
+  public static Function1<Object, Enumerable<ComparableList<Comparable>>>
+  flatProduct(final int[] fieldCounts, final boolean withOrdinality,
+      final FlatProductInputType[] inputTypes) {
     if (fieldCounts.length == 1) {
-      if (!withOrdinality) {
+      if (!withOrdinality && inputTypes[0] == FlatProductInputType.SCALAR) {
         //noinspection unchecked
         return (Function1) LIST_AS_ENUMERABLE;
       } else {
-        return new Function1<Object, Enumerable<FlatLists.ComparableList<E>>>() {
-          public Enumerable<FlatLists.ComparableList<E>> apply(Object row) {
-            return p2(new Object[] {row}, fieldCounts, true);
+        return new Function1<Object, Enumerable<ComparableList<Comparable>>>() {
+          public Enumerable<ComparableList<Comparable>> apply(Object row) {
+            return p2(new Object[] { row }, fieldCounts, withOrdinality,
+                  inputTypes);
           }
         };
       }
     }
-    return new Function1<Object, Enumerable<FlatLists.ComparableList<E>>>() {
-      public Enumerable<FlatLists.ComparableList<E>> apply(Object lists) {
-        return p2((Object[]) lists, fieldCounts, withOrdinality);
+    return new Function1<Object, Enumerable<FlatLists.ComparableList<Comparable>>>() {
+      public Enumerable<FlatLists.ComparableList<Comparable>> apply(Object lists) {
+        return p2((Object[]) lists, fieldCounts, withOrdinality,
+            inputTypes);
       }
     };
   }
 
-  private static <E extends Comparable>
-  Enumerable<FlatLists.ComparableList<E>> p2(Object[] lists, int[] fieldCounts,
-      boolean withOrdinality) {
-    final List<Enumerator<List<E>>> enumerators = new ArrayList<>();
+  private static Enumerable<FlatLists.ComparableList<Comparable>>
+  p2(Object[] lists, int[] fieldCounts, boolean withOrdinality,
+      FlatProductInputType[] inputTypes) {
+    final List<Enumerator<List<Comparable>>> enumerators = new ArrayList<>();
     int totalFieldCount = 0;
     for (int i = 0; i < lists.length; i++) {
       int fieldCount = fieldCounts[i];
-      if (fieldCount < 0) {
-        ++totalFieldCount;
-        @SuppressWarnings("unchecked")
-        List<E> list = (List<E>) lists[i];
+      FlatProductInputType inputType = inputTypes[i];
+      Object inputObject = lists[i];
+      switch (inputType) {
+      case SCALAR:
+        @SuppressWarnings("unchecked") List<Comparable> list =
+            (List<Comparable>) inputObject;
         enumerators.add(
             Linq4j.transform(
                 Linq4j.enumerator(list),
-                new Function1<E, List<E>>() {
-                  public List<E> apply(E a0) {
+                new Function1<Comparable, List<Comparable>>() {
+                  public List<Comparable> apply(Comparable a0) {
                     return FlatLists.of(a0);
                   }
                 }));
+        break;
+      case LIST:
+        @SuppressWarnings("unchecked") List<List<Comparable>> listList =
+            (List<List<Comparable>>) inputObject;
+        enumerators.add(Linq4j.enumerator(listList));
+        break;
+      case MAP:
+        @SuppressWarnings("unchecked") Map<Comparable, Comparable> map =
+            (Map<Comparable, Comparable>) inputObject;
+        Enumerator<Entry<Comparable, Comparable>> enumerator =
+            Linq4j.enumerator(map.entrySet());
+
+        Enumerator<List<Comparable>> transformed = Linq4j.transform(enumerator,
+          new Function1<Entry<Comparable, Comparable>, List<Comparable>>() {
+            public List<Comparable> apply(Entry<Comparable, Comparable> entry) {
+              return FlatLists.<Comparable>of(entry.getKey(), entry.getValue());
+            }
+          });
+        enumerators.add(transformed);
+        break;
+      default:
+        break;
+      }
+      if (fieldCount < 0) {
+        ++totalFieldCount;
       } else {
         totalFieldCount += fieldCount;
-        @SuppressWarnings("unchecked")
-        List<List<E>> list = (List<List<E>>) lists[i];
-        enumerators.add(Linq4j.enumerator(list));
       }
     }
     if (withOrdinality) {
@@ -1592,6 +1620,12 @@ public class SqlFunctions {
       return FlatLists.ofComparable(list);
     }
   }
+
+  /** Type of argument passed into {@link #flatProduct}. */
+  public enum FlatProductInputType {
+    SCALAR, LIST, MAP
+  }
+
 }
 
 // End SqlFunctions.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d757201f/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 b3d0a1b..7d32fe6 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java
@@ -16,15 +16,18 @@
  */
 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.MapSqlType;
 import org.apache.calcite.sql.type.MultisetSqlType;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.SqlOperandCountRanges;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.Util;
 
+
 /**
  * The <code>UNNEST</code> operator.
  */
@@ -36,6 +39,10 @@ public class SqlUnnestOperator extends SqlFunctionalOperator {
 
   public static final String ORDINALITY_COLUMN_NAME = "ORDINALITY";
 
+  public static final String MAP_KEY_COLUMN_NAME = "KEY";
+
+  public static final String MAP_VALUE_COLUMN_NAME = "VALUE";
+
   //~ Constructors -----------------------------------------------------------
 
   public SqlUnnestOperator(boolean withOrdinality) {
@@ -47,7 +54,7 @@ public class SqlUnnestOperator extends SqlFunctionalOperator {
         null,
         null,
         OperandTypes.repeat(SqlOperandCountRanges.from(1),
-            OperandTypes.SCALAR_OR_RECORD_COLLECTION));
+            OperandTypes.SCALAR_OR_RECORD_COLLECTION_OR_MAP));
     this.withOrdinality = withOrdinality;
   }
 
@@ -61,12 +68,18 @@ public class SqlUnnestOperator extends SqlFunctionalOperator {
       if (type.isStruct()) {
         type = type.getFieldList().get(0).getType();
       }
-      assert type instanceof ArraySqlType || type instanceof MultisetSqlType;
-      if (type.getComponentType().isStruct()) {
-        builder.addAll(type.getComponentType().getFieldList());
+      assert type instanceof ArraySqlType || type instanceof MultisetSqlType
+          || type instanceof MapSqlType;
+      if (type instanceof MapSqlType) {
+        builder.add(MAP_KEY_COLUMN_NAME, type.getKeyType());
+        builder.add(MAP_VALUE_COLUMN_NAME, type.getValueType());
       } else {
-        builder.add(SqlUtil.deriveAliasFromOrdinal(operand),
-            type.getComponentType());
+        if (type.getComponentType().isStruct()) {
+          builder.addAll(type.getComponentType().getFieldList());
+        } else {
+          builder.add(SqlUtil.deriveAliasFromOrdinal(operand),
+              type.getComponentType());
+        }
       }
     }
     if (withOrdinality) {
@@ -86,6 +99,7 @@ public class SqlUnnestOperator extends SqlFunctionalOperator {
   public boolean argumentMustBeScalar(int ordinal) {
     return false;
   }
+
 }
 
 // End SqlUnnestOperator.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d757201f/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
index 1d43ba0..f101ee9 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
@@ -529,6 +529,9 @@ public abstract class OperandTypes {
   public static final SqlSingleOperandTypeChecker SCALAR_OR_RECORD_COLLECTION =
       OperandTypes.or(COLLECTION, RECORD_COLLECTION);
 
+  public static final SqlSingleOperandTypeChecker SCALAR_OR_RECORD_COLLECTION_OR_MAP =
+      OperandTypes.or(COLLECTION_OR_MAP, RECORD_COLLECTION);
+
   public static final SqlOperandTypeChecker MULTISET_MULTISET =
       new MultisetOperandTypeChecker();
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/d757201f/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 53f7880..6a17843 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -43,9 +43,24 @@ import org.apache.calcite.linq4j.function.Predicate2;
 import org.apache.calcite.linq4j.tree.FunctionExpression;
 import org.apache.calcite.linq4j.tree.Primitive;
 import org.apache.calcite.linq4j.tree.Types;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.Collation;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.ColumnOrigin;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.ColumnUniqueness;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.CumulativeCost;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.DistinctRowCount;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.Distribution;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.ExplainVisibility;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.MaxRowCount;
 import org.apache.calcite.rel.metadata.BuiltInMetadata.Memory;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.NonCumulativeCost;
 import org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.PercentageOriginalRows;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.PopulationSize;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.Predicates;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.RowCount;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.Selectivity;
 import org.apache.calcite.rel.metadata.BuiltInMetadata.Size;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.UniqueKeys;
 import org.apache.calcite.rel.metadata.Metadata;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.runtime.ArrayBindable;
@@ -56,6 +71,7 @@ import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.ResultSetEnumerable;
 import org.apache.calcite.runtime.SortedMultiMap;
 import org.apache.calcite.runtime.SqlFunctions;
+import org.apache.calcite.runtime.SqlFunctions.FlatProductInputType;
 import org.apache.calcite.runtime.Utilities;
 import org.apache.calcite.schema.FilterableTable;
 import org.apache.calcite.schema.ModifiableTable;
@@ -85,23 +101,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.TimeZone;
-import javax.sql.DataSource;
 
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.Collation;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.ColumnOrigin;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.ColumnUniqueness;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.CumulativeCost;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.DistinctRowCount;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.Distribution;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.ExplainVisibility;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.MaxRowCount;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.NonCumulativeCost;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.PercentageOriginalRows;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.PopulationSize;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.Predicates;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.RowCount;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.Selectivity;
-import static org.apache.calcite.rel.metadata.BuiltInMetadata.UniqueKeys;
+import javax.sql.DataSource;
 
 /**
  * Built-in methods.
@@ -177,7 +178,8 @@ public enum BuiltInMethod {
   FUNCTION1_APPLY(Function1.class, "apply", Object.class),
   ARRAYS_AS_LIST(Arrays.class, "asList", Object[].class),
   ARRAY(SqlFunctions.class, "array", Object[].class),
-  FLAT_PRODUCT(SqlFunctions.class, "flatProduct", int[].class, boolean.class),
+  FLAT_PRODUCT(SqlFunctions.class, "flatProduct", int[].class, boolean.class,
+      FlatProductInputType[].class),
   LIST_N(FlatLists.class, "copyOf", Comparable[].class),
   LIST2(FlatLists.class, "of", Object.class, Object.class),
   LIST3(FlatLists.class, "of", Object.class, Object.class, Object.class),

http://git-wip-us.apache.org/repos/asf/calcite/blob/d757201f/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 06a6854..bab74d6 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -52,6 +52,7 @@ import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rel.logical.LogicalTableModify;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.runtime.SqlFunctions;
 import org.apache.calcite.schema.ModifiableTable;
@@ -2286,6 +2287,50 @@ public class JdbcTest {
             "name=Sales; EI=150; D=10; N=Sebastian; S=7000.0; C=null; I=2; O=5");
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-1250">[CALCITE-1250]
+   * UNNEST applied to MAP data type</a>. */
+  @Test public void testUnnestItemsInMap() throws SQLException {
+    Connection connection = DriverManager.getConnection("jdbc:calcite:");
+    final String sql = "select * from unnest(MAP['a', 1, 'b', 2]) as um(k, v)";
+    ResultSet resultSet = connection.createStatement().executeQuery(sql);
+    final String expected = "K=a; V=1\n"
+        + "K=b; V=2\n";
+    assertThat(CalciteAssert.toString(resultSet), is(expected));
+    connection.close();
+  }
+
+  @Test public void testUnnestItemsInMapWithOrdinality() throws SQLException {
+    Connection connection = DriverManager.getConnection("jdbc:calcite:");
+    final String sql = "select *\n"
+        + "from unnest(MAP['a', 1, 'b', 2]) with ordinality as um(k, v, i)";
+    ResultSet resultSet = connection.createStatement().executeQuery(sql);
+    final String expected = "K=a; V=1; I=1\n"
+        + "K=b; V=2; I=2\n";
+    assertThat(CalciteAssert.toString(resultSet), is(expected));
+    connection.close();
+  }
+
+  @Test public void testUnnestItemsInMapWithNoAliasAndAdditionalArgument()
+      throws SQLException {
+    Connection connection = DriverManager.getConnection("jdbc:calcite:");
+    final String sql =
+        "select * from unnest(MAP['a', 1, 'b', 2], array[5, 6, 7])";
+    ResultSet resultSet = connection.createStatement().executeQuery(sql);
+
+    List<String> map = FlatLists.of("KEY=a; VALUE=1", "KEY=b; VALUE=2");
+    List<String> array = FlatLists.of(" EXPR$1=5", " EXPR$1=6", " EXPR$1=7");
+
+    final StringBuilder b = new StringBuilder();
+    for (List<String> row : Linq4j.product(FlatLists.of(map, array))) {
+      b.append(row.get(0)).append(";").append(row.get(1)).append("\n");
+    }
+    final String expected = b.toString();
+
+    assertThat(CalciteAssert.toString(resultSet), is(expected));
+    connection.close();
+  }
+
   private CalciteAssert.AssertQuery withFoodMartQuery(int id)
       throws IOException {
     final FoodmartTest.FoodMartQuerySet set =

http://git-wip-us.apache.org/repos/asf/calcite/blob/d757201f/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 4a29961..c05dab3 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -6700,6 +6700,11 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         "Column 'ORDINALITY' not found in any table");
   }
 
+  @Test public void unnestMapMustNameColumnsKeyAndValueWhenNotAliased() {
+    checkResultType("select * from unnest(map[1, 12, 2, 22])",
+        "RecordType(INTEGER NOT NULL KEY, INTEGER NOT NULL VALUE) NOT NULL");
+  }
+
   @Test public void testCorrelationJoin() {
     check("select *,"
         + "         multiset(select * from emp where deptno=dept.deptno) "