This is an automated email from the ASF dual-hosted git repository.
mihaibudiu 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 8737f2e5ec [CALCITE-7699] ARRAY_INSERT fails in validation when first
argument is not an array constructor
8737f2e5ec is described below
commit 8737f2e5ec206030afdd9841d82280a452ff32a6
Author: Mihai Budiu <[email protected]>
AuthorDate: Mon Aug 10 11:48:20 2026 -0700
[CALCITE-7699] ARRAY_INSERT fails in validation when first argument is not
an array constructor
Signed-off-by: Mihai Budiu <[email protected]>
---
.../calcite/sql/fun/SqlLibraryOperators.java | 4 +-
.../calcite/sql/validate/SqlValidatorUtil.java | 39 ++++++++++++++++-
.../org/apache/calcite/test/SqlOperatorTest.java | 50 ++++++++++++++++++++++
3 files changed, 89 insertions(+), 4 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
index aef50bbc63..174f562a80 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
@@ -1471,7 +1471,7 @@ private static RelDataType
arrayAppendPrependReturnType(SqlOperatorBinding opBin
adjustTypeForArrayFunctions(type, opBinding, 1);
} else {
SqlValidatorUtil.
- adjustTypeForArrayFunctions(type, opBinding, 0);
+ adjustArrayTypeForArrayFunctions(type, opBinding, 0);
}
}
@@ -1601,7 +1601,7 @@ private static RelDataType
arrayInsertReturnType(SqlOperatorBinding opBinding) {
}
if (!componentType.equalsSansFieldNamesAndNullability(type)) {
SqlValidatorUtil.
- adjustTypeForArrayFunctions(type, opBinding, 0);
+ adjustArrayTypeForArrayFunctions(type, opBinding, 0);
}
boolean nullable = arrayType.isNullable() || elementType1.isNullable();
return SqlTypeUtil.createArrayType(opBinding.getTypeFactory(), type,
nullable);
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
index 84495c0a66..ca1b2f9276 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
@@ -1428,6 +1428,9 @@ public static void adjustTypeForArrayConstructor(
* if targetType is double, this method would ensure that the elements of the
* first array and the second operand are cast to double.
*
+ * <p>Use {@link #adjustArrayTypeForArrayFunctions} for an operand that is
+ * itself an array whose element type must become {@code targetType}.
+ *
* @param targetType The target {@link RelDataType} to which the operands
should be cast.
* @param opBinding The {@link SqlOperatorBinding} context, which provides
access to the
* {@link SqlCall} and its operands.
@@ -1437,6 +1440,30 @@ public static void adjustTypeForArrayConstructor(
*/
public static void adjustTypeForArrayFunctions(
RelDataType targetType, SqlOperatorBinding opBinding, int... indexes) {
+ adjustTypeForArrayFunctions(targetType, opBinding, false, indexes);
+ }
+
+ /**
+ * Same as {@link #adjustTypeForArrayFunctions}, for operands that are
+ * arrays whose element type must become {@code targetType}.
+ *
+ * <p>The two methods differ for an operand that is not a call to the ARRAY
+ * constructor (e.g., a CAST expression): this method casts such an operand
to an array of
+ * {@code targetType} rather than to {@code targetType} itself.
+ *
+ * @param targetType The array element type to which the operands' elements
+ * should be cast.
+ * @param arrayOperands The indexes of the array operands within the {@link
SqlCall}
+ * that need to be adjusted to the target type.
+ */
+ public static void adjustArrayTypeForArrayFunctions(
+ RelDataType targetType, SqlOperatorBinding opBinding, int...
arrayOperands) {
+ adjustTypeForArrayFunctions(targetType, opBinding, true, arrayOperands);
+ }
+
+ private static void adjustTypeForArrayFunctions(
+ RelDataType targetType, SqlOperatorBinding opBinding, boolean
arrayOperands,
+ int... indexes) {
if (opBinding instanceof SqlCallBinding) {
requireNonNull(targetType, "array function target type");
final SqlValidator validator = ((SqlCallBinding)
opBinding).getValidator();
@@ -1459,9 +1486,17 @@ public static void adjustTypeForArrayFunctions(
targetType, priorType.isNullable()));
}
} else {
- SqlNode cast = castTo(operand, targetType);
+ RelDataType castType = targetType;
+ if (arrayOperands) {
+ // An array operand that is not an ARRAY constructor call must be
+ // cast to an array of the target type, not to the target type
+ castType =
+ SqlTypeUtil.createArrayType(opBinding.getTypeFactory(),
+ targetType, opBinding.getOperandType(idx).isNullable());
+ }
+ SqlNode cast = castTo(operand, castType);
call.setOperand(idx, cast);
- validator.setValidatedNodeType(cast, targetType);
+ validator.setValidatedNodeType(cast, castType);
}
}
}
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 9473246f62..95381426f9 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -8343,6 +8343,22 @@ void checkRegexpExtract(SqlOperatorFixture f0,
FunctionAlias functionAlias) {
f.checkFails("^array_append(array[1, 2], true)^",
"INTEGER is not comparable to BOOLEAN", false);
+ // Test cases for [CALCITE-7699]
+ // ARRAY_INSERT fails in validation when first argument is not an array
+ // constructor
+ f.checkScalar("array_append(cast(array[1, 2, 3] as integer array), "
+ + "cast(4 as double))",
+ "[1.0, 2.0, 3.0, 4.0]", "DOUBLE NOT NULL ARRAY NOT NULL");
+ f.checkScalar("array_append(array_distinct(array[1, 2, 3]), "
+ + "cast(4 as double))",
+ "[1.0, 2.0, 3.0, 4.0]", "DOUBLE NOT NULL ARRAY NOT NULL");
+ // Array of arrays as a non-constructor operand
+ f.checkScalar("array_append("
+ + "cast(array[array[1, 2]] as integer array array), "
+ + "array[cast(3 as double)])",
+ "[[1.0, 2.0], [3.0]]",
+ "DOUBLE NOT NULL ARRAY NOT NULL ARRAY NOT NULL");
+
// element cast to the biggest type
f.checkScalar("array_append(array(cast(1 as tinyint)), 2)", "[1, 2]",
"INTEGER NOT NULL ARRAY NOT NULL");
@@ -8682,6 +8698,16 @@ void checkRegexpExtract(SqlOperatorFixture f0,
FunctionAlias functionAlias) {
f.checkFails("^array_prepend(array[1, 2], true)^",
"INTEGER is not comparable to BOOLEAN", false);
+ // Test case for [CALCITE-7699]
+ // ARRAY_INSERT fails in validation when first argument is not an array
+ // constructor
+ f.checkScalar("array_prepend(cast(array[1, 2, 3] as integer array), "
+ + "cast(4 as double))",
+ "[4.0, 1.0, 2.0, 3.0]", "DOUBLE NOT NULL ARRAY NOT NULL");
+ f.checkScalar("array_prepend(array_distinct(array[1, 2, 3]), "
+ + "cast(4 as double))",
+ "[4.0, 1.0, 2.0, 3.0]", "DOUBLE NOT NULL ARRAY NOT NULL");
+
// element cast to the biggest type
f.checkScalar("array_prepend(array(1), cast(3 as float))", "[3.0, 1.0]",
"FLOAT NOT NULL ARRAY NOT NULL");
@@ -9015,6 +9041,30 @@ void checkArrayReverseFunc(SqlOperatorFixture f0,
SqlFunction function,
+ "An index shall be either < 0 or > 0 \\(the first element has
index 1\\) "
+ "and not exceeds the allowed limit.", true);
+ // Test case for [CALCITE-7699]
+ // ARRAY_INSERT fails in validation when first argument is not an array
+ // constructor
+ f1.checkScalar("array_insert(cast(array[1, 2, 3] as integer array), 3, "
+ + "cast(4 as double))",
+ "[1.0, 2.0, 4.0, 3.0]", "DOUBLE ARRAY NOT NULL");
+ f1.checkScalar("array_insert(array_distinct(array[1, 2, 3]), 3, "
+ + "cast(4 as double))",
+ "[1.0, 2.0, 4.0, 3.0]", "DOUBLE ARRAY NOT NULL");
+ f1.checkScalar("array_insert(cast(array[1, 2] as integer array), 2, 2.5)",
+ "[1.0, 2.5, 2.0]", "DECIMAL(11, 1) ARRAY NOT NULL");
+ f1.checkNull("array_insert(cast(null as integer array), 3, "
+ + "cast(4 as double))");
+ f1.checkType("array_insert(cast(null as integer array), 3, "
+ + "cast(4 as double))", "DOUBLE ARRAY");
+ // Array of arrays as a non-constructor operand
+ f1.checkScalar("array_insert("
+ + "cast(array[array[1, 2]] as integer array array), 1, array[3])",
+ "[[3], [1, 2]]", "INTEGER NOT NULL ARRAY ARRAY NOT NULL");
+ f1.checkScalar("array_insert("
+ + "cast(array[array[1, 2]] as integer array array), 1, "
+ + "array[cast(3 as double)])",
+ "[[3.0], [1.0, 2.0]]", "DOUBLE NOT NULL ARRAY ARRAY NOT NULL");
+
f1.checkScalar("array_insert(array[1, 2, 3], 3, 4)",
"[1, 2, 4, 3]", "INTEGER ARRAY NOT NULL");
f1.checkScalar("array_insert(array[1, 2, 3], 3, cast(null as integer))",