## 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/contains.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 crate::utils::make_scalar_function;
    19
    use arrow::array::{Array, ArrayRef, AsArray};
    20
    use arrow::compute::contains as arrow_contains;
    21
    use arrow::datatypes::DataType;
    22
    use arrow::datatypes::DataType::{Boolean, LargeUtf8, Utf8, Utf8View};
    23
    use datafusion_common::types::logical_string;
    24
    use datafusion_common::{exec_err, DataFusionError, Result};
    25
    use datafusion_expr::binary::{binary_to_string_coercion, string_coercion};
    26
    use datafusion_expr::{
    27
        Coercion, ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
    28
        TypeSignatureClass, Volatility,
    29
    };
    30
    use datafusion_macros::user_doc;
    31
    use std::any::Any;
    32
    use std::sync::Arc;
    33
    34
    #[user_doc(
    35
        doc_section(label = "String Functions"),
    36
        description = "Return true if search_str is found within string (case-sensitive).",
    37
        syntax_example = "contains(str, search_str)",
    38
        sql_example = r#"```sql
    39
    > select contains('the quick brown fox', 'row');
    40
    +---------------------------------------------------+
    41
    | contains(Utf8("the quick brown fox"),Utf8("row")) |
    42
    +---------------------------------------------------+
    43
    | true                                              |
    44
    +---------------------------------------------------+
    45
    ```"#,
    46
        standard_argument(name = "str", prefix = "String"),
    47
        argument(name = "search_str", description = "The string to search for in str.")
    48
    )]
    49
    #[derive(Debug, PartialEq, Eq, Hash)]
    50
    pub struct ContainsFunc {
    51
        signature: Signature,
    52
    }
    53
    54
    impl Default for ContainsFunc {
    55
    0
        fn default() -> Self {
    56
    0
            ContainsFunc::new()
    57
    0
        }
    58
    }
    59
    60
    impl ContainsFunc {
    61
    0
        pub fn new() -> Self {
    62
    0
            Self {
    63
    0
                signature: Signature::coercible(
    64
    0
                    vec![
    65
    0
                        Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
    66
    0
                        Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
    67
    0
                    ],
    68
    0
                    Volatility::Immutable,
    69
    0
                ),
    70
    0
            }
    71
    0
        }
    72
    }
    73
    74
    impl ScalarUDFImpl for ContainsFunc {
    75
    0
        fn as_any(&self) -> &dyn Any {
    76
    0
            self
    77
    0
        }
    78
    79
    0
        fn name(&self) -> &str {
    80
    0
            "contains"
    81
    0
        }
    82
    83
    0
        fn signature(&self) -> &Signature {
    84
    0
            &self.signature
    85
    0
        }
    86
    87
    0
        fn return_type(&self, _: &[DataType]) -> Result<DataType> {
    88
    0
            Ok(Boolean)
    89
    0
        }
    90
    91
    0
        fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
    92
    0
            make_scalar_function(contains, vec![])(&args.args)
    93
    0
        }
    94
    95
    0
        fn documentation(&self) -> Option<&Documentation> {
    96
    0
            self.doc()
    97
    0
        }
    98
    }
    99
    100
    /// use `arrow::compute::contains` to do the calculation for contains
    101
    0
    fn contains(args: &[ArrayRef]) -> Result<ArrayRef, DataFusionError> {
    102
    0
        if let Some(coercion_data_type) =
    103
    0
            string_coercion(args[0].data_type(), args[1].data_type()).or_else(|| {
    104
    0
                binary_to_string_coercion(args[0].data_type(), args[1].data_type())
    105
    0
            })
    106
        {
    107
    0
            let arg0 = if args[0].data_type() == &coercion_data_type {
    108
    0
                Arc::clone(&args[0])
    109
            } else {
    110
    0
                arrow::compute::kernels::cast::cast(&args[0], &coercion_data_type)?
    111
            };
    112
    0
            let arg1 = if args[1].data_type() == &coercion_data_type {
    113
    0
                Arc::clone(&args[1])
    114
            } else {
    115
    0
                arrow::compute::kernels::cast::cast(&args[1], &coercion_data_type)?
    116
            };
    117
    118
    0
            match coercion_data_type {
    119
                Utf8View => {
    120
    0
                    let mod_str = arg0.as_string_view();
    121
    0
                    let match_str = arg1.as_string_view();
    122
    0
                    let res = arrow_contains(mod_str, match_str)?;
    123
    0
                    Ok(Arc::new(res) as ArrayRef)
    124
                }
    125
                Utf8 => {
    126
    0
                    let mod_str = arg0.as_string::<i32>();
    127
    0
                    let match_str = arg1.as_string::<i32>();
    128
    0
                    let res = arrow_contains(mod_str, match_str)?;
    129
    0
                    Ok(Arc::new(res) as ArrayRef)
    130
                }
    131
                LargeUtf8 => {
    132
    0
                    let mod_str = arg0.as_string::<i64>();
    133
    0
                    let match_str = arg1.as_string::<i64>();
    134
    0
                    let res = arrow_contains(mod_str, match_str)?;
    135
    0
                    Ok(Arc::new(res) as ArrayRef)
    136
                }
    137
    0
                other => {
    138
    0
                    exec_err!("Unsupported data type {other:?} for function `contains`.")
    139
                }
    140
            }
    141
        } else {
    142
    0
            exec_err!(
    143
    0
                "Unsupported data type {}, {:?} for function `contains`.",
    144
    0
                args[0].data_type(),
    145
    0
                args[1].data_type()
    146
            )
    147
        }
    148
    0
    }
    149
    150
    #[cfg(test)]
    151
    mod test {
    152
        use super::ContainsFunc;
    153
        use crate::expr_fn::contains;
    154
        use arrow::array::{BooleanArray, StringArray};
    155
        use arrow::datatypes::{DataType, Field};
    156
        use datafusion_common::config::ConfigOptions;
    157
        use datafusion_common::ScalarValue;
    158
        use datafusion_expr::{ColumnarValue, Expr, ScalarFunctionArgs, ScalarUDFImpl};
    159
        use std::sync::Arc;
    160
    161
        #[test]
    162
        fn test_contains_udf() {
    163
            let udf = ContainsFunc::new();
    164
            let array = ColumnarValue::Array(Arc::new(StringArray::from(vec![
    165
                Some("xxx?()"),
    166
                Some("yyy?()"),
    167
            ])));
    168
            let scalar = ColumnarValue::Scalar(ScalarValue::Utf8(Some("x?(".to_string())));
    169
            let arg_fields = vec![
    170
                Field::new("a", DataType::Utf8, true).into(),
    171
                Field::new("a", DataType::Utf8, true).into(),
    172
            ];
    173
    174
            let args = ScalarFunctionArgs {
    175
                args: vec![array, scalar],
    176
                arg_fields,
    177
                number_rows: 2,
    178
                return_field: Field::new("f", DataType::Boolean, true).into(),
    179
                config_options: Arc::new(ConfigOptions::default()),
    180
            };
    181
    182
            let actual = udf.invoke_with_args(args).unwrap();
    183
            let expect = ColumnarValue::Array(Arc::new(BooleanArray::from(vec![
    184
                Some(true),
    185
                Some(false),
    186
            ])));
    187
            assert_eq!(
    188
                *actual.into_array(2).unwrap(),
    189
                *expect.into_array(2).unwrap()
    190
            );
    191
        }
    192
    193
        #[test]
    194
        fn test_contains_api() {
    195
            let expr = contains(
    196
                Expr::Literal(
    197
                    ScalarValue::Utf8(Some("the quick brown fox".to_string())),
    198
                    None,
    199
                ),
    200
                Expr::Literal(ScalarValue::Utf8(Some("row".to_string())), None),
    201
            );
    202
            assert_eq!(
    203
                expr.to_string(),
    204
                "contains(Utf8(\"the quick brown fox\"), Utf8(\"row\"))"
    205
            );
    206
        }
    207
    }