Method chaining (#4469)
This commit is contained in:
parent
82349eb557
commit
c5a6e08225
19 changed files with 628 additions and 131 deletions
core/src
fnc
sql
syn/parser
lib
|
@ -65,7 +65,7 @@ pub async fn run(
|
|||
/// it is `async`. Finally, the path may be prefixed by a parenthesized wrapper function e.g.
|
||||
/// `cpu_intensive`.
|
||||
macro_rules! dispatch {
|
||||
($name: ident, $args: ident, $($function_name: literal => $(($wrapper: tt))* $($function_path: ident)::+ $(($ctx_arg: expr))* $(.$await:tt)*,)+) => {
|
||||
($name: ident, $args: expr, $message: expr, $($function_name: literal => $(($wrapper: tt))* $($function_path: ident)::+ $(($ctx_arg: expr))* $(.$await:tt)*,)+) => {
|
||||
{
|
||||
match $name {
|
||||
$($function_name => {
|
||||
|
@ -76,7 +76,7 @@ macro_rules! dispatch {
|
|||
_ => {
|
||||
return Err($crate::err::Error::InvalidFunction{
|
||||
name: String::from($name),
|
||||
message: "no such builtin function".to_string()
|
||||
message: $message.to_string()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ pub fn synchronous(
|
|||
dispatch!(
|
||||
name,
|
||||
args,
|
||||
"no such builtin function found",
|
||||
"array::add" => array::add,
|
||||
"array::all" => array::all,
|
||||
"array::any" => array::any,
|
||||
|
@ -344,17 +345,21 @@ pub fn synchronous(
|
|||
"time::from::unix" => time::from::unix,
|
||||
//
|
||||
"type::bool" => r#type::bool,
|
||||
"type::bytes" => r#type::bytes,
|
||||
"type::datetime" => r#type::datetime,
|
||||
"type::decimal" => r#type::decimal,
|
||||
"type::duration" => r#type::duration,
|
||||
"type::float" => r#type::float,
|
||||
"type::geometry" => r#type::geometry,
|
||||
"type::int" => r#type::int,
|
||||
"type::number" => r#type::number,
|
||||
"type::point" => r#type::point,
|
||||
"type::range" => r#type::range,
|
||||
"type::record" => r#type::record,
|
||||
"type::string" => r#type::string,
|
||||
"type::table" => r#type::table,
|
||||
"type::thing" => r#type::thing,
|
||||
"type::range" => r#type::range,
|
||||
"type::uuid" => r#type::uuid,
|
||||
"type::is::array" => r#type::is::array,
|
||||
"type::is::bool" => r#type::is::bool,
|
||||
"type::is::bytes" => r#type::is::bytes,
|
||||
|
@ -403,6 +408,288 @@ pub fn synchronous(
|
|||
)
|
||||
}
|
||||
|
||||
/// Attempts to run any synchronous function.
|
||||
pub fn idiom(
|
||||
ctx: &Context<'_>,
|
||||
doc: Option<&CursorDoc<'_>>,
|
||||
value: Value,
|
||||
name: &str,
|
||||
args: Vec<Value>,
|
||||
) -> Result<Value, Error> {
|
||||
let args = [vec![value.clone()], args].concat();
|
||||
let specific = match value {
|
||||
Value::Array(_) => {
|
||||
dispatch!(
|
||||
name,
|
||||
args.clone(),
|
||||
"no such method found for the array type",
|
||||
"add" => array::add,
|
||||
"all" => array::all,
|
||||
"any" => array::any,
|
||||
"append" => array::append,
|
||||
"at" => array::at,
|
||||
"boolean_and" => array::boolean_and,
|
||||
"boolean_not" => array::boolean_not,
|
||||
"boolean_or" => array::boolean_or,
|
||||
"boolean_xor" => array::boolean_xor,
|
||||
"clump" => array::clump,
|
||||
"combine" => array::combine,
|
||||
"complement" => array::complement,
|
||||
"concat" => array::concat,
|
||||
"difference" => array::difference,
|
||||
"distinct" => array::distinct,
|
||||
"filter_index" => array::filter_index,
|
||||
"find_index" => array::find_index,
|
||||
"first" => array::first,
|
||||
"flatten" => array::flatten,
|
||||
"group" => array::group,
|
||||
"insert" => array::insert,
|
||||
"intersect" => array::intersect,
|
||||
"join" => array::join,
|
||||
"last" => array::last,
|
||||
"len" => array::len,
|
||||
"logical_and" => array::logical_and,
|
||||
"logical_or" => array::logical_or,
|
||||
"logical_xor" => array::logical_xor,
|
||||
"matches" => array::matches,
|
||||
"max" => array::max,
|
||||
"min" => array::min,
|
||||
"pop" => array::pop,
|
||||
"prepend" => array::prepend,
|
||||
"push" => array::push,
|
||||
"remove" => array::remove,
|
||||
"reverse" => array::reverse,
|
||||
"shuffle" => array::shuffle,
|
||||
"slice" => array::slice,
|
||||
"sort" => array::sort,
|
||||
"transpose" => array::transpose,
|
||||
"union" => array::union,
|
||||
"sort_asc" => array::sort::asc,
|
||||
"sort_desc" => array::sort::desc,
|
||||
"windows" => array::windows,
|
||||
//
|
||||
"vector_add" => vector::add,
|
||||
"vector_angle" => vector::angle,
|
||||
"vector_cross" => vector::cross,
|
||||
"vector_dot" => vector::dot,
|
||||
"vector_divide" => vector::divide,
|
||||
"vector_magnitude" => vector::magnitude,
|
||||
"vector_multiply" => vector::multiply,
|
||||
"vector_normalize" => vector::normalize,
|
||||
"vector_project" => vector::project,
|
||||
"vector_subtract" => vector::subtract,
|
||||
"vector_distance_chebyshev" => vector::distance::chebyshev,
|
||||
"vector_distance_euclidean" => vector::distance::euclidean,
|
||||
"vector_distance_hamming" => vector::distance::hamming,
|
||||
"vector_distance_knn" => vector::distance::knn((ctx, doc)),
|
||||
"vector_distance_mahalanobis" => vector::distance::mahalanobis,
|
||||
"vector_distance_manhattan" => vector::distance::manhattan,
|
||||
"vector_distance_minkowski" => vector::distance::minkowski,
|
||||
"vector_similarity_cosine" => vector::similarity::cosine,
|
||||
"vector_similarity_jaccard" => vector::similarity::jaccard,
|
||||
"vector_similarity_pearson" => vector::similarity::pearson,
|
||||
"vector_similarity_spearman" => vector::similarity::spearman,
|
||||
)
|
||||
}
|
||||
Value::Bytes(_) => {
|
||||
dispatch!(
|
||||
name,
|
||||
args.clone(),
|
||||
"no such method found for the bytes type",
|
||||
"len" => bytes::len,
|
||||
)
|
||||
}
|
||||
Value::Duration(_) => {
|
||||
dispatch!(
|
||||
name,
|
||||
args.clone(),
|
||||
"no such method found for the duration type",
|
||||
"days" => duration::days,
|
||||
"hours" => duration::hours,
|
||||
"micros" => duration::micros,
|
||||
"millis" => duration::millis,
|
||||
"mins" => duration::mins,
|
||||
"nanos" => duration::nanos,
|
||||
"secs" => duration::secs,
|
||||
"weeks" => duration::weeks,
|
||||
"years" => duration::years,
|
||||
)
|
||||
}
|
||||
Value::Geometry(_) => {
|
||||
dispatch!(
|
||||
name,
|
||||
args.clone(),
|
||||
"no such method found for the geometry type",
|
||||
"area" => geo::area,
|
||||
"bearing" => geo::bearing,
|
||||
"centroid" => geo::centroid,
|
||||
"distance" => geo::distance,
|
||||
"hash::decode" => geo::hash::decode,
|
||||
"hash::encode" => geo::hash::encode,
|
||||
)
|
||||
}
|
||||
Value::Thing(_) => {
|
||||
dispatch!(
|
||||
name,
|
||||
args.clone(),
|
||||
"no such method found for the record type",
|
||||
"id" => meta::id,
|
||||
"table" => meta::tb,
|
||||
"tb" => meta::tb,
|
||||
)
|
||||
}
|
||||
Value::Object(_) => {
|
||||
dispatch!(
|
||||
name,
|
||||
args.clone(),
|
||||
"no such method found for the object type",
|
||||
"entries" => object::entries,
|
||||
"keys" => object::keys,
|
||||
"len" => object::len,
|
||||
"values" => object::values,
|
||||
)
|
||||
}
|
||||
Value::Strand(_) => {
|
||||
dispatch!(
|
||||
name,
|
||||
args.clone(),
|
||||
"no such method found for the string type",
|
||||
"concat" => string::concat,
|
||||
"contains" => string::contains,
|
||||
"endsWith" => string::ends_with,
|
||||
"join" => string::join,
|
||||
"len" => string::len,
|
||||
"lowercase" => string::lowercase,
|
||||
"matches" => string::matches,
|
||||
"repeat" => string::repeat,
|
||||
"replace" => string::replace,
|
||||
"reverse" => string::reverse,
|
||||
"slice" => string::slice,
|
||||
"slug" => string::slug,
|
||||
"split" => string::split,
|
||||
"startsWith" => string::starts_with,
|
||||
"trim" => string::trim,
|
||||
"uppercase" => string::uppercase,
|
||||
"words" => string::words,
|
||||
"distance_hamming" => string::distance::hamming,
|
||||
"distance_levenshtein" => string::distance::levenshtein,
|
||||
"html_encode" => string::html::encode,
|
||||
"html_sanitize" => string::html::sanitize,
|
||||
"is_alphanum" => string::is::alphanum,
|
||||
"is_alpha" => string::is::alpha,
|
||||
"is_ascii" => string::is::ascii,
|
||||
"is_datetime" => string::is::datetime,
|
||||
"is_domain" => string::is::domain,
|
||||
"is_email" => string::is::email,
|
||||
"is_hexadecimal" => string::is::hexadecimal,
|
||||
"is_ip" => string::is::ip,
|
||||
"is_ipv4" => string::is::ipv4,
|
||||
"is_ipv6" => string::is::ipv6,
|
||||
"is_latitude" => string::is::latitude,
|
||||
"is_longitude" => string::is::longitude,
|
||||
"is_numeric" => string::is::numeric,
|
||||
"is_semver" => string::is::semver,
|
||||
"is_url" => string::is::url,
|
||||
"is_uuid" => string::is::uuid,
|
||||
"is_record" => string::is::record,
|
||||
"similarity_fuzzy" => string::similarity::fuzzy,
|
||||
"similarity_jaro" => string::similarity::jaro,
|
||||
"similarity_smithwaterman" => string::similarity::smithwaterman,
|
||||
"semver_compare" => string::semver::compare,
|
||||
"semver_major" => string::semver::major,
|
||||
"semver_minor" => string::semver::minor,
|
||||
"semver_patch" => string::semver::patch,
|
||||
"semver_inc::major" => string::semver::inc::major,
|
||||
"semver_inc::minor" => string::semver::inc::minor,
|
||||
"semver_inc::patch" => string::semver::inc::patch,
|
||||
"semver_set::major" => string::semver::set::major,
|
||||
"semver_set::minor" => string::semver::set::minor,
|
||||
"semver_set::patch" => string::semver::set::patch,
|
||||
)
|
||||
}
|
||||
Value::Datetime(_) => {
|
||||
dispatch!(
|
||||
name,
|
||||
args.clone(),
|
||||
"no such method found for the datetime type",
|
||||
"time_ceil" => time::ceil,
|
||||
"time_day" => time::day,
|
||||
"time_floor" => time::floor,
|
||||
"time_format" => time::format,
|
||||
"time_group" => time::group,
|
||||
"time_hour" => time::hour,
|
||||
"time_minute" => time::minute,
|
||||
"time_month" => time::month,
|
||||
"time_nano" => time::nano,
|
||||
"time_micros" => time::micros,
|
||||
"time_millis" => time::millis,
|
||||
"time_round" => time::round,
|
||||
"time_second" => time::second,
|
||||
"time_unix" => time::unix,
|
||||
"time_wday" => time::wday,
|
||||
"time_week" => time::week,
|
||||
"time_yday" => time::yday,
|
||||
"time_year" => time::year,
|
||||
)
|
||||
}
|
||||
_ => Err(Error::InvalidFunction {
|
||||
name: "".into(),
|
||||
message: "".into(),
|
||||
}),
|
||||
};
|
||||
|
||||
match specific {
|
||||
Err(Error::InvalidFunction {
|
||||
..
|
||||
}) => {
|
||||
let message = format!("no such method found for the {} type", value.kindof());
|
||||
dispatch!(
|
||||
name,
|
||||
args,
|
||||
message,
|
||||
"is_array" => r#type::is::array,
|
||||
"is_bool" => r#type::is::bool,
|
||||
"is_bytes" => r#type::is::bytes,
|
||||
"is_collection" => r#type::is::collection,
|
||||
"is_datetime" => r#type::is::datetime,
|
||||
"is_decimal" => r#type::is::decimal,
|
||||
"is_duration" => r#type::is::duration,
|
||||
"is_float" => r#type::is::float,
|
||||
"is_geometry" => r#type::is::geometry,
|
||||
"is_int" => r#type::is::int,
|
||||
"is_line" => r#type::is::line,
|
||||
"is_none" => r#type::is::none,
|
||||
"is_null" => r#type::is::null,
|
||||
"is_multiline" => r#type::is::multiline,
|
||||
"is_multipoint" => r#type::is::multipoint,
|
||||
"is_multipolygon" => r#type::is::multipolygon,
|
||||
"is_number" => r#type::is::number,
|
||||
"is_object" => r#type::is::object,
|
||||
"is_point" => r#type::is::point,
|
||||
"is_polygon" => r#type::is::polygon,
|
||||
"is_record" => r#type::is::record,
|
||||
"is_string" => r#type::is::string,
|
||||
"is_uuid" => r#type::is::uuid,
|
||||
//
|
||||
"to_bool" => r#type::bool,
|
||||
"to_bytes" => r#type::bytes,
|
||||
"to_datetime" => r#type::datetime,
|
||||
"to_decimal" => r#type::decimal,
|
||||
"to_duration" => r#type::duration,
|
||||
"to_float" => r#type::float,
|
||||
"to_geometry" => r#type::geometry,
|
||||
"to_int" => r#type::int,
|
||||
"to_number" => r#type::number,
|
||||
"to_point" => r#type::point,
|
||||
"to_record" => r#type::record,
|
||||
"to_string" => r#type::string,
|
||||
"to_uuid" => r#type::uuid,
|
||||
)
|
||||
}
|
||||
v => v,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to run any asynchronous function.
|
||||
pub async fn asynchronous(
|
||||
stk: &mut Stk,
|
||||
|
@ -431,6 +718,7 @@ pub async fn asynchronous(
|
|||
dispatch!(
|
||||
name,
|
||||
args,
|
||||
"no such builtin function found",
|
||||
"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,
|
||||
|
@ -477,6 +765,8 @@ fn get_execution_context<'a>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use regex::Regex;
|
||||
|
||||
#[cfg(all(feature = "scripting", feature = "kv-mem"))]
|
||||
use crate::dbs::Capabilities;
|
||||
use crate::sql::{statements::OutputStatement, Function, Query, Statement, Value};
|
||||
|
@ -488,7 +778,12 @@ mod tests {
|
|||
|
||||
// Read the source code of this file
|
||||
let fnc_mod = include_str!("mod.rs");
|
||||
for line in fnc_mod.lines() {
|
||||
|
||||
// Patch out idiom methods
|
||||
let re = Regex::new(r"(?ms)pub fn idiom\(.*}\n+///").unwrap();
|
||||
let fnc_no_idiom = re.replace(fnc_mod, "");
|
||||
|
||||
for line in fnc_no_idiom.lines() {
|
||||
if !(line.contains("=>")
|
||||
&& (line.trim().starts_with('"') || line.trim().ends_with(',')))
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@ impl_module_def!(
|
|||
Package,
|
||||
"type",
|
||||
"bool" => run,
|
||||
"bytes" => run,
|
||||
"datetime" => run,
|
||||
"decimal" => run,
|
||||
"duration" => run,
|
||||
|
@ -26,5 +27,8 @@ impl_module_def!(
|
|||
"string" => run,
|
||||
"table" => run,
|
||||
"thing" => run,
|
||||
"range" => run
|
||||
"range" => run,
|
||||
"record" => run,
|
||||
"uuid" => run,
|
||||
"geometry" => run
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::err::Error;
|
|||
use crate::sql::table::Table;
|
||||
use crate::sql::thing::Thing;
|
||||
use crate::sql::value::Value;
|
||||
use crate::sql::{Id, Range, Strand};
|
||||
use crate::sql::{Id, Kind, Range, Strand};
|
||||
use crate::syn;
|
||||
use reblessive::tree::Stk;
|
||||
|
||||
|
@ -15,6 +15,10 @@ pub fn bool((val,): (Value,)) -> Result<Value, Error> {
|
|||
val.convert_to_bool().map(Value::from)
|
||||
}
|
||||
|
||||
pub fn bytes((val,): (Value,)) -> Result<Value, Error> {
|
||||
val.convert_to_bytes().map(Value::from)
|
||||
}
|
||||
|
||||
pub fn datetime((val,): (Value,)) -> Result<Value, Error> {
|
||||
val.convert_to_datetime().map(Value::from)
|
||||
}
|
||||
|
@ -71,6 +75,10 @@ pub fn float((val,): (Value,)) -> Result<Value, Error> {
|
|||
val.convert_to_float().map(Value::from)
|
||||
}
|
||||
|
||||
pub fn geometry((val,): (Value,)) -> Result<Value, Error> {
|
||||
val.convert_to_geometry().map(Value::from)
|
||||
}
|
||||
|
||||
pub fn int((val,): (Value,)) -> Result<Value, Error> {
|
||||
val.convert_to_int().map(Value::from)
|
||||
}
|
||||
|
@ -83,57 +91,6 @@ pub fn point((val,): (Value,)) -> Result<Value, Error> {
|
|||
val.convert_to_point().map(Value::from)
|
||||
}
|
||||
|
||||
pub fn string((val,): (Value,)) -> Result<Value, Error> {
|
||||
val.convert_to_strand().map(Value::from)
|
||||
}
|
||||
|
||||
pub fn table((val,): (Value,)) -> Result<Value, Error> {
|
||||
Ok(Value::Table(Table(match val {
|
||||
Value::Thing(t) => t.tb,
|
||||
v => v.as_string(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn thing((arg1, arg2): (Value, Option<Value>)) -> Result<Value, Error> {
|
||||
match (arg1, arg2) {
|
||||
// Empty table name
|
||||
(Value::Strand(arg1), _) if arg1.is_empty() => Err(Error::TbInvalid {
|
||||
value: arg1.as_string(),
|
||||
}),
|
||||
|
||||
// Empty ID part
|
||||
(_, Some(Value::Strand(arg2))) if arg2.is_empty() => Err(Error::IdInvalid {
|
||||
value: arg2.as_string(),
|
||||
}),
|
||||
|
||||
// Handle second argument
|
||||
(arg1, Some(arg2)) => Ok(Value::Thing(Thing {
|
||||
tb: arg1.as_string(),
|
||||
id: match arg2 {
|
||||
Value::Thing(v) => v.id,
|
||||
Value::Array(v) => v.into(),
|
||||
Value::Object(v) => v.into(),
|
||||
Value::Number(v) => v.into(),
|
||||
v => v.as_string().into(),
|
||||
},
|
||||
})),
|
||||
|
||||
// No second argument passed
|
||||
(arg1, _) => Ok(match arg1 {
|
||||
Value::Thing(v) => Ok(v),
|
||||
Value::Strand(v) => Thing::try_from(v.as_str()).map_err(move |_| Error::ConvertTo {
|
||||
from: Value::Strand(v),
|
||||
into: "record".into(),
|
||||
}),
|
||||
v => Err(Error::ConvertTo {
|
||||
from: v,
|
||||
into: "record".into(),
|
||||
}),
|
||||
}?
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range(args: Vec<Value>) -> Result<Value, Error> {
|
||||
if args.len() > 4 || args.is_empty() {
|
||||
return Err(Error::InvalidArguments {
|
||||
|
@ -221,6 +178,79 @@ pub fn range(args: Vec<Value>) -> Result<Value, Error> {
|
|||
.into())
|
||||
}
|
||||
|
||||
pub fn record((rid, tb): (Value, Option<Value>)) -> Result<Value, Error> {
|
||||
match tb {
|
||||
Some(Value::Strand(Strand(tb)) | Value::Table(Table(tb))) if tb.is_empty() => {
|
||||
Err(Error::TbInvalid {
|
||||
value: tb,
|
||||
})
|
||||
}
|
||||
Some(Value::Strand(Strand(tb)) | Value::Table(Table(tb))) => {
|
||||
rid.convert_to(&Kind::Record(vec![tb.into()]))
|
||||
}
|
||||
Some(_) => Err(Error::InvalidArguments {
|
||||
name: "type::record".into(),
|
||||
message: "The second argument must be a table name or a string.".into(),
|
||||
}),
|
||||
None => rid.convert_to(&Kind::Record(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string((val,): (Value,)) -> Result<Value, Error> {
|
||||
val.convert_to_strand().map(Value::from)
|
||||
}
|
||||
|
||||
pub fn table((val,): (Value,)) -> Result<Value, Error> {
|
||||
Ok(Value::Table(Table(match val {
|
||||
Value::Thing(t) => t.tb,
|
||||
v => v.as_string(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn thing((arg1, arg2): (Value, Option<Value>)) -> Result<Value, Error> {
|
||||
match (arg1, arg2) {
|
||||
// Empty table name
|
||||
(Value::Strand(arg1), _) if arg1.is_empty() => Err(Error::TbInvalid {
|
||||
value: arg1.as_string(),
|
||||
}),
|
||||
|
||||
// Empty ID part
|
||||
(_, Some(Value::Strand(arg2))) if arg2.is_empty() => Err(Error::IdInvalid {
|
||||
value: arg2.as_string(),
|
||||
}),
|
||||
|
||||
// Handle second argument
|
||||
(arg1, Some(arg2)) => Ok(Value::Thing(Thing {
|
||||
tb: arg1.as_string(),
|
||||
id: match arg2 {
|
||||
Value::Thing(v) => v.id,
|
||||
Value::Array(v) => v.into(),
|
||||
Value::Object(v) => v.into(),
|
||||
Value::Number(v) => v.into(),
|
||||
v => v.as_string().into(),
|
||||
},
|
||||
})),
|
||||
|
||||
// No second argument passed
|
||||
(arg1, _) => Ok(match arg1 {
|
||||
Value::Thing(v) => Ok(v),
|
||||
Value::Strand(v) => Thing::try_from(v.as_str()).map_err(move |_| Error::ConvertTo {
|
||||
from: Value::Strand(v),
|
||||
into: "record".into(),
|
||||
}),
|
||||
v => Err(Error::ConvertTo {
|
||||
from: v,
|
||||
into: "record".into(),
|
||||
}),
|
||||
}?
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uuid((val,): (Value,)) -> Result<Value, Error> {
|
||||
val.convert_to_uuid().map(Value::from)
|
||||
}
|
||||
|
||||
pub mod is {
|
||||
use crate::err::Error;
|
||||
use crate::sql::table::Table;
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::err::Error;
|
|||
use crate::sql::statements::info::InfoStructure;
|
||||
use crate::sql::{
|
||||
fmt::{fmt_separated_by, Fmt},
|
||||
part::Next,
|
||||
part::{Next, NextMethod},
|
||||
paths::{ID, IN, META, OUT},
|
||||
Part, Value,
|
||||
};
|
||||
|
@ -185,7 +185,13 @@ impl Idiom {
|
|||
v.doc.get(stk, ctx, opt, doc, self).await?.compute(stk, ctx, opt, doc).await
|
||||
}
|
||||
// There isn't any document
|
||||
None => Ok(Value::None),
|
||||
None => {
|
||||
Value::None
|
||||
.get(stk, ctx, opt, doc, self.next_method())
|
||||
.await?
|
||||
.compute(stk, ctx, opt, doc)
|
||||
.await
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,6 +149,30 @@ impl<'a> Next<'a> for &'a [Part] {
|
|||
|
||||
// ------------------------------
|
||||
|
||||
pub trait NextMethod<'a> {
|
||||
fn next_method(&'a self) -> &[Part];
|
||||
}
|
||||
|
||||
impl<'a> NextMethod<'a> for &'a [Part] {
|
||||
fn next_method(&'a self) -> &'a [Part] {
|
||||
match self.iter().position(|p| matches!(p, Part::Method(_, _))) {
|
||||
None => &[],
|
||||
Some(i) => &self[i..],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> NextMethod<'a> for &'a Idiom {
|
||||
fn next_method(&'a self) -> &'a [Part] {
|
||||
match self.iter().position(|p| matches!(p, Part::Method(_, _))) {
|
||||
None => &[],
|
||||
Some(i) => &self[i..],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
|
||||
#[revisioned(revision = 1)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
|
|
|
@ -6,11 +6,12 @@ use crate::dbs::Options;
|
|||
use crate::doc::CursorDoc;
|
||||
use crate::err::Error;
|
||||
use crate::exe::try_join_all_buffered;
|
||||
use crate::fnc::idiom;
|
||||
use crate::sql::edges::Edges;
|
||||
use crate::sql::field::{Field, Fields};
|
||||
use crate::sql::id::Id;
|
||||
use crate::sql::part::Next;
|
||||
use crate::sql::part::Part;
|
||||
use crate::sql::part::{Next, NextMethod};
|
||||
use crate::sql::paths::ID;
|
||||
use crate::sql::statements::select::SelectStatement;
|
||||
use crate::sql::thing::Thing;
|
||||
|
@ -57,6 +58,10 @@ impl Value {
|
|||
let obj = Value::Object(v.as_object());
|
||||
stk.run(|stk| obj.get(stk, ctx, opt, doc, path)).await
|
||||
}
|
||||
Part::Method(name, args) => {
|
||||
let v = idiom(ctx, doc, v.clone().into(), name, args.clone())?;
|
||||
stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await
|
||||
}
|
||||
// Otherwise return none
|
||||
_ => Ok(Value::None),
|
||||
},
|
||||
|
@ -134,6 +139,10 @@ impl Value {
|
|||
let obj = Value::from(obj);
|
||||
stk.run(|stk| obj.get(stk, ctx, opt, doc, path.next())).await
|
||||
}
|
||||
Part::Method(name, args) => {
|
||||
let v = idiom(ctx, doc, v.clone().into(), name, args.clone())?;
|
||||
stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await
|
||||
}
|
||||
_ => Ok(Value::None),
|
||||
},
|
||||
// Current value at path is an array
|
||||
|
@ -183,6 +192,10 @@ impl Value {
|
|||
},
|
||||
_ => Ok(Value::None),
|
||||
},
|
||||
Part::Method(name, args) => {
|
||||
let v = idiom(ctx, doc, v.clone().into(), name, args.clone())?;
|
||||
stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await
|
||||
}
|
||||
_ => stk
|
||||
.scope(|scope| {
|
||||
let futs =
|
||||
|
@ -267,6 +280,10 @@ impl Value {
|
|||
}
|
||||
}
|
||||
}
|
||||
Part::Method(name, args) => {
|
||||
let v = idiom(ctx, doc, v.clone().into(), name, args.clone())?;
|
||||
stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await
|
||||
}
|
||||
// This is a remote field expression
|
||||
_ => {
|
||||
let stm = SelectStatement {
|
||||
|
@ -282,11 +299,19 @@ impl Value {
|
|||
}
|
||||
}
|
||||
v => {
|
||||
if matches!(p, Part::Flatten) {
|
||||
stk.run(|stk| v.get(stk, ctx, opt, None, path.next())).await
|
||||
} else {
|
||||
// Ignore everything else
|
||||
Ok(Value::None)
|
||||
match p {
|
||||
Part::Flatten => {
|
||||
stk.run(|stk| v.get(stk, ctx, opt, None, path.next())).await
|
||||
}
|
||||
Part::Method(name, args) => {
|
||||
let v = idiom(ctx, doc, v.clone(), name, args.clone())?;
|
||||
stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await
|
||||
}
|
||||
// Only continue processing the path from the point that it contains a method
|
||||
_ => {
|
||||
stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next_method()))
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -4,6 +4,8 @@ use crate::err::Error;
|
|||
use crate::sql::value::serde::ser;
|
||||
use crate::sql::Ident;
|
||||
use crate::sql::Part;
|
||||
use crate::sql::Value;
|
||||
use ser::Serializer as _;
|
||||
use serde::ser::Error as _;
|
||||
use serde::ser::Impossible;
|
||||
use serde::ser::Serialize;
|
||||
|
@ -17,7 +19,7 @@ impl ser::Serializer for Serializer {
|
|||
type SerializeSeq = Impossible<Part, Error>;
|
||||
type SerializeTuple = Impossible<Part, Error>;
|
||||
type SerializeTupleStruct = Impossible<Part, Error>;
|
||||
type SerializeTupleVariant = Impossible<Part, Error>;
|
||||
type SerializeTupleVariant = SerializePart;
|
||||
type SerializeMap = Impossible<Part, Error>;
|
||||
type SerializeStruct = Impossible<Part, Error>;
|
||||
type SerializeStructVariant = Impossible<Part, Error>;
|
||||
|
@ -62,6 +64,67 @@ impl ser::Serializer for Serializer {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
||||
let inner = match variant {
|
||||
"Method" => Inner::Method(Default::default(), Default::default()),
|
||||
variant => {
|
||||
return Err(Error::custom(format!("unexpected tuple variant `{name}::{variant}`")));
|
||||
}
|
||||
};
|
||||
Ok(SerializePart {
|
||||
inner,
|
||||
index: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct SerializePart {
|
||||
index: usize,
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
enum Inner {
|
||||
Method(String, Vec<Value>),
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeTupleVariant for SerializePart {
|
||||
type Ok = Part;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: Serialize + ?Sized,
|
||||
{
|
||||
match (self.index, &mut self.inner) {
|
||||
(0, Inner::Method(ref mut var, _)) => {
|
||||
*var = value.serialize(ser::string::Serializer.wrap())?;
|
||||
}
|
||||
(1, Inner::Method(_, ref mut var)) => {
|
||||
*var = value.serialize(ser::value::vec::Serializer.wrap())?;
|
||||
}
|
||||
(index, inner) => {
|
||||
let variant = match inner {
|
||||
Inner::Method(..) => "Method",
|
||||
};
|
||||
return Err(Error::custom(format!("unexpected `Part::{variant}` index `{index}`")));
|
||||
}
|
||||
}
|
||||
self.index += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
match self.inner {
|
||||
Inner::Method(one, two) => Ok(Part::Method(one, two)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -133,4 +196,11 @@ mod tests {
|
|||
let serialized = part.serialize(Serializer.wrap()).unwrap();
|
||||
assert_eq!(part, serialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method() {
|
||||
let part = Part::Method(Default::default(), Default::default());
|
||||
let serialized = part.serialize(Serializer.wrap()).unwrap();
|
||||
assert_eq!(part, serialized);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -333,6 +333,7 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
|
|||
UniCase::ascii("time::from::unix") => PathKind::Function,
|
||||
//
|
||||
UniCase::ascii("type::bool") => PathKind::Function,
|
||||
UniCase::ascii("type::bytes") => PathKind::Function,
|
||||
UniCase::ascii("type::datetime") => PathKind::Function,
|
||||
UniCase::ascii("type::decimal") => PathKind::Function,
|
||||
UniCase::ascii("type::duration") => PathKind::Function,
|
||||
|
@ -344,6 +345,9 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
|
|||
UniCase::ascii("type::table") => PathKind::Function,
|
||||
UniCase::ascii("type::thing") => PathKind::Function,
|
||||
UniCase::ascii("type::range") => PathKind::Function,
|
||||
UniCase::ascii("type::record") => PathKind::Function,
|
||||
UniCase::ascii("type::uuid") => PathKind::Function,
|
||||
UniCase::ascii("type::geometry") => PathKind::Function,
|
||||
UniCase::ascii("type::is::array") => PathKind::Function,
|
||||
UniCase::ascii("type::is::bool") => PathKind::Function,
|
||||
UniCase::ascii("type::is::bytes") => PathKind::Function,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use reblessive::Stk;
|
||||
|
||||
use crate::{
|
||||
sql::{Function, Ident, Model},
|
||||
sql::{Function, Ident, Model, Value},
|
||||
syn::{
|
||||
parser::{
|
||||
mac::{expected, expected_whitespace, unexpected},
|
||||
|
@ -24,7 +24,13 @@ impl Parser<'_> {
|
|||
name.push_str("::");
|
||||
name.push_str(&self.next_token_value::<Ident>()?.0)
|
||||
}
|
||||
let start = expected!(self, t!("(")).span;
|
||||
expected!(self, t!("(")).span;
|
||||
let args = self.parse_function_args(ctx).await?;
|
||||
Ok(Function::Custom(name, args))
|
||||
}
|
||||
|
||||
pub async fn parse_function_args(&mut self, ctx: &mut Stk) -> ParseResult<Vec<Value>> {
|
||||
let start = self.last_span();
|
||||
let mut args = Vec::new();
|
||||
loop {
|
||||
if self.eat(t!(")")) {
|
||||
|
@ -39,8 +45,7 @@ impl Parser<'_> {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Function::Custom(name, args))
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
/// Parse a model invocation
|
||||
|
|
|
@ -83,7 +83,7 @@ impl Parser<'_> {
|
|||
}
|
||||
t!(".") => {
|
||||
self.pop_peek();
|
||||
res.push(self.parse_dot_part()?)
|
||||
res.push(self.parse_dot_part(stk).await?)
|
||||
}
|
||||
t!("[") => {
|
||||
let span = self.pop_peek().span;
|
||||
|
@ -141,7 +141,7 @@ impl Parser<'_> {
|
|||
}
|
||||
t!(".") => {
|
||||
self.pop_peek();
|
||||
res.push(self.parse_dot_part()?)
|
||||
res.push(self.parse_dot_part(ctx).await?)
|
||||
}
|
||||
t!("[") => {
|
||||
let span = self.pop_peek().span;
|
||||
|
@ -249,7 +249,7 @@ impl Parser<'_> {
|
|||
}
|
||||
|
||||
/// Parse the part after the `.` in a idiom
|
||||
pub fn parse_dot_part(&mut self) -> ParseResult<Part> {
|
||||
pub async fn parse_dot_part(&mut self, ctx: &mut Stk) -> ParseResult<Part> {
|
||||
let res = match self.peek_kind() {
|
||||
t!("*") => {
|
||||
self.pop_peek();
|
||||
|
@ -257,14 +257,25 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("{") => {
|
||||
self.pop_peek();
|
||||
self.parse_destructure_part()?
|
||||
ctx.run(|ctx| self.parse_destructure_part(ctx)).await?
|
||||
}
|
||||
_ => {
|
||||
let ident: Ident = self.next_token_value()?;
|
||||
if self.eat(t!("(")) {
|
||||
self.parse_function_part(ctx, ident).await?
|
||||
} else {
|
||||
Part::Field(ident)
|
||||
}
|
||||
}
|
||||
_ => Part::Field(self.next_token_value()?),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
pub async fn parse_function_part(&mut self, ctx: &mut Stk, name: Ident) -> ParseResult<Part> {
|
||||
let args = self.parse_function_args(ctx).await?;
|
||||
Ok(Part::Method(name.0, args))
|
||||
}
|
||||
/// Parse the part after the `.{` in an idiom
|
||||
pub fn parse_destructure_part(&mut self) -> ParseResult<Part> {
|
||||
pub async fn parse_destructure_part(&mut self, ctx: &mut Stk) -> ParseResult<Part> {
|
||||
let start = self.last_span();
|
||||
let mut destructured: Vec<DestructurePart> = Vec::new();
|
||||
loop {
|
||||
|
@ -277,12 +288,12 @@ impl Parser<'_> {
|
|||
let part = match self.peek_kind() {
|
||||
t!(":") => {
|
||||
self.pop_peek();
|
||||
DestructurePart::Aliased(field, self.parse_local_idiom()?)
|
||||
DestructurePart::Aliased(field, self.parse_local_idiom(ctx).await?)
|
||||
}
|
||||
t!(".") => {
|
||||
self.pop_peek();
|
||||
let found = self.peek_kind();
|
||||
match self.parse_dot_part()? {
|
||||
match self.parse_dot_part(ctx).await? {
|
||||
Part::All => DestructurePart::All(field),
|
||||
Part::Destructure(v) => DestructurePart::Destructure(field, v),
|
||||
_ => {
|
||||
|
@ -338,7 +349,7 @@ impl Parser<'_> {
|
|||
t!("$param") => Part::Value(Value::Param(self.next_token_value()?)),
|
||||
TokenKind::Qoute(_x) => Part::Value(Value::Strand(self.next_token_value()?)),
|
||||
_ => {
|
||||
let idiom = self.parse_basic_idiom()?;
|
||||
let idiom = self.parse_basic_idiom(ctx).await?;
|
||||
Part::Value(Value::Idiom(idiom))
|
||||
}
|
||||
};
|
||||
|
@ -347,10 +358,10 @@ impl Parser<'_> {
|
|||
}
|
||||
|
||||
/// Parse a list of basic idioms seperated by a ','
|
||||
pub fn parse_basic_idiom_list(&mut self) -> ParseResult<Vec<Idiom>> {
|
||||
let mut res = vec![self.parse_basic_idiom()?];
|
||||
pub async fn parse_basic_idiom_list(&mut self, ctx: &mut Stk) -> ParseResult<Vec<Idiom>> {
|
||||
let mut res = vec![self.parse_basic_idiom(ctx).await?];
|
||||
while self.eat(t!(",")) {
|
||||
res.push(self.parse_basic_idiom()?);
|
||||
res.push(self.parse_basic_idiom(ctx).await?);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
@ -359,7 +370,7 @@ impl Parser<'_> {
|
|||
///
|
||||
/// Basic idioms differ from normal idioms in that they are more restrictive.
|
||||
/// Flatten, graphs, conditions and indexing by param is not allowed.
|
||||
pub fn parse_basic_idiom(&mut self) -> ParseResult<Idiom> {
|
||||
pub async fn parse_basic_idiom(&mut self, ctx: &mut Stk) -> ParseResult<Idiom> {
|
||||
let start = self.next_token_value::<Ident>()?;
|
||||
let mut parts = vec![Part::Field(start)];
|
||||
loop {
|
||||
|
@ -367,7 +378,7 @@ impl Parser<'_> {
|
|||
let part = match token.kind {
|
||||
t!(".") => {
|
||||
self.pop_peek();
|
||||
self.parse_dot_part()?
|
||||
self.parse_dot_part(ctx).await?
|
||||
}
|
||||
t!("[") => {
|
||||
self.pop_peek();
|
||||
|
@ -409,7 +420,7 @@ impl Parser<'_> {
|
|||
/// Basic idioms differ from local idioms in that they are more restrictive.
|
||||
/// Only field, all and number indexing is allowed. Flatten is also allowed but only at the
|
||||
/// end.
|
||||
pub fn parse_local_idiom(&mut self) -> ParseResult<Idiom> {
|
||||
pub async fn parse_local_idiom(&mut self, ctx: &mut Stk) -> ParseResult<Idiom> {
|
||||
let start = self.next_token_value()?;
|
||||
let mut parts = vec![Part::Field(start)];
|
||||
loop {
|
||||
|
@ -417,7 +428,7 @@ impl Parser<'_> {
|
|||
let part = match token.kind {
|
||||
t!(".") => {
|
||||
self.pop_peek();
|
||||
self.parse_dot_part()?
|
||||
self.parse_dot_part(ctx).await?
|
||||
}
|
||||
t!("[") => {
|
||||
self.pop_peek();
|
||||
|
|
|
@ -132,19 +132,19 @@ impl Parser<'_> {
|
|||
let value = match token.kind {
|
||||
t!("NONE") => {
|
||||
self.pop_peek();
|
||||
return Ok(Value::None);
|
||||
Value::None
|
||||
}
|
||||
t!("NULL") => {
|
||||
self.pop_peek();
|
||||
return Ok(Value::Null);
|
||||
Value::Null
|
||||
}
|
||||
t!("true") => {
|
||||
self.pop_peek();
|
||||
return Ok(Value::Bool(true));
|
||||
Value::Bool(true)
|
||||
}
|
||||
t!("false") => {
|
||||
self.pop_peek();
|
||||
return Ok(Value::Bool(false));
|
||||
Value::Bool(false)
|
||||
}
|
||||
t!("<") => {
|
||||
self.pop_peek();
|
||||
|
@ -153,7 +153,7 @@ impl Parser<'_> {
|
|||
self.expect_closing_delimiter(t!(">"), token.span)?;
|
||||
let next = expected!(self, t!("{")).span;
|
||||
let block = self.parse_block(ctx, next).await?;
|
||||
return Ok(Value::Future(Box::new(crate::sql::Future(block))));
|
||||
Value::Future(Box::new(crate::sql::Future(block)))
|
||||
}
|
||||
t!("r\"") => {
|
||||
self.pop_peek();
|
||||
|
@ -180,14 +180,14 @@ impl Parser<'_> {
|
|||
return Ok(x);
|
||||
}
|
||||
}
|
||||
return Ok(Value::Strand(s));
|
||||
Value::Strand(s)
|
||||
}
|
||||
t!("+") | t!("-") | TokenKind::Number(_) | TokenKind::Digits | TokenKind::Duration => {
|
||||
self.parse_number_like_prime()?
|
||||
}
|
||||
TokenKind::NaN => {
|
||||
self.pop_peek();
|
||||
return Ok(Value::Number(Number::Float(f64::NAN)));
|
||||
Value::Number(Number::Float(f64::NAN))
|
||||
}
|
||||
t!("$param") => {
|
||||
let param = self.next_token_value()?;
|
||||
|
@ -285,11 +285,6 @@ impl Parser<'_> {
|
|||
// Parse the rest of the idiom if it is being continued.
|
||||
if Self::continues_idiom(self.peek_kind()) {
|
||||
match value {
|
||||
Value::None
|
||||
| Value::Null
|
||||
| Value::Bool(_)
|
||||
| Value::Future(_)
|
||||
| Value::Strand(_) => unreachable!(),
|
||||
Value::Idiom(Idiom(x)) => self.parse_remaining_value_idiom(ctx, x).await,
|
||||
Value::Table(Table(x)) => {
|
||||
self.parse_remaining_value_idiom(ctx, vec![Part::Field(Ident(x))]).await
|
||||
|
@ -420,7 +415,7 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("REMOVE") => {
|
||||
self.pop_peek();
|
||||
let stmt = self.parse_remove_stmt()?;
|
||||
let stmt = self.parse_remove_stmt(ctx).await?;
|
||||
Subquery::Remove(stmt)
|
||||
}
|
||||
t!("REBUILD") => {
|
||||
|
@ -553,7 +548,7 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("REMOVE") => {
|
||||
self.pop_peek();
|
||||
let stmt = self.parse_remove_stmt()?;
|
||||
let stmt = self.parse_remove_stmt(ctx).await?;
|
||||
Subquery::Remove(stmt)
|
||||
}
|
||||
t!("REBUILD") => {
|
||||
|
@ -648,7 +643,7 @@ impl Parser<'_> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::syn::{self, Parse};
|
||||
use crate::syn::Parse;
|
||||
|
||||
#[test]
|
||||
fn subquery_expression_statement() {
|
||||
|
@ -657,12 +652,6 @@ mod tests {
|
|||
assert_eq!("(1 + 2 + 3)", format!("{}", out))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_idiom() {
|
||||
let sql = "'hello'.foo";
|
||||
syn::parse(sql).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subquery_ifelse_statement() {
|
||||
let sql = "IF true THEN false END";
|
||||
|
|
|
@ -47,7 +47,9 @@ impl Parser<'_> {
|
|||
t!("FIELD") => {
|
||||
ctx.run(|ctx| self.parse_define_field(ctx)).await.map(DefineStatement::Field)
|
||||
}
|
||||
t!("INDEX") => self.parse_define_index().map(DefineStatement::Index),
|
||||
t!("INDEX") => {
|
||||
ctx.run(|ctx| self.parse_define_index(ctx)).await.map(DefineStatement::Index)
|
||||
}
|
||||
t!("ANALYZER") => self.parse_define_analyzer().map(DefineStatement::Analyzer),
|
||||
t!("ACCESS") => self.parse_define_access(ctx).await.map(DefineStatement::Access),
|
||||
x => unexpected!(self, x, "a define statement keyword"),
|
||||
|
@ -739,7 +741,7 @@ impl Parser<'_> {
|
|||
} else {
|
||||
(false, false)
|
||||
};
|
||||
let name = self.parse_local_idiom()?;
|
||||
let name = self.parse_local_idiom(ctx).await?;
|
||||
expected!(self, t!("ON"));
|
||||
self.eat(t!("TABLE"));
|
||||
let what = self.next_token_value()?;
|
||||
|
@ -794,7 +796,7 @@ impl Parser<'_> {
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn parse_define_index(&mut self) -> ParseResult<DefineIndexStatement> {
|
||||
pub async fn parse_define_index(&mut self, ctx: &mut Stk) -> ParseResult<DefineIndexStatement> {
|
||||
let (if_not_exists, overwrite) = if self.eat(t!("IF")) {
|
||||
expected!(self, t!("NOT"));
|
||||
expected!(self, t!("EXISTS"));
|
||||
|
@ -823,9 +825,9 @@ impl Parser<'_> {
|
|||
// COLUMNS and FIELDS are the same tokenkind
|
||||
t!("FIELDS") => {
|
||||
self.pop_peek();
|
||||
res.cols = Idioms(vec![self.parse_local_idiom()?]);
|
||||
res.cols = Idioms(vec![self.parse_local_idiom(ctx).await?]);
|
||||
while self.eat(t!(",")) {
|
||||
res.cols.0.push(self.parse_local_idiom()?);
|
||||
res.cols.0.push(self.parse_local_idiom(ctx).await?);
|
||||
}
|
||||
}
|
||||
t!("UNIQUE") => {
|
||||
|
|
|
@ -190,7 +190,7 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("REMOVE") => {
|
||||
self.pop_peek();
|
||||
self.parse_remove_stmt().map(Statement::Remove)
|
||||
ctx.run(|ctx| self.parse_remove_stmt(ctx)).await.map(Statement::Remove)
|
||||
}
|
||||
t!("SELECT") => {
|
||||
self.pop_peek();
|
||||
|
@ -291,7 +291,7 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("REMOVE") => {
|
||||
self.pop_peek();
|
||||
self.parse_remove_stmt().map(Entry::Remove)
|
||||
self.parse_remove_stmt(ctx).await.map(Entry::Remove)
|
||||
}
|
||||
t!("SELECT") => {
|
||||
self.pop_peek();
|
||||
|
|
|
@ -205,8 +205,9 @@ impl Parser<'_> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn try_parse_group(
|
||||
pub async fn try_parse_group(
|
||||
&mut self,
|
||||
ctx: &mut Stk,
|
||||
fields: &Fields,
|
||||
fields_span: Span,
|
||||
) -> ParseResult<Option<Groups>> {
|
||||
|
@ -223,7 +224,7 @@ impl Parser<'_> {
|
|||
let has_all = fields.contains(&Field::All);
|
||||
|
||||
let before = self.peek().span;
|
||||
let group = self.parse_basic_idiom()?;
|
||||
let group = self.parse_basic_idiom(ctx).await?;
|
||||
let group_span = before.covers(self.last_span());
|
||||
if !has_all {
|
||||
Self::check_idiom(MissingKind::Group, fields, fields_span, &group, group_span)?;
|
||||
|
@ -232,7 +233,7 @@ impl Parser<'_> {
|
|||
let mut groups = Groups(vec![Group(group)]);
|
||||
while self.eat(t!(",")) {
|
||||
let before = self.peek().span;
|
||||
let group = self.parse_basic_idiom()?;
|
||||
let group = self.parse_basic_idiom(ctx).await?;
|
||||
let group_span = before.covers(self.last_span());
|
||||
if !has_all {
|
||||
Self::check_idiom(MissingKind::Group, fields, fields_span, &group, group_span)?;
|
||||
|
@ -406,7 +407,7 @@ impl Parser<'_> {
|
|||
}
|
||||
|
||||
let cond = self.try_parse_condition(stk).await?;
|
||||
let group = self.try_parse_group(&fields, fields_span)?;
|
||||
let group = self.try_parse_group(stk, &fields, fields_span).await?;
|
||||
|
||||
Ok(View {
|
||||
expr: fields,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use reblessive::Stk;
|
||||
|
||||
use crate::{
|
||||
sql::{
|
||||
statements::{
|
||||
|
@ -18,7 +20,7 @@ use crate::{
|
|||
};
|
||||
|
||||
impl Parser<'_> {
|
||||
pub fn parse_remove_stmt(&mut self) -> ParseResult<RemoveStatement> {
|
||||
pub async fn parse_remove_stmt(&mut self, ctx: &mut Stk) -> ParseResult<RemoveStatement> {
|
||||
let res = match self.next().kind {
|
||||
t!("NAMESPACE") | t!("ns") => {
|
||||
let if_exists = if self.eat(t!("IF")) {
|
||||
|
@ -136,7 +138,7 @@ impl Parser<'_> {
|
|||
} else {
|
||||
false
|
||||
};
|
||||
let idiom = self.parse_local_idiom()?;
|
||||
let idiom = self.parse_local_idiom(ctx).await?;
|
||||
expected!(self, t!("ON"));
|
||||
self.eat(t!("TABLE"));
|
||||
let table = self.next_token_value()?;
|
||||
|
|
|
@ -42,9 +42,9 @@ impl Parser<'_> {
|
|||
|
||||
let with = self.try_parse_with()?;
|
||||
let cond = self.try_parse_condition(stk).await?;
|
||||
let split = self.try_parse_split(&expr, fields_span)?;
|
||||
let group = self.try_parse_group(&expr, fields_span)?;
|
||||
let order = self.try_parse_orders(&expr, fields_span)?;
|
||||
let split = self.try_parse_split(stk, &expr, fields_span).await?;
|
||||
let group = self.try_parse_group(stk, &expr, fields_span).await?;
|
||||
let order = self.try_parse_orders(stk, &expr, fields_span).await?;
|
||||
let (limit, start) = if let t!("START") = self.peek_kind() {
|
||||
let start = self.try_parse_start(stk).await?;
|
||||
let limit = self.try_parse_limit(stk).await?;
|
||||
|
@ -104,8 +104,9 @@ impl Parser<'_> {
|
|||
Ok(Some(with))
|
||||
}
|
||||
|
||||
fn try_parse_split(
|
||||
async fn try_parse_split(
|
||||
&mut self,
|
||||
ctx: &mut Stk,
|
||||
fields: &Fields,
|
||||
fields_span: Span,
|
||||
) -> ParseResult<Option<Splits>> {
|
||||
|
@ -118,7 +119,7 @@ impl Parser<'_> {
|
|||
let has_all = fields.contains(&Field::All);
|
||||
|
||||
let before = self.peek().span;
|
||||
let split = self.parse_basic_idiom()?;
|
||||
let split = self.parse_basic_idiom(ctx).await?;
|
||||
let split_span = before.covers(self.last_span());
|
||||
if !has_all {
|
||||
Self::check_idiom(MissingKind::Split, fields, fields_span, &split, split_span)?;
|
||||
|
@ -127,7 +128,7 @@ impl Parser<'_> {
|
|||
let mut res = vec![Split(split)];
|
||||
while self.eat(t!(",")) {
|
||||
let before = self.peek().span;
|
||||
let split = self.parse_basic_idiom()?;
|
||||
let split = self.parse_basic_idiom(ctx).await?;
|
||||
let split_span = before.covers(self.last_span());
|
||||
if !has_all {
|
||||
Self::check_idiom(MissingKind::Split, fields, fields_span, &split, split_span)?;
|
||||
|
@ -137,8 +138,9 @@ impl Parser<'_> {
|
|||
Ok(Some(Splits(res)))
|
||||
}
|
||||
|
||||
fn try_parse_orders(
|
||||
async fn try_parse_orders(
|
||||
&mut self,
|
||||
ctx: &mut Stk,
|
||||
fields: &Fields,
|
||||
fields_span: Span,
|
||||
) -> ParseResult<Option<Orders>> {
|
||||
|
@ -164,7 +166,7 @@ impl Parser<'_> {
|
|||
let has_all = fields.contains(&Field::All);
|
||||
|
||||
let before = self.recent_span();
|
||||
let order = self.parse_order()?;
|
||||
let order = self.parse_order(ctx).await?;
|
||||
let order_span = before.covers(self.last_span());
|
||||
if !has_all {
|
||||
Self::check_idiom(MissingKind::Order, fields, fields_span, &order, order_span)?;
|
||||
|
@ -173,7 +175,7 @@ impl Parser<'_> {
|
|||
let mut orders = vec![order];
|
||||
while self.eat(t!(",")) {
|
||||
let before = self.recent_span();
|
||||
let order = self.parse_order()?;
|
||||
let order = self.parse_order(ctx).await?;
|
||||
let order_span = before.covers(self.last_span());
|
||||
if !has_all {
|
||||
Self::check_idiom(MissingKind::Order, fields, fields_span, &order, order_span)?;
|
||||
|
@ -184,8 +186,8 @@ impl Parser<'_> {
|
|||
Ok(Some(Orders(orders)))
|
||||
}
|
||||
|
||||
fn parse_order(&mut self) -> ParseResult<Order> {
|
||||
let start = self.parse_basic_idiom()?;
|
||||
async fn parse_order(&mut self, ctx: &mut Stk) -> ParseResult<Order> {
|
||||
let start = self.parse_basic_idiom(ctx).await?;
|
||||
let collate = self.eat(t!("COLLATE"));
|
||||
let numeric = self.eat(t!("NUMERIC"));
|
||||
let direction = match self.peek_kind() {
|
||||
|
|
|
@ -404,6 +404,9 @@
|
|||
"type::string("
|
||||
"type::table("
|
||||
"type::thing("
|
||||
"type::record("
|
||||
"type::uuid("
|
||||
"type::geometry("
|
||||
"vector::add("
|
||||
"vector::angle("
|
||||
"vector::cross("
|
||||
|
|
|
@ -403,6 +403,9 @@
|
|||
"type::table("
|
||||
"type::thing("
|
||||
"type::range("
|
||||
"type::record("
|
||||
"type::uuid("
|
||||
"type::geometry("
|
||||
"vector::add("
|
||||
"vector::angle("
|
||||
"vector::cross("
|
||||
|
|
|
@ -6227,3 +6227,24 @@ async fn function_outside_database() -> Result<(), Error> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn function_idiom_chaining() -> Result<(), Error> {
|
||||
let sql = r#"
|
||||
{ a: 1, b: 2 }.entries().flatten();
|
||||
"ABC".lowercase();
|
||||
true.is_number();
|
||||
true.is_bool();
|
||||
true.doesnt_exist();
|
||||
field.bla.nested.is_none();
|
||||
"#;
|
||||
Test::new(sql)
|
||||
.await?
|
||||
.expect_val("['a', 1, 'b', 2]")?
|
||||
.expect_val("'abc'")?
|
||||
.expect_val("false")?
|
||||
.expect_val("true")?
|
||||
.expect_error("There was a problem running the doesnt_exist() function. no such method found for the bool type")?
|
||||
.expect_val("true")?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue