Skip to content

Commit

Permalink
Fix overflow in factorial (apache#11134)
Browse files Browse the repository at this point in the history
* fix: Return overflow error instead of panicking

* refactor: change the way we do checked multiplication and the error returned

* cleaner order of operations on factorial results

* test: add test for factorial overflow
  • Loading branch information
LorrensP-2158466 committed Jun 26, 2024
1 parent aff777b commit f6f63b9
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 21 deletions.
44 changes: 23 additions & 21 deletions datafusion/functions/src/math/factorial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@
// specific language governing permissions and limitations
// under the License.

use arrow::array::{ArrayRef, Int64Array};
use arrow::{
array::{ArrayRef, Int64Array},
error::ArrowError,
};
use std::any::Any;
use std::sync::Arc;

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, ScalarUDFImpl, Signature, Volatility};

#[derive(Debug)]
Expand Down Expand Up @@ -67,28 +70,27 @@ impl ScalarUDFImpl for FactorialFunc {
}
}

macro_rules! make_function_scalar_inputs {
($ARG: expr, $NAME:expr, $ARRAY_TYPE:ident, $FUNC: block) => {{
let arg = downcast_arg!($ARG, $NAME, $ARRAY_TYPE);

arg.iter()
.map(|a| match a {
Some(a) => Some($FUNC(a)),
_ => None,
})
.collect::<$ARRAY_TYPE>()
}};
}

/// Factorial SQL function
fn factorial(args: &[ArrayRef]) -> Result<ArrayRef> {
match args[0].data_type() {
DataType::Int64 => Ok(Arc::new(make_function_scalar_inputs!(
&args[0],
"value",
Int64Array,
{ |value: i64| { (1..=value).product() } }
)) as ArrayRef),
DataType::Int64 => {
let arg = downcast_arg!((&args[0]), "value", Int64Array);
Ok(arg
.iter()
.map(|a| match a {
Some(a) => (2..=a)
.try_fold(1i64, i64::checked_mul)
.ok_or_else(|| {
arrow_datafusion_err!(ArrowError::ComputeError(format!(
"Overflow happened on FACTORIAL({a})"
)))
})
.map(Some),
_ => Ok(None),
})
.collect::<Result<Int64Array>>()
.map(Arc::new)? as ArrayRef)
}
other => exec_err!("Unsupported data type {other:?} for function factorial."),
}
}
Expand Down
4 changes: 4 additions & 0 deletions datafusion/sqllogictest/test_files/math.slt
Original file line number Diff line number Diff line change
Expand Up @@ -592,3 +592,7 @@ select lcm(-9223372036854775808, -9223372036854775808);

query error DataFusion error: Arrow error: Compute error: Overflow happened on: 2107754225 \^ 1221660777
select power(2107754225, 1221660777);

# factorial overflow
query error DataFusion error: Arrow error: Compute error: Overflow happened on FACTORIAL\(350943270\)
select FACTORIAL(350943270);

0 comments on commit f6f63b9

Please sign in to comment.