Improve array function implementations, and improve value::diff() and value::patch() functions (#4612)

This commit is contained in:
Tobie Morgan Hitchcock 2024-08-27 16:34:40 +01:00 committed by GitHub
parent e9b36ba162
commit 93a9ba3cb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 443 additions and 187 deletions

View file

@ -18,7 +18,6 @@ use crate::sql::array::Windows;
use crate::sql::value::Value; use crate::sql::value::Value;
use crate::sql::Closure; use crate::sql::Closure;
use crate::sql::Function; use crate::sql::Function;
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use reblessive::tree::Stk; use reblessive::tree::Stk;
use std::mem::size_of_val; 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> { pub async fn all(
Ok(array.iter().all(Value::is_truthy).into()) (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> { pub async fn any(
Ok(array.iter().any(Value::is_truthy).into()) (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> { pub fn append((mut array, value): (Array, Value)) -> Result<Value, Error> {
@ -194,27 +237,119 @@ pub fn fill(
Ok(array.into()) Ok(array.into())
} }
pub fn filter_index((array, value): (Array, Value)) -> Result<Value, Error> { pub async fn filter(
Ok(array (stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
.iter() (array, check): (Array, Value),
.enumerate() ) -> Result<Value, Error> {
.filter_map(|(i, v)| { Ok(match check {
if *v == value { closure if closure.is_closure() => {
Some(Value::from(i)) 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 { } else {
None Value::None
} }
}) }
.collect::<Vec<_>>() value => array.into_iter().filter(|v: &Value| *v == value).collect::<Vec<_>>().into(),
.into()) })
} }
pub fn find_index((array, value): (Array, Value)) -> Result<Value, Error> { pub async fn filter_index(
Ok(array (stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
.iter() (array, value): (Array, Value),
.enumerate() ) -> Result<Value, Error> {
.find(|(_i, v)| **v == value) Ok(match value {
.map_or(Value::Null, |(i, _v)| i.into())) 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)| {
if *v == value {
Some(Value::from(i))
} else {
None
}
})
.collect::<Vec<_>>()
.into(),
})
}
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_map(|(i, v)| {
if *v == value {
Some(Value::from(i))
} else {
None
}
})
.into(),
})
} }
pub fn first((array,): (Array,)) -> Result<Value, Error> { 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( 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), (array, mapper): (Array, Closure),
) -> Result<Value, Error> { ) -> Result<Value, Error> {
let mut array = array; if let Some(opt) = opt {
for i in 0..array.len() { let mut res = Vec::with_capacity(array.len());
let v = array.get(i).unwrap(); for (i, val) in array.into_iter().enumerate() {
let fnc = Function::Anonymous(mapper.clone().into(), vec![v.to_owned(), i.into()]); let arg = val.compute(stk, ctx, opt, doc).await?;
array[i] = fnc.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> { pub fn matches((array, compare_val): (Array, Value)) -> Result<Value, Error> {

View file

@ -27,7 +27,6 @@ pub mod record;
pub mod script; pub mod script;
pub mod search; pub mod search;
pub mod session; pub mod session;
pub mod shared;
pub mod sleep; pub mod sleep;
pub mod string; pub mod string;
pub mod time; pub mod time;
@ -46,16 +45,28 @@ pub async fn run(
args: Vec<Value>, args: Vec<Value>,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
if name.eq("sleep") 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("http")
|| name.starts_with("type::field") || name.starts_with("search")
|| name.starts_with("type::fields")
|| name.starts_with("crypto::argon2") || name.starts_with("crypto::argon2")
|| name.starts_with("crypto::bcrypt") || name.starts_with("crypto::bcrypt")
|| name.starts_with("crypto::pbkdf2") || name.starts_with("crypto::pbkdf2")
|| name.starts_with("crypto::scrypt") || 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 stk.run(|stk| asynchronous(stk, ctx, opt, doc, name, args)).await
} else { } else {
@ -99,9 +110,8 @@ pub fn synchronous(
name, name,
args, args,
"no such builtin function found", "no such builtin function found",
//
"array::add" => array::add, "array::add" => array::add,
"array::all" => array::all,
"array::any" => array::any,
"array::append" => array::append, "array::append" => array::append,
"array::at" => array::at, "array::at" => array::at,
"array::boolean_and" => array::boolean_and, "array::boolean_and" => array::boolean_and,
@ -115,8 +125,6 @@ pub fn synchronous(
"array::difference" => array::difference, "array::difference" => array::difference,
"array::distinct" => array::distinct, "array::distinct" => array::distinct,
"array::fill" => array::fill, "array::fill" => array::fill,
"array::filter_index" => array::filter_index,
"array::find_index" => array::find_index,
"array::first" => array::first, "array::first" => array::first,
"array::flatten" => array::flatten, "array::flatten" => array::flatten,
"array::group" => array::group, "array::group" => array::group,
@ -276,7 +284,7 @@ pub fn synchronous(
// //
"string::concat" => string::concat, "string::concat" => string::concat,
"string::contains" => string::contains, "string::contains" => string::contains,
"string::endsWith" => string::ends_with, "string::ends_with" => string::ends_with,
"string::join" => string::join, "string::join" => string::join,
"string::len" => string::len, "string::len" => string::len,
"string::lowercase" => string::lowercase, "string::lowercase" => string::lowercase,
@ -287,7 +295,7 @@ pub fn synchronous(
"string::slice" => string::slice, "string::slice" => string::slice,
"string::slug" => string::slug, "string::slug" => string::slug,
"string::split" => string::split, "string::split" => string::split,
"string::startsWith" => string::starts_with, "string::starts_with" => string::starts_with,
"string::trim" => string::trim, "string::trim" => string::trim,
"string::uppercase" => string::uppercase, "string::uppercase" => string::uppercase,
"string::words" => string::words, "string::words" => string::words,
@ -395,9 +403,6 @@ pub fn synchronous(
"type::is::string" => r#type::is::string, "type::is::string" => r#type::is::string,
"type::is::uuid" => r#type::is::uuid, "type::is::uuid" => r#type::is::uuid,
// //
"value::diff" => value::diff,
"value::patch" => value::patch,
//
"vector::add" => vector::add, "vector::add" => vector::add,
"vector::angle" => vector::angle, "vector::angle" => vector::angle,
"vector::cross" => vector::cross, "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. /// Attempts to run any synchronous function.
pub async fn idiom( pub async fn idiom(
stk: &mut Stk, stk: &mut Stk,
@ -440,9 +520,10 @@ pub async fn idiom(
name, name,
args.clone(), args.clone(),
"no such method found for the array type", "no such method found for the array type",
//
"add" => array::add, "add" => array::add,
"all" => array::all, "all" => array::all((stk, ctx, Some(opt), doc)).await,
"any" => array::any, "any" => array::any((stk, ctx, Some(opt), doc)).await,
"append" => array::append, "append" => array::append,
"at" => array::at, "at" => array::at,
"boolean_and" => array::boolean_and, "boolean_and" => array::boolean_and,
@ -455,12 +536,17 @@ pub async fn idiom(
"concat" => array::concat, "concat" => array::concat,
"difference" => array::difference, "difference" => array::difference,
"distinct" => array::distinct, "distinct" => array::distinct,
"every" => array::all((stk, ctx, Some(opt), doc)).await,
"fill" => array::fill, "fill" => array::fill,
"filter_index" => array::filter_index, "filter" => array::filter((stk, ctx, Some(opt), doc)).await,
"find_index" => array::find_index, "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, "first" => array::first,
"flatten" => array::flatten, "flatten" => array::flatten,
"group" => array::group, "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, "insert" => array::insert,
"intersect" => array::intersect, "intersect" => array::intersect,
"is_empty" => array::is_empty, "is_empty" => array::is_empty,
@ -471,7 +557,7 @@ pub async fn idiom(
"logical_or" => array::logical_or, "logical_or" => array::logical_or,
"logical_xor" => array::logical_xor, "logical_xor" => array::logical_xor,
"matches" => array::matches, "matches" => array::matches,
"map" => array::map((stk, ctx, opt, doc)).await, "map" => array::map((stk, ctx, Some(opt), doc)).await,
"max" => array::max, "max" => array::max,
"min" => array::min, "min" => array::min,
"pop" => array::pop, "pop" => array::pop,
@ -481,6 +567,7 @@ pub async fn idiom(
"reverse" => array::reverse, "reverse" => array::reverse,
"shuffle" => array::shuffle, "shuffle" => array::shuffle,
"slice" => array::slice, "slice" => array::slice,
"some" => array::any((stk, ctx, Some(opt), doc)).await,
"sort" => array::sort, "sort" => array::sort,
"swap" => array::swap, "swap" => array::swap,
"transpose" => array::transpose, "transpose" => array::transpose,
@ -518,6 +605,7 @@ pub async fn idiom(
name, name,
args.clone(), args.clone(),
"no such method found for the bytes type", "no such method found for the bytes type",
//
"len" => bytes::len, "len" => bytes::len,
) )
} }
@ -526,6 +614,7 @@ pub async fn idiom(
name, name,
args.clone(), args.clone(),
"no such method found for the duration type", "no such method found for the duration type",
//
"days" => duration::days, "days" => duration::days,
"hours" => duration::hours, "hours" => duration::hours,
"micros" => duration::micros, "micros" => duration::micros,
@ -542,6 +631,7 @@ pub async fn idiom(
name, name,
args.clone(), args.clone(),
"no such method found for the geometry type", "no such method found for the geometry type",
//
"area" => geo::area, "area" => geo::area,
"bearing" => geo::bearing, "bearing" => geo::bearing,
"centroid" => geo::centroid, "centroid" => geo::centroid,
@ -555,6 +645,7 @@ pub async fn idiom(
name, name,
args.clone(), args.clone(),
"no such method found for the record type", "no such method found for the record type",
//
"exists" => record::exists((stk, ctx, Some(opt), doc)).await, "exists" => record::exists((stk, ctx, Some(opt), doc)).await,
"id" => record::id, "id" => record::id,
"table" => record::tb, "table" => record::tb,
@ -566,6 +657,7 @@ pub async fn idiom(
name, name,
args.clone(), args.clone(),
"no such method found for the object type", "no such method found for the object type",
//
"entries" => object::entries, "entries" => object::entries,
"keys" => object::keys, "keys" => object::keys,
"len" => object::len, "len" => object::len,
@ -577,9 +669,10 @@ pub async fn idiom(
name, name,
args.clone(), args.clone(),
"no such method found for the string type", "no such method found for the string type",
//
"concat" => string::concat, "concat" => string::concat,
"contains" => string::contains, "contains" => string::contains,
"endsWith" => string::ends_with, "ends_with" => string::ends_with,
"join" => string::join, "join" => string::join,
"len" => string::len, "len" => string::len,
"lowercase" => string::lowercase, "lowercase" => string::lowercase,
@ -590,7 +683,7 @@ pub async fn idiom(
"slice" => string::slice, "slice" => string::slice,
"slug" => string::slug, "slug" => string::slug,
"split" => string::split, "split" => string::split,
"startsWith" => string::starts_with, "starts_with" => string::starts_with,
"trim" => string::trim, "trim" => string::trim,
"uppercase" => string::uppercase, "uppercase" => string::uppercase,
"words" => string::words, "words" => string::words,
@ -635,6 +728,7 @@ pub async fn idiom(
name, name,
args.clone(), args.clone(),
"no such method found for the datetime type", "no such method found for the datetime type",
//
"ceil" => time::ceil, "ceil" => time::ceil,
"day" => time::day, "day" => time::day,
"floor" => time::floor, "floor" => time::floor,
@ -711,79 +805,17 @@ pub async fn idiom(
"to_string" => r#type::string, "to_string" => r#type::string,
"to_uuid" => r#type::uuid, "to_uuid" => r#type::uuid,
// //
"diff" => value::diff, "chain" => value::chain((stk, ctx, Some(opt), doc)).await,
"patch" => value::patch, "diff" => value::diff((stk, ctx, Some(opt), doc)).await,
"patch" => value::patch((stk, ctx, Some(opt), doc)).await,
// //
"repeat" => array::repeat, "repeat" => array::repeat,
//
"chain" => shared::chain((stk, ctx, opt, doc)).await,
) )
} }
v => v, 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>( fn get_execution_context<'a>(
ctx: &'a Context, ctx: &'a Context,
doc: Option<&'a CursorDoc>, doc: Option<&'a CursorDoc>,
@ -810,9 +842,6 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn implementations_are_present() { 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. // Accumulate and display all problems at once to avoid a test -> fix -> test -> fix cycle.
let mut problems = Vec::new(); let mut problems = Vec::new();
@ -820,7 +849,7 @@ mod tests {
let fnc_mod = include_str!("mod.rs"); let fnc_mod = include_str!("mod.rs");
// Patch out idiom methods // 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, ""); let fnc_no_idiom = re.replace(fnc_mod, "");
for line in fnc_no_idiom.lines() { for line in fnc_no_idiom.lines() {
@ -863,10 +892,6 @@ mod tests {
{ {
use crate::sql::Value; use crate::sql::Value;
if excluded_from_scripting.contains(&name) {
continue;
}
let name = name.replace("::", "."); let name = name.replace("::", ".");
let sql = let sql =
format!("RETURN function() {{ return typeof surrealdb.functions.{name}; }}"); format!("RETURN function() {{ return typeof surrealdb.functions.{name}; }}");

View file

@ -1,5 +1,7 @@
use super::fut;
use super::run; use super::run;
use crate::fnc::script::modules::impl_module_def; use crate::fnc::script::modules::impl_module_def;
use js::prelude::Async;
mod sort; mod sort;
#[non_exhaustive] #[non_exhaustive]
@ -9,8 +11,8 @@ impl_module_def!(
Package, Package,
"array", "array",
"add" => run, "add" => run,
"all" => run, "all" => fut Async,
"any" => run, "any" => fut Async,
"at" => run, "at" => run,
"append" => run, "append" => run,
"boolean_and" => run, "boolean_and" => run,
@ -23,12 +25,17 @@ impl_module_def!(
"concat" => run, "concat" => run,
"difference" => run, "difference" => run,
"distinct" => run, "distinct" => run,
"every" => fut Async,
"fill" => run, "fill" => run,
"filter_index" => run, "filter" => fut Async,
"find_index" => run, "filter_index" => fut Async,
"find" => fut Async,
"find_index" => fut Async,
"first" => run, "first" => run,
"flatten" => run, "flatten" => run,
"group" => run, "group" => run,
"includes" => fut Async,
"index_of" => fut Async,
"insert" => run, "insert" => run,
"intersect" => run, "intersect" => run,
"is_empty" => run, "is_empty" => run,
@ -39,6 +46,7 @@ impl_module_def!(
"logical_and" => run, "logical_and" => run,
"logical_or" => run, "logical_or" => run,
"logical_xor" => run, "logical_xor" => run,
"map" => fut Async,
"matches" => run, "matches" => run,
"max" => run, "max" => run,
"min" => run, "min" => run,
@ -51,6 +59,7 @@ impl_module_def!(
"reverse" => run, "reverse" => run,
"shuffle" => run, "shuffle" => run,
"slice" => run, "slice" => run,
"some" => fut Async,
"sort" => (sort::Package), "sort" => (sort::Package),
"swap" => run, "swap" => run,
"transpose" => run, "transpose" => run,

View file

@ -16,7 +16,7 @@ impl_module_def!(
"concat" => run, "concat" => run,
"contains" => run, "contains" => run,
"distance" => (distance::Package), "distance" => (distance::Package),
"endsWith" => run, "ends_with" => run,
"html" => (html::Package), "html" => (html::Package),
"is" => (is::Package), "is" => (is::Package),
"join" => run, "join" => run,
@ -30,7 +30,7 @@ impl_module_def!(
"slice" => run, "slice" => run,
"slug" => run, "slug" => run,
"split" => run, "split" => run,
"startsWith" => run, "starts_with" => run,
"trim" => run, "trim" => run,
"uppercase" => run, "uppercase" => run,
"words" => run, "words" => run,

View file

@ -1,5 +1,6 @@
use super::run; use super::fut;
use crate::fnc::script::modules::impl_module_def; use crate::fnc::script::modules::impl_module_def;
use js::prelude::Async;
#[non_exhaustive] #[non_exhaustive]
pub struct Package; pub struct Package;
@ -7,6 +8,6 @@ pub struct Package;
impl_module_def!( impl_module_def!(
Package, Package,
"value", "value",
"diff" => run, "diff" => fut Async,
"patch" => run "patch" => fut Async
); );

View file

@ -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
}

View file

@ -69,24 +69,25 @@ pub fn matches((val, regex): (String, Regex)) -> Result<Value, Error> {
Ok(regex.0.is_match(&val).into()) Ok(regex.0.is_match(&val).into())
} }
pub fn replace((val, old_or_regexp, new): (String, Value, String)) -> Result<Value, Error> { pub fn replace((val, search, replace): (String, Value, String)) -> Result<Value, Error> {
match old_or_regexp { match search {
Value::Strand(old) => { Value::Strand(search) => {
if new.len() > old.len() { if replace.len() > search.len() {
let increase = new.len() - old.len(); let increase = replace.len() - search.len();
limit( limit(
"string::replace", "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 { _ => Err(Error::InvalidArguments {
name: "string::replace".to_string(), name: "string::replace".to_string(),
message: format!( message: format!(
"Argument 2 was the wrong type. Expected a string but found {}", "Argument 2 was the wrong type. Expected a string but found {}",
old_or_regexp search
), ),
}), }),
} }

View file

@ -1,11 +1,46 @@
use crate::ctx::Context;
use crate::dbs::Options;
use crate::doc::CursorDoc;
use crate::err::Error; 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> { pub async fn chain(
Ok(val1.diff(&val2, Idiom::default()).into()) (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(
val.patch(diff)?; (stk, ctx, opt, doc): (&mut Stk, &Context, Option<&Options>, Option<&CursorDoc>),
Ok(val) (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)
}
} }

View file

@ -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 { impl From<Vec<&str>> for Array {
fn from(v: Vec<&str>) -> Self { fn from(v: Vec<&str>) -> Self {
Self(v.into_iter().map(Value::from).collect()) Self(v.into_iter().map(Value::from).collect())

View file

@ -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 { impl From<Vec<Value>> for Value {
fn from(v: Vec<Value>) -> Self { fn from(v: Vec<Value>) -> Self {
Value::Array(Array::from(v)) Value::Array(Array::from(v))
@ -1057,6 +1063,11 @@ impl Value {
matches!(self, Value::Thing(_)) 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 /// Check if this Value is a Thing, and belongs to a certain table
pub fn is_record_of_table(&self, table: String) -> bool { pub fn is_record_of_table(&self, table: String) -> bool {
match self { match self {

View file

@ -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::concat") => PathKind::Function,
UniCase::ascii("array::difference") => PathKind::Function, UniCase::ascii("array::difference") => PathKind::Function,
UniCase::ascii("array::distinct") => PathKind::Function, UniCase::ascii("array::distinct") => PathKind::Function,
UniCase::ascii("array::every") => PathKind::Function,
UniCase::ascii("array::fill") => PathKind::Function, UniCase::ascii("array::fill") => PathKind::Function,
UniCase::ascii("array::filter") => PathKind::Function,
UniCase::ascii("array::filter_index") => 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::find_index") => PathKind::Function,
UniCase::ascii("array::first") => PathKind::Function, UniCase::ascii("array::first") => PathKind::Function,
UniCase::ascii("array::flatten") => PathKind::Function, UniCase::ascii("array::flatten") => PathKind::Function,
UniCase::ascii("array::group") => 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::insert") => PathKind::Function,
UniCase::ascii("array::intersect") => PathKind::Function, UniCase::ascii("array::intersect") => PathKind::Function,
UniCase::ascii("array::is_empty") => 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::reverse") => PathKind::Function,
UniCase::ascii("array::shuffle") => PathKind::Function, UniCase::ascii("array::shuffle") => PathKind::Function,
UniCase::ascii("array::slice") => PathKind::Function, UniCase::ascii("array::slice") => PathKind::Function,
UniCase::ascii("array::sort::asc") => PathKind::Function, UniCase::ascii("array::some") => PathKind::Function,
UniCase::ascii("array::sort::desc") => PathKind::Function,
UniCase::ascii("array::sort") => PathKind::Function, UniCase::ascii("array::sort") => PathKind::Function,
UniCase::ascii("array::swap") => PathKind::Function, UniCase::ascii("array::swap") => PathKind::Function,
UniCase::ascii("array::transpose") => PathKind::Function, UniCase::ascii("array::transpose") => PathKind::Function,
UniCase::ascii("array::union") => PathKind::Function, UniCase::ascii("array::union") => PathKind::Function,
UniCase::ascii("array::windows") => 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, 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::concat") => PathKind::Function,
UniCase::ascii("string::contains") => 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::join") => PathKind::Function,
UniCase::ascii("string::len") => PathKind::Function, UniCase::ascii("string::len") => PathKind::Function,
UniCase::ascii("string::lowercase") => 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::slice") => PathKind::Function,
UniCase::ascii("string::slug") => PathKind::Function, UniCase::ascii("string::slug") => PathKind::Function,
UniCase::ascii("string::split") => 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::trim") => PathKind::Function,
UniCase::ascii("string::uppercase") => PathKind::Function, UniCase::ascii("string::uppercase") => PathKind::Function,
UniCase::ascii("string::words") => PathKind::Function, UniCase::ascii("string::words") => PathKind::Function,

View file

@ -166,12 +166,17 @@
"array::concat(" "array::concat("
"array::difference(" "array::difference("
"array::distinct(" "array::distinct("
"array::every("
"array::fill(" "array::fill("
"array::filter("
"array::filter_index(" "array::filter_index("
"array::find("
"array::find_index(" "array::find_index("
"array::first(" "array::first("
"array::flatten(" "array::flatten("
"array::group(" "array::group("
"array::includes("
"array::index_of("
"array::insert(" "array::insert("
"array::intersect(" "array::intersect("
"array::is_empty(" "array::is_empty("
@ -194,6 +199,7 @@
"array::reverse(" "array::reverse("
"array::shuffle(" "array::shuffle("
"array::slice(" "array::slice("
"array::some("
"array::sort(" "array::sort("
"array::sort::asc(" "array::sort::asc("
"array::sort::desc(" "array::sort::desc("
@ -302,7 +308,7 @@
"string::contains(" "string::contains("
"string::distance::hamming(" "string::distance::hamming("
"string::distance::levenshtein(" "string::distance::levenshtein("
"string::endsWith(" "string::ends_with("
"string::html::encode(" "string::html::encode("
"string::html::sanitize(" "string::html::sanitize("
"string::is::alphanum(" "string::is::alphanum("
@ -335,7 +341,7 @@
"string::slice(" "string::slice("
"string::slug(" "string::slug("
"string::split(" "string::split("
"string::startsWith(" "string::starts_with("
"string::trim(" "string::trim("
"string::uppercase(" "string::uppercase("
"string::words(" "string::words("

View file

@ -165,12 +165,17 @@
"array::concat(" "array::concat("
"array::difference(" "array::difference("
"array::distinct(" "array::distinct("
"array::every("
"array::fill(" "array::fill("
"array::filter("
"array::filter_index(" "array::filter_index("
"array::find("
"array::find_index(" "array::find_index("
"array::first(" "array::first("
"array::flatten(" "array::flatten("
"array::group(" "array::group("
"array::includes("
"array::index_of("
"array::insert(" "array::insert("
"array::intersect(" "array::intersect("
"array::is_empty(" "array::is_empty("
@ -193,6 +198,7 @@
"array::reverse(" "array::reverse("
"array::shuffle(" "array::shuffle("
"array::slice(" "array::slice("
"array::some("
"array::sort(" "array::sort("
"array::sort::asc(" "array::sort::asc("
"array::sort::desc(" "array::sort::desc("
@ -300,7 +306,7 @@
"string::contains(" "string::contains("
"string::distance::hamming(" "string::distance::hamming("
"string::distance::levenshtein(" "string::distance::levenshtein("
"string::endsWith(" "string::ends_with("
"string::html::encode(" "string::html::encode("
"string::html::sanitize(" "string::html::sanitize("
"string::is::alphanum(" "string::is::alphanum("
@ -333,7 +339,7 @@
"string::slice(" "string::slice("
"string::slug(" "string::slug("
"string::split(" "string::split("
"string::startsWith(" "string::starts_with("
"string::trim(" "string::trim("
"string::uppercase(" "string::uppercase("
"string::words(" "string::words("

View file

@ -305,23 +305,51 @@ async fn function_array_fill() -> Result<(), Error> {
Ok(()) 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] #[tokio::test]
async fn function_array_filter_index() -> Result<(), Error> { async fn function_array_filter_index() -> Result<(), Error> {
let sql = r#"RETURN array::filter_index([0, 1, 2], 1); let sql = r#"
RETURN array::filter_index([0, 0, 2], 0); RETURN array::filter_index([0, 1, 2], 1);
RETURN array::filter_index(["hello_world", "hello world", "hello wombat", "hello world"], "hello world"); RETURN array::filter_index([0, 0, 2], 0);
RETURN array::filter_index(["nothing here"], 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]", "[]"]; let desired_responses = ["[1]", "[0, 1]", "[1, 3]", "[]"];
test_queries(sql, &desired_responses).await?; test_queries(sql, &desired_responses).await?;
Ok(()) 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] #[tokio::test]
async fn function_array_find_index() -> Result<(), Error> { async fn function_array_find_index() -> Result<(), Error> {
let sql = r#"RETURN array::find_index([5, 6, 7], 7); let sql = r#"
RETURN array::find_index(["hello world", null, true], null); RETURN array::find_index([5, 6, 7], 7);
RETURN array::find_index([0, 1, 2], 3);"#; RETURN array::find_index(["hello world", null, true], null);
let desired_responses = ["2", "1", "null"]; RETURN array::find_index([0, 1, 2], 3);
"#;
let desired_responses = ["2", "1", "NONE"];
test_queries(sql, &desired_responses).await?; test_queries(sql, &desired_responses).await?;
Ok(()) Ok(())
} }
@ -3375,9 +3403,9 @@ async fn function_string_contains() -> Result<(), Error> {
#[tokio::test] #[tokio::test]
async fn function_string_ends_with() -> Result<(), Error> { async fn function_string_ends_with() -> Result<(), Error> {
let sql = r#" let sql = r#"
RETURN string::endsWith("", ""); RETURN string::ends_with("", "");
RETURN string::endsWith("", "test"); RETURN string::ends_with("", "test");
RETURN string::endsWith("this is a test", "test"); RETURN string::ends_with("this is a test", "test");
"#; "#;
let mut test = Test::new(sql).await?; let mut test = Test::new(sql).await?;
// //
@ -4187,9 +4215,9 @@ async fn function_string_split() -> Result<(), Error> {
#[tokio::test] #[tokio::test]
async fn function_string_starts_with() -> Result<(), Error> { async fn function_string_starts_with() -> Result<(), Error> {
let sql = r#" let sql = r#"
RETURN string::startsWith("", ""); RETURN string::starts_with("", "");
RETURN string::startsWith("", "test"); RETURN string::starts_with("", "test");
RETURN string::startsWith("test this string", "test"); RETURN string::starts_with("test this string", "test");
"#; "#;
let mut test = Test::new(sql).await?; let mut test = Test::new(sql).await?;
// //