## 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/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};
    20
    use datafusion_common::{exec_err, internal_err, Result};
    21
    use datafusion_expr::{ColumnarValue, Documentation, ScalarFunctionArgs};
    22
    use datafusion_expr::{ScalarUDFImpl, Signature, Volatility};
    23
    use datafusion_macros::user_doc;
    24
    use std::any::Any;
    25
    use std::sync::Arc;
    26
    27
    #[user_doc(
    28
        doc_section(label = "Struct Functions"),
    29
        description = "Returns an Arrow struct using the specified input expressions optionally named.
    30
    Fields in the returned struct use the optional name or the `cN` naming convention.
    31
    For example: `c0`, `c1`, `c2`, etc.",
    32
        syntax_example = "struct(expression1[, ..., expression_n])",
    33
        sql_example = r#"For example, this query converts two columns `a` and `b` to a single column with
    34
    a struct type of fields `field_a` and `c1`:
    35
    ```sql
    36
    > select * from t;
    37
    +---+---+
    38
    | a | b |
    39
    +---+---+
    40
    | 1 | 2 |
    41
    | 3 | 4 |
    42
    +---+---+
    43
    44
    -- use default names `c0`, `c1`
    45
    > select struct(a, b) from t;
    46
    +-----------------+
    47
    | struct(t.a,t.b) |
    48
    +-----------------+
    49
    | {c0: 1, c1: 2}  |
    50
    | {c0: 3, c1: 4}  |
    51
    +-----------------+
    52
    53
    -- name the first field `field_a`
    54
    select struct(a as field_a, b) from t;
    55
    +--------------------------------------------------+
    56
    | named_struct(Utf8("field_a"),t.a,Utf8("c1"),t.b) |
    57
    +--------------------------------------------------+
    58
    | {field_a: 1, c1: 2}                              |
    59
    | {field_a: 3, c1: 4}                              |
    60
    +--------------------------------------------------+
    61
    ```"#,
    62
        argument(
    63
            name = "expression1, expression_n",
    64
            description = "Expression to include in the output struct. Can be a constant, column, or function, any combination of arithmetic or string operators."
    65
        )
    66
    )]
    67
    #[derive(Debug, PartialEq, Eq, Hash)]
    68
    pub struct StructFunc {
    69
        signature: Signature,
    70
        aliases: Vec<String>,
    71
    }
    72
    73
    impl Default for StructFunc {
    74
    0
        fn default() -> Self {
    75
    0
            Self::new()
    76
    0
        }
    77
    }
    78
    79
    impl StructFunc {
    80
    0
        pub fn new() -> Self {
    81
    0
            Self {
    82
    0
                signature: Signature::variadic_any(Volatility::Immutable),
    83
    0
                aliases: vec![String::from("row")],
    84
    0
            }
    85
    0
        }
    86
    }
    87
    88
    impl ScalarUDFImpl for StructFunc {
    89
    0
        fn as_any(&self) -> &dyn Any {
    90
    0
            self
    91
    0
        }
    92
    0
        fn name(&self) -> &str {
    93
    0
            "struct"
    94
    0
        }
    95
    96
    0
        fn aliases(&self) -> &[String] {
    97
    0
            &self.aliases
    98
    0
        }
    99
    100
    0
        fn signature(&self) -> &Signature {
    101
    0
            &self.signature
    102
    0
        }
    103
    104
    0
        fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
    105
    0
            if arg_types.is_empty() {
    106
    0
                return exec_err!("struct requires at least one argument, got 0 instead");
    107
    0
            }
    108
    109
    0
            let fields = arg_types
    110
    0
                .iter()
    111
    0
                .enumerate()
    112
    0
                .map(|(pos, dt)| Field::new(format!("c{pos}"), dt.clone(), true))
    113
    0
                .collect::<Vec<Field>>()
    114
    0
                .into();
    115
    116
    0
            Ok(DataType::Struct(fields))
    117
    0
        }
    118
    119
    0
        fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
    120
    0
            let DataType::Struct(fields) = args.return_type() else {
    121
    0
                return internal_err!("incorrect struct return type");
    122
            };
    123
    124
    0
            assert_eq!(
    125
    0
                fields.len(),
    126
    0
                args.args.len(),
    127
    0
                "return type field count != argument count"
    128
            );
    129
    130
    0
            let arrays = ColumnarValue::values_to_arrays(&args.args)?;
    131
    0
            Ok(ColumnarValue::Array(Arc::new(StructArray::new(
    132
    0
                fields.clone(),
    133
    0
                arrays,
    134
    0
                None,
    135
    0
            ))))
    136
    0
        }
    137
    138
    0
        fn documentation(&self) -> Option<&Documentation> {
    139
    0
            self.doc()
    140
    0
        }
    141
    }