use arrow::array::{ArrayRef, AsArray, PrimitiveArray};
use arrow::compute::try_binary;
use arrow::datatypes::{
DataType, Decimal32Type, Decimal64Type, Decimal128Type, Decimal256Type, Int64Type,
};
use std::sync::Arc;
use crate::math::common::{gcd_signed, gcd_signed_int, unsigned_gcd};
use crate::utils::calculate_binary_decimal_math_cast;
use datafusion_common::utils::take_function_args;
use datafusion_common::{
Result, ScalarValue, exec_err, internal_datafusion_err, plan_err,
};
use datafusion_expr::{
ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
Volatility,
};
use datafusion_expr_common::type_coercion::binary::decimal_coercion;
use datafusion_macros::user_doc;
#[user_doc(
doc_section(label = "Math Functions"),
description = "Returns the greatest common divisor of `expression_x` and `expression_y`. Returns 0 if both inputs are zero.",
syntax_example = "gcd(expression_x, expression_y)",
sql_example = r#"```sql
> SELECT gcd(48, 18);
+------------+
| gcd(48,18) |
+------------+
| 6 |
+------------+
```"#,
standard_argument(name = "expression_x", prefix = "First numeric"),
standard_argument(name = "expression_y", prefix = "Second numeric")
)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct GcdFunc {
signature: Signature,
}
impl Default for GcdFunc {
fn default() -> Self {
Self::new()
}
}
impl GcdFunc {
pub fn new() -> Self {
Self {
signature: Signature::user_defined(Volatility::Immutable),
}
}
}
impl ScalarUDFImpl for GcdFunc {
fn name(&self) -> &str {
"gcd"
}
fn signature(&self) -> &Signature {
&self.signature
}
fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
Ok(arg_types[0].clone())
}
fn is_strict(&self) -> bool {
true
}
fn coerce_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
let [arg1, arg2] = take_function_args(self.name(), arg_types)?;
let coerced_type = match (arg1, arg2) {
(DataType::Null, _) | (_, DataType::Null) => Ok(DataType::Int64),
(lhs, rhs) if lhs.is_integer() && rhs.is_integer() => Ok(DataType::Int64),
(lhs, rhs) if lhs.is_decimal() || rhs.is_decimal() => {
decimal_coercion(lhs, rhs).map(Ok).unwrap_or_else(|| {
plan_err!(
"Unsupported argument types {lhs:?} and {rhs:?} for function {}",
self.name()
)
})
}
(lhs, rhs) => {
plan_err!(
"Unsupported argument types {lhs:?} and {rhs:?} for function {}",
self.name()
)
}
}?;
Ok(vec![coerced_type.clone(), coerced_type])
}
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
let number_rows = args.number_rows;
let args: [ColumnarValue; 2] = args.args.try_into().map_err(|_| {
internal_datafusion_err!("Expected 2 arguments for function gcd")
})?;
if args[0].data_type() == DataType::Int64 {
match args {
[ColumnarValue::Array(a), ColumnarValue::Array(b)] => {
compute_gcd_for_arrays(&a, &b)
}
[
ColumnarValue::Scalar(ScalarValue::Int64(a)),
ColumnarValue::Scalar(ScalarValue::Int64(b)),
] => match (a, b) {
(Some(a), Some(b)) => Ok(ColumnarValue::Scalar(ScalarValue::Int64(
Some(gcd_signed_int(a, b)?),
))),
_ => Ok(ColumnarValue::Scalar(ScalarValue::Int64(None))),
},
[
ColumnarValue::Array(a),
ColumnarValue::Scalar(ScalarValue::Int64(b)),
] => compute_gcd_with_scalar(&a, b),
[
ColumnarValue::Scalar(ScalarValue::Int64(a)),
ColumnarValue::Array(b),
] => compute_gcd_with_scalar(&b, a),
_ => exec_err!("Unsupported argument types for function gcd"),
}
} else {
let left = args[0].to_array(number_rows)?;
let right = &args[1];
let arr: ArrayRef = match (left.data_type(), right.data_type()) {
(
lhs @ DataType::Decimal32(precision, scale),
rhs @ DataType::Decimal32(_, _),
) if *lhs == rhs => calculate_binary_decimal_math_cast::<
Decimal32Type,
Decimal32Type,
Decimal32Type,
_,
>(
&left, right, gcd_signed, *precision, *scale, lhs
)?,
(
lhs @ DataType::Decimal64(precision, scale),
rhs @ DataType::Decimal64(_, _),
) if *lhs == rhs => calculate_binary_decimal_math_cast::<
Decimal64Type,
Decimal64Type,
Decimal64Type,
_,
>(
&left, right, gcd_signed, *precision, *scale, lhs
)?,
(
lhs @ DataType::Decimal128(precision, scale),
rhs @ DataType::Decimal128(_, _),
) if *lhs == rhs => calculate_binary_decimal_math_cast::<
Decimal128Type,
Decimal128Type,
Decimal128Type,
_,
>(
&left, right, gcd_signed, *precision, *scale, lhs
)?,
(
lhs @ DataType::Decimal256(precision, scale),
rhs @ DataType::Decimal256(_, _),
) if *lhs == rhs => calculate_binary_decimal_math_cast::<
Decimal256Type,
Decimal256Type,
Decimal256Type,
_,
>(
&left, right, gcd_signed, *precision, *scale, lhs
)?,
(lhs, rhs) => {
exec_err!(
"Unsupported data types {lhs:?} and {rhs:?} for function {}",
self.name()
)
}?,
};
Ok(ColumnarValue::Array(arr))
}
}
fn documentation(&self) -> Option<&Documentation> {
self.doc()
}
}
fn compute_gcd_for_arrays(a: &ArrayRef, b: &ArrayRef) -> Result<ColumnarValue> {
let a = a.as_primitive::<Int64Type>();
let b = b.as_primitive::<Int64Type>();
try_binary(a, b, gcd_signed_int)
.map(|arr: PrimitiveArray<Int64Type>| {
ColumnarValue::Array(Arc::new(arr) as ArrayRef)
})
.map_err(Into::into) }
fn compute_gcd_with_scalar(arr: &ArrayRef, scalar: Option<i64>) -> Result<ColumnarValue> {
let prim = arr.as_primitive::<Int64Type>();
match scalar {
Some(scalar_value) if scalar_value != 0 && scalar_value != i64::MIN => {
let sv = scalar_value.unsigned_abs();
let result: PrimitiveArray<Int64Type> =
prim.unary(|val| unsigned_gcd(val.unsigned_abs(), sv) as i64);
Ok(ColumnarValue::Array(Arc::new(result) as ArrayRef))
}
Some(scalar_value) => {
let result: PrimitiveArray<Int64Type> =
prim.try_unary(|val| gcd_signed_int(val, scalar_value))?;
Ok(ColumnarValue::Array(Arc::new(result) as ArrayRef))
}
None => Ok(ColumnarValue::Scalar(ScalarValue::Int64(None))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coercion() {
let mut coerced = GcdFunc::new()
.coerce_types(&[DataType::Int64, DataType::Int32])
.expect("coercion should succeed");
assert_eq!(coerced, vec![DataType::Int64, DataType::Int64]);
coerced = GcdFunc::new()
.coerce_types(&[DataType::Decimal128(10, 2), DataType::Int32])
.expect("coercion should succeed");
assert_eq!(
coerced,
vec![DataType::Decimal128(12, 2), DataType::Decimal128(12, 2)]
);
coerced = GcdFunc::new()
.coerce_types(&[DataType::Decimal128(10, 2), DataType::Null])
.expect("coercion should succeed");
assert_eq!(coerced, vec![DataType::Int64, DataType::Int64]);
}
}