This is an automated email from the ASF dual-hosted git repository.
mbudiu 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 0af5284d9f [CALCITE-7238] Query that creates a ROW value triggers an
assertion failure in SqlToRelConverte
0af5284d9f is described below
commit 0af5284d9f004fd9e38e2d62abd8c04de004be5c
Author: Mihai Budiu <[email protected]>
AuthorDate: Mon Oct 20 17:26:15 2025 -0700
[CALCITE-7238] Query that creates a ROW value triggers an assertion failure
in SqlToRelConverte
Signed-off-by: Mihai Budiu <[email protected]>
---
.../java/org/apache/calcite/rel/core/Collect.java | 4 +-
.../java/org/apache/calcite/rex/RexSubQuery.java | 6 ++-
.../apache/calcite/sql/type/SqlTypeTransforms.java | 10 +++--
.../org/apache/calcite/sql/type/SqlTypeUtil.java | 48 ++++++++++++++++++++++
.../apache/calcite/test/SqlToRelConverterTest.java | 7 ++++
.../apache/calcite/test/SqlToRelConverterTest.xml | 16 ++++++++
6 files changed, 85 insertions(+), 6 deletions(-)
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 88e2cc7927..c308bfe6bf 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
@@ -153,7 +153,9 @@ public static Collect create(RelNode input,
rowType =
deriveRowType(input.getCluster().getTypeFactory(), collectionType,
fieldName,
- SqlTypeUtil.deriveCollectionQueryComponentType(collectionType,
+ SqlTypeUtil.deriveCollectionQueryComponentType(
+ input.getCluster().getTypeFactory(),
+ collectionType,
input.getRowType()));
break;
default:
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 61d10174f8..5a9cad2a97 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java
@@ -126,7 +126,8 @@ public static RexSubQuery array(RelNode rel) {
final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
final RelDataType type =
typeFactory.createArrayType(
- SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.ARRAY,
rel.getRowType()),
+ SqlTypeUtil.deriveCollectionQueryComponentType(
+ rel.getCluster().getTypeFactory(), SqlTypeName.ARRAY,
rel.getRowType()),
-1L);
return new RexSubQuery(type, SqlStdOperatorTable.ARRAY_QUERY,
ImmutableList.of(), rel);
@@ -137,7 +138,8 @@ public static RexSubQuery multiset(RelNode rel) {
final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
final RelDataType type =
typeFactory.createMultisetType(
-
SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.MULTISET,
rel.getRowType()),
+ SqlTypeUtil.deriveCollectionQueryComponentType(
+ rel.getCluster().getTypeFactory(), 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/type/SqlTypeTransforms.java
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
index f1a699e9f3..253b93b3a0 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
@@ -239,7 +239,9 @@ private SqlTypeName toVar(RelDataType type) {
public static final SqlTypeTransform TO_MULTISET_QUERY =
(opBinding, typeToTransform) ->
TO_MULTISET.transformType(opBinding,
-
SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.MULTISET,
+ SqlTypeUtil.deriveCollectionQueryComponentType(
+ opBinding.getTypeFactory(),
+ SqlTypeName.MULTISET,
typeToTransform));
/**
@@ -296,7 +298,8 @@ private SqlTypeName toVar(RelDataType type) {
public static final SqlTypeTransform TO_ARRAY_QUERY =
(opBinding, typeToTransform) ->
TO_ARRAY.transformType(opBinding,
- SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.ARRAY,
typeToTransform));
+ SqlTypeUtil.deriveCollectionQueryComponentType(
+ opBinding.getTypeFactory(), SqlTypeName.ARRAY,
typeToTransform));
/**
* Parameter type-inference transform strategy that converts a two-field
@@ -318,7 +321,8 @@ private SqlTypeName toVar(RelDataType type) {
public static final SqlTypeTransform TO_MAP_QUERY =
(opBinding, typeToTransform) ->
TO_MAP.transformType(opBinding,
- SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.MAP,
typeToTransform));
+ SqlTypeUtil.deriveCollectionQueryComponentType(
+ opBinding.getTypeFactory(), SqlTypeName.MAP,
typeToTransform));
/**
* Parameter type-inference transform strategy that converts a 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 42d6bc9edb..122708a41b 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
@@ -39,6 +39,7 @@
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
+import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.util.NumberUtil;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
@@ -178,12 +179,59 @@ public static boolean isCharTypeComparable(
return true;
}
+ /** A namespace may contain multiple fields with the same name.
+ * However, a proper ROW type cannot; this function will assign
+ * unique names to fields when they are used to build a concrete ROW type.
+ *
+ * @param type A type for which type.isStruct() is true.
+ * @return A new version of this type where all fields have unique names.
+ *
+ * <p>Note: the same rule to rename fields is used by the {@link
SqlToRelConverter} later,
+ * in convertNonAggregateSelectList (a private method). This ensures that
+ * the generated field names there will match with the inferred field names
here.
+ */
+ private static RelDataType uniquify(RelDataTypeFactory factory, RelDataType
type) {
+ List<String> unique = SqlValidatorUtil.uniquify(type.getFieldNames(),
true);
+ List<RelDataType> types = type.getFieldList()
+ .stream()
+ .map(RelDataTypeField::getType)
+ .collect(Collectors.toList());
+ return factory.createStructType(type.getStructKind(), types, unique);
+ }
+
+ /**
+ * Derives component type for ARRAY, MULTISET, MAP when input is sub-query.
+ *
+ * @param factory Type factory used to generate new types if necessary
+ * @param origin original component type
+ * @return component type
+ */
+ public static RelDataType deriveCollectionQueryComponentType(
+ RelDataTypeFactory factory,
+ SqlTypeName collectionType,
+ RelDataType origin) {
+ switch (collectionType) {
+ case ARRAY:
+ case MULTISET:
+ return origin.isStruct() && origin.getFieldCount() == 1
+ ? origin.getFieldList().get(0).getType() : uniquify(factory, origin);
+ case MAP:
+ return origin;
+ default:
+ throw new AssertionError(
+ "Impossible to derive component type for " + collectionType);
+ }
+ }
+
/**
* Derives component type for ARRAY, MULTISET, MAP when input is sub-query.
*
* @param origin original component type
* @return component type
+ * @deprecated Use
+ * {@link SqlTypeUtil#deriveCollectionQueryComponentType(RelDataTypeFactory,
SqlTypeName, RelDataType)}
*/
+ @Deprecated
public static RelDataType deriveCollectionQueryComponentType(
SqlTypeName collectionType,
RelDataType origin) {
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 8f020b6f78..be285b6438 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -5430,6 +5430,13 @@ void checkUserDefinedOrderByOver(NullCollation
nullCollation) {
sql(sql).ok();
}
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-7238">[CALCITE-7238]
+ * Query that creates a ROW value triggers an assertion failure in
SqlToRelConverter</a>. */
+ @Test void testArrayRow() {
+ sql("WITH tbl(r) AS (VALUES(1)) SELECT\n"
+ + "ARRAY(SELECT r, r FROM tbl)").ok();
+ }
+
/**
* Test case for
* <a
href="https://issues.apache.org/jira/browse/CALCITE-4145">[CALCITE-4145]
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 50e6f27b0b..5b0760b6e8 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -521,6 +521,22 @@ LogicalProject(EXPR$0=[ROW(ITEM(ITEM(ITEM(ITEM($3, 0),
'detail'), 'skills'), 0).
<![CDATA[
LogicalProject(EXPR$0=[ITEM(ITEM($3, 1).DETAIL.SKILLS, +(2, 3)).DESC])
LogicalTableScan(table=[[CATALOG, SALES, DEPT_NESTED]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testArrayRow">
+ <Resource name="sql">
+ <![CDATA[WITH tbl(r) AS (VALUES(1)) SELECT
+ARRAY(SELECT r, r FROM tbl)]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalProject(EXPR$0=[$1])
+ LogicalJoin(condition=[true], joinType=[inner])
+ LogicalValues(tuples=[[{ 0 }]])
+ Collect(field=[EXPR$0])
+ LogicalProject(R=[$0], R0=[$0])
+ LogicalValues(tuples=[[{ 1 }]])
]]>
</Resource>
</TestCase>