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 62bb64cf9f Check precision overflow for casting floating to decimal 
(#4866)
62bb64cf9f is described below

commit 62bb64cf9f034a75e4485719653253077eb8efa6
Author: Liang-Chi Hsieh <[email protected]>
AuthorDate: Wed Sep 27 12:17:30 2023 -0700

    Check precision overflow for casting floating to decimal (#4866)
    
    * Check precision overflow for casting floating to decimal
    
    * For review
---
 arrow-cast/src/cast.rs | 122 +++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 102 insertions(+), 20 deletions(-)

diff --git a/arrow-cast/src/cast.rs b/arrow-cast/src/cast.rs
index e7727565c9..54c500f1ac 100644
--- a/arrow-cast/src/cast.rs
+++ b/arrow-cast/src/cast.rs
@@ -364,21 +364,32 @@ where
 
     if cast_options.safe {
         array
-            .unary_opt::<_, Decimal128Type>(|v| (mul * 
v.as_()).round().to_i128())
+            .unary_opt::<_, Decimal128Type>(|v| {
+                (mul * v.as_()).round().to_i128().filter(|v| {
+                    Decimal128Type::validate_decimal_precision(*v, 
precision).is_ok()
+                })
+            })
             .with_precision_and_scale(precision, scale)
             .map(|a| Arc::new(a) as ArrayRef)
     } else {
         array
             .try_unary::<_, Decimal128Type, _>(|v| {
-                (mul * v.as_()).round().to_i128().ok_or_else(|| {
-                    ArrowError::CastError(format!(
-                        "Cannot cast to {}({}, {}). Overflowing on {:?}",
-                        Decimal128Type::PREFIX,
-                        precision,
-                        scale,
-                        v
-                    ))
-                })
+                (mul * v.as_())
+                    .round()
+                    .to_i128()
+                    .ok_or_else(|| {
+                        ArrowError::CastError(format!(
+                            "Cannot cast to {}({}, {}). Overflowing on {:?}",
+                            Decimal128Type::PREFIX,
+                            precision,
+                            scale,
+                            v
+                        ))
+                    })
+                    .and_then(|v| {
+                        Decimal128Type::validate_decimal_precision(v, 
precision)
+                            .map(|_| v)
+                    })
             })?
             .with_precision_and_scale(precision, scale)
             .map(|a| Arc::new(a) as ArrayRef)
@@ -398,21 +409,30 @@ where
 
     if cast_options.safe {
         array
-            .unary_opt::<_, Decimal256Type>(|v| i256::from_f64((v.as_() * 
mul).round()))
+            .unary_opt::<_, Decimal256Type>(|v| {
+                i256::from_f64((v.as_() * mul).round()).filter(|v| {
+                    Decimal256Type::validate_decimal_precision(*v, 
precision).is_ok()
+                })
+            })
             .with_precision_and_scale(precision, scale)
             .map(|a| Arc::new(a) as ArrayRef)
     } else {
         array
             .try_unary::<_, Decimal256Type, _>(|v| {
-                i256::from_f64((v.as_() * mul).round()).ok_or_else(|| {
-                    ArrowError::CastError(format!(
-                        "Cannot cast to {}({}, {}). Overflowing on {:?}",
-                        Decimal256Type::PREFIX,
-                        precision,
-                        scale,
-                        v
-                    ))
-                })
+                i256::from_f64((v.as_() * mul).round())
+                    .ok_or_else(|| {
+                        ArrowError::CastError(format!(
+                            "Cannot cast to {}({}, {}). Overflowing on {:?}",
+                            Decimal256Type::PREFIX,
+                            precision,
+                            scale,
+                            v
+                        ))
+                    })
+                    .and_then(|v| {
+                        Decimal256Type::validate_decimal_precision(v, 
precision)
+                            .map(|_| v)
+                    })
             })?
             .with_precision_and_scale(precision, scale)
             .map(|a| Arc::new(a) as ArrayRef)
@@ -7748,6 +7768,68 @@ mod tests {
         assert!(casted_array.is_err());
     }
 
+    #[test]
+    fn test_cast_floating_point_to_decimal128_precision_overflow() {
+        let array = Float64Array::from(vec![1.1]);
+        let array = Arc::new(array) as ArrayRef;
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal128(2, 2),
+            &CastOptions {
+                safe: true,
+                format_options: FormatOptions::default(),
+            },
+        );
+        assert!(casted_array.is_ok());
+        assert!(casted_array.unwrap().is_null(0));
+
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal128(2, 2),
+            &CastOptions {
+                safe: false,
+                format_options: FormatOptions::default(),
+            },
+        );
+        let err = casted_array.unwrap_err().to_string();
+        let expected_error = "Invalid argument error: 110 is too large to 
store in a Decimal128 of precision 2. Max is 99";
+        assert!(
+            err.contains(expected_error),
+            "did not find expected error '{expected_error}' in actual error 
'{err}'"
+        );
+    }
+
+    #[test]
+    fn test_cast_floating_point_to_decimal256_precision_overflow() {
+        let array = Float64Array::from(vec![1.1]);
+        let array = Arc::new(array) as ArrayRef;
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal256(2, 2),
+            &CastOptions {
+                safe: true,
+                format_options: FormatOptions::default(),
+            },
+        );
+        assert!(casted_array.is_ok());
+        assert!(casted_array.unwrap().is_null(0));
+
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal256(2, 2),
+            &CastOptions {
+                safe: false,
+                format_options: FormatOptions::default(),
+            },
+        );
+        let err = casted_array.unwrap_err().to_string();
+        let expected_error = "Invalid argument error: 110 is too large to 
store in a Decimal256 of precision 2. Max is 99";
+        assert!(
+            err.contains(expected_error),
+            "did not find expected error '{expected_error}' in actual error 
'{err}'"
+        );
+    }
+
     #[test]
     fn test_cast_floating_point_to_decimal128_overflow() {
         let array = Float64Array::from(vec![f64::MAX]);

Reply via email to