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

jayzhan211 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 980667fc52 Add interval multiplication by i64 (#10336)
980667fc52 is described below

commit 980667fc52fc4cce49489f3cd2eedf832ec0022a
Author: Peter Lee <[email protected]>
AuthorDate: Thu Jul 16 21:54:10 2026 +0800

    Add interval multiplication by i64 (#10336)
    
    # Which issue does this PR close?
    
    Part of https://github.com/apache/arrow-rs/issues/9030.
    
    # Rationale for this change
    
    As part of updating Datafusion to better support date / time math
    operations (https://github.com/apache/datafusion/issues/19022 among
    others) it was uncovered that `arrow-rs` does not support interval *
    number ops that should be valid.
    
    For example:
    interval '1 second' * 900 → 00:15:00
    interval '1 day' * 21 → 21 days
    
    this pr only include the interval multiply integer support because div
    and floating is too complex in case of
    https://github.com/apache/arrow-rs/pull/6906#discussion_r1894338923
    
    # What changes are included in this PR?
    
    Allow:
    - (Int64, Interval(YearMonth))
    - (Int64, Interval(DayTime))
    - (Int64, Interval(MonthDayNano)
    
    and their swap to multiple pair by pair.
    
    # Are these changes tested?
    
    - `cargo test -p arrow-arith`
    
    # Are there any user-facing changes?
    
    yes, `arithmetic_op` now accept
    
    1. (Interval(YearMonth), Int64)
    2. (Interval(DayTime), Int64)
    3. (Interval(MonthDayNano), Int64)
    4. (Int64, Interval(YearMonth))
    5. (Int64, Interval(DayTime))
    6. (Int64, Interval(MonthDayNano))
    
    as its (lhs, rhs) pair type.
---
 arrow-arith/src/numeric.rs | 117 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)

diff --git a/arrow-arith/src/numeric.rs b/arrow-arith/src/numeric.rs
index 7e0bb2e7aa..cc94006fae 100644
--- a/arrow-arith/src/numeric.rs
+++ b/arrow-arith/src/numeric.rs
@@ -243,6 +243,12 @@ fn arithmetic_op(op: Op, lhs: &dyn Datum, rhs: &dyn Datum) 
-> Result<ArrayRef, A
         (Duration(Millisecond), Duration(Millisecond)) => 
duration_op::<DurationMillisecondType>(op, l, l_scalar, r, r_scalar),
         (Duration(Microsecond), Duration(Microsecond)) => 
duration_op::<DurationMicrosecondType>(op, l, l_scalar, r, r_scalar),
         (Duration(Nanosecond), Duration(Nanosecond)) => 
duration_op::<DurationNanosecondType>(op, l, l_scalar, r, r_scalar),
+        (Interval(YearMonth), Int64) if matches!(op, Op::Mul) => 
interval_mul_op::<IntervalYearMonthType>(l, l_scalar, r, r_scalar),
+        (Interval(DayTime), Int64) if matches!(op, Op::Mul) => 
interval_mul_op::<IntervalDayTimeType>(l, l_scalar, r, r_scalar),
+        (Interval(MonthDayNano), Int64) if matches!(op, Op::Mul) => 
interval_mul_op::<IntervalMonthDayNanoType>(l, l_scalar, r, r_scalar),
+        (Int64, Interval(YearMonth)) if matches!(op, Op::Mul) => 
interval_mul_op::<IntervalYearMonthType>(r, r_scalar, l, l_scalar),
+        (Int64, Interval(DayTime)) if matches!(op, Op::Mul) => 
interval_mul_op::<IntervalDayTimeType>(r, r_scalar, l, l_scalar),
+        (Int64, Interval(MonthDayNano)) if matches!(op, Op::Mul) => 
interval_mul_op::<IntervalMonthDayNanoType>(r, r_scalar, l, l_scalar),
         (Interval(YearMonth), Interval(YearMonth)) => 
interval_op::<IntervalYearMonthType>(op, l, l_scalar, r, r_scalar),
         (Interval(DayTime), Interval(DayTime)) => 
interval_op::<IntervalDayTimeType>(op, l, l_scalar, r, r_scalar),
         (Interval(MonthDayNano), Interval(MonthDayNano)) => 
interval_op::<IntervalMonthDayNanoType>(op, l, l_scalar, r, r_scalar),
@@ -621,6 +627,14 @@ date!(Date64Type);
 trait IntervalOp: ArrowPrimitiveType {
     fn add(left: Self::Native, right: Self::Native) -> Result<Self::Native, 
ArrowError>;
     fn sub(left: Self::Native, right: Self::Native) -> Result<Self::Native, 
ArrowError>;
+    fn mul_i64(left: Self::Native, right: i64) -> Result<Self::Native, 
ArrowError>;
+}
+
+fn mul_i32_i64(left: i32, right: i64) -> Result<i32, ArrowError> {
+    let value = i64::from(left).mul_checked(right)?;
+    i32::try_from(value).map_err(|_| {
+        ArrowError::ArithmeticOverflow(format!("Overflow happened on: {left} * 
{right}"))
+    })
 }
 
 impl IntervalOp for IntervalYearMonthType {
@@ -631,6 +645,10 @@ impl IntervalOp for IntervalYearMonthType {
     fn sub(left: Self::Native, right: Self::Native) -> Result<Self::Native, 
ArrowError> {
         left.sub_checked(right)
     }
+
+    fn mul_i64(left: Self::Native, right: i64) -> Result<Self::Native, 
ArrowError> {
+        mul_i32_i64(left, right)
+    }
 }
 
 impl IntervalOp for IntervalDayTimeType {
@@ -649,6 +667,14 @@ impl IntervalOp for IntervalDayTimeType {
         let ms = l_ms.sub_checked(r_ms)?;
         Ok(Self::make_value(days, ms))
     }
+
+    fn mul_i64(left: Self::Native, right: i64) -> Result<Self::Native, 
ArrowError> {
+        let (days, ms) = Self::to_parts(left);
+        Ok(Self::make_value(
+            mul_i32_i64(days, right)?,
+            mul_i32_i64(ms, right)?,
+        ))
+    }
 }
 
 impl IntervalOp for IntervalMonthDayNanoType {
@@ -669,6 +695,33 @@ impl IntervalOp for IntervalMonthDayNanoType {
         let nanos = l_nanos.sub_checked(r_nanos)?;
         Ok(Self::make_value(months, days, nanos))
     }
+
+    fn mul_i64(left: Self::Native, right: i64) -> Result<Self::Native, 
ArrowError> {
+        let (months, days, nanos) = Self::to_parts(left);
+        Ok(Self::make_value(
+            mul_i32_i64(months, right)?,
+            mul_i32_i64(days, right)?,
+            nanos.mul_checked(right)?,
+        ))
+    }
+}
+
+fn interval_mul_op<T: IntervalOp>(
+    interval: &dyn Array,
+    interval_scalar: bool,
+    factor: &dyn Array,
+    factor_scalar: bool,
+) -> Result<ArrayRef, ArrowError> {
+    let interval = interval.as_primitive::<T>();
+    let factor = factor.as_primitive::<Int64Type>();
+    Ok(try_op_ref!(
+        T,
+        interval,
+        interval_scalar,
+        factor,
+        factor_scalar,
+        T::mul_i64(interval, factor)
+    ))
 }
 
 /// Perform arithmetic operation on an interval array
@@ -1555,6 +1608,70 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_interval_mul_i64() {
+        let interval = IntervalYearMonthArray::from(vec![16, 5, 0]);
+        let factor = Int64Array::from(vec![3, -2, i64::MAX]);
+        let expected = IntervalYearMonthArray::from(vec![48, -10, 0]);
+        assert_eq!(mul(&interval, &factor).unwrap().as_ref(), &expected);
+        assert_eq!(mul(&factor, &interval).unwrap().as_ref(), &expected);
+
+        let interval = IntervalDayTimeArray::from(vec![
+            Some(IntervalDayTimeType::make_value(10, 2 * 60 * 60 * 1000)),
+            None,
+        ]);
+        let factor = Int64Array::new_scalar(3);
+        let expected = IntervalDayTimeArray::from(vec![
+            Some(IntervalDayTimeType::make_value(30, 6 * 60 * 60 * 1000)),
+            None,
+        ]);
+        assert_eq!(mul(&interval, &factor).unwrap().as_ref(), &expected);
+        assert_eq!(mul(&factor, &interval).unwrap().as_ref(), &expected);
+
+        let null_factor = Scalar::new(Int64Array::new_null(1));
+        let expected = IntervalDayTimeArray::new_null(interval.len());
+        assert_eq!(mul(&interval, &null_factor).unwrap().as_ref(), &expected);
+        assert_eq!(mul(&null_factor, &interval).unwrap().as_ref(), &expected);
+
+        let interval = 
IntervalMonthDayNanoArray::new_scalar(IntervalMonthDayNanoType::make_value(
+            12,
+            15,
+            5_000_000_000,
+        ));
+        let factor = Int64Array::from(vec![2, 0, -1]);
+        let expected = IntervalMonthDayNanoArray::from(vec![
+            IntervalMonthDayNanoType::make_value(24, 30, 10_000_000_000),
+            IntervalMonthDayNanoType::make_value(0, 0, 0),
+            IntervalMonthDayNanoType::make_value(-12, -15, -5_000_000_000),
+        ]);
+        assert_eq!(mul(&interval, &factor).unwrap().as_ref(), &expected);
+        assert_eq!(mul(&factor, &interval).unwrap().as_ref(), &expected);
+
+        let float_factor = Float64Array::new_scalar(2.);
+        assert!(mul(&interval, &float_factor).is_err());
+        assert!(mul_wrapping(&factor, &interval).is_err());
+    }
+
+    #[test]
+    fn test_interval_mul_i64_overflow() {
+        let interval = IntervalYearMonthArray::from(vec![i32::MAX]);
+        let factor = Int64Array::from(vec![2]);
+        assert_eq!(
+            mul(&interval, &factor).unwrap_err().to_string(),
+            "Arithmetic overflow: Overflow happened on: 2147483647 * 2"
+        );
+
+        let interval = 
IntervalMonthDayNanoArray::from(vec![IntervalMonthDayNanoType::make_value(
+            0,
+            0,
+            i64::MAX,
+        )]);
+        assert_eq!(
+            mul(&interval, &factor).unwrap_err().to_string(),
+            "Arithmetic overflow: Overflow happened on: 9223372036854775807 * 
2"
+        );
+    }
+
     fn test_duration_impl<T: ArrowPrimitiveType<Native = i64>>() {
         let a = PrimitiveArray::<T>::new(vec![1000, 4394, -3944].into(), None);
         let b = PrimitiveArray::<T>::new(vec![4, -5, -243].into(), None);

Reply via email to