Feature: Add type::range function (#3748)

Co-authored-by: Rushmore Mushambi <rushmore@webenchanter.com>
This commit is contained in:
Mees Delzenne 2024-04-03 14:05:29 +02:00 committed by GitHub
parent b03aeca08c
commit 6375bd45a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 132 additions and 1 deletions

View file

@ -321,6 +321,7 @@ pub fn synchronous(ctx: &Context<'_>, name: &str, args: Vec<Value>) -> Result<Va
"type::string" => r#type::string, "type::string" => r#type::string,
"type::table" => r#type::table, "type::table" => r#type::table,
"type::thing" => r#type::thing, "type::thing" => r#type::thing,
"type::range" => r#type::range,
"type::is::array" => r#type::is::array, "type::is::array" => r#type::is::array,
"type::is::bool" => r#type::is::bool, "type::is::bool" => r#type::is::bool,
"type::is::bytes" => r#type::is::bytes, "type::is::bytes" => r#type::is::bytes,

View file

@ -25,5 +25,6 @@ impl_module_def!(
"regex" => run, "regex" => run,
"string" => run, "string" => run,
"table" => run, "table" => run,
"thing" => run "thing" => run,
"range" => run
); );

View file

@ -1,3 +1,5 @@
use std::ops::Bound;
use crate::ctx::Context; use crate::ctx::Context;
use crate::dbs::{Options, Transaction}; use crate::dbs::{Options, Transaction};
use crate::doc::CursorDoc; use crate::doc::CursorDoc;
@ -5,6 +7,7 @@ use crate::err::Error;
use crate::sql::table::Table; use crate::sql::table::Table;
use crate::sql::thing::Thing; use crate::sql::thing::Thing;
use crate::sql::value::Value; use crate::sql::value::Value;
use crate::sql::{Id, Range, Strand};
use crate::syn; use crate::syn;
pub fn bool((val,): (Value,)) -> Result<Value, Error> { pub fn bool((val,): (Value,)) -> Result<Value, Error> {
@ -128,6 +131,93 @@ pub fn thing((arg1, arg2): (Value, Option<Value>)) -> Result<Value, Error> {
}) })
} }
pub fn range(args: Vec<Value>) -> Result<Value, Error> {
if args.len() > 4 || args.is_empty() {
return Err(Error::InvalidArguments {
name: "type::range".to_owned(),
message: "Expected atleast 1 and at most 4 arguments".to_owned(),
});
}
let mut args = args.into_iter();
// Unwrap will never trigger since length is checked above.
let id = args.next().unwrap().as_string();
let start = args.next().and_then(|x| match x {
Value::Thing(v) => Some(v.id),
Value::Array(v) => Some(v.into()),
Value::Object(v) => Some(v.into()),
Value::Number(v) => Some(v.into()),
Value::Null | Value::None => None,
v => Some(Id::from(v.as_string())),
});
let end = args.next().and_then(|x| match x {
Value::Thing(v) => Some(v.id),
Value::Array(v) => Some(v.into()),
Value::Object(v) => Some(v.into()),
Value::Number(v) => Some(v.into()),
Value::Null | Value::None => None,
v => Some(Id::from(v.as_string())),
});
let (begin, end) = if let Some(x) = args.next() {
let Value::Object(x) = x else {
return Err(Error::ConvertTo {
from: x,
into: "object".to_owned(),
});
};
let begin = if let Some(x) = x.get("begin") {
let start = start.ok_or_else(|| Error::InvalidArguments {
name: "type::range".to_string(),
message: "Can't define an inclusion for begin if there is no begin bound"
.to_string(),
})?;
match x {
Value::Strand(Strand(x)) if x == "included" => Bound::Included(start),
Value::Strand(Strand(x)) if x == "excluded" => Bound::Excluded(start),
x => {
return Err(Error::ConvertTo {
from: x.clone(),
into: r#""included" | "excluded""#.to_owned(),
})
}
}
} else {
start.map(Bound::Included).unwrap_or(Bound::Unbounded)
};
let end = if let Some(x) = x.get("end") {
let end = end.ok_or_else(|| Error::InvalidArguments {
name: "type::range".to_string(),
message: "Can't define an inclusion for end if there is no end bound".to_string(),
})?;
match x {
Value::Strand(Strand(x)) if x == "included" => Bound::Included(end),
Value::Strand(Strand(x)) if x == "excluded" => Bound::Excluded(end),
x => {
return Err(Error::ConvertTo {
from: x.clone(),
into: r#""included" | "excluded""#.to_owned(),
})
}
}
} else {
end.map(Bound::Excluded).unwrap_or(Bound::Unbounded)
};
(begin, end)
} else {
(
start.map(Bound::Included).unwrap_or(Bound::Unbounded),
end.map(Bound::Excluded).unwrap_or(Bound::Unbounded),
)
};
Ok(Range {
tb: id,
beg: begin,
end,
}
.into())
}
pub mod is { pub mod is {
use crate::err::Error; use crate::err::Error;
use crate::sql::table::Table; use crate::sql::table::Table;

View file

@ -458,6 +458,7 @@ pub(crate) fn builtin_name(i: &str) -> IResult<&str, BuiltinName<&str>, ParseErr
string => { fn }, string => { fn },
table => { fn }, table => { fn },
thing => { fn }, thing => { fn },
range => { fn },
is => { is => {
array => { fn }, array => { fn },
r#bool = "bool" => { fn }, r#bool = "bool" => { fn },

View file

@ -314,6 +314,7 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
UniCase::ascii("type::string") => PathKind::Function, UniCase::ascii("type::string") => PathKind::Function,
UniCase::ascii("type::table") => PathKind::Function, UniCase::ascii("type::table") => PathKind::Function,
UniCase::ascii("type::thing") => PathKind::Function, UniCase::ascii("type::thing") => PathKind::Function,
UniCase::ascii("type::range") => PathKind::Function,
UniCase::ascii("type::is::array") => PathKind::Function, UniCase::ascii("type::is::array") => PathKind::Function,
UniCase::ascii("type::is::bool") => PathKind::Function, UniCase::ascii("type::is::bool") => PathKind::Function,
UniCase::ascii("type::is::bytes") => PathKind::Function, UniCase::ascii("type::is::bytes") => PathKind::Function,

View file

@ -393,6 +393,7 @@
"type::string(" "type::string("
"type::table(" "type::table("
"type::thing(" "type::thing("
"type::range("
"vector::add(" "vector::add("
"vector::angle(" "vector::angle("
"vector::cross(" "vector::cross("

View file

@ -5702,6 +5702,42 @@ async fn function_type_thing() -> Result<(), Error> {
Ok(()) Ok(())
} }
#[tokio::test]
async fn function_type_range() -> Result<(), Error> {
let sql = r#"
RETURN type::range('person');
RETURN type::range('person',1);
RETURN type::range('person',null,10);
RETURN type::range('person',1,10);
RETURN type::range('person',1,10, { begin: "excluded", end: "included"});
"#;
let dbs = new_ds().await?;
let ses = Session::owner().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 5);
//
let tmp = res.remove(0).result?;
let val = Value::parse("person:..");
assert_eq!(tmp, val);
let tmp = res.remove(0).result?;
let val = Value::parse("person:1..");
assert_eq!(tmp, val);
let tmp = res.remove(0).result?;
let val = Value::parse("person:..10");
assert_eq!(tmp, val);
let tmp = res.remove(0).result?;
let val = Value::parse("person:1..10");
assert_eq!(tmp, val);
let tmp = res.remove(0).result?;
let val = Value::parse("person:1>..=10");
assert_eq!(tmp, val);
Ok(())
}
#[tokio::test] #[tokio::test]
async fn function_vector_add() -> Result<(), Error> { async fn function_vector_add() -> Result<(), Error> {
test_queries( test_queries(