## 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/string/replace.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, GenericStringArray, OffsetSizeTrait, StringArray};
    22
    use arrow::datatypes::DataType;
    23
    24
    use crate::utils::{make_scalar_function, utf8_to_str_type};
    25
    use datafusion_common::cast::{as_generic_string_array, as_string_view_array};
    26
    use datafusion_common::types::logical_string;
    27
    use datafusion_common::{exec_err, Result};
    28
    use datafusion_expr::type_coercion::binary::{
    29
        binary_to_string_coercion, string_coercion,
    30
    };
    31
    use datafusion_expr::{
    32
        Coercion, ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
    33
        TypeSignatureClass, Volatility,
    34
    };
    35
    use datafusion_macros::user_doc;
    36
    #[user_doc(
    37
        doc_section(label = "String Functions"),
    38
        description = "Replaces all occurrences of a specified substring in a string with a new substring.",
    39
        syntax_example = "replace(str, substr, replacement)",
    40
        sql_example = r#"```sql
    41
    > select replace('ABabbaBA', 'ab', 'cd');
    42
    +-------------------------------------------------+
    43
    | replace(Utf8("ABabbaBA"),Utf8("ab"),Utf8("cd")) |
    44
    +-------------------------------------------------+
    45
    | ABcdbaBA                                        |
    46
    +-------------------------------------------------+
    47
    ```"#,
    48
        standard_argument(name = "str", prefix = "String"),
    49
        standard_argument(
    50
            name = "substr",
    51
            prefix = "Substring expression to replace in the input string. Substring"
    52
        ),
    53
        standard_argument(name = "replacement", prefix = "Replacement substring")
    54
    )]
    55
    #[derive(Debug, PartialEq, Eq, Hash)]
    56
    pub struct ReplaceFunc {
    57
        signature: Signature,
    58
    }
    59
    60
    impl Default for ReplaceFunc {
    61
    0
        fn default() -> Self {
    62
    0
            Self::new()
    63
    0
        }
    64
    }
    65
    66
    impl ReplaceFunc {
    67
    0
        pub fn new() -> Self {
    68
    0
            Self {
    69
    0
                signature: Signature::coercible(
    70
    0
                    vec![
    71
    0
                        Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
    72
    0
                        Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
    73
    0
                        Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
    74
    0
                    ],
    75
    0
                    Volatility::Immutable,
    76
    0
                ),
    77
    0
            }
    78
    0
        }
    79
    }
    80
    81
    impl ScalarUDFImpl for ReplaceFunc {
    82
    0
        fn as_any(&self) -> &dyn Any {
    83
    0
            self
    84
    0
        }
    85
    86
    0
        fn name(&self) -> &str {
    87
    0
            "replace"
    88
    0
        }
    89
    90
    0
        fn signature(&self) -> &Signature {
    91
    0
            &self.signature
    92
    0
        }
    93
    94
    0
        fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
    95
    0
            if let Some(coercion_data_type) = string_coercion(&arg_types[0], &arg_types[1])
    96
    0
                .and_then(|dt| string_coercion(&dt, &arg_types[2]))
    97
    0
                .or_else(|| {
    98
    0
                    binary_to_string_coercion(&arg_types[0], &arg_types[1])
    99
    0
                        .and_then(|dt| binary_to_string_coercion(&dt, &arg_types[2]))
    100
    0
                })
    101
            {
    102
    0
                utf8_to_str_type(&coercion_data_type, "replace")
    103
            } else {
    104
    0
                exec_err!("Unsupported data types for replace. Expected Utf8, LargeUtf8 or Utf8View")
    105
            }
    106
    0
        }
    107
    108
    0
        fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
    109
    0
            let data_types = args
    110
    0
                .args
    111
    0
                .iter()
    112
    0
                .map(|arg| arg.data_type())
    113
    0
                .collect::<Vec<_>>();
    114
    115
    0
            if let Some(coercion_type) = string_coercion(&data_types[0], &data_types[1])
    116
    0
                .and_then(|dt| string_coercion(&dt, &data_types[2]))
    117
    0
                .or_else(|| {
    118
    0
                    binary_to_string_coercion(&data_types[0], &data_types[1])
    119
    0
                        .and_then(|dt| binary_to_string_coercion(&dt, &data_types[2]))
    120
    0
                })
    121
            {
    122
    0
                let mut converted_args = Vec::with_capacity(args.args.len());
    123
    0
                for arg in &args.args {
    124
    0
                    if arg.data_type() == coercion_type {
    125
    0
                        converted_args.push(arg.clone());
    126
    0
                    } else {
    127
    0
                        let converted = arg.cast_to(&coercion_type, None)?;
    128
    0
                        converted_args.push(converted);
    129
                    }
    130
                }
    131
    132
    0
                match coercion_type {
    133
                    DataType::Utf8 => {
    134
    0
                        make_scalar_function(replace::<i32>, vec![])(&converted_args)
    135
                    }
    136
                    DataType::LargeUtf8 => {
    137
    0
                        make_scalar_function(replace::<i64>, vec![])(&converted_args)
    138
                    }
    139
                    DataType::Utf8View => {
    140
    0
                        make_scalar_function(replace_view, vec![])(&converted_args)
    141
                    }
    142
    0
                    other => exec_err!(
    143
    0
                        "Unsupported coercion data type {other:?} for function replace"
    144
                    ),
    145
                }
    146
            } else {
    147
    0
                exec_err!(
    148
    0
                    "Unsupported data type {}, {:?}, {:?} for function replace.",
    149
    0
                    data_types[0],
    150
    0
                    data_types[1],
    151
    0
                    data_types[2]
    152
                )
    153
            }
    154
    0
        }
    155
    156
    0
        fn documentation(&self) -> Option<&Documentation> {
    157
    0
            self.doc()
    158
    0
        }
    159
    }
    160
    161
    0
    fn replace_view(args: &[ArrayRef]) -> Result<ArrayRef> {
    162
    0
        let string_array = as_string_view_array(&args[0])?;
    163
    0
        let from_array = as_string_view_array(&args[1])?;
    164
    0
        let to_array = as_string_view_array(&args[2])?;
    165
    166
    0
        let result = string_array
    167
    0
            .iter()
    168
    0
            .zip(from_array.iter())
    169
    0
            .zip(to_array.iter())
    170
    0
            .map(|((string, from), to)| match (string, from, to) {
    171
    0
                (Some(string), Some(from), Some(to)) => Some(string.replace(from, to)),
    172
    0
                _ => None,
    173
    0
            })
    174
    0
            .collect::<StringArray>();
    175
    176
    0
        Ok(Arc::new(result) as ArrayRef)
    177
    0
    }
    178
    179
    /// Replaces all occurrences in string of substring from with substring to.
    180
    /// replace('abcdefabcdef', 'cd', 'XX') = 'abXXefabXXef'
    181
    0
    fn replace<T: OffsetSizeTrait>(args: &[ArrayRef]) -> Result<ArrayRef> {
    182
    0
        let string_array = as_generic_string_array::<T>(&args[0])?;
    183
    0
        let from_array = as_generic_string_array::<T>(&args[1])?;
    184
    0
        let to_array = as_generic_string_array::<T>(&args[2])?;
    185
    186
    0
        let result = string_array
    187
    0
            .iter()
    188
    0
            .zip(from_array.iter())
    189
    0
            .zip(to_array.iter())
    190
    0
            .map(|((string, from), to)| match (string, from, to) {
    191
    0
                (Some(string), Some(from), Some(to)) => Some(string.replace(from, to)),
    192
    0
                _ => None,
    193
    0
            })
    194
    0
            .collect::<GenericStringArray<T>>();
    195
    196
    0
        Ok(Arc::new(result) as ArrayRef)
    197
    0
    }
    198
    199
    #[cfg(test)]
    200
    mod tests {
    201
        use super::*;
    202
        use crate::utils::test::test_function;
    203
        use arrow::array::Array;
    204
        use arrow::array::LargeStringArray;
    205
        use arrow::array::StringArray;
    206
        use arrow::datatypes::DataType::{LargeUtf8, Utf8};
    207
        use datafusion_common::ScalarValue;
    208
        #[test]
    209
        fn test_functions() -> Result<()> {
    210
            test_function!(
    211
                ReplaceFunc::new(),
    212
                vec![
    213
                    ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("aabbdqcbb")))),
    214
                    ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("bb")))),
    215
                    ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("ccc")))),
    216
                ],
    217
                Ok(Some("aacccdqcccc")),
    218
                &str,
    219
                Utf8,
    220
                StringArray
    221
            );
    222
    223
            test_function!(
    224
                ReplaceFunc::new(),
    225
                vec![
    226
                    ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(String::from(
    227
                        "aabbb"
    228
                    )))),
    229
                    ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(String::from("bbb")))),
    230
                    ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(String::from("cc")))),
    231
                ],
    232
                Ok(Some("aacc")),
    233
                &str,
    234
                LargeUtf8,
    235
                LargeStringArray
    236
            );
    237
    238
            test_function!(
    239
                ReplaceFunc::new(),
    240
                vec![
    241
                    ColumnarValue::Scalar(ScalarValue::Utf8View(Some(String::from(
    242
                        "aabbbcw"
    243
                    )))),
    244
                    ColumnarValue::Scalar(ScalarValue::Utf8View(Some(String::from("bb")))),
    245
                    ColumnarValue::Scalar(ScalarValue::Utf8View(Some(String::from("cc")))),
    246
                ],
    247
                Ok(Some("aaccbcw")),
    248
                &str,
    249
                Utf8,
    250
                StringArray
    251
            );
    252
    253
            Ok(())
    254
        }
    255
    }