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

philo-he pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new b2b84dbc4d [GLUTEN-6101][VL] Enable map_from_arrays function (#12976)
b2b84dbc4d is described below

commit b2b84dbc4d82cf2fc00691234b6e9276d9b2ea78
Author: Pedrum Jalali <[email protected]>
AuthorDate: Tue Sep 15 20:36:48 2026 -0700

    [GLUTEN-6101][VL] Enable map_from_arrays function (#12976)
---
 .../gluten/execution/MiscOperatorSuite.scala       |  10 --
 .../functions/ScalarFunctionsValidateSuite.scala   |  36 ++++++
 .../substrait/SubstraitToVeloxPlanValidator.cc     |   2 +-
 docs/velox-backend-scalar-function-support.md      |   4 +-
 .../utils/clickhouse/ClickHouseTestSettings.scala  |  11 ++
 .../gluten/utils/velox/VeloxTestSettings.scala     |  12 ++
 .../spark/sql/GlutenCharVarcharTestSuite.scala     |  77 +++++++++++-
 .../spark/sql/GlutenDataFrameFunctionsSuite.scala  |  45 +++++++
 .../utils/clickhouse/ClickHouseTestSettings.scala  |  14 +++
 .../gluten/utils/velox/VeloxTestSettings.scala     |  18 +++
 .../spark/sql/GlutenCharVarcharTestSuite.scala     |  77 +++++++++++-
 .../spark/sql/GlutenDataFrameFunctionsSuite.scala  |  48 ++++++++
 .../sql/GlutenRuntimeNullChecksV2Writes.scala      | 135 +++++++++++++++++++-
 .../utils/clickhouse/ClickHouseTestSettings.scala  |   4 +
 .../gluten/utils/velox/VeloxTestSettings.scala     |   8 ++
 .../spark/sql/GlutenDataFrameFunctionsSuite.scala  |  50 ++++++++
 .../sql/GlutenRuntimeNullChecksV2Writes.scala      | 137 ++++++++++++++++++++-
 .../utils/clickhouse/ClickHouseTestSettings.scala  |   4 +
 .../gluten/utils/velox/VeloxTestSettings.scala     |   8 ++
 .../spark/sql/GlutenDataFrameFunctionsSuite.scala  |  50 ++++++++
 .../sql/GlutenRuntimeNullChecksV2Writes.scala      | 134 +++++++++++++++++++-
 21 files changed, 860 insertions(+), 24 deletions(-)

diff --git 
a/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
 
b/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
index 948df8f8a3..7b43ad18e5 100644
--- 
a/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
+++ 
b/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
@@ -2340,16 +2340,6 @@ class MiscOperatorSuite extends 
VeloxWholeStageTransformerSuite with AdaptiveSpa
       })
   }
 
-  test("Expression unsupported by backend can be handled by 
ColumnarPartialProject") {
-    runQueryAndCompare(
-      "SELECT c_custkey, map_from_arrays(array(c_name), array(c_comment)) FROM 
customer") {
-      df =>
-        val executedPlan = getExecutedPlan(df)
-        assert(executedPlan.count(_.isInstanceOf[ProjectExec]) == 0)
-        assert(executedPlan.count(_.isInstanceOf[ColumnarPartialProjectExec]) 
== 1)
-    }
-  }
-
   testWithMinSparkVersion("Left single join should not result into exception", 
"4.0") {
     withSQLConf(SQLConf.ANSI_ENABLED.key -> "false") {
       spark.sql("create temp view x (x1, x2) as values (1, 1), (2, 2);")
diff --git 
a/backends-velox/src/test/scala/org/apache/gluten/functions/ScalarFunctionsValidateSuite.scala
 
b/backends-velox/src/test/scala/org/apache/gluten/functions/ScalarFunctionsValidateSuite.scala
index 557b86a17d..78051d4539 100644
--- 
a/backends-velox/src/test/scala/org/apache/gluten/functions/ScalarFunctionsValidateSuite.scala
+++ 
b/backends-velox/src/test/scala/org/apache/gluten/functions/ScalarFunctionsValidateSuite.scala
@@ -550,6 +550,42 @@ class ScalarFunctionsValidateSuite extends 
FunctionsValidateSuite {
     }
   }
 
+  test("map_from_arrays offloads to Velox under both mapKeyDedupPolicy 
values") {
+    // l_orderkey is repeated.
+    val duplicateKeyQuery =
+      "select map_from_arrays(array(l_orderkey, l_orderkey + 1, l_orderkey), " 
+
+        "array(l_partkey, l_suppkey, l_linenumber)) as m, " +
+        "map_keys(map_from_arrays(array(l_orderkey, l_orderkey + 1, 
l_orderkey), " +
+        "array(l_partkey, l_suppkey, l_linenumber))) as k from lineitem limit 
10"
+
+    // l_orderkey is not repeated.
+    val distinctKeyQuery =
+      "select map_from_arrays(array(l_orderkey, l_orderkey + 1), " +
+        "array(l_partkey, l_suppkey)) from lineitem limit 10"
+
+    withSQLConf(SQLConf.MAP_KEY_DEDUP_POLICY.key -> 
SQLConf.MapKeyDedupPolicy.EXCEPTION.toString) {
+      // EXCEPTION policy passes when there is no duplicate.
+      runQueryAndCompare(distinctKeyQuery) {
+        checkGlutenPlan[ProjectExecTransformer]
+      }
+
+      // EXCEPTION policy raises on a duplicate.
+      val df = sql(duplicateKeyQuery)
+      checkGlutenPlan[ProjectExecTransformer](df)
+      val e = intercept[SparkException] {
+        df.collect()
+      }
+      assert(e.getMessage.contains("Duplicate map key"))
+    }
+
+    withSQLConf(SQLConf.MAP_KEY_DEDUP_POLICY.key -> 
SQLConf.MapKeyDedupPolicy.LAST_WIN.toString) {
+      // LAST_WIN policy keeps the duplicate's first position and its last 
value.
+      runQueryAndCompare(duplicateKeyQuery) {
+        checkGlutenPlan[ProjectExecTransformer]
+      }
+    }
+  }
+
   test("raise_error, assert_true") {
     runQueryAndCompare("""SELECT assert_true(l_orderkey >= 1), l_orderkey
                          | from lineitem limit 100""".stripMargin) {
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc 
b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
index 618ffa1c0c..7ef0e807f1 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
@@ -56,7 +56,7 @@ const char* extractFileName(const char* file) {
 const std::unordered_set<std::string> kRegexFunctions =
     {"regexp_extract", "regexp_extract_all", "regexp_replace", "regexp_instr", 
"rlike", "split"};
 
-const std::unordered_set<std::string> kBlackList = {"split_part", "sequence", 
"approx_percentile", "map_from_arrays"};
+const std::unordered_set<std::string> kBlackList = {"split_part", "sequence", 
"approx_percentile"};
 } // namespace
 
 bool SubstraitToVeloxPlanValidator::parseVeloxType(
diff --git a/docs/velox-backend-scalar-function-support.md 
b/docs/velox-backend-scalar-function-support.md
index 744b4365d6..f4abc7a962 100644
--- a/docs/velox-backend-scalar-function-support.md
+++ b/docs/velox-backend-scalar-function-support.md
@@ -1,6 +1,6 @@
 # Scalar Functions Support Status
 
-**Out of 357 scalar functions in Spark 3.5, Gluten currently fully supports 
246 functions and partially supports 28 functions.**
+**Out of 357 scalar functions in Spark 3.5, Gluten currently fully supports 
247 functions and partially supports 28 functions.**
 
 **Gluten also fully supports 2 additional functions introduced in Spark 4.0.**
 
@@ -215,7 +215,7 @@
 | map_concat        | MapConcat           | PS       |                         
                                                    |
 | map_contains_key  | MapContainsKey      | S        |                         
                                                    |
 | map_entries       | MapEntries          | S        |                         
                                                    |
-| map_from_arrays   | MapFromArrays       |          |                         
                                                    |
+| map_from_arrays   | MapFromArrays       | S        |                         
                                                    |
 | map_from_entries  | MapFromEntries      | S        |                         
                                                    |
 | map_keys          | MapKeys             | S        |                         
                                                    |
 | map_values        | MapValues           | S        |                         
                                                    |
diff --git 
a/gluten-ut/spark34/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
 
b/gluten-ut/spark34/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
index 3493681aad..5203a075f9 100644
--- 
a/gluten-ut/spark34/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
+++ 
b/gluten-ut/spark34/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
@@ -167,6 +167,13 @@ class ClickHouseTestSettings extends BackendTestSettings {
     .excludeGlutenTest("length check for input string values: with implicit 
cast")
     .excludeGlutenTest("char/varchar type values length check: partitioned 
columns of other types")
     .excludeGlutenTest("SPARK-42611: check char/varchar length in reordered 
structs within arrays")
+    .excludeGlutenTest("length check for input string values: nested in map 
key")
+    .excludeGlutenTest("length check for input string values: nested in map 
value")
+    .excludeGlutenTest("length check for input string values: nested in both 
map key and value")
+    .excludeGlutenTest(
+      "SPARK-42611: check char/varchar length in reordered structs within map 
keys")
+    .excludeGlutenTest(
+      "SPARK-42611: check char/varchar length in reordered structs within map 
values")
   enableSuite[GlutenDSV2SQLInsertTestSuite]
   enableSuite[GlutenDataFrameAggregateSuite]
     .exclude("average")
@@ -195,6 +202,7 @@ class ClickHouseTestSettings extends BackendTestSettings {
     // Expected exception org.apache.spark.SparkException to be thrown, but no 
exception was thrown
     .exclude("map_concat function")
     .exclude("map with arrays")
+    .excludeGlutenTest("map with arrays")
     .exclude("flatten function")
     .exclude("aggregate function - array for primitive type not containing 
null")
     .exclude("aggregate function - array for primitive type containing null")
@@ -391,6 +399,9 @@ class ClickHouseTestSettings extends BackendTestSettings {
     .excludeGlutenTest("length check for input string values: nested in array 
of array")
     .excludeGlutenTest("length check for input string values: nested in array 
of struct")
     .excludeGlutenTest("length check for input string values: nested in array")
+    .excludeGlutenTest("length check for input string values: nested in map 
key")
+    .excludeGlutenTest("length check for input string values: nested in map 
value")
+    .excludeGlutenTest("length check for input string values: nested in both 
map key and value")
   enableSuite[GlutenFileSourceSQLInsertTestSuite]
     .exclude("SPARK-33474: Support typed literals as partition spec values")
     .exclude(
diff --git 
a/gluten-ut/spark34/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
 
b/gluten-ut/spark34/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
index 6d43178512..2346d5f7d3 100644
--- 
a/gluten-ut/spark34/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
+++ 
b/gluten-ut/spark34/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
@@ -686,6 +686,16 @@ class VeloxTestSettings extends BackendTestSettings {
     .exclude("length check for input string values: nested in array of struct")
     .exclude("length check for input string values: nested in array of array")
     .exclude("length check for input string values: with implicit cast")
+    // Overridden.
+    .exclude("length check for input string values: nested in map key")
+    // Overridden.
+    .exclude("length check for input string values: nested in map value")
+    // Overridden.
+    .exclude("length check for input string values: nested in both map key and 
value")
+    // Overridden.
+    .exclude("SPARK-42611: check char/varchar length in reordered structs 
within map keys")
+    // Overridden.
+    .exclude("SPARK-42611: check char/varchar length in reordered structs 
within map values")
 
   enableSuite[GlutenColumnExpressionSuite]
     // Velox raise_error('errMsg') throws a velox_user_error exception with 
the message 'errMsg'.
@@ -729,6 +739,8 @@ class VeloxTestSettings extends BackendTestSettings {
     .exclude("map_zip_with function - map of primitive types")
     // Exception class different.
     .exclude("array_insert functions")
+    // Overridden.
+    .exclude("map with arrays")
   enableSuite[GlutenDataFrameHintSuite]
   enableSuite[GlutenDataFrameImplicitsSuite]
   enableSuite[GlutenDataFrameJoinSuite]
diff --git 
a/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/GlutenCharVarcharTestSuite.scala
 
b/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/GlutenCharVarcharTestSuite.scala
index 4f0d826f57..5930d7ce7b 100644
--- 
a/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/GlutenCharVarcharTestSuite.scala
+++ 
b/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/GlutenCharVarcharTestSuite.scala
@@ -63,7 +63,8 @@ class GlutenFileSourceCharVarcharTestSuite
       typeName =>
         sql(s"CREATE TABLE t(c MAP<$typeName(5), STRING>) USING $format")
         val e = intercept[SparkException](sql("INSERT INTO t VALUES 
(map('123456', 'a'))"))
-        assert(e.getMessage.contains(ERROR_MESSAGE))
+        // Gluten exception differs from Spark
+        assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
     }
   }
 
@@ -74,7 +75,8 @@ class GlutenFileSourceCharVarcharTestSuite
         sql("INSERT INTO t VALUES (map('a', null))")
         checkAnswer(spark.table("t"), Row(Map("a" -> null)))
         val e = intercept[SparkException](sql("INSERT INTO t VALUES (map('a', 
'123456'))"))
-        assert(e.getMessage.contains(ERROR_MESSAGE))
+        // Gluten exception differs from Spark
+        assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
     }
   }
 
@@ -83,9 +85,11 @@ class GlutenFileSourceCharVarcharTestSuite
       typeName =>
         sql(s"CREATE TABLE t(c MAP<$typeName(5), $typeName(5)>) USING $format")
         val e1 = intercept[SparkException](sql("INSERT INTO t VALUES 
(map('123456', 'a'))"))
-        assert(e1.getMessage.contains(ERROR_MESSAGE))
+        // Gluten exception differs from Spark
+        assert(e1.getMessage.contains(VELOX_ERROR_MESSAGE))
         val e2 = intercept[SparkException](sql("INSERT INTO t VALUES (map('a', 
'123456'))"))
-        assert(e2.getMessage.contains(ERROR_MESSAGE))
+        // Gluten exception differs from Spark
+        assert(e2.getMessage.contains(VELOX_ERROR_MESSAGE))
     }
   }
 
@@ -301,4 +305,69 @@ class GlutenDSV2CharVarcharTestSuite extends 
DSV2CharVarcharTestSuite with Glute
         }
     }
   }
+
+  testGluten("length check for input string values: nested in map key") {
+    testTableWrite {
+      typeName =>
+        sql(s"CREATE TABLE t(c MAP<$typeName(5), STRING>) USING $format")
+        val e = intercept[SparkException](sql("INSERT INTO t VALUES 
(map('123456', 'a'))"))
+        // Gluten exception differs from Spark
+        assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
+    }
+  }
+
+  testGluten("length check for input string values: nested in map value") {
+    testTableWrite {
+      typeName =>
+        sql(s"CREATE TABLE t(c MAP<STRING, $typeName(5)>) USING $format")
+        sql("INSERT INTO t VALUES (map('a', null))")
+        checkAnswer(spark.table("t"), Row(Map("a" -> null)))
+        val e = intercept[SparkException](sql("INSERT INTO t VALUES (map('a', 
'123456'))"))
+        // Gluten exception differs from Spark
+        assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
+    }
+  }
+
+  testGluten("length check for input string values: nested in both map key and 
value") {
+    testTableWrite {
+      typeName =>
+        sql(s"CREATE TABLE t(c MAP<$typeName(5), $typeName(5)>) USING $format")
+        val e1 = intercept[SparkException](sql("INSERT INTO t VALUES 
(map('123456', 'a'))"))
+        // Gluten exception differs from Spark
+        assert(e1.getMessage.contains(VELOX_ERROR_MESSAGE))
+        val e2 = intercept[SparkException](sql("INSERT INTO t VALUES (map('a', 
'123456'))"))
+        // Gluten exception differs from Spark
+        assert(e2.getMessage.contains(VELOX_ERROR_MESSAGE))
+    }
+  }
+
+  testGluten("SPARK-42611: check char/varchar length in reordered structs 
within map keys") {
+    Seq("CHAR(5)", "VARCHAR(5)").foreach {
+      typ =>
+        withTable("t") {
+          sql(s"CREATE TABLE t(m MAP<STRUCT<n_c: $typ, n_i: INT>, INT>) USING 
$format")
+
+          val inputDF = sql("SELECT map(named_struct('n_i', 1, 'n_c', 
'123456'), 1) AS m")
+
+          val e = intercept[SparkException](inputDF.writeTo("t").append())
+          // Gluten exception differs from Spark
+          assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
+        }
+    }
+  }
+
+  testGluten("SPARK-42611: check char/varchar length in reordered structs 
within map values") {
+    Seq("CHAR(5)", "VARCHAR(5)").foreach {
+      typ =>
+        withTable("t") {
+          sql(s"CREATE TABLE t(m MAP<INT, STRUCT<n_c: $typ, n_i: INT>>) USING 
$format")
+
+          val inputDF = sql("SELECT map(1, named_struct('n_i', 1, 'n_c', 
'123456')) AS m")
+
+          val e = intercept[SparkException](inputDF.writeTo("t").append())
+          // Gluten exception differs from Spark
+          assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
+        }
+    }
+  }
 }
diff --git 
a/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
 
b/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
index ebe1fc50fc..a55403c356 100644
--- 
a/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
+++ 
b/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
@@ -21,10 +21,55 @@ import org.apache.gluten.exception.GlutenException
 import org.apache.spark.SparkException
 import org.apache.spark.sql.functions._
 import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types.{IntegerType, MapType, StringType}
 
 class GlutenDataFrameFunctionsSuite extends DataFrameFunctionsSuite with 
GlutenSQLTestsTrait {
   import testImplicits._
 
+  testGluten("map with arrays") {
+    val df1 = Seq((Seq(1, 2), Seq("a", "b"))).toDF("k", "v")
+    val expectedType = MapType(IntegerType, StringType, valueContainsNull = 
true)
+    val row = df1.select(map_from_arrays($"k", $"v")).first()
+    assert(row.schema(0).dataType === expectedType)
+    assert(row.getMap[Int, String](0) === Map(1 -> "a", 2 -> "b"))
+    checkAnswer(df1.select(map_from_arrays($"k", $"v")), Seq(Row(Map(1 -> "a", 
2 -> "b"))))
+
+    val df2 = Seq((Seq(1, 2), Seq(null, "b"))).toDF("k", "v")
+    checkAnswer(df2.select(map_from_arrays($"k", $"v")), Seq(Row(Map(1 -> 
null, 2 -> "b"))))
+
+    val df3 = Seq((null, null)).toDF("k", "v")
+    checkAnswer(df3.select(map_from_arrays($"k", $"v")), Seq(Row(null)))
+
+    val df4 = Seq((1, "a")).toDF("k", "v")
+    checkError(
+      exception = intercept[AnalysisException] {
+        df4.select(map_from_arrays($"k", $"v"))
+      },
+      errorClass = "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
+      parameters = Map(
+        "sqlExpr" -> "\"map_from_arrays(k, v)\"",
+        "paramIndex" -> "1",
+        "requiredType" -> "\"ARRAY\"",
+        "inputSql" -> "\"k\"",
+        "inputType" -> "\"INT\""
+      )
+    )
+
+    val df5 = Seq((Seq("a", null), Seq(1, 2))).toDF("k", "v")
+    val e1 = intercept[SparkException] {
+      df5.select(map_from_arrays($"k", $"v")).collect
+    }
+    // Gluten exception differs from Spark
+    assert(e1.getCause.isInstanceOf[GlutenException])
+    assert(e1.getCause.getMessage.contains("Cannot use null as map key"))
+
+    val df6 = Seq((Seq(1, 2), Seq("a"))).toDF("k", "v")
+    val msg2 = intercept[Exception] {
+      df6.select(map_from_arrays($"k", $"v")).collect
+    }.getMessage
+    assert(msg2.contains("The key array and value array of MapData must have 
the same length"))
+  }
+
   testGluten("map_zip_with function - map of primitive types") {
     val df = Seq(
       (Map(8 -> 6L, 3 -> 5L, 6 -> 2L), Map[Integer, Integer]((6, 4), (8, 2), 
(3, 2))),
diff --git 
a/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
 
b/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
index 49d637681b..64bf921217 100644
--- 
a/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
+++ 
b/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
@@ -463,6 +463,13 @@ class ClickHouseTestSettings extends BackendTestSettings {
     .excludeGlutenTest("length check for input string values: with implicit 
cast")
     .excludeGlutenTest("char/varchar type values length check: partitioned 
columns of other types")
     .excludeGlutenTest("SPARK-42611: check char/varchar length in reordered 
structs within arrays")
+    .excludeGlutenTest("length check for input string values: nested in map 
key")
+    .excludeGlutenTest("length check for input string values: nested in map 
value")
+    .excludeGlutenTest("length check for input string values: nested in both 
map key and value")
+    .excludeGlutenTest(
+      "SPARK-42611: check char/varchar length in reordered structs within map 
keys")
+    .excludeGlutenTest(
+      "SPARK-42611: check char/varchar length in reordered structs within map 
values")
   enableSuite[GlutenDSV2SQLInsertTestSuite]
   enableSuite[GlutenDataFrameAggregateSuite]
     // Test for vanilla spark codegen, not apply for Gluten
@@ -504,6 +511,7 @@ class ClickHouseTestSettings extends BackendTestSettings {
     // Rewrite this test because Velox sorts rows by key for primitive data 
types, which disrupts the original row sequence.
     .includeCH("map_zip_with function - map of primitive types")
     .excludeCH("map with arrays")
+    .excludeGlutenTest("map with arrays")
     .excludeCH("flatten function")
     .excludeCH("SPARK-41233: array prepend")
     .excludeCH("array_insert functions")
@@ -882,6 +890,9 @@ class ClickHouseTestSettings extends BackendTestSettings {
     .excludeGlutenTest("length check for input string values: nested in array")
     .excludeGlutenTest("length check for input string values: nested in array 
of struct")
     .excludeGlutenTest("length check for input string values: nested in array 
of array")
+    .excludeGlutenTest("length check for input string values: nested in map 
key")
+    .excludeGlutenTest("length check for input string values: nested in map 
value")
+    .excludeGlutenTest("length check for input string values: nested in both 
map key and value")
   enableSuite[GlutenFileSourceCustomMetadataStructSuite]
   enableSuite[GlutenFileSourceSQLInsertTestSuite]
     .excludeCH("SPARK-33474: Support typed literals as partition spec values")
@@ -2034,6 +2045,9 @@ class ClickHouseTestSettings extends BackendTestSettings {
   enableSuite[GlutenResolvedDataSourceSuite]
   enableSuite[GlutenReuseExchangeAndSubquerySuite]
   enableSuite[GlutenRuntimeNullChecksV2Writes]
+    .excludeGlutenTest("NOT NULL checks for nullable map with required values 
(byName)")
+    .excludeGlutenTest("NOT NULL checks for nullable map with required values 
(byPosition)")
+    .excludeGlutenTest("NOT NULL checks for fields inside nullable maps 
(byPosition)")
   enableSuite[GlutenSQLAggregateFunctionSuite]
     .excludeGlutenTest("Return NaN or null when dividing by zero")
   enableSuite[GlutenSQLQuerySuite]
diff --git 
a/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
 
b/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
index d26fcbf494..72ef076d04 100644
--- 
a/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
+++ 
b/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
@@ -646,6 +646,16 @@ class VeloxTestSettings extends BackendTestSettings {
     .exclude("length check for input string values: with implicit cast")
     .exclude("char/varchar type values length check: partitioned columns of 
other types")
     .exclude("SPARK-42611: check char/varchar length in reordered structs 
within arrays")
+    // Overridden.
+    .exclude("length check for input string values: nested in map key")
+    // Overridden.
+    .exclude("length check for input string values: nested in map value")
+    // Overridden.
+    .exclude("length check for input string values: nested in both map key and 
value")
+    // Overridden.
+    .exclude("SPARK-42611: check char/varchar length in reordered structs 
within map keys")
+    // Overridden.
+    .exclude("SPARK-42611: check char/varchar length in reordered structs 
within map values")
   enableSuite[GlutenColumnExpressionSuite]
     // Velox raise_error('errMsg') throws a velox_user_error exception with 
the message 'errMsg'.
     // The final caught Spark exception's getCause().getMessage() contains 
'errMsg' but does not
@@ -688,6 +698,8 @@ class VeloxTestSettings extends BackendTestSettings {
     .exclude("aggregate function - array for non-primitive type")
     // Rewrite this test because Velox sorts rows by key for primitive data 
types, which disrupts the original row sequence.
     .exclude("map_zip_with function - map of primitive types")
+    // Overridden.
+    .exclude("map with arrays")
   enableSuite[GlutenDataFrameHintSuite]
   enableSuite[GlutenDataFrameImplicitsSuite]
   enableSuite[GlutenDataFrameJoinSuite]
@@ -945,6 +957,12 @@ class VeloxTestSettings extends BackendTestSettings {
     .exclude("NOT NULL checks for nested structs, arrays, maps (byPosition)")
     .exclude("NOT NULL checks for nullable array with required element 
(byPosition)")
     .exclude("not null checks for fields inside nullable array (byPosition)")
+    // Overridden.
+    .exclude("NOT NULL checks for nullable map with required values (byName)")
+    // Overridden.
+    .exclude("NOT NULL checks for nullable map with required values 
(byPosition)")
+    // Overridden.
+    .exclude("NOT NULL checks for fields inside nullable maps (byPosition)")
   enableSuite[GlutenTableOptionsConstantFoldingSuite]
   enableSuite[GlutenDeltaBasedMergeIntoTableSuite]
   enableSuite[GlutenDeltaBasedMergeIntoTableUpdateAsDeleteAndInsertSuite]
diff --git 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenCharVarcharTestSuite.scala
 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenCharVarcharTestSuite.scala
index 689946547d..af23dc8a1e 100644
--- 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenCharVarcharTestSuite.scala
+++ 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenCharVarcharTestSuite.scala
@@ -63,7 +63,8 @@ class GlutenFileSourceCharVarcharTestSuite
       typeName =>
         sql(s"CREATE TABLE t(c MAP<$typeName(5), STRING>) USING $format")
         val e = intercept[SparkException](sql("INSERT INTO t VALUES 
(map('123456', 'a'))"))
-        assert(e.getMessage.contains(ERROR_MESSAGE))
+        // Gluten exception differs from Spark
+        assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
     }
   }
 
@@ -74,7 +75,8 @@ class GlutenFileSourceCharVarcharTestSuite
         sql("INSERT INTO t VALUES (map('a', null))")
         checkAnswer(spark.table("t"), Row(Map("a" -> null)))
         val e = intercept[SparkException](sql("INSERT INTO t VALUES (map('a', 
'123456'))"))
-        assert(e.getMessage.contains(ERROR_MESSAGE))
+        // Gluten exception differs from Spark
+        assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
     }
   }
 
@@ -83,9 +85,11 @@ class GlutenFileSourceCharVarcharTestSuite
       typeName =>
         sql(s"CREATE TABLE t(c MAP<$typeName(5), $typeName(5)>) USING $format")
         val e1 = intercept[SparkException](sql("INSERT INTO t VALUES 
(map('123456', 'a'))"))
-        assert(e1.getMessage.contains(ERROR_MESSAGE))
+        // Gluten exception differs from Spark
+        assert(e1.getMessage.contains(VELOX_ERROR_MESSAGE))
         val e2 = intercept[SparkException](sql("INSERT INTO t VALUES (map('a', 
'123456'))"))
-        assert(e2.getMessage.contains(ERROR_MESSAGE))
+        // Gluten exception differs from Spark
+        assert(e2.getMessage.contains(VELOX_ERROR_MESSAGE))
     }
   }
 
@@ -300,4 +304,69 @@ class GlutenDSV2CharVarcharTestSuite extends 
DSV2CharVarcharTestSuite with Glute
         }
     }
   }
+
+  testGluten("length check for input string values: nested in map key") {
+    testTableWrite {
+      typeName =>
+        sql(s"CREATE TABLE t(c MAP<$typeName(5), STRING>) USING $format")
+        val e = intercept[SparkException](sql("INSERT INTO t VALUES 
(map('123456', 'a'))"))
+        // Gluten exception differs from Spark
+        assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
+    }
+  }
+
+  testGluten("length check for input string values: nested in map value") {
+    testTableWrite {
+      typeName =>
+        sql(s"CREATE TABLE t(c MAP<STRING, $typeName(5)>) USING $format")
+        sql("INSERT INTO t VALUES (map('a', null))")
+        checkAnswer(spark.table("t"), Row(Map("a" -> null)))
+        val e = intercept[SparkException](sql("INSERT INTO t VALUES (map('a', 
'123456'))"))
+        // Gluten exception differs from Spark
+        assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
+    }
+  }
+
+  testGluten("length check for input string values: nested in both map key and 
value") {
+    testTableWrite {
+      typeName =>
+        sql(s"CREATE TABLE t(c MAP<$typeName(5), $typeName(5)>) USING $format")
+        val e1 = intercept[SparkException](sql("INSERT INTO t VALUES 
(map('123456', 'a'))"))
+        // Gluten exception differs from Spark
+        assert(e1.getMessage.contains(VELOX_ERROR_MESSAGE))
+        val e2 = intercept[SparkException](sql("INSERT INTO t VALUES (map('a', 
'123456'))"))
+        // Gluten exception differs from Spark
+        assert(e2.getMessage.contains(VELOX_ERROR_MESSAGE))
+    }
+  }
+
+  testGluten("SPARK-42611: check char/varchar length in reordered structs 
within map keys") {
+    Seq("CHAR(5)", "VARCHAR(5)").foreach {
+      typ =>
+        withTable("t") {
+          sql(s"CREATE TABLE t(m MAP<STRUCT<n_c: $typ, n_i: INT>, INT>) USING 
$format")
+
+          val inputDF = sql("SELECT map(named_struct('n_i', 1, 'n_c', 
'123456'), 1) AS m")
+
+          val e = intercept[SparkException](inputDF.writeTo("t").append())
+          // Gluten exception differs from Spark
+          assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
+        }
+    }
+  }
+
+  testGluten("SPARK-42611: check char/varchar length in reordered structs 
within map values") {
+    Seq("CHAR(5)", "VARCHAR(5)").foreach {
+      typ =>
+        withTable("t") {
+          sql(s"CREATE TABLE t(m MAP<INT, STRUCT<n_c: $typ, n_i: INT>>) USING 
$format")
+
+          val inputDF = sql("SELECT map(1, named_struct('n_i', 1, 'n_c', 
'123456')) AS m")
+
+          val e = intercept[SparkException](inputDF.writeTo("t").append())
+          // Gluten exception differs from Spark
+          assert(e.getMessage.contains(VELOX_ERROR_MESSAGE))
+        }
+    }
+  }
 }
diff --git 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
index e64f760ab5..d19bb06649 100644
--- 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
+++ 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
@@ -16,11 +16,59 @@
  */
 package org.apache.spark.sql
 
+import org.apache.gluten.exception.GlutenException
+
+import org.apache.spark.SparkException
 import org.apache.spark.sql.functions._
+import org.apache.spark.sql.types.{IntegerType, MapType, StringType}
 
 class GlutenDataFrameFunctionsSuite extends DataFrameFunctionsSuite with 
GlutenSQLTestsTrait {
   import testImplicits._
 
+  testGluten("map with arrays") {
+    val df1 = Seq((Seq(1, 2), Seq("a", "b"))).toDF("k", "v")
+    val expectedType = MapType(IntegerType, StringType, valueContainsNull = 
true)
+    val row = df1.select(map_from_arrays($"k", $"v")).first()
+    assert(row.schema(0).dataType === expectedType)
+    assert(row.getMap[Int, String](0) === Map(1 -> "a", 2 -> "b"))
+    checkAnswer(df1.select(map_from_arrays($"k", $"v")), Seq(Row(Map(1 -> "a", 
2 -> "b"))))
+
+    val df2 = Seq((Seq(1, 2), Seq(null, "b"))).toDF("k", "v")
+    checkAnswer(df2.select(map_from_arrays($"k", $"v")), Seq(Row(Map(1 -> 
null, 2 -> "b"))))
+
+    val df3 = Seq((null, null)).toDF("k", "v")
+    checkAnswer(df3.select(map_from_arrays($"k", $"v")), Seq(Row(null)))
+
+    val df4 = Seq((1, "a")).toDF("k", "v")
+    checkError(
+      exception = intercept[AnalysisException] {
+        df4.select(map_from_arrays($"k", $"v"))
+      },
+      errorClass = "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
+      parameters = Map(
+        "sqlExpr" -> "\"map_from_arrays(k, v)\"",
+        "paramIndex" -> "1",
+        "requiredType" -> "\"ARRAY\"",
+        "inputSql" -> "\"k\"",
+        "inputType" -> "\"INT\""
+      )
+    )
+
+    val df5 = Seq((Seq("a", null), Seq(1, 2))).toDF("k", "v")
+    val e1 = intercept[SparkException] {
+      df5.select(map_from_arrays($"k", $"v")).collect
+    }
+    // Gluten exception differs from Spark
+    assert(e1.getCause.isInstanceOf[GlutenException])
+    assert(e1.getCause.getMessage.contains("Cannot use null as map key"))
+
+    val df6 = Seq((Seq(1, 2), Seq("a"))).toDF("k", "v")
+    val msg2 = intercept[Exception] {
+      df6.select(map_from_arrays($"k", $"v")).collect
+    }.getMessage
+    assert(msg2.contains("The key array and value array of MapData must have 
the same length"))
+  }
+
   testGluten("map_zip_with function - map of primitive types") {
     val df = Seq(
       (Map(8 -> 6L, 3 -> 5L, 6 -> 2L), Map[Integer, Integer]((6, 4), (8, 2), 
(3, 2))),
diff --git 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
index abd997bea8..0ef7eb2da9 100644
--- 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
+++ 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
@@ -16,4 +16,137 @@
  */
 package org.apache.spark.sql
 
-class GlutenRuntimeNullChecksV2Writes extends RuntimeNullChecksV2Writes with 
GlutenSQLTestsTrait {}
+import org.apache.spark.SparkException
+import org.apache.spark.sql.connector.catalog.{Column => ColumnV2, Identifier}
+import org.apache.spark.sql.connector.expressions.Transform
+import org.apache.spark.sql.types.{IntegerType, MapType, StructType}
+
+import java.util.Collections
+
+class GlutenRuntimeNullChecksV2Writes extends RuntimeNullChecksV2Writes with 
GlutenSQLTestsTrait {
+
+  /**
+   * Shadows Spark's `assertNotNullException`, which is private and so cannot 
be reused.
+   *
+   * Spark requires the cause to be a `NullPointerException` and matches the 
offending column path
+   * against `colPath.mkString("\n", "\n", "\n")`. Velox raises a 
`VeloxUserError` that Gluten
+   * surfaces as a `SparkException`, carrying the same reason text but not 
Spark's column path
+   * formatting, so `colPath` is reported on failure rather than asserted. The 
reason match is case
+   * insensitive because Spark 3.5 words it "Null value appeared ..." and 
Spark 4.x "NULL value
+   * appeared ...".
+   */
+  private def assertNotNullException(e: SparkException, colPath: Seq[String]): 
Unit = {
+    val messages = Iterator
+      .iterate[Throwable](e)(_.getCause)
+      .takeWhile(_ != null)
+      .flatMap(t => Option(t.getMessage))
+      .mkString("\n")
+
+    assert(
+      messages.toLowerCase(java.util.Locale.ROOT).contains("value appeared in 
non-nullable field"),
+      s"expected a not-null violation for ${colPath.mkString(".")}, 
got:\n$messages"
+    )
+  }
+
+  testGluten("NOT NULL checks for nullable map with required values (byName)") 
{
+    checkNullableMapWithNonNullValues(byName = true)
+  }
+
+  testGluten("NOT NULL checks for nullable map with required values 
(byPosition)") {
+    checkNullableMapWithNonNullValues(byName = false)
+  }
+
+  private def checkNullableMapWithNonNullValues(byName: Boolean): Unit = {
+    withTable("t") {
+      catalog.createTable(
+        ident = Identifier.of(Array(), "t"),
+        columns = Array(
+          ColumnV2.create("i", IntegerType),
+          ColumnV2.create("m", MapType(IntegerType, IntegerType, 
valueContainsNull = false))),
+        partitions = Array.empty[Transform],
+        properties = Collections.emptyMap[String, String]
+      )
+
+      if (byName) {
+        val inputDF = sql("SELECT 1 AS i, null AS m")
+        inputDF.writeTo("t").append()
+      } else {
+        sql("INSERT INTO t VALUES (1 AS i, null AS m)")
+      }
+      checkAnswer(spark.table("t"), Row(1, null))
+
+      val e = intercept[SparkException] {
+        if (byName) {
+          val inputDF = sql("SELECT 1 AS i, map(1, null) AS m")
+          inputDF.writeTo("t").append()
+        } else {
+          sql("INSERT INTO t VALUES (1 AS i, map(1, null) AS m)")
+        }
+      }
+      assertNotNullException(e, Seq("m", "value"))
+    }
+  }
+
+  /** Only the byPosition case is overridden. */
+  testGluten("NOT NULL checks for fields inside nullable maps (byPosition)") {
+    checkNotNullFieldsInsideNullableMap(byName = false)
+  }
+
+  private def checkNotNullFieldsInsideNullableMap(byName: Boolean): Unit = {
+    withTable("t") {
+      val structType = new StructType().add("x", "int", nullable = 
false).add("y", "int")
+      catalog.createTable(
+        ident = Identifier.of(Array(), "t"),
+        columns = Array(
+          ColumnV2.create("i", IntegerType),
+          ColumnV2.create("m", MapType(structType, structType, 
valueContainsNull = true))),
+        partitions = Array.empty[Transform],
+        properties = Collections.emptyMap[String, String]
+      )
+
+      if (byName) {
+        val inputDF = sql("SELECT 1 AS i, map(named_struct('x', 1, 'y', 1), 
null) AS m")
+        inputDF.writeTo("t").append()
+      } else {
+        sql("INSERT INTO t VALUES (1 AS i, map(named_struct('x', 1, 'y', 1), 
null) AS m)")
+      }
+      checkAnswer(spark.table("t"), Row(1, Map(Row(1, 1) -> null)))
+
+      val e1 = intercept[SparkException] {
+        if (byName) {
+          val inputDF = sql(
+            s"""SELECT
+               | 1 AS i,
+               | map(named_struct('x', null, 'y', 1), null) AS m
+             """.stripMargin)
+          inputDF.writeTo("t").append()
+        } else {
+          sql(
+            s"""INSERT INTO t VALUES (
+               | 1 AS i,
+               | map(named_struct('x', null, 'y', 1), null) AS m)
+             """.stripMargin)
+        }
+      }
+      assertNotNullException(e1, Seq("m", "key", "x"))
+
+      val e2 = intercept[SparkException] {
+        if (byName) {
+          val inputDF = sql(
+            s"""SELECT
+               | 1 AS i,
+               | map(named_struct('x', 1, 'y', 1), named_struct('x', null, 
'y', 1)) AS m
+             """.stripMargin)
+          inputDF.writeTo("t").append()
+        } else {
+          sql(
+            s"""INSERT INTO t VALUES (
+               | 1 AS i,
+               | map(named_struct('x', 1, 'y', 1), named_struct('x', null, 
'y', 1)) AS m)
+             """.stripMargin)
+        }
+      }
+      assertNotNullException(e2, Seq("m", "value", "x"))
+    }
+  }
+}
diff --git 
a/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
 
b/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
index 90ad2f598d..decdc1990d 100644
--- 
a/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
+++ 
b/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
@@ -489,6 +489,7 @@ class ClickHouseTestSettings extends BackendTestSettings {
     // Rewrite this test because Velox sorts rows by key for primitive data 
types, which disrupts the original row sequence.
     .includeCH("map_zip_with function - map of primitive types")
     .excludeCH("map with arrays")
+    .excludeGlutenTest("map with arrays")
     .excludeCH("flatten function")
     .excludeCH("SPARK-41233: array prepend")
     .excludeCH("array_insert functions")
@@ -2002,6 +2003,9 @@ class ClickHouseTestSettings extends BackendTestSettings {
   enableSuite[GlutenResolvedDataSourceSuite]
   enableSuite[GlutenReuseExchangeAndSubquerySuite]
   enableSuite[GlutenRuntimeNullChecksV2Writes]
+    .excludeGlutenTest("NOT NULL checks for nullable map with required values 
(byName)")
+    .excludeGlutenTest("NOT NULL checks for nullable map with required values 
(byPosition)")
+    .excludeGlutenTest("NOT NULL checks for fields inside nullable maps 
(byPosition)")
   enableSuite[GlutenSQLAggregateFunctionSuite]
     .excludeGlutenTest("Return NaN or null when dividing by zero")
   enableSuite[GlutenSQLQuerySuite]
diff --git 
a/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
 
b/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
index 10f467378e..0ae8965619 100644
--- 
a/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
+++ 
b/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
@@ -983,6 +983,8 @@ class VeloxTestSettings extends BackendTestSettings {
     // Vanilla spark throw SparkRuntimeException, gluten throw SparkException.
     .exclude("map_concat function")
     .exclude("transform keys function - primitive data types")
+    // Overridden.
+    .exclude("map with arrays")
   enableSuite[GlutenDataFrameHintSuite]
   enableSuite[GlutenDataFrameImplicitsSuite]
   enableSuite[GlutenDataFrameJoinSuite]
@@ -1227,6 +1229,12 @@ class VeloxTestSettings extends BackendTestSettings {
     .exclude("NOT NULL checks for nested structs, arrays, maps (byPosition)")
     .exclude("NOT NULL checks for nullable array with required element 
(byPosition)")
     .exclude("not null checks for fields inside nullable array (byPosition)")
+    // Overridden.
+    .exclude("NOT NULL checks for nullable map with required values (byName)")
+    // Overridden.
+    .exclude("NOT NULL checks for nullable map with required values 
(byPosition)")
+    // Overridden.
+    .exclude("NOT NULL checks for fields inside nullable maps (byPosition)")
   enableSuite[GlutenTableOptionsConstantFoldingSuite]
   enableSuite[GlutenDeltaBasedMergeIntoTableSuite]
     // Replaced by Gluten versions that handle wrapped exceptions
diff --git 
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
 
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
index 0d2caf6100..40ac1edd60 100644
--- 
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
+++ 
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
@@ -16,6 +16,8 @@
  */
 package org.apache.spark.sql
 
+import org.apache.gluten.exception.GlutenException
+
 import org.apache.spark.SparkException
 import org.apache.spark.sql.functions._
 import org.apache.spark.sql.internal.SQLConf
@@ -24,6 +26,54 @@ import org.apache.spark.sql.types.{IntegerType, MapType, 
StringType, StructField
 class GlutenDataFrameFunctionsSuite extends DataFrameFunctionsSuite with 
GlutenSQLTestsTrait {
   import testImplicits._
 
+  testGluten("map with arrays") {
+    val df1 = Seq((Seq(1, 2), Seq("a", "b"))).toDF("k", "v")
+    val expectedType = MapType(IntegerType, StringType, valueContainsNull = 
true)
+    val row = df1.select(map_from_arrays($"k", $"v")).first()
+    assert(row.schema(0).dataType === expectedType)
+    assert(row.getMap[Int, String](0) === Map(1 -> "a", 2 -> "b"))
+    checkAnswer(df1.select(map_from_arrays($"k", $"v")), Seq(Row(Map(1 -> "a", 
2 -> "b"))))
+
+    val df2 = Seq((Seq(1, 2), Seq(null, "b"))).toDF("k", "v")
+    checkAnswer(df2.select(map_from_arrays($"k", $"v")), Seq(Row(Map(1 -> 
null, 2 -> "b"))))
+
+    val df3 = Seq((null, null)).toDF("k", "v")
+    checkAnswer(df3.select(map_from_arrays($"k", $"v")), Seq(Row(null)))
+
+    val df4 = Seq((1, "a")).toDF("k", "v")
+    checkError(
+      exception = intercept[AnalysisException] {
+        df4.select(map_from_arrays($"k", $"v"))
+      },
+      condition = "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
+      parameters = Map(
+        "sqlExpr" -> "\"map_from_arrays(k, v)\"",
+        "paramIndex" -> "first",
+        "requiredType" -> "\"ARRAY\"",
+        "inputSql" -> "\"k\"",
+        "inputType" -> "\"INT\""
+      ),
+      queryContext = Array(
+        ExpectedContext(
+          fragment = "map_from_arrays",
+          callSitePattern = getCurrentClassCallSitePattern))
+    )
+
+    val df5 = Seq((Seq("a", null), Seq(1, 2))).toDF("k", "v")
+    // Gluten exception differs from Spark
+    val e1 = intercept[SparkException] {
+      df5.select(map_from_arrays($"k", $"v")).collect()
+    }
+    assert(e1.getCause.isInstanceOf[GlutenException])
+    assert(e1.getCause.getMessage.contains("Cannot use null as map key"))
+
+    val df6 = Seq((Seq(1, 2), Seq("a"))).toDF("k", "v")
+    val msg2 = intercept[Exception] {
+      df6.select(map_from_arrays($"k", $"v")).collect()
+    }.getMessage
+    assert(msg2.contains("The key array and value array of MapData must have 
the same length"))
+  }
+
   testGluten("map_zip_with function - map of primitive types") {
     val df = Seq(
       (Map(8 -> 6L, 3 -> 5L, 6 -> 2L), Map[Integer, Integer]((6, 4), (8, 2), 
(3, 2))),
diff --git 
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
 
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
index abd997bea8..d0cd89b643 100644
--- 
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
+++ 
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
@@ -16,4 +16,139 @@
  */
 package org.apache.spark.sql
 
-class GlutenRuntimeNullChecksV2Writes extends RuntimeNullChecksV2Writes with 
GlutenSQLTestsTrait {}
+import org.apache.spark.SparkException
+import org.apache.spark.sql.connector.catalog.{Column => ColumnV2, Identifier}
+import org.apache.spark.sql.connector.expressions.Transform
+import org.apache.spark.sql.types.{IntegerType, MapType, StructType}
+
+import java.util.Collections
+
+class GlutenRuntimeNullChecksV2Writes extends RuntimeNullChecksV2Writes with 
GlutenSQLTestsTrait {
+
+  /**
+   * Shadows Spark's `assertNotNullException`, which is private and so cannot 
be reused.
+   *
+   * Spark expects a `SparkRuntimeException` carrying 
`NOT_NULL_ASSERT_VIOLATION`, asserts nothing
+   * else, and never reads `colPath`. Velox raises a `VeloxUserError` that 
Gluten surfaces as a
+   * `SparkException`, which carries the same reason text but not the error 
condition, so this
+   * asserts the reason and reports `colPath` on failure. The reason match is 
case insensitive
+   * because Spark 3.5 words it "Null value appeared ..." and Spark 4.x "NULL 
value appeared ...".
+   */
+  private def assertNotNullException(e: SparkException, colPath: Seq[String]): 
Unit = {
+    val messages = Iterator
+      .iterate[Throwable](e)(_.getCause)
+      .takeWhile(_ != null)
+      .flatMap(t => Option(t.getMessage))
+      .mkString("\n")
+
+    assert(
+      messages.toLowerCase(java.util.Locale.ROOT).contains("value appeared in 
non-nullable field"),
+      s"expected a not-null violation for ${colPath.mkString(".")}, 
got:\n$messages"
+    )
+  }
+
+  testGluten("NOT NULL checks for nullable map with required values (byName)") 
{
+    checkNullableMapWithNonNullValues(byName = true)
+  }
+
+  testGluten("NOT NULL checks for nullable map with required values 
(byPosition)") {
+    checkNullableMapWithNonNullValues(byName = false)
+  }
+
+  private def checkNullableMapWithNonNullValues(byName: Boolean): Unit = {
+    withTable("t") {
+      catalog.createTable(
+        ident = Identifier.of(Array(), "t"),
+        columns = Array(
+          ColumnV2.create("i", IntegerType),
+          ColumnV2.create("m", MapType(IntegerType, IntegerType, 
valueContainsNull = false))),
+        partitions = Array.empty[Transform],
+        properties = Collections.emptyMap[String, String]
+      )
+
+      if (byName) {
+        val inputDF = sql("SELECT 1 AS i, null AS m")
+        inputDF.writeTo("t").append()
+      } else {
+        sql("INSERT INTO t VALUES (1 AS i, null AS m)")
+      }
+      checkAnswer(spark.table("t"), Row(1, null))
+
+      // Gluten exception differs from Spark
+      val e = intercept[SparkException] {
+        if (byName) {
+          val inputDF = sql("SELECT 1 AS i, map(1, null) AS m")
+          inputDF.writeTo("t").append()
+        } else {
+          sql("INSERT INTO t VALUES (1 AS i, map(1, null) AS m)")
+        }
+      }
+      assertNotNullException(e, Seq("m", "value"))
+    }
+  }
+
+  /** Only the byPosition case is overridden. */
+  testGluten("NOT NULL checks for fields inside nullable maps (byPosition)") {
+    checkNotNullFieldsInsideNullableMap(byName = false)
+  }
+
+  private def checkNotNullFieldsInsideNullableMap(byName: Boolean): Unit = {
+    withTable("t") {
+      val structType = new StructType().add("x", "int", nullable = 
false).add("y", "int")
+      catalog.createTable(
+        ident = Identifier.of(Array(), "t"),
+        columns = Array(
+          ColumnV2.create("i", IntegerType),
+          ColumnV2.create("m", MapType(structType, structType, 
valueContainsNull = true))),
+        partitions = Array.empty[Transform],
+        properties = Collections.emptyMap[String, String]
+      )
+
+      if (byName) {
+        val inputDF = sql("SELECT 1 AS i, map(named_struct('x', 1, 'y', 1), 
null) AS m")
+        inputDF.writeTo("t").append()
+      } else {
+        sql("INSERT INTO t VALUES (1 AS i, map(named_struct('x', 1, 'y', 1), 
null) AS m)")
+      }
+      checkAnswer(spark.table("t"), Row(1, Map(Row(1, 1) -> null)))
+
+      // Gluten exception differs from Spark
+      val e1 = intercept[SparkException] {
+        if (byName) {
+          val inputDF = sql(
+            s"""SELECT
+               | 1 AS i,
+               | map(named_struct('x', null, 'y', 1), null) AS m
+             """.stripMargin)
+          inputDF.writeTo("t").append()
+        } else {
+          sql(
+            s"""INSERT INTO t VALUES (
+               | 1 AS i,
+               | map(named_struct('x', null, 'y', 1), null) AS m)
+             """.stripMargin)
+        }
+      }
+      assertNotNullException(e1, Seq("m", "key", "x"))
+
+      // Gluten exception differs from Spark
+      val e2 = intercept[SparkException] {
+        if (byName) {
+          val inputDF = sql(
+            s"""SELECT
+               | 1 AS i,
+               | map(named_struct('x', 1, 'y', 1), named_struct('x', null, 
'y', 1)) AS m
+             """.stripMargin)
+          inputDF.writeTo("t").append()
+        } else {
+          sql(
+            s"""INSERT INTO t VALUES (
+               | 1 AS i,
+               | map(named_struct('x', 1, 'y', 1), named_struct('x', null, 
'y', 1)) AS m)
+             """.stripMargin)
+        }
+      }
+      assertNotNullException(e2, Seq("m", "value", "x"))
+    }
+  }
+}
diff --git 
a/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
 
b/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
index 90ad2f598d..decdc1990d 100644
--- 
a/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
+++ 
b/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/clickhouse/ClickHouseTestSettings.scala
@@ -489,6 +489,7 @@ class ClickHouseTestSettings extends BackendTestSettings {
     // Rewrite this test because Velox sorts rows by key for primitive data 
types, which disrupts the original row sequence.
     .includeCH("map_zip_with function - map of primitive types")
     .excludeCH("map with arrays")
+    .excludeGlutenTest("map with arrays")
     .excludeCH("flatten function")
     .excludeCH("SPARK-41233: array prepend")
     .excludeCH("array_insert functions")
@@ -2002,6 +2003,9 @@ class ClickHouseTestSettings extends BackendTestSettings {
   enableSuite[GlutenResolvedDataSourceSuite]
   enableSuite[GlutenReuseExchangeAndSubquerySuite]
   enableSuite[GlutenRuntimeNullChecksV2Writes]
+    .excludeGlutenTest("NOT NULL checks for nullable map with required values 
(byName)")
+    .excludeGlutenTest("NOT NULL checks for nullable map with required values 
(byPosition)")
+    .excludeGlutenTest("NOT NULL checks for fields inside nullable maps 
(byPosition)")
   enableSuite[GlutenSQLAggregateFunctionSuite]
     .excludeGlutenTest("Return NaN or null when dividing by zero")
   enableSuite[GlutenSQLQuerySuite]
diff --git 
a/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
 
b/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
index 5b2757a95c..0890acd58f 100644
--- 
a/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
+++ 
b/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
@@ -967,6 +967,8 @@ class VeloxTestSettings extends BackendTestSettings {
     // Vanilla spark throw SparkRuntimeException, gluten throw SparkException.
     .exclude("map_concat function")
     .exclude("transform keys function - primitive data types")
+    // Overridden.
+    .exclude("map with arrays")
   enableSuite[GlutenDataFrameHintSuite]
   enableSuite[GlutenDataFrameImplicitsSuite]
   enableSuite[GlutenDataFrameJoinSuite]
@@ -1224,6 +1226,12 @@ class VeloxTestSettings extends BackendTestSettings {
     .exclude("NOT NULL checks for nested structs, arrays, maps (byPosition)")
     .exclude("NOT NULL checks for nullable array with required element 
(byPosition)")
     .exclude("not null checks for fields inside nullable array (byPosition)")
+    // Overridden.
+    .exclude("NOT NULL checks for nullable map with required values (byName)")
+    // Overridden.
+    .exclude("NOT NULL checks for nullable map with required values 
(byPosition)")
+    // Overridden.
+    .exclude("NOT NULL checks for fields inside nullable maps (byPosition)")
   enableSuite[GlutenTableOptionsConstantFoldingSuite]
   enableSuite[GlutenDeltaBasedMergeIntoTableSuite]
     // Replaced by Gluten versions that handle wrapped exceptions
diff --git 
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
 
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
index 0d2caf6100..40ac1edd60 100644
--- 
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
+++ 
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/GlutenDataFrameFunctionsSuite.scala
@@ -16,6 +16,8 @@
  */
 package org.apache.spark.sql
 
+import org.apache.gluten.exception.GlutenException
+
 import org.apache.spark.SparkException
 import org.apache.spark.sql.functions._
 import org.apache.spark.sql.internal.SQLConf
@@ -24,6 +26,54 @@ import org.apache.spark.sql.types.{IntegerType, MapType, 
StringType, StructField
 class GlutenDataFrameFunctionsSuite extends DataFrameFunctionsSuite with 
GlutenSQLTestsTrait {
   import testImplicits._
 
+  testGluten("map with arrays") {
+    val df1 = Seq((Seq(1, 2), Seq("a", "b"))).toDF("k", "v")
+    val expectedType = MapType(IntegerType, StringType, valueContainsNull = 
true)
+    val row = df1.select(map_from_arrays($"k", $"v")).first()
+    assert(row.schema(0).dataType === expectedType)
+    assert(row.getMap[Int, String](0) === Map(1 -> "a", 2 -> "b"))
+    checkAnswer(df1.select(map_from_arrays($"k", $"v")), Seq(Row(Map(1 -> "a", 
2 -> "b"))))
+
+    val df2 = Seq((Seq(1, 2), Seq(null, "b"))).toDF("k", "v")
+    checkAnswer(df2.select(map_from_arrays($"k", $"v")), Seq(Row(Map(1 -> 
null, 2 -> "b"))))
+
+    val df3 = Seq((null, null)).toDF("k", "v")
+    checkAnswer(df3.select(map_from_arrays($"k", $"v")), Seq(Row(null)))
+
+    val df4 = Seq((1, "a")).toDF("k", "v")
+    checkError(
+      exception = intercept[AnalysisException] {
+        df4.select(map_from_arrays($"k", $"v"))
+      },
+      condition = "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
+      parameters = Map(
+        "sqlExpr" -> "\"map_from_arrays(k, v)\"",
+        "paramIndex" -> "first",
+        "requiredType" -> "\"ARRAY\"",
+        "inputSql" -> "\"k\"",
+        "inputType" -> "\"INT\""
+      ),
+      queryContext = Array(
+        ExpectedContext(
+          fragment = "map_from_arrays",
+          callSitePattern = getCurrentClassCallSitePattern))
+    )
+
+    val df5 = Seq((Seq("a", null), Seq(1, 2))).toDF("k", "v")
+    // Gluten exception differs from Spark
+    val e1 = intercept[SparkException] {
+      df5.select(map_from_arrays($"k", $"v")).collect()
+    }
+    assert(e1.getCause.isInstanceOf[GlutenException])
+    assert(e1.getCause.getMessage.contains("Cannot use null as map key"))
+
+    val df6 = Seq((Seq(1, 2), Seq("a"))).toDF("k", "v")
+    val msg2 = intercept[Exception] {
+      df6.select(map_from_arrays($"k", $"v")).collect()
+    }.getMessage
+    assert(msg2.contains("The key array and value array of MapData must have 
the same length"))
+  }
+
   testGluten("map_zip_with function - map of primitive types") {
     val df = Seq(
       (Map(8 -> 6L, 3 -> 5L, 6 -> 2L), Map[Integer, Integer]((6, 4), (8, 2), 
(3, 2))),
diff --git 
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
 
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
index abd997bea8..642ad786fb 100644
--- 
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
+++ 
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/GlutenRuntimeNullChecksV2Writes.scala
@@ -16,4 +16,136 @@
  */
 package org.apache.spark.sql
 
-class GlutenRuntimeNullChecksV2Writes extends RuntimeNullChecksV2Writes with 
GlutenSQLTestsTrait {}
+import org.apache.spark.SparkException
+import org.apache.spark.sql.connector.catalog.{Column => ColumnV2, Identifier, 
TableInfo}
+import org.apache.spark.sql.types.{IntegerType, MapType, StructType}
+
+class GlutenRuntimeNullChecksV2Writes extends RuntimeNullChecksV2Writes with 
GlutenSQLTestsTrait {
+
+  /**
+   * Shadows Spark's `assertNotNullException`, which is private and so cannot 
be reused.
+   *
+   * Spark expects a `SparkRuntimeException` carrying 
`NOT_NULL_ASSERT_VIOLATION`, asserts nothing
+   * else, and never reads `colPath`. Velox raises a `VeloxUserError` that 
Gluten surfaces as a
+   * `SparkException`, which carries the same reason text but not the error 
condition, so this
+   * asserts the reason and reports `colPath` on failure. The reason match is 
case insensitive
+   * because Spark 3.5 words it "Null value appeared ..." and Spark 4.x "NULL 
value appeared ...".
+   */
+  private def assertNotNullException(e: SparkException, colPath: Seq[String]): 
Unit = {
+    val messages = Iterator
+      .iterate[Throwable](e)(_.getCause)
+      .takeWhile(_ != null)
+      .flatMap(t => Option(t.getMessage))
+      .mkString("\n")
+
+    assert(
+      messages.toLowerCase(java.util.Locale.ROOT).contains("value appeared in 
non-nullable field"),
+      s"expected a not-null violation for ${colPath.mkString(".")}, 
got:\n$messages"
+    )
+  }
+
+  testGluten("NOT NULL checks for nullable map with required values (byName)") 
{
+    checkNullableMapWithNonNullValues(byName = true)
+  }
+
+  testGluten("NOT NULL checks for nullable map with required values 
(byPosition)") {
+    checkNullableMapWithNonNullValues(byName = false)
+  }
+
+  private def checkNullableMapWithNonNullValues(byName: Boolean): Unit = {
+    withTable("t") {
+      val tableInfo = new TableInfo.Builder()
+        .withColumns(Array(
+          ColumnV2.create("i", IntegerType),
+          ColumnV2.create("m", MapType(IntegerType, IntegerType, 
valueContainsNull = false))))
+        .build()
+      catalog.createTable(
+        ident = Identifier.of(Array(), "t"),
+        tableInfo = tableInfo)
+
+      if (byName) {
+        val inputDF = sql("SELECT 1 AS i, null AS m")
+        inputDF.writeTo("t").append()
+      } else {
+        sql("INSERT INTO t VALUES (1 AS i, null AS m)")
+      }
+      checkAnswer(spark.table("t"), Row(1, null))
+
+      // Gluten exception differs from Spark
+      val e = intercept[SparkException] {
+        if (byName) {
+          val inputDF = sql("SELECT 1 AS i, map(1, null) AS m")
+          inputDF.writeTo("t").append()
+        } else {
+          sql("INSERT INTO t VALUES (1 AS i, map(1, null) AS m)")
+        }
+      }
+      assertNotNullException(e, Seq("m", "value"))
+    }
+  }
+
+  /** Only the byPosition case is overridden. */
+  testGluten("NOT NULL checks for fields inside nullable maps (byPosition)") {
+    checkNotNullFieldsInsideNullableMap(byName = false)
+  }
+
+  private def checkNotNullFieldsInsideNullableMap(byName: Boolean): Unit = {
+    withTable("t") {
+      val structType = new StructType().add("x", "int", nullable = 
false).add("y", "int")
+      val tableInfo = new TableInfo.Builder()
+        .withColumns(Array(
+          ColumnV2.create("i", IntegerType),
+          ColumnV2.create("m", MapType(structType, structType, 
valueContainsNull = true))))
+        .build()
+      catalog.createTable(
+        ident = Identifier.of(Array(), "t"),
+        tableInfo = tableInfo)
+
+      if (byName) {
+        val inputDF = sql("SELECT 1 AS i, map(named_struct('x', 1, 'y', 1), 
null) AS m")
+        inputDF.writeTo("t").append()
+      } else {
+        sql("INSERT INTO t VALUES (1 AS i, map(named_struct('x', 1, 'y', 1), 
null) AS m)")
+      }
+      checkAnswer(spark.table("t"), Row(1, Map(Row(1, 1) -> null)))
+
+      // Gluten exception differs from Spark
+      val e1 = intercept[SparkException] {
+        if (byName) {
+          val inputDF = sql(
+            s"""SELECT
+               | 1 AS i,
+               | map(named_struct('x', null, 'y', 1), null) AS m
+             """.stripMargin)
+          inputDF.writeTo("t").append()
+        } else {
+          sql(
+            s"""INSERT INTO t VALUES (
+               | 1 AS i,
+               | map(named_struct('x', null, 'y', 1), null) AS m)
+             """.stripMargin)
+        }
+      }
+      assertNotNullException(e1, Seq("m", "key", "x"))
+
+      // Gluten exception differs from Spark
+      val e2 = intercept[SparkException] {
+        if (byName) {
+          val inputDF = sql(
+            s"""SELECT
+               | 1 AS i,
+               | map(named_struct('x', 1, 'y', 1), named_struct('x', null, 
'y', 1)) AS m
+             """.stripMargin)
+          inputDF.writeTo("t").append()
+        } else {
+          sql(
+            s"""INSERT INTO t VALUES (
+               | 1 AS i,
+               | map(named_struct('x', 1, 'y', 1), named_struct('x', null, 
'y', 1)) AS m)
+             """.stripMargin)
+        }
+      }
+      assertNotNullException(e2, Seq("m", "value", "x"))
+    }
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to