Improve array
function implementations, and improve value::diff()
and value::patch()
functions (#4612)
This commit is contained in:
parent
e9b36ba162
commit
93a9ba3cb6
14 changed files with 443 additions and 187 deletions
|
@ -18,7 +18,6 @@ use crate::sql::array::Windows;
|
|||
use crate::sql::value::Value;
|
||||
use crate::sql::Closure;
|
||||
use crate::sql::Function;
|
||||
|
||||
use rand::prelude::SliceRandom;
|
||||
use reblessive::tree::Stk;
|
||||
use std::mem::size_of_val;
|
||||
|
@ -54,12 +53,56 @@ pub fn add((mut array, value): (Array, Value)) -> Result<Value, Error> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn all((array,): (Array,)) -> Result<Value, Error> {
|
||||
Ok(array.iter().all(Value::is_truthy).into())
|
||||
pub async fn all(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(array, check): (Array, Option<Value>),
|
||||
) -> Result<Value, Error> {
|
||||
Ok(match check {
|
||||
Some(closure) if closure.is_closure() => {
|
||||
if let Some(opt) = opt {
|
||||
for val in array.iter() {
|
||||
let arg = val.compute(stk, ctx, opt, doc).await?;
|
||||
let fnc = Function::Anonymous(closure.clone(), vec![arg]);
|
||||
if fnc.compute(stk, ctx, opt, doc).await?.is_truthy() {
|
||||
continue;
|
||||
} else {
|
||||
return Ok(Value::Bool(false));
|
||||
}
|
||||
}
|
||||
Value::Bool(true)
|
||||
} else {
|
||||
Value::None
|
||||
}
|
||||
}
|
||||
Some(value) => array.iter().all(|v: &Value| *v == value).into(),
|
||||
None => array.iter().all(Value::is_truthy).into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn any((array,): (Array,)) -> Result<Value, Error> {
|
||||
Ok(array.iter().any(Value::is_truthy).into())
|
||||
pub async fn any(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(array, check): (Array, Option<Value>),
|
||||
) -> Result<Value, Error> {
|
||||
Ok(match check {
|
||||
Some(closure) if closure.is_closure() => {
|
||||
if let Some(opt) = opt {
|
||||
for val in array.iter() {
|
||||
let arg = val.compute(stk, ctx, opt, doc).await?;
|
||||
let fnc = Function::Anonymous(closure.clone(), vec![arg]);
|
||||
if fnc.compute(stk, ctx, opt, doc).await?.is_truthy() {
|
||||
return Ok(Value::Bool(true));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Value::Bool(false)
|
||||
} else {
|
||||
Value::None
|
||||
}
|
||||
}
|
||||
Some(value) => array.iter().any(|v: &Value| *v == value).into(),
|
||||
None => array.iter().any(Value::is_truthy).into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn append((mut array, value): (Array, Value)) -> Result<Value, Error> {
|
||||
|
@ -194,8 +237,51 @@ pub fn fill(
|
|||
Ok(array.into())
|
||||
}
|
||||
|
||||
pub fn filter_index((array, value): (Array, Value)) -> Result<Value, Error> {
|
||||
Ok(array
|
||||
pub async fn filter(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(array, check): (Array, Value),
|
||||
) -> Result<Value, Error> {
|
||||
Ok(match check {
|
||||
closure if closure.is_closure() => {
|
||||
if let Some(opt) = opt {
|
||||
let mut res = Vec::with_capacity(array.len());
|
||||
for val in array.iter() {
|
||||
let arg = val.compute(stk, ctx, opt, doc).await?;
|
||||
let fnc = Function::Anonymous(closure.clone(), vec![arg.clone()]);
|
||||
if fnc.compute(stk, ctx, opt, doc).await?.is_truthy() {
|
||||
res.push(arg)
|
||||
}
|
||||
}
|
||||
Value::from(res)
|
||||
} else {
|
||||
Value::None
|
||||
}
|
||||
}
|
||||
value => array.into_iter().filter(|v: &Value| *v == value).collect::<Vec<_>>().into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn filter_index(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(array, value): (Array, Value),
|
||||
) -> Result<Value, Error> {
|
||||
Ok(match value {
|
||||
closure if closure.is_closure() => {
|
||||
if let Some(opt) = opt {
|
||||
let mut res = Vec::with_capacity(array.len());
|
||||
for (i, val) in array.iter().enumerate() {
|
||||
let arg = val.compute(stk, ctx, opt, doc).await?;
|
||||
let fnc = Function::Anonymous(closure.clone(), vec![arg, i.into()]);
|
||||
if fnc.compute(stk, ctx, opt, doc).await?.is_truthy() {
|
||||
res.push(i);
|
||||
}
|
||||
}
|
||||
Value::from(res)
|
||||
} else {
|
||||
Value::None
|
||||
}
|
||||
}
|
||||
value => array
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, v)| {
|
||||
|
@ -206,15 +292,64 @@ pub fn filter_index((array, value): (Array, Value)) -> Result<Value, Error> {
|
|||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into())
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_index((array, value): (Array, Value)) -> Result<Value, Error> {
|
||||
Ok(array
|
||||
pub async fn find(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(array, value): (Array, Value),
|
||||
) -> Result<Value, Error> {
|
||||
Ok(match value {
|
||||
closure if closure.is_closure() => {
|
||||
if let Some(opt) = opt {
|
||||
for val in array.iter() {
|
||||
let arg = val.compute(stk, ctx, opt, doc).await?;
|
||||
let fnc = Function::Anonymous(closure.clone(), vec![arg.clone()]);
|
||||
if fnc.compute(stk, ctx, opt, doc).await?.is_truthy() {
|
||||
return Ok(arg);
|
||||
}
|
||||
}
|
||||
Value::None
|
||||
} else {
|
||||
Value::None
|
||||
}
|
||||
}
|
||||
value => array.into_iter().find(|v: &Value| *v == value).into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn find_index(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(array, value): (Array, Value),
|
||||
) -> Result<Value, Error> {
|
||||
Ok(match value {
|
||||
closure if closure.is_closure() => {
|
||||
if let Some(opt) = opt {
|
||||
for (i, val) in array.iter().enumerate() {
|
||||
let arg = val.compute(stk, ctx, opt, doc).await?;
|
||||
let fnc = Function::Anonymous(closure.clone(), vec![arg, i.into()]);
|
||||
if fnc.compute(stk, ctx, opt, doc).await?.is_truthy() {
|
||||
return Ok(i.into());
|
||||
}
|
||||
}
|
||||
Value::None
|
||||
} else {
|
||||
Value::None
|
||||
}
|
||||
}
|
||||
value => array
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_i, v)| **v == value)
|
||||
.map_or(Value::Null, |(i, _v)| i.into()))
|
||||
.find_map(|(i, v)| {
|
||||
if *v == value {
|
||||
Some(Value::from(i))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn first((array,): (Array,)) -> Result<Value, Error> {
|
||||
|
@ -353,17 +488,20 @@ pub fn logical_xor((lh, rh): (Array, Array)) -> Result<Value, Error> {
|
|||
}
|
||||
|
||||
pub async fn map(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, &Options, Option<&CursorDoc>),
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(array, mapper): (Array, Closure),
|
||||
) -> Result<Value, Error> {
|
||||
let mut array = array;
|
||||
for i in 0..array.len() {
|
||||
let v = array.get(i).unwrap();
|
||||
let fnc = Function::Anonymous(mapper.clone().into(), vec![v.to_owned(), i.into()]);
|
||||
array[i] = fnc.compute(stk, ctx, opt, doc).await?;
|
||||
if let Some(opt) = opt {
|
||||
let mut res = Vec::with_capacity(array.len());
|
||||
for (i, val) in array.into_iter().enumerate() {
|
||||
let arg = val.compute(stk, ctx, opt, doc).await?;
|
||||
let fnc = Function::Anonymous(mapper.clone().into(), vec![arg, i.into()]);
|
||||
res.push(fnc.compute(stk, ctx, opt, doc).await?);
|
||||
}
|
||||
Ok(res.into())
|
||||
} else {
|
||||
Ok(Value::None)
|
||||
}
|
||||
|
||||
Ok(array.into())
|
||||
}
|
||||
|
||||
pub fn matches((array, compare_val): (Array, Value)) -> Result<Value, Error> {
|
||||
|
|
|
@ -27,7 +27,6 @@ pub mod record;
|
|||
pub mod script;
|
||||
pub mod search;
|
||||
pub mod session;
|
||||
pub mod shared;
|
||||
pub mod sleep;
|
||||
pub mod string;
|
||||
pub mod time;
|
||||
|
@ -46,16 +45,28 @@ pub async fn run(
|
|||
args: Vec<Value>,
|
||||
) -> Result<Value, Error> {
|
||||
if name.eq("sleep")
|
||||
|| name.starts_with("search")
|
||||
|| name.eq("array::all")
|
||||
|| name.eq("array::any")
|
||||
|| name.eq("array::every")
|
||||
|| name.eq("array::filter_index")
|
||||
|| name.eq("array::filter")
|
||||
|| name.eq("array::find_index")
|
||||
|| name.eq("array::find")
|
||||
|| name.eq("array::includes")
|
||||
|| name.eq("array::index_of")
|
||||
|| name.eq("array::map")
|
||||
|| name.eq("array::some")
|
||||
|| name.eq("record::exists")
|
||||
|| name.eq("type::field")
|
||||
|| name.eq("type::fields")
|
||||
|| name.eq("value::diff")
|
||||
|| name.eq("value::patch")
|
||||
|| name.starts_with("http")
|
||||
|| name.starts_with("type::field")
|
||||
|| name.starts_with("type::fields")
|
||||
|| name.starts_with("search")
|
||||
|| name.starts_with("crypto::argon2")
|
||||
|| name.starts_with("crypto::bcrypt")
|
||||
|| name.starts_with("crypto::pbkdf2")
|
||||
|| name.starts_with("crypto::scrypt")
|
||||
|| name.starts_with("array::map")
|
||||
|| name.starts_with("record::exists")
|
||||
{
|
||||
stk.run(|stk| asynchronous(stk, ctx, opt, doc, name, args)).await
|
||||
} else {
|
||||
|
@ -99,9 +110,8 @@ pub fn synchronous(
|
|||
name,
|
||||
args,
|
||||
"no such builtin function found",
|
||||
//
|
||||
"array::add" => array::add,
|
||||
"array::all" => array::all,
|
||||
"array::any" => array::any,
|
||||
"array::append" => array::append,
|
||||
"array::at" => array::at,
|
||||
"array::boolean_and" => array::boolean_and,
|
||||
|
@ -115,8 +125,6 @@ pub fn synchronous(
|
|||
"array::difference" => array::difference,
|
||||
"array::distinct" => array::distinct,
|
||||
"array::fill" => array::fill,
|
||||
"array::filter_index" => array::filter_index,
|
||||
"array::find_index" => array::find_index,
|
||||
"array::first" => array::first,
|
||||
"array::flatten" => array::flatten,
|
||||
"array::group" => array::group,
|
||||
|
@ -276,7 +284,7 @@ pub fn synchronous(
|
|||
//
|
||||
"string::concat" => string::concat,
|
||||
"string::contains" => string::contains,
|
||||
"string::endsWith" => string::ends_with,
|
||||
"string::ends_with" => string::ends_with,
|
||||
"string::join" => string::join,
|
||||
"string::len" => string::len,
|
||||
"string::lowercase" => string::lowercase,
|
||||
|
@ -287,7 +295,7 @@ pub fn synchronous(
|
|||
"string::slice" => string::slice,
|
||||
"string::slug" => string::slug,
|
||||
"string::split" => string::split,
|
||||
"string::startsWith" => string::starts_with,
|
||||
"string::starts_with" => string::starts_with,
|
||||
"string::trim" => string::trim,
|
||||
"string::uppercase" => string::uppercase,
|
||||
"string::words" => string::words,
|
||||
|
@ -395,9 +403,6 @@ pub fn synchronous(
|
|||
"type::is::string" => r#type::is::string,
|
||||
"type::is::uuid" => r#type::is::uuid,
|
||||
//
|
||||
"value::diff" => value::diff,
|
||||
"value::patch" => value::patch,
|
||||
//
|
||||
"vector::add" => vector::add,
|
||||
"vector::angle" => vector::angle,
|
||||
"vector::cross" => vector::cross,
|
||||
|
@ -423,6 +428,81 @@ pub fn synchronous(
|
|||
)
|
||||
}
|
||||
|
||||
/// Attempts to run any asynchronous function.
|
||||
pub async fn asynchronous(
|
||||
stk: &mut Stk,
|
||||
ctx: &Context,
|
||||
opt: &Options,
|
||||
doc: Option<&CursorDoc>,
|
||||
name: &str,
|
||||
args: Vec<Value>,
|
||||
) -> Result<Value, Error> {
|
||||
// Wrappers return a function as opposed to a value so that the dispatch! method can always
|
||||
// perform a function call.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn cpu_intensive<R: Send + 'static>(
|
||||
function: impl FnOnce() -> R + Send + 'static,
|
||||
) -> impl FnOnce() -> executor::Task<R> {
|
||||
|| crate::exe::spawn(async move { function() })
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn cpu_intensive<R: Send + 'static>(
|
||||
function: impl FnOnce() -> R + Send + 'static,
|
||||
) -> impl FnOnce() -> std::future::Ready<R> {
|
||||
|| std::future::ready(function())
|
||||
}
|
||||
|
||||
dispatch!(
|
||||
name,
|
||||
args,
|
||||
"no such builtin function found",
|
||||
//
|
||||
"array::all" => array::all((stk, ctx, Some(opt), doc)).await,
|
||||
"array::any" => array::any((stk, ctx, Some(opt), doc)).await,
|
||||
"array::every" => array::all((stk, ctx, Some(opt), doc)).await,
|
||||
"array::filter" => array::filter((stk, ctx, Some(opt), doc)).await,
|
||||
"array::filter_index" => array::filter_index((stk, ctx, Some(opt), doc)).await,
|
||||
"array::find" => array::find((stk, ctx, Some(opt), doc)).await,
|
||||
"array::find_index" => array::find_index((stk, ctx, Some(opt), doc)).await,
|
||||
"array::includes" => array::any((stk, ctx, Some(opt), doc)).await,
|
||||
"array::index_of" => array::find_index((stk, ctx, Some(opt), doc)).await,
|
||||
"array::map" => array::map((stk, ctx, Some(opt), doc)).await,
|
||||
"array::some" => array::any((stk, ctx, Some(opt), doc)).await,
|
||||
//
|
||||
"crypto::argon2::compare" => (cpu_intensive) crypto::argon2::cmp.await,
|
||||
"crypto::argon2::generate" => (cpu_intensive) crypto::argon2::gen.await,
|
||||
"crypto::bcrypt::compare" => (cpu_intensive) crypto::bcrypt::cmp.await,
|
||||
"crypto::bcrypt::generate" => (cpu_intensive) crypto::bcrypt::gen.await,
|
||||
"crypto::pbkdf2::compare" => (cpu_intensive) crypto::pbkdf2::cmp.await,
|
||||
"crypto::pbkdf2::generate" => (cpu_intensive) crypto::pbkdf2::gen.await,
|
||||
"crypto::scrypt::compare" => (cpu_intensive) crypto::scrypt::cmp.await,
|
||||
"crypto::scrypt::generate" => (cpu_intensive) crypto::scrypt::gen.await,
|
||||
//
|
||||
"http::head" => http::head(ctx).await,
|
||||
"http::get" => http::get(ctx).await,
|
||||
"http::put" => http::put(ctx).await,
|
||||
"http::post" => http::post(ctx).await,
|
||||
"http::patch" => http::patch(ctx).await,
|
||||
"http::delete" => http::delete(ctx).await,
|
||||
//
|
||||
"record::exists" => record::exists((stk, ctx, Some(opt), doc)).await,
|
||||
//
|
||||
"search::analyze" => search::analyze((stk, ctx, Some(opt))).await,
|
||||
"search::score" => search::score((ctx, doc)).await,
|
||||
"search::highlight" => search::highlight((ctx, doc)).await,
|
||||
"search::offsets" => search::offsets((ctx, doc)).await,
|
||||
//
|
||||
"sleep" => sleep::sleep(ctx).await,
|
||||
//
|
||||
"type::field" => r#type::field((stk, ctx, Some(opt), doc)).await,
|
||||
"type::fields" => r#type::fields((stk, ctx, Some(opt), doc)).await,
|
||||
//
|
||||
"value::diff" => value::diff((stk, ctx, Some(opt), doc)).await,
|
||||
"value::patch" => value::patch((stk, ctx, Some(opt), doc)).await,
|
||||
)
|
||||
}
|
||||
|
||||
/// Attempts to run any synchronous function.
|
||||
pub async fn idiom(
|
||||
stk: &mut Stk,
|
||||
|
@ -440,9 +520,10 @@ pub async fn idiom(
|
|||
name,
|
||||
args.clone(),
|
||||
"no such method found for the array type",
|
||||
//
|
||||
"add" => array::add,
|
||||
"all" => array::all,
|
||||
"any" => array::any,
|
||||
"all" => array::all((stk, ctx, Some(opt), doc)).await,
|
||||
"any" => array::any((stk, ctx, Some(opt), doc)).await,
|
||||
"append" => array::append,
|
||||
"at" => array::at,
|
||||
"boolean_and" => array::boolean_and,
|
||||
|
@ -455,12 +536,17 @@ pub async fn idiom(
|
|||
"concat" => array::concat,
|
||||
"difference" => array::difference,
|
||||
"distinct" => array::distinct,
|
||||
"every" => array::all((stk, ctx, Some(opt), doc)).await,
|
||||
"fill" => array::fill,
|
||||
"filter_index" => array::filter_index,
|
||||
"find_index" => array::find_index,
|
||||
"filter" => array::filter((stk, ctx, Some(opt), doc)).await,
|
||||
"filter_index" => array::filter_index((stk, ctx, Some(opt), doc)).await,
|
||||
"find" => array::find((stk, ctx, Some(opt), doc)).await,
|
||||
"find_index" => array::find_index((stk, ctx, Some(opt), doc)).await,
|
||||
"first" => array::first,
|
||||
"flatten" => array::flatten,
|
||||
"group" => array::group,
|
||||
"includes" => array::any((stk, ctx, Some(opt), doc)).await,
|
||||
"index_of" => array::find_index((stk, ctx, Some(opt), doc)).await,
|
||||
"insert" => array::insert,
|
||||
"intersect" => array::intersect,
|
||||
"is_empty" => array::is_empty,
|
||||
|
@ -471,7 +557,7 @@ pub async fn idiom(
|
|||
"logical_or" => array::logical_or,
|
||||
"logical_xor" => array::logical_xor,
|
||||
"matches" => array::matches,
|
||||
"map" => array::map((stk, ctx, opt, doc)).await,
|
||||
"map" => array::map((stk, ctx, Some(opt), doc)).await,
|
||||
"max" => array::max,
|
||||
"min" => array::min,
|
||||
"pop" => array::pop,
|
||||
|
@ -481,6 +567,7 @@ pub async fn idiom(
|
|||
"reverse" => array::reverse,
|
||||
"shuffle" => array::shuffle,
|
||||
"slice" => array::slice,
|
||||
"some" => array::any((stk, ctx, Some(opt), doc)).await,
|
||||
"sort" => array::sort,
|
||||
"swap" => array::swap,
|
||||
"transpose" => array::transpose,
|
||||
|
@ -518,6 +605,7 @@ pub async fn idiom(
|
|||
name,
|
||||
args.clone(),
|
||||
"no such method found for the bytes type",
|
||||
//
|
||||
"len" => bytes::len,
|
||||
)
|
||||
}
|
||||
|
@ -526,6 +614,7 @@ pub async fn idiom(
|
|||
name,
|
||||
args.clone(),
|
||||
"no such method found for the duration type",
|
||||
//
|
||||
"days" => duration::days,
|
||||
"hours" => duration::hours,
|
||||
"micros" => duration::micros,
|
||||
|
@ -542,6 +631,7 @@ pub async fn idiom(
|
|||
name,
|
||||
args.clone(),
|
||||
"no such method found for the geometry type",
|
||||
//
|
||||
"area" => geo::area,
|
||||
"bearing" => geo::bearing,
|
||||
"centroid" => geo::centroid,
|
||||
|
@ -555,6 +645,7 @@ pub async fn idiom(
|
|||
name,
|
||||
args.clone(),
|
||||
"no such method found for the record type",
|
||||
//
|
||||
"exists" => record::exists((stk, ctx, Some(opt), doc)).await,
|
||||
"id" => record::id,
|
||||
"table" => record::tb,
|
||||
|
@ -566,6 +657,7 @@ pub async fn idiom(
|
|||
name,
|
||||
args.clone(),
|
||||
"no such method found for the object type",
|
||||
//
|
||||
"entries" => object::entries,
|
||||
"keys" => object::keys,
|
||||
"len" => object::len,
|
||||
|
@ -577,9 +669,10 @@ pub async fn idiom(
|
|||
name,
|
||||
args.clone(),
|
||||
"no such method found for the string type",
|
||||
//
|
||||
"concat" => string::concat,
|
||||
"contains" => string::contains,
|
||||
"endsWith" => string::ends_with,
|
||||
"ends_with" => string::ends_with,
|
||||
"join" => string::join,
|
||||
"len" => string::len,
|
||||
"lowercase" => string::lowercase,
|
||||
|
@ -590,7 +683,7 @@ pub async fn idiom(
|
|||
"slice" => string::slice,
|
||||
"slug" => string::slug,
|
||||
"split" => string::split,
|
||||
"startsWith" => string::starts_with,
|
||||
"starts_with" => string::starts_with,
|
||||
"trim" => string::trim,
|
||||
"uppercase" => string::uppercase,
|
||||
"words" => string::words,
|
||||
|
@ -635,6 +728,7 @@ pub async fn idiom(
|
|||
name,
|
||||
args.clone(),
|
||||
"no such method found for the datetime type",
|
||||
//
|
||||
"ceil" => time::ceil,
|
||||
"day" => time::day,
|
||||
"floor" => time::floor,
|
||||
|
@ -711,79 +805,17 @@ pub async fn idiom(
|
|||
"to_string" => r#type::string,
|
||||
"to_uuid" => r#type::uuid,
|
||||
//
|
||||
"diff" => value::diff,
|
||||
"patch" => value::patch,
|
||||
"chain" => value::chain((stk, ctx, Some(opt), doc)).await,
|
||||
"diff" => value::diff((stk, ctx, Some(opt), doc)).await,
|
||||
"patch" => value::patch((stk, ctx, Some(opt), doc)).await,
|
||||
//
|
||||
"repeat" => array::repeat,
|
||||
//
|
||||
"chain" => shared::chain((stk, ctx, opt, doc)).await,
|
||||
)
|
||||
}
|
||||
v => v,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to run any asynchronous function.
|
||||
pub async fn asynchronous(
|
||||
stk: &mut Stk,
|
||||
ctx: &Context,
|
||||
opt: &Options,
|
||||
doc: Option<&CursorDoc>,
|
||||
name: &str,
|
||||
args: Vec<Value>,
|
||||
) -> Result<Value, Error> {
|
||||
// Wrappers return a function as opposed to a value so that the dispatch! method can always
|
||||
// perform a function call.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn cpu_intensive<R: Send + 'static>(
|
||||
function: impl FnOnce() -> R + Send + 'static,
|
||||
) -> impl FnOnce() -> executor::Task<R> {
|
||||
|| crate::exe::spawn(async move { function() })
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn cpu_intensive<R: Send + 'static>(
|
||||
function: impl FnOnce() -> R + Send + 'static,
|
||||
) -> impl FnOnce() -> std::future::Ready<R> {
|
||||
|| std::future::ready(function())
|
||||
}
|
||||
|
||||
dispatch!(
|
||||
name,
|
||||
args,
|
||||
"no such builtin function found",
|
||||
"array::map" => array::map((stk, ctx, opt, doc)).await,
|
||||
//
|
||||
"crypto::argon2::compare" => (cpu_intensive) crypto::argon2::cmp.await,
|
||||
"crypto::argon2::generate" => (cpu_intensive) crypto::argon2::gen.await,
|
||||
"crypto::bcrypt::compare" => (cpu_intensive) crypto::bcrypt::cmp.await,
|
||||
"crypto::bcrypt::generate" => (cpu_intensive) crypto::bcrypt::gen.await,
|
||||
"crypto::pbkdf2::compare" => (cpu_intensive) crypto::pbkdf2::cmp.await,
|
||||
"crypto::pbkdf2::generate" => (cpu_intensive) crypto::pbkdf2::gen.await,
|
||||
"crypto::scrypt::compare" => (cpu_intensive) crypto::scrypt::cmp.await,
|
||||
"crypto::scrypt::generate" => (cpu_intensive) crypto::scrypt::gen.await,
|
||||
//
|
||||
"http::head" => http::head(ctx).await,
|
||||
"http::get" => http::get(ctx).await,
|
||||
"http::put" => http::put(ctx).await,
|
||||
"http::post" => http::post(ctx).await,
|
||||
"http::patch" => http::patch(ctx).await,
|
||||
"http::delete" => http::delete(ctx).await,
|
||||
//
|
||||
"record::exists" => record::exists((stk, ctx, Some(opt), doc)).await,
|
||||
//
|
||||
"search::analyze" => search::analyze((stk, ctx, Some(opt))).await,
|
||||
"search::score" => search::score((ctx, doc)).await,
|
||||
"search::highlight" => search::highlight((ctx, doc)).await,
|
||||
"search::offsets" => search::offsets((ctx, doc)).await,
|
||||
//
|
||||
"sleep" => sleep::sleep(ctx).await,
|
||||
//
|
||||
"type::field" => r#type::field((stk, ctx, Some(opt), doc)).await,
|
||||
"type::fields" => r#type::fields((stk, ctx, Some(opt), doc)).await,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_execution_context<'a>(
|
||||
ctx: &'a Context,
|
||||
doc: Option<&'a CursorDoc>,
|
||||
|
@ -810,9 +842,6 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn implementations_are_present() {
|
||||
#[cfg(all(feature = "scripting", feature = "kv-mem"))]
|
||||
let excluded_from_scripting = &["array::map"];
|
||||
|
||||
// Accumulate and display all problems at once to avoid a test -> fix -> test -> fix cycle.
|
||||
let mut problems = Vec::new();
|
||||
|
||||
|
@ -820,7 +849,7 @@ mod tests {
|
|||
let fnc_mod = include_str!("mod.rs");
|
||||
|
||||
// Patch out idiom methods
|
||||
let re = Regex::new(r"(?ms)pub async fn idiom\(.*}\n+///").unwrap();
|
||||
let re = Regex::new(r"(?ms)pub async fn idiom\(.*}").unwrap();
|
||||
let fnc_no_idiom = re.replace(fnc_mod, "");
|
||||
|
||||
for line in fnc_no_idiom.lines() {
|
||||
|
@ -863,10 +892,6 @@ mod tests {
|
|||
{
|
||||
use crate::sql::Value;
|
||||
|
||||
if excluded_from_scripting.contains(&name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = name.replace("::", ".");
|
||||
let sql =
|
||||
format!("RETURN function() {{ return typeof surrealdb.functions.{name}; }}");
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use super::fut;
|
||||
use super::run;
|
||||
use crate::fnc::script::modules::impl_module_def;
|
||||
use js::prelude::Async;
|
||||
|
||||
mod sort;
|
||||
#[non_exhaustive]
|
||||
|
@ -9,8 +11,8 @@ impl_module_def!(
|
|||
Package,
|
||||
"array",
|
||||
"add" => run,
|
||||
"all" => run,
|
||||
"any" => run,
|
||||
"all" => fut Async,
|
||||
"any" => fut Async,
|
||||
"at" => run,
|
||||
"append" => run,
|
||||
"boolean_and" => run,
|
||||
|
@ -23,12 +25,17 @@ impl_module_def!(
|
|||
"concat" => run,
|
||||
"difference" => run,
|
||||
"distinct" => run,
|
||||
"every" => fut Async,
|
||||
"fill" => run,
|
||||
"filter_index" => run,
|
||||
"find_index" => run,
|
||||
"filter" => fut Async,
|
||||
"filter_index" => fut Async,
|
||||
"find" => fut Async,
|
||||
"find_index" => fut Async,
|
||||
"first" => run,
|
||||
"flatten" => run,
|
||||
"group" => run,
|
||||
"includes" => fut Async,
|
||||
"index_of" => fut Async,
|
||||
"insert" => run,
|
||||
"intersect" => run,
|
||||
"is_empty" => run,
|
||||
|
@ -39,6 +46,7 @@ impl_module_def!(
|
|||
"logical_and" => run,
|
||||
"logical_or" => run,
|
||||
"logical_xor" => run,
|
||||
"map" => fut Async,
|
||||
"matches" => run,
|
||||
"max" => run,
|
||||
"min" => run,
|
||||
|
@ -51,6 +59,7 @@ impl_module_def!(
|
|||
"reverse" => run,
|
||||
"shuffle" => run,
|
||||
"slice" => run,
|
||||
"some" => fut Async,
|
||||
"sort" => (sort::Package),
|
||||
"swap" => run,
|
||||
"transpose" => run,
|
||||
|
|
|
@ -16,7 +16,7 @@ impl_module_def!(
|
|||
"concat" => run,
|
||||
"contains" => run,
|
||||
"distance" => (distance::Package),
|
||||
"endsWith" => run,
|
||||
"ends_with" => run,
|
||||
"html" => (html::Package),
|
||||
"is" => (is::Package),
|
||||
"join" => run,
|
||||
|
@ -30,7 +30,7 @@ impl_module_def!(
|
|||
"slice" => run,
|
||||
"slug" => run,
|
||||
"split" => run,
|
||||
"startsWith" => run,
|
||||
"starts_with" => run,
|
||||
"trim" => run,
|
||||
"uppercase" => run,
|
||||
"words" => run,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::run;
|
||||
use super::fut;
|
||||
use crate::fnc::script::modules::impl_module_def;
|
||||
use js::prelude::Async;
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct Package;
|
||||
|
@ -7,6 +8,6 @@ pub struct Package;
|
|||
impl_module_def!(
|
||||
Package,
|
||||
"value",
|
||||
"diff" => run,
|
||||
"patch" => run
|
||||
"diff" => fut Async,
|
||||
"patch" => fut Async
|
||||
);
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
use reblessive::tree::Stk;
|
||||
|
||||
use crate::ctx::Context;
|
||||
use crate::dbs::Options;
|
||||
use crate::doc::CursorDoc;
|
||||
use crate::err::Error;
|
||||
use crate::sql::value::Value;
|
||||
use crate::sql::{Closure, Function};
|
||||
|
||||
pub async fn chain(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, &Options, Option<&CursorDoc>),
|
||||
(value, worker): (Value, Closure),
|
||||
) -> Result<Value, Error> {
|
||||
let fnc = Function::Anonymous(worker.into(), vec![value]);
|
||||
fnc.compute(stk, ctx, opt, doc).await
|
||||
}
|
|
@ -69,24 +69,25 @@ pub fn matches((val, regex): (String, Regex)) -> Result<Value, Error> {
|
|||
Ok(regex.0.is_match(&val).into())
|
||||
}
|
||||
|
||||
pub fn replace((val, old_or_regexp, new): (String, Value, String)) -> Result<Value, Error> {
|
||||
match old_or_regexp {
|
||||
Value::Strand(old) => {
|
||||
if new.len() > old.len() {
|
||||
let increase = new.len() - old.len();
|
||||
pub fn replace((val, search, replace): (String, Value, String)) -> Result<Value, Error> {
|
||||
match search {
|
||||
Value::Strand(search) => {
|
||||
if replace.len() > search.len() {
|
||||
let increase = replace.len() - search.len();
|
||||
limit(
|
||||
"string::replace",
|
||||
val.len().saturating_add(val.matches(&old.0).count().saturating_mul(increase)),
|
||||
val.len()
|
||||
.saturating_add(val.matches(&search.0).count().saturating_mul(increase)),
|
||||
)?;
|
||||
}
|
||||
Ok(val.replace(&old.0, &new).into())
|
||||
Ok(val.replace(&search.0, &replace).into())
|
||||
}
|
||||
Value::Regex(r) => Ok(r.0.replace_all(&val, new).into_owned().into()),
|
||||
Value::Regex(search) => Ok(search.0.replace_all(&val, replace).into_owned().into()),
|
||||
_ => Err(Error::InvalidArguments {
|
||||
name: "string::replace".to_string(),
|
||||
message: format!(
|
||||
"Argument 2 was the wrong type. Expected a string but found {}",
|
||||
old_or_regexp
|
||||
search
|
||||
),
|
||||
}),
|
||||
}
|
||||
|
|
|
@ -1,11 +1,46 @@
|
|||
use crate::ctx::Context;
|
||||
use crate::dbs::Options;
|
||||
use crate::doc::CursorDoc;
|
||||
use crate::err::Error;
|
||||
use crate::sql::{Idiom, Value};
|
||||
use crate::sql::idiom::Idiom;
|
||||
use crate::sql::value::Value;
|
||||
use crate::sql::{Closure, Function};
|
||||
use reblessive::tree::Stk;
|
||||
|
||||
pub fn diff((val1, val2): (Value, Value)) -> Result<Value, Error> {
|
||||
Ok(val1.diff(&val2, Idiom::default()).into())
|
||||
pub async fn chain(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(value, worker): (Value, Closure),
|
||||
) -> Result<Value, Error> {
|
||||
if let Some(opt) = opt {
|
||||
let fnc = Function::Anonymous(worker.into(), vec![value]);
|
||||
fnc.compute(stk, ctx, opt, doc).await
|
||||
} else {
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn patch((mut val, diff): (Value, Value)) -> Result<Value, Error> {
|
||||
pub async fn diff(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(val1, val2): (Value, Value),
|
||||
) -> Result<Value, Error> {
|
||||
if let Some(opt) = opt {
|
||||
let val1 = val1.compute(stk, ctx, opt, doc).await?;
|
||||
let val2 = val2.compute(stk, ctx, opt, doc).await?;
|
||||
Ok(val1.diff(&val2, Idiom::default()).into())
|
||||
} else {
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn patch(
|
||||
(stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
|
||||
(val, diff): (Value, Value),
|
||||
) -> Result<Value, Error> {
|
||||
if let Some(opt) = opt {
|
||||
let mut val = val.compute(stk, ctx, opt, doc).await?;
|
||||
val.patch(diff)?;
|
||||
Ok(val)
|
||||
} else {
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,12 @@ impl From<Vec<f64>> for Array {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Vec<usize>> for Array {
|
||||
fn from(v: Vec<usize>) -> Self {
|
||||
Self(v.into_iter().map(Value::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<&str>> for Array {
|
||||
fn from(v: Vec<&str>) -> Self {
|
||||
Self(v.into_iter().map(Value::from).collect())
|
||||
|
|
|
@ -500,6 +500,12 @@ impl From<Vec<f32>> for Value {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Vec<usize>> for Value {
|
||||
fn from(v: Vec<usize>) -> Self {
|
||||
Value::Array(Array::from(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Value>> for Value {
|
||||
fn from(v: Vec<Value>) -> Self {
|
||||
Value::Array(Array::from(v))
|
||||
|
@ -1057,6 +1063,11 @@ impl Value {
|
|||
matches!(self, Value::Thing(_))
|
||||
}
|
||||
|
||||
/// Check if this Value is a Closure
|
||||
pub fn is_closure(&self) -> bool {
|
||||
matches!(self, Value::Closure(_))
|
||||
}
|
||||
|
||||
/// Check if this Value is a Thing, and belongs to a certain table
|
||||
pub fn is_record_of_table(&self, table: String) -> bool {
|
||||
match self {
|
||||
|
|
|
@ -97,12 +97,17 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
|
|||
UniCase::ascii("array::concat") => PathKind::Function,
|
||||
UniCase::ascii("array::difference") => PathKind::Function,
|
||||
UniCase::ascii("array::distinct") => PathKind::Function,
|
||||
UniCase::ascii("array::every") => PathKind::Function,
|
||||
UniCase::ascii("array::fill") => PathKind::Function,
|
||||
UniCase::ascii("array::filter") => PathKind::Function,
|
||||
UniCase::ascii("array::filter_index") => PathKind::Function,
|
||||
UniCase::ascii("array::find") => PathKind::Function,
|
||||
UniCase::ascii("array::find_index") => PathKind::Function,
|
||||
UniCase::ascii("array::first") => PathKind::Function,
|
||||
UniCase::ascii("array::flatten") => PathKind::Function,
|
||||
UniCase::ascii("array::group") => PathKind::Function,
|
||||
UniCase::ascii("array::includes") => PathKind::Function,
|
||||
UniCase::ascii("array::index_of") => PathKind::Function,
|
||||
UniCase::ascii("array::insert") => PathKind::Function,
|
||||
UniCase::ascii("array::intersect") => PathKind::Function,
|
||||
UniCase::ascii("array::is_empty") => PathKind::Function,
|
||||
|
@ -125,13 +130,14 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
|
|||
UniCase::ascii("array::reverse") => PathKind::Function,
|
||||
UniCase::ascii("array::shuffle") => PathKind::Function,
|
||||
UniCase::ascii("array::slice") => PathKind::Function,
|
||||
UniCase::ascii("array::sort::asc") => PathKind::Function,
|
||||
UniCase::ascii("array::sort::desc") => PathKind::Function,
|
||||
UniCase::ascii("array::some") => PathKind::Function,
|
||||
UniCase::ascii("array::sort") => PathKind::Function,
|
||||
UniCase::ascii("array::swap") => PathKind::Function,
|
||||
UniCase::ascii("array::transpose") => PathKind::Function,
|
||||
UniCase::ascii("array::union") => PathKind::Function,
|
||||
UniCase::ascii("array::windows") => PathKind::Function,
|
||||
UniCase::ascii("array::sort::asc") => PathKind::Function,
|
||||
UniCase::ascii("array::sort::desc") => PathKind::Function,
|
||||
//
|
||||
UniCase::ascii("bytes::len") => PathKind::Function,
|
||||
//
|
||||
|
@ -284,7 +290,7 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
|
|||
//
|
||||
UniCase::ascii("string::concat") => PathKind::Function,
|
||||
UniCase::ascii("string::contains") => PathKind::Function,
|
||||
UniCase::ascii("string::endsWith") => PathKind::Function,
|
||||
UniCase::ascii("string::ends_with") => PathKind::Function,
|
||||
UniCase::ascii("string::join") => PathKind::Function,
|
||||
UniCase::ascii("string::len") => PathKind::Function,
|
||||
UniCase::ascii("string::lowercase") => PathKind::Function,
|
||||
|
@ -294,7 +300,7 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
|
|||
UniCase::ascii("string::slice") => PathKind::Function,
|
||||
UniCase::ascii("string::slug") => PathKind::Function,
|
||||
UniCase::ascii("string::split") => PathKind::Function,
|
||||
UniCase::ascii("string::startsWith") => PathKind::Function,
|
||||
UniCase::ascii("string::starts_with") => PathKind::Function,
|
||||
UniCase::ascii("string::trim") => PathKind::Function,
|
||||
UniCase::ascii("string::uppercase") => PathKind::Function,
|
||||
UniCase::ascii("string::words") => PathKind::Function,
|
||||
|
|
|
@ -166,12 +166,17 @@
|
|||
"array::concat("
|
||||
"array::difference("
|
||||
"array::distinct("
|
||||
"array::every("
|
||||
"array::fill("
|
||||
"array::filter("
|
||||
"array::filter_index("
|
||||
"array::find("
|
||||
"array::find_index("
|
||||
"array::first("
|
||||
"array::flatten("
|
||||
"array::group("
|
||||
"array::includes("
|
||||
"array::index_of("
|
||||
"array::insert("
|
||||
"array::intersect("
|
||||
"array::is_empty("
|
||||
|
@ -194,6 +199,7 @@
|
|||
"array::reverse("
|
||||
"array::shuffle("
|
||||
"array::slice("
|
||||
"array::some("
|
||||
"array::sort("
|
||||
"array::sort::asc("
|
||||
"array::sort::desc("
|
||||
|
@ -302,7 +308,7 @@
|
|||
"string::contains("
|
||||
"string::distance::hamming("
|
||||
"string::distance::levenshtein("
|
||||
"string::endsWith("
|
||||
"string::ends_with("
|
||||
"string::html::encode("
|
||||
"string::html::sanitize("
|
||||
"string::is::alphanum("
|
||||
|
@ -335,7 +341,7 @@
|
|||
"string::slice("
|
||||
"string::slug("
|
||||
"string::split("
|
||||
"string::startsWith("
|
||||
"string::starts_with("
|
||||
"string::trim("
|
||||
"string::uppercase("
|
||||
"string::words("
|
||||
|
|
|
@ -165,12 +165,17 @@
|
|||
"array::concat("
|
||||
"array::difference("
|
||||
"array::distinct("
|
||||
"array::every("
|
||||
"array::fill("
|
||||
"array::filter("
|
||||
"array::filter_index("
|
||||
"array::find("
|
||||
"array::find_index("
|
||||
"array::first("
|
||||
"array::flatten("
|
||||
"array::group("
|
||||
"array::includes("
|
||||
"array::index_of("
|
||||
"array::insert("
|
||||
"array::intersect("
|
||||
"array::is_empty("
|
||||
|
@ -193,6 +198,7 @@
|
|||
"array::reverse("
|
||||
"array::shuffle("
|
||||
"array::slice("
|
||||
"array::some("
|
||||
"array::sort("
|
||||
"array::sort::asc("
|
||||
"array::sort::desc("
|
||||
|
@ -300,7 +306,7 @@
|
|||
"string::contains("
|
||||
"string::distance::hamming("
|
||||
"string::distance::levenshtein("
|
||||
"string::endsWith("
|
||||
"string::ends_with("
|
||||
"string::html::encode("
|
||||
"string::html::sanitize("
|
||||
"string::is::alphanum("
|
||||
|
@ -333,7 +339,7 @@
|
|||
"string::slice("
|
||||
"string::slug("
|
||||
"string::split("
|
||||
"string::startsWith("
|
||||
"string::starts_with("
|
||||
"string::trim("
|
||||
"string::uppercase("
|
||||
"string::words("
|
||||
|
|
|
@ -305,23 +305,51 @@ async fn function_array_fill() -> Result<(), Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn function_array_filter() -> Result<(), Error> {
|
||||
let sql = r#"
|
||||
RETURN array::filter([5, 7, 9], |$v| $v > 6);
|
||||
RETURN array::filter(["hello_world", "goodbye world", "hello wombat", "goodbye world"], |$v| $v CONTAINS 'hello');
|
||||
RETURN array::filter(["nothing here"], |$v| $v == 3);
|
||||
"#;
|
||||
let desired_responses = ["[7, 9]", "['hello_world', 'hello wombat']", "[]"];
|
||||
test_queries(sql, &desired_responses).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn function_array_filter_index() -> Result<(), Error> {
|
||||
let sql = r#"RETURN array::filter_index([0, 1, 2], 1);
|
||||
RETURN array::filter_index([0, 0, 2], 0);
|
||||
RETURN array::filter_index(["hello_world", "hello world", "hello wombat", "hello world"], "hello world");
|
||||
RETURN array::filter_index(["nothing here"], 0);"#;
|
||||
let sql = r#"
|
||||
RETURN array::filter_index([0, 1, 2], 1);
|
||||
RETURN array::filter_index([0, 0, 2], 0);
|
||||
RETURN array::filter_index(["hello_world", "hello world", "hello wombat", "hello world"], "hello world");
|
||||
RETURN array::filter_index(["nothing here"], 0);
|
||||
"#;
|
||||
let desired_responses = ["[1]", "[0, 1]", "[1, 3]", "[]"];
|
||||
test_queries(sql, &desired_responses).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn function_array_find() -> Result<(), Error> {
|
||||
let sql = r#"
|
||||
RETURN array::find([5, 7, 9], |$v| $v >= 6);
|
||||
RETURN array::find(["hello world", null, true], |$v| $v != NULL);
|
||||
RETURN array::find([0, 1, 2], |$v| $v > 5);
|
||||
"#;
|
||||
let desired_responses = ["7", "'hello world'", "NONE"];
|
||||
test_queries(sql, &desired_responses).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn function_array_find_index() -> Result<(), Error> {
|
||||
let sql = r#"RETURN array::find_index([5, 6, 7], 7);
|
||||
RETURN array::find_index(["hello world", null, true], null);
|
||||
RETURN array::find_index([0, 1, 2], 3);"#;
|
||||
let desired_responses = ["2", "1", "null"];
|
||||
let sql = r#"
|
||||
RETURN array::find_index([5, 6, 7], 7);
|
||||
RETURN array::find_index(["hello world", null, true], null);
|
||||
RETURN array::find_index([0, 1, 2], 3);
|
||||
"#;
|
||||
let desired_responses = ["2", "1", "NONE"];
|
||||
test_queries(sql, &desired_responses).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3375,9 +3403,9 @@ async fn function_string_contains() -> Result<(), Error> {
|
|||
#[tokio::test]
|
||||
async fn function_string_ends_with() -> Result<(), Error> {
|
||||
let sql = r#"
|
||||
RETURN string::endsWith("", "");
|
||||
RETURN string::endsWith("", "test");
|
||||
RETURN string::endsWith("this is a test", "test");
|
||||
RETURN string::ends_with("", "");
|
||||
RETURN string::ends_with("", "test");
|
||||
RETURN string::ends_with("this is a test", "test");
|
||||
"#;
|
||||
let mut test = Test::new(sql).await?;
|
||||
//
|
||||
|
@ -4187,9 +4215,9 @@ async fn function_string_split() -> Result<(), Error> {
|
|||
#[tokio::test]
|
||||
async fn function_string_starts_with() -> Result<(), Error> {
|
||||
let sql = r#"
|
||||
RETURN string::startsWith("", "");
|
||||
RETURN string::startsWith("", "test");
|
||||
RETURN string::startsWith("test this string", "test");
|
||||
RETURN string::starts_with("", "");
|
||||
RETURN string::starts_with("", "test");
|
||||
RETURN string::starts_with("test this string", "test");
|
||||
"#;
|
||||
let mut test = Test::new(sql).await?;
|
||||
//
|
||||
|
|
Loading…
Reference in a new issue