## 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/core/greatest_least_utils.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 arrow::array::{Array, ArrayRef, BooleanArray};
    19
    use arrow::compute::kernels::zip::zip;
    20
    use arrow::datatypes::DataType;
    21
    use datafusion_common::{
    22
        assert_or_internal_err, plan_err, DataFusionError, Result, ScalarValue,
    23
    };
    24
    use datafusion_expr_common::columnar_value::ColumnarValue;
    25
    use datafusion_expr_common::type_coercion::binary::type_union_resolution;
    26
    use std::sync::Arc;
    27
    28
    pub(super) trait GreatestLeastOperator {
    29
        const NAME: &'static str;
    30
    31
        fn keep_scalar<'a>(
    32
            lhs: &'a ScalarValue,
    33
            rhs: &'a ScalarValue,
    34
        ) -> Result<&'a ScalarValue>;
    35
    36
        /// Return array with true for values that we should keep from the lhs array
    37
        fn get_indexes_to_keep(lhs: &dyn Array, rhs: &dyn Array) -> Result<BooleanArray>;
    38
    }
    39
    40
    0
    fn keep_array<Op: GreatestLeastOperator>(
    41
    0
        lhs: ArrayRef,
    42
    0
        rhs: ArrayRef,
    43
    0
    ) -> Result<ArrayRef> {
    44
        // True for values that we should keep from the left array
    45
    0
        let keep_lhs = Op::get_indexes_to_keep(lhs.as_ref(), rhs.as_ref())?;
    46
    47
    0
        let result = zip(&keep_lhs, &lhs, &rhs)?;
    48
    49
    0
        Ok(result)
    50
    0
    }
    51
    52
    0
    pub(super) fn execute_conditional<Op: GreatestLeastOperator>(
    53
    0
        args: &[ColumnarValue],
    54
    0
    ) -> Result<ColumnarValue> {
    55
    0
        assert_or_internal_err!(
    56
    0
            !args.is_empty(),
    57
    0
            "{} was called with no arguments. It requires at least 1.",
    58
            Op::NAME
    59
        );
    60
    61
        // Some engines (e.g. SQL Server) allow greatest/least with single arg, it's a noop
    62
    0
        if args.len() == 1 {
    63
    0
            return Ok(args[0].clone());
    64
    0
        }
    65
    66
        // Split to scalars and arrays for later optimization
    67
    0
        let (scalars, arrays): (Vec<_>, Vec<_>) = args.iter().partition(|x| match x {
    68
    0
            ColumnarValue::Scalar(_) => true,
    69
    0
            ColumnarValue::Array(_) => false,
    70
    0
        });
    71
    72
    0
        let mut arrays_iter = arrays.iter().map(|x| match x {
    73
    0
            ColumnarValue::Array(a) => a,
    74
    0
            _ => unreachable!(),
    75
    0
        });
    76
    77
    0
        let first_array = arrays_iter.next();
    78
    79
        let mut result: ArrayRef;
    80
    81
        // Optimization: merge all scalars into one to avoid recomputing (constant folding)
    82
    0
        if !scalars.is_empty() {
    83
    0
            let mut scalars_iter = scalars.iter().map(|x| match x {
    84
    0
                ColumnarValue::Scalar(s) => s,
    85
    0
                _ => unreachable!(),
    86
    0
            });
    87
    88
            // We have at least one scalar
    89
    0
            let mut result_scalar = scalars_iter.next().unwrap();
    90
    91
    0
            for scalar in scalars_iter {
    92
    0
                result_scalar = Op::keep_scalar(result_scalar, scalar)?;
    93
            }
    94
    95
            // If we only have scalars, return the one that we should keep (largest/least)
    96
    0
            if arrays.is_empty() {
    97
    0
                return Ok(ColumnarValue::Scalar(result_scalar.clone()));
    98
    0
            }
    99
    100
            // We have at least one array
    101
    0
            let first_array = first_array.unwrap();
    102
    103
            // Start with the result value
    104
    0
            result = keep_array::<Op>(
    105
    0
                Arc::clone(first_array),
    106
    0
                result_scalar.to_array_of_size(first_array.len())?,
    107
    0
            )?;
    108
    0
        } else {
    109
    0
            // If we only have arrays, start with the first array
    110
    0
            // (We must have at least one array)
    111
    0
            result = Arc::clone(first_array.unwrap());
    112
    0
        }
    113
    114
    0
        for array in arrays_iter {
    115
    0
            result = keep_array::<Op>(Arc::clone(array), result)?;
    116
        }
    117
    118
    0
        Ok(ColumnarValue::Array(result))
    119
    0
    }
    120
    121
    0
    pub(super) fn find_coerced_type<Op: GreatestLeastOperator>(
    122
    0
        data_types: &[DataType],
    123
    0
    ) -> Result<DataType> {
    124
    0
        if data_types.is_empty() {
    125
    0
            plan_err!(
    126
    0
                "{} was called without any arguments. It requires at least 1.",
    127
                Op::NAME
    128
            )
    129
    0
        } else if let Some(coerced_type) = type_union_resolution(data_types) {
    130
    0
            Ok(coerced_type)
    131
        } else {
    132
    0
            plan_err!("Cannot find a common type for arguments")
    133
        }
    134
    0
    }