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 <[email protected]>
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);