## 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/named_struct.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::StructArray;
    19
    use arrow::datatypes::{DataType, Field, FieldRef, Fields};
    20
    use datafusion_common::{exec_err, internal_err, Result};
    21
    use datafusion_expr::{
    22
        ColumnarValue, Documentation, ReturnFieldArgs, ScalarFunctionArgs,
    23
    };
    24
    use datafusion_expr::{ScalarUDFImpl, Signature, Volatility};
    25
    use datafusion_macros::user_doc;
    26
    use std::any::Any;
    27
    use std::sync::Arc;
    28
    29
    #[user_doc(
    30
        doc_section(label = "Struct Functions"),
    31
        description = "Returns an Arrow struct using the specified name and input expressions pairs.",
    32
        syntax_example = "named_struct(expression1_name, expression1_input[, ..., expression_n_name, expression_n_input])",
    33
        sql_example = r#"
    34
    For example, this query converts two columns `a` and `b` to a single column with
    35
    a struct type of fields `field_a` and `field_b`:
    36
    ```sql
    37
    > select * from t;
    38
    +---+---+
    39
    | a | b |
    40
    +---+---+
    41
    | 1 | 2 |
    42
    | 3 | 4 |
    43
    +---+---+
    44
    > select named_struct('field_a', a, 'field_b', b) from t;
    45
    +-------------------------------------------------------+
    46
    | named_struct(Utf8("field_a"),t.a,Utf8("field_b"),t.b) |
    47
    +-------------------------------------------------------+
    48
    | {field_a: 1, field_b: 2}                              |
    49
    | {field_a: 3, field_b: 4}                              |
    50
    +-------------------------------------------------------+
    51
    ```"#,
    52
        argument(
    53
            name = "expression_n_name",
    54
            description = "Name of the column field. Must be a constant string."
    55
        ),
    56
        argument(
    57
            name = "expression_n_input",
    58
            description = "Expression to include in the output struct. Can be a constant, column, or function, and any combination of arithmetic or string operators."
    59
        )
    60
    )]
    61
    #[derive(Debug, PartialEq, Eq, Hash)]
    62
    pub struct NamedStructFunc {
    63
        signature: Signature,
    64
    }
    65
    66
    impl Default for NamedStructFunc {
    67
    0
        fn default() -> Self {
    68
    0
            Self::new()
    69
    0
        }
    70
    }
    71
    72
    impl NamedStructFunc {
    73
    0
        pub fn new() -> Self {
    74
    0
            Self {
    75
    0
                signature: Signature::variadic_any(Volatility::Immutable),
    76
    0
            }
    77
    0
        }
    78
    }
    79
    80
    impl ScalarUDFImpl for NamedStructFunc {
    81
    0
        fn as_any(&self) -> &dyn Any {
    82
    0
            self
    83
    0
        }
    84
    85
    0
        fn name(&self) -> &str {
    86
    0
            "named_struct"
    87
    0
        }
    88
    89
    0
        fn signature(&self) -> &Signature {
    90
    0
            &self.signature
    91
    0
        }
    92
    93
    0
        fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
    94
    0
            internal_err!(
    95
    0
                "named_struct: return_type called instead of return_field_from_args"
    96
            )
    97
    0
        }
    98
    99
    0
        fn return_field_from_args(&self, args: ReturnFieldArgs) -> Result<FieldRef> {
    100
            // do not accept 0 arguments.
    101
    0
            if args.scalar_arguments.is_empty() {
    102
    0
                return exec_err!(
    103
    0
                    "named_struct requires at least one pair of arguments, got 0 instead"
    104
                );
    105
    0
            }
    106
    107
    0
            if !args.scalar_arguments.len().is_multiple_of(2) {
    108
    0
                return exec_err!(
    109
    0
                    "named_struct requires an even number of arguments, got {} instead",
    110
    0
                    args.scalar_arguments.len()
    111
                );
    112
    0
            }
    113
    114
    0
            let names = args
    115
    0
                .scalar_arguments
    116
    0
                .iter()
    117
    0
                .enumerate()
    118
    0
                .step_by(2)
    119
    0
                .map(|(i, sv)|
    120
    0
                    sv.and_then(|sv| sv.try_as_str().flatten().filter(|s| !s.is_empty()))
    121
    0
                    .map_or_else(
    122
                        ||
    123
    0
                            exec_err!(
    124
    0
                        "{} requires {i}-th (0-indexed) field name as non-empty constant string",
    125
    0
                        self.name()
    126
                    ),
    127
                    Ok
    128
                    )
    129
                )
    130
    0
                .collect::<Result<Vec<_>>>()?;
    131
    0
            let types = args
    132
    0
                .arg_fields
    133
    0
                .iter()
    134
    0
                .skip(1)
    135
    0
                .step_by(2)
    136
    0
                .map(|f| f.data_type())
    137
    0
                .collect::<Vec<_>>();
    138
    139
    0
            let return_fields = names
    140
    0
                .into_iter()
    141
    0
                .zip(types.into_iter())
    142
    0
                .map(|(name, data_type)| Ok(Field::new(name, data_type.to_owned(), true)))
    143
    0
                .collect::<Result<Vec<Field>>>()?;
    144
    145
    0
            Ok(Field::new(
    146
    0
                self.name(),
    147
    0
                DataType::Struct(Fields::from(return_fields)),
    148
    0
                true,
    149
    0
            )
    150
    0
            .into())
    151
    0
        }
    152
    153
    0
        fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
    154
    0
            let DataType::Struct(fields) = args.return_type() else {
    155
    0
                return internal_err!("incorrect named_struct return type");
    156
            };
    157
    158
    0
            assert_eq!(
    159
    0
                fields.len(),
    160
    0
                args.args.len() / 2,
    161
    0
                "return type field count != argument count / 2"
    162
            );
    163
    164
    0
            let values: Vec<ColumnarValue> = args
    165
    0
                .args
    166
    0
                .chunks_exact(2)
    167
    0
                .map(|chunk| chunk[1].clone())
    168
    0
                .collect();
    169
    0
            let arrays = ColumnarValue::values_to_arrays(&values)?;
    170
    0
            Ok(ColumnarValue::Array(Arc::new(StructArray::new(
    171
    0
                fields.clone(),
    172
    0
                arrays,
    173
    0
                None,
    174
    0
            ))))
    175
    0
        }
    176
    177
    0
        fn documentation(&self) -> Option<&Documentation> {
    178
    0
            self.doc()
    179
    0
        }
    180
    }