From 1cd00cf3fc9afb49ce78df5bad23edc07d84b6ab Mon Sep 17 00:00:00 2001 From: Jan Tebernum Date: Wed, 3 May 2023 12:40:17 +0200 Subject: [PATCH] Feature: Add function `string::contains` (#1906) --- lib/fuzz/fuzz_targets/fuzz_executor.dict | 1 + lib/fuzz/fuzz_targets/fuzz_sql_parser.dict | 1 + lib/src/fnc/mod.rs | 1 + .../modules/surrealdb/functions/string.rs | 3 + lib/src/fnc/string.rs | 24 ++++- lib/src/sql/function.rs | 1 + lib/tests/function.rs | 87 +++++++++++++++++++ 7 files changed, 117 insertions(+), 1 deletion(-) diff --git a/lib/fuzz/fuzz_targets/fuzz_executor.dict b/lib/fuzz/fuzz_targets/fuzz_executor.dict index 80523a66..a128c581 100644 --- a/lib/fuzz/fuzz_targets/fuzz_executor.dict +++ b/lib/fuzz/fuzz_targets/fuzz_executor.dict @@ -274,6 +274,7 @@ # "sleep(" "string" "string::concat(" +"string::contains(" "string::endsWith(" "string::join(" "string::len(" diff --git a/lib/fuzz/fuzz_targets/fuzz_sql_parser.dict b/lib/fuzz/fuzz_targets/fuzz_sql_parser.dict index 6e907713..183e5344 100644 --- a/lib/fuzz/fuzz_targets/fuzz_sql_parser.dict +++ b/lib/fuzz/fuzz_targets/fuzz_sql_parser.dict @@ -273,6 +273,7 @@ "sleep(" "string" "string::concat(" +"string::contains(" "string::endsWith(" "string::join(" "string::len(" diff --git a/lib/src/fnc/mod.rs b/lib/src/fnc/mod.rs index 7f1a8241..0e5cad7b 100644 --- a/lib/src/fnc/mod.rs +++ b/lib/src/fnc/mod.rs @@ -201,6 +201,7 @@ pub fn synchronous(ctx: &Context<'_>, name: &str, args: Vec) -> Result session::token(ctx), // "string::concat" => string::concat, + "string::contains" => string::contains, "string::endsWith" => string::ends_with, "string::join" => string::join, "string::len" => string::len, diff --git a/lib/src/fnc/script/modules/surrealdb/functions/string.rs b/lib/src/fnc/script/modules/surrealdb/functions/string.rs index 32a86661..8be39f99 100644 --- a/lib/src/fnc/script/modules/surrealdb/functions/string.rs +++ b/lib/src/fnc/script/modules/surrealdb/functions/string.rs @@ -19,6 +19,7 @@ impl ModuleDef for Package { fn load<'js>(_ctx: Ctx<'js>, module: &Module<'js, Created>) -> Result<()> { module.add("default")?; module.add("concat")?; + module.add("contains")?; module.add("endsWith")?; module.add("join")?; module.add("len")?; @@ -39,6 +40,7 @@ impl ModuleDef for Package { fn eval<'js>(ctx: Ctx<'js>, module: &Module<'js, Loaded>) -> Result<()> { // Set specific exports module.set("concat", Func::from(|v: Any| run("string::concat", v.0)))?; + module.set("contains", Func::from(|v: Any| run("string::contains", v.0)))?; module.set("endsWith", Func::from(|v: Any| run("string::endsWith", v.0)))?; module.set("join", Func::from(|v: Any| run("string::join", v.0)))?; module.set("len", Func::from(|v: Any| run("string::len", v.0)))?; @@ -56,6 +58,7 @@ impl ModuleDef for Package { // Set default export let default = Object::new(ctx)?; default.set("concat", Func::from(|v: Any| run("string::concat", v.0)))?; + default.set("contains", Func::from(|v: Any| run("string::contains", v.0)))?; default.set("endsWith", Func::from(|v: Any| run("string::endsWith", v.0)))?; default.set("join", Func::from(|v: Any| run("string::join", v.0)))?; default.set("len", Func::from(|v: Any| run("string::len", v.0)))?; diff --git a/lib/src/fnc/string.rs b/lib/src/fnc/string.rs index 52c7aba9..4e9a996a 100644 --- a/lib/src/fnc/string.rs +++ b/lib/src/fnc/string.rs @@ -6,6 +6,10 @@ pub fn concat(args: Vec) -> Result { Ok(args.into_iter().map(|x| x.as_string()).collect::>().concat().into()) } +pub fn contains((val, check): (String, String)) -> Result { + Ok(val.contains(&check).into()) +} + pub fn ends_with((val, chr): (String, String)) -> Result { Ok(val.ends_with(&chr).into()) } @@ -107,7 +111,7 @@ pub fn words((string,): (String,)) -> Result { #[cfg(test)] mod tests { - use super::slice; + use super::{contains, slice}; use crate::sql::Value; #[test] @@ -131,4 +135,22 @@ mod tests { test(string, Some(-1), None, "界"); test(string, Some(-2), Some(1), "世"); } + + #[test] + fn string_contains() { + fn test(base: &str, contained: &str, expected: bool) { + assert_eq!( + contains((base.to_string(), contained.to_string())).unwrap(), + Value::from(expected) + ); + } + + test("", "", true); + test("", "a", false); + test("a", "", true); + test("abcde", "bcd", true); + test("abcde", "cbcd", false); + test("好世界", "世", true); + test("好世界", "你好", false); + } } diff --git a/lib/src/sql/function.rs b/lib/src/sql/function.rs index a36a8a80..862e6274 100644 --- a/lib/src/sql/function.rs +++ b/lib/src/sql/function.rs @@ -485,6 +485,7 @@ fn function_session(i: &str) -> IResult<&str, &str> { fn function_string(i: &str) -> IResult<&str, &str> { alt(( tag("concat"), + tag("contains"), tag("endsWith"), tag("join"), tag("len"), diff --git a/lib/tests/function.rs b/lib/tests/function.rs index 6cd47e6b..fb5d92a9 100644 --- a/lib/tests/function.rs +++ b/lib/tests/function.rs @@ -2947,6 +2947,93 @@ async fn function_string_concat() -> Result<(), Error> { Ok(()) } +#[tokio::test] +async fn function_string_contains() -> Result<(), Error> { + let sql = r#" + RETURN string::contains("", ""); + RETURN string::contains("a", ""); + RETURN string::contains("abcdefg", ""); + RETURN string::contains("abcdefg", "bcd"); + RETURN string::contains("abcdefg", "abcd"); + RETURN string::contains("abcdefg", "xxabcd"); + RETURN string::contains("abcdefg", "hij"); + RETURN string::contains("ประเทศไทย中华", "ประเ"); + RETURN string::contains("ประเทศไทย中华", "ะเ"); + RETURN string::contains("ประเทศไทย中华", "ไท华"); + RETURN string::contains("1234567ah012345678901ah", "hah"); + RETURN string::contains("00abc01234567890123456789abc", "bcabc"); + RETURN string::contains("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaba"); + RETURN string::contains("* \t", " "); + RETURN string::contains("* \t", "?"); + "#; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 15); + // 1 + let tmp = res.remove(0).result?; + let val = Value::Bool(true); + assert_eq!(tmp, val); + // 2 + let tmp = res.remove(0).result?; + let val = Value::Bool(true); + assert_eq!(tmp, val); + // 3 + let tmp = res.remove(0).result?; + let val = Value::Bool(true); + assert_eq!(tmp, val); + // 4 + let tmp = res.remove(0).result?; + let val = Value::Bool(true); + assert_eq!(tmp, val); + // 5 + let tmp = res.remove(0).result?; + let val = Value::Bool(true); + assert_eq!(tmp, val); + // 6 + let tmp = res.remove(0).result?; + let val = Value::Bool(false); + assert_eq!(tmp, val); + // 7 + let tmp = res.remove(0).result?; + let val = Value::Bool(false); + assert_eq!(tmp, val); + // 8 + let tmp = res.remove(0).result?; + let val = Value::Bool(true); + assert_eq!(tmp, val); + // 9 + let tmp = res.remove(0).result?; + let val = Value::Bool(true); + assert_eq!(tmp, val); + // 10 + let tmp = res.remove(0).result?; + let val = Value::Bool(false); + assert_eq!(tmp, val); + // 11 + let tmp = res.remove(0).result?; + let val = Value::Bool(false); + assert_eq!(tmp, val); + // 12 + let tmp = res.remove(0).result?; + let val = Value::Bool(false); + assert_eq!(tmp, val); + // 13 + let tmp = res.remove(0).result?; + let val = Value::Bool(false); + assert_eq!(tmp, val); + // 14 + let tmp = res.remove(0).result?; + let val = Value::Bool(true); + assert_eq!(tmp, val); + // 15 + let tmp = res.remove(0).result?; + let val = Value::Bool(false); + assert_eq!(tmp, val); + // + Ok(()) +} + #[tokio::test] async fn function_string_ends_with() -> Result<(), Error> { let sql = r#"