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]