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))",

Reply via email to