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

andygrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git


The following commit(s) were added to refs/heads/main by this push:
     new 578379339e fix: skip null slots when checking overflow in unary 
negation (#5162)
578379339e is described below

commit 578379339ee8867f7134afb293eccfbab90e85c9
Author: Han-Yin Chang <[email protected]>
AuthorDate: Fri Aug 7 05:12:35 2026 +0800

    fix: skip null slots when checking overflow in unary negation (#5162)
    
    * fix: skip null slots when checking overflow in unary negation (#5093)
    
    * review: collapse ANSI negate dispatch, pin interval null regression
    
    Addresses review feedback on #5162:
    - Collapse the five near-identical array arms into a from_type lookup and
      rename map_neg_error's parameter to match the SparkError field.
    - Pin the interval half of the regression: a MIN sentinel in a *null* slot 
for
      Interval(YearMonth) and the whole Interval(DayTime)::MIN value both 
raised a
      spurious ANSI overflow, because the pre-scan walked value(i) without
      consulting the null buffer.
    - Reposition the single-component DayTime cases as error-mapping coverage: 
they
      already failed before this PR (neg_wrapping delegates to neg for every
      non-integer type), just with a raw ArrowError instead of a Spark error, 
so the
      failure set is unchanged.
    - Use "byte" / "short" as from_type for Int8/Int16 on both the array and
      scalar paths. The scalar arms previously passed a bare " caused", 
rendering
      as "[ARITHMETIC_OVERFLOW]  caused overflow.". No string matches every 
Spark
      version here -- 3.4/3.5 throw _LEGACY_ERROR_TEMP_2043 ("- <sqlValue> 
caused
      overflow.") while 4.x uses MathUtils.negateExact ("byte overflow") -- and
      since the shims always render ARITHMETIC_OVERFLOW, the 3.x class is out of
      reach regardless. "byte"/"short" is exact on 4.x and needs no hardcoded 
value.
    - Turn the byte/short dtype placeholders in CometExpressionSuite's unary
      negative overflow test into version-specific Spark and Comet byte/short
      overflow assertions.
    - Drop the stale supported-datatypes comment and record why the fallthrough 
is
      still overflow-safe.
    - Deduplicate the per-width test bodies behind generic helpers and give the
      let-else arms diagnostic panic messages.
    
    ---------
    
    Co-authored-by: Andy Grove <[email protected]>
---
 native/spark-expr/src/math_funcs/negative.rs       | 321 +++++++++++++++++----
 .../org/apache/comet/CometExpressionSuite.scala    |  93 +++---
 2 files changed, 318 insertions(+), 96 deletions(-)

diff --git a/native/spark-expr/src/math_funcs/negative.rs 
b/native/spark-expr/src/math_funcs/negative.rs
index 650fa401ef..a0da6f52fb 100644
--- a/native/spark-expr/src/math_funcs/negative.rs
+++ b/native/spark-expr/src/math_funcs/negative.rs
@@ -18,9 +18,10 @@
 use crate::arithmetic_overflow_error;
 use crate::SparkError;
 use arrow::array::RecordBatch;
-use arrow::datatypes::IntervalDayTime;
-use arrow::datatypes::{DataType, Schema};
-use arrow::{compute::kernels::numeric::neg_wrapping, 
datatypes::IntervalDayTimeType};
+use arrow::compute::kernels::numeric::{neg, neg_wrapping};
+use arrow::datatypes::IntervalDayTimeType;
+use arrow::datatypes::{DataType, IntervalUnit, Schema};
+use arrow::error::ArrowError;
 use datafusion::common::{DataFusionError, Result, ScalarValue};
 use datafusion::logical_expr::sort_properties::ExprProperties;
 use datafusion::{
@@ -59,24 +60,6 @@ impl PartialEq for NegativeExpr {
     }
 }
 
-macro_rules! check_overflow {
-    ($array:expr, $array_type:ty, $min_val:expr, $type_name:expr) => {{
-        let typed_array = $array
-            .as_any()
-            .downcast_ref::<$array_type>()
-            .expect(concat!(stringify!($array_type), " expected"));
-        for i in 0..typed_array.len() {
-            if typed_array.value(i) == $min_val {
-                if $type_name == "byte" || $type_name == "short" {
-                    let value = format!("{:?} caused", typed_array.value(i));
-                    return 
Err(arithmetic_overflow_error(value.as_str()).into());
-                }
-                return Err(arithmetic_overflow_error($type_name).into());
-            }
-        }
-    }};
-}
-
 impl NegativeExpr {
     /// Create new not expression
     pub fn new(arg: Arc<dyn PhysicalExpr>, fail_on_error: bool) -> Self {
@@ -95,6 +78,13 @@ impl std::fmt::Display for NegativeExpr {
     }
 }
 
+fn map_neg_error(err: ArrowError, from_type: &'static str) -> DataFusionError {
+    match err {
+        ArrowError::ArithmeticOverflow(_) => 
arithmetic_overflow_error(from_type).into(),
+        other => DataFusionError::from(other),
+    }
+}
+
 impl PhysicalExpr for NegativeExpr {
     fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
         self.arg.data_type(input_schema)
@@ -107,57 +97,50 @@ impl PhysicalExpr for NegativeExpr {
     fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
         let arg = self.arg.evaluate(batch)?;
 
-        // overflow checks only apply in ANSI mode
-        // datatypes supported are byte, short, integer, long, float, interval
+        // Overflow checks only apply in ANSI mode, and only the types listed 
in the
+        // match below have a Spark overflow message. Everything else (float, 
decimal,
+        // `Interval(MonthDayNano)`, ...) falls through to `neg_wrapping`.
         match arg {
             ColumnarValue::Array(array) => {
-                if self.fail_on_error {
-                    match array.data_type() {
-                        DataType::Int8 => {
-                            check_overflow!(array, arrow::array::Int8Array, 
i8::MIN, "byte")
-                        }
-                        DataType::Int16 => {
-                            check_overflow!(array, arrow::array::Int16Array, 
i16::MIN, "short")
-                        }
-                        DataType::Int32 => {
-                            check_overflow!(array, arrow::array::Int32Array, 
i32::MIN, "integer")
-                        }
-                        DataType::Int64 => {
-                            check_overflow!(array, arrow::array::Int64Array, 
i64::MIN, "long")
-                        }
-                        DataType::Interval(value) => match value {
-                            arrow::datatypes::IntervalUnit::YearMonth => 
check_overflow!(
-                                array,
-                                arrow::array::IntervalYearMonthArray,
-                                i32::MIN,
-                                "interval"
-                            ),
-                            arrow::datatypes::IntervalUnit::DayTime => 
check_overflow!(
-                                array,
-                                arrow::array::IntervalDayTimeArray,
-                                IntervalDayTime::MIN,
-                                "interval"
-                            ),
-                            arrow::datatypes::IntervalUnit::MonthDayNano => {
-                                // Overflow checks are not supported
-                            }
-                        },
-                        _ => {
-                            // Overflow checks are not supported for other 
datatypes
-                        }
-                    }
+                if !self.fail_on_error {
+                    return 
Ok(ColumnarValue::Array(neg_wrapping(array.as_ref())?));
                 }
-                let result = neg_wrapping(array.as_ref())?;
-                Ok(ColumnarValue::Array(result))
+                // The shims render this as `{from_type} overflow` under
+                // `ARITHMETIC_OVERFLOW`. For byte/short that is 
byte-identical to Spark
+                // 4.x, which routes them through `MathUtils.negateExact` 
("byte overflow" /
+                // "short overflow"). Spark 3.4/3.5 instead throw
+                // `_LEGACY_ERROR_TEMP_2043` ("- <sqlValue> caused 
overflow."); that error
+                // class is out of reach here, so no string can match every 
version.
+                let from_type = match array.data_type() {
+                    DataType::Int8 => "byte",
+                    DataType::Int16 => "short",
+                    DataType::Int32 => "integer",
+                    DataType::Int64 => "long",
+                    // `neg` checks each `DayTime` component, so either `days` 
or `ms` at
+                    // `i32::MIN` overflows; routing intervals here maps the 
Arrow overflow
+                    // error onto Spark's, matching the scalar path below.
+                    DataType::Interval(IntervalUnit::YearMonth | 
IntervalUnit::DayTime) => {
+                        "interval"
+                    }
+                    // Everything else falls through to `neg_wrapping`. Note 
it wraps only
+                    // for integers: for any other type, 
`Interval(MonthDayNano)` included,
+                    // it delegates to `neg`, so overflow is still detected -- 
it just
+                    // surfaces as an Arrow error rather than a Spark one.
+                    _ => return 
Ok(ColumnarValue::Array(neg_wrapping(array.as_ref())?)),
+                };
+                Ok(ColumnarValue::Array(
+                    neg(array.as_ref()).map_err(|e| map_neg_error(e, 
from_type))?,
+                ))
             }
             ColumnarValue::Scalar(scalar) => {
                 if self.fail_on_error {
                     match scalar {
+                        // Keep scalar overflow type names aligned with the 
array path.
                         ScalarValue::Int8(Some(i8::MIN)) => {
-                            return Err(arithmetic_overflow_error(" 
caused").into());
+                            return 
Err(arithmetic_overflow_error("byte").into());
                         }
                         ScalarValue::Int16(Some(i16::MIN)) => {
-                            return Err(arithmetic_overflow_error(" 
caused").into());
+                            return 
Err(arithmetic_overflow_error("short").into());
                         }
                         ScalarValue::Int32(Some(i32::MIN)) => {
                             return 
Err(arithmetic_overflow_error("integer").into());
@@ -250,3 +233,217 @@ impl PhysicalExpr for NegativeExpr {
         Display::fmt(self, f)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use arrow::{array::*, buffer::NullBuffer, datatypes::*};
+    use datafusion::{
+        physical_expr::expressions::{Column, Literal},
+        physical_plan::ColumnarValue,
+    };
+
+    fn eval_array(array: ArrayRef, fail_on_error: bool) -> 
Result<ColumnarValue> {
+        let schema = Arc::new(Schema::new(vec![Field::new(
+            "a",
+            array.data_type().clone(),
+            true,
+        )]));
+        let batch = RecordBatch::try_new(Arc::clone(&schema), vec![array])?;
+        NegativeExpr::new(Arc::new(Column::new("a", 0)), 
fail_on_error).evaluate(&batch)
+    }
+
+    fn eval_scalar(scalar: ScalarValue, fail_on_error: bool) -> 
Result<ColumnarValue> {
+        let batch = RecordBatch::new_empty(Arc::new(Schema::empty()));
+        NegativeExpr::new(Arc::new(Literal::new(scalar)), 
fail_on_error).evaluate(&batch)
+    }
+
+    fn assert_spark_overflow(err: DataFusionError, expected_from_type: &str) {
+        if let DataFusionError::External(ref e) = err {
+            if let Some(SparkError::ArithmeticOverflow { from_type }) =
+                e.downcast_ref::<SparkError>()
+            {
+                assert_eq!(from_type, expected_from_type);
+                return;
+            }
+        }
+        panic!(
+            "Expected SparkError::ArithmeticOverflow {{ from_type: {:?} }}, 
got: {:?}",
+            expected_from_type, err
+        );
+    }
+
+    /// Negate `[min, other]` in ANSI mode with slot 0 marked null, and assert 
the
+    /// null slot is skipped instead of raising a spurious overflow.
+    fn assert_null_min_slot_is_skipped<T: ArrowPrimitiveType>(
+        min: T::Native,
+        other: T::Native,
+        negated_other: T::Native,
+    ) {
+        let nulls = NullBuffer::from(vec![false, true]);
+        let array: ArrayRef = Arc::new(PrimitiveArray::<T>::new(
+            vec![min, other].into(),
+            Some(nulls),
+        ));
+        let ColumnarValue::Array(result) = eval_array(array, true).unwrap() 
else {
+            panic!("expected array result")
+        };
+        let result = result.as_primitive::<T>();
+        assert!(result.is_null(0));
+        assert_eq!(result.value(1), negated_other);
+    }
+
+    /// A MIN sentinel left behind in a null slot (by filter, slice or FFI) 
must not raise a
+    /// spurious ANSI overflow: the overflow check has to consult the null 
buffer and skip
+    /// invalid slots. Each case below places `MIN` in a null slot.
+    #[test]
+    fn test_ansi_null_slot_with_min_values_does_not_overflow() {
+        assert_null_min_slot_is_skipped::<Int8Type>(i8::MIN, 7, -7);
+        assert_null_min_slot_is_skipped::<Int16Type>(i16::MIN, 7, -7);
+        assert_null_min_slot_is_skipped::<Int32Type>(i32::MIN, 7, -7);
+        assert_null_min_slot_is_skipped::<Int64Type>(i64::MIN, 7, -7);
+        assert_null_min_slot_is_skipped::<IntervalYearMonthType>(i32::MIN, 7, 
-7);
+        // `IntervalDayTime::MIN` is both components at `i32::MIN`.
+        assert_null_min_slot_is_skipped::<IntervalDayTimeType>(
+            IntervalDayTime::MIN,
+            IntervalDayTime::new(1, 2),
+            IntervalDayTime::new(-1, -2),
+        );
+    }
+
+    #[test]
+    fn test_ansi_valid_min_values_raise_exact_spark_overflow_errors() {
+        let arr_i8: ArrayRef = Arc::new(Int8Array::from(vec![i8::MIN]));
+        assert_spark_overflow(eval_array(arr_i8, true).unwrap_err(), "byte");
+
+        let arr_i16: ArrayRef = Arc::new(Int16Array::from(vec![i16::MIN]));
+        assert_spark_overflow(eval_array(arr_i16, true).unwrap_err(), "short");
+
+        let arr_i32: ArrayRef = Arc::new(Int32Array::from(vec![i32::MIN]));
+        assert_spark_overflow(eval_array(arr_i32, true).unwrap_err(), 
"integer");
+
+        let arr_i64: ArrayRef = Arc::new(Int64Array::from(vec![i64::MIN]));
+        assert_spark_overflow(eval_array(arr_i64, true).unwrap_err(), "long");
+
+        let arr_ym: ArrayRef = 
Arc::new(IntervalYearMonthArray::from(vec![i32::MIN]));
+        assert_spark_overflow(eval_array(arr_ym, true).unwrap_err(), 
"interval");
+
+        let arr_dt: ArrayRef = 
Arc::new(IntervalDayTimeArray::from(vec![IntervalDayTime::MIN]));
+        assert_spark_overflow(eval_array(arr_dt, true).unwrap_err(), 
"interval");
+    }
+
+    /// A single `DayTime` component at `i32::MIN` overflows: `neg` checks 
each component
+    /// (`neg_wrapping` delegates to `neg` for every non-integer type,
+    /// `downcast_integer! { ..., _ => neg(array) }`). These surface as Spark 
overflows like
+    /// every other ANSI overflow in this expression.
+    #[test]
+    fn test_ansi_interval_day_time_component_overflow_maps_to_spark_error() {
+        for value in [
+            IntervalDayTime::new(i32::MIN, 0),
+            IntervalDayTime::new(0, i32::MIN),
+        ] {
+            let array: ArrayRef = 
Arc::new(IntervalDayTimeArray::from(vec![value]));
+            assert_spark_overflow(eval_array(array, true).unwrap_err(), 
"interval");
+        }
+    }
+
+    /// Negate `[min, null]` in legacy mode and assert `min` wraps to itself.
+    fn assert_legacy_wraps_min<T: ArrowPrimitiveType>(min: T::Native) {
+        let array: ArrayRef = Arc::new(PrimitiveArray::<T>::new(
+            vec![min, T::Native::default()].into(),
+            Some(NullBuffer::from(vec![true, false])),
+        ));
+        let ColumnarValue::Array(result) = eval_array(array, false).unwrap() 
else {
+            panic!("expected array result")
+        };
+        let result = result.as_primitive::<T>();
+        assert_eq!(result.value(0), min);
+        assert!(result.is_null(1));
+    }
+
+    #[test]
+    fn test_legacy_mode_wraps_min_values() {
+        assert_legacy_wraps_min::<Int8Type>(i8::MIN);
+        assert_legacy_wraps_min::<Int16Type>(i16::MIN);
+        assert_legacy_wraps_min::<Int32Type>(i32::MIN);
+        assert_legacy_wraps_min::<Int64Type>(i64::MIN);
+    }
+
+    #[test]
+    fn test_mixed_ordinary_values() {
+        let arr: ArrayRef = Arc::new(Int32Array::from(vec![Some(-7), Some(0), 
Some(12), None]));
+
+        // ANSI mode
+        let ColumnarValue::Array(res_ansi) = eval_array(Arc::clone(&arr), 
true).unwrap() else {
+            panic!("expected array result")
+        };
+        assert_eq!(
+            res_ansi.as_primitive::<Int32Type>(),
+            &Int32Array::from(vec![Some(7), Some(0), Some(-12), None])
+        );
+
+        // Legacy mode
+        let ColumnarValue::Array(res_legacy) = eval_array(Arc::clone(&arr), 
false).unwrap() else {
+            panic!("expected array result")
+        };
+        assert_eq!(
+            res_legacy.as_primitive::<Int32Type>(),
+            &Int32Array::from(vec![Some(7), Some(0), Some(-12), None])
+        );
+    }
+
+    #[test]
+    fn test_interval_month_day_nano_preserves_existing_dispatch() {
+        let arr: ArrayRef = Arc::new(IntervalMonthDayNanoArray::from(vec![
+            Some(IntervalMonthDayNano::new(1, 2, 3)),
+            None,
+        ]));
+        let ColumnarValue::Array(res) = eval_array(arr, true).unwrap() else {
+            panic!("expected array result")
+        };
+        let p = res.as_primitive::<IntervalMonthDayNanoType>();
+        assert_eq!(p.value(0), IntervalMonthDayNano::new(-1, -2, -3));
+        assert!(p.is_null(1));
+    }
+
+    #[test]
+    fn test_scalar_negation() {
+        // Valid scalar
+        let ColumnarValue::Scalar(res_valid) =
+            eval_scalar(ScalarValue::Int32(Some(42)), true).unwrap()
+        else {
+            panic!("expected scalar result")
+        };
+        assert_eq!(res_valid, ScalarValue::Int32(Some(-42)));
+
+        // Null scalar
+        let ColumnarValue::Scalar(res_null) = 
eval_scalar(ScalarValue::Int32(None), true).unwrap()
+        else {
+            panic!("expected scalar result")
+        };
+        assert_eq!(res_null, ScalarValue::Int32(None));
+
+        // MIN scalar overflows in ANSI, with the same messages as the array 
path
+        for (scalar, from_type) in [
+            (ScalarValue::Int8(Some(i8::MIN)), "byte"),
+            (ScalarValue::Int16(Some(i16::MIN)), "short"),
+            (ScalarValue::Int32(Some(i32::MIN)), "integer"),
+            (ScalarValue::Int64(Some(i64::MIN)), "long"),
+        ] {
+            assert_spark_overflow(eval_scalar(scalar, true).unwrap_err(), 
from_type);
+        }
+    }
+
+    #[test]
+    fn test_map_neg_error_preserves_non_overflow_errors() {
+        let err = map_neg_error(
+            ArrowError::InvalidArgumentError("test custom error".to_string()),
+            "integer",
+        );
+        assert!(
+            matches!(&err, DataFusionError::ArrowError(inner, _)
+                if matches!(inner.as_ref(), 
ArrowError::InvalidArgumentError(msg) if msg == "test custom error")),
+            "expected the ArrowError to pass through unchanged, got: {err:?}"
+        );
+    }
+}
diff --git a/spark/src/test/scala/org/apache/comet/CometExpressionSuite.scala 
b/spark/src/test/scala/org/apache/comet/CometExpressionSuite.scala
index 50161d46c7..80f92c4b7f 100644
--- a/spark/src/test/scala/org/apache/comet/CometExpressionSuite.scala
+++ b/spark/src/test/scala/org/apache/comet/CometExpressionSuite.scala
@@ -19,8 +19,6 @@
 
 package org.apache.comet
 
-import java.time.{Duration, Period}
-
 import scala.util.Random
 
 import org.apache.hadoop.fs.Path
@@ -2324,11 +2322,19 @@ class CometExpressionSuite extends CometTestBase with 
AdaptiveSparkPlanHelper {
         CometConf.COMET_EXEC_ENABLED.key -> "true")(f)
     }
 
-    def checkOverflow(query: String, dtype: String): Unit = {
+    // Spark 3.4/3.5 throw `_LEGACY_ERROR_TEMP_2043` ("- <sqlValue> caused 
overflow.") for byte and
+    // short. Spark 4.x routes them through `MathUtils.negateExact` and agrees 
with Comet, which
+    // always renders `SparkError::ArithmeticOverflow` with the Spark type 
name.
+    def sparkOverflowMsg(dtype: String): String =
+      if (isSpark40Plus) s"$dtype overflow" else "caused overflow"
+
+    // Spark and Comet can render different overflow messages for the same 
operation, so assert each
+    // side's expected substring separately.
+    def checkOverflow(query: String, sparkExpected: String, cometExpected: 
String): Unit = {
       checkSparkAnswerMaybeThrows(sql(query)) match {
         case (Some(sparkException), Some(cometException)) =>
-          assert(sparkException.getMessage.contains(dtype + " overflow"))
-          assert(cometException.getMessage.contains(dtype + " overflow"))
+          assert(sparkException.getMessage.contains(sparkExpected))
+          assert(cometException.getMessage.contains(cometExpected))
         case (None, None) => checkSparkAnswerAndOperator(sql(query))
         case (None, Some(ex)) =>
           fail("Comet threw an exception but Spark did not " + ex.getMessage)
@@ -2337,44 +2343,50 @@ class CometExpressionSuite extends CometTestBase with 
AdaptiveSparkPlanHelper {
       }
     }
 
-    def runArrayTest(query: String, dtype: String, path: String): Unit = {
+    def runArrayTest(
+        query: String,
+        sparkExpected: String,
+        cometExpected: String,
+        path: String): Unit = {
       withParquetTable(path, "t") {
         withAnsiMode(enabled = false) {
           checkSparkAnswerAndOperator(sql(query))
         }
         withAnsiMode(enabled = true) {
-          checkOverflow(query, dtype)
+          checkOverflow(query, sparkExpected, cometExpected)
         }
       }
     }
 
     withTempDir { dir =>
-      // Array values test
+      // Array values test. Tuple is (file, data, Spark-expected substring, 
Comet-expected substring).
       val dataTypes = Seq(
-        ("array_test.parquet", Seq(Int.MaxValue, Int.MinValue).toDF("a"), 
"integer"),
-        ("long_array_test.parquet", Seq(Long.MaxValue, 
Long.MinValue).toDF("a"), "long"),
-        ("short_array_test.parquet", Seq(Short.MaxValue, 
Short.MinValue).toDF("a"), ""),
-        ("byte_array_test.parquet", Seq(Byte.MaxValue, 
Byte.MinValue).toDF("a"), ""))
-
-      dataTypes.foreach { case (fileName, df, dtype) =>
+        (
+          "array_test.parquet",
+          Seq(Int.MaxValue, Int.MinValue).toDF("a"),
+          "integer overflow",
+          "integer overflow"),
+        (
+          "long_array_test.parquet",
+          Seq(Long.MaxValue, Long.MinValue).toDF("a"),
+          "long overflow",
+          "long overflow"),
+        (
+          "short_array_test.parquet",
+          Seq(Short.MaxValue, Short.MinValue).toDF("a"),
+          sparkOverflowMsg("short"),
+          "short overflow"),
+        (
+          "byte_array_test.parquet",
+          Seq(Byte.MaxValue, Byte.MinValue).toDF("a"),
+          sparkOverflowMsg("byte"),
+          "byte overflow"))
+
+      dataTypes.foreach { case (fileName, df, sparkExpected, cometExpected) =>
         val path = new Path(dir.toURI.toString, fileName).toString
         df.write.mode("overwrite").parquet(path)
         val query = "select a, -a from t"
-        runArrayTest(query, dtype, path)
-      }
-
-      withParquetTable((0 until 5).map(i => (i % 5, i % 3)), "tbl") {
-        withAnsiMode(enabled = true) {
-          // interval test without cast
-          val longDf = Seq(Long.MaxValue, Long.MaxValue, 2)
-          val yearMonthDf = Seq(Int.MaxValue, Int.MaxValue, 2)
-            .map(Period.ofMonths)
-          val dayTimeDf = Seq(106751991L, 106751991L, 2L)
-            .map(Duration.ofDays)
-          Seq(longDf, yearMonthDf, dayTimeDf).foreach { _ =>
-            checkOverflow("select -(_1) FROM tbl", "")
-          }
-        }
+        runArrayTest(query, sparkExpected, cometExpected, path)
       }
 
       // scalar tests
@@ -2385,19 +2397,32 @@ class CometExpressionSuite extends CometTestBase with 
AdaptiveSparkPlanHelper {
           CometConf.COMET_ENABLED.key -> "true",
           CometConf.COMET_EXEC_ENABLED.key -> "true") {
           for (n <- Seq("2147483647", "-2147483648")) {
-            checkOverflow(s"select -(cast(${n} as int)) FROM tbl", "integer")
+            checkOverflow(
+              s"select -(cast(${n} as int)) FROM tbl",
+              "integer overflow",
+              "integer overflow")
           }
           for (n <- Seq("32767", "-32768")) {
-            checkOverflow(s"select -(cast(${n} as short)) FROM tbl", "")
+            checkOverflow(
+              s"select -(cast(${n} as short)) FROM tbl",
+              sparkOverflowMsg("short"),
+              "short overflow")
           }
           for (n <- Seq("127", "-128")) {
-            checkOverflow(s"select -(cast(${n} as byte)) FROM tbl", "")
+            checkOverflow(
+              s"select -(cast(${n} as byte)) FROM tbl",
+              sparkOverflowMsg("byte"),
+              "byte overflow")
           }
           for (n <- Seq("9223372036854775807", "-9223372036854775808")) {
-            checkOverflow(s"select -(cast(${n} as long)) FROM tbl", "long")
+            checkOverflow(
+              s"select -(cast(${n} as long)) FROM tbl",
+              "long overflow",
+              "long overflow")
           }
+          // Float negation cannot overflow; confirm it stays native and 
returns the negated value.
           for (n <- Seq("3.4028235E38", "-3.4028235E38")) {
-            checkOverflow(s"select -(cast(${n} as float)) FROM tbl", "float")
+            checkSparkAnswerAndOperator(sql(s"select -(cast(${n} as float)) 
FROM tbl"))
           }
         }
       }


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

Reply via email to