## Coverage Report

#### Created: 2025-11-22 06:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /Users/andrewlamb/Software/datafusion/datafusion/functions/src/math/lcm.rs

    Line
    Count
    Source
    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
    18
    use std::any::Any;
    19
    use std::sync::Arc;
    20
    21
    use arrow::array::{ArrayRef, Int64Array};
    22
    use arrow::datatypes::DataType;
    23
    use arrow::datatypes::DataType::Int64;
    24
    25
    use arrow::error::ArrowError;
    26
    use datafusion_common::{arrow_datafusion_err, exec_err, DataFusionError, Result};
    27
    use datafusion_expr::{
    28
        ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
    29
        Volatility,
    30
    };
    31
    use datafusion_macros::user_doc;
    32
    33
    use super::gcd::unsigned_gcd;
    34
    use crate::utils::make_scalar_function;
    35
    36
    #[user_doc(
    37
        doc_section(label = "Math Functions"),
    38
        description = "Returns the least common multiple of `expression_x` and `expression_y`. Returns 0 if either input is zero.",
    39
        syntax_example = "lcm(expression_x, expression_y)",
    40
        sql_example = r#"```sql
    41
    > SELECT lcm(4, 5);
    42
    +----------+
    43
    | lcm(4,5) |
    44
    +----------+
    45
    | 20       |
    46
    +----------+
    47
    ```"#,
    48
        standard_argument(name = "expression_x", prefix = "First numeric"),
    49
        standard_argument(name = "expression_y", prefix = "Second numeric")
    50
    )]
    51
    #[derive(Debug, PartialEq, Eq, Hash)]
    52
    pub struct LcmFunc {
    53
        signature: Signature,
    54
    }
    55
    56
    impl Default for LcmFunc {
    57
    0
        fn default() -> Self {
    58
    0
            LcmFunc::new()
    59
    0
        }
    60
    }
    61
    62
    impl LcmFunc {
    63
    0
        pub fn new() -> Self {
    64
            use DataType::*;
    65
    0
            Self {
    66
    0
                signature: Signature::uniform(2, vec![Int64], Volatility::Immutable),
    67
    0
            }
    68
    0
        }
    69
    }
    70
    71
    impl ScalarUDFImpl for LcmFunc {
    72
    0
        fn as_any(&self) -> &dyn Any {
    73
    0
            self
    74
    0
        }
    75
    76
    0
        fn name(&self) -> &str {
    77
    0
            "lcm"
    78
    0
        }
    79
    80
    0
        fn signature(&self) -> &Signature {
    81
    0
            &self.signature
    82
    0
        }
    83
    84
    0
        fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
    85
    0
            Ok(Int64)
    86
    0
        }
    87
    88
    0
        fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
    89
    0
            make_scalar_function(lcm, vec![])(&args.args)
    90
    0
        }
    91
    92
    0
        fn documentation(&self) -> Option<&Documentation> {
    93
    0
            self.doc()
    94
    0
        }
    95
    }
    96
    97
    /// Lcm SQL function
    98
    0
    fn lcm(args: &[ArrayRef]) -> Result<ArrayRef> {
    99
    0
        let compute_lcm = |x: i64, y: i64| {
    100
    0
            if x == 0 || y == 0 {
    101
    0
                return Ok(0);
    102
    0
            }
    103
    104
            // lcm(x, y) = |x| * |y| / gcd(|x|, |y|)
    105
    0
            let a = x.unsigned_abs();
    106
    0
            let b = y.unsigned_abs();
    107
    0
            let gcd = unsigned_gcd(a, b);
    108
            // gcd is not zero since both a and b are not zero, so the division is safe.
    109
    0
            (a / gcd)
    110
    0
                .checked_mul(b)
    111
    0
                .and_then(|v| i64::try_from(v).ok())
    112
    0
                .ok_or_else(|| {
    113
    0
                    arrow_datafusion_err!(ArrowError::ComputeError(format!(
    114
    0
                        "Signed integer overflow in LCM({x}, {y})"
    115
    0
                    )))
    116
    0
                })
    117
    0
        };
    118
    119
    0
        match args[0].data_type() {
    120
            Int64 => {
    121
    0
                let arg1 = downcast_named_arg!(&args[0], "x", Int64Array);
    122
    0
                let arg2 = downcast_named_arg!(&args[1], "y", Int64Array);
    123
    124
    0
                Ok(arg1
    125
    0
                    .iter()
    126
    0
                    .zip(arg2.iter())
    127
    0
                    .map(|(a1, a2)| match (a1, a2) {
    128
    0
                        (Some(a1), Some(a2)) => Ok(Some(compute_lcm(a1, a2)?)),
    129
    0
                        _ => Ok(None),
    130
    0
                    })
    131
    0
                    .collect::<Result<Int64Array>>()
    132
    0
                    .map(Arc::new)? as ArrayRef)
    133
            }
    134
    0
            other => exec_err!("Unsupported data type {other:?} for function lcm"),
    135
        }
    136
    0
    }
    137
    138
    #[cfg(test)]
    139
    mod test {
    140
        use std::sync::Arc;
    141
    142
        use arrow::array::{ArrayRef, Int64Array};
    143
    144
        use datafusion_common::cast::as_int64_array;
    145
    146
        use crate::math::lcm::lcm;
    147
    148
        #[test]
    149
        fn test_lcm_i64() {
    150
            let args: Vec<ArrayRef> = vec![
    151
                Arc::new(Int64Array::from(vec![0, 3, 25, -16])), // x
    152
                Arc::new(Int64Array::from(vec![0, -2, 15, 8])),  // y
    153
            ];
    154
    155
            let result = lcm(&args).expect("failed to initialize function lcm");
    156
            let ints = as_int64_array(&result).expect("failed to initialize function lcm");
    157
    158
            assert_eq!(ints.len(), 4);
    159
            assert_eq!(ints.value(0), 0);
    160
            assert_eq!(ints.value(1), 6);
    161
            assert_eq!(ints.value(2), 75);
    162
            assert_eq!(ints.value(3), 16);
    163
        }
    164
    }