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/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 8aad208299 handle overflow in gcd and return this as an error (#11057)
8aad208299 is described below

commit 8aad2082994bdea14eaf7d0e8c177aa3ce9af67b
Author: Lorrens Pantelis <[email protected]>
AuthorDate: Sun Jun 23 12:22:58 2024 +0200

    handle overflow in gcd and return this as an error (#11057)
    
    * handle overflow in gcd and return this as an error
    
    * Add SLT test
    
    ---------
    
    Co-authored-by: Andrew Lamb <[email protected]>
---
 datafusion/functions/src/math/gcd.rs        | 62 +++++++++++++++++++++--------
 datafusion/functions/src/math/lcm.rs        | 36 +++++++++++------
 datafusion/sqllogictest/test_files/math.slt | 11 +++++
 3 files changed, 81 insertions(+), 28 deletions(-)

diff --git a/datafusion/functions/src/math/gcd.rs 
b/datafusion/functions/src/math/gcd.rs
index d0199f7a22..6346f96d2c 100644
--- a/datafusion/functions/src/math/gcd.rs
+++ b/datafusion/functions/src/math/gcd.rs
@@ -16,6 +16,7 @@
 // under the License.
 
 use arrow::array::{ArrayRef, Int64Array};
+use arrow::error::ArrowError;
 use std::any::Any;
 use std::mem::swap;
 use std::sync::Arc;
@@ -24,7 +25,7 @@ use arrow::datatypes::DataType;
 use arrow::datatypes::DataType::Int64;
 
 use crate::utils::make_scalar_function;
-use datafusion_common::{exec_err, DataFusionError, Result};
+use datafusion_common::{arrow_datafusion_err, exec_err, DataFusionError, 
Result};
 use datafusion_expr::ColumnarValue;
 use datafusion_expr::{ScalarUDFImpl, Signature, Volatility};
 
@@ -73,26 +74,31 @@ impl ScalarUDFImpl for GcdFunc {
 /// Gcd SQL function
 fn gcd(args: &[ArrayRef]) -> Result<ArrayRef> {
     match args[0].data_type() {
-        Int64 => Ok(Arc::new(make_function_inputs2!(
-            &args[0],
-            &args[1],
-            "x",
-            "y",
-            Int64Array,
-            Int64Array,
-            { compute_gcd }
-        )) as ArrayRef),
+        Int64 => {
+            let arg1 = downcast_arg!(&args[0], "x", Int64Array);
+            let arg2 = downcast_arg!(&args[1], "y", Int64Array);
+
+            Ok(arg1
+                .iter()
+                .zip(arg2.iter())
+                .map(|(a1, a2)| match (a1, a2) {
+                    (Some(a1), Some(a2)) => Ok(Some(compute_gcd(a1, a2)?)),
+                    _ => Ok(None),
+                })
+                .collect::<Result<Int64Array>>()
+                .map(Arc::new)? as ArrayRef)
+        }
         other => exec_err!("Unsupported data type {other:?} for function gcd"),
     }
 }
 
 /// Computes greatest common divisor using Binary GCD algorithm.
-pub fn compute_gcd(x: i64, y: i64) -> i64 {
+pub fn compute_gcd(x: i64, y: i64) -> Result<i64> {
     if x == 0 {
-        return y;
+        return Ok(y);
     }
     if y == 0 {
-        return x;
+        return Ok(x);
     }
 
     let mut a = x.unsigned_abs();
@@ -113,7 +119,11 @@ pub fn compute_gcd(x: i64, y: i64) -> i64 {
 
         if b == 0 {
             // because the input values are i64, casting this back to i64 is 
safe
-            return (a << shift) as i64;
+            return (a << shift).try_into().map_err(|_| {
+                arrow_datafusion_err!(ArrowError::ComputeError(format!(
+                    "Signed integer overflow in GCD({x}, {y})"
+                )))
+            });
         }
     }
 }
@@ -122,10 +132,13 @@ pub fn compute_gcd(x: i64, y: i64) -> i64 {
 mod test {
     use std::sync::Arc;
 
-    use arrow::array::{ArrayRef, Int64Array};
+    use arrow::{
+        array::{ArrayRef, Int64Array},
+        error::ArrowError,
+    };
 
     use crate::math::gcd::gcd;
-    use datafusion_common::cast::as_int64_array;
+    use datafusion_common::{cast::as_int64_array, DataFusionError};
 
     #[test]
     fn test_gcd_i64() {
@@ -143,4 +156,21 @@ mod test {
         assert_eq!(ints.value(2), 5);
         assert_eq!(ints.value(3), 8);
     }
+
+    #[test]
+    fn overflow_on_both_param_i64_min() {
+        let args: Vec<ArrayRef> = vec![
+            Arc::new(Int64Array::from(vec![i64::MIN])), // x
+            Arc::new(Int64Array::from(vec![i64::MIN])), // y
+        ];
+
+        match gcd(&args) {
+            // we expect a overflow
+            Err(DataFusionError::ArrowError(ArrowError::ComputeError(_), _)) 
=> {}
+            Err(_) => {
+                panic!("failed to initialize function gcd")
+            }
+            Ok(_) => panic!("GCD({0}, {0}) should have overflown", i64::MIN),
+        };
+    }
 }
diff --git a/datafusion/functions/src/math/lcm.rs 
b/datafusion/functions/src/math/lcm.rs
index 3674f7371d..d58bc5ef0b 100644
--- a/datafusion/functions/src/math/lcm.rs
+++ b/datafusion/functions/src/math/lcm.rs
@@ -22,7 +22,8 @@ use arrow::array::{ArrayRef, Int64Array};
 use arrow::datatypes::DataType;
 use arrow::datatypes::DataType::Int64;
 
-use datafusion_common::{exec_err, DataFusionError, Result};
+use arrow::error::ArrowError;
+use datafusion_common::{arrow_datafusion_err, exec_err, DataFusionError, 
Result};
 use datafusion_expr::ColumnarValue;
 use datafusion_expr::{ScalarUDFImpl, Signature, Volatility};
 
@@ -78,21 +79,32 @@ fn lcm(args: &[ArrayRef]) -> Result<ArrayRef> {
         let b = y.wrapping_abs();
 
         if a == 0 || b == 0 {
-            return 0;
+            return Ok(0);
+        }
+        match compute_gcd(a, b) {
+            Ok(gcd) => Ok(a / gcd * b),
+            // change the error string to use LCM instead of GCD
+            Err(_) => 
Err(arrow_datafusion_err!(ArrowError::ComputeError(format!(
+                "Signed integer overflow in LCM({x}, {y})"
+            )))),
         }
-        a / compute_gcd(a, b) * b
     };
 
     match args[0].data_type() {
-        Int64 => Ok(Arc::new(make_function_inputs2!(
-            &args[0],
-            &args[1],
-            "x",
-            "y",
-            Int64Array,
-            Int64Array,
-            { compute_lcm }
-        )) as ArrayRef),
+        Int64 => {
+            let arg1 = downcast_arg!(&args[0], "x", Int64Array);
+            let arg2 = downcast_arg!(&args[1], "y", Int64Array);
+
+            Ok(arg1
+                .iter()
+                .zip(arg2.iter())
+                .map(|(a1, a2)| match (a1, a2) {
+                    (Some(a1), Some(a2)) => Ok(Some(compute_lcm(a1, a2)?)),
+                    _ => Ok(None),
+                })
+                .collect::<Result<Int64Array>>()
+                .map(Arc::new)? as ArrayRef)
+        }
         other => exec_err!("Unsupported data type {other:?} for function lcm"),
     }
 }
diff --git a/datafusion/sqllogictest/test_files/math.slt 
b/datafusion/sqllogictest/test_files/math.slt
index 78efbb3f56..3a433c76fc 100644
--- a/datafusion/sqllogictest/test_files/math.slt
+++ b/datafusion/sqllogictest/test_files/math.slt
@@ -564,3 +564,14 @@ SELECT c1%0 FROM test_non_nullable_decimal
 
 statement ok
 drop table test_non_nullable_decimal 
+
+query II
+select gcd(20, 1000), lcm(20, 1000);
+----
+20 1000
+
+query error DataFusion error: Arrow error: Compute error: Signed integer 
overflow in GCD\(\-9223372036854775808, \-9223372036854775808\)
+select gcd(-9223372036854775808, -9223372036854775808);
+
+query error DataFusion error: Arrow error: Compute error: Signed integer 
overflow in LCM\(\-9223372036854775808, \-9223372036854775808\)
+select lcm(-9223372036854775808, -9223372036854775808);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to