This is an automated email from the ASF dual-hosted git repository.

rubenql pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new 71e30e2c70 [CALCITE-4999] ARRAY, MULTISET functions should return a 
collection of scalars if a sub-query returns 1 column
71e30e2c70 is described below

commit 71e30e2c70e454bdd09eefaaaa12ad239ba510ce
Author: dssysolyatin <dm.sysolya...@gmail.com>
AuthorDate: Mon Aug 29 12:13:50 2022 +0300

    [CALCITE-4999] ARRAY, MULTISET functions should return a collection of 
scalars if a sub-query returns 1 column
---
 .../adapter/enumerable/EnumerableCollect.java      | 30 ++++++++++-----
 .../java/org/apache/calcite/rel/core/Collect.java  | 45 ++++++++++++++++++++++
 .../apache/calcite/rel/mutable/MutableRels.java    |  3 +-
 .../calcite/rel/rules/SubQueryRemoveRule.java      | 14 ++-----
 .../java/org/apache/calcite/rex/RexSubQuery.java   |  9 ++++-
 .../calcite/sql/fun/SqlArrayQueryConstructor.java  |  2 +-
 .../sql/fun/SqlMultisetQueryConstructor.java       |  2 +-
 .../apache/calcite/sql/type/SqlTypeTransforms.java | 21 ++++++++++
 .../org/apache/calcite/sql/type/SqlTypeUtil.java   | 22 +++++++++++
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  6 +--
 .../calcite/sql2rel/StandardConvertletTable.java   | 16 ++------
 .../java/org/apache/calcite/test/JdbcTest.java     |  2 +-
 .../org/apache/calcite/test/SqlValidatorTest.java  | 32 ++++++++++++++-
 .../org/apache/calcite/test/SqlOperatorTest.java   | 31 +++++++++++++++
 14 files changed, 189 insertions(+), 46 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCollect.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCollect.java
index da50dff617..64ead33167 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCollect.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCollect.java
@@ -25,8 +25,11 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Collect;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeUtil;
 import org.apache.calcite.util.BuiltInMethod;
 
+import static java.util.Objects.requireNonNull;
+
 /** Implementation of {@link org.apache.calcite.rel.core.Collect} in
  * {@link org.apache.calcite.adapter.enumerable.EnumerableConvention 
enumerable calling convention}. */
 public class EnumerableCollect extends Collect implements EnumerableRel {
@@ -91,15 +94,24 @@ public class EnumerableCollect extends Collect implements 
EnumerableRel {
     Expression child_ =
         builder.append(
             "child", result.block);
-    // In the internal representation of multisets , every element must be a 
record. In case the
-    // result above is a scalar type we have to wrap it around a physical type 
capable of
-    // representing records. For this reason the following conversion is 
necessary.
-    // REVIEW zabetak January 7, 2019: If we can ensure that the input to this 
operator
-    // has the correct physical type (e.g., respecting the Prefer.ARRAY above) 
then this conversion
-    // can be removed.
-    Expression conv_ =
-        builder.append(
-            "converted", result.physType.convertTo(child_, 
JavaRowFormat.ARRAY));
+
+    RelDataType collectionComponentType =
+        
requireNonNull(rowType().getFieldList().get(0).getType().getComponentType());
+    RelDataType childRecordType = 
result.physType.getRowType().getFieldList().get(0).getType();
+
+    Expression conv_ = child_;
+    if (!SqlTypeUtil.sameNamedType(collectionComponentType, childRecordType)) {
+      // In the internal representation of multisets , every element must be a 
record. In case the
+      // result above is a scalar type we have to wrap it around a physical 
type capable of
+      // representing records. For this reason the following conversion is 
necessary.
+      // REVIEW zabetak January 7, 2019: If we can ensure that the input to 
this operator
+      // has the correct physical type (e.g., respecting the Prefer.ARRAY 
above)
+      // then this conversion can be removed.
+      conv_ =
+          builder.append(
+              "converted", result.physType.convertTo(child_, 
JavaRowFormat.ARRAY));
+    }
+
     Expression list_ =
         builder.append("list",
             Expressions.call(conv_,
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Collect.java 
b/core/src/main/java/org/apache/calcite/rel/core/Collect.java
index 28b83c5ff5..50891d5a7e 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Collect.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Collect.java
@@ -25,6 +25,7 @@ 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.sql.SqlKind;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
 
@@ -124,6 +125,7 @@ public class Collect extends SingleRel {
    * @param collectionType ARRAY, MAP or MULTISET
    * @param fieldName      Name of the sole output field
    */
+  @Deprecated // to be removed before 2.0
   public static Collect create(RelNode input,
       SqlTypeName collectionType,
       String fieldName) {
@@ -132,6 +134,32 @@ public class Collect extends SingleRel {
             fieldName, input.getRowType()));
   }
 
+  /**
+   * Creates a Collect.
+   *
+   * @param input          Input relational expression
+   * @param sqlKind        SqlKind
+   * @param fieldName      Name of the sole output field
+   */
+  public static Collect create(RelNode input,
+      SqlKind sqlKind,
+      String fieldName) {
+    SqlTypeName collectionType = getCollectionType(sqlKind);
+    RelDataType rowType;
+    switch (sqlKind) {
+    case ARRAY_QUERY_CONSTRUCTOR:
+    case MULTISET_QUERY_CONSTRUCTOR:
+      rowType = deriveRowType(input.getCluster().getTypeFactory(),
+          collectionType, fieldName,
+          SqlTypeUtil.deriveCollectionQueryComponentType(collectionType, 
input.getRowType()));
+      break;
+    default:
+      rowType = deriveRowType(input.getCluster().getTypeFactory(), 
collectionType,
+          fieldName, input.getRowType());
+    }
+    return create(input, rowType);
+  }
+
   /** Returns the row type, guaranteed not null.
    * (The row type is never null after initialization, but
    * CheckerFramework can't deduce that references are safe.) */
@@ -173,6 +201,23 @@ public class Collect extends SingleRel {
         .getType().getSqlTypeName();
   }
 
+  private static SqlTypeName getCollectionType(SqlKind sqlKind) {
+    switch (sqlKind) {
+    case ARRAY_QUERY_CONSTRUCTOR:
+    case ARRAY_VALUE_CONSTRUCTOR:
+      return SqlTypeName.ARRAY;
+    case MULTISET_QUERY_CONSTRUCTOR:
+    case MULTISET_VALUE_CONSTRUCTOR:
+      return SqlTypeName.MULTISET;
+    case MAP_QUERY_CONSTRUCTOR:
+    case MAP_VALUE_CONSTRUCTOR:
+      return SqlTypeName.MAP;
+    default:
+      throw new IllegalArgumentException("not a collection kind "
+          + sqlKind);
+    }
+  }
+
   @Override protected RelDataType deriveRowType() {
     // this method should never be called; rowType is always set
     throw new UnsupportedOperationException();
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java 
b/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
index 708e8816d3..dcaccbb480 100644
--- a/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
@@ -53,7 +53,6 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
-import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.Mapping;
@@ -254,7 +253,7 @@ public abstract class MutableRels {
     case COLLECT: {
       final MutableCollect collect = (MutableCollect) node;
       final RelNode child = fromMutable(collect.getInput(), relBuilder);
-      return Collect.create(child, SqlTypeName.MULTISET, collect.fieldName);
+      return Collect.create(child, collect.rowType);
     }
     case UNCOLLECT: {
       final MutableUncollect uncollect = (MutableUncollect) node;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
index b387724153..69e405b73e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
@@ -40,7 +40,6 @@ import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.fun.SqlQuantifyOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
-import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql2rel.RelDecorrelator;
 import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.ImmutableBitSet;
@@ -94,13 +93,9 @@ public class SubQueryRemoveRule
     case SCALAR_QUERY:
       return rewriteScalarQuery(e, variablesSet, builder, inputCount, offset);
     case ARRAY_QUERY_CONSTRUCTOR:
-      return rewriteCollection(e, SqlTypeName.ARRAY, variablesSet, builder,
-          inputCount, offset);
     case MAP_QUERY_CONSTRUCTOR:
-      return rewriteCollection(e, SqlTypeName.MAP, variablesSet, builder,
-          inputCount, offset);
     case MULTISET_QUERY_CONSTRUCTOR:
-      return rewriteCollection(e, SqlTypeName.MULTISET, variablesSet, builder,
+      return rewriteCollection(e, variablesSet, builder,
           inputCount, offset);
     case SOME:
       return rewriteSome(e, variablesSet, builder);
@@ -147,7 +142,6 @@ public class SubQueryRemoveRule
    * {@link org.apache.calcite.rel.core.Collect}.
    *
    * @param e            Sub-query to rewrite
-   * @param collectionType Collection type (ARRAY, MAP, MULTISET)
    * @param variablesSet A set of variables used by a relational
    *                     expression of the specified RexSubQuery
    * @param builder      Builder
@@ -155,11 +149,11 @@ public class SubQueryRemoveRule
    * @return Expression that may be used to replace the RexSubQuery
    */
   private static RexNode rewriteCollection(RexSubQuery e,
-      SqlTypeName collectionType, Set<CorrelationId> variablesSet,
-      RelBuilder builder, int inputCount, int offset) {
+      Set<CorrelationId> variablesSet, RelBuilder builder,
+      int inputCount, int offset) {
     builder.push(e.rel);
     builder.push(
-        Collect.create(builder.build(), collectionType, "x"));
+        Collect.create(builder.build(), e.getKind(), "x"));
     builder.join(JoinRelType.INNER, builder.literal(true), variablesSet);
     return field(builder, inputCount, offset);
   }
diff --git a/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java 
b/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java
index 564e2b63b1..3bb6f2892d 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java
@@ -26,6 +26,7 @@ import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlQuantifyOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeUtil;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -123,7 +124,9 @@ public class RexSubQuery extends RexCall {
   public static RexSubQuery array(RelNode rel) {
     final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
     final RelDataType type =
-        typeFactory.createArrayType(rel.getRowType(), -1L);
+        typeFactory.createArrayType(
+            SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.ARRAY, 
rel.getRowType()),
+            -1L);
     return new RexSubQuery(type, SqlStdOperatorTable.ARRAY_QUERY,
         ImmutableList.of(), rel);
   }
@@ -132,7 +135,9 @@ public class RexSubQuery extends RexCall {
   public static RexSubQuery multiset(RelNode rel) {
     final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
     final RelDataType type =
-        typeFactory.createMultisetType(rel.getRowType(), -1L);
+        typeFactory.createMultisetType(
+            
SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.MULTISET, 
rel.getRowType()),
+            -1L);
     return new RexSubQuery(type, SqlStdOperatorTable.MULTISET_QUERY,
         ImmutableList.of(), rel);
   }
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 810df293b9..273f6642bd 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
@@ -27,6 +27,6 @@ public class SqlArrayQueryConstructor extends 
SqlMultisetQueryConstructor {
   //~ Constructors -----------------------------------------------------------
 
   public SqlArrayQueryConstructor() {
-    super("ARRAY", SqlKind.ARRAY_QUERY_CONSTRUCTOR, 
SqlTypeTransforms.TO_ARRAY);
+    super("ARRAY", SqlKind.ARRAY_QUERY_CONSTRUCTOR, 
SqlTypeTransforms.TO_ARRAY_QUERY);
   }
 }
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 2391bba50b..05ed42ca84 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
@@ -52,7 +52,7 @@ public class SqlMultisetQueryConstructor extends 
SqlSpecialOperator {
 
   public SqlMultisetQueryConstructor() {
     this("MULTISET", SqlKind.MULTISET_QUERY_CONSTRUCTOR,
-        SqlTypeTransforms.TO_MULTISET);
+        SqlTypeTransforms.TO_MULTISET_QUERY);
   }
 
   protected SqlMultisetQueryConstructor(String name, SqlKind kind,
diff --git 
a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java 
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
index cc4e63794b..c6fce21756 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
@@ -174,6 +174,17 @@ public abstract class SqlTypeTransforms {
       (opBinding, typeToTransform) ->
           opBinding.getTypeFactory().createMultisetType(typeToTransform, -1);
 
+  /**
+   * Parameter type-inference transform strategy that wraps a given type in a 
multiset or
+   * wraps a field of the given type in a multiset if the given type is struct 
with one field.
+   * It is used when a multiset input is a sub-query.
+   */
+  public static final SqlTypeTransform TO_MULTISET_QUERY =
+      (opBinding, typeToTransform) ->
+          TO_MULTISET.transformType(opBinding,
+              
SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.MULTISET,
+                  typeToTransform));
+
   /**
    * Parameter type-inference transform strategy that wraps a given type
    * in a array.
@@ -184,6 +195,16 @@ public abstract class SqlTypeTransforms {
       (opBinding, typeToTransform) ->
           opBinding.getTypeFactory().createArrayType(typeToTransform, -1);
 
+  /**
+   * Parameter type-inference transform strategy that wraps a given type in an 
array or
+   * wraps a field of the given type in an array if the given type is struct 
with one field.
+   * It is used when an array input is a sub-query.
+   */
+  public static final SqlTypeTransform TO_ARRAY_QUERY =
+      (opBinding, typeToTransform) ->
+        TO_ARRAY.transformType(opBinding,
+            SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.ARRAY, 
typeToTransform));
+
   /**
    * Parameter type-inference transform strategy that converts a two-field
    * record type to a MAP type.
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java 
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
index 3f5a8ec967..23d04b0328 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
@@ -140,6 +140,28 @@ public abstract class SqlTypeUtil {
     return true;
   }
 
+  /**
+   * Derives component type for ARRAY, MULTISET, MAP when input is sub-query.
+   *
+   * @param origin original component type
+   * @return component type
+   */
+  public static RelDataType deriveCollectionQueryComponentType(
+      SqlTypeName collectionType,
+      RelDataType origin) {
+    switch (collectionType) {
+    case ARRAY:
+    case MULTISET:
+      return origin.isStruct() && origin.getFieldCount() == 1
+          ? origin.getFieldList().get(0).getType() : origin;
+    case MAP:
+      return origin;
+    default:
+      throw new AssertionError(
+          "Impossible to derive component type for " + collectionType);
+    }
+  }
+
   /**
    * Iterates over all operands, derives their types, and collects them into
    * a list.
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 fa02ce5849..571019c589 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -4435,13 +4435,9 @@ public class SqlToRelConverter {
         joinList.add(lastList);
       }
       lastList = new ArrayList<>();
-      final SqlTypeName typeName =
-          requireNonNull(validator, "validator")
-              .getValidatedNodeType(call)
-              .getSqlTypeName();
       relBuilder.push(
           Collect.create(requireNonNull(input, "input"),
-              typeName, castNonNull(validator().deriveAlias(call, i))));
+              call.getKind(), castNonNull(validator().deriveAlias(call, i))));
       joinList.add(relBuilder.build());
     }
 
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java 
b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index 7989ebab1a..82d5f1ced8 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -530,20 +530,10 @@ public class StandardConvertletTable extends 
ReflectiveConvertletTable {
         cx.getRexBuilder().makeInputRef(
             msType,
             rr.getOffset());
-    assert msType.getComponentType() != null && 
msType.getComponentType().isStruct()
-        : "componentType of " + msType + " must be struct";
+    assert msType.getComponentType() != null
+        : "componentType of " + msType + " must not be null";
     assert originalType.getComponentType() != null
-        : "componentType of " + originalType + " must be struct";
-    if (!originalType.getComponentType().isStruct()) {
-      // If the type is not a struct, the multiset operator will have
-      // wrapped the type as a record. Add a call to the $SLICE operator
-      // to compensate. For example,
-      // if '<ms>' has type 'RECORD (INTEGER x) MULTISET',
-      // then '$SLICE(<ms>) has type 'INTEGER MULTISET'.
-      // This will be removed as the expression is translated.
-      expr =
-          cx.getRexBuilder().makeCall(SqlStdOperatorTable.SLICE, expr);
-    }
+        : "componentType of " + originalType + " must not be null";
     return expr;
   }
 
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 ff73459140..2ed221d69d 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -2112,7 +2112,7 @@ public class JdbcTest {
         .query("select multiset(\n"
             + "  select \"deptno\" from \"hr\".\"emps\") as a\n"
             + "from (values (1))")
-        .returnsUnordered("A=[{10}, {20}, {10}, {10}]");
+        .returnsUnordered("A=[10, 20, 10, 10]");
   }
 
   @Test void testUnnestArray() {
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 c8d5fe6ec6..00dd5e5d7b 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -8171,6 +8171,20 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
         .columnType("CHAR(3) ARRAY NOT NULL");
   }
 
+  /**
+   * Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-4999";>[CALCITE-4999]
+   * ARRAY, MULTISET functions should return a collection of scalars
+   * if a sub-query returns 1 column</a>.
+   */
+  @Test void testArrayQueryConstructor() {
+    sql("select array(select 1)")
+        .columnType("INTEGER NOT NULL ARRAY NOT NULL");
+    sql("select array(select ROW(1,2))")
+        .columnType(
+            "RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL EXPR$1) NOT 
NULL ARRAY NOT NULL");
+  }
+
   @Test void testCastAsCollectionType() {
     sql("select cast(array[1,null,2] as int array) from (values (1))")
         .columnType("INTEGER NOT NULL ARRAY NOT NULL");
@@ -8254,6 +8268,20 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
         .columnType("INTEGER MULTISET NOT NULL");
   }
 
+  /**
+   * Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-4999";>[CALCITE-4999]
+   * ARRAY, MULTISET functions should return an collection of scalars
+   * if a sub-query returns 1 column</a>.
+   */
+  @Test void testMultisetQueryConstructor() {
+    sql("select multiset(select 1)")
+        .columnType("INTEGER NOT NULL MULTISET NOT NULL");
+    sql("select multiset(select ROW(1,2))")
+        .columnType(
+            "RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL EXPR$1) NOT 
NULL MULTISET NOT NULL");
+  }
+
   @Test void testUnnestArrayColumn() {
     final String sql1 = "select d.deptno, e.*\n"
         + "from dept_nested as d,\n"
@@ -8305,14 +8333,14 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
     sql("select c from unnest(\n"
         + "  array(select deptno from dept)) with ordinality as t(^c^)")
         .fails("List of column aliases must have same degree as table; table 
has 2 "
-            + "columns \\('DEPTNO', 'ORDINALITY'\\), "
+            + "columns \\('EXPR\\$0', 'ORDINALITY'\\), "
             + "whereas alias list has 1 columns");
     sql("select c from unnest(\n"
         + "  array(select deptno from dept)) with ordinality as t(c, d)").ok();
     sql("select c from unnest(\n"
         + "  array(select deptno from dept)) with ordinality as t(^c, d, e^)")
         .fails("List of column aliases must have same degree as table; table 
has 2 "
-            + "columns \\('DEPTNO', 'ORDINALITY'\\), "
+            + "columns \\('EXPR\\$0', 'ORDINALITY'\\), "
             + "whereas alias list has 3 columns");
     sql("select c\n"
         + "from unnest(array(select * from dept)) with ordinality as t(^c, d, 
e, f^)")
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java 
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index a277b1ea65..5bac48f953 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -7125,6 +7125,37 @@ public class SqlOperatorTest {
     f.checkFails("^Array[]^", "Require at least 1 argument", false);
   }
 
+  /**
+   * Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-4999";>[CALCITE-4999]
+   * ARRAY, MULTISET functions should return an collection of scalars
+   * if a sub-query returns 1 column</a>.
+   */
+  @Test void testArrayQueryConstructor() {
+    final SqlOperatorFixture f = fixture();
+    f.setFor(SqlStdOperatorTable.ARRAY_QUERY, 
SqlOperatorFixture.VmName.EXPAND);
+    f.checkScalar("array(select 1)", "[1]",
+        "INTEGER NOT NULL ARRAY NOT NULL");
+    f.check("select array(select ROW(1,2))",
+        "RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL EXPR$1) NOT NULL 
ARRAY NOT NULL",
+        "[{1, 2}]");
+  }
+
+  /**
+   * Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-4999";>[CALCITE-4999]
+   * ARRAY, MULTISET functions should return an collection of scalars
+   * if a sub-query returns 1 column</a>.
+   */
+  @Test void testMultisetQueryConstructor() {
+    final SqlOperatorFixture f = fixture();
+    f.setFor(SqlStdOperatorTable.MULTISET_QUERY, 
SqlOperatorFixture.VmName.EXPAND);
+    f.checkScalar("multiset(select 1)", "[1]", "INTEGER NOT NULL MULTISET NOT 
NULL");
+    f.check("select multiset(select ROW(1,2))",
+        "RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL EXPR$1) NOT NULL 
MULTISET NOT NULL",
+        "[{1, 2}]");
+  }
+
   @Test void testItemOp() {
     final SqlOperatorFixture f = fixture();
     f.setFor(SqlStdOperatorTable.ITEM, VmName.EXPAND);

Reply via email to