From 9f555938f9ff1280d6bdfd0487fd0ece09d1a8d9 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Fri, 19 Jun 2026 18:02:42 -0700 Subject: [PATCH] fix(arrow-cast): respect cast safety for overflowing temporal casts Signed-off-by: Sai Asish Y --- arrow-cast/src/cast/mod.rs | 73 ++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs index 7ef5f1750a1d..8d62f09a17e1 100644 --- a/arrow-cast/src/cast/mod.rs +++ b/arrow-cast/src/cast/mod.rs @@ -1710,17 +1710,33 @@ pub fn cast_with_options( .as_primitive::() .unary::<_, Date64Type>(|x| x as i64 * MILLISECONDS_IN_DAY), )), - (Date64, Date32) => Ok(Arc::new( - array - .as_primitive::() - .unary::<_, Date32Type>(|x| (x / MILLISECONDS_IN_DAY) as i32), - )), + (Date64, Date32) => { + let array = array.as_primitive::(); + let result = if cast_options.safe { + array.unary_opt::<_, Date32Type>(|x| i32::try_from(x / MILLISECONDS_IN_DAY).ok()) + } else { + array.try_unary::<_, Date32Type, _>(|x| { + i32::try_from(x / MILLISECONDS_IN_DAY).map_err(|_| { + ArrowError::CastError(format!( + "Cannot cast Date64 value {x} to Date32 without overflow" + )) + }) + })? + }; + Ok(Arc::new(result)) + } - (Time32(TimeUnit::Second), Time32(TimeUnit::Millisecond)) => Ok(Arc::new( - array - .as_primitive::() - .unary::<_, Time32MillisecondType>(|x| x * MILLISECONDS as i32), - )), + (Time32(TimeUnit::Second), Time32(TimeUnit::Millisecond)) => { + let array = array.as_primitive::(); + let result = if cast_options.safe { + array.unary_opt::<_, Time32MillisecondType>(|x| x.checked_mul(MILLISECONDS as i32)) + } else { + array.try_unary::<_, Time32MillisecondType, _>(|x| { + x.mul_checked(MILLISECONDS as i32) + })? + }; + Ok(Arc::new(result)) + } (Time32(TimeUnit::Second), Time64(TimeUnit::Microsecond)) => Ok(Arc::new( array .as_primitive::() @@ -5239,6 +5255,26 @@ mod tests { assert!(c.is_null(2)); } + #[test] + fn test_cast_date64_to_date32_overflow() { + let a = Date64Array::from(vec![i64::MAX]); + let array = Arc::new(a) as ArrayRef; + + let b = cast(&array, &DataType::Date32).unwrap(); + let c = b.as_primitive::(); + assert!(c.is_null(0)); + + let options = CastOptions { + safe: false, + ..Default::default() + }; + let err = cast_with_options(&array, &DataType::Date32, &options).unwrap_err(); + assert!( + err.to_string().contains("Cannot cast Date64 value"), + "{err}" + ); + } + #[test] fn test_cast_string_to_integral_overflow() { let str = Arc::new(StringArray::from(vec![ @@ -13841,6 +13877,23 @@ mod tests { assert_eq!(c.value(3), 43_200_000_000); } + #[test] + fn test_cast_time32_second_to_time32_millisecond_overflow() { + let array = Time32SecondArray::from(vec![i32::MAX]); + + let b = cast(&array, &DataType::Time32(TimeUnit::Millisecond)).unwrap(); + let c = b.as_primitive::(); + assert!(c.is_null(0)); + + let options = CastOptions { + safe: false, + ..Default::default() + }; + let err = cast_with_options(&array, &DataType::Time32(TimeUnit::Millisecond), &options) + .unwrap_err(); + assert!(err.to_string().contains("Overflow"), "{err}"); + } + #[test] fn test_cast_string_to_time32_second_to_int64() { // Mimic: select arrow_cast('03:12:44'::time, 'Time32(Second)')::bigint;