This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/main by this push:
new b220251bdd fix(arrow-cast): check for overflow when converting Date32
-> Timestamp (#9825)
b220251bdd is described below
commit b220251bddc66050b9a964060fc6f77a873c8724
Author: bboissin <[email protected]>
AuthorDate: Thu Jun 18 21:46:28 2026 +0200
fix(arrow-cast): check for overflow when converting Date32 -> Timestamp
(#9825)
Instead of panic (in debug) or wrapping, check for overflow similarly to
other conversions (e.g. Timestamp -> Timestamp).
# Which issue does this PR close?
- Closes #9824 .
---------
Co-authored-by: Jeffrey Vo <[email protected]>
---
arrow-cast/src/cast/mod.rs | 77 ++++++++++++++++++++++++++++++++++++++++------
1 file changed, 67 insertions(+), 10 deletions(-)
diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs
index 4d67703ea6..7ef5f1750a 100644
--- a/arrow-cast/src/cast/mod.rs
+++ b/arrow-cast/src/cast/mod.rs
@@ -2137,18 +2137,30 @@ pub fn cast_with_options(
cast_with_options(&array, to_type, cast_options)
}
(Date32, Timestamp(TimeUnit::Microsecond, _)) => {
- let array = array
- .as_primitive::<Date32Type>()
- .unary::<_, TimestampMicrosecondType>(|x| (x as i64) *
MICROSECONDS_IN_DAY);
-
- cast_with_options(&array, to_type, cast_options)
+ let date_array = array.as_primitive::<Date32Type>();
+ let converted = if cast_options.safe {
+ date_array.unary_opt::<_, TimestampMicrosecondType>(|x| {
+ (x as i64).checked_mul(MICROSECONDS_IN_DAY)
+ })
+ } else {
+ date_array.try_unary::<_, TimestampMicrosecondType, _>(|x| {
+ (x as i64).mul_checked(MICROSECONDS_IN_DAY)
+ })?
+ };
+ cast_with_options(&converted, to_type, cast_options)
}
(Date32, Timestamp(TimeUnit::Nanosecond, _)) => {
- let array = array
- .as_primitive::<Date32Type>()
- .unary::<_, TimestampNanosecondType>(|x| (x as i64) *
NANOSECONDS_IN_DAY);
-
- cast_with_options(&array, to_type, cast_options)
+ let date_array = array.as_primitive::<Date32Type>();
+ let converted = if cast_options.safe {
+ date_array.unary_opt::<_, TimestampNanosecondType>(|x| {
+ (x as i64).checked_mul(NANOSECONDS_IN_DAY)
+ })
+ } else {
+ date_array.try_unary::<_, TimestampNanosecondType, _>(|x| {
+ (x as i64).mul_checked(NANOSECONDS_IN_DAY)
+ })?
+ };
+ cast_with_options(&converted, to_type, cast_options)
}
(_, Duration(unit)) if from_type.is_numeric() => {
@@ -11412,6 +11424,51 @@ mod tests {
assert!(c.is_null(2));
}
+ #[test]
+ fn test_cast_date32_to_timestamp_us_overflow() {
+ const MAX_DAYS_MICROS: i32 = (i64::MAX / MICROSECONDS_IN_DAY) as i32;
+ let a = Date32Array::from(vec![Some(MAX_DAYS_MICROS),
Some(MAX_DAYS_MICROS + 1), None]);
+ let array = Arc::new(a) as ArrayRef;
+ let err = cast_with_options(
+ &array,
+ &DataType::Timestamp(TimeUnit::Microsecond, None),
+ &CastOptions {
+ safe: false,
+ format_options: FormatOptions::default(),
+ },
+ );
+ assert!(err.is_err());
+
+ let b = cast(&array, &DataType::Timestamp(TimeUnit::Microsecond,
None)).unwrap();
+ let c = b.as_primitive::<TimestampMicrosecondType>();
+ assert_eq!(MAX_DAYS_MICROS as i64 * MICROSECONDS_IN_DAY, c.value(0));
+ assert!(c.is_null(1));
+ assert!(c.is_null(2));
+ }
+
+ #[test]
+ fn test_cast_date32_to_timestamp_ns_overflow() {
+ // 2262-04-11, 2062-04-12
+ let upper_limit = 106_751;
+ let a = Date32Array::from(vec![Some(upper_limit), Some(upper_limit +
1), None]);
+ let array = Arc::new(a) as ArrayRef;
+ let err = cast_with_options(
+ &array,
+ &DataType::Timestamp(TimeUnit::Nanosecond, None),
+ &CastOptions {
+ safe: false,
+ format_options: FormatOptions::default(),
+ },
+ );
+ assert!(err.is_err());
+
+ let b = cast(&array, &DataType::Timestamp(TimeUnit::Nanosecond,
None)).unwrap();
+ let c = b.as_primitive::<TimestampNanosecondType>();
+ assert_eq!(upper_limit as i64 * NANOSECONDS_IN_DAY, c.value(0));
+ assert!(c.is_null(1));
+ assert!(c.is_null(2));
+ }
+
#[test]
fn test_timezone_cast() {
let a = StringArray::from(vec![