This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 87aea14309 Add contains function, and support in datafusion substrait 
consumer (#10879)
87aea14309 is described below

commit 87aea143099b7220037bd54629c38d450b894de1
Author: Lordworms <[email protected]>
AuthorDate: Sat Jun 15 09:45:57 2024 -0700

    Add contains function, and support in datafusion substrait consumer (#10879)
    
    * adding new function contains
    
    * adding substrait test
    
    * adding doc
    
    * adding doc
    
    * Update docs/source/user-guide/sql/scalar_functions.md
    
    Co-authored-by: Alex Huang <[email protected]>
    
    * adding entry
    
    ---------
    
    Co-authored-by: Alex Huang <[email protected]>
---
 datafusion/functions/src/string/contains.rs        | 143 +++++++++++++++++++++
 datafusion/functions/src/string/mod.rs             |   8 +-
 datafusion/sqllogictest/test_files/functions.slt   |  18 +++
 datafusion/substrait/tests/cases/function_test.rs  |  58 +++++++++
 datafusion/substrait/tests/cases/mod.rs            |   1 +
 .../tests/testdata/contains_plan.substrait.json    | 133 +++++++++++++++++++
 docs/source/user-guide/sql/scalar_functions.md     |  14 ++
 7 files changed, 373 insertions(+), 2 deletions(-)

diff --git a/datafusion/functions/src/string/contains.rs 
b/datafusion/functions/src/string/contains.rs
new file mode 100644
index 0000000000..faf979f806
--- /dev/null
+++ b/datafusion/functions/src/string/contains.rs
@@ -0,0 +1,143 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::utils::make_scalar_function;
+use arrow::array::{ArrayRef, OffsetSizeTrait};
+use arrow::datatypes::DataType;
+use arrow::datatypes::DataType::Boolean;
+use datafusion_common::cast::as_generic_string_array;
+use datafusion_common::DataFusionError;
+use datafusion_common::Result;
+use datafusion_common::{arrow_datafusion_err, exec_err};
+use datafusion_expr::ScalarUDFImpl;
+use datafusion_expr::TypeSignature::Exact;
+use datafusion_expr::{ColumnarValue, Signature, Volatility};
+use std::any::Any;
+use std::sync::Arc;
+#[derive(Debug)]
+pub struct ContainsFunc {
+    signature: Signature,
+}
+
+impl Default for ContainsFunc {
+    fn default() -> Self {
+        ContainsFunc::new()
+    }
+}
+
+impl ContainsFunc {
+    pub fn new() -> Self {
+        use DataType::*;
+        Self {
+            signature: Signature::one_of(
+                vec![Exact(vec![Utf8, Utf8]), Exact(vec![LargeUtf8, 
LargeUtf8])],
+                Volatility::Immutable,
+            ),
+        }
+    }
+}
+
+impl ScalarUDFImpl for ContainsFunc {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn name(&self) -> &str {
+        "contains"
+    }
+
+    fn signature(&self) -> &Signature {
+        &self.signature
+    }
+
+    fn return_type(&self, _: &[DataType]) -> Result<DataType> {
+        Ok(Boolean)
+    }
+
+    fn invoke(&self, args: &[ColumnarValue]) -> Result<ColumnarValue> {
+        match args[0].data_type() {
+            DataType::Utf8 => make_scalar_function(contains::<i32>, 
vec![])(args),
+            DataType::LargeUtf8 => make_scalar_function(contains::<i64>, 
vec![])(args),
+            other => {
+                exec_err!("unsupported data type {other:?} for function 
contains")
+            }
+        }
+    }
+}
+
+/// use regexp_is_match_utf8_scalar to do the calculation for contains
+pub fn contains<T: OffsetSizeTrait>(
+    args: &[ArrayRef],
+) -> Result<ArrayRef, DataFusionError> {
+    let mod_str = as_generic_string_array::<T>(&args[0])?;
+    let match_str = as_generic_string_array::<T>(&args[1])?;
+    let res = arrow::compute::kernels::comparison::regexp_is_match_utf8(
+        mod_str, match_str, None,
+    )
+    .map_err(|e| arrow_datafusion_err!(e))?;
+
+    Ok(Arc::new(res) as ArrayRef)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::string::contains::ContainsFunc;
+    use crate::utils::test::test_function;
+    use arrow::array::Array;
+    use arrow::{array::BooleanArray, datatypes::DataType::Boolean};
+    use datafusion_common::Result;
+    use datafusion_common::ScalarValue;
+    use datafusion_expr::ColumnarValue;
+    use datafusion_expr::ScalarUDFImpl;
+    #[test]
+    fn test_functions() -> Result<()> {
+        test_function!(
+            ContainsFunc::new(),
+            &[
+                ColumnarValue::Scalar(ScalarValue::from("alphabet")),
+                ColumnarValue::Scalar(ScalarValue::from("alph")),
+            ],
+            Ok(Some(true)),
+            bool,
+            Boolean,
+            BooleanArray
+        );
+        test_function!(
+            ContainsFunc::new(),
+            &[
+                ColumnarValue::Scalar(ScalarValue::from("alphabet")),
+                ColumnarValue::Scalar(ScalarValue::from("dddddd")),
+            ],
+            Ok(Some(false)),
+            bool,
+            Boolean,
+            BooleanArray
+        );
+        test_function!(
+            ContainsFunc::new(),
+            &[
+                ColumnarValue::Scalar(ScalarValue::from("alphabet")),
+                ColumnarValue::Scalar(ScalarValue::from("pha")),
+            ],
+            Ok(Some(true)),
+            bool,
+            Boolean,
+            BooleanArray
+        );
+        Ok(())
+    }
+}
diff --git a/datafusion/functions/src/string/mod.rs 
b/datafusion/functions/src/string/mod.rs
index 219ef8b5a5..5bf372c29f 100644
--- a/datafusion/functions/src/string/mod.rs
+++ b/datafusion/functions/src/string/mod.rs
@@ -28,6 +28,7 @@ pub mod chr;
 pub mod common;
 pub mod concat;
 pub mod concat_ws;
+pub mod contains;
 pub mod ends_with;
 pub mod initcap;
 pub mod levenshtein;
@@ -43,7 +44,6 @@ pub mod starts_with;
 pub mod to_hex;
 pub mod upper;
 pub mod uuid;
-
 // create UDFs
 make_udf_function!(ascii::AsciiFunc, ASCII, ascii);
 make_udf_function!(bit_length::BitLengthFunc, BIT_LENGTH, bit_length);
@@ -66,7 +66,7 @@ make_udf_function!(split_part::SplitPartFunc, SPLIT_PART, 
split_part);
 make_udf_function!(to_hex::ToHexFunc, TO_HEX, to_hex);
 make_udf_function!(upper::UpperFunc, UPPER, upper);
 make_udf_function!(uuid::UuidFunc, UUID, uuid);
-
+make_udf_function!(contains::ContainsFunc, CONTAINS, contains);
 pub mod expr_fn {
     use datafusion_expr::Expr;
 
@@ -149,6 +149,9 @@ pub mod expr_fn {
     ),(
         uuid,
         "returns uuid v4 as a string value",
+    ), (
+        contains,
+        "Return true if search_string is found within string. treated it like 
a reglike",
     ));
 
     #[doc = "Removes all characters, spaces by default, from both sides of a 
string"]
@@ -188,5 +191,6 @@ pub fn functions() -> Vec<Arc<ScalarUDF>> {
         to_hex(),
         upper(),
         uuid(),
+        contains(),
     ]
 }
diff --git a/datafusion/sqllogictest/test_files/functions.slt 
b/datafusion/sqllogictest/test_files/functions.slt
index df6295d63b..c3dd791f6c 100644
--- a/datafusion/sqllogictest/test_files/functions.slt
+++ b/datafusion/sqllogictest/test_files/functions.slt
@@ -1158,3 +1158,21 @@ drop table uuid_table
 
 statement ok
 drop table t
+
+
+# test for contains
+
+query B
+select contains('alphabet', 'pha');
+----
+true
+
+query B
+select contains('alphabet', 'dddd');
+----
+false
+
+query B
+select contains('', '');
+----
+true
diff --git a/datafusion/substrait/tests/cases/function_test.rs 
b/datafusion/substrait/tests/cases/function_test.rs
new file mode 100644
index 0000000000..b4c5659a3a
--- /dev/null
+++ b/datafusion/substrait/tests/cases/function_test.rs
@@ -0,0 +1,58 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Tests for Function Compatibility
+
+#[cfg(test)]
+mod tests {
+    use datafusion::common::Result;
+    use datafusion::prelude::{CsvReadOptions, SessionContext};
+    use datafusion_substrait::logical_plan::consumer::from_substrait_plan;
+    use std::fs::File;
+    use std::io::BufReader;
+    use substrait::proto::Plan;
+
+    #[tokio::test]
+    async fn contains_function_test() -> Result<()> {
+        let ctx = create_context().await?;
+
+        let path = "tests/testdata/contains_plan.substrait.json";
+        let proto = serde_json::from_reader::<_, Plan>(BufReader::new(
+            File::open(path).expect("file not found"),
+        ))
+        .expect("failed to parse json");
+
+        let plan = from_substrait_plan(&ctx, &proto).await?;
+
+        let plan_str = format!("{:?}", plan);
+
+        assert_eq!(
+            plan_str,
+            "Projection: nation.b AS n_name\
+            \n  Filter: contains(nation.b, Utf8(\"IA\"))\
+            \n    TableScan: nation projection=[a, b, c, d, e, f]"
+        );
+        Ok(())
+    }
+
+    async fn create_context() -> datafusion::common::Result<SessionContext> {
+        let ctx = SessionContext::new();
+        ctx.register_csv("nation", "tests/testdata/data.csv", 
CsvReadOptions::new())
+            .await?;
+        Ok(ctx)
+    }
+}
diff --git a/datafusion/substrait/tests/cases/mod.rs 
b/datafusion/substrait/tests/cases/mod.rs
index a31f93087d..d3ea7695e4 100644
--- a/datafusion/substrait/tests/cases/mod.rs
+++ b/datafusion/substrait/tests/cases/mod.rs
@@ -16,6 +16,7 @@
 // under the License.
 
 mod consumer_integration;
+mod function_test;
 mod logical_plans;
 mod roundtrip_logical_plan;
 mod roundtrip_physical_plan;
diff --git a/datafusion/substrait/tests/testdata/contains_plan.substrait.json 
b/datafusion/substrait/tests/testdata/contains_plan.substrait.json
new file mode 100644
index 0000000000..76edde34e3
--- /dev/null
+++ b/datafusion/substrait/tests/testdata/contains_plan.substrait.json
@@ -0,0 +1,133 @@
+{
+  "extensionUris": [
+    {
+      "extensionUriAnchor": 1,
+      "uri": 
"https://github.com/substrait-io/substrait/blob/main/extensions/functions_string.yaml";
+    }
+  ],
+  "extensions": [
+    {
+      "extensionFunction": {
+        "extensionUriReference": 1,
+        "functionAnchor": 1,
+        "name": "contains:str_str"
+      }
+    }
+  ],
+  "relations": [
+    {
+      "root": {
+        "input": {
+          "project": {
+            "common": {
+              "emit": {
+                "outputMapping": [
+                  4
+                ]
+              }
+            },
+            "input": {
+              "filter": {
+                "input": {
+                  "read": {
+                    "common": {
+                      "direct": {}
+                    },
+                    "baseSchema": {
+                      "names": [
+                        "n_nationkey",
+                        "n_name",
+                        "n_regionkey",
+                        "n_comment"
+                      ],
+                      "struct": {
+                        "types": [
+                          {
+                            "i32": {
+                              "nullability": "NULLABILITY_REQUIRED"
+                            }
+                          },
+                          {
+                            "string": {
+                              "nullability": "NULLABILITY_REQUIRED"
+                            }
+                          },
+                          {
+                            "i32": {
+                              "nullability": "NULLABILITY_REQUIRED"
+                            }
+                          },
+                          {
+                            "string": {
+                              "nullability": "NULLABILITY_REQUIRED"
+                            }
+                          }
+                        ],
+                        "nullability": "NULLABILITY_REQUIRED"
+                      }
+                    },
+                    "namedTable": {
+                      "names": [
+                        "nation"
+                      ]
+                    }
+                  }
+                },
+                "condition": {
+                  "scalarFunction": {
+                    "functionReference": 1,
+                    "outputType": {
+                      "bool": {
+                        "nullability": "NULLABILITY_NULLABLE"
+                      }
+                    },
+                    "arguments": [
+                      {
+                        "value": {
+                          "selection": {
+                            "directReference": {
+                              "structField": {
+                                "field": 1
+                              }
+                            },
+                            "rootReference": {}
+                          }
+                        }
+                      },
+                      {
+                        "value": {
+                          "literal": {
+                            "string": "IA"
+                          }
+                        }
+                      }
+                    ]
+                  }
+                }
+              }
+            },
+            "expressions": [
+              {
+                "selection": {
+                  "directReference": {
+                    "structField": {
+                      "field": 1
+                    }
+                  },
+                  "rootReference": {}
+                }
+              }
+            ]
+          }
+        },
+        "names": [
+          "n_name"
+        ]
+      }
+    }
+  ],
+  "version": {
+    "minorNumber": 38,
+    "producer": "ibis-substrait"
+  }
+}
\ No newline at end of file
diff --git a/docs/source/user-guide/sql/scalar_functions.md 
b/docs/source/user-guide/sql/scalar_functions.md
index 10c52bc5de..ec34dbf9ba 100644
--- a/docs/source/user-guide/sql/scalar_functions.md
+++ b/docs/source/user-guide/sql/scalar_functions.md
@@ -681,6 +681,7 @@ _Alias of [nvl](#nvl)._
 - [substr_index](#substr_index)
 - [find_in_set](#find_in_set)
 - [position](#position)
+- [contains](#contains)
 
 ### `ascii`
 
@@ -1443,6 +1444,19 @@ position(substr in origstr)
 - **substr**: The pattern string.
 - **origstr**: The model string.
 
+### `contains`
+
+Return true if search_string is found within string.
+
+```
+contains(string, search_string)
+```
+
+#### Arguments
+
+- **string**: The pattern string.
+- **search_string**: The model string.
+
 ## Time and Date Functions
 
 - [now](#now)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to