Range values (#4506)
This commit is contained in:
parent
b1e9af5d4e
commit
e77df62114
48 changed files with 1382 additions and 569 deletions
|
@ -186,7 +186,7 @@ mod tests {
|
||||||
let mut tx1 = ds.transaction(Write, Optimistic).await.unwrap().inner();
|
let mut tx1 = ds.transaction(Write, Optimistic).await.unwrap().inner();
|
||||||
let thing_a = Thing {
|
let thing_a = Thing {
|
||||||
tb: TB.to_owned(),
|
tb: TB.to_owned(),
|
||||||
id: Id::String("A".to_string()),
|
id: Id::from("A"),
|
||||||
};
|
};
|
||||||
let value_a: Value = "a".into();
|
let value_a: Value = "a".into();
|
||||||
let previous = Value::None;
|
let previous = Value::None;
|
||||||
|
@ -205,7 +205,7 @@ mod tests {
|
||||||
let mut tx2 = ds.transaction(Write, Optimistic).await.unwrap().inner();
|
let mut tx2 = ds.transaction(Write, Optimistic).await.unwrap().inner();
|
||||||
let thing_c = Thing {
|
let thing_c = Thing {
|
||||||
tb: TB.to_owned(),
|
tb: TB.to_owned(),
|
||||||
id: Id::String("C".to_string()),
|
id: Id::from("C"),
|
||||||
};
|
};
|
||||||
let value_c: Value = "c".into();
|
let value_c: Value = "c".into();
|
||||||
tx2.record_change(
|
tx2.record_change(
|
||||||
|
@ -223,7 +223,7 @@ mod tests {
|
||||||
let mut tx3 = ds.transaction(Write, Optimistic).await.unwrap().inner();
|
let mut tx3 = ds.transaction(Write, Optimistic).await.unwrap().inner();
|
||||||
let thing_b = Thing {
|
let thing_b = Thing {
|
||||||
tb: TB.to_owned(),
|
tb: TB.to_owned(),
|
||||||
id: Id::String("B".to_string()),
|
id: Id::from("B"),
|
||||||
};
|
};
|
||||||
let value_b: Value = "b".into();
|
let value_b: Value = "b".into();
|
||||||
tx3.record_change(
|
tx3.record_change(
|
||||||
|
@ -237,7 +237,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
let thing_c2 = Thing {
|
let thing_c2 = Thing {
|
||||||
tb: TB.to_owned(),
|
tb: TB.to_owned(),
|
||||||
id: Id::String("C".to_string()),
|
id: Id::from("C"),
|
||||||
};
|
};
|
||||||
let value_c2: Value = "c2".into();
|
let value_c2: Value = "c2".into();
|
||||||
tx3.record_change(
|
tx3.record_change(
|
||||||
|
@ -434,7 +434,7 @@ mod tests {
|
||||||
ds.tick_at(ts.0.timestamp().try_into().unwrap()).await.unwrap();
|
ds.tick_at(ts.0.timestamp().try_into().unwrap()).await.unwrap();
|
||||||
let thing = Thing {
|
let thing = Thing {
|
||||||
tb: TB.to_owned(),
|
tb: TB.to_owned(),
|
||||||
id: Id::String("A".to_string()),
|
id: Id::from("A"),
|
||||||
};
|
};
|
||||||
let ses = Session::owner().with_ns(NS).with_db(DB);
|
let ses = Session::owner().with_ns(NS).with_db(DB);
|
||||||
let res =
|
let res =
|
||||||
|
@ -531,7 +531,7 @@ mod tests {
|
||||||
async fn record_change_feed_entry(tx: Transaction, id: String) -> Thing {
|
async fn record_change_feed_entry(tx: Transaction, id: String) -> Thing {
|
||||||
let thing = Thing {
|
let thing = Thing {
|
||||||
tb: TB.to_owned(),
|
tb: TB.to_owned(),
|
||||||
id: Id::String(id),
|
id: Id::from(id),
|
||||||
};
|
};
|
||||||
let value_a: Value = "a".into();
|
let value_a: Value = "a".into();
|
||||||
let previous = Value::None.into();
|
let previous = Value::None.into();
|
||||||
|
|
|
@ -68,8 +68,8 @@ pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> =
|
||||||
pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> = Lazy::new(|| true);
|
pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> = Lazy::new(|| true);
|
||||||
|
|
||||||
/// Used to limit allocation for builtin functions
|
/// Used to limit allocation for builtin functions
|
||||||
pub static FUNCTION_ALLOCATION_LIMIT: Lazy<usize> = once_cell::sync::Lazy::new(|| {
|
pub static GENERATION_ALLOCATION_LIMIT: Lazy<usize> = once_cell::sync::Lazy::new(|| {
|
||||||
let n = std::env::var("SURREAL_FUNCTION_ALLOCATION_LIMIT")
|
let n = std::env::var("SURREAL_GENERATION_ALLOCATION_LIMIT")
|
||||||
.map(|s| s.parse::<u32>().unwrap_or(20))
|
.map(|s| s.parse::<u32>().unwrap_or(20))
|
||||||
.unwrap_or(20);
|
.unwrap_or(20);
|
||||||
2usize.pow(n)
|
2usize.pow(n)
|
||||||
|
|
|
@ -12,10 +12,10 @@ use crate::err::Error;
|
||||||
use crate::idx::planner::iterators::{IteratorRecord, IteratorRef};
|
use crate::idx::planner::iterators::{IteratorRecord, IteratorRef};
|
||||||
use crate::idx::planner::IterationStage;
|
use crate::idx::planner::IterationStage;
|
||||||
use crate::sql::edges::Edges;
|
use crate::sql::edges::Edges;
|
||||||
use crate::sql::range::Range;
|
|
||||||
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, IdRange};
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use reblessive::TreeStack;
|
use reblessive::TreeStack;
|
||||||
|
@ -27,7 +27,7 @@ pub(crate) enum Iterable {
|
||||||
Value(Value),
|
Value(Value),
|
||||||
Table(Table),
|
Table(Table),
|
||||||
Thing(Thing),
|
Thing(Thing),
|
||||||
Range(Range),
|
TableRange(String, IdRange),
|
||||||
Edges(Edges),
|
Edges(Edges),
|
||||||
Defer(Thing),
|
Defer(Thing),
|
||||||
Mergeable(Thing, Value),
|
Mergeable(Thing, Value),
|
||||||
|
@ -152,6 +152,20 @@ impl Iterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add the record to the iterator
|
// Add the record to the iterator
|
||||||
|
match &v.id {
|
||||||
|
Id::Range(r) => {
|
||||||
|
match stm {
|
||||||
|
Statement::Create(_) => {
|
||||||
|
return Err(Error::InvalidStatementTarget {
|
||||||
|
value: v.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.ingest(Iterable::TableRange(v.tb, *r.to_owned()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
match stm {
|
match stm {
|
||||||
Statement::Create(_) => {
|
Statement::Create(_) => {
|
||||||
self.ingest(Iterable::Defer(v));
|
self.ingest(Iterable::Defer(v));
|
||||||
|
@ -161,6 +175,8 @@ impl Iterator {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Value::Mock(v) => {
|
Value::Mock(v) => {
|
||||||
// Check if there is a data clause
|
// Check if there is a data clause
|
||||||
if let Some(data) = stm.data() {
|
if let Some(data) = stm.data() {
|
||||||
|
@ -176,25 +192,6 @@ impl Iterator {
|
||||||
self.ingest(Iterable::Thing(v))
|
self.ingest(Iterable::Thing(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Range(v) => {
|
|
||||||
// Check if this is a create statement
|
|
||||||
if let Statement::Create(_) = stm {
|
|
||||||
return Err(Error::InvalidStatementTarget {
|
|
||||||
value: v.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Check if there is a data clause
|
|
||||||
if let Some(data) = stm.data() {
|
|
||||||
// Check if there is an id field specified
|
|
||||||
if let Some(id) = data.rid(stk, ctx, opt).await? {
|
|
||||||
return Err(Error::IdMismatch {
|
|
||||||
value: id.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add the record to the iterator
|
|
||||||
self.ingest(Iterable::Range(*v));
|
|
||||||
}
|
|
||||||
Value::Edges(v) => {
|
Value::Edges(v) => {
|
||||||
// Check if this is a create statement
|
// Check if this is a create statement
|
||||||
if let Statement::Create(_) = stm {
|
if let Statement::Create(_) = stm {
|
||||||
|
|
|
@ -105,9 +105,9 @@ impl ExplainItem {
|
||||||
name: "Iterate Defer".into(),
|
name: "Iterate Defer".into(),
|
||||||
details: vec![("thing", Value::Thing(t.to_owned()))],
|
details: vec![("thing", Value::Thing(t.to_owned()))],
|
||||||
},
|
},
|
||||||
Iterable::Range(r) => Self {
|
Iterable::TableRange(tb, r) => Self {
|
||||||
name: "Iterate Range".into(),
|
name: "Iterate Range".into(),
|
||||||
details: vec![("table", Value::from(r.tb.to_owned()))],
|
details: vec![("table", tb.to_owned().into()), ("range", r.to_owned().into())],
|
||||||
},
|
},
|
||||||
Iterable::Edges(e) => Self {
|
Iterable::Edges(e) => Self {
|
||||||
name: "Iterate Edges".into(),
|
name: "Iterate Edges".into(),
|
||||||
|
|
|
@ -10,7 +10,8 @@ use crate::idx::planner::IterationStage;
|
||||||
use crate::key::{graph, thing};
|
use crate::key::{graph, thing};
|
||||||
use crate::kvs::Transaction;
|
use crate::kvs::Transaction;
|
||||||
use crate::sql::dir::Dir;
|
use crate::sql::dir::Dir;
|
||||||
use crate::sql::{Edges, Range, Table, Thing, Value};
|
use crate::sql::id::range::IdRange;
|
||||||
|
use crate::sql::{Edges, Table, Thing, Value};
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use channel::Sender;
|
use channel::Sender;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
@ -123,7 +124,9 @@ impl<'a> Processor<'a> {
|
||||||
Iterable::Value(v) => self.process_value(stk, ctx, opt, stm, v).await?,
|
Iterable::Value(v) => self.process_value(stk, ctx, opt, stm, v).await?,
|
||||||
Iterable::Thing(v) => self.process_thing(stk, ctx, opt, stm, v).await?,
|
Iterable::Thing(v) => self.process_thing(stk, ctx, opt, stm, v).await?,
|
||||||
Iterable::Defer(v) => self.process_defer(stk, ctx, opt, stm, v).await?,
|
Iterable::Defer(v) => self.process_defer(stk, ctx, opt, stm, v).await?,
|
||||||
Iterable::Range(v) => self.process_range(stk, ctx, opt, stm, v).await?,
|
Iterable::TableRange(tb, v) => {
|
||||||
|
self.process_range(stk, ctx, opt, stm, tb, v).await?
|
||||||
|
}
|
||||||
Iterable::Edges(e) => self.process_edge(stk, ctx, opt, stm, e).await?,
|
Iterable::Edges(e) => self.process_edge(stk, ctx, opt, stm, e).await?,
|
||||||
Iterable::Table(v) => {
|
Iterable::Table(v) => {
|
||||||
if let Some(qp) = ctx.get_query_planner() {
|
if let Some(qp) = ctx.get_query_planner() {
|
||||||
|
@ -343,28 +346,29 @@ impl<'a> Processor<'a> {
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
v: Range,
|
tb: String,
|
||||||
|
r: IdRange,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Get the transaction
|
// Get the transaction
|
||||||
let txn = ctx.tx();
|
let txn = ctx.tx();
|
||||||
// Check that the table exists
|
// Check that the table exists
|
||||||
txn.check_ns_db_tb(opt.ns()?, opt.db()?, &v.tb, opt.strict).await?;
|
txn.check_ns_db_tb(opt.ns()?, opt.db()?, &tb, opt.strict).await?;
|
||||||
// Prepare the range start key
|
// Prepare the range start key
|
||||||
let beg = match &v.beg {
|
let beg = match &r.beg {
|
||||||
Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, &v.tb),
|
Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, &tb),
|
||||||
Bound::Included(id) => thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(),
|
Bound::Included(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(),
|
||||||
Bound::Excluded(id) => {
|
Bound::Excluded(v) => {
|
||||||
let mut key = thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap();
|
let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap();
|
||||||
key.push(0x00);
|
key.push(0x00);
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Prepare the range end key
|
// Prepare the range end key
|
||||||
let end = match &v.end {
|
let end = match &r.end {
|
||||||
Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, &v.tb),
|
Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, &tb),
|
||||||
Bound::Excluded(id) => thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(),
|
Bound::Excluded(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(),
|
||||||
Bound::Included(id) => {
|
Bound::Included(v) => {
|
||||||
let mut key = thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap();
|
let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap();
|
||||||
key.push(0x00);
|
key.push(0x00);
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
|
|
|
@ -1081,6 +1081,19 @@ pub enum Error {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[error("The underlying datastore does not support versioned queries")]
|
#[error("The underlying datastore does not support versioned queries")]
|
||||||
UnsupportedVersionedQueries,
|
UnsupportedVersionedQueries,
|
||||||
|
|
||||||
|
/// Found an unexpected value in a range
|
||||||
|
#[error("Expected a range value of '{expected}', but found '{found}'")]
|
||||||
|
InvalidRangeValue {
|
||||||
|
expected: String,
|
||||||
|
found: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Found an unexpected value in a range
|
||||||
|
#[error("The range cannot exceed a size of {max} for this operation")]
|
||||||
|
RangeTooBig {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for String {
|
impl From<Error> for String {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::cnf::FUNCTION_ALLOCATION_LIMIT;
|
use crate::cnf::GENERATION_ALLOCATION_LIMIT;
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::dbs::Options;
|
use crate::dbs::Options;
|
||||||
use crate::doc::CursorDoc;
|
use crate::doc::CursorDoc;
|
||||||
|
@ -25,10 +25,10 @@ use std::mem::size_of_val;
|
||||||
|
|
||||||
/// Returns an error if an array of this length is too much to allocate.
|
/// Returns an error if an array of this length is too much to allocate.
|
||||||
fn limit(name: &str, n: usize) -> Result<(), Error> {
|
fn limit(name: &str, n: usize) -> Result<(), Error> {
|
||||||
if n > *FUNCTION_ALLOCATION_LIMIT {
|
if n > *GENERATION_ALLOCATION_LIMIT {
|
||||||
Err(Error::InvalidArguments {
|
Err(Error::InvalidArguments {
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
message: format!("Output must not exceed {} bytes.", *FUNCTION_ALLOCATION_LIMIT),
|
message: format!("Output must not exceed {} bytes.", *GENERATION_ALLOCATION_LIMIT),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -352,6 +352,7 @@ pub fn synchronous(
|
||||||
"time::from::secs" => time::from::secs,
|
"time::from::secs" => time::from::secs,
|
||||||
"time::from::unix" => time::from::unix,
|
"time::from::unix" => time::from::unix,
|
||||||
//
|
//
|
||||||
|
"type::array" => r#type::array,
|
||||||
"type::bool" => r#type::bool,
|
"type::bool" => r#type::bool,
|
||||||
"type::bytes" => r#type::bytes,
|
"type::bytes" => r#type::bytes,
|
||||||
"type::datetime" => r#type::datetime,
|
"type::datetime" => r#type::datetime,
|
||||||
|
@ -687,6 +688,7 @@ pub async fn idiom(
|
||||||
"is_string" => r#type::is::string,
|
"is_string" => r#type::is::string,
|
||||||
"is_uuid" => r#type::is::uuid,
|
"is_uuid" => r#type::is::uuid,
|
||||||
//
|
//
|
||||||
|
"to_array" => r#type::array,
|
||||||
"to_bool" => r#type::bool,
|
"to_bool" => r#type::bool,
|
||||||
"to_bytes" => r#type::bytes,
|
"to_bytes" => r#type::bytes,
|
||||||
"to_datetime" => r#type::datetime,
|
"to_datetime" => r#type::datetime,
|
||||||
|
@ -697,6 +699,7 @@ pub async fn idiom(
|
||||||
"to_int" => r#type::int,
|
"to_int" => r#type::int,
|
||||||
"to_number" => r#type::number,
|
"to_number" => r#type::number,
|
||||||
"to_point" => r#type::point,
|
"to_point" => r#type::point,
|
||||||
|
"to_range" => r#type::range,
|
||||||
"to_record" => r#type::record,
|
"to_record" => r#type::record,
|
||||||
"to_string" => r#type::string,
|
"to_string" => r#type::string,
|
||||||
"to_uuid" => r#type::uuid,
|
"to_uuid" => r#type::uuid,
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub struct Package;
|
||||||
impl_module_def!(
|
impl_module_def!(
|
||||||
Package,
|
Package,
|
||||||
"type",
|
"type",
|
||||||
|
"array" => run,
|
||||||
"bool" => run,
|
"bool" => run,
|
||||||
"bytes" => run,
|
"bytes" => run,
|
||||||
"datetime" => run,
|
"datetime" => run,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::cnf::FUNCTION_ALLOCATION_LIMIT;
|
use crate::cnf::GENERATION_ALLOCATION_LIMIT;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::fnc::util::string;
|
use crate::fnc::util::string;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
|
@ -6,10 +6,10 @@ use crate::sql::Regex;
|
||||||
|
|
||||||
/// Returns `true` if a string of this length is too much to allocate.
|
/// Returns `true` if a string of this length is too much to allocate.
|
||||||
fn limit(name: &str, n: usize) -> Result<(), Error> {
|
fn limit(name: &str, n: usize) -> Result<(), Error> {
|
||||||
if n > *FUNCTION_ALLOCATION_LIMIT {
|
if n > *GENERATION_ALLOCATION_LIMIT {
|
||||||
Err(Error::InvalidArguments {
|
Err(Error::InvalidArguments {
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
message: format!("Output must not exceed {} bytes.", *FUNCTION_ALLOCATION_LIMIT),
|
message: format!("Output must not exceed {} bytes.", *GENERATION_ALLOCATION_LIMIT),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::ops::Bound;
|
|
||||||
|
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::dbs::Options;
|
use crate::dbs::Options;
|
||||||
use crate::doc::CursorDoc;
|
use crate::doc::CursorDoc;
|
||||||
|
@ -7,10 +5,14 @@ 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, Kind, Range, Strand};
|
use crate::sql::{Kind, Strand};
|
||||||
use crate::syn;
|
use crate::syn;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
|
|
||||||
|
pub fn array((val,): (Value,)) -> Result<Value, Error> {
|
||||||
|
val.convert_to_array().map(Value::from)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bool((val,): (Value,)) -> Result<Value, Error> {
|
pub fn bool((val,): (Value,)) -> Result<Value, Error> {
|
||||||
val.convert_to_bool().map(Value::from)
|
val.convert_to_bool().map(Value::from)
|
||||||
}
|
}
|
||||||
|
@ -91,91 +93,8 @@ pub fn point((val,): (Value,)) -> Result<Value, Error> {
|
||||||
val.convert_to_point().map(Value::from)
|
val.convert_to_point().map(Value::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn range(args: Vec<Value>) -> Result<Value, Error> {
|
pub fn range((val,): (Value,)) -> Result<Value, Error> {
|
||||||
if args.len() > 4 || args.is_empty() {
|
val.convert_to_range().map(Value::from)
|
||||||
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 fn record((rid, tb): (Value, Option<Value>)) -> Result<Value, Error> {
|
pub fn record((rid, tb): (Value, Option<Value>)) -> Result<Value, Error> {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::idx::planner::executor::KnnExpressions;
|
use crate::idx::planner::executor::KnnExpressions;
|
||||||
|
use crate::sql::id::range::IdRange;
|
||||||
use crate::sql::part::DestructurePart;
|
use crate::sql::part::DestructurePart;
|
||||||
use crate::sql::{
|
use crate::sql::{
|
||||||
Array, Cast, Cond, Expression, Function, Id, Idiom, Model, Object, Part, Range, Thing, Value,
|
Array, Cast, Cond, Expression, Function, Id, Idiom, Model, Object, Part, Range, Thing, Value,
|
||||||
|
@ -127,6 +128,7 @@ impl<'a> KnnConditionRewriter<'a> {
|
||||||
Id::Number(_) | Id::String(_) | Id::Generate(_) => Some(id.clone()),
|
Id::Number(_) | Id::String(_) | Id::Generate(_) => Some(id.clone()),
|
||||||
Id::Array(a) => self.eval_array(a).map(Id::Array),
|
Id::Array(a) => self.eval_array(a).map(Id::Array),
|
||||||
Id::Object(o) => self.eval_object(o).map(Id::Object),
|
Id::Object(o) => self.eval_object(o).map(Id::Object),
|
||||||
|
Id::Range(r) => self.eval_id_range(r).map(|v| Id::Range(Box::new(v))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +180,6 @@ impl<'a> KnnConditionRewriter<'a> {
|
||||||
fn eval_range(&self, r: &Range) -> Option<Range> {
|
fn eval_range(&self, r: &Range) -> Option<Range> {
|
||||||
if let Some(beg) = self.eval_bound(&r.beg) {
|
if let Some(beg) = self.eval_bound(&r.beg) {
|
||||||
self.eval_bound(&r.end).map(|end| Range {
|
self.eval_bound(&r.end).map(|end| Range {
|
||||||
tb: r.tb.clone(),
|
|
||||||
beg,
|
beg,
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
@ -187,10 +188,29 @@ impl<'a> KnnConditionRewriter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_bound(&self, b: &Bound<Id>) -> Option<Bound<Id>> {
|
fn eval_bound(&self, b: &Bound<Value>) -> Option<Bound<Value>> {
|
||||||
match b {
|
match b {
|
||||||
Bound::Included(id) => self.eval_id(id).map(Bound::Included),
|
Bound::Included(v) => self.eval_value(v).map(Bound::Included),
|
||||||
Bound::Excluded(id) => self.eval_id(id).map(Bound::Excluded),
|
Bound::Excluded(v) => self.eval_value(v).map(Bound::Excluded),
|
||||||
|
Bound::Unbounded => Some(Bound::Unbounded),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_id_range(&self, r: &IdRange) -> Option<IdRange> {
|
||||||
|
if let Some(beg) = self.eval_id_bound(&r.beg) {
|
||||||
|
self.eval_id_bound(&r.end).map(|end| IdRange {
|
||||||
|
beg,
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_id_bound(&self, b: &Bound<Id>) -> Option<Bound<Id>> {
|
||||||
|
match b {
|
||||||
|
Bound::Included(v) => self.eval_id(v).map(Bound::Included),
|
||||||
|
Bound::Excluded(v) => self.eval_id(v).map(Bound::Excluded),
|
||||||
Bound::Unbounded => Some(Bound::Unbounded),
|
Bound::Unbounded => Some(Bound::Unbounded),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Stores a record document
|
//! Stores a record document
|
||||||
use crate::key::category::Categorise;
|
use crate::key::category::Categorise;
|
||||||
use crate::key::category::Category;
|
use crate::key::category::Category;
|
||||||
use crate::sql::id::Id;
|
use crate::sql::Id;
|
||||||
use derive::Key;
|
use derive::Key;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,20 @@ use ciborium::Value as Data;
|
||||||
use geo::{LineString, Point, Polygon};
|
use geo::{LineString, Point, Polygon};
|
||||||
use geo_types::{MultiLineString, MultiPoint, MultiPolygon};
|
use geo_types::{MultiLineString, MultiPoint, MultiPolygon};
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
|
use std::ops::Bound;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::sql::id::range::IdRange;
|
||||||
|
use crate::sql::Array;
|
||||||
use crate::sql::Datetime;
|
use crate::sql::Datetime;
|
||||||
use crate::sql::Duration;
|
use crate::sql::Duration;
|
||||||
use crate::sql::Geometry;
|
use crate::sql::Geometry;
|
||||||
use crate::sql::Id;
|
use crate::sql::Id;
|
||||||
use crate::sql::Number;
|
use crate::sql::Number;
|
||||||
|
use crate::sql::Object;
|
||||||
|
use crate::sql::Range;
|
||||||
use crate::sql::Thing;
|
use crate::sql::Thing;
|
||||||
use crate::sql::Uuid;
|
use crate::sql::Uuid;
|
||||||
use crate::sql::Value;
|
use crate::sql::Value;
|
||||||
|
@ -30,6 +36,11 @@ const TAG_CUSTOM_DATETIME: u64 = 12;
|
||||||
const TAG_STRING_DURATION: u64 = 13;
|
const TAG_STRING_DURATION: u64 = 13;
|
||||||
const TAG_CUSTOM_DURATION: u64 = 14;
|
const TAG_CUSTOM_DURATION: u64 = 14;
|
||||||
|
|
||||||
|
// Ranges
|
||||||
|
const TAG_RANGE: u64 = 49;
|
||||||
|
const TAG_BOUND_INCLUDED: u64 = 50;
|
||||||
|
const TAG_BOUND_EXCLUDED: u64 = 51;
|
||||||
|
|
||||||
// Custom Geometries
|
// Custom Geometries
|
||||||
const TAG_GEOMETRY_POINT: u64 = 88;
|
const TAG_GEOMETRY_POINT: u64 = 88;
|
||||||
const TAG_GEOMETRY_LINE: u64 = 89;
|
const TAG_GEOMETRY_LINE: u64 = 89;
|
||||||
|
@ -52,17 +63,8 @@ impl TryFrom<Cbor> for Value {
|
||||||
Data::Float(v) => Ok(Value::from(v)),
|
Data::Float(v) => Ok(Value::from(v)),
|
||||||
Data::Bytes(v) => Ok(Value::Bytes(v.into())),
|
Data::Bytes(v) => Ok(Value::Bytes(v.into())),
|
||||||
Data::Text(v) => Ok(Value::from(v)),
|
Data::Text(v) => Ok(Value::from(v)),
|
||||||
Data::Array(v) => {
|
Data::Array(v) => Ok(Value::Array(Array::try_from(v)?)),
|
||||||
v.into_iter().map(|v| Value::try_from(Cbor(v))).collect::<Result<Value, &str>>()
|
Data::Map(v) => Ok(Value::Object(Object::try_from(v)?)),
|
||||||
}
|
|
||||||
Data::Map(v) => v
|
|
||||||
.into_iter()
|
|
||||||
.map(|(k, v)| {
|
|
||||||
let k = Value::try_from(Cbor(k)).map(|k| k.as_raw_string());
|
|
||||||
let v = Value::try_from(Cbor(v));
|
|
||||||
Ok((k?, v?))
|
|
||||||
})
|
|
||||||
.collect::<Result<Value, &str>>(),
|
|
||||||
Data::Tag(t, v) => {
|
Data::Tag(t, v) => {
|
||||||
match t {
|
match t {
|
||||||
// A literal datetime
|
// A literal datetime
|
||||||
|
@ -175,21 +177,12 @@ impl TryFrom<Cbor> for Value {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
match Value::try_from(Cbor(v.remove(0))) {
|
let id = Id::try_from(v.remove(0))?;
|
||||||
Ok(Value::Strand(id)) => {
|
|
||||||
Ok(Value::from(Thing::from((tb, Id::from(id)))))
|
Ok(Value::Thing(Thing {
|
||||||
}
|
tb,
|
||||||
Ok(Value::Number(Number::Int(id))) => {
|
id,
|
||||||
Ok(Value::from(Thing::from((tb, Id::from(id)))))
|
}))
|
||||||
}
|
|
||||||
Ok(Value::Array(id)) => {
|
|
||||||
Ok(Value::from(Thing::from((tb, Id::from(id)))))
|
|
||||||
}
|
|
||||||
Ok(Value::Object(id)) => {
|
|
||||||
Ok(Value::from(Thing::from((tb, Id::from(id)))))
|
|
||||||
}
|
|
||||||
_ => Err("Expected the id of a Record Id to be a String, Integer, Array or Object value"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => Err("Expected a CBOR text data type, or a CBOR array with 2 elements"),
|
_ => Err("Expected a CBOR text data type, or a CBOR array with 2 elements"),
|
||||||
},
|
},
|
||||||
|
@ -198,6 +191,8 @@ impl TryFrom<Cbor> for Value {
|
||||||
Data::Text(v) => Ok(Value::Table(v.into())),
|
Data::Text(v) => Ok(Value::Table(v.into())),
|
||||||
_ => Err("Expected a CBOR text data type"),
|
_ => Err("Expected a CBOR text data type"),
|
||||||
},
|
},
|
||||||
|
// A range
|
||||||
|
TAG_RANGE => Ok(Value::Range(Box::new(Range::try_from(*v)?))),
|
||||||
TAG_GEOMETRY_POINT => match *v {
|
TAG_GEOMETRY_POINT => match *v {
|
||||||
Data::Array(mut v) if v.len() == 2 => {
|
Data::Array(mut v) if v.len() == 2 => {
|
||||||
let x = Value::try_from(Cbor(v.remove(0)))?;
|
let x = Value::try_from(Cbor(v.remove(0)))?;
|
||||||
|
@ -393,11 +388,13 @@ impl TryFrom<Value> for Cbor {
|
||||||
Id::Generate(_) => {
|
Id::Generate(_) => {
|
||||||
return Err("Cannot encode an ungenerated Record ID into CBOR")
|
return Err("Cannot encode an ungenerated Record ID into CBOR")
|
||||||
}
|
}
|
||||||
|
Id::Range(v) => Data::try_from(*v)?,
|
||||||
},
|
},
|
||||||
])),
|
])),
|
||||||
))),
|
))),
|
||||||
Value::Table(v) => Ok(Cbor(Data::Tag(TAG_TABLE, Box::new(Data::Text(v.0))))),
|
Value::Table(v) => Ok(Cbor(Data::Tag(TAG_TABLE, Box::new(Data::Text(v.0))))),
|
||||||
Value::Geometry(v) => Ok(Cbor(encode_geometry(v)?)),
|
Value::Geometry(v) => Ok(Cbor(encode_geometry(v)?)),
|
||||||
|
Value::Range(v) => Ok(Cbor(Data::try_from(*v)?)),
|
||||||
// We shouldn't reach here
|
// We shouldn't reach here
|
||||||
_ => Err("Found unsupported SurrealQL value being encoded into a CBOR value"),
|
_ => Err("Found unsupported SurrealQL value being encoded into a CBOR value"),
|
||||||
}
|
}
|
||||||
|
@ -460,3 +457,136 @@ fn encode_geometry(v: Geometry) -> Result<Data, &'static str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Data> for Range {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(val: Data) -> Result<Self, &'static str> {
|
||||||
|
fn decode_bound(v: Data) -> Result<Bound<Value>, &'static str> {
|
||||||
|
match v {
|
||||||
|
Data::Tag(TAG_BOUND_INCLUDED, v) => Ok(Bound::Included(Value::try_from(Cbor(*v))?)),
|
||||||
|
Data::Tag(TAG_BOUND_EXCLUDED, v) => Ok(Bound::Excluded(Value::try_from(Cbor(*v))?)),
|
||||||
|
Data::Null => Ok(Bound::Unbounded),
|
||||||
|
_ => Err("Expected a bound tag"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match val {
|
||||||
|
Data::Array(v) if v.len() == 2 => {
|
||||||
|
let mut v = v;
|
||||||
|
let beg = decode_bound(v.remove(0).to_owned())?;
|
||||||
|
let end = decode_bound(v.remove(0).to_owned())?;
|
||||||
|
Ok(Range::new(beg, end))
|
||||||
|
}
|
||||||
|
_ => Err("Expected a CBOR array with 2 bounds"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Range> for Data {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(r: Range) -> Result<Data, &'static str> {
|
||||||
|
fn encode(b: Bound<Value>) -> Result<Data, &'static str> {
|
||||||
|
match b {
|
||||||
|
Bound::Included(v) => {
|
||||||
|
Ok(Data::Tag(TAG_BOUND_INCLUDED, Box::new(Cbor::try_from(v)?.0)))
|
||||||
|
}
|
||||||
|
Bound::Excluded(v) => {
|
||||||
|
Ok(Data::Tag(TAG_BOUND_EXCLUDED, Box::new(Cbor::try_from(v)?.0)))
|
||||||
|
}
|
||||||
|
Bound::Unbounded => Ok(Data::Null),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Data::Array(vec![encode(r.beg)?, encode(r.end)?]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Data> for IdRange {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(val: Data) -> Result<Self, &'static str> {
|
||||||
|
fn decode_bound(v: Data) -> Result<Bound<Id>, &'static str> {
|
||||||
|
match v {
|
||||||
|
Data::Tag(TAG_BOUND_INCLUDED, v) => Ok(Bound::Included(Id::try_from(*v)?)),
|
||||||
|
Data::Tag(TAG_BOUND_EXCLUDED, v) => Ok(Bound::Excluded(Id::try_from(*v)?)),
|
||||||
|
Data::Null => Ok(Bound::Unbounded),
|
||||||
|
_ => Err("Expected a bound tag"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match val {
|
||||||
|
Data::Array(v) if v.len() == 2 => {
|
||||||
|
let mut v = v;
|
||||||
|
let beg = decode_bound(v.remove(0).to_owned())?;
|
||||||
|
let end = decode_bound(v.remove(0).to_owned())?;
|
||||||
|
Ok(IdRange::try_from((beg, end))
|
||||||
|
.map_err(|_| "Found an invalid range with ranges as bounds")?)
|
||||||
|
}
|
||||||
|
_ => Err("Expected a CBOR array with 2 bounds"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<IdRange> for Data {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(r: IdRange) -> Result<Data, &'static str> {
|
||||||
|
fn encode(b: Bound<Id>) -> Result<Data, &'static str> {
|
||||||
|
match b {
|
||||||
|
Bound::Included(v) => Ok(Data::Tag(TAG_BOUND_INCLUDED, Box::new(v.try_into()?))),
|
||||||
|
Bound::Excluded(v) => Ok(Data::Tag(TAG_BOUND_EXCLUDED, Box::new(v.try_into()?))),
|
||||||
|
Bound::Unbounded => Ok(Data::Null),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Data::Array(vec![encode(r.beg)?, encode(r.end)?]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Data> for Id {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(val: Data) -> Result<Self, &'static str> {
|
||||||
|
match val {
|
||||||
|
Data::Integer(v) => Ok(Id::Number(i128::from(v) as i64)),
|
||||||
|
Data::Text(v) => Ok(Id::String(v)),
|
||||||
|
Data::Array(v) => Ok(Id::Array(v.try_into()?)),
|
||||||
|
Data::Map(v) => Ok(Id::Object(v.try_into()?)),
|
||||||
|
Data::Tag(TAG_RANGE, v) => Ok(Id::Range(Box::new(IdRange::try_from(*v)?))),
|
||||||
|
_ => Err("Expected a CBOR integer, text, array or map"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Id> for Data {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(v: Id) -> Result<Data, &'static str> {
|
||||||
|
match v {
|
||||||
|
Id::Number(v) => Ok(Data::Integer(v.into())),
|
||||||
|
Id::String(v) => Ok(Data::Text(v)),
|
||||||
|
Id::Array(v) => Ok(Cbor::try_from(Value::from(v))?.0),
|
||||||
|
Id::Object(v) => Ok(Cbor::try_from(Value::from(v))?.0),
|
||||||
|
Id::Range(v) => Ok(Data::Tag(TAG_RANGE, Box::new(v.deref().to_owned().try_into()?))),
|
||||||
|
Id::Generate(_) => Err("Cannot encode an ungenerated Record ID into CBOR"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<Data>> for Array {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(val: Vec<Data>) -> Result<Self, &'static str> {
|
||||||
|
val.into_iter().map(|v| Value::try_from(Cbor(v))).collect::<Result<Array, &str>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<(Data, Data)>> for Object {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(val: Vec<(Data, Data)>) -> Result<Self, &'static str> {
|
||||||
|
Ok(Object(
|
||||||
|
val.into_iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
let k = Value::try_from(Cbor(k)).map(|k| k.as_raw_string());
|
||||||
|
let v = Value::try_from(Cbor(v));
|
||||||
|
Ok((k?, v?))
|
||||||
|
})
|
||||||
|
.collect::<Result<BTreeMap<String, Value>, &str>>()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -267,7 +267,7 @@ pub trait RpcContext {
|
||||||
return Err(RpcError::InvalidParams);
|
return Err(RpcError::InvalidParams);
|
||||||
};
|
};
|
||||||
// Return a single result?
|
// Return a single result?
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
// Specify the SQL query string
|
// Specify the SQL query string
|
||||||
let sql = "SELECT * FROM $what";
|
let sql = "SELECT * FROM $what";
|
||||||
// Specify the query parameters
|
// Specify the query parameters
|
||||||
|
@ -295,7 +295,7 @@ pub trait RpcContext {
|
||||||
return Err(RpcError::InvalidParams);
|
return Err(RpcError::InvalidParams);
|
||||||
};
|
};
|
||||||
// Return a single result?
|
// Return a single result?
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
// Specify the SQL query string
|
// Specify the SQL query string
|
||||||
let sql = "INSERT INTO $what $data RETURN AFTER";
|
let sql = "INSERT INTO $what $data RETURN AFTER";
|
||||||
// Specify the query parameters
|
// Specify the query parameters
|
||||||
|
@ -324,7 +324,7 @@ pub trait RpcContext {
|
||||||
return Err(RpcError::InvalidParams);
|
return Err(RpcError::InvalidParams);
|
||||||
};
|
};
|
||||||
// Return a single result?
|
// Return a single result?
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
// Specify the SQL query string
|
// Specify the SQL query string
|
||||||
let sql = if data.is_none_or_null() {
|
let sql = if data.is_none_or_null() {
|
||||||
"CREATE $what RETURN AFTER"
|
"CREATE $what RETURN AFTER"
|
||||||
|
@ -357,7 +357,7 @@ pub trait RpcContext {
|
||||||
return Err(RpcError::InvalidParams);
|
return Err(RpcError::InvalidParams);
|
||||||
};
|
};
|
||||||
// Return a single result?
|
// Return a single result?
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
// Specify the SQL query string
|
// Specify the SQL query string
|
||||||
let sql = if data.is_none_or_null() {
|
let sql = if data.is_none_or_null() {
|
||||||
"UPSERT $what RETURN AFTER"
|
"UPSERT $what RETURN AFTER"
|
||||||
|
@ -390,7 +390,7 @@ pub trait RpcContext {
|
||||||
return Err(RpcError::InvalidParams);
|
return Err(RpcError::InvalidParams);
|
||||||
};
|
};
|
||||||
// Return a single result?
|
// Return a single result?
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
// Specify the SQL query string
|
// Specify the SQL query string
|
||||||
let sql = if data.is_none_or_null() {
|
let sql = if data.is_none_or_null() {
|
||||||
"UPDATE $what RETURN AFTER"
|
"UPDATE $what RETURN AFTER"
|
||||||
|
@ -423,7 +423,7 @@ pub trait RpcContext {
|
||||||
return Err(RpcError::InvalidParams);
|
return Err(RpcError::InvalidParams);
|
||||||
};
|
};
|
||||||
// Return a single result?
|
// Return a single result?
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
// Specify the SQL query string
|
// Specify the SQL query string
|
||||||
let sql = if data.is_none_or_null() {
|
let sql = if data.is_none_or_null() {
|
||||||
"UPDATE $what RETURN AFTER"
|
"UPDATE $what RETURN AFTER"
|
||||||
|
@ -456,7 +456,7 @@ pub trait RpcContext {
|
||||||
return Err(RpcError::InvalidParams);
|
return Err(RpcError::InvalidParams);
|
||||||
};
|
};
|
||||||
// Return a single result?
|
// Return a single result?
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
// Specify the SQL query string
|
// Specify the SQL query string
|
||||||
let sql = match diff.is_true() {
|
let sql = match diff.is_true() {
|
||||||
true => "UPDATE $what PATCH $data RETURN DIFF",
|
true => "UPDATE $what PATCH $data RETURN DIFF",
|
||||||
|
@ -488,7 +488,7 @@ pub trait RpcContext {
|
||||||
return Err(RpcError::InvalidParams);
|
return Err(RpcError::InvalidParams);
|
||||||
};
|
};
|
||||||
// Return a single result?
|
// Return a single result?
|
||||||
let one = kind.is_thing();
|
let one = kind.is_thing_single();
|
||||||
// Specify the SQL query string
|
// Specify the SQL query string
|
||||||
let sql = if data.is_none_or_null() {
|
let sql = if data.is_none_or_null() {
|
||||||
"RELATE $from->$kind->$to"
|
"RELATE $from->$kind->$to"
|
||||||
|
@ -523,7 +523,7 @@ pub trait RpcContext {
|
||||||
return Err(RpcError::InvalidParams);
|
return Err(RpcError::InvalidParams);
|
||||||
};
|
};
|
||||||
// Return a single result?
|
// Return a single result?
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
// Specify the SQL query string
|
// Specify the SQL query string
|
||||||
let sql = "DELETE $what RETURN BEFORE";
|
let sql = "DELETE $what RETURN BEFORE";
|
||||||
// Specify the query parameters
|
// Specify the query parameters
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::Range;
|
||||||
use crate::cnf::ID_CHARS;
|
use crate::cnf::ID_CHARS;
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::dbs::Options;
|
use crate::dbs::Options;
|
||||||
|
@ -6,13 +7,17 @@ use crate::err::Error;
|
||||||
use crate::sql::{escape::escape_rid, Array, Number, Object, Strand, Thing, Uuid, Value};
|
use crate::sql::{escape::escape_rid, Array, Number, Object, Strand, Thing, Uuid, Value};
|
||||||
use derive::Key;
|
use derive::Key;
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
|
use range::IdRange;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
use std::ops::{Bound, Deref};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
|
pub mod range;
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 1)]
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
@ -33,6 +38,7 @@ pub enum Id {
|
||||||
Array(Array),
|
Array(Array),
|
||||||
Object(Object),
|
Object(Object),
|
||||||
Generate(Gen),
|
Generate(Gen),
|
||||||
|
Range(Box<IdRange>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i64> for Id {
|
impl From<i64> for Id {
|
||||||
|
@ -97,25 +103,25 @@ impl From<&String> for Id {
|
||||||
|
|
||||||
impl From<Vec<&str>> for Id {
|
impl From<Vec<&str>> for Id {
|
||||||
fn from(v: Vec<&str>) -> Self {
|
fn from(v: Vec<&str>) -> Self {
|
||||||
Id::Array(v.into())
|
Self::Array(v.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<String>> for Id {
|
impl From<Vec<String>> for Id {
|
||||||
fn from(v: Vec<String>) -> Self {
|
fn from(v: Vec<String>) -> Self {
|
||||||
Id::Array(v.into())
|
Self::Array(v.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<Value>> for Id {
|
impl From<Vec<Value>> for Id {
|
||||||
fn from(v: Vec<Value>) -> Self {
|
fn from(v: Vec<Value>) -> Self {
|
||||||
Id::Array(v.into())
|
Self::Array(v.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BTreeMap<String, Value>> for Id {
|
impl From<BTreeMap<String, Value>> for Id {
|
||||||
fn from(v: BTreeMap<String, Value>) -> Self {
|
fn from(v: BTreeMap<String, Value>) -> Self {
|
||||||
Id::Object(v.into())
|
Self::Object(v.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +135,42 @@ impl From<Number> for Id {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<IdRange> for Id {
|
||||||
|
fn from(v: IdRange) -> Self {
|
||||||
|
Self::Range(Box::new(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<(Bound<Id>, Bound<Id>)> for Id {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(v: (Bound<Id>, Bound<Id>)) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self::Range(Box::new(v.try_into()?)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Range> for Id {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(v: Range) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Id::Range(Box::new(v.try_into()?)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Value> for Id {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(v: Value) -> Result<Self, Self::Error> {
|
||||||
|
match v {
|
||||||
|
Value::Number(Number::Int(v)) => Ok(v.into()),
|
||||||
|
Value::Strand(v) => Ok(v.into()),
|
||||||
|
Value::Array(v) => Ok(v.into()),
|
||||||
|
Value::Object(v) => Ok(v.into()),
|
||||||
|
Value::Range(v) => v.deref().to_owned().try_into(),
|
||||||
|
v => Err(Error::IdInvalid {
|
||||||
|
value: v.kindof().to_string(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Thing> for Id {
|
impl From<Thing> for Id {
|
||||||
fn from(v: Thing) -> Self {
|
fn from(v: Thing) -> Self {
|
||||||
v.id
|
v.id
|
||||||
|
@ -160,6 +202,7 @@ impl Id {
|
||||||
Gen::Ulid => "ulid()".to_string(),
|
Gen::Ulid => "ulid()".to_string(),
|
||||||
Gen::Uuid => "uuid()".to_string(),
|
Gen::Uuid => "uuid()".to_string(),
|
||||||
},
|
},
|
||||||
|
Self::Range(v) => v.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,6 +219,7 @@ impl Display for Id {
|
||||||
Gen::Ulid => Display::fmt("ulid()", f),
|
Gen::Ulid => Display::fmt("ulid()", f),
|
||||||
Gen::Uuid => Display::fmt("uuid()", f),
|
Gen::Uuid => Display::fmt("uuid()", f),
|
||||||
},
|
},
|
||||||
|
Self::Range(v) => Display::fmt(v, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,6 +249,7 @@ impl Id {
|
||||||
Gen::Ulid => Ok(Self::ulid()),
|
Gen::Ulid => Ok(Self::ulid()),
|
||||||
Gen::Uuid => Ok(Self::uuid()),
|
Gen::Uuid => Ok(Self::uuid()),
|
||||||
},
|
},
|
||||||
|
Id::Range(v) => Ok(Id::Range(Box::new(v.compute(stk, ctx, opt, doc).await?))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
185
core/src/sql/id/range.rs
Normal file
185
core/src/sql/id/range.rs
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
use super::Id;
|
||||||
|
use crate::{
|
||||||
|
ctx::Context,
|
||||||
|
dbs::Options,
|
||||||
|
doc::CursorDoc,
|
||||||
|
err::Error,
|
||||||
|
sql::{Range, Value},
|
||||||
|
};
|
||||||
|
use reblessive::tree::Stk;
|
||||||
|
use revision::revisioned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{cmp::Ordering, fmt, ops::Bound};
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct IdRange {
|
||||||
|
pub beg: Bound<Id>,
|
||||||
|
pub end: Bound<Id>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<(Bound<Id>, Bound<Id>)> for IdRange {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from((beg, end): (Bound<Id>, Bound<Id>)) -> Result<Self, Self::Error> {
|
||||||
|
if matches!(beg, Bound::Included(Id::Range(_)) | Bound::Excluded(Id::Range(_))) {
|
||||||
|
return Err(Error::IdInvalid {
|
||||||
|
value: "range".into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(end, Bound::Included(Id::Range(_)) | Bound::Excluded(Id::Range(_))) {
|
||||||
|
return Err(Error::IdInvalid {
|
||||||
|
value: "range".into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(IdRange {
|
||||||
|
beg,
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Range> for IdRange {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(v: Range) -> Result<Self, Self::Error> {
|
||||||
|
let beg = match v.beg {
|
||||||
|
Bound::Included(beg) => Bound::Included(Id::try_from(beg)?),
|
||||||
|
Bound::Excluded(beg) => Bound::Excluded(Id::try_from(beg)?),
|
||||||
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
let end = match v.end {
|
||||||
|
Bound::Included(end) => Bound::Included(Id::try_from(end)?),
|
||||||
|
Bound::Excluded(end) => Bound::Excluded(Id::try_from(end)?),
|
||||||
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The TryFrom implementation ensures that the bounds do not contain an `Id::Range` value
|
||||||
|
IdRange::try_from((beg, end))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Value> for IdRange {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(v: Value) -> Result<Self, Self::Error> {
|
||||||
|
match v {
|
||||||
|
Value::Range(v) => IdRange::try_from(*v),
|
||||||
|
v => Err(Error::IdInvalid {
|
||||||
|
value: v.kindof().to_string(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for IdRange {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for IdRange {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
match &self.beg {
|
||||||
|
Bound::Unbounded => match &other.beg {
|
||||||
|
Bound::Unbounded => Ordering::Equal,
|
||||||
|
_ => Ordering::Less,
|
||||||
|
},
|
||||||
|
Bound::Included(v) => match &other.beg {
|
||||||
|
Bound::Unbounded => Ordering::Greater,
|
||||||
|
Bound::Included(w) => match v.cmp(w) {
|
||||||
|
Ordering::Equal => match &self.end {
|
||||||
|
Bound::Unbounded => match &other.end {
|
||||||
|
Bound::Unbounded => Ordering::Equal,
|
||||||
|
_ => Ordering::Greater,
|
||||||
|
},
|
||||||
|
Bound::Included(v) => match &other.end {
|
||||||
|
Bound::Unbounded => Ordering::Less,
|
||||||
|
Bound::Included(w) => v.cmp(w),
|
||||||
|
_ => Ordering::Greater,
|
||||||
|
},
|
||||||
|
Bound::Excluded(v) => match &other.end {
|
||||||
|
Bound::Excluded(w) => v.cmp(w),
|
||||||
|
_ => Ordering::Less,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ordering => ordering,
|
||||||
|
},
|
||||||
|
_ => Ordering::Less,
|
||||||
|
},
|
||||||
|
Bound::Excluded(v) => match &other.beg {
|
||||||
|
Bound::Excluded(w) => match v.cmp(w) {
|
||||||
|
Ordering::Equal => match &self.end {
|
||||||
|
Bound::Unbounded => match &other.end {
|
||||||
|
Bound::Unbounded => Ordering::Equal,
|
||||||
|
_ => Ordering::Greater,
|
||||||
|
},
|
||||||
|
Bound::Included(v) => match &other.end {
|
||||||
|
Bound::Unbounded => Ordering::Less,
|
||||||
|
Bound::Included(w) => v.cmp(w),
|
||||||
|
_ => Ordering::Greater,
|
||||||
|
},
|
||||||
|
Bound::Excluded(v) => match &other.end {
|
||||||
|
Bound::Excluded(w) => v.cmp(w),
|
||||||
|
_ => Ordering::Less,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ordering => ordering,
|
||||||
|
},
|
||||||
|
_ => Ordering::Greater,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for IdRange {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match &self.beg {
|
||||||
|
Bound::Unbounded => write!(f, ""),
|
||||||
|
Bound::Included(v) => write!(f, "{v}"),
|
||||||
|
Bound::Excluded(v) => write!(f, "{v}>"),
|
||||||
|
}?;
|
||||||
|
match &self.end {
|
||||||
|
Bound::Unbounded => write!(f, ".."),
|
||||||
|
Bound::Excluded(v) => write!(f, "..{v}"),
|
||||||
|
Bound::Included(v) => write!(f, "..={v}"),
|
||||||
|
}?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdRange {
|
||||||
|
/// Process the values in the bounds for this IdRange
|
||||||
|
pub(crate) async fn compute(
|
||||||
|
&self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
doc: Option<&CursorDoc>,
|
||||||
|
) -> Result<IdRange, Error> {
|
||||||
|
let beg = match &self.beg {
|
||||||
|
Bound::Included(beg) => {
|
||||||
|
Bound::Included(stk.run(|stk| beg.compute(stk, ctx, opt, doc)).await?)
|
||||||
|
}
|
||||||
|
Bound::Excluded(beg) => {
|
||||||
|
Bound::Excluded(stk.run(|stk| beg.compute(stk, ctx, opt, doc)).await?)
|
||||||
|
}
|
||||||
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
let end = match &self.end {
|
||||||
|
Bound::Included(end) => {
|
||||||
|
Bound::Included(stk.run(|stk| end.compute(stk, ctx, opt, doc)).await?)
|
||||||
|
}
|
||||||
|
Bound::Excluded(end) => {
|
||||||
|
Bound::Excluded(stk.run(|stk| end.compute(stk, ctx, opt, doc)).await?)
|
||||||
|
}
|
||||||
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The TryFrom implementation ensures that the bounds do not contain an `Id::Range` value
|
||||||
|
IdRange::try_from((beg, end))
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ pub enum Kind {
|
||||||
Set(Box<Kind>, Option<u64>),
|
Set(Box<Kind>, Option<u64>),
|
||||||
Array(Box<Kind>, Option<u64>),
|
Array(Box<Kind>, Option<u64>),
|
||||||
Function(Option<Vec<Kind>>, Option<Box<Kind>>),
|
Function(Option<Vec<Kind>>, Option<Box<Kind>>),
|
||||||
|
Range,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Kind {
|
impl Default for Kind {
|
||||||
|
@ -73,7 +74,8 @@ impl Kind {
|
||||||
| Kind::Uuid
|
| Kind::Uuid
|
||||||
| Kind::Record(_)
|
| Kind::Record(_)
|
||||||
| Kind::Geometry(_)
|
| Kind::Geometry(_)
|
||||||
| Kind::Function(_, _) => return None,
|
| Kind::Function(_, _)
|
||||||
|
| Kind::Range => return None,
|
||||||
Kind::Option(x) => {
|
Kind::Option(x) => {
|
||||||
this = x;
|
this = x;
|
||||||
}
|
}
|
||||||
|
@ -137,6 +139,7 @@ impl Display for Kind {
|
||||||
(k, Some(l)) => write!(f, "array<{k}, {l}>"),
|
(k, Some(l)) => write!(f, "array<{k}, {l}>"),
|
||||||
},
|
},
|
||||||
Kind::Either(k) => write!(f, "{}", Fmt::verbar_separated(k)),
|
Kind::Either(k) => write!(f, "{}", Fmt::verbar_separated(k)),
|
||||||
|
Kind::Range => f.write_str("range"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,7 @@ pub use self::geometry::Geometry;
|
||||||
pub use self::graph::Graph;
|
pub use self::graph::Graph;
|
||||||
pub use self::group::Group;
|
pub use self::group::Group;
|
||||||
pub use self::group::Groups;
|
pub use self::group::Groups;
|
||||||
|
pub use self::id::range::IdRange;
|
||||||
pub use self::id::Id;
|
pub use self::id::Id;
|
||||||
pub use self::ident::Ident;
|
pub use self::ident::Ident;
|
||||||
pub use self::idiom::Idiom;
|
pub use self::idiom::Idiom;
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
|
use crate::cnf::GENERATION_ALLOCATION_LIMIT;
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::dbs::Options;
|
use crate::dbs::Options;
|
||||||
use crate::doc::CursorDoc;
|
use crate::doc::CursorDoc;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql::Cond;
|
use crate::sql::{Number, Subquery, Value};
|
||||||
use crate::sql::Expression;
|
|
||||||
use crate::sql::Ident;
|
|
||||||
use crate::sql::Idiom;
|
|
||||||
use crate::sql::Operator;
|
|
||||||
use crate::sql::Part;
|
|
||||||
use crate::sql::Thing;
|
|
||||||
use crate::sql::{strand::no_nul_bytes, Id, Value};
|
|
||||||
use crate::syn;
|
use crate::syn;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
|
@ -19,7 +13,8 @@ use std::fmt;
|
||||||
use std::ops::Bound;
|
use std::ops::Bound;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
const ID: &str = "id";
|
use super::Id;
|
||||||
|
|
||||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range";
|
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range";
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 1)]
|
||||||
|
@ -28,10 +23,8 @@ pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range";
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Range {
|
pub struct Range {
|
||||||
#[serde(with = "no_nul_bytes")]
|
pub beg: Bound<Value>,
|
||||||
pub tb: String,
|
pub end: Bound<Value>,
|
||||||
pub beg: Bound<Id>,
|
|
||||||
pub end: Bound<Id>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Range {
|
impl FromStr for Range {
|
||||||
|
@ -51,108 +44,54 @@ impl TryFrom<&str> for Range {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Range {
|
impl From<(Bound<Id>, Bound<Id>)> for Range {
|
||||||
/// Construct a new range
|
fn from(v: (Bound<Id>, Bound<Id>)) -> Self {
|
||||||
pub fn new(tb: String, beg: Bound<Id>, end: Bound<Id>) -> Self {
|
fn convert(v: Bound<Id>) -> Bound<Value> {
|
||||||
Self {
|
match v {
|
||||||
tb,
|
Bound::Included(v) => Bound::Included(v.into()),
|
||||||
beg,
|
Bound::Excluded(v) => Bound::Excluded(v.into()),
|
||||||
end,
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert `Range` to `Cond`
|
Self {
|
||||||
pub fn to_cond(self) -> Option<Cond> {
|
beg: convert(v.0),
|
||||||
match (self.beg, self.end) {
|
end: convert(v.1),
|
||||||
(Bound::Unbounded, Bound::Unbounded) => None,
|
|
||||||
(Bound::Unbounded, Bound::Excluded(id)) => {
|
|
||||||
Some(Cond(Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::LessThan,
|
|
||||||
Thing::from((self.tb, id)).into(),
|
|
||||||
)))))
|
|
||||||
}
|
}
|
||||||
(Bound::Unbounded, Bound::Included(id)) => {
|
|
||||||
Some(Cond(Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::LessThanOrEqual,
|
|
||||||
Thing::from((self.tb, id)).into(),
|
|
||||||
)))))
|
|
||||||
}
|
}
|
||||||
(Bound::Excluded(id), Bound::Unbounded) => {
|
}
|
||||||
Some(Cond(Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
impl TryInto<std::ops::Range<i64>> for Range {
|
||||||
Operator::MoreThan,
|
type Error = Error;
|
||||||
Thing::from((self.tb, id)).into(),
|
fn try_into(self) -> Result<std::ops::Range<i64>, Self::Error> {
|
||||||
)))))
|
let beg = match self.beg {
|
||||||
|
Bound::Unbounded => i64::MIN,
|
||||||
|
Bound::Included(beg) => to_i64(beg)?,
|
||||||
|
Bound::Excluded(beg) => to_i64(beg)? + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let end = match self.end {
|
||||||
|
Bound::Unbounded => i64::MAX,
|
||||||
|
Bound::Included(end) => to_i64(end)? + 1,
|
||||||
|
Bound::Excluded(end) => to_i64(end)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (beg + *GENERATION_ALLOCATION_LIMIT as i64) < end {
|
||||||
|
Err(Error::RangeTooBig {
|
||||||
|
max: *GENERATION_ALLOCATION_LIMIT,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(beg..end)
|
||||||
}
|
}
|
||||||
(Bound::Included(id), Bound::Unbounded) => {
|
|
||||||
Some(Cond(Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::MoreThanOrEqual,
|
|
||||||
Thing::from((self.tb, id)).into(),
|
|
||||||
)))))
|
|
||||||
}
|
|
||||||
(Bound::Included(lid), Bound::Included(rid)) => {
|
|
||||||
Some(Cond(Value::Expression(Box::new(Expression::new(
|
|
||||||
Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::MoreThanOrEqual,
|
|
||||||
Thing::from((self.tb.clone(), lid)).into(),
|
|
||||||
))),
|
|
||||||
Operator::And,
|
|
||||||
Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::LessThanOrEqual,
|
|
||||||
Thing::from((self.tb, rid)).into(),
|
|
||||||
))),
|
|
||||||
)))))
|
|
||||||
}
|
|
||||||
(Bound::Included(lid), Bound::Excluded(rid)) => {
|
|
||||||
Some(Cond(Value::Expression(Box::new(Expression::new(
|
|
||||||
Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::MoreThanOrEqual,
|
|
||||||
Thing::from((self.tb.clone(), lid)).into(),
|
|
||||||
))),
|
|
||||||
Operator::And,
|
|
||||||
Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::LessThan,
|
|
||||||
Thing::from((self.tb, rid)).into(),
|
|
||||||
))),
|
|
||||||
)))))
|
|
||||||
}
|
|
||||||
(Bound::Excluded(lid), Bound::Included(rid)) => {
|
|
||||||
Some(Cond(Value::Expression(Box::new(Expression::new(
|
|
||||||
Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::MoreThan,
|
|
||||||
Thing::from((self.tb.clone(), lid)).into(),
|
|
||||||
))),
|
|
||||||
Operator::And,
|
|
||||||
Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::LessThanOrEqual,
|
|
||||||
Thing::from((self.tb, rid)).into(),
|
|
||||||
))),
|
|
||||||
)))))
|
|
||||||
}
|
|
||||||
(Bound::Excluded(lid), Bound::Excluded(rid)) => {
|
|
||||||
Some(Cond(Value::Expression(Box::new(Expression::new(
|
|
||||||
Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::MoreThan,
|
|
||||||
Thing::from((self.tb.clone(), lid)).into(),
|
|
||||||
))),
|
|
||||||
Operator::And,
|
|
||||||
Value::Expression(Box::new(Expression::new(
|
|
||||||
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
|
||||||
Operator::LessThan,
|
|
||||||
Thing::from((self.tb, rid)).into(),
|
|
||||||
))),
|
|
||||||
)))))
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Range {
|
||||||
|
/// Construct a new range
|
||||||
|
pub fn new(beg: Bound<Value>, end: Bound<Value>) -> Self {
|
||||||
|
Self {
|
||||||
|
beg,
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,15 +104,14 @@ impl Range {
|
||||||
doc: Option<&CursorDoc>,
|
doc: Option<&CursorDoc>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
Ok(Value::Range(Box::new(Range {
|
Ok(Value::Range(Box::new(Range {
|
||||||
tb: self.tb.clone(),
|
|
||||||
beg: match &self.beg {
|
beg: match &self.beg {
|
||||||
Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?),
|
Bound::Included(v) => Bound::Included(v.compute(stk, ctx, opt, doc).await?),
|
||||||
Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?),
|
Bound::Excluded(v) => Bound::Excluded(v.compute(stk, ctx, opt, doc).await?),
|
||||||
Bound::Unbounded => Bound::Unbounded,
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
},
|
},
|
||||||
end: match &self.end {
|
end: match &self.end {
|
||||||
Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?),
|
Bound::Included(v) => Bound::Included(v.compute(stk, ctx, opt, doc).await?),
|
||||||
Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?),
|
Bound::Excluded(v) => Bound::Excluded(v.compute(stk, ctx, opt, doc).await?),
|
||||||
Bound::Unbounded => Bound::Unbounded,
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
},
|
},
|
||||||
})))
|
})))
|
||||||
|
@ -182,74 +120,94 @@ impl Range {
|
||||||
|
|
||||||
impl PartialOrd for Range {
|
impl PartialOrd for Range {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
match self.tb.partial_cmp(&other.tb) {
|
Some(self.cmp(other))
|
||||||
Some(Ordering::Equal) => match &self.beg {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Range {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
match &self.beg {
|
||||||
Bound::Unbounded => match &other.beg {
|
Bound::Unbounded => match &other.beg {
|
||||||
Bound::Unbounded => Some(Ordering::Equal),
|
Bound::Unbounded => Ordering::Equal,
|
||||||
_ => Some(Ordering::Less),
|
_ => Ordering::Less,
|
||||||
},
|
},
|
||||||
Bound::Included(v) => match &other.beg {
|
Bound::Included(v) => match &other.beg {
|
||||||
Bound::Unbounded => Some(Ordering::Greater),
|
Bound::Unbounded => Ordering::Greater,
|
||||||
Bound::Included(w) => match v.partial_cmp(w) {
|
Bound::Included(w) => match v.cmp(w) {
|
||||||
Some(Ordering::Equal) => match &self.end {
|
Ordering::Equal => match &self.end {
|
||||||
Bound::Unbounded => match &other.end {
|
Bound::Unbounded => match &other.end {
|
||||||
Bound::Unbounded => Some(Ordering::Equal),
|
Bound::Unbounded => Ordering::Equal,
|
||||||
_ => Some(Ordering::Greater),
|
_ => Ordering::Greater,
|
||||||
},
|
},
|
||||||
Bound::Included(v) => match &other.end {
|
Bound::Included(v) => match &other.end {
|
||||||
Bound::Unbounded => Some(Ordering::Less),
|
Bound::Unbounded => Ordering::Less,
|
||||||
Bound::Included(w) => v.partial_cmp(w),
|
Bound::Included(w) => v.cmp(w),
|
||||||
_ => Some(Ordering::Greater),
|
_ => Ordering::Greater,
|
||||||
},
|
},
|
||||||
Bound::Excluded(v) => match &other.end {
|
Bound::Excluded(v) => match &other.end {
|
||||||
Bound::Excluded(w) => v.partial_cmp(w),
|
Bound::Excluded(w) => v.cmp(w),
|
||||||
_ => Some(Ordering::Less),
|
_ => Ordering::Less,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ordering => ordering,
|
ordering => ordering,
|
||||||
},
|
},
|
||||||
_ => Some(Ordering::Less),
|
_ => Ordering::Less,
|
||||||
},
|
},
|
||||||
Bound::Excluded(v) => match &other.beg {
|
Bound::Excluded(v) => match &other.beg {
|
||||||
Bound::Excluded(w) => match v.partial_cmp(w) {
|
Bound::Excluded(w) => match v.cmp(w) {
|
||||||
Some(Ordering::Equal) => match &self.end {
|
Ordering::Equal => match &self.end {
|
||||||
Bound::Unbounded => match &other.end {
|
Bound::Unbounded => match &other.end {
|
||||||
Bound::Unbounded => Some(Ordering::Equal),
|
Bound::Unbounded => Ordering::Equal,
|
||||||
_ => Some(Ordering::Greater),
|
_ => Ordering::Greater,
|
||||||
},
|
},
|
||||||
Bound::Included(v) => match &other.end {
|
Bound::Included(v) => match &other.end {
|
||||||
Bound::Unbounded => Some(Ordering::Less),
|
Bound::Unbounded => Ordering::Less,
|
||||||
Bound::Included(w) => v.partial_cmp(w),
|
Bound::Included(w) => v.cmp(w),
|
||||||
_ => Some(Ordering::Greater),
|
_ => Ordering::Greater,
|
||||||
},
|
},
|
||||||
Bound::Excluded(v) => match &other.end {
|
Bound::Excluded(v) => match &other.end {
|
||||||
Bound::Excluded(w) => v.partial_cmp(w),
|
Bound::Excluded(w) => v.cmp(w),
|
||||||
_ => Some(Ordering::Less),
|
_ => Ordering::Less,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ordering => ordering,
|
ordering => ordering,
|
||||||
},
|
},
|
||||||
_ => Some(Ordering::Greater),
|
_ => Ordering::Greater,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
ordering => ordering,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Range {
|
impl fmt::Display for Range {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}:", self.tb)?;
|
fn bound_value(v: &Value) -> Value {
|
||||||
|
if v.can_be_range_bound() {
|
||||||
|
v.to_owned()
|
||||||
|
} else {
|
||||||
|
Value::Subquery(Box::new(Subquery::Value(v.to_owned())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match &self.beg {
|
match &self.beg {
|
||||||
Bound::Unbounded => write!(f, ""),
|
Bound::Unbounded => write!(f, ""),
|
||||||
Bound::Included(id) => write!(f, "{id}"),
|
Bound::Included(v) => write!(f, "{}", bound_value(v)),
|
||||||
Bound::Excluded(id) => write!(f, "{id}>"),
|
Bound::Excluded(v) => write!(f, "{}>", bound_value(v)),
|
||||||
}?;
|
}?;
|
||||||
match &self.end {
|
match &self.end {
|
||||||
Bound::Unbounded => write!(f, ".."),
|
Bound::Unbounded => write!(f, ".."),
|
||||||
Bound::Excluded(id) => write!(f, "..{id}"),
|
Bound::Excluded(v) => write!(f, "..{}", bound_value(v)),
|
||||||
Bound::Included(id) => write!(f, "..={id}"),
|
Bound::Included(v) => write!(f, "..={}", bound_value(v)),
|
||||||
}?;
|
}?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_i64(v: Value) -> Result<i64, Error> {
|
||||||
|
match v {
|
||||||
|
Value::Number(Number::Int(v)) => Ok(v),
|
||||||
|
v => Err(Error::InvalidRangeValue {
|
||||||
|
expected: "int".to_string(),
|
||||||
|
found: v.kindof().to_string(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ impl AlterTableStatement {
|
||||||
dt.changefeed = *changefeed;
|
dt.changefeed = *changefeed;
|
||||||
}
|
}
|
||||||
if let Some(ref comment) = &self.comment {
|
if let Some(ref comment) = &self.comment {
|
||||||
dt.comment = comment.clone();
|
dt.comment.clone_from(comment);
|
||||||
}
|
}
|
||||||
if let Some(ref kind) = &self.kind {
|
if let Some(ref kind) = &self.kind {
|
||||||
dt.kind = kind.clone();
|
dt.kind = kind.clone();
|
||||||
|
|
|
@ -8,6 +8,7 @@ use reblessive::tree::Stk;
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 1)]
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||||
|
@ -19,6 +20,22 @@ pub struct ForeachStatement {
|
||||||
pub block: Block,
|
pub block: Block,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ForeachIter {
|
||||||
|
Array(std::vec::IntoIter<Value>),
|
||||||
|
Range(std::iter::Map<std::ops::Range<i64>, fn(i64) -> Value>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ForeachIter {
|
||||||
|
type Item = Value;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self {
|
||||||
|
ForeachIter::Array(iter) => iter.next(),
|
||||||
|
ForeachIter::Range(iter) => iter.next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ForeachStatement {
|
impl ForeachStatement {
|
||||||
/// Check if we require a writeable transaction
|
/// Check if we require a writeable transaction
|
||||||
pub(crate) fn writeable(&self) -> bool {
|
pub(crate) fn writeable(&self) -> bool {
|
||||||
|
@ -35,10 +52,23 @@ impl ForeachStatement {
|
||||||
doc: Option<&CursorDoc>,
|
doc: Option<&CursorDoc>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check the loop data
|
// Check the loop data
|
||||||
match &self.range.compute(stk, ctx, opt, doc).await? {
|
let data = self.range.compute(stk, ctx, opt, doc).await?;
|
||||||
Value::Array(arr) => {
|
let iter = match data {
|
||||||
|
Value::Array(arr) => ForeachIter::Array(arr.into_iter()),
|
||||||
|
Value::Range(r) => {
|
||||||
|
let r: std::ops::Range<i64> = r.deref().to_owned().try_into()?;
|
||||||
|
ForeachIter::Range(r.map(Value::from))
|
||||||
|
}
|
||||||
|
|
||||||
|
v => {
|
||||||
|
return Err(Error::InvalidStatementTarget {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Loop over the values
|
// Loop over the values
|
||||||
'foreach: for v in arr.iter() {
|
'foreach: for v in iter {
|
||||||
// Duplicate context
|
// Duplicate context
|
||||||
let ctx = MutableContext::new(ctx).freeze();
|
let ctx = MutableContext::new(ctx).freeze();
|
||||||
// Set the current parameter
|
// Set the current parameter
|
||||||
|
@ -61,9 +91,7 @@ impl ForeachStatement {
|
||||||
Entry::Value(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
Entry::Value(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
||||||
Entry::Break(v) => v.compute(&ctx, opt, doc).await,
|
Entry::Break(v) => v.compute(&ctx, opt, doc).await,
|
||||||
Entry::Continue(v) => v.compute(&ctx, opt, doc).await,
|
Entry::Continue(v) => v.compute(&ctx, opt, doc).await,
|
||||||
Entry::Foreach(v) => {
|
Entry::Foreach(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
||||||
stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await
|
|
||||||
}
|
|
||||||
Entry::Ifelse(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
Entry::Ifelse(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
||||||
Entry::Select(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
Entry::Select(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
||||||
Entry::Create(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
Entry::Create(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
||||||
|
@ -95,11 +123,6 @@ impl ForeachStatement {
|
||||||
// Ok all good
|
// Ok all good
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
v => Err(Error::InvalidStatementTarget {
|
|
||||||
value: v.to_string(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ForeachStatement {
|
impl Display for ForeachStatement {
|
||||||
|
|
|
@ -4,8 +4,8 @@ use crate::doc::CursorDoc;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::idx::planner::QueryPlanner;
|
use crate::idx::planner::QueryPlanner;
|
||||||
use crate::sql::{
|
use crate::sql::{
|
||||||
Cond, Explain, Fetchs, Field, Fields, Groups, Idioms, Limit, Orders, Splits, Start, Timeout,
|
Cond, Explain, Fetchs, Field, Fields, Groups, Id, Idioms, Limit, Orders, Splits, Start,
|
||||||
Value, Values, Version, With,
|
Timeout, Value, Values, Version, With,
|
||||||
};
|
};
|
||||||
use derive::Store;
|
use derive::Store;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
|
@ -101,14 +101,10 @@ impl SelectStatement {
|
||||||
|
|
||||||
planner.add_iterables(stk, ctx, t, &mut i).await?;
|
planner.add_iterables(stk, ctx, t, &mut i).await?;
|
||||||
}
|
}
|
||||||
Value::Thing(v) => i.ingest(Iterable::Thing(v)),
|
Value::Thing(v) => match &v.id {
|
||||||
Value::Range(v) => {
|
Id::Range(r) => i.ingest(Iterable::TableRange(v.tb, *r.to_owned())),
|
||||||
if self.only && !limit_is_one_or_zero {
|
_ => i.ingest(Iterable::Thing(v)),
|
||||||
return Err(Error::SingleOnlyOutput);
|
},
|
||||||
}
|
|
||||||
|
|
||||||
i.ingest(Iterable::Range(*v))
|
|
||||||
}
|
|
||||||
Value::Edges(v) => {
|
Value::Edges(v) => {
|
||||||
if self.only && !limit_is_one_or_zero {
|
if self.only && !limit_is_one_or_zero {
|
||||||
return Err(Error::SingleOnlyOutput);
|
return Err(Error::SingleOnlyOutput);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use super::id::range::IdRange;
|
||||||
|
use super::{Cond, Expression, Ident, Idiom, Operator, Part, Table};
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::dbs::Options;
|
use crate::dbs::Options;
|
||||||
use crate::doc::CursorDoc;
|
use crate::doc::CursorDoc;
|
||||||
|
@ -9,10 +11,10 @@ use reblessive::tree::Stk;
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::ops::Bound;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::Table;
|
const ID: &str = "id";
|
||||||
|
|
||||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing";
|
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing";
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 1)]
|
||||||
|
@ -26,6 +28,110 @@ pub struct Thing {
|
||||||
pub id: Id,
|
pub id: Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Thing {
|
||||||
|
/// Convert `Thing` to `Cond`
|
||||||
|
pub fn to_cond(self) -> Option<Cond> {
|
||||||
|
match &self.id {
|
||||||
|
Id::Range(r) => match (&r.beg, &r.end) {
|
||||||
|
(Bound::Unbounded, Bound::Unbounded) => None,
|
||||||
|
(Bound::Unbounded, Bound::Excluded(id)) => {
|
||||||
|
Some(Cond(Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::LessThan,
|
||||||
|
Thing::from((self.tb, id.to_owned())).into(),
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
(Bound::Unbounded, Bound::Included(id)) => {
|
||||||
|
Some(Cond(Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::LessThanOrEqual,
|
||||||
|
Thing::from((self.tb, id.to_owned())).into(),
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
(Bound::Excluded(id), Bound::Unbounded) => {
|
||||||
|
Some(Cond(Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::MoreThan,
|
||||||
|
Thing::from((self.tb, id.to_owned())).into(),
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
(Bound::Included(id), Bound::Unbounded) => {
|
||||||
|
Some(Cond(Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::MoreThanOrEqual,
|
||||||
|
Thing::from((self.tb, id.to_owned())).into(),
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
(Bound::Included(lid), Bound::Included(rid)) => {
|
||||||
|
Some(Cond(Value::Expression(Box::new(Expression::new(
|
||||||
|
Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::MoreThanOrEqual,
|
||||||
|
Thing::from((self.tb.clone(), lid.to_owned())).into(),
|
||||||
|
))),
|
||||||
|
Operator::And,
|
||||||
|
Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::LessThanOrEqual,
|
||||||
|
Thing::from((self.tb, rid.to_owned())).into(),
|
||||||
|
))),
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
(Bound::Included(lid), Bound::Excluded(rid)) => {
|
||||||
|
Some(Cond(Value::Expression(Box::new(Expression::new(
|
||||||
|
Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::MoreThanOrEqual,
|
||||||
|
Thing::from((self.tb.clone(), lid.to_owned())).into(),
|
||||||
|
))),
|
||||||
|
Operator::And,
|
||||||
|
Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::LessThan,
|
||||||
|
Thing::from((self.tb, rid.to_owned())).into(),
|
||||||
|
))),
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
(Bound::Excluded(lid), Bound::Included(rid)) => {
|
||||||
|
Some(Cond(Value::Expression(Box::new(Expression::new(
|
||||||
|
Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::MoreThan,
|
||||||
|
Thing::from((self.tb.clone(), lid.to_owned())).into(),
|
||||||
|
))),
|
||||||
|
Operator::And,
|
||||||
|
Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::LessThanOrEqual,
|
||||||
|
Thing::from((self.tb, rid.to_owned())).into(),
|
||||||
|
))),
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
(Bound::Excluded(lid), Bound::Excluded(rid)) => {
|
||||||
|
Some(Cond(Value::Expression(Box::new(Expression::new(
|
||||||
|
Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::MoreThan,
|
||||||
|
Thing::from((self.tb.clone(), lid.to_owned())).into(),
|
||||||
|
))),
|
||||||
|
Operator::And,
|
||||||
|
Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::LessThan,
|
||||||
|
Thing::from((self.tb, rid.to_owned())).into(),
|
||||||
|
))),
|
||||||
|
)))))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => Some(Cond(Value::Expression(Box::new(Expression::new(
|
||||||
|
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
|
||||||
|
Operator::Equal,
|
||||||
|
Thing::from((self.tb, self.id)).into(),
|
||||||
|
))))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<(&str, Id)> for Thing {
|
impl From<(&str, Id)> for Thing {
|
||||||
fn from((tb, id): (&str, Id)) -> Self {
|
fn from((tb, id): (&str, Id)) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -44,6 +150,24 @@ impl From<(String, Id)> for Thing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<(&str, IdRange)> for Thing {
|
||||||
|
fn from((tb, id): (&str, IdRange)) -> Self {
|
||||||
|
Self {
|
||||||
|
tb: tb.to_owned(),
|
||||||
|
id: id.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(String, IdRange)> for Thing {
|
||||||
|
fn from((tb, id): (String, IdRange)) -> Self {
|
||||||
|
Self {
|
||||||
|
tb,
|
||||||
|
id: id.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<(String, String)> for Thing {
|
impl From<(String, String)> for Thing {
|
||||||
fn from((tb, id): (String, String)) -> Self {
|
fn from((tb, id): (String, String)) -> Self {
|
||||||
Self::from((tb, Id::from(id)))
|
Self::from((tb, Id::from(id)))
|
||||||
|
|
|
@ -388,7 +388,6 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn range() {
|
fn range() {
|
||||||
let range = Box::new(Range {
|
let range = Box::new(Range {
|
||||||
tb: "foo".to_owned(),
|
|
||||||
beg: Bound::Included("foo".into()),
|
beg: Bound::Included("foo".into()),
|
||||||
end: Bound::Unbounded,
|
end: Bound::Unbounded,
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::dbs::Options;
|
||||||
use crate::doc::CursorDoc;
|
use crate::doc::CursorDoc;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::fnc::util::string::fuzzy::Fuzzy;
|
use crate::fnc::util::string::fuzzy::Fuzzy;
|
||||||
|
use crate::sql::id::range::IdRange;
|
||||||
use crate::sql::statements::info::InfoStructure;
|
use crate::sql::statements::info::InfoStructure;
|
||||||
use crate::sql::Closure;
|
use crate::sql::Closure;
|
||||||
use crate::sql::{
|
use crate::sql::{
|
||||||
|
@ -28,7 +29,7 @@ use std::cmp::Ordering;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Display, Formatter, Write};
|
use std::fmt::{self, Display, Formatter, Write};
|
||||||
use std::ops::Deref;
|
use std::ops::{Bound, Deref};
|
||||||
|
|
||||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Value";
|
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Value";
|
||||||
|
|
||||||
|
@ -263,6 +264,12 @@ impl From<Range> for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Box<Range>> for Value {
|
||||||
|
fn from(v: Box<Range>) -> Self {
|
||||||
|
Value::Range(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Edges> for Value {
|
impl From<Edges> for Value {
|
||||||
fn from(v: Edges) -> Self {
|
fn from(v: Edges) -> Self {
|
||||||
Value::Edges(Box::new(v))
|
Value::Edges(Box::new(v))
|
||||||
|
@ -566,6 +573,27 @@ impl From<Option<Datetime>> for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<IdRange> for Value {
|
||||||
|
fn from(v: IdRange) -> Self {
|
||||||
|
let beg = match v.beg {
|
||||||
|
Bound::Included(beg) => Bound::Included(Value::from(beg)),
|
||||||
|
Bound::Excluded(beg) => Bound::Excluded(Value::from(beg)),
|
||||||
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
let end = match v.end {
|
||||||
|
Bound::Included(end) => Bound::Included(Value::from(end)),
|
||||||
|
Bound::Excluded(end) => Bound::Excluded(Value::from(end)),
|
||||||
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::Range(Box::new(Range {
|
||||||
|
beg,
|
||||||
|
end,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Id> for Value {
|
impl From<Id> for Value {
|
||||||
fn from(v: Id) -> Self {
|
fn from(v: Id) -> Self {
|
||||||
match v {
|
match v {
|
||||||
|
@ -578,6 +606,7 @@ impl From<Id> for Value {
|
||||||
Gen::Ulid => Id::ulid().into(),
|
Gen::Ulid => Id::ulid().into(),
|
||||||
Gen::Uuid => Id::uuid().into(),
|
Gen::Uuid => Id::uuid().into(),
|
||||||
},
|
},
|
||||||
|
Id::Range(v) => v.deref().to_owned().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -923,6 +952,25 @@ impl Value {
|
||||||
matches!(self, Value::Thing(_))
|
matches!(self, Value::Thing(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if this Value is a single Thing
|
||||||
|
pub fn is_thing_single(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Value::Thing(t) => !matches!(t.id, Id::Range(_)),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this Value is a single Thing
|
||||||
|
pub fn is_thing_range(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Value::Thing(Thing {
|
||||||
|
id: Id::Range(_),
|
||||||
|
..
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if this Value is a Mock
|
/// Check if this Value is a Mock
|
||||||
pub fn is_mock(&self) -> bool {
|
pub fn is_mock(&self) -> bool {
|
||||||
matches!(self, Value::Mock(_))
|
matches!(self, Value::Mock(_))
|
||||||
|
@ -1223,6 +1271,7 @@ impl Value {
|
||||||
Self::Geometry(Geometry::MultiPolygon(_)) => "geometry<multipolygon>",
|
Self::Geometry(Geometry::MultiPolygon(_)) => "geometry<multipolygon>",
|
||||||
Self::Geometry(Geometry::Collection(_)) => "geometry<collection>",
|
Self::Geometry(Geometry::Collection(_)) => "geometry<collection>",
|
||||||
Self::Bytes(_) => "bytes",
|
Self::Bytes(_) => "bytes",
|
||||||
|
Self::Range(_) => "range",
|
||||||
_ => "incorrect type",
|
_ => "incorrect type",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1249,6 +1298,7 @@ impl Value {
|
||||||
Kind::Point => self.coerce_to_point().map(Value::from),
|
Kind::Point => self.coerce_to_point().map(Value::from),
|
||||||
Kind::Bytes => self.coerce_to_bytes().map(Value::from),
|
Kind::Bytes => self.coerce_to_bytes().map(Value::from),
|
||||||
Kind::Uuid => self.coerce_to_uuid().map(Value::from),
|
Kind::Uuid => self.coerce_to_uuid().map(Value::from),
|
||||||
|
Kind::Range => self.coerce_to_range().map(Value::from),
|
||||||
Kind::Function(_, _) => self.coerce_to_function().map(Value::from),
|
Kind::Function(_, _) => self.coerce_to_function().map(Value::from),
|
||||||
Kind::Set(t, l) => match l {
|
Kind::Set(t, l) => match l {
|
||||||
Some(l) => self.coerce_to_set_type_len(t, l).map(Value::from),
|
Some(l) => self.coerce_to_set_type_len(t, l).map(Value::from),
|
||||||
|
@ -1643,6 +1693,19 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to coerce this value to a `Range`
|
||||||
|
pub(crate) fn coerce_to_range(self) -> Result<Range, Error> {
|
||||||
|
match self {
|
||||||
|
// Ranges are allowed
|
||||||
|
Value::Range(v) => Ok(*v),
|
||||||
|
// Anything else raises an error
|
||||||
|
_ => Err(Error::CoerceTo {
|
||||||
|
from: self,
|
||||||
|
into: "range".into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to coerce this value to an `Geometry` point
|
/// Try to coerce this value to an `Geometry` point
|
||||||
pub(crate) fn coerce_to_point(self) -> Result<Geometry, Error> {
|
pub(crate) fn coerce_to_point(self) -> Result<Geometry, Error> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -1818,6 +1881,7 @@ impl Value {
|
||||||
Kind::Point => self.convert_to_point().map(Value::from),
|
Kind::Point => self.convert_to_point().map(Value::from),
|
||||||
Kind::Bytes => self.convert_to_bytes().map(Value::from),
|
Kind::Bytes => self.convert_to_bytes().map(Value::from),
|
||||||
Kind::Uuid => self.convert_to_uuid().map(Value::from),
|
Kind::Uuid => self.convert_to_uuid().map(Value::from),
|
||||||
|
Kind::Range => self.convert_to_range().map(Value::from),
|
||||||
Kind::Function(_, _) => self.convert_to_function().map(Value::from),
|
Kind::Function(_, _) => self.convert_to_function().map(Value::from),
|
||||||
Kind::Set(t, l) => match l {
|
Kind::Set(t, l) => match l {
|
||||||
Some(l) => self.convert_to_set_type_len(t, l).map(Value::from),
|
Some(l) => self.convert_to_set_type_len(t, l).map(Value::from),
|
||||||
|
@ -2206,6 +2270,10 @@ impl Value {
|
||||||
match self {
|
match self {
|
||||||
// Arrays are allowed
|
// Arrays are allowed
|
||||||
Value::Array(v) => Ok(v),
|
Value::Array(v) => Ok(v),
|
||||||
|
Value::Range(r) => {
|
||||||
|
let range: std::ops::Range<i64> = r.deref().to_owned().try_into()?;
|
||||||
|
Ok(range.into_iter().map(Value::from).collect::<Vec<Value>>().into())
|
||||||
|
}
|
||||||
// Anything else raises an error
|
// Anything else raises an error
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
from: self,
|
from: self,
|
||||||
|
@ -2214,6 +2282,26 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to convert this value to a `Range`
|
||||||
|
pub(crate) fn convert_to_range(self) -> Result<Range, Error> {
|
||||||
|
match self {
|
||||||
|
// Arrays with two elements are allowed
|
||||||
|
Value::Array(v) if v.len() == 2 => {
|
||||||
|
let mut v = v;
|
||||||
|
Ok(Range {
|
||||||
|
beg: Bound::Included(v.remove(0)),
|
||||||
|
end: Bound::Excluded(v.remove(0)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Value::Range(r) => Ok(*r),
|
||||||
|
// Anything else raises an error
|
||||||
|
_ => Err(Error::ConvertTo {
|
||||||
|
from: self,
|
||||||
|
into: "range".into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to convert this value to an `Geometry` point
|
/// Try to convert this value to an `Geometry` point
|
||||||
pub(crate) fn convert_to_point(self) -> Result<Geometry, Error> {
|
pub(crate) fn convert_to_point(self) -> Result<Geometry, Error> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -2588,6 +2676,19 @@ impl Value {
|
||||||
Value::Geometry(w) => v.contains(w),
|
Value::Geometry(w) => v.contains(w),
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
Value::Range(r) => {
|
||||||
|
let beg = match &r.beg {
|
||||||
|
Bound::Unbounded => true,
|
||||||
|
Bound::Included(beg) => beg.le(other),
|
||||||
|
Bound::Excluded(beg) => beg.lt(other),
|
||||||
|
};
|
||||||
|
|
||||||
|
beg && match &r.end {
|
||||||
|
Bound::Unbounded => true,
|
||||||
|
Bound::Included(end) => end.ge(other),
|
||||||
|
Bound::Excluded(end) => end.gt(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2654,6 +2755,25 @@ impl Value {
|
||||||
_ => self.partial_cmp(other),
|
_ => self.partial_cmp(other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn can_be_range_bound(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Value::None
|
||||||
|
| Value::Null | Value::Array(_)
|
||||||
|
| Value::Block(_)
|
||||||
|
| Value::Bool(_) | Value::Datetime(_)
|
||||||
|
| Value::Duration(_)
|
||||||
|
| Value::Geometry(_)
|
||||||
|
| Value::Number(_)
|
||||||
|
| Value::Object(_)
|
||||||
|
| Value::Param(_)
|
||||||
|
| Value::Strand(_)
|
||||||
|
| Value::Subquery(_)
|
||||||
|
| Value::Table(_)
|
||||||
|
| Value::Uuid(_)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Value {
|
impl fmt::Display for Value {
|
||||||
|
|
|
@ -183,6 +183,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
||||||
UniCase::ascii("POSTINGS_ORDER") => TokenKind::Keyword(Keyword::PostingsOrder),
|
UniCase::ascii("POSTINGS_ORDER") => TokenKind::Keyword(Keyword::PostingsOrder),
|
||||||
UniCase::ascii("PRUNE") => TokenKind::Keyword(Keyword::Prune),
|
UniCase::ascii("PRUNE") => TokenKind::Keyword(Keyword::Prune),
|
||||||
UniCase::ascii("PUNCT") => TokenKind::Keyword(Keyword::Punct),
|
UniCase::ascii("PUNCT") => TokenKind::Keyword(Keyword::Punct),
|
||||||
|
UniCase::ascii("RANGE") => TokenKind::Keyword(Keyword::Range),
|
||||||
UniCase::ascii("READONLY") => TokenKind::Keyword(Keyword::Readonly),
|
UniCase::ascii("READONLY") => TokenKind::Keyword(Keyword::Readonly),
|
||||||
UniCase::ascii("RELATE") => TokenKind::Keyword(Keyword::Relate),
|
UniCase::ascii("RELATE") => TokenKind::Keyword(Keyword::Relate),
|
||||||
UniCase::ascii("RELATION") => TokenKind::Keyword(Keyword::Relation),
|
UniCase::ascii("RELATION") => TokenKind::Keyword(Keyword::Relation),
|
||||||
|
|
|
@ -339,6 +339,7 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
|
||||||
UniCase::ascii("time::from::secs") => PathKind::Function,
|
UniCase::ascii("time::from::secs") => PathKind::Function,
|
||||||
UniCase::ascii("time::from::unix") => PathKind::Function,
|
UniCase::ascii("time::from::unix") => PathKind::Function,
|
||||||
//
|
//
|
||||||
|
UniCase::ascii("type::array") => PathKind::Function,
|
||||||
UniCase::ascii("type::bool") => PathKind::Function,
|
UniCase::ascii("type::bool") => PathKind::Function,
|
||||||
UniCase::ascii("type::bytes") => PathKind::Function,
|
UniCase::ascii("type::bytes") => PathKind::Function,
|
||||||
UniCase::ascii("type::datetime") => PathKind::Function,
|
UniCase::ascii("type::datetime") => PathKind::Function,
|
||||||
|
|
|
@ -912,7 +912,7 @@ mod tests {
|
||||||
Value::from(Idiom(vec![
|
Value::from(Idiom(vec![
|
||||||
Part::Start(Value::Thing(Thing {
|
Part::Start(Value::Thing(Thing {
|
||||||
tb: "test".to_owned(),
|
tb: "test".to_owned(),
|
||||||
id: Id::Number(1),
|
id: Id::from(1),
|
||||||
})),
|
})),
|
||||||
Part::from("foo"),
|
Part::from("foo"),
|
||||||
]))
|
]))
|
||||||
|
@ -928,7 +928,7 @@ mod tests {
|
||||||
Value::from(Idiom(vec![
|
Value::from(Idiom(vec![
|
||||||
Part::Start(Value::Thing(Thing {
|
Part::Start(Value::Thing(Thing {
|
||||||
tb: "test".to_owned(),
|
tb: "test".to_owned(),
|
||||||
id: Id::Number(1),
|
id: Id::from(1),
|
||||||
})),
|
})),
|
||||||
Part::Value(Value::Strand(Strand("foo".to_owned()))),
|
Part::Value(Value::Strand(Strand("foo".to_owned()))),
|
||||||
]))
|
]))
|
||||||
|
@ -944,7 +944,7 @@ mod tests {
|
||||||
Value::from(Idiom(vec![
|
Value::from(Idiom(vec![
|
||||||
Part::Start(Value::Thing(Thing {
|
Part::Start(Value::Thing(Thing {
|
||||||
tb: "test".to_owned(),
|
tb: "test".to_owned(),
|
||||||
id: Id::Number(1),
|
id: Id::from(1),
|
||||||
})),
|
})),
|
||||||
Part::All
|
Part::All
|
||||||
]))
|
]))
|
||||||
|
|
|
@ -82,6 +82,7 @@ impl Parser<'_> {
|
||||||
t!("POINT") => Ok(Kind::Point),
|
t!("POINT") => Ok(Kind::Point),
|
||||||
t!("STRING") => Ok(Kind::String),
|
t!("STRING") => Ok(Kind::String),
|
||||||
t!("UUID") => Ok(Kind::Uuid),
|
t!("UUID") => Ok(Kind::Uuid),
|
||||||
|
t!("RANGE") => Ok(Kind::Range),
|
||||||
t!("FUNCTION") => Ok(Kind::Function(Default::default(), Default::default())),
|
t!("FUNCTION") => Ok(Kind::Function(Default::default(), Default::default())),
|
||||||
t!("RECORD") => {
|
t!("RECORD") => {
|
||||||
let span = self.peek().span;
|
let span = self.peek().span;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ops::Bound;
|
||||||
|
|
||||||
use geo::Point;
|
use geo::Point;
|
||||||
use reblessive::Stk;
|
use reblessive::Stk;
|
||||||
|
|
||||||
|
@ -6,14 +8,14 @@ use crate::{
|
||||||
enter_object_recursion, enter_query_recursion,
|
enter_object_recursion, enter_query_recursion,
|
||||||
sql::{
|
sql::{
|
||||||
Array, Closure, Dir, Function, Geometry, Ident, Idiom, Kind, Mock, Number, Param, Part,
|
Array, Closure, Dir, Function, Geometry, Ident, Idiom, Kind, Mock, Number, Param, Part,
|
||||||
Script, Strand, Subquery, Table, Value,
|
Range, Script, Strand, Subquery, Table, Value,
|
||||||
},
|
},
|
||||||
syn::{
|
syn::{
|
||||||
parser::{
|
parser::{
|
||||||
mac::{expected, unexpected},
|
mac::{expected, expected_whitespace, unexpected},
|
||||||
ParseError, ParseErrorKind,
|
ParseError, ParseErrorKind,
|
||||||
},
|
},
|
||||||
token::{t, Span, TokenKind},
|
token::{t, DurationSuffix, Span, TokenKind},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,27 +25,29 @@ impl Parser<'_> {
|
||||||
/// What's are values which are more restricted in what expressions they can contain.
|
/// What's are values which are more restricted in what expressions they can contain.
|
||||||
pub async fn parse_what_primary(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
|
pub async fn parse_what_primary(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
|
||||||
match self.peek_kind() {
|
match self.peek_kind() {
|
||||||
|
t!("..") => Ok(self.try_parse_range(ctx, None).await?.unwrap()),
|
||||||
t!("r\"") => {
|
t!("r\"") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
let thing = self.parse_record_string(ctx, true).await?;
|
let value = Value::Thing(self.parse_record_string(ctx, true).await?);
|
||||||
Ok(Value::Thing(thing))
|
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
|
||||||
}
|
}
|
||||||
t!("r'") => {
|
t!("r'") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
let thing = self.parse_record_string(ctx, false).await?;
|
let value = Value::Thing(self.parse_record_string(ctx, false).await?);
|
||||||
Ok(Value::Thing(thing))
|
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
|
||||||
}
|
}
|
||||||
t!("d\"") | t!("d'") => {
|
t!("d\"") | t!("d'") => {
|
||||||
let datetime = self.next_token_value()?;
|
let value = Value::Datetime(self.next_token_value()?);
|
||||||
Ok(Value::Datetime(datetime))
|
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
|
||||||
}
|
}
|
||||||
t!("u\"") | t!("u'") => {
|
t!("u\"") | t!("u'") => {
|
||||||
let uuid = self.next_token_value()?;
|
let value = Value::Uuid(self.next_token_value()?);
|
||||||
Ok(Value::Uuid(uuid))
|
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
|
||||||
}
|
}
|
||||||
t!("$param") => {
|
t!("$param") => {
|
||||||
let value = Value::Param(self.next_token_value()?);
|
let value = Value::Param(self.next_token_value()?);
|
||||||
Ok(self.try_parse_inline(ctx, &value).await?.unwrap_or(value))
|
let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value);
|
||||||
|
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
|
||||||
}
|
}
|
||||||
t!("FUNCTION") => {
|
t!("FUNCTION") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
|
@ -128,12 +132,60 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn try_parse_range(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut Stk,
|
||||||
|
subject: Option<&Value>,
|
||||||
|
) -> ParseResult<Option<Value>> {
|
||||||
|
// The ">" can also mean a comparison.
|
||||||
|
// If the token after is not "..", then return
|
||||||
|
if self.peek_whitespace().kind == t!(">")
|
||||||
|
&& self.peek_whitespace_token_at(1).kind != t!("..")
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let beg = if let Some(subject) = subject {
|
||||||
|
if self.eat_whitespace(t!(">")) {
|
||||||
|
expected_whitespace!(self, t!(".."));
|
||||||
|
Bound::Excluded(subject.to_owned())
|
||||||
|
} else {
|
||||||
|
if !self.eat_whitespace(t!("..")) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bound::Included(subject.to_owned())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !self.eat_whitespace(t!("..")) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bound::Unbounded
|
||||||
|
};
|
||||||
|
|
||||||
|
let end = if self.eat_whitespace(t!("=")) {
|
||||||
|
let id = ctx.run(|ctx| self.parse_simple_value(ctx)).await?;
|
||||||
|
Bound::Included(id)
|
||||||
|
} else if Self::tokenkind_can_start_simple_value(self.peek_whitespace().kind) {
|
||||||
|
let id = ctx.run(|ctx| self.parse_simple_value(ctx)).await?;
|
||||||
|
Bound::Excluded(id)
|
||||||
|
} else {
|
||||||
|
Bound::Unbounded
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(Value::Range(Box::new(Range {
|
||||||
|
beg,
|
||||||
|
end,
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn try_parse_inline(
|
pub async fn try_parse_inline(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut Stk,
|
ctx: &mut Stk,
|
||||||
subject: &Value,
|
subject: &Value,
|
||||||
) -> ParseResult<Option<Value>> {
|
) -> ParseResult<Option<Value>> {
|
||||||
if self.eat(t!("(")) {
|
if self.eat_whitespace(t!("(")) {
|
||||||
let start = self.last_span();
|
let start = self.last_span();
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
|
@ -171,21 +223,26 @@ impl Parser<'_> {
|
||||||
pub async fn parse_idiom_expression(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
|
pub async fn parse_idiom_expression(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
|
||||||
let token = self.peek();
|
let token = self.peek();
|
||||||
let value = match token.kind {
|
let value = match token.kind {
|
||||||
|
t!("..") => self.try_parse_range(ctx, None).await?.unwrap(),
|
||||||
t!("NONE") => {
|
t!("NONE") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
Value::None
|
let value = Value::None;
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("NULL") => {
|
t!("NULL") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
Value::Null
|
let value = Value::Null;
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("true") => {
|
t!("true") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
Value::Bool(true)
|
let value = Value::Bool(true);
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("false") => {
|
t!("false") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
Value::Bool(false)
|
let value = Value::Bool(false);
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("<") => {
|
t!("<") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
|
@ -198,21 +255,21 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
t!("r\"") => {
|
t!("r\"") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
let thing = self.parse_record_string(ctx, true).await?;
|
let value = Value::Thing(self.parse_record_string(ctx, true).await?);
|
||||||
Value::Thing(thing)
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("r'") => {
|
t!("r'") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
let thing = self.parse_record_string(ctx, false).await?;
|
let value = Value::Thing(self.parse_record_string(ctx, false).await?);
|
||||||
Value::Thing(thing)
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("d\"") | t!("d'") => {
|
t!("d\"") | t!("d'") => {
|
||||||
let datetime = self.next_token_value()?;
|
let value = Value::Datetime(self.next_token_value()?);
|
||||||
Value::Datetime(datetime)
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("u\"") | t!("u'") => {
|
t!("u\"") | t!("u'") => {
|
||||||
let uuid = self.next_token_value()?;
|
let value = Value::Uuid(self.next_token_value()?);
|
||||||
Value::Uuid(uuid)
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("'") | t!("\"") | TokenKind::Strand => {
|
t!("'") | t!("\"") | TokenKind::Strand => {
|
||||||
let s = self.next_token_value::<Strand>()?;
|
let s = self.next_token_value::<Strand>()?;
|
||||||
|
@ -221,18 +278,22 @@ impl Parser<'_> {
|
||||||
return Ok(x);
|
return Ok(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Strand(s)
|
let value = Value::Strand(s);
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("+") | t!("-") | TokenKind::Number(_) | TokenKind::Digits | TokenKind::Duration => {
|
t!("+") | t!("-") | TokenKind::Number(_) | TokenKind::Digits | TokenKind::Duration => {
|
||||||
self.parse_number_like_prime()?
|
let value = self.parse_number_like_prime()?;
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
TokenKind::NaN => {
|
TokenKind::NaN => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
Value::Number(Number::Float(f64::NAN))
|
let value = Value::Number(Number::Float(f64::NAN));
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("$param") => {
|
t!("$param") => {
|
||||||
let value = Value::Param(self.next_token_value()?);
|
let value = Value::Param(self.next_token_value()?);
|
||||||
self.try_parse_inline(ctx, &value).await?.unwrap_or(value)
|
let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value);
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("FUNCTION") => {
|
t!("FUNCTION") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
|
@ -256,12 +317,14 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
t!("[") => {
|
t!("[") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
self.parse_array(ctx, token.span).await.map(Value::Array)?
|
let value = self.parse_array(ctx, token.span).await.map(Value::Array)?;
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("{") => {
|
t!("{") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
let value = self.parse_object_like(ctx, token.span).await?;
|
let value = self.parse_object_like(ctx, token.span).await?;
|
||||||
self.try_parse_inline(ctx, &value).await?.unwrap_or(value)
|
let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value);
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("|") => {
|
t!("|") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
|
@ -281,7 +344,8 @@ impl Parser<'_> {
|
||||||
t!("(") => {
|
t!("(") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
let value = self.parse_inner_subquery_or_coordinate(ctx, token.span).await?;
|
let value = self.parse_inner_subquery_or_coordinate(ctx, token.span).await?;
|
||||||
self.try_parse_inline(ctx, &value).await?.unwrap_or(value)
|
let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value);
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
}
|
}
|
||||||
t!("/") => self.next_token_value().map(Value::Regex)?,
|
t!("/") => self.next_token_value().map(Value::Regex)?,
|
||||||
t!("RETURN")
|
t!("RETURN")
|
||||||
|
@ -749,6 +813,133 @@ impl Parser<'_> {
|
||||||
.map_err(|(e, span)| ParseError::new(ParseErrorKind::InvalidToken(e), span))?;
|
.map_err(|(e, span)| ParseError::new(ParseErrorKind::InvalidToken(e), span))?;
|
||||||
Ok(Function::Script(Script(body), args))
|
Ok(Function::Script(Script(body), args))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a simple singular value
|
||||||
|
pub async fn parse_simple_value(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
|
||||||
|
let token = self.peek();
|
||||||
|
let value = match token.kind {
|
||||||
|
t!("NONE") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let value = Value::None;
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
|
}
|
||||||
|
t!("NULL") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let value = Value::Null;
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
|
}
|
||||||
|
t!("true") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let value = Value::Bool(true);
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
|
}
|
||||||
|
t!("false") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let value = Value::Bool(false);
|
||||||
|
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||||
|
}
|
||||||
|
t!("r\"") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let thing = self.parse_record_string(ctx, true).await?;
|
||||||
|
Value::Thing(thing)
|
||||||
|
}
|
||||||
|
t!("r'") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let thing = self.parse_record_string(ctx, false).await?;
|
||||||
|
Value::Thing(thing)
|
||||||
|
}
|
||||||
|
t!("d\"") | t!("d'") => {
|
||||||
|
let datetime = self.next_token_value()?;
|
||||||
|
Value::Datetime(datetime)
|
||||||
|
}
|
||||||
|
t!("u\"") | t!("u'") => {
|
||||||
|
let uuid = self.next_token_value()?;
|
||||||
|
Value::Uuid(uuid)
|
||||||
|
}
|
||||||
|
t!("'") | t!("\"") | TokenKind::Strand => {
|
||||||
|
let s = self.next_token_value::<Strand>()?;
|
||||||
|
if self.legacy_strands {
|
||||||
|
if let Some(x) = self.reparse_legacy_strand(ctx, &s.0).await {
|
||||||
|
return Ok(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Strand(s)
|
||||||
|
}
|
||||||
|
t!("+") | t!("-") | TokenKind::Number(_) | TokenKind::Digits | TokenKind::Duration => {
|
||||||
|
self.parse_number_like_prime()?
|
||||||
|
}
|
||||||
|
TokenKind::NaN => {
|
||||||
|
self.pop_peek();
|
||||||
|
Value::Number(Number::Float(f64::NAN))
|
||||||
|
}
|
||||||
|
t!("$param") => {
|
||||||
|
let value = Value::Param(self.next_token_value()?);
|
||||||
|
self.try_parse_inline(ctx, &value).await?.unwrap_or(value)
|
||||||
|
}
|
||||||
|
t!("[") => {
|
||||||
|
self.pop_peek();
|
||||||
|
self.parse_array(ctx, token.span).await.map(Value::Array)?
|
||||||
|
}
|
||||||
|
t!("{") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let value = self.parse_object_like(ctx, token.span).await?;
|
||||||
|
self.try_parse_inline(ctx, &value).await?.unwrap_or(value)
|
||||||
|
}
|
||||||
|
t!("(") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let value = self.parse_inner_subquery_or_coordinate(ctx, token.span).await?;
|
||||||
|
self.try_parse_inline(ctx, &value).await?.unwrap_or(value)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.glue()?;
|
||||||
|
let x = self.peek_token_at(1).kind;
|
||||||
|
if x.has_data() {
|
||||||
|
unexpected!(self, x, "a value");
|
||||||
|
} else if self.table_as_field {
|
||||||
|
Value::Idiom(Idiom(vec![Part::Field(self.next_token_value()?)]))
|
||||||
|
} else {
|
||||||
|
Value::Table(self.next_token_value()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tokenkind_can_start_simple_value(t: TokenKind) -> bool {
|
||||||
|
matches!(
|
||||||
|
t,
|
||||||
|
t!("NONE")
|
||||||
|
| t!("NULL") | t!("true")
|
||||||
|
| t!("false") | t!("r\"")
|
||||||
|
| t!("r'") | t!("d\"")
|
||||||
|
| t!("d'") | t!("u\"")
|
||||||
|
| t!("u'") | t!("\"")
|
||||||
|
| t!("'") | t!("+")
|
||||||
|
| t!("-") | TokenKind::Number(_)
|
||||||
|
| TokenKind::Digits
|
||||||
|
| TokenKind::Duration
|
||||||
|
| TokenKind::NaN | t!("$param")
|
||||||
|
| t!("[") | t!("{")
|
||||||
|
| t!("(") | TokenKind::Keyword(_)
|
||||||
|
| TokenKind::Language(_)
|
||||||
|
| TokenKind::Algorithm(_)
|
||||||
|
| TokenKind::Distance(_)
|
||||||
|
| TokenKind::VectorType(_)
|
||||||
|
| TokenKind::Identifier
|
||||||
|
| TokenKind::Exponent
|
||||||
|
| TokenKind::DatetimeChars(_)
|
||||||
|
| TokenKind::NumberSuffix(_)
|
||||||
|
| TokenKind::DurationSuffix(
|
||||||
|
// All except Micro unicode
|
||||||
|
DurationSuffix::Nano
|
||||||
|
| DurationSuffix::Micro | DurationSuffix::Milli
|
||||||
|
| DurationSuffix::Second | DurationSuffix::Minute
|
||||||
|
| DurationSuffix::Hour | DurationSuffix::Day
|
||||||
|
| DurationSuffix::Week | DurationSuffix::Year
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1664,7 +1664,7 @@ fn parse_delete_2() {
|
||||||
dir: Dir::Out,
|
dir: Dir::Out,
|
||||||
from: Thing {
|
from: Thing {
|
||||||
tb: "a".to_owned(),
|
tb: "a".to_owned(),
|
||||||
id: Id::String("b".to_owned()),
|
id: Id::from("b"),
|
||||||
},
|
},
|
||||||
what: Tables::default(),
|
what: Tables::default(),
|
||||||
}))),
|
}))),
|
||||||
|
@ -1867,7 +1867,7 @@ SELECT bar as foo,[1,2],bar OMIT bar FROM ONLY a,1
|
||||||
}])),
|
}])),
|
||||||
limit: Some(Limit(Value::Thing(Thing {
|
limit: Some(Limit(Value::Thing(Thing {
|
||||||
tb: "a".to_owned(),
|
tb: "a".to_owned(),
|
||||||
id: Id::String("b".to_owned()),
|
id: Id::from("b"),
|
||||||
}))),
|
}))),
|
||||||
start: Some(Start(Value::Object(Object(
|
start: Some(Start(Value::Object(Object(
|
||||||
[("a".to_owned(), Value::Bool(true))].into_iter().collect()
|
[("a".to_owned(), Value::Bool(true))].into_iter().collect()
|
||||||
|
@ -2191,7 +2191,7 @@ fn parse_relate() {
|
||||||
only: true,
|
only: true,
|
||||||
kind: Value::Thing(Thing {
|
kind: Value::Thing(Thing {
|
||||||
tb: "a".to_owned(),
|
tb: "a".to_owned(),
|
||||||
id: Id::String("b".to_owned()),
|
id: Id::from("b"),
|
||||||
}),
|
}),
|
||||||
from: Value::Array(Array(vec![
|
from: Value::Array(Array(vec![
|
||||||
Value::Number(Number::Int(1)),
|
Value::Number(Number::Int(1)),
|
||||||
|
|
|
@ -410,7 +410,7 @@ fn statements() -> Vec<Statement> {
|
||||||
dir: Dir::Out,
|
dir: Dir::Out,
|
||||||
from: Thing {
|
from: Thing {
|
||||||
tb: "a".to_owned(),
|
tb: "a".to_owned(),
|
||||||
id: Id::String("b".to_owned()),
|
id: Id::from("b"),
|
||||||
},
|
},
|
||||||
what: Tables::default(),
|
what: Tables::default(),
|
||||||
}))),
|
}))),
|
||||||
|
@ -519,7 +519,7 @@ fn statements() -> Vec<Statement> {
|
||||||
}])),
|
}])),
|
||||||
limit: Some(Limit(Value::Thing(Thing {
|
limit: Some(Limit(Value::Thing(Thing {
|
||||||
tb: "a".to_owned(),
|
tb: "a".to_owned(),
|
||||||
id: Id::String("b".to_owned()),
|
id: Id::from("b"),
|
||||||
}))),
|
}))),
|
||||||
start: Some(Start(Value::Object(Object(
|
start: Some(Start(Value::Object(Object(
|
||||||
[("a".to_owned(), Value::Bool(true))].into_iter().collect(),
|
[("a".to_owned(), Value::Bool(true))].into_iter().collect(),
|
||||||
|
@ -625,7 +625,7 @@ fn statements() -> Vec<Statement> {
|
||||||
only: true,
|
only: true,
|
||||||
kind: Value::Thing(Thing {
|
kind: Value::Thing(Thing {
|
||||||
tb: "a".to_owned(),
|
tb: "a".to_owned(),
|
||||||
id: Id::String("b".to_owned()),
|
id: Id::from("b"),
|
||||||
}),
|
}),
|
||||||
from: Value::Array(Array(vec![
|
from: Value::Array(Array(vec![
|
||||||
Value::Number(Number::Int(1)),
|
Value::Number(Number::Int(1)),
|
||||||
|
|
|
@ -93,13 +93,13 @@ fn parse_recursive_record_string() {
|
||||||
res,
|
res,
|
||||||
Value::Thing(Thing {
|
Value::Thing(Thing {
|
||||||
tb: "a".to_owned(),
|
tb: "a".to_owned(),
|
||||||
id: Id::Array(Array(vec![Value::Thing(Thing {
|
id: Id::from(Array(vec![Value::Thing(Thing {
|
||||||
tb: "b".to_owned(),
|
tb: "b".to_owned(),
|
||||||
id: Id::Object(Object(BTreeMap::from([(
|
id: Id::from(Object(BTreeMap::from([(
|
||||||
"c".to_owned(),
|
"c".to_owned(),
|
||||||
Value::Thing(Thing {
|
Value::Thing(Thing {
|
||||||
tb: "d".to_owned(),
|
tb: "d".to_owned(),
|
||||||
id: Id::Number(1)
|
id: Id::from(1)
|
||||||
})
|
})
|
||||||
)])))
|
)])))
|
||||||
})]))
|
})]))
|
||||||
|
@ -114,7 +114,7 @@ fn parse_record_string_2() {
|
||||||
res,
|
res,
|
||||||
Value::Thing(Thing {
|
Value::Thing(Thing {
|
||||||
tb: "a".to_owned(),
|
tb: "a".to_owned(),
|
||||||
id: Id::Array(Array(vec![Value::Strand(Strand("foo".to_owned()))]))
|
id: Id::from(Array(vec![Value::Strand(Strand("foo".to_owned()))]))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ use reblessive::Stk;
|
||||||
|
|
||||||
use super::{ParseResult, Parser};
|
use super::{ParseResult, Parser};
|
||||||
use crate::{
|
use crate::{
|
||||||
sql::{id::Gen, Id, Ident, Range, Thing, Value},
|
sql::{
|
||||||
|
id::{range::IdRange, Gen},
|
||||||
|
Id, Ident, Range, Thing, Value,
|
||||||
|
},
|
||||||
syn::{
|
syn::{
|
||||||
parser::{
|
parser::{
|
||||||
mac::{expected, expected_whitespace, unexpected},
|
mac::{expected, expected_whitespace, unexpected},
|
||||||
|
@ -51,22 +54,24 @@ impl Parser<'_> {
|
||||||
} else {
|
} else {
|
||||||
Bound::Unbounded
|
Bound::Unbounded
|
||||||
};
|
};
|
||||||
return Ok(Value::Range(Box::new(Range {
|
return Ok(Value::Thing(Thing {
|
||||||
tb: ident,
|
tb: ident,
|
||||||
|
id: Id::Range(Box::new(IdRange {
|
||||||
beg: Bound::Unbounded,
|
beg: Bound::Unbounded,
|
||||||
end,
|
end,
|
||||||
})));
|
})),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Didn't eat range yet so we need to parse the id.
|
// Didn't eat range yet so we need to parse the id.
|
||||||
let beg = if Self::kind_cast_start_id(self.peek_whitespace().kind) {
|
let beg = if Self::kind_cast_start_id(self.peek_whitespace().kind) {
|
||||||
let id = stk.run(|ctx| self.parse_id(ctx)).await?;
|
let v = stk.run(|stk| self.parse_id(stk)).await?;
|
||||||
|
|
||||||
// check for exclusive
|
// check for exclusive
|
||||||
if self.eat_whitespace(t!(">")) {
|
if self.eat_whitespace(t!(">")) {
|
||||||
Bound::Excluded(id)
|
Bound::Excluded(v)
|
||||||
} else {
|
} else {
|
||||||
Bound::Included(id)
|
Bound::Included(v)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Bound::Unbounded
|
Bound::Unbounded
|
||||||
|
@ -76,19 +81,21 @@ impl Parser<'_> {
|
||||||
// If we already ate the exclusive it must be a range.
|
// If we already ate the exclusive it must be a range.
|
||||||
if self.eat_whitespace(t!("..")) {
|
if self.eat_whitespace(t!("..")) {
|
||||||
let end = if self.eat_whitespace(t!("=")) {
|
let end = if self.eat_whitespace(t!("=")) {
|
||||||
let id = stk.run(|ctx| self.parse_id(ctx)).await?;
|
let id = stk.run(|stk| self.parse_id(stk)).await?;
|
||||||
Bound::Included(id)
|
Bound::Included(id)
|
||||||
} else if Self::kind_cast_start_id(self.peek_whitespace().kind) {
|
} else if Self::kind_cast_start_id(self.peek_whitespace().kind) {
|
||||||
let id = stk.run(|ctx| self.parse_id(ctx)).await?;
|
let id = stk.run(|stk| self.parse_id(stk)).await?;
|
||||||
Bound::Excluded(id)
|
Bound::Excluded(id)
|
||||||
} else {
|
} else {
|
||||||
Bound::Unbounded
|
Bound::Unbounded
|
||||||
};
|
};
|
||||||
Ok(Value::Range(Box::new(Range {
|
Ok(Value::Thing(Thing {
|
||||||
tb: ident,
|
tb: ident,
|
||||||
|
id: Id::Range(Box::new(IdRange {
|
||||||
beg,
|
beg,
|
||||||
end,
|
end,
|
||||||
})))
|
})),
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
let id = match beg {
|
let id = match beg {
|
||||||
Bound::Unbounded => {
|
Bound::Unbounded => {
|
||||||
|
@ -110,7 +117,8 @@ impl Parser<'_> {
|
||||||
// we have matched a bounded id but we don't see an range operator.
|
// we have matched a bounded id but we don't see an range operator.
|
||||||
unexpected!(self, self.peek_whitespace().kind, "the range operator `..`")
|
unexpected!(self, self.peek_whitespace().kind, "the range operator `..`")
|
||||||
}
|
}
|
||||||
Bound::Included(id) => id,
|
// We previously converted the `Id` value to `Value` so it's safe to unwrap here.
|
||||||
|
Bound::Included(v) => v,
|
||||||
};
|
};
|
||||||
Ok(Value::Thing(Thing {
|
Ok(Value::Thing(Thing {
|
||||||
tb: ident,
|
tb: ident,
|
||||||
|
@ -121,18 +129,14 @@ impl Parser<'_> {
|
||||||
|
|
||||||
/// Parse an range
|
/// Parse an range
|
||||||
pub async fn parse_range(&mut self, ctx: &mut Stk) -> ParseResult<Range> {
|
pub async fn parse_range(&mut self, ctx: &mut Stk) -> ParseResult<Range> {
|
||||||
let tb = self.next_token_value::<Ident>()?.0;
|
|
||||||
|
|
||||||
expected_whitespace!(self, t!(":"));
|
|
||||||
|
|
||||||
// Check for beginning id
|
// Check for beginning id
|
||||||
let beg = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) {
|
let beg = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) {
|
||||||
let id = ctx.run(|ctx| self.parse_id(ctx)).await?;
|
let v = ctx.run(|ctx| self.parse_value(ctx)).await?;
|
||||||
|
|
||||||
if self.eat_whitespace(t!(">")) {
|
if self.eat_whitespace(t!(">")) {
|
||||||
Bound::Excluded(id)
|
Bound::Excluded(v)
|
||||||
} else {
|
} else {
|
||||||
Bound::Included(id)
|
Bound::Included(v)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Bound::Unbounded
|
Bound::Unbounded
|
||||||
|
@ -144,18 +148,17 @@ impl Parser<'_> {
|
||||||
|
|
||||||
// parse ending id.
|
// parse ending id.
|
||||||
let end = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) {
|
let end = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) {
|
||||||
let id = ctx.run(|ctx| self.parse_id(ctx)).await?;
|
let v = ctx.run(|ctx| self.parse_value(ctx)).await?;
|
||||||
if inclusive {
|
if inclusive {
|
||||||
Bound::Included(id)
|
Bound::Included(v)
|
||||||
} else {
|
} else {
|
||||||
Bound::Excluded(id)
|
Bound::Excluded(v)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Bound::Unbounded
|
Bound::Unbounded
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Range {
|
Ok(Range {
|
||||||
tb,
|
|
||||||
beg,
|
beg,
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
@ -498,7 +501,7 @@ mod tests {
|
||||||
out,
|
out,
|
||||||
Thing {
|
Thing {
|
||||||
tb: String::from("test"),
|
tb: String::from("test"),
|
||||||
id: Id::Object(Object::from(map! {
|
id: Id::from(Object::from(map! {
|
||||||
"location".to_string() => Value::from("GBR"),
|
"location".to_string() => Value::from("GBR"),
|
||||||
"year".to_string() => Value::from(2022),
|
"year".to_string() => Value::from(2022),
|
||||||
})),
|
})),
|
||||||
|
@ -516,7 +519,7 @@ mod tests {
|
||||||
out,
|
out,
|
||||||
Thing {
|
Thing {
|
||||||
tb: String::from("test"),
|
tb: String::from("test"),
|
||||||
id: Id::Array(Array::from(vec![Value::from("GBR"), Value::from(2022)])),
|
id: Id::from(Array::from(vec![Value::from("GBR"), Value::from(2022)])),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -535,7 +538,7 @@ mod tests {
|
||||||
.finish()
|
.finish()
|
||||||
.unwrap_or_else(|_| panic!("failed on {}", ident))
|
.unwrap_or_else(|_| panic!("failed on {}", ident))
|
||||||
.id;
|
.id;
|
||||||
assert_eq!(r, Id::String(ident.to_string()),);
|
assert_eq!(r, Id::from(ident.to_string()),);
|
||||||
|
|
||||||
let mut parser = Parser::new(thing.as_bytes());
|
let mut parser = Parser::new(thing.as_bytes());
|
||||||
let r = stack
|
let r = stack
|
||||||
|
@ -548,7 +551,7 @@ mod tests {
|
||||||
sql::Query(sql::Statements(vec![sql::Statement::Value(sql::Value::Thing(
|
sql::Query(sql::Statements(vec![sql::Statement::Value(sql::Value::Thing(
|
||||||
sql::Thing {
|
sql::Thing {
|
||||||
tb: "t".to_string(),
|
tb: "t".to_string(),
|
||||||
id: Id::String(ident.to_string())
|
id: Id::from(ident.to_string())
|
||||||
}
|
}
|
||||||
))]))
|
))]))
|
||||||
)
|
)
|
||||||
|
|
|
@ -142,6 +142,7 @@ keyword! {
|
||||||
PostingsOrder => "POSTINGS_ORDER",
|
PostingsOrder => "POSTINGS_ORDER",
|
||||||
Prune => "PRUNE",
|
Prune => "PRUNE",
|
||||||
Punct => "PUNCT",
|
Punct => "PUNCT",
|
||||||
|
Range => "RANGE",
|
||||||
Readonly => "READONLY",
|
Readonly => "READONLY",
|
||||||
Rebuild => "REBUILD",
|
Rebuild => "REBUILD",
|
||||||
Relate => "RELATE",
|
Relate => "RELATE",
|
||||||
|
|
|
@ -71,7 +71,7 @@ fn bench_hash_trie_btree_thing(c: &mut Criterion) {
|
||||||
const N: usize = 50_000;
|
const N: usize = 50_000;
|
||||||
let mut samples = Vec::with_capacity(N);
|
let mut samples = Vec::with_capacity(N);
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
let key = Thing::from(("test", Id::Array(Array::from(vec![i as i32; 5]))));
|
let key = Thing::from(("test", Id::from(Array::from(vec![i as i32; 5]))));
|
||||||
samples.push((key, i));
|
samples.push((key, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ fn new_vectors_from_file(path: &str) -> Vec<(Thing, Array)> {
|
||||||
let line = line_result.unwrap();
|
let line = line_result.unwrap();
|
||||||
let value = value(&line).unwrap();
|
let value = value(&line).unwrap();
|
||||||
if let Value::Array(a) = value {
|
if let Value::Array(a) = value {
|
||||||
res.push((Thing::from(("e", Id::Number(i as i64))), a));
|
res.push((Thing::from(("e", Id::from(i as i64))), a));
|
||||||
} else {
|
} else {
|
||||||
panic!("Wrong value");
|
panic!("Wrong value");
|
||||||
}
|
}
|
||||||
|
|
|
@ -553,7 +553,7 @@ async fn router(
|
||||||
data,
|
data,
|
||||||
} => {
|
} => {
|
||||||
let mut query = Query::default();
|
let mut query = Query::default();
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
let statement = {
|
let statement = {
|
||||||
let mut stmt = UpsertStatement::default();
|
let mut stmt = UpsertStatement::default();
|
||||||
stmt.what = value_to_values(what);
|
stmt.what = value_to_values(what);
|
||||||
|
@ -571,7 +571,7 @@ async fn router(
|
||||||
data,
|
data,
|
||||||
} => {
|
} => {
|
||||||
let mut query = Query::default();
|
let mut query = Query::default();
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
let statement = {
|
let statement = {
|
||||||
let mut stmt = UpdateStatement::default();
|
let mut stmt = UpdateStatement::default();
|
||||||
stmt.what = value_to_values(what);
|
stmt.what = value_to_values(what);
|
||||||
|
@ -607,7 +607,7 @@ async fn router(
|
||||||
data,
|
data,
|
||||||
} => {
|
} => {
|
||||||
let mut query = Query::default();
|
let mut query = Query::default();
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
let statement = {
|
let statement = {
|
||||||
let mut stmt = UpdateStatement::default();
|
let mut stmt = UpdateStatement::default();
|
||||||
stmt.what = value_to_values(what);
|
stmt.what = value_to_values(what);
|
||||||
|
@ -625,7 +625,7 @@ async fn router(
|
||||||
data,
|
data,
|
||||||
} => {
|
} => {
|
||||||
let mut query = Query::default();
|
let mut query = Query::default();
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
let statement = {
|
let statement = {
|
||||||
let mut stmt = UpdateStatement::default();
|
let mut stmt = UpdateStatement::default();
|
||||||
stmt.what = value_to_values(what);
|
stmt.what = value_to_values(what);
|
||||||
|
@ -642,7 +642,7 @@ async fn router(
|
||||||
what,
|
what,
|
||||||
} => {
|
} => {
|
||||||
let mut query = Query::default();
|
let mut query = Query::default();
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
let statement = {
|
let statement = {
|
||||||
let mut stmt = SelectStatement::default();
|
let mut stmt = SelectStatement::default();
|
||||||
stmt.what = value_to_values(what);
|
stmt.what = value_to_values(what);
|
||||||
|
@ -658,7 +658,7 @@ async fn router(
|
||||||
what,
|
what,
|
||||||
} => {
|
} => {
|
||||||
let mut query = Query::default();
|
let mut query = Query::default();
|
||||||
let one = what.is_thing();
|
let one = what.is_thing_single();
|
||||||
let statement = {
|
let statement = {
|
||||||
let mut stmt = DeleteStatement::default();
|
let mut stmt = DeleteStatement::default();
|
||||||
stmt.what = value_to_values(what);
|
stmt.what = value_to_values(what);
|
||||||
|
|
|
@ -14,13 +14,10 @@ use crate::method::Select;
|
||||||
use crate::opt::Resource;
|
use crate::opt::Resource;
|
||||||
use crate::sql::from_value;
|
use crate::sql::from_value;
|
||||||
use crate::sql::statements::LiveStatement;
|
use crate::sql::statements::LiveStatement;
|
||||||
use crate::sql::Cond;
|
|
||||||
use crate::sql::Expression;
|
|
||||||
use crate::sql::Field;
|
use crate::sql::Field;
|
||||||
use crate::sql::Fields;
|
use crate::sql::Fields;
|
||||||
use crate::sql::Ident;
|
use crate::sql::Ident;
|
||||||
use crate::sql::Idiom;
|
use crate::sql::Idiom;
|
||||||
use crate::sql::Operator;
|
|
||||||
use crate::sql::Part;
|
use crate::sql::Part;
|
||||||
use crate::sql::Statement;
|
use crate::sql::Statement;
|
||||||
use crate::sql::Table;
|
use crate::sql::Table;
|
||||||
|
@ -63,10 +60,10 @@ macro_rules! into_future {
|
||||||
let mut table = Table::default();
|
let mut table = Table::default();
|
||||||
match range {
|
match range {
|
||||||
Some(range) => {
|
Some(range) => {
|
||||||
let range = resource?.with_range(range)?;
|
let record = resource?.with_range(range)?;
|
||||||
table.0 = range.tb.clone();
|
table.0 = record.tb.clone();
|
||||||
stmt.what = table.into();
|
stmt.what = table.into();
|
||||||
stmt.cond = range.to_cond();
|
stmt.cond = record.to_cond();
|
||||||
}
|
}
|
||||||
None => match resource? {
|
None => match resource? {
|
||||||
Resource::Table(table) => {
|
Resource::Table(table) => {
|
||||||
|
@ -79,13 +76,7 @@ macro_rules! into_future {
|
||||||
ident.0 = ID.to_owned();
|
ident.0 = ID.to_owned();
|
||||||
let mut idiom = Idiom::default();
|
let mut idiom = Idiom::default();
|
||||||
idiom.0 = vec![Part::from(ident)];
|
idiom.0 = vec![Part::from(ident)];
|
||||||
let mut cond = Cond::default();
|
stmt.cond = record.to_cond();
|
||||||
cond.0 = Value::Expression(Box::new(Expression::new(
|
|
||||||
idiom.into(),
|
|
||||||
Operator::Equal,
|
|
||||||
record.into(),
|
|
||||||
)));
|
|
||||||
stmt.cond = Some(cond);
|
|
||||||
}
|
}
|
||||||
Resource::Object(object) => return Err(Error::LiveOnObject(object).into()),
|
Resource::Object(object) => return Err(Error::LiveOnObject(object).into()),
|
||||||
Resource::Array(array) => return Err(Error::LiveOnArray(array).into()),
|
Resource::Array(array) => return Err(Error::LiveOnArray(array).into()),
|
||||||
|
|
|
@ -4,6 +4,8 @@ use crate::api::conn::DbResponse;
|
||||||
use crate::api::conn::Route;
|
use crate::api::conn::Route;
|
||||||
use crate::api::Response as QueryResponse;
|
use crate::api::Response as QueryResponse;
|
||||||
use crate::sql::to_value;
|
use crate::sql::to_value;
|
||||||
|
use crate::sql::Id;
|
||||||
|
use crate::sql::Thing;
|
||||||
use crate::sql::Value;
|
use crate::sql::Value;
|
||||||
use channel::Receiver;
|
use channel::Receiver;
|
||||||
|
|
||||||
|
@ -61,10 +63,13 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
|
||||||
what,
|
what,
|
||||||
..
|
..
|
||||||
} => match what {
|
} => match what {
|
||||||
|
Value::Table(..)
|
||||||
|
| Value::Array(..)
|
||||||
|
| Value::Thing(Thing {
|
||||||
|
id: Id::Range(_),
|
||||||
|
..
|
||||||
|
}) => Ok(DbResponse::Other(Value::Array(Default::default()))),
|
||||||
Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())),
|
Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())),
|
||||||
Value::Table(..) | Value::Array(..) | Value::Range(..) => {
|
|
||||||
Ok(DbResponse::Other(Value::Array(Default::default())))
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
Command::Upsert {
|
Command::Upsert {
|
||||||
|
@ -83,10 +88,13 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
|
||||||
what,
|
what,
|
||||||
..
|
..
|
||||||
} => match what {
|
} => match what {
|
||||||
|
Value::Table(..)
|
||||||
|
| Value::Array(..)
|
||||||
|
| Value::Thing(Thing {
|
||||||
|
id: Id::Range(_),
|
||||||
|
..
|
||||||
|
}) => Ok(DbResponse::Other(Value::Array(Default::default()))),
|
||||||
Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())),
|
Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())),
|
||||||
Value::Table(..) | Value::Array(..) | Value::Range(..) => {
|
|
||||||
Ok(DbResponse::Other(Value::Array(Default::default())))
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
Command::Insert {
|
Command::Insert {
|
||||||
|
|
|
@ -9,6 +9,7 @@ use crate::api::opt::Resource;
|
||||||
use crate::api::Connection;
|
use crate::api::Connection;
|
||||||
use crate::api::Result;
|
use crate::api::Result;
|
||||||
use crate::method::OnceLockExt;
|
use crate::method::OnceLockExt;
|
||||||
|
use crate::sql::to_value;
|
||||||
use crate::sql::Id;
|
use crate::sql::Id;
|
||||||
use crate::sql::Value;
|
use crate::sql::Value;
|
||||||
use crate::Surreal;
|
use crate::Surreal;
|
||||||
|
@ -17,7 +18,6 @@ use serde::Serialize;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::future::IntoFuture;
|
use std::future::IntoFuture;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use surrealdb_core::sql::to_value;
|
|
||||||
|
|
||||||
/// An update future
|
/// An update future
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -9,6 +9,7 @@ use crate::api::opt::Resource;
|
||||||
use crate::api::Connection;
|
use crate::api::Connection;
|
||||||
use crate::api::Result;
|
use crate::api::Result;
|
||||||
use crate::method::OnceLockExt;
|
use crate::method::OnceLockExt;
|
||||||
|
use crate::sql::to_value;
|
||||||
use crate::sql::Id;
|
use crate::sql::Id;
|
||||||
use crate::sql::Value;
|
use crate::sql::Value;
|
||||||
use crate::Surreal;
|
use crate::Surreal;
|
||||||
|
@ -17,7 +18,6 @@ use serde::Serialize;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::future::IntoFuture;
|
use std::future::IntoFuture;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use surrealdb_core::sql::to_value;
|
|
||||||
|
|
||||||
/// An upsert future
|
/// An upsert future
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -19,9 +19,12 @@ pub enum Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource {
|
impl Resource {
|
||||||
pub(crate) fn with_range(self, range: Range<Id>) -> Result<sql::Range> {
|
pub(crate) fn with_range(self, range: Range<Id>) -> Result<sql::Thing> {
|
||||||
match self {
|
match self {
|
||||||
Resource::Table(table) => Ok(sql::Range::new(table.0, range.start, range.end)),
|
Resource::Table(table) => Ok(sql::Thing::from((
|
||||||
|
table.0,
|
||||||
|
sql::Id::Range(Box::new(sql::IdRange::try_from((range.start, range.end))?)),
|
||||||
|
))),
|
||||||
Resource::RecordId(record_id) => Err(Error::RangeOnRecordId(record_id).into()),
|
Resource::RecordId(record_id) => Err(Error::RangeOnRecordId(record_id).into()),
|
||||||
Resource::Object(object) => Err(Error::RangeOnObject(object).into()),
|
Resource::Object(object) => Err(Error::RangeOnObject(object).into()),
|
||||||
Resource::Array(array) => Err(Error::RangeOnArray(array).into()),
|
Resource::Array(array) => Err(Error::RangeOnArray(array).into()),
|
||||||
|
|
|
@ -62,3 +62,35 @@ async fn cast_to_record_table() -> Result<(), Error> {
|
||||||
//
|
//
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cast_range_to_array() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
<array> 1..5;
|
||||||
|
<array> 1>..5;
|
||||||
|
<array> 1..=5;
|
||||||
|
<array> 1>..=5;
|
||||||
|
"#;
|
||||||
|
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(), 4);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("[1, 2, 3, 4]");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("[2, 3, 4]");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("[1, 2, 3, 4, 5]");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("[2, 3, 4, 5]");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,23 @@ use helpers::Test;
|
||||||
use surrealdb::err::Error;
|
use surrealdb::err::Error;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn modulo() -> Result<(), Error> {
|
async fn expr_modulo() -> Result<(), Error> {
|
||||||
Test::new("8 % 3").await?.expect_val("2")?;
|
Test::new("8 % 3").await?.expect_val("2")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn expr_value_in_range() -> Result<(), Error> {
|
||||||
|
Test::new(
|
||||||
|
"
|
||||||
|
1 in 1..2;
|
||||||
|
'a' in 'a'..'b';
|
||||||
|
0 in 1..2;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.expect_val("true")?
|
||||||
|
.expect_val("true")?
|
||||||
|
.expect_val("false")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -199,3 +199,36 @@ async fn foreach_nested() -> Result<(), Error> {
|
||||||
//
|
//
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn foreach_range() -> Result<(), Error> {
|
||||||
|
let sql = "
|
||||||
|
FOR $test IN 1..4 {
|
||||||
|
IF $test == 2 {
|
||||||
|
BREAK;
|
||||||
|
};
|
||||||
|
UPSERT type::thing('person', $test) SET test = $test;
|
||||||
|
};
|
||||||
|
SELECT * FROM person;
|
||||||
|
";
|
||||||
|
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(), 2);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:1,
|
||||||
|
test: 1,
|
||||||
|
},
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -5527,33 +5527,24 @@ async fn function_type_thing() -> Result<(), Error> {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn function_type_range() -> Result<(), Error> {
|
async fn function_type_range() -> Result<(), Error> {
|
||||||
let sql = r#"
|
let sql = r#"
|
||||||
RETURN type::range('person');
|
RETURN type::range(..);
|
||||||
RETURN type::range('person',1);
|
RETURN type::range(1..2);
|
||||||
RETURN type::range('person',null,10);
|
RETURN type::range([1, 2]);
|
||||||
RETURN type::range('person',1,10);
|
|
||||||
RETURN type::range('person',1,10, { begin: "excluded", end: "included"});
|
|
||||||
"#;
|
"#;
|
||||||
let mut test = Test::new(sql).await?;
|
let mut test = Test::new(sql).await?;
|
||||||
//
|
//
|
||||||
let tmp = test.next()?.result?;
|
let tmp = test.next()?.result?;
|
||||||
let val = Value::parse("person:..");
|
let val = Value::parse("..");
|
||||||
assert_eq!(tmp, val);
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
let tmp = test.next()?.result?;
|
let tmp = test.next()?.result?;
|
||||||
let val = Value::parse("person:1..");
|
let val = Value::parse("1..2");
|
||||||
assert_eq!(tmp, val);
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
let tmp = test.next()?.result?;
|
let tmp = test.next()?.result?;
|
||||||
let val = Value::parse("person:..10");
|
let val = Value::parse("1..2");
|
||||||
assert_eq!(tmp, val);
|
|
||||||
|
|
||||||
let tmp = test.next()?.result?;
|
|
||||||
let val = Value::parse("person:1..10");
|
|
||||||
assert_eq!(tmp, val);
|
|
||||||
|
|
||||||
let tmp = test.next()?.result?;
|
|
||||||
let val = Value::parse("person:1>..=10");
|
|
||||||
assert_eq!(tmp, val);
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue