## 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/unicode/initcap.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::{
    22
        Array, ArrayRef, GenericStringBuilder, OffsetSizeTrait, StringViewBuilder,
    23
    };
    24
    use arrow::datatypes::DataType;
    25
    26
    use crate::utils::{make_scalar_function, utf8_to_str_type};
    27
    use datafusion_common::cast::{as_generic_string_array, as_string_view_array};
    28
    use datafusion_common::types::logical_string;
    29
    use datafusion_common::{exec_err, Result};
    30
    use datafusion_expr::{
    31
        Coercion, ColumnarValue, Documentation, ScalarUDFImpl, Signature, TypeSignatureClass,
    32
        Volatility,
    33
    };
    34
    use datafusion_macros::user_doc;
    35
    36
    #[user_doc(
    37
        doc_section(label = "String Functions"),
    38
        description = "Capitalizes the first character in each word in the input string. \
    39
                Words are delimited by non-alphanumeric characters.",
    40
        syntax_example = "initcap(str)",
    41
        sql_example = r#"```sql
    42
    > select initcap('apache datafusion');
    43
    +------------------------------------+
    44
    | initcap(Utf8("apache datafusion")) |
    45
    +------------------------------------+
    46
    | Apache Datafusion                  |
    47
    +------------------------------------+
    48
    ```"#,
    49
        standard_argument(name = "str", prefix = "String"),
    50
        related_udf(name = "lower"),
    51
        related_udf(name = "upper")
    52
    )]
    53
    #[derive(Debug, PartialEq, Eq, Hash)]
    54
    pub struct InitcapFunc {
    55
        signature: Signature,
    56
    }
    57
    58
    impl Default for InitcapFunc {
    59
    0
        fn default() -> Self {
    60
    0
            InitcapFunc::new()
    61
    0
        }
    62
    }
    63
    64
    impl InitcapFunc {
    65
    0
        pub fn new() -> Self {
    66
    0
            Self {
    67
    0
                signature: Signature::coercible(
    68
    0
                    vec![Coercion::new_exact(TypeSignatureClass::Native(
    69
    0
                        logical_string(),
    70
    0
                    ))],
    71
    0
                    Volatility::Immutable,
    72
    0
                ),
    73
    0
            }
    74
    0
        }
    75
    }
    76
    77
    impl ScalarUDFImpl for InitcapFunc {
    78
    0
        fn as_any(&self) -> &dyn Any {
    79
    0
            self
    80
    0
        }
    81
    82
    0
        fn name(&self) -> &str {
    83
    0
            "initcap"
    84
    0
        }
    85
    86
    0
        fn signature(&self) -> &Signature {
    87
    0
            &self.signature
    88
    0
        }
    89
    90
    0
        fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
    91
    0
            if let DataType::Utf8View = arg_types[0] {
    92
    0
                Ok(DataType::Utf8View)
    93
            } else {
    94
    0
                utf8_to_str_type(&arg_types[0], "initcap")
    95
            }
    96
    0
        }
    97
    98
    0
        fn invoke_with_args(
    99
    0
            &self,
    100
    0
            args: datafusion_expr::ScalarFunctionArgs,
    101
    0
        ) -> Result<ColumnarValue> {
    102
    0
            let args = &args.args;
    103
    0
            match args[0].data_type() {
    104
    0
                DataType::Utf8 => make_scalar_function(initcap::<i32>, vec![])(args),
    105
    0
                DataType::LargeUtf8 => make_scalar_function(initcap::<i64>, vec![])(args),
    106
    0
                DataType::Utf8View => make_scalar_function(initcap_utf8view, vec![])(args),
    107
    0
                other => {
    108
    0
                    exec_err!("Unsupported data type {other:?} for function `initcap`")
    109
                }
    110
            }
    111
    0
        }
    112
    113
    0
        fn documentation(&self) -> Option<&Documentation> {
    114
    0
            self.doc()
    115
    0
        }
    116
    }
    117
    118
    /// Converts the first letter of each word to upper case and the rest to lower
    119
    /// case. Words are sequences of alphanumeric characters separated by
    120
    /// non-alphanumeric characters.
    121
    ///
    122
    /// Example:
    123
    /// ```sql
    124
    /// initcap('hi THOMAS') = 'Hi Thomas'
    125
    /// ```
    126
    0
    fn initcap<T: OffsetSizeTrait>(args: &[ArrayRef]) -> Result<ArrayRef> {
    127
    0
        let string_array = as_generic_string_array::<T>(&args[0])?;
    128
    129
    0
        let mut builder = GenericStringBuilder::<T>::with_capacity(
    130
    0
            string_array.len(),
    131
    0
            string_array.value_data().len(),
    132
        );
    133
    134
    0
        let mut container = String::new();
    135
    0
        string_array.iter().for_each(|str| match str {
    136
    0
            Some(s) => {
    137
    0
                initcap_string(s, &mut container);
    138
    0
                builder.append_value(&container);
    139
    0
            }
    140
    0
            None => builder.append_null(),
    141
    0
        });
    142
    143
    0
        Ok(Arc::new(builder.finish()) as ArrayRef)
    144
    0
    }
    145
    146
    0
    fn initcap_utf8view(args: &[ArrayRef]) -> Result<ArrayRef> {
    147
    0
        let string_view_array = as_string_view_array(&args[0])?;
    148
    149
    0
        let mut builder = StringViewBuilder::with_capacity(string_view_array.len());
    150
    151
    0
        let mut container = String::new();
    152
    0
        string_view_array.iter().for_each(|str| match str {
    153
    0
            Some(s) => {
    154
    0
                initcap_string(s, &mut container);
    155
    0
                builder.append_value(&container);
    156
    0
            }
    157
    0
            None => builder.append_null(),
    158
    0
        });
    159
    160
    0
        Ok(Arc::new(builder.finish()) as ArrayRef)
    161
    0
    }
    162
    163
    0
    fn initcap_string(input: &str, container: &mut String) {
    164
    0
        container.clear();
    165
    0
        let mut prev_is_alphanumeric = false;
    166
    167
    0
        if input.is_ascii() {
    168
    0
            for c in input.chars() {
    169
    0
                if prev_is_alphanumeric {
    170
    0
                    container.push(c.to_ascii_lowercase());
    171
    0
                } else {
    172
    0
                    container.push(c.to_ascii_uppercase());
    173
    0
                };
    174
    0
                prev_is_alphanumeric = c.is_ascii_alphanumeric();
    175
            }
    176
        } else {
    177
    0
            for c in input.chars() {
    178
    0
                if prev_is_alphanumeric {
    179
    0
                    container.extend(c.to_lowercase());
    180
    0
                } else {
    181
    0
                    container.extend(c.to_uppercase());
    182
    0
                }
    183
    0
                prev_is_alphanumeric = c.is_alphanumeric();
    184
            }
    185
        }
    186
    0
    }
    187
    188
    #[cfg(test)]
    189
    mod tests {
    190
        use crate::unicode::initcap::InitcapFunc;
    191
        use crate::utils::test::test_function;
    192
        use arrow::array::{Array, StringArray, StringViewArray};
    193
        use arrow::datatypes::DataType::{Utf8, Utf8View};
    194
        use datafusion_common::{Result, ScalarValue};
    195
        use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
    196
    197
        #[test]
    198
        fn test_functions() -> Result<()> {
    199
            test_function!(
    200
                InitcapFunc::new(),
    201
                vec![ColumnarValue::Scalar(ScalarValue::from("hi THOMAS"))],
    202
                Ok(Some("Hi Thomas")),
    203
                &str,
    204
                Utf8,
    205
                StringArray
    206
            );
    207
            test_function!(
    208
                InitcapFunc::new(),
    209
                vec![ColumnarValue::Scalar(ScalarValue::Utf8(Some(
    210
                    "êM ả ñAnDÚ ÁrBOL ОлЕГ ИвАНОВИч ÍslENsku ÞjóðaRiNNaR εΛλΗΝΙκΉ"
    211
                        .to_string()
    212
                )))],
    213
                Ok(Some(
    214
                    "Êm Ả Ñandú Árbol Олег Иванович Íslensku Þjóðarinnar Ελληνική"
    215
                )),
    216
                &str,
    217
                Utf8,
    218
                StringArray
    219
            );
    220
            test_function!(
    221
                InitcapFunc::new(),
    222
                vec![ColumnarValue::Scalar(ScalarValue::from(""))],
    223
                Ok(Some("")),
    224
                &str,
    225
                Utf8,
    226
                StringArray
    227
            );
    228
            test_function!(
    229
                InitcapFunc::new(),
    230
                vec![ColumnarValue::Scalar(ScalarValue::from(""))],
    231
                Ok(Some("")),
    232
                &str,
    233
                Utf8,
    234
                StringArray
    235
            );
    236
            test_function!(
    237
                InitcapFunc::new(),
    238
                vec![ColumnarValue::Scalar(ScalarValue::Utf8(None))],
    239
                Ok(None),
    240
                &str,
    241
                Utf8,
    242
                StringArray
    243
            );
    244
    245
            test_function!(
    246
                InitcapFunc::new(),
    247
                vec![ColumnarValue::Scalar(ScalarValue::Utf8View(Some(
    248
                    "hi THOMAS".to_string()
    249
                )))],
    250
                Ok(Some("Hi Thomas")),
    251
                &str,
    252
                Utf8View,
    253
                StringViewArray
    254
            );
    255
            test_function!(
    256
                InitcapFunc::new(),
    257
                vec![ColumnarValue::Scalar(ScalarValue::Utf8View(Some(
    258
                    "hi THOMAS wIth M0re ThAN 12 ChaRs".to_string()
    259
                )))],
    260
                Ok(Some("Hi Thomas With M0re Than 12 Chars")),
    261
                &str,
    262
                Utf8View,
    263
                StringViewArray
    264
            );
    265
            test_function!(
    266
                InitcapFunc::new(),
    267
                vec![ColumnarValue::Scalar(ScalarValue::Utf8View(Some(
    268
                    "đẸp đẼ êM ả ñAnDÚ ÁrBOL ОлЕГ ИвАНОВИч ÍslENsku ÞjóðaRiNNaR εΛλΗΝΙκΉ"
    269
                        .to_string()
    270
                )))],
    271
                Ok(Some(
    272
                    "Đẹp Đẽ Êm Ả Ñandú Árbol Олег Иванович Íslensku Þjóðarinnar Ελληνική"
    273
                )),
    274
                &str,
    275
                Utf8View,
    276
                StringViewArray
    277
            );
    278
            test_function!(
    279
                InitcapFunc::new(),
    280
                vec![ColumnarValue::Scalar(ScalarValue::Utf8View(Some(
    281
                    "".to_string()
    282
                )))],
    283
                Ok(Some("")),
    284
                &str,
    285
                Utf8View,
    286
                StringViewArray
    287
            );
    288
            test_function!(
    289
                InitcapFunc::new(),
    290
                vec![ColumnarValue::Scalar(ScalarValue::Utf8View(None))],
    291
                Ok(None),
    292
                &str,
    293
                Utf8View,
    294
                StringViewArray
    295
            );
    296
    297
            Ok(())
    298
        }
    299
    }