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

changchen pushed a commit to branch feature/ansi-ci-baseline
in repository https://gitbox.apache.org/repos/asf/gluten.git

commit f4e6232d4e0caa8d6752bb72887e0492220fb980
Author: Chang chen <[email protected]>
AuthorDate: Wed Apr 15 10:27:07 2026 +0000

    [GLUTEN-10134][VL] Fix TYPE_MISMATCH exceptions, add C2R translation path, 
and improve test framework
    
    - Add translateOther() to VeloxCastExceptionTranslator for MapFromEntries 
(NULL_MAP_KEY),
      make_date (DateTimeException), timestamp_seconds/millis 
(ArithmeticException),
      and make_ym_interval (INTERVAL_ARITHMETIC_OVERFLOW)
    - Add exception translation in VeloxColumnarToRowExec for C2R path 
(timestamp_millis)
    - Use forceCause() reflection to preserve native exception stack when Spark 
exception
      constructors set cause=null making initCause() unusable
    - Add SparkThrowable/GlutenException pass-through guards in 
ColumnarBatchOutIterator
    - Rewrite glutenCheckExceptionInExpression to use intercept[Exception] + 
findCause
      traversing cause chain, fixing fail()-inside-try and 
SparkArithmeticException
      not extending SparkException issues
    - Restructure verify-ansi-expressions.sh to support per-category testing
    
    Co-Authored-By: Claude Opus 4 <[email protected]>
---
 .../gluten/execution/VeloxColumnarToRowExec.scala  |  12 +-
 dev/verify-ansi-expressions.sh                     | 294 +++++++++++++++------
 .../vectorized/ColumnarBatchOutIterator.java       |  31 ++-
 .../spark/sql/VeloxCastExceptionTranslator.scala   |  82 ++++--
 .../org/apache/spark/sql/GlutenTestsTrait.scala    |  36 +--
 5 files changed, 346 insertions(+), 109 deletions(-)

diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxColumnarToRowExec.scala
 
b/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxColumnarToRowExec.scala
index 2354ebf39f..e55e015186 100644
--- 
a/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxColumnarToRowExec.scala
+++ 
b/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxColumnarToRowExec.scala
@@ -25,6 +25,7 @@ import org.apache.gluten.vectorized.{NativeColumnarToRowInfo, 
NativeColumnarToRo
 
 import org.apache.spark.broadcast.Broadcast
 import org.apache.spark.rdd.RDD
+import org.apache.spark.sql.VeloxCastExceptionTranslator
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions.UnsafeRow
 import org.apache.spark.sql.execution.{BroadcastUtils, SparkPlan}
@@ -199,7 +200,16 @@ object VeloxColumnarToRowExec {
               baseLength + info.lengths.length
             }
             val before = System.currentTimeMillis()
-            info = jniWrapper.nativeColumnarToRowConvert(c2rId, batchHandle, 
rowId)
+            try {
+              info = jniWrapper.nativeColumnarToRowConvert(c2rId, batchHandle, 
rowId)
+            } catch {
+              case e: Exception =>
+                val translated = 
VeloxCastExceptionTranslator.translate(e.getMessage)
+                if (translated != null) {
+                  throw translated
+                }
+                throw e
+            }
             convertTime += (System.currentTimeMillis() - before)
           }
           val (offset, length) =
diff --git a/dev/verify-ansi-expressions.sh b/dev/verify-ansi-expressions.sh
index 00dfe62ec1..c2fd9ed8b8 100755
--- a/dev/verify-ansi-expressions.sh
+++ b/dev/verify-ansi-expressions.sh
@@ -1,122 +1,268 @@
 #!/usr/bin/env bash
-# verify-ansi-expressions.sh — 验证 73 个 ANSI 表达式
-#
-# 基于 expression-matrix.md v3,运行所有覆盖 ANSI 影响表达式的 Gluten UT
-# 分 4 段运行:spark41 gluten-ut + backends-velox,spark40 gluten-ut + backends-velox
+# verify-ansi-expressions.sh — 按 expression-matrix v7 分类验证 ANSI 表达式
 #
 # 用法:
 #   cd /root/SourceCode/gluten
-#   bash dev/verify-ansi-expressions.sh [spark41|spark40|all]
+#   bash dev/verify-ansi-expressions.sh <category> [spark41|spark40|all] 
[--clean]
+#
+# category(对应矩阵第三节):
+#   cast        — §3.1.1 Cast + §3.3.1 try_cast
+#   arithmetic  — §3.1.2 算术 + §3.2.6 Abs/UnaryMinus + §3.3.1 try 算术
+#   collection  — §3.2.1 集合 + §3.3.2 try_element_at
+#   datetime    — §3.2.2 日期时间/Interval + §3.3.2 try_to_timestamp 等
+#   math        — §3.2.3 数学(Round/BRound/conv)
+#   decimal     — §3.2.4 Decimal(CheckOverflow)
+#   string      — §3.2.5 字符串 + §3.3.2 try_parse_url
+#   aggregate   — §3.1.3 聚合 + §3.4 间接(Sum/Avg/VAR/STDDEV,需人工校验)
+#   errors      — QueryExecutionAnsiErrorsSuite
+#   all         — 以上全部
+#
+# spark version(默认 spark41):
+#   spark41     — Spark 4.1
+#   spark40     — Spark 4.0
+#   all         — 先 spark41 再 spark40
 #
-# 输出:
-#   /tmp/ansi-verify-spark41-ut.log
-#   /tmp/ansi-verify-spark41-backends.log
-#   /tmp/ansi-verify-spark40-ut.log
-#   /tmp/ansi-verify-spark40-backends.log
 
 set -uo pipefail
 
 export SPARK_ANSI_SQL_MODE=true
 export SPARK_TESTING=true
 
-TARGET="${1:-all}"
+CATEGORY="${1:?Usage: $0 <category> [spark41|spark40|all] [--clean]}"
+SPARK_VER="${2:-spark41}"
+CLEAN_FLAG=""
+if [[ "${3:-}" == "--clean" ]] || [[ "${2:-}" == "--clean" ]]; then
+  CLEAN_FLAG="--clean"
+  # if --clean was $2, default spark version
+  if [[ "${2:-}" == "--clean" ]]; then
+    SPARK_VER="spark41"
+  fi
+fi
 
-SUITES_UT=(
-  -s org.apache.spark.sql.catalyst.expressions.GlutenArithmeticExpressionSuite
+case "${SPARK_VER}" in
+  spark41) 
PROFILES="-Pjava-17,spark-4.1,scala-2.13,backends-velox,hadoop-3.3"; 
UT_MODULE="gluten-ut/spark41" ;;
+  spark40) 
PROFILES="-Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3"; 
UT_MODULE="gluten-ut/spark40" ;;
+  all)     ;; # handled in main entry
+  *)       echo "Unknown spark version: ${SPARK_VER}"; echo "Usage: $0 
<category> [spark41|spark40|all] [--clean]"; exit 1 ;;
+esac
+
+ANSI_ARG="--jvm-arg -Dspark.gluten.sql.ansiFallback.enabled=false"
+LOG_DIR="/tmp/ansi-matrix"
+mkdir -p "${LOG_DIR}"
+
+# ── Suite 定义 ──────────────────────────────────────────────
+# 按矩阵 v7 第三节,强相关 Suite 映射
+
+# §3.1.1 Cast + §3.3.1 try_cast
+# 强相关 Suite: CastWithAnsiOnSuite(42), CastWithAnsiOffSuite(30), 
TryCastSuite(3), ComplexTypeSuite(2)
+CAST_UT=(
   -s org.apache.spark.sql.catalyst.expressions.GlutenCastWithAnsiOnSuite
   -s org.apache.spark.sql.catalyst.expressions.GlutenCastWithAnsiOffSuite
   -s org.apache.spark.sql.catalyst.expressions.GlutenTryCastSuite
-  -s org.apache.spark.sql.catalyst.expressions.GlutenMathExpressionsSuite
+)
+CAST_BACKENDS=(
+  -s org.apache.spark.sql.catalyst.expressions.VeloxCastSuite
+)
+
+# §3.1.2 算术 + §3.2.6 Abs/UnaryMinus + §3.3.1 try 算术
+# 强相关 Suite: ArithmeticExpressionSuite(37), TryEvalSuite(1)
+ARITHMETIC_UT=(
+  -s org.apache.spark.sql.catalyst.expressions.GlutenArithmeticExpressionSuite
+  -s org.apache.spark.sql.catalyst.expressions.GlutenTryEvalSuite
+)
+ARITHMETIC_BACKENDS=(
+  -s org.apache.gluten.functions.ArithmeticAnsiValidateSuite
+  -s org.apache.gluten.functions.MathFunctionsValidateSuiteAnsiOn
+)
+
+# §3.2.1 集合 + §3.3.2 try_element_at
+# 强相关 Suite: CollectionExpressionsSuite(21)
+COLLECTION_UT=(
   -s org.apache.spark.sql.catalyst.expressions.GlutenCollectionExpressionsSuite
-  -s org.apache.spark.sql.catalyst.expressions.GlutenStringExpressionsSuite
+)
+
+# §3.2.2 日期时间/Interval + §3.3.2 
try_to_timestamp/try_to_date/try_make_timestamp/try_make_interval
+# 强相关 Suite: DateExpressionsSuite(22), IntervalExpressionsSuite(9), 
DateFunctionsSuite(6*)
+DATETIME_UT=(
   -s org.apache.spark.sql.catalyst.expressions.GlutenDateExpressionsSuite
+  -s org.apache.spark.sql.catalyst.expressions.GlutenIntervalExpressionsSuite
+  -s org.apache.spark.sql.GlutenDateFunctionsSuite
+)
+
+# §3.2.3 数学
+# 强相关 Suite: MathExpressionsSuite(2)
+MATH_UT=(
+  -s org.apache.spark.sql.catalyst.expressions.GlutenMathExpressionsSuite
+)
+
+# §3.2.4 Decimal
+# 强相关 Suite: DecimalExpressionSuite(2)
+DECIMAL_UT=(
   -s org.apache.spark.sql.catalyst.expressions.GlutenDecimalExpressionSuite
-  -s org.apache.spark.sql.catalyst.expressions.GlutenDecimalPrecisionSuite
-  -s org.apache.spark.sql.GlutenMathFunctionsSuite
-  -s org.apache.spark.sql.GlutenDataFrameAggregateSuite
-  -s org.apache.spark.sql.GlutenStringFunctionsSuite
-  -s org.apache.spark.sql.errors.GlutenQueryExecutionAnsiErrorsSuite
 )
 
-SUITES_BACKENDS=(
-  -s org.apache.gluten.functions.ArithmeticAnsiValidateSuite
-  -s org.apache.gluten.functions.MathFunctionsValidateSuiteAnsiOn
-  -s org.apache.gluten.functions.FunctionsValidateSuite
-  -s org.apache.spark.sql.catalyst.expressions.VeloxCastSuite
+# §3.2.5 字符串 + §3.3.2 try_parse_url
+# 强相关 Suite: StringExpressionsSuite(3), UrlFunctionsSuite(2*)
+STRING_UT=(
+  -s org.apache.spark.sql.catalyst.expressions.GlutenStringExpressionsSuite
+  -s org.apache.spark.sql.GlutenUrlFunctionsSuite
 )
 
-echo "========================================"
-echo "ANSI Expression Matrix Verification"
-echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
-echo "Target: ${TARGET}"
-echo "SPARK_ANSI_SQL_MODE=${SPARK_ANSI_SQL_MODE}"
-echo "SPARK_TESTING=${SPARK_TESTING}"
-echo "spark.gluten.sql.ansiFallback.enabled=false"
-echo "========================================"
+# §3.1.3 聚合 + §3.4 间接(VAR/STDDEV)— 需人工校验
+# 强相关 Suite: DataFrameAggregateSuite(2* intercept)
+AGGREGATE_UT=(
+  -s org.apache.spark.sql.GlutenDataFrameAggregateSuite
+)
 
-run_spark41() {
-  local PROFILES="-Pjava-17,spark-4.1,scala-2.13,backends-velox,hadoop-3.3"
-  local ANSI_ARG='--jvm-arg -Dspark.gluten.sql.ansiFallback.enabled=false'
+# ANSI 错误语义
+# 强相关 Suite: QueryExecutionAnsiErrorsSuite(3* intercept)
+ERRORS_UT=(
+  -s org.apache.spark.sql.errors.GlutenQueryExecutionAnsiErrorsSuite
+)
+
+# ── 运行函数 ──────────────────────────────────────────────
 
+run_ut() {
+  local name="$1"
+  shift
+  local log="${LOG_DIR}/${name}-${SPARK_VER}-ut.log"
   echo ""
-  echo "=== Part 1/4: spark41 gluten-ut (14 suites) ==="
+  echo "=== ${name}: ${UT_MODULE} ==="
   ./dev/run-scala-test.sh \
-    --clean \
+    ${CLEAN_FLAG} \
     ${ANSI_ARG} \
     ${PROFILES},spark-ut \
-    -pl gluten-ut/spark41 \
-    "${SUITES_UT[@]}" \
-    2>&1 | tee /tmp/ansi-verify-spark41-ut.log
+    -pl "${UT_MODULE}" \
+    "$@" \
+    2>&1 | tee "${log}"
+  # 只第一次 clean
+  CLEAN_FLAG=""
+}
 
+run_backends() {
+  local name="$1"
+  shift
+  local log="${LOG_DIR}/${name}-${SPARK_VER}-backends.log"
   echo ""
-  echo "=== Part 2/4: spark41 backends-velox (4 suites) ==="
+  echo "=== ${name}: backends-velox (${SPARK_VER}) ==="
   ./dev/run-scala-test.sh \
     ${ANSI_ARG} \
     ${PROFILES} \
     -pl backends-velox \
-    "${SUITES_BACKENDS[@]}" \
-    2>&1 | tee /tmp/ansi-verify-spark41-backends.log
+    "$@" \
+    2>&1 | tee "${log}"
 }
 
-run_spark40() {
-  local PROFILES="-Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3"
-  local ANSI_ARG='--jvm-arg -Dspark.gluten.sql.ansiFallback.enabled=false'
+# ── 分类执行 ──────────────────────────────────────────────
 
-  echo ""
-  echo "=== Part 3/4: spark40 gluten-ut (14 suites) ==="
-  ./dev/run-scala-test.sh \
-    --clean \
-    ${ANSI_ARG} \
-    ${PROFILES},spark-ut \
-    -pl gluten-ut/spark40 \
-    "${SUITES_UT[@]}" \
-    2>&1 | tee /tmp/ansi-verify-spark40-ut.log
+run_cast() {
+  run_ut cast "${CAST_UT[@]}"
+  run_backends cast "${CAST_BACKENDS[@]}"
+}
 
-  echo ""
-  echo "=== Part 4/4: spark40 backends-velox (4 suites) ==="
-  ./dev/run-scala-test.sh \
-    ${ANSI_ARG} \
-    ${PROFILES} \
-    -pl backends-velox \
-    "${SUITES_BACKENDS[@]}" \
-    2>&1 | tee /tmp/ansi-verify-spark40-backends.log
+run_arithmetic() {
+  run_ut arithmetic "${ARITHMETIC_UT[@]}"
+  run_backends arithmetic "${ARITHMETIC_BACKENDS[@]}"
 }
 
-case "${TARGET}" in
-  spark41) run_spark41 ;;
-  spark40) run_spark40 ;;
-  all)     run_spark41; run_spark40 ;;
-  *)       echo "Usage: $0 [spark41|spark40|all]"; exit 1 ;;
-esac
+run_collection() {
+  run_ut collection "${COLLECTION_UT[@]}"
+}
+
+run_datetime() {
+  run_ut datetime "${DATETIME_UT[@]}"
+}
+
+run_math() {
+  run_ut math "${MATH_UT[@]}"
+}
+
+run_decimal() {
+  run_ut decimal "${DECIMAL_UT[@]}"
+}
+
+run_string() {
+  run_ut string "${STRING_UT[@]}"
+}
+
+run_aggregate() {
+  echo "⚠️  aggregate: §3.1.3 + §3.4 — DataFrame 级 intercept,需人工校验结果"
+  run_ut aggregate "${AGGREGATE_UT[@]}"
+}
+
+run_errors() {
+  run_ut errors "${ERRORS_UT[@]}"
+}
+
+run_all() {
+  run_cast
+  run_arithmetic
+  run_collection
+  run_datetime
+  run_math
+  run_decimal
+  run_string
+  run_aggregate
+  run_errors
+}
+
+# ── 主入口 ──────────────────────────────────────────────
+
+run_category() {
+  case "${CATEGORY}" in
+    cast)       run_cast ;;
+    arithmetic) run_arithmetic ;;
+    collection) run_collection ;;
+    datetime)   run_datetime ;;
+    math)       run_math ;;
+    decimal)    run_decimal ;;
+    string)     run_string ;;
+    aggregate)  run_aggregate ;;
+    errors)     run_errors ;;
+    all)        run_all ;;
+    *)
+      echo "Unknown category: ${CATEGORY}"
+      echo "Usage: $0 <category> [spark41|spark40|all] [--clean]"
+      exit 1
+      ;;
+  esac
+}
+
+echo "========================================"
+echo "ANSI Expression Matrix Verification (v7)"
+echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
+echo "Category: ${CATEGORY}"
+echo "Spark: ${SPARK_VER}"
+echo "SPARK_ANSI_SQL_MODE=${SPARK_ANSI_SQL_MODE}"
+echo "SPARK_TESTING=${SPARK_TESTING}"
+echo "ansiFallback=false"
+echo "Logs: ${LOG_DIR}/"
+echo "========================================"
+
+if [[ "${SPARK_VER}" == "all" ]]; then
+  # Run spark41 first, then spark40
+  SPARK_VER="spark41"
+  PROFILES="-Pjava-17,spark-4.1,scala-2.13,backends-velox,hadoop-3.3"
+  UT_MODULE="gluten-ut/spark41"
+  run_category
+
+  SPARK_VER="spark40"
+  PROFILES="-Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3"
+  UT_MODULE="gluten-ut/spark40"
+  CLEAN_FLAG="--clean"
+  run_category
+else
+  run_category
+fi
 
 echo ""
 echo "========================================"
-echo "Verification Complete"
+echo "Verification Complete — ${CATEGORY}"
 echo "Results:"
-for f in /tmp/ansi-verify-spark4{1,0}-{ut,backends}.log; do
+for f in "${LOG_DIR}"/*.log; do
   if [ -f "$f" ]; then
-    echo "  $(basename $f):"
-    grep -E "Tests:.*succeeded|All tests passed|FAILED" "$f" | tail -2 | sed 
's/^/    /'
+    echo "  $(basename "$f"):"
+    grep -E "Tests:.*succeeded|Tests run|BUILD SUCCESS|BUILD FAILURE|FAILED" 
"$f" | tail -3 | sed 's/^/    /'
   fi
 done
 echo "========================================"
diff --git 
a/gluten-arrow/src/main/java/org/apache/gluten/vectorized/ColumnarBatchOutIterator.java
 
b/gluten-arrow/src/main/java/org/apache/gluten/vectorized/ColumnarBatchOutIterator.java
index db7b8e1842..96bfa14fe1 100644
--- 
a/gluten-arrow/src/main/java/org/apache/gluten/vectorized/ColumnarBatchOutIterator.java
+++ 
b/gluten-arrow/src/main/java/org/apache/gluten/vectorized/ColumnarBatchOutIterator.java
@@ -22,11 +22,13 @@ import org.apache.gluten.iterator.ClosableIterator;
 import org.apache.gluten.runtime.Runtime;
 import org.apache.gluten.runtime.RuntimeAware;
 
+import org.apache.spark.SparkThrowable;
 import org.apache.spark.sql.VeloxCastExceptionTranslator;
 import 
org.apache.spark.sql.execution.datasources.SchemaColumnConvertNotSupportedException;
 import org.apache.spark.sql.vectorized.ColumnarBatch;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
 
 public class ColumnarBatchOutIterator extends ClosableIterator<ColumnarBatch>
     implements RuntimeAware {
@@ -155,13 +157,40 @@ public class ColumnarBatchOutIterator extends 
ClosableIterator<ColumnarBatch>
       }
       RuntimeException castEx = VeloxCastExceptionTranslator.translate(msg);
       if (castEx != null) {
-        castEx.initCause(e);
+        forceCause(castEx, e);
         return castEx;
       }
     }
+
+    if (e instanceof GlutenException) {
+      return (GlutenException) e;
+    }
+
+    if (e instanceof RuntimeException && e instanceof SparkThrowable) {
+      return (RuntimeException) e;
+    }
     return new GlutenException(e);
   }
 
+  /**
+   * Forces the cause of a Throwable via reflection. Spark exception 
constructors pass cause=null to
+   * super(message, null), which sets the internal cause field to null rather 
than the sentinel
+   * value "this", making initCause() unusable. Reflection bypasses this 
limitation so the original
+   * native exception is preserved in the chain for debugging.
+   */
+  private static void forceCause(Throwable target, Throwable cause) {
+    if (target.getCause() != null) {
+      return;
+    }
+    try {
+      Field f = Throwable.class.getDeclaredField("cause");
+      f.setAccessible(true);
+      f.set(target, cause);
+    } catch (NoSuchFieldException | IllegalAccessException ignored) {
+      // Should not happen with standard JDK Throwable; fall through without 
cause.
+    }
+  }
+
   private static String findFirstNonNullMessage(Throwable t) {
     while (t != null) {
       String msg = t.getMessage();
diff --git 
a/gluten-arrow/src/main/scala/org/apache/spark/sql/VeloxCastExceptionTranslator.scala
 
b/gluten-arrow/src/main/scala/org/apache/spark/sql/VeloxCastExceptionTranslator.scala
index 27da0d80b7..de44402cc5 100644
--- 
a/gluten-arrow/src/main/scala/org/apache/spark/sql/VeloxCastExceptionTranslator.scala
+++ 
b/gluten-arrow/src/main/scala/org/apache/spark/sql/VeloxCastExceptionTranslator.scala
@@ -25,10 +25,10 @@ import scala.util.matching.Regex
 /**
  * Translates Velox cast error messages into the corresponding Spark exception 
types.
  *
- * Velox cast errors follow the format: "Cannot cast FROMTYPE 'VALUE' to 
TOTYPE. DETAILS"
- * (produced by makeErrorMessage in CastExpr-inl.h). This translator parses 
that format and
- * constructs the correct Spark exception using QueryExecutionErrors factory 
methods, so that
- * the exception type and message match what Spark's native Cast expression 
would produce.
+ * Velox cast errors follow the format: "Cannot cast FROMTYPE 'VALUE' to 
TOTYPE. DETAILS" (produced
+ * by makeErrorMessage in CastExpr-inl.h). This translator parses that format 
and constructs the
+ * correct Spark exception using QueryExecutionErrors factory methods, so that 
the exception type
+ * and message match what Spark's native Cast expression would produce.
  *
  * Velox errors arrive via JNI as GlutenException with a structured message:
  * {{{
@@ -42,7 +42,8 @@ import scala.util.matching.Regex
  * The translator extracts the "Reason:" line to find the actual error message.
  *
  * Also translates Velox arithmetic errors (division by zero, overflow) into
- * SparkArithmeticException.
+ * SparkArithmeticException, and Velox datetime/collection errors into the 
corresponding Spark
+ * exception types.
  *
  * Must be in org.apache.spark.sql package to access private[sql] 
QueryExecutionErrors.
  */
@@ -61,6 +62,22 @@ object VeloxCastExceptionTranslator {
   private val ArithmeticOverflowRegex: Regex =
     """(?i)(?:Arithmetic overflow|Overflow in .+?)(?::?\s*(.+))?""".r
 
+  // Velox: "Invalid value for MAKE_DATE: year=X, month=Y, day=Z"
+  private val MakeDateRegex: Regex =
+    """Invalid value for MAKE_DATE: year=(-?\d+), month=(-?\d+), 
day=(-?\d+)""".r
+
+  // Velox: "map key cannot be null"
+  private val NullMapKeyRegex: Regex =
+    """(?i)map key cannot be null""".r
+
+  // Velox: "Timestamp seconds out of range" or "Could not convert 
Timestamp... to microseconds"
+  private val TimestampOverflowRegex: Regex =
+    """(?i)(?:Timestamp seconds out of range|Could not convert Timestamp.+to 
microseconds)""".r
+
+  // Velox: "Integer overflow in make_ym_interval(X, Y)"
+  private val MakeYMIntervalOverflowRegex: Regex =
+    """Integer overflow in make_ym_interval""".r
+
   private val VeloxTypeMap: Map[String, DataType] = Map(
     "BOOLEAN" -> BooleanType,
     "TINYINT" -> ByteType,
@@ -78,8 +95,8 @@ object VeloxCastExceptionTranslator {
     Set(ByteType, ShortType, IntegerType, LongType, FloatType, DoubleType)
 
   /**
-   * Translates a Velox cast error message into the corresponding Spark 
exception.
-   * Returns null if the message is not a recognized Velox cast error.
+   * Translates a Velox cast error message into the corresponding Spark 
exception. Returns null if
+   * the message is not a recognized Velox cast error.
    *
    * Called from ColumnarBatchOutIterator.translateException() (Java).
    */
@@ -92,6 +109,7 @@ object VeloxCastExceptionTranslator {
 
     translateCast(reason)
       .orElse(translateArithmetic(reason, msg))
+      .orElse(translateOther(reason))
       .orNull
   }
 
@@ -99,7 +117,7 @@ object VeloxCastExceptionTranslator {
     ReasonLineRegex.findFirstMatchIn(msg) match {
       case Some(m) =>
         val reason = m.group(1).trim
-        // Velox sometimes produces "Reason: Reason: ..." — strip the inner 
prefix
+        // Velox sometimes produces "Reason: Reason: ..." -- strip the inner 
prefix
         if (reason.startsWith("Reason:")) 
reason.substring("Reason:".length).trim
         else reason
       case None => msg
@@ -119,20 +137,24 @@ object VeloxCastExceptionTranslator {
 
         (fromType, toType) match {
           case (StringType, BooleanType) =>
-            Some(QueryExecutionErrors.invalidInputSyntaxForBooleanError(
-              UTF8String.fromString(value), null))
+            Some(
+              QueryExecutionErrors
+                
.invalidInputSyntaxForBooleanError(UTF8String.fromString(value), null))
 
           case (StringType, _: IntegralType | FloatType | DoubleType | _: 
DecimalType) =>
-            Some(QueryExecutionErrors.invalidInputInCastToNumberError(
-              toType, UTF8String.fromString(value), null))
+            Some(
+              QueryExecutionErrors
+                .invalidInputInCastToNumberError(toType, 
UTF8String.fromString(value), null))
 
           case (StringType, TimestampType | DateType) =>
-            Some(QueryExecutionErrors.invalidInputInCastToDatetimeError(
-              UTF8String.fromString(value), toType, null))
+            Some(
+              QueryExecutionErrors
+                
.invalidInputInCastToDatetimeError(UTF8String.fromString(value), toType, null))
 
           case (_: DecimalType, dt: DecimalType) =>
-            Some(QueryExecutionErrors.cannotChangeDecimalPrecisionError(
-              Decimal(value), dt.precision, dt.scale, null))
+            Some(
+              QueryExecutionErrors
+                .cannotChangeDecimalPrecisionError(Decimal(value), 
dt.precision, dt.scale, null))
 
           case (ft, tt) if ft != null && ft != StringType && 
NumericTypes.contains(tt) =>
             Some(QueryExecutionErrors.castingCauseOverflowError(value, ft, tt))
@@ -163,6 +185,34 @@ object VeloxCastExceptionTranslator {
     }
   }
 
+  private def translateOther(reason: String): Option[RuntimeException] = {
+    // MapFromEntries: "map key cannot be null" -> 
SparkRuntimeException(NULL_MAP_KEY)
+    if (NullMapKeyRegex.findFirstIn(reason).isDefined) {
+      return Some(QueryExecutionErrors.nullAsMapKeyNotAllowedError())
+    }
+
+    // make_date: "Invalid value for MAKE_DATE: year=X, month=Y, day=Z" -> 
SparkDateTimeException
+    reason match {
+      case MakeDateRegex(year, month, day) =>
+        val dtException = new java.time.DateTimeException(
+          s"Invalid value for MAKE_DATE: year=$year, month=$month, day=$day")
+        return 
Some(QueryExecutionErrors.ansiDateTimeArgumentOutOfRange(dtException))
+      case _ =>
+    }
+
+    // SecondsToTimestamp/MillisToTimestamp overflow -> 
ArithmeticException("long overflow")
+    if (TimestampOverflowRegex.findFirstIn(reason).isDefined) {
+      return Some(new ArithmeticException("long overflow"))
+    }
+
+    // make_ym_interval overflow -> 
SparkArithmeticException(INTERVAL_ARITHMETIC_OVERFLOW)
+    if (MakeYMIntervalOverflowRegex.findFirstIn(reason).isDefined) {
+      return 
Some(QueryExecutionErrors.withoutSuggestionIntervalArithmeticOverflowError(null))
+    }
+
+    None
+  }
+
   private def resolveType(veloxTypeName: String): DataType = {
     veloxTypeName match {
       case DecimalTypeRegex(p, s) => DecimalType(p.toInt, s.toInt)
diff --git 
a/gluten-ut/common/src/test/scala/org/apache/spark/sql/GlutenTestsTrait.scala 
b/gluten-ut/common/src/test/scala/org/apache/spark/sql/GlutenTestsTrait.scala
index 58bb96c511..925624f159 100644
--- 
a/gluten-ut/common/src/test/scala/org/apache/spark/sql/GlutenTestsTrait.scala
+++ 
b/gluten-ut/common/src/test/scala/org/apache/spark/sql/GlutenTestsTrait.scala
@@ -332,23 +332,14 @@ trait GlutenTestsTrait extends GlutenTestsCommonTrait {
       _spark.createDataFrame(_spark.sparkContext.parallelize(empData), schema)
     }
     val resultDF = df.select(ClassicColumn(expression))
-    val exception =
-      try {
-        resultDF.collect()
-        fail(s"Expected ${clazz.getSimpleName} but no exception was thrown 
for: $expression")
-      } catch {
-        case e: SparkException if e.getCause != null && 
clazz.isInstance(e.getCause) =>
-          e.getCause
-        case e: Throwable if clazz.isInstance(e) =>
-          e
-        case e: SparkException if e.getCause != null =>
-          e.getCause
-        case e: Throwable =>
-          fail(
-            s"Expected ${clazz.getSimpleName} but got 
${e.getClass.getSimpleName}: " +
-              s"${e.getMessage}",
-            e)
-      }
+    val thrown = intercept[Exception](resultDF.collect())
+    // Walk the cause chain to find the target exception type
+    val exception = findCause(thrown, clazz).getOrElse {
+      fail(
+        s"Expected ${clazz.getSimpleName} but got 
${thrown.getClass.getSimpleName}: " +
+          s"${thrown.getMessage}",
+        thrown)
+    }
     if (expectedErrMsg != null && exception.getMessage != null) {
       assert(
         exception.getMessage.contains(expectedErrMsg),
@@ -357,6 +348,17 @@ trait GlutenTestsTrait extends GlutenTestsCommonTrait {
     }
   }
 
+  private def findCause(e: Throwable, clazz: Class[_]): Option[Throwable] = {
+    var current: Throwable = e
+    while (current != null) {
+      if (clazz.isAssignableFrom(current.getClass)) {
+        return Some(current)
+      }
+      current = current.getCause
+    }
+    None
+  }
+
   def shouldNotFallback(): Unit = {
     TestStats.offloadGluten = false
     if (!BackendTestUtils.isCHBackendLoaded()) {


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

Reply via email to