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::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> {

View file

@ -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}; }}");

View file

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

View file

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

View file

@ -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
);

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())
}
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
),
}),
}

View file

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

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 {
fn from(v: Vec<&str>) -> Self {
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 {
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 {

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::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,

View file

@ -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("

View file

@ -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("

View file

@ -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?;
//