Skip to main content

datafusion_functions/
utils.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use arrow::array::{Array, ArrayRef, ArrowPrimitiveType, AsArray, PrimitiveArray};
19use arrow::compute::try_binary;
20use arrow::datatypes::{DataType, DecimalType};
21use arrow::error::ArrowError;
22use datafusion_common::{DataFusionError, Result, ScalarValue};
23use datafusion_expr::ColumnarValue;
24use datafusion_expr::function::Hint;
25use std::sync::Arc;
26
27/// Creates a function to identify the optimal return type of a string function given
28/// the type of its first argument.
29///
30/// If the input type is `LargeUtf8` or `LargeBinary` the return type is
31/// `$largeUtf8Type`,
32///
33/// If the input type is `Utf8` or `Binary` the return type is `$utf8Type`,
34///
35/// If the input type is `Utf8View` the return type is $utf8Type,
36macro_rules! get_optimal_return_type {
37    ($FUNC:ident, $largeUtf8Type:expr, $utf8Type:expr) => {
38        pub(crate) fn $FUNC(arg_type: &DataType, name: &str) -> Result<DataType> {
39            Ok(match arg_type {
40                // LargeBinary inputs are automatically coerced to Utf8
41                DataType::LargeUtf8 | DataType::LargeBinary => $largeUtf8Type,
42                // Binary inputs are automatically coerced to Utf8
43                DataType::Utf8 | DataType::Binary => $utf8Type,
44                // Utf8View max offset size is u32::MAX, the same as UTF8
45                DataType::Utf8View | DataType::BinaryView => $utf8Type,
46                DataType::Null => DataType::Null,
47                DataType::Dictionary(_, value_type) => match **value_type {
48                    DataType::LargeUtf8 | DataType::LargeBinary => $largeUtf8Type,
49                    DataType::Utf8 | DataType::Binary => $utf8Type,
50                    DataType::Null => DataType::Null,
51                    _ => {
52                        return datafusion_common::exec_err!(
53                            "The {} function can only accept strings, but got {:?}.",
54                            name.to_uppercase(),
55                            **value_type
56                        );
57                    }
58                },
59                data_type => {
60                    return datafusion_common::exec_err!(
61                        "The {} function can only accept strings, but got {:?}.",
62                        name.to_uppercase(),
63                        data_type
64                    );
65                }
66            })
67        }
68    };
69}
70
71// `utf8_to_str_type`: returns either a Utf8 or LargeUtf8 based on the input type size.
72get_optimal_return_type!(utf8_to_str_type, DataType::LargeUtf8, DataType::Utf8);
73
74// `utf8_to_int_type`: returns either a Int32 or Int64 based on the input type size.
75get_optimal_return_type!(utf8_to_int_type, DataType::Int64, DataType::Int32);
76
77/// Transforms the leaf type while preserving supported encoding containers.
78///
79/// Keep encoded type handling centralized here so additional encodings can be
80/// supported without changing each function's return type implementation.
81pub(crate) fn transform_leaf_type_preserving_encoding<F>(
82    arg_type: &DataType,
83    transform: &F,
84) -> Result<DataType>
85where
86    F: Fn(&DataType) -> Result<DataType>,
87{
88    match arg_type {
89        DataType::Dictionary(key_type, value_type) => Ok(DataType::Dictionary(
90            key_type.clone(),
91            Box::new(transform_leaf_type_preserving_encoding(
92                value_type, transform,
93            )?),
94        )),
95        _ => transform(arg_type),
96    }
97}
98
99/// Creates a scalar function implementation for the given function.
100/// * `inner` - the function to be executed
101/// * `hints` - hints to be used when expanding scalars to arrays
102pub fn make_scalar_function<F>(
103    inner: F,
104    hints: Vec<Hint>,
105) -> impl Fn(&[ColumnarValue]) -> Result<ColumnarValue>
106where
107    F: Fn(&[ArrayRef]) -> Result<ArrayRef>,
108{
109    move |args: &[ColumnarValue]| {
110        // first, identify if any of the arguments is an Array. If yes, store its `len`,
111        // as any scalar will need to be converted to an array of len `len`.
112        let len = args
113            .iter()
114            .fold(Option::<usize>::None, |acc, arg| match arg {
115                ColumnarValue::Scalar(_) => acc,
116                ColumnarValue::Array(a) => Some(a.len()),
117            });
118
119        let is_scalar = len.is_none();
120
121        let inferred_length = len.unwrap_or(1);
122        let args = args
123            .iter()
124            .zip(hints.iter().chain(std::iter::repeat(&Hint::Pad)))
125            .map(|(arg, hint)| {
126                // Decide on the length to expand this scalar to depending
127                // on the given hints.
128                let expansion_len = match hint {
129                    Hint::AcceptsSingular => 1,
130                    Hint::Pad => inferred_length,
131                };
132                arg.to_array(expansion_len)
133            })
134            .collect::<Result<Vec<_>>>()?;
135
136        let result = (inner)(&args);
137        if is_scalar {
138            // If all inputs are scalar, keeps output as scalar
139            let result = result.and_then(|arr| ScalarValue::try_from_array(&arr, 0));
140            result.map(ColumnarValue::Scalar)
141        } else {
142            result.map(ColumnarValue::Array)
143        }
144    }
145}
146
147/// Computes a binary math function for input arrays using a specified function.
148/// Generic types:
149/// - `L`: Left array primitive type
150/// - `R`: Right array primitive type
151/// - `O`: Output array primitive type
152/// - `F`: Functor computing `fun(l: L, r: R) -> Result<OutputType>`
153pub fn calculate_binary_math<L, R, O, F>(
154    left: &dyn Array,
155    right: &ColumnarValue,
156    fun: F,
157) -> Result<Arc<PrimitiveArray<O>>>
158where
159    L: ArrowPrimitiveType,
160    R: ArrowPrimitiveType,
161    O: ArrowPrimitiveType,
162    F: Fn(L::Native, R::Native) -> Result<O::Native, ArrowError>,
163    R::Native: TryFrom<ScalarValue>,
164{
165    calculate_binary_math_cast::<L, R, O, F>(left, right, fun, &R::DATA_TYPE)
166}
167
168/// Computes a binary math function for input arrays using a specified function
169/// and applies rescaling to given precision and scale.
170/// Generic types:
171/// - `L`: Left array decimal type
172/// - `R`: Right array primitive type
173/// - `O`: Output array decimal type
174/// - `F`: Functor computing `fun(l: L, r: R) -> Result<OutputType>`
175#[deprecated(
176    since = "55.0.0",
177    note = "Use `calculate_binary_decimal_math_cast` instead"
178)]
179pub fn calculate_binary_decimal_math<L, R, O, F>(
180    left: &dyn Array,
181    right: &ColumnarValue,
182    fun: F,
183    precision: u8,
184    scale: i8,
185) -> Result<Arc<PrimitiveArray<O>>>
186where
187    L: DecimalType,
188    R: ArrowPrimitiveType,
189    O: DecimalType,
190    F: Fn(L::Native, R::Native) -> Result<O::Native, ArrowError>,
191    R::Native: TryFrom<ScalarValue>,
192{
193    calculate_binary_decimal_math_cast::<L, R, O, F>(
194        left,
195        right,
196        fun,
197        precision,
198        scale,
199        &R::DATA_TYPE,
200    )
201}
202
203/// Computes a binary math function for input arrays using a specified function.
204///
205/// It casts the right operand to `cast_target` instead of the default `R::DATA_TYPE` to preserve
206/// the right operand scale.
207///
208/// # Type Parameters
209/// - `L`: Left array primitive type
210/// - `R`: Right array primitive type
211/// - `O`: Output array primitive type
212/// - `F`: Functor computing `fun(l: L, r: R) -> Result<OutputType>`
213/// # Arguments
214/// - `left`: Left input array
215/// - `right`: Right input array or scalar value
216/// - `fun`: Function of type `F`
217/// - `cast_target`: Data type to cast right operand to before applying function
218fn calculate_binary_math_cast<L, R, O, F>(
219    left: &dyn Array,
220    right: &ColumnarValue,
221    fun: F,
222    cast_target: &DataType,
223) -> Result<Arc<PrimitiveArray<O>>>
224where
225    L: ArrowPrimitiveType,
226    R: ArrowPrimitiveType,
227    O: ArrowPrimitiveType,
228    F: Fn(L::Native, R::Native) -> Result<O::Native, ArrowError>,
229    R::Native: TryFrom<ScalarValue>,
230{
231    let left = left.as_primitive::<L>();
232    let right = right.cast_to(cast_target, None)?;
233    let result = match right {
234        ColumnarValue::Scalar(scalar) => {
235            if scalar.is_null() {
236                // Null scalar is castable to any numeric, creating a non-null expression.
237                // Provide null array explicitly to make result null
238                PrimitiveArray::<O>::new_null(left.len())
239            } else {
240                let right = R::Native::try_from(scalar.clone()).map_err(|_| {
241                    DataFusionError::NotImplemented(format!(
242                        "Cannot convert scalar value {scalar} to {cast_target}"
243                    ))
244                })?;
245                left.try_unary::<_, O, _>(|lvalue| fun(lvalue, right))?
246            }
247        }
248        ColumnarValue::Array(right) => {
249            let right = right.as_primitive::<R>();
250            try_binary::<_, _, _, O>(left, right, &fun)?
251        }
252    };
253    Ok(Arc::new(result) as _)
254}
255
256/// Computes a binary math function for input arrays using a specified function
257/// and applies rescaling to given precision and scale.
258///
259/// It casts the right operand to `cast_target` instead of the default `R::DATA_TYPE` to preserve
260/// the right operand scale.
261///
262/// # Type Parameters
263/// - `L`: Left array decimal type
264/// - `R`: Right array primitive type
265/// - `O`: Output array decimal type
266/// - `F`: Functor computing `fun(l: L, r: R) -> Result<OutputType>`
267/// # Arguments
268/// - `left`: Left input array
269/// - `right`: Right input array or scalar value
270/// - `fun`: Function of type `F`
271/// - `precision`: Precision to apply to output decimal array
272/// - `scale`: Scale to apply to output decimal array
273/// - `cast_target`: Data type to cast right operand to before applying function
274pub fn calculate_binary_decimal_math_cast<L, R, O, F>(
275    left: &dyn Array,
276    right: &ColumnarValue,
277    fun: F,
278    precision: u8,
279    scale: i8,
280    cast_target: &DataType,
281) -> Result<Arc<PrimitiveArray<O>>>
282where
283    L: DecimalType,
284    R: ArrowPrimitiveType,
285    O: DecimalType,
286    F: Fn(L::Native, R::Native) -> Result<O::Native, ArrowError>,
287    R::Native: TryFrom<ScalarValue>,
288{
289    let result_array =
290        calculate_binary_math_cast::<L, R, O, F>(left, right, fun, cast_target)?;
291    Ok(Arc::new(
292        result_array
293            .as_ref()
294            .clone()
295            .with_precision_and_scale(precision, scale)?,
296    ))
297}
298
299/// Converts Decimal128 components (value and scale) to an unscaled i128
300pub fn decimal128_to_i128(value: i128, scale: i8) -> Result<i128, ArrowError> {
301    if scale < 0 {
302        Err(ArrowError::ComputeError(
303            "Negative scale is not supported".into(),
304        ))
305    } else if scale == 0 {
306        Ok(value)
307    } else {
308        match i128::from(10).checked_pow(scale as u32) {
309            Some(divisor) => Ok(value / divisor),
310            None => Err(ArrowError::ComputeError(format!(
311                "Cannot get a power of {scale}"
312            ))),
313        }
314    }
315}
316
317pub fn decimal32_to_i32(value: i32, scale: i8) -> Result<i32, ArrowError> {
318    if scale < 0 {
319        Err(ArrowError::ComputeError(
320            "Negative scale is not supported".into(),
321        ))
322    } else if scale == 0 {
323        Ok(value)
324    } else {
325        match 10_i32.checked_pow(scale as u32) {
326            Some(divisor) => Ok(value / divisor),
327            None => Err(ArrowError::ComputeError(format!(
328                "Cannot get a power of {scale}"
329            ))),
330        }
331    }
332}
333
334pub fn decimal64_to_i64(value: i64, scale: i8) -> Result<i64, ArrowError> {
335    if scale < 0 {
336        Err(ArrowError::ComputeError(
337            "Negative scale is not supported".into(),
338        ))
339    } else if scale == 0 {
340        Ok(value)
341    } else {
342        match i64::from(10).checked_pow(scale as u32) {
343            Some(divisor) => Ok(value / divisor),
344            None => Err(ArrowError::ComputeError(format!(
345                "Cannot get a power of {scale}"
346            ))),
347        }
348    }
349}
350
351#[cfg(test)]
352pub mod test {
353    /// $FUNC ScalarUDFImpl to test
354    /// $ARGS arguments (vec) to pass to function
355    /// $EXPECTED a Result<ColumnarValue>
356    /// $EXPECTED_TYPE is the expected value type
357    /// $EXPECTED_DATA_TYPE is the expected result type
358    /// $ARRAY_TYPE is the column type after function applied
359    /// $CONFIG_OPTIONS config options to pass to function
360    macro_rules! test_function {
361    ($FUNC:expr, $ARGS:expr, $EXPECTED:expr, $EXPECTED_TYPE:ty, $EXPECTED_DATA_TYPE:expr, $ARRAY_TYPE:ident, $CONFIG_OPTIONS:expr) => {
362        let expected: Result<Option<$EXPECTED_TYPE>> = $EXPECTED;
363        let func = $FUNC;
364
365        let data_array = $ARGS.iter().map(|arg| arg.data_type()).collect::<Vec<_>>();
366        let cardinality = $ARGS
367            .iter()
368            .fold(Option::<usize>::None, |acc, arg| match arg {
369                ColumnarValue::Scalar(_) => acc,
370                ColumnarValue::Array(a) => Some(a.len()),
371            })
372            .unwrap_or(1);
373
374            let scalar_arguments = $ARGS.iter().map(|arg| match arg {
375                ColumnarValue::Scalar(scalar) => Some(scalar.clone()),
376                ColumnarValue::Array(_) => None,
377            }).collect::<Vec<_>>();
378            let scalar_arguments_refs = scalar_arguments.iter().map(|arg| arg.as_ref()).collect::<Vec<_>>();
379
380            let nullables = $ARGS.iter().map(|arg| match arg {
381                ColumnarValue::Scalar(scalar) => scalar.is_null(),
382                ColumnarValue::Array(a) => a.null_count() > 0,
383            }).collect::<Vec<_>>();
384
385            let field_array = data_array.into_iter().zip(nullables).enumerate()
386                .map(|(idx, (data_type, nullable))| arrow::datatypes::Field::new(format!("field_{idx}"), data_type, nullable))
387            .map(std::sync::Arc::new)
388            .collect::<Vec<_>>();
389
390        let return_field = func.return_field_from_args(datafusion_expr::ReturnFieldArgs {
391            arg_fields: &field_array,
392            scalar_arguments: &scalar_arguments_refs,
393        });
394            let arg_fields = $ARGS.iter()
395            .enumerate()
396                .map(|(idx, arg)| arrow::datatypes::Field::new(format!("f_{idx}"), arg.data_type(), true).into())
397            .collect::<Vec<_>>();
398
399        match expected {
400            Ok(expected) => {
401                assert_eq!(return_field.is_ok(), true);
402                let return_field = return_field.unwrap();
403                let return_type = return_field.data_type();
404                assert_eq!(return_type, &$EXPECTED_DATA_TYPE);
405
406                    let result = func.invoke_with_args(datafusion_expr::ScalarFunctionArgs{
407                    args: $ARGS,
408                    arg_fields,
409                    number_rows: cardinality,
410                    return_field,
411                        config_options: $CONFIG_OPTIONS
412                });
413                    assert_eq!(result.is_ok(), true, "function returned an error: {}", result.unwrap_err());
414
415                    let result = result.unwrap().to_array(cardinality).expect("Failed to convert to array");
416                    let result = result.as_any().downcast_ref::<$ARRAY_TYPE>().expect("Failed to convert to type");
417                assert_eq!(result.data_type(), &$EXPECTED_DATA_TYPE);
418
419                // value is correct
420                match expected {
421                    Some(v) => assert_eq!(result.value(0), v),
422                    None => assert!(result.is_null(0)),
423                };
424            }
425            Err(expected_error) => {
426                if let Ok(return_field) = return_field {
427                    // invoke is expected error - cannot use .expect_err() due to Debug not being implemented
428                    match func.invoke_with_args(datafusion_expr::ScalarFunctionArgs {
429                        args: $ARGS,
430                        arg_fields,
431                        number_rows: cardinality,
432                        return_field,
433                        config_options: $CONFIG_OPTIONS,
434                    }) {
435                        Ok(_) => assert!(false, "expected error"),
436                        Err(error) => {
437                            assert!(expected_error
438                                .strip_backtrace()
439                                .starts_with(&error.strip_backtrace()));
440                        }
441                    }
442                } else if let Err(error) = return_field {
443                    datafusion_common::assert_contains!(
444                        expected_error.strip_backtrace(),
445                        error.strip_backtrace()
446                    );
447                }
448            }
449        };
450    };
451
452        ($FUNC:expr, $ARGS:expr, $EXPECTED:expr, $EXPECTED_TYPE:ty, $EXPECTED_DATA_TYPE:expr, $ARRAY_TYPE:ident) => {
453            test_function!(
454                $FUNC,
455                $ARGS,
456                $EXPECTED,
457                $EXPECTED_TYPE,
458                $EXPECTED_DATA_TYPE,
459                $ARRAY_TYPE,
460                std::sync::Arc::new(datafusion_common::config::ConfigOptions::default())
461            )
462        };
463    }
464
465    use arrow::{
466        array::Int32Array,
467        datatypes::{DataType, Int32Type},
468    };
469    use itertools::Either;
470    pub(crate) use test_function;
471
472    use super::*;
473
474    #[test]
475    fn test_calculate_binary_math_scalar_null() {
476        let left = Int32Array::from(vec![1, 2]);
477        let right = ColumnarValue::Scalar(ScalarValue::Int32(None));
478        let result = calculate_binary_math::<Int32Type, Int32Type, Int32Type, _>(
479            &left,
480            &right,
481            |x, y| Ok(x + y),
482        )
483        .unwrap();
484
485        assert_eq!(result.len(), 2);
486        assert_eq!(result.null_count(), 2);
487    }
488
489    #[test]
490    fn string_to_int_type() {
491        let v = utf8_to_int_type(&DataType::Utf8, "test").unwrap();
492        assert_eq!(v, DataType::Int32);
493
494        let v = utf8_to_int_type(&DataType::Utf8View, "test").unwrap();
495        assert_eq!(v, DataType::Int32);
496
497        let v = utf8_to_int_type(&DataType::LargeUtf8, "test").unwrap();
498        assert_eq!(v, DataType::Int64);
499    }
500
501    #[test]
502    fn test_decimal128_to_i128() {
503        let cases = [
504            (123, 0, Some(123)),
505            (1230, 1, Some(123)),
506            (123000, 3, Some(123)),
507            (1, 0, Some(1)),
508            (123, -3, None),
509            (123, i8::MAX, None),
510            (i128::MAX, 0, Some(i128::MAX)),
511            (i128::MAX, 3, Some(i128::MAX / 1000)),
512        ];
513
514        for (value, scale, expected) in cases {
515            match decimal128_to_i128(value, scale) {
516                Ok(actual) => {
517                    assert_eq!(
518                        actual,
519                        expected.expect("Got value but expected none"),
520                        "{value} and {scale} vs {expected:?}"
521                    );
522                }
523                Err(_) => assert!(expected.is_none()),
524            }
525        }
526    }
527
528    #[test]
529    fn test_decimal32_to_i32() {
530        let cases: [(i32, i8, Either<i32, String>); _] = [
531            (123, 0, Either::Left(123)),
532            (1230, 1, Either::Left(123)),
533            (123000, 3, Either::Left(123)),
534            (1234567, 2, Either::Left(12345)),
535            (-1234567, 2, Either::Left(-12345)),
536            (1, 0, Either::Left(1)),
537            (
538                123,
539                -3,
540                Either::Right("Negative scale is not supported".into()),
541            ),
542            (
543                123,
544                i8::MAX,
545                Either::Right("Cannot get a power of 127".into()),
546            ),
547            (999999999, 0, Either::Left(999999999)),
548            (999999999, 3, Either::Left(999999)),
549        ];
550
551        for (value, scale, expected) in cases {
552            match decimal32_to_i32(value, scale) {
553                Ok(actual) => {
554                    let expected_value =
555                        expected.left().expect("Got value but expected none");
556                    assert_eq!(
557                        actual, expected_value,
558                        "{value} and {scale} vs {expected_value:?}"
559                    );
560                }
561                Err(ArrowError::ComputeError(msg)) => {
562                    assert_eq!(
563                        msg,
564                        expected.right().expect("Got error but expected value")
565                    );
566                }
567                Err(_) => {
568                    assert!(expected.is_right())
569                }
570            }
571        }
572    }
573
574    #[test]
575    fn test_decimal64_to_i64() {
576        let cases: [(i64, i8, Either<i64, String>); _] = [
577            (123, 0, Either::Left(123)),
578            (1234567890, 2, Either::Left(12345678)),
579            (-1234567890, 2, Either::Left(-12345678)),
580            (
581                123,
582                -3,
583                Either::Right("Negative scale is not supported".into()),
584            ),
585            (
586                123,
587                i8::MAX,
588                Either::Right("Cannot get a power of 127".into()),
589            ),
590            (
591                999999999999999999i64,
592                0,
593                Either::Left(999999999999999999i64),
594            ),
595            (
596                999999999999999999i64,
597                3,
598                Either::Left(999999999999999999i64 / 1000),
599            ),
600            (
601                -999999999999999999i64,
602                3,
603                Either::Left(-999999999999999999i64 / 1000),
604            ),
605        ];
606
607        for (value, scale, expected) in cases {
608            match decimal64_to_i64(value, scale) {
609                Ok(actual) => {
610                    let expected_value =
611                        expected.left().expect("Got value but expected none");
612                    assert_eq!(
613                        actual, expected_value,
614                        "{value} and {scale} vs {expected_value:?}"
615                    );
616                }
617                Err(ArrowError::ComputeError(msg)) => {
618                    assert_eq!(
619                        msg,
620                        expected.right().expect("Got error but expected value")
621                    );
622                }
623                Err(_) => {
624                    assert!(expected.is_right())
625                }
626            }
627        }
628    }
629}