You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by tu...@apache.org on 2023/04/01 18:27:35 UTC

[arrow-rs] branch master updated: Handle precision overflow when casting from integer to decimal (#3996)

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

tustvold pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/master by this push:
     new 4e7bb4505 Handle precision overflow when casting from integer to decimal (#3996)
4e7bb4505 is described below

commit 4e7bb45050622d5b43505aa64dacf410cb329941
Author: Liang-Chi Hsieh <vi...@gmail.com>
AuthorDate: Sat Apr 1 11:27:29 2023 -0700

    Handle precision overflow when casting from integer to decimal (#3996)
    
    * Handle overflow precision when casting from integer to decimal
    
    * fix clippy
    
    * Update test
    
    * Update test
---
 arrow-cast/src/cast.rs | 73 +++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 63 insertions(+), 10 deletions(-)

diff --git a/arrow-cast/src/cast.rs b/arrow-cast/src/cast.rs
index 9886decd9..e4f4370fd 100644
--- a/arrow-cast/src/cast.rs
+++ b/arrow-cast/src/cast.rs
@@ -358,13 +358,29 @@ where
 
     let array = if scale < 0 {
         match cast_options.safe {
-            true => array.unary_opt::<_, D>(|v| v.as_().div_checked(scale_factor).ok()),
-            false => array.try_unary::<_, D, _>(|v| v.as_().div_checked(scale_factor))?,
+            true => array.unary_opt::<_, D>(|v| {
+                v.as_().div_checked(scale_factor).ok().and_then(|v| {
+                    (D::validate_decimal_precision(v, precision).is_ok()).then_some(v)
+                })
+            }),
+            false => array.try_unary::<_, D, _>(|v| {
+                v.as_()
+                    .div_checked(scale_factor)
+                    .and_then(|v| D::validate_decimal_precision(v, precision).map(|_| v))
+            })?,
         }
     } else {
         match cast_options.safe {
-            true => array.unary_opt::<_, D>(|v| v.as_().mul_checked(scale_factor).ok()),
-            false => array.try_unary::<_, D, _>(|v| v.as_().mul_checked(scale_factor))?,
+            true => array.unary_opt::<_, D>(|v| {
+                v.as_().mul_checked(scale_factor).ok().and_then(|v| {
+                    (D::validate_decimal_precision(v, precision).is_ok()).then_some(v)
+                })
+            }),
+            false => array.try_unary::<_, D, _>(|v| {
+                v.as_()
+                    .mul_checked(scale_factor)
+                    .and_then(|v| D::validate_decimal_precision(v, precision).map(|_| v))
+            })?,
         }
     };
 
@@ -4375,8 +4391,7 @@ mod tests {
         assert!(casted_array.is_ok());
         let array = casted_array.unwrap();
         let array: &Decimal128Array = array.as_primitive();
-        let err = array.validate_decimal_precision(3);
-        assert_eq!("Invalid argument error: 1000 is too large to store in a Decimal128 of precision 3. Max is 999", err.unwrap_err().to_string());
+        assert!(array.is_null(4));
 
         // test i8 to decimal type with overflow the result type
         // the 100 will be converted to 1000_i128, but it is out of range for max value in the precision 3.
@@ -4385,8 +4400,7 @@ mod tests {
         assert!(casted_array.is_ok());
         let array = casted_array.unwrap();
         let array: &Decimal128Array = array.as_primitive();
-        let err = array.validate_decimal_precision(3);
-        assert_eq!("Invalid argument error: 1000 is too large to store in a Decimal128 of precision 3. Max is 999", err.unwrap_err().to_string());
+        assert!(array.is_null(4));
 
         // test f32 to decimal type
         let array = Float32Array::from(vec![
@@ -4544,8 +4558,7 @@ mod tests {
         assert!(casted_array.is_ok());
         let array = casted_array.unwrap();
         let array: &Decimal256Array = array.as_primitive();
-        let err = array.validate_decimal_precision(3);
-        assert_eq!("Invalid argument error: 1000 is too large to store in a Decimal256 of precision 3. Max is 999", err.unwrap_err().to_string());
+        assert!(array.is_null(4));
 
         // test f32 to decimal type
         let array = Float32Array::from(vec![
@@ -8132,4 +8145,44 @@ mod tests {
             .unwrap();
         assert_eq!(1672531200000000000, c.value(0));
     }
+
+    #[test]
+    fn test_cast_numeric_to_decimal128_precision_overflow() {
+        let array = Int64Array::from(vec![1234567]);
+        let array = Arc::new(array) as ArrayRef;
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal128(7, 3),
+            &CastOptions { safe: true },
+        );
+        assert!(casted_array.is_ok());
+        assert!(casted_array.unwrap().is_null(0));
+
+        let err = cast_with_options(
+            &array,
+            &DataType::Decimal128(7, 3),
+            &CastOptions { safe: false },
+        );
+        assert_eq!("Invalid argument error: 1234567000 is too large to store in a Decimal128 of precision 7. Max is 9999999", err.unwrap_err().to_string());
+    }
+
+    #[test]
+    fn test_cast_numeric_to_decimal256_precision_overflow() {
+        let array = Int64Array::from(vec![1234567]);
+        let array = Arc::new(array) as ArrayRef;
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal256(7, 3),
+            &CastOptions { safe: true },
+        );
+        assert!(casted_array.is_ok());
+        assert!(casted_array.unwrap().is_null(0));
+
+        let err = cast_with_options(
+            &array,
+            &DataType::Decimal256(7, 3),
+            &CastOptions { safe: false },
+        );
+        assert_eq!("Invalid argument error: 1234567000 is too large to store in a Decimal256 of precision 7. Max is 9999999", err.unwrap_err().to_string());
+    }
 }