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 thing_a = Thing {
|
||||
tb: TB.to_owned(),
|
||||
id: Id::String("A".to_string()),
|
||||
id: Id::from("A"),
|
||||
};
|
||||
let value_a: Value = "a".into();
|
||||
let previous = Value::None;
|
||||
|
@ -205,7 +205,7 @@ mod tests {
|
|||
let mut tx2 = ds.transaction(Write, Optimistic).await.unwrap().inner();
|
||||
let thing_c = Thing {
|
||||
tb: TB.to_owned(),
|
||||
id: Id::String("C".to_string()),
|
||||
id: Id::from("C"),
|
||||
};
|
||||
let value_c: Value = "c".into();
|
||||
tx2.record_change(
|
||||
|
@ -223,7 +223,7 @@ mod tests {
|
|||
let mut tx3 = ds.transaction(Write, Optimistic).await.unwrap().inner();
|
||||
let thing_b = Thing {
|
||||
tb: TB.to_owned(),
|
||||
id: Id::String("B".to_string()),
|
||||
id: Id::from("B"),
|
||||
};
|
||||
let value_b: Value = "b".into();
|
||||
tx3.record_change(
|
||||
|
@ -237,7 +237,7 @@ mod tests {
|
|||
);
|
||||
let thing_c2 = Thing {
|
||||
tb: TB.to_owned(),
|
||||
id: Id::String("C".to_string()),
|
||||
id: Id::from("C"),
|
||||
};
|
||||
let value_c2: Value = "c2".into();
|
||||
tx3.record_change(
|
||||
|
@ -434,7 +434,7 @@ mod tests {
|
|||
ds.tick_at(ts.0.timestamp().try_into().unwrap()).await.unwrap();
|
||||
let thing = Thing {
|
||||
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 res =
|
||||
|
@ -531,7 +531,7 @@ mod tests {
|
|||
async fn record_change_feed_entry(tx: Transaction, id: String) -> Thing {
|
||||
let thing = Thing {
|
||||
tb: TB.to_owned(),
|
||||
id: Id::String(id),
|
||||
id: Id::from(id),
|
||||
};
|
||||
let value_a: Value = "a".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);
|
||||
|
||||
/// Used to limit allocation for builtin functions
|
||||
pub static FUNCTION_ALLOCATION_LIMIT: Lazy<usize> = once_cell::sync::Lazy::new(|| {
|
||||
let n = std::env::var("SURREAL_FUNCTION_ALLOCATION_LIMIT")
|
||||
pub static GENERATION_ALLOCATION_LIMIT: Lazy<usize> = once_cell::sync::Lazy::new(|| {
|
||||
let n = std::env::var("SURREAL_GENERATION_ALLOCATION_LIMIT")
|
||||
.map(|s| s.parse::<u32>().unwrap_or(20))
|
||||
.unwrap_or(20);
|
||||
2usize.pow(n)
|
||||
|
|
|
@ -12,10 +12,10 @@ use crate::err::Error;
|
|||
use crate::idx::planner::iterators::{IteratorRecord, IteratorRef};
|
||||
use crate::idx::planner::IterationStage;
|
||||
use crate::sql::edges::Edges;
|
||||
use crate::sql::range::Range;
|
||||
use crate::sql::table::Table;
|
||||
use crate::sql::thing::Thing;
|
||||
use crate::sql::value::Value;
|
||||
use crate::sql::{Id, IdRange};
|
||||
use reblessive::tree::Stk;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use reblessive::TreeStack;
|
||||
|
@ -27,7 +27,7 @@ pub(crate) enum Iterable {
|
|||
Value(Value),
|
||||
Table(Table),
|
||||
Thing(Thing),
|
||||
Range(Range),
|
||||
TableRange(String, IdRange),
|
||||
Edges(Edges),
|
||||
Defer(Thing),
|
||||
Mergeable(Thing, Value),
|
||||
|
@ -152,6 +152,20 @@ impl 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 {
|
||||
Statement::Create(_) => {
|
||||
self.ingest(Iterable::Defer(v));
|
||||
|
@ -161,6 +175,8 @@ impl Iterator {
|
|||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Mock(v) => {
|
||||
// Check if there is a data clause
|
||||
if let Some(data) = stm.data() {
|
||||
|
@ -176,25 +192,6 @@ impl Iterator {
|
|||
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) => {
|
||||
// Check if this is a create statement
|
||||
if let Statement::Create(_) = stm {
|
||||
|
|
|
@ -105,9 +105,9 @@ impl ExplainItem {
|
|||
name: "Iterate Defer".into(),
|
||||
details: vec![("thing", Value::Thing(t.to_owned()))],
|
||||
},
|
||||
Iterable::Range(r) => Self {
|
||||
Iterable::TableRange(tb, r) => Self {
|
||||
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 {
|
||||
name: "Iterate Edges".into(),
|
||||
|
|
|
@ -10,7 +10,8 @@ use crate::idx::planner::IterationStage;
|
|||
use crate::key::{graph, thing};
|
||||
use crate::kvs::Transaction;
|
||||
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"))]
|
||||
use channel::Sender;
|
||||
use futures::StreamExt;
|
||||
|
@ -123,7 +124,9 @@ impl<'a> Processor<'a> {
|
|||
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::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::Table(v) => {
|
||||
if let Some(qp) = ctx.get_query_planner() {
|
||||
|
@ -343,28 +346,29 @@ impl<'a> Processor<'a> {
|
|||
ctx: &Context,
|
||||
opt: &Options,
|
||||
stm: &Statement<'_>,
|
||||
v: Range,
|
||||
tb: String,
|
||||
r: IdRange,
|
||||
) -> Result<(), Error> {
|
||||
// Get the transaction
|
||||
let txn = ctx.tx();
|
||||
// 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
|
||||
let beg = match &v.beg {
|
||||
Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, &v.tb),
|
||||
Bound::Included(id) => thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(),
|
||||
Bound::Excluded(id) => {
|
||||
let mut key = thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap();
|
||||
let beg = match &r.beg {
|
||||
Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, &tb),
|
||||
Bound::Included(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(),
|
||||
Bound::Excluded(v) => {
|
||||
let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap();
|
||||
key.push(0x00);
|
||||
key
|
||||
}
|
||||
};
|
||||
// Prepare the range end key
|
||||
let end = match &v.end {
|
||||
Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, &v.tb),
|
||||
Bound::Excluded(id) => thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(),
|
||||
Bound::Included(id) => {
|
||||
let mut key = thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap();
|
||||
let end = match &r.end {
|
||||
Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, &tb),
|
||||
Bound::Excluded(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(),
|
||||
Bound::Included(v) => {
|
||||
let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap();
|
||||
key.push(0x00);
|
||||
key
|
||||
}
|
||||
|
|
|
@ -1081,6 +1081,19 @@ pub enum Error {
|
|||
#[doc(hidden)]
|
||||
#[error("The underlying datastore does not support versioned queries")]
|
||||
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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::cnf::FUNCTION_ALLOCATION_LIMIT;
|
||||
use crate::cnf::GENERATION_ALLOCATION_LIMIT;
|
||||
use crate::ctx::Context;
|
||||
use crate::dbs::Options;
|
||||
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.
|
||||
fn limit(name: &str, n: usize) -> Result<(), Error> {
|
||||
if n > *FUNCTION_ALLOCATION_LIMIT {
|
||||
if n > *GENERATION_ALLOCATION_LIMIT {
|
||||
Err(Error::InvalidArguments {
|
||||
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 {
|
||||
Ok(())
|
||||
|
|
|
@ -352,6 +352,7 @@ pub fn synchronous(
|
|||
"time::from::secs" => time::from::secs,
|
||||
"time::from::unix" => time::from::unix,
|
||||
//
|
||||
"type::array" => r#type::array,
|
||||
"type::bool" => r#type::bool,
|
||||
"type::bytes" => r#type::bytes,
|
||||
"type::datetime" => r#type::datetime,
|
||||
|
@ -687,6 +688,7 @@ pub async fn idiom(
|
|||
"is_string" => r#type::is::string,
|
||||
"is_uuid" => r#type::is::uuid,
|
||||
//
|
||||
"to_array" => r#type::array,
|
||||
"to_bool" => r#type::bool,
|
||||
"to_bytes" => r#type::bytes,
|
||||
"to_datetime" => r#type::datetime,
|
||||
|
@ -697,6 +699,7 @@ pub async fn idiom(
|
|||
"to_int" => r#type::int,
|
||||
"to_number" => r#type::number,
|
||||
"to_point" => r#type::point,
|
||||
"to_range" => r#type::range,
|
||||
"to_record" => r#type::record,
|
||||
"to_string" => r#type::string,
|
||||
"to_uuid" => r#type::uuid,
|
||||
|
|
|
@ -11,6 +11,7 @@ pub struct Package;
|
|||
impl_module_def!(
|
||||
Package,
|
||||
"type",
|
||||
"array" => run,
|
||||
"bool" => run,
|
||||
"bytes" => 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::fnc::util::string;
|
||||
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.
|
||||
fn limit(name: &str, n: usize) -> Result<(), Error> {
|
||||
if n > *FUNCTION_ALLOCATION_LIMIT {
|
||||
if n > *GENERATION_ALLOCATION_LIMIT {
|
||||
Err(Error::InvalidArguments {
|
||||
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 {
|
||||
Ok(())
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::ops::Bound;
|
||||
|
||||
use crate::ctx::Context;
|
||||
use crate::dbs::Options;
|
||||
use crate::doc::CursorDoc;
|
||||
|
@ -7,10 +5,14 @@ use crate::err::Error;
|
|||
use crate::sql::table::Table;
|
||||
use crate::sql::thing::Thing;
|
||||
use crate::sql::value::Value;
|
||||
use crate::sql::{Id, Kind, Range, Strand};
|
||||
use crate::sql::{Kind, Strand};
|
||||
use crate::syn;
|
||||
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> {
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn range(args: Vec<Value>) -> Result<Value, Error> {
|
||||
if args.len() > 4 || args.is_empty() {
|
||||
return Err(Error::InvalidArguments {
|
||||
name: "type::range".to_owned(),
|
||||
message: "Expected atleast 1 and at most 4 arguments".to_owned(),
|
||||
});
|
||||
}
|
||||
let mut args = args.into_iter();
|
||||
|
||||
// Unwrap will never trigger since length is checked above.
|
||||
let id = args.next().unwrap().as_string();
|
||||
let start = args.next().and_then(|x| match x {
|
||||
Value::Thing(v) => Some(v.id),
|
||||
Value::Array(v) => Some(v.into()),
|
||||
Value::Object(v) => Some(v.into()),
|
||||
Value::Number(v) => Some(v.into()),
|
||||
Value::Null | Value::None => None,
|
||||
v => Some(Id::from(v.as_string())),
|
||||
});
|
||||
let end = args.next().and_then(|x| match x {
|
||||
Value::Thing(v) => Some(v.id),
|
||||
Value::Array(v) => Some(v.into()),
|
||||
Value::Object(v) => Some(v.into()),
|
||||
Value::Number(v) => Some(v.into()),
|
||||
Value::Null | Value::None => None,
|
||||
v => Some(Id::from(v.as_string())),
|
||||
});
|
||||
let (begin, end) = if let Some(x) = args.next() {
|
||||
let Value::Object(x) = x else {
|
||||
return Err(Error::ConvertTo {
|
||||
from: x,
|
||||
into: "object".to_owned(),
|
||||
});
|
||||
};
|
||||
let begin = if let Some(x) = x.get("begin") {
|
||||
let start = start.ok_or_else(|| Error::InvalidArguments {
|
||||
name: "type::range".to_string(),
|
||||
message: "Can't define an inclusion for begin if there is no begin bound"
|
||||
.to_string(),
|
||||
})?;
|
||||
match x {
|
||||
Value::Strand(Strand(x)) if x == "included" => Bound::Included(start),
|
||||
Value::Strand(Strand(x)) if x == "excluded" => Bound::Excluded(start),
|
||||
x => {
|
||||
return Err(Error::ConvertTo {
|
||||
from: x.clone(),
|
||||
into: r#""included" | "excluded""#.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
start.map(Bound::Included).unwrap_or(Bound::Unbounded)
|
||||
};
|
||||
let end = if let Some(x) = x.get("end") {
|
||||
let end = end.ok_or_else(|| Error::InvalidArguments {
|
||||
name: "type::range".to_string(),
|
||||
message: "Can't define an inclusion for end if there is no end bound".to_string(),
|
||||
})?;
|
||||
match x {
|
||||
Value::Strand(Strand(x)) if x == "included" => Bound::Included(end),
|
||||
Value::Strand(Strand(x)) if x == "excluded" => Bound::Excluded(end),
|
||||
x => {
|
||||
return Err(Error::ConvertTo {
|
||||
from: x.clone(),
|
||||
into: r#""included" | "excluded""#.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
end.map(Bound::Excluded).unwrap_or(Bound::Unbounded)
|
||||
};
|
||||
(begin, end)
|
||||
} else {
|
||||
(
|
||||
start.map(Bound::Included).unwrap_or(Bound::Unbounded),
|
||||
end.map(Bound::Excluded).unwrap_or(Bound::Unbounded),
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Range {
|
||||
tb: id,
|
||||
beg: begin,
|
||||
end,
|
||||
}
|
||||
.into())
|
||||
pub fn range((val,): (Value,)) -> Result<Value, Error> {
|
||||
val.convert_to_range().map(Value::from)
|
||||
}
|
||||
|
||||
pub fn record((rid, tb): (Value, Option<Value>)) -> Result<Value, Error> {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::idx::planner::executor::KnnExpressions;
|
||||
use crate::sql::id::range::IdRange;
|
||||
use crate::sql::part::DestructurePart;
|
||||
use crate::sql::{
|
||||
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::Array(a) => self.eval_array(a).map(Id::Array),
|
||||
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> {
|
||||
if let Some(beg) = self.eval_bound(&r.beg) {
|
||||
self.eval_bound(&r.end).map(|end| Range {
|
||||
tb: r.tb.clone(),
|
||||
beg,
|
||||
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 {
|
||||
Bound::Included(id) => self.eval_id(id).map(Bound::Included),
|
||||
Bound::Excluded(id) => self.eval_id(id).map(Bound::Excluded),
|
||||
Bound::Included(v) => self.eval_value(v).map(Bound::Included),
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Stores a record document
|
||||
use crate::key::category::Categorise;
|
||||
use crate::key::category::Category;
|
||||
use crate::sql::id::Id;
|
||||
use crate::sql::Id;
|
||||
use derive::Key;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
@ -2,14 +2,20 @@ use ciborium::Value as Data;
|
|||
use geo::{LineString, Point, Polygon};
|
||||
use geo_types::{MultiLineString, MultiPoint, MultiPolygon};
|
||||
use rust_decimal::Decimal;
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::once;
|
||||
use std::ops::Bound;
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::sql::id::range::IdRange;
|
||||
use crate::sql::Array;
|
||||
use crate::sql::Datetime;
|
||||
use crate::sql::Duration;
|
||||
use crate::sql::Geometry;
|
||||
use crate::sql::Id;
|
||||
use crate::sql::Number;
|
||||
use crate::sql::Object;
|
||||
use crate::sql::Range;
|
||||
use crate::sql::Thing;
|
||||
use crate::sql::Uuid;
|
||||
use crate::sql::Value;
|
||||
|
@ -30,6 +36,11 @@ const TAG_CUSTOM_DATETIME: u64 = 12;
|
|||
const TAG_STRING_DURATION: u64 = 13;
|
||||
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
|
||||
const TAG_GEOMETRY_POINT: u64 = 88;
|
||||
const TAG_GEOMETRY_LINE: u64 = 89;
|
||||
|
@ -52,17 +63,8 @@ impl TryFrom<Cbor> for Value {
|
|||
Data::Float(v) => Ok(Value::from(v)),
|
||||
Data::Bytes(v) => Ok(Value::Bytes(v.into())),
|
||||
Data::Text(v) => Ok(Value::from(v)),
|
||||
Data::Array(v) => {
|
||||
v.into_iter().map(|v| Value::try_from(Cbor(v))).collect::<Result<Value, &str>>()
|
||||
}
|
||||
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::Array(v) => Ok(Value::Array(Array::try_from(v)?)),
|
||||
Data::Map(v) => Ok(Value::Object(Object::try_from(v)?)),
|
||||
Data::Tag(t, v) => {
|
||||
match t {
|
||||
// A literal datetime
|
||||
|
@ -175,21 +177,12 @@ impl TryFrom<Cbor> for Value {
|
|||
),
|
||||
};
|
||||
|
||||
match Value::try_from(Cbor(v.remove(0))) {
|
||||
Ok(Value::Strand(id)) => {
|
||||
Ok(Value::from(Thing::from((tb, Id::from(id)))))
|
||||
}
|
||||
Ok(Value::Number(Number::Int(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"),
|
||||
}
|
||||
let id = Id::try_from(v.remove(0))?;
|
||||
|
||||
Ok(Value::Thing(Thing {
|
||||
tb,
|
||||
id,
|
||||
}))
|
||||
}
|
||||
_ => 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())),
|
||||
_ => 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 {
|
||||
Data::Array(mut v) if v.len() == 2 => {
|
||||
let x = Value::try_from(Cbor(v.remove(0)))?;
|
||||
|
@ -393,11 +388,13 @@ impl TryFrom<Value> for Cbor {
|
|||
Id::Generate(_) => {
|
||||
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::Geometry(v) => Ok(Cbor(encode_geometry(v)?)),
|
||||
Value::Range(v) => Ok(Cbor(Data::try_from(*v)?)),
|
||||
// We shouldn't reach here
|
||||
_ => 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 a single result?
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
let sql = "SELECT * FROM $what";
|
||||
// Specify the query parameters
|
||||
|
@ -295,7 +295,7 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
// Return a single result?
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
let sql = "INSERT INTO $what $data RETURN AFTER";
|
||||
// Specify the query parameters
|
||||
|
@ -324,7 +324,7 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
// Return a single result?
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
let sql = if data.is_none_or_null() {
|
||||
"CREATE $what RETURN AFTER"
|
||||
|
@ -357,7 +357,7 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
// Return a single result?
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
let sql = if data.is_none_or_null() {
|
||||
"UPSERT $what RETURN AFTER"
|
||||
|
@ -390,7 +390,7 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
// Return a single result?
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
let sql = if data.is_none_or_null() {
|
||||
"UPDATE $what RETURN AFTER"
|
||||
|
@ -423,7 +423,7 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
// Return a single result?
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
let sql = if data.is_none_or_null() {
|
||||
"UPDATE $what RETURN AFTER"
|
||||
|
@ -456,7 +456,7 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
// Return a single result?
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
let sql = match diff.is_true() {
|
||||
true => "UPDATE $what PATCH $data RETURN DIFF",
|
||||
|
@ -488,7 +488,7 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
// Return a single result?
|
||||
let one = kind.is_thing();
|
||||
let one = kind.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
let sql = if data.is_none_or_null() {
|
||||
"RELATE $from->$kind->$to"
|
||||
|
@ -523,7 +523,7 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
// Return a single result?
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
let sql = "DELETE $what RETURN BEFORE";
|
||||
// Specify the query parameters
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use super::Range;
|
||||
use crate::cnf::ID_CHARS;
|
||||
use crate::ctx::Context;
|
||||
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 derive::Key;
|
||||
use nanoid::nanoid;
|
||||
use range::IdRange;
|
||||
use reblessive::tree::Stk;
|
||||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::{Bound, Deref};
|
||||
use ulid::Ulid;
|
||||
|
||||
pub mod range;
|
||||
|
||||
#[revisioned(revision = 1)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
|
@ -33,6 +38,7 @@ pub enum Id {
|
|||
Array(Array),
|
||||
Object(Object),
|
||||
Generate(Gen),
|
||||
Range(Box<IdRange>),
|
||||
}
|
||||
|
||||
impl From<i64> for Id {
|
||||
|
@ -97,25 +103,25 @@ impl From<&String> for Id {
|
|||
|
||||
impl From<Vec<&str>> for Id {
|
||||
fn from(v: Vec<&str>) -> Self {
|
||||
Id::Array(v.into())
|
||||
Self::Array(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<String>> for Id {
|
||||
fn from(v: Vec<String>) -> Self {
|
||||
Id::Array(v.into())
|
||||
Self::Array(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Value>> for Id {
|
||||
fn from(v: Vec<Value>) -> Self {
|
||||
Id::Array(v.into())
|
||||
Self::Array(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<String, Value>> for Id {
|
||||
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 {
|
||||
fn from(v: Thing) -> Self {
|
||||
v.id
|
||||
|
@ -160,6 +202,7 @@ impl Id {
|
|||
Gen::Ulid => "ulid()".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::Uuid => Display::fmt("uuid()", f),
|
||||
},
|
||||
Self::Range(v) => Display::fmt(v, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,6 +249,7 @@ impl Id {
|
|||
Gen::Ulid => Ok(Self::ulid()),
|
||||
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>),
|
||||
Array(Box<Kind>, Option<u64>),
|
||||
Function(Option<Vec<Kind>>, Option<Box<Kind>>),
|
||||
Range,
|
||||
}
|
||||
|
||||
impl Default for Kind {
|
||||
|
@ -73,7 +74,8 @@ impl Kind {
|
|||
| Kind::Uuid
|
||||
| Kind::Record(_)
|
||||
| Kind::Geometry(_)
|
||||
| Kind::Function(_, _) => return None,
|
||||
| Kind::Function(_, _)
|
||||
| Kind::Range => return None,
|
||||
Kind::Option(x) => {
|
||||
this = x;
|
||||
}
|
||||
|
@ -137,6 +139,7 @@ impl Display for Kind {
|
|||
(k, Some(l)) => write!(f, "array<{k}, {l}>"),
|
||||
},
|
||||
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::group::Group;
|
||||
pub use self::group::Groups;
|
||||
pub use self::id::range::IdRange;
|
||||
pub use self::id::Id;
|
||||
pub use self::ident::Ident;
|
||||
pub use self::idiom::Idiom;
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
use crate::cnf::GENERATION_ALLOCATION_LIMIT;
|
||||
use crate::ctx::Context;
|
||||
use crate::dbs::Options;
|
||||
use crate::doc::CursorDoc;
|
||||
use crate::err::Error;
|
||||
use crate::sql::Cond;
|
||||
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::sql::{Number, Subquery, Value};
|
||||
use crate::syn;
|
||||
use reblessive::tree::Stk;
|
||||
use revision::revisioned;
|
||||
|
@ -19,7 +13,8 @@ use std::fmt;
|
|||
use std::ops::Bound;
|
||||
use std::str::FromStr;
|
||||
|
||||
const ID: &str = "id";
|
||||
use super::Id;
|
||||
|
||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range";
|
||||
|
||||
#[revisioned(revision = 1)]
|
||||
|
@ -28,10 +23,8 @@ pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range";
|
|||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
#[non_exhaustive]
|
||||
pub struct Range {
|
||||
#[serde(with = "no_nul_bytes")]
|
||||
pub tb: String,
|
||||
pub beg: Bound<Id>,
|
||||
pub end: Bound<Id>,
|
||||
pub beg: Bound<Value>,
|
||||
pub end: Bound<Value>,
|
||||
}
|
||||
|
||||
impl FromStr for Range {
|
||||
|
@ -51,108 +44,54 @@ impl TryFrom<&str> for Range {
|
|||
}
|
||||
}
|
||||
|
||||
impl Range {
|
||||
/// Construct a new range
|
||||
pub fn new(tb: String, beg: Bound<Id>, end: Bound<Id>) -> Self {
|
||||
Self {
|
||||
tb,
|
||||
beg,
|
||||
end,
|
||||
impl From<(Bound<Id>, Bound<Id>)> for Range {
|
||||
fn from(v: (Bound<Id>, Bound<Id>)) -> Self {
|
||||
fn convert(v: Bound<Id>) -> Bound<Value> {
|
||||
match v {
|
||||
Bound::Included(v) => Bound::Included(v.into()),
|
||||
Bound::Excluded(v) => Bound::Excluded(v.into()),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `Range` to `Cond`
|
||||
pub fn to_cond(self) -> Option<Cond> {
|
||||
match (self.beg, self.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)).into(),
|
||||
)))))
|
||||
Self {
|
||||
beg: convert(v.0),
|
||||
end: convert(v.1),
|
||||
}
|
||||
(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(),
|
||||
Operator::MoreThan,
|
||||
Thing::from((self.tb, id)).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)).into(),
|
||||
)))))
|
||||
|
||||
impl TryInto<std::ops::Range<i64>> for Range {
|
||||
type Error = Error;
|
||||
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(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>,
|
||||
) -> Result<Value, Error> {
|
||||
Ok(Value::Range(Box::new(Range {
|
||||
tb: self.tb.clone(),
|
||||
beg: match &self.beg {
|
||||
Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?),
|
||||
Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?),
|
||||
Bound::Included(v) => Bound::Included(v.compute(stk, ctx, opt, doc).await?),
|
||||
Bound::Excluded(v) => Bound::Excluded(v.compute(stk, ctx, opt, doc).await?),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
},
|
||||
end: match &self.end {
|
||||
Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?),
|
||||
Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?),
|
||||
Bound::Included(v) => Bound::Included(v.compute(stk, ctx, opt, doc).await?),
|
||||
Bound::Excluded(v) => Bound::Excluded(v.compute(stk, ctx, opt, doc).await?),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
},
|
||||
})))
|
||||
|
@ -182,74 +120,94 @@ impl Range {
|
|||
|
||||
impl PartialOrd for Range {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match self.tb.partial_cmp(&other.tb) {
|
||||
Some(Ordering::Equal) => match &self.beg {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Range {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match &self.beg {
|
||||
Bound::Unbounded => match &other.beg {
|
||||
Bound::Unbounded => Some(Ordering::Equal),
|
||||
_ => Some(Ordering::Less),
|
||||
Bound::Unbounded => Ordering::Equal,
|
||||
_ => Ordering::Less,
|
||||
},
|
||||
Bound::Included(v) => match &other.beg {
|
||||
Bound::Unbounded => Some(Ordering::Greater),
|
||||
Bound::Included(w) => match v.partial_cmp(w) {
|
||||
Some(Ordering::Equal) => match &self.end {
|
||||
Bound::Unbounded => Ordering::Greater,
|
||||
Bound::Included(w) => match v.cmp(w) {
|
||||
Ordering::Equal => match &self.end {
|
||||
Bound::Unbounded => match &other.end {
|
||||
Bound::Unbounded => Some(Ordering::Equal),
|
||||
_ => Some(Ordering::Greater),
|
||||
Bound::Unbounded => Ordering::Equal,
|
||||
_ => Ordering::Greater,
|
||||
},
|
||||
Bound::Included(v) => match &other.end {
|
||||
Bound::Unbounded => Some(Ordering::Less),
|
||||
Bound::Included(w) => v.partial_cmp(w),
|
||||
_ => Some(Ordering::Greater),
|
||||
Bound::Unbounded => Ordering::Less,
|
||||
Bound::Included(w) => v.cmp(w),
|
||||
_ => Ordering::Greater,
|
||||
},
|
||||
Bound::Excluded(v) => match &other.end {
|
||||
Bound::Excluded(w) => v.partial_cmp(w),
|
||||
_ => Some(Ordering::Less),
|
||||
Bound::Excluded(w) => v.cmp(w),
|
||||
_ => Ordering::Less,
|
||||
},
|
||||
},
|
||||
ordering => ordering,
|
||||
},
|
||||
_ => Some(Ordering::Less),
|
||||
_ => Ordering::Less,
|
||||
},
|
||||
Bound::Excluded(v) => match &other.beg {
|
||||
Bound::Excluded(w) => match v.partial_cmp(w) {
|
||||
Some(Ordering::Equal) => match &self.end {
|
||||
Bound::Excluded(w) => match v.cmp(w) {
|
||||
Ordering::Equal => match &self.end {
|
||||
Bound::Unbounded => match &other.end {
|
||||
Bound::Unbounded => Some(Ordering::Equal),
|
||||
_ => Some(Ordering::Greater),
|
||||
Bound::Unbounded => Ordering::Equal,
|
||||
_ => Ordering::Greater,
|
||||
},
|
||||
Bound::Included(v) => match &other.end {
|
||||
Bound::Unbounded => Some(Ordering::Less),
|
||||
Bound::Included(w) => v.partial_cmp(w),
|
||||
_ => Some(Ordering::Greater),
|
||||
Bound::Unbounded => Ordering::Less,
|
||||
Bound::Included(w) => v.cmp(w),
|
||||
_ => Ordering::Greater,
|
||||
},
|
||||
Bound::Excluded(v) => match &other.end {
|
||||
Bound::Excluded(w) => v.partial_cmp(w),
|
||||
_ => Some(Ordering::Less),
|
||||
Bound::Excluded(w) => v.cmp(w),
|
||||
_ => Ordering::Less,
|
||||
},
|
||||
},
|
||||
ordering => ordering,
|
||||
},
|
||||
_ => Some(Ordering::Greater),
|
||||
_ => Ordering::Greater,
|
||||
},
|
||||
},
|
||||
ordering => ordering,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Range {
|
||||
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 {
|
||||
Bound::Unbounded => write!(f, ""),
|
||||
Bound::Included(id) => write!(f, "{id}"),
|
||||
Bound::Excluded(id) => write!(f, "{id}>"),
|
||||
Bound::Included(v) => write!(f, "{}", bound_value(v)),
|
||||
Bound::Excluded(v) => write!(f, "{}>", bound_value(v)),
|
||||
}?;
|
||||
match &self.end {
|
||||
Bound::Unbounded => write!(f, ".."),
|
||||
Bound::Excluded(id) => write!(f, "..{id}"),
|
||||
Bound::Included(id) => write!(f, "..={id}"),
|
||||
Bound::Excluded(v) => write!(f, "..{}", bound_value(v)),
|
||||
Bound::Included(v) => write!(f, "..={}", bound_value(v)),
|
||||
}?;
|
||||
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;
|
||||
}
|
||||
if let Some(ref comment) = &self.comment {
|
||||
dt.comment = comment.clone();
|
||||
dt.comment.clone_from(comment);
|
||||
}
|
||||
if let Some(ref kind) = &self.kind {
|
||||
dt.kind = kind.clone();
|
||||
|
|
|
@ -8,6 +8,7 @@ use reblessive::tree::Stk;
|
|||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{self, Display};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[revisioned(revision = 1)]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||
|
@ -19,6 +20,22 @@ pub struct ForeachStatement {
|
|||
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 {
|
||||
/// Check if we require a writeable transaction
|
||||
pub(crate) fn writeable(&self) -> bool {
|
||||
|
@ -35,10 +52,23 @@ impl ForeachStatement {
|
|||
doc: Option<&CursorDoc>,
|
||||
) -> Result<Value, Error> {
|
||||
// Check the loop data
|
||||
match &self.range.compute(stk, ctx, opt, doc).await? {
|
||||
Value::Array(arr) => {
|
||||
let data = self.range.compute(stk, ctx, opt, doc).await?;
|
||||
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
|
||||
'foreach: for v in arr.iter() {
|
||||
'foreach: for v in iter {
|
||||
// Duplicate context
|
||||
let ctx = MutableContext::new(ctx).freeze();
|
||||
// Set the current parameter
|
||||
|
@ -61,9 +91,7 @@ impl ForeachStatement {
|
|||
Entry::Value(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
||||
Entry::Break(v) => v.compute(&ctx, opt, doc).await,
|
||||
Entry::Continue(v) => v.compute(&ctx, opt, doc).await,
|
||||
Entry::Foreach(v) => {
|
||||
stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await
|
||||
}
|
||||
Entry::Foreach(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::Create(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
|
||||
|
@ -95,11 +123,6 @@ impl ForeachStatement {
|
|||
// Ok all good
|
||||
Ok(Value::None)
|
||||
}
|
||||
v => Err(Error::InvalidStatementTarget {
|
||||
value: v.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ForeachStatement {
|
||||
|
|
|
@ -4,8 +4,8 @@ use crate::doc::CursorDoc;
|
|||
use crate::err::Error;
|
||||
use crate::idx::planner::QueryPlanner;
|
||||
use crate::sql::{
|
||||
Cond, Explain, Fetchs, Field, Fields, Groups, Idioms, Limit, Orders, Splits, Start, Timeout,
|
||||
Value, Values, Version, With,
|
||||
Cond, Explain, Fetchs, Field, Fields, Groups, Id, Idioms, Limit, Orders, Splits, Start,
|
||||
Timeout, Value, Values, Version, With,
|
||||
};
|
||||
use derive::Store;
|
||||
use reblessive::tree::Stk;
|
||||
|
@ -101,14 +101,10 @@ impl SelectStatement {
|
|||
|
||||
planner.add_iterables(stk, ctx, t, &mut i).await?;
|
||||
}
|
||||
Value::Thing(v) => i.ingest(Iterable::Thing(v)),
|
||||
Value::Range(v) => {
|
||||
if self.only && !limit_is_one_or_zero {
|
||||
return Err(Error::SingleOnlyOutput);
|
||||
}
|
||||
|
||||
i.ingest(Iterable::Range(*v))
|
||||
}
|
||||
Value::Thing(v) => match &v.id {
|
||||
Id::Range(r) => i.ingest(Iterable::TableRange(v.tb, *r.to_owned())),
|
||||
_ => i.ingest(Iterable::Thing(v)),
|
||||
},
|
||||
Value::Edges(v) => {
|
||||
if self.only && !limit_is_one_or_zero {
|
||||
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::dbs::Options;
|
||||
use crate::doc::CursorDoc;
|
||||
|
@ -9,10 +11,10 @@ use reblessive::tree::Stk;
|
|||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::ops::Bound;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::Table;
|
||||
|
||||
const ID: &str = "id";
|
||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing";
|
||||
|
||||
#[revisioned(revision = 1)]
|
||||
|
@ -26,6 +28,110 @@ pub struct Thing {
|
|||
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 {
|
||||
fn from((tb, id): (&str, Id)) -> 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 {
|
||||
fn from((tb, id): (String, String)) -> Self {
|
||||
Self::from((tb, Id::from(id)))
|
||||
|
|
|
@ -388,7 +388,6 @@ mod tests {
|
|||
#[test]
|
||||
fn range() {
|
||||
let range = Box::new(Range {
|
||||
tb: "foo".to_owned(),
|
||||
beg: Bound::Included("foo".into()),
|
||||
end: Bound::Unbounded,
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::dbs::Options;
|
|||
use crate::doc::CursorDoc;
|
||||
use crate::err::Error;
|
||||
use crate::fnc::util::string::fuzzy::Fuzzy;
|
||||
use crate::sql::id::range::IdRange;
|
||||
use crate::sql::statements::info::InfoStructure;
|
||||
use crate::sql::Closure;
|
||||
use crate::sql::{
|
||||
|
@ -28,7 +29,7 @@ use std::cmp::Ordering;
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
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";
|
||||
|
||||
|
@ -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 {
|
||||
fn from(v: Edges) -> Self {
|
||||
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 {
|
||||
fn from(v: Id) -> Self {
|
||||
match v {
|
||||
|
@ -578,6 +606,7 @@ impl From<Id> for Value {
|
|||
Gen::Ulid => Id::ulid().into(),
|
||||
Gen::Uuid => Id::uuid().into(),
|
||||
},
|
||||
Id::Range(v) => v.deref().to_owned().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -923,6 +952,25 @@ impl Value {
|
|||
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
|
||||
pub fn is_mock(&self) -> bool {
|
||||
matches!(self, Value::Mock(_))
|
||||
|
@ -1223,6 +1271,7 @@ impl Value {
|
|||
Self::Geometry(Geometry::MultiPolygon(_)) => "geometry<multipolygon>",
|
||||
Self::Geometry(Geometry::Collection(_)) => "geometry<collection>",
|
||||
Self::Bytes(_) => "bytes",
|
||||
Self::Range(_) => "range",
|
||||
_ => "incorrect type",
|
||||
}
|
||||
}
|
||||
|
@ -1249,6 +1298,7 @@ impl Value {
|
|||
Kind::Point => self.coerce_to_point().map(Value::from),
|
||||
Kind::Bytes => self.coerce_to_bytes().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::Set(t, l) => match l {
|
||||
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
|
||||
pub(crate) fn coerce_to_point(self) -> Result<Geometry, Error> {
|
||||
match self {
|
||||
|
@ -1818,6 +1881,7 @@ impl Value {
|
|||
Kind::Point => self.convert_to_point().map(Value::from),
|
||||
Kind::Bytes => self.convert_to_bytes().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::Set(t, l) => match l {
|
||||
Some(l) => self.convert_to_set_type_len(t, l).map(Value::from),
|
||||
|
@ -2206,6 +2270,10 @@ impl Value {
|
|||
match self {
|
||||
// Arrays are allowed
|
||||
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
|
||||
_ => Err(Error::ConvertTo {
|
||||
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
|
||||
pub(crate) fn convert_to_point(self) -> Result<Geometry, Error> {
|
||||
match self {
|
||||
|
@ -2588,6 +2676,19 @@ impl Value {
|
|||
Value::Geometry(w) => v.contains(w),
|
||||
_ => 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,
|
||||
}
|
||||
}
|
||||
|
@ -2654,6 +2755,25 @@ impl Value {
|
|||
_ => 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 {
|
||||
|
|
|
@ -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("PRUNE") => TokenKind::Keyword(Keyword::Prune),
|
||||
UniCase::ascii("PUNCT") => TokenKind::Keyword(Keyword::Punct),
|
||||
UniCase::ascii("RANGE") => TokenKind::Keyword(Keyword::Range),
|
||||
UniCase::ascii("READONLY") => TokenKind::Keyword(Keyword::Readonly),
|
||||
UniCase::ascii("RELATE") => TokenKind::Keyword(Keyword::Relate),
|
||||
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::unix") => PathKind::Function,
|
||||
//
|
||||
UniCase::ascii("type::array") => PathKind::Function,
|
||||
UniCase::ascii("type::bool") => PathKind::Function,
|
||||
UniCase::ascii("type::bytes") => PathKind::Function,
|
||||
UniCase::ascii("type::datetime") => PathKind::Function,
|
||||
|
|
|
@ -912,7 +912,7 @@ mod tests {
|
|||
Value::from(Idiom(vec![
|
||||
Part::Start(Value::Thing(Thing {
|
||||
tb: "test".to_owned(),
|
||||
id: Id::Number(1),
|
||||
id: Id::from(1),
|
||||
})),
|
||||
Part::from("foo"),
|
||||
]))
|
||||
|
@ -928,7 +928,7 @@ mod tests {
|
|||
Value::from(Idiom(vec![
|
||||
Part::Start(Value::Thing(Thing {
|
||||
tb: "test".to_owned(),
|
||||
id: Id::Number(1),
|
||||
id: Id::from(1),
|
||||
})),
|
||||
Part::Value(Value::Strand(Strand("foo".to_owned()))),
|
||||
]))
|
||||
|
@ -944,7 +944,7 @@ mod tests {
|
|||
Value::from(Idiom(vec![
|
||||
Part::Start(Value::Thing(Thing {
|
||||
tb: "test".to_owned(),
|
||||
id: Id::Number(1),
|
||||
id: Id::from(1),
|
||||
})),
|
||||
Part::All
|
||||
]))
|
||||
|
|
|
@ -82,6 +82,7 @@ impl Parser<'_> {
|
|||
t!("POINT") => Ok(Kind::Point),
|
||||
t!("STRING") => Ok(Kind::String),
|
||||
t!("UUID") => Ok(Kind::Uuid),
|
||||
t!("RANGE") => Ok(Kind::Range),
|
||||
t!("FUNCTION") => Ok(Kind::Function(Default::default(), Default::default())),
|
||||
t!("RECORD") => {
|
||||
let span = self.peek().span;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::ops::Bound;
|
||||
|
||||
use geo::Point;
|
||||
use reblessive::Stk;
|
||||
|
||||
|
@ -6,14 +8,14 @@ use crate::{
|
|||
enter_object_recursion, enter_query_recursion,
|
||||
sql::{
|
||||
Array, Closure, Dir, Function, Geometry, Ident, Idiom, Kind, Mock, Number, Param, Part,
|
||||
Script, Strand, Subquery, Table, Value,
|
||||
Range, Script, Strand, Subquery, Table, Value,
|
||||
},
|
||||
syn::{
|
||||
parser::{
|
||||
mac::{expected, unexpected},
|
||||
mac::{expected, expected_whitespace, unexpected},
|
||||
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.
|
||||
pub async fn parse_what_primary(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
|
||||
match self.peek_kind() {
|
||||
t!("..") => Ok(self.try_parse_range(ctx, None).await?.unwrap()),
|
||||
t!("r\"") => {
|
||||
self.pop_peek();
|
||||
let thing = self.parse_record_string(ctx, true).await?;
|
||||
Ok(Value::Thing(thing))
|
||||
let value = Value::Thing(self.parse_record_string(ctx, true).await?);
|
||||
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
|
||||
}
|
||||
t!("r'") => {
|
||||
self.pop_peek();
|
||||
let thing = self.parse_record_string(ctx, false).await?;
|
||||
Ok(Value::Thing(thing))
|
||||
let value = Value::Thing(self.parse_record_string(ctx, false).await?);
|
||||
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
|
||||
}
|
||||
t!("d\"") | t!("d'") => {
|
||||
let datetime = self.next_token_value()?;
|
||||
Ok(Value::Datetime(datetime))
|
||||
let value = Value::Datetime(self.next_token_value()?);
|
||||
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
|
||||
}
|
||||
t!("u\"") | t!("u'") => {
|
||||
let uuid = self.next_token_value()?;
|
||||
Ok(Value::Uuid(uuid))
|
||||
let value = Value::Uuid(self.next_token_value()?);
|
||||
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
|
||||
}
|
||||
t!("$param") => {
|
||||
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") => {
|
||||
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(
|
||||
&mut self,
|
||||
ctx: &mut Stk,
|
||||
subject: &Value,
|
||||
) -> ParseResult<Option<Value>> {
|
||||
if self.eat(t!("(")) {
|
||||
if self.eat_whitespace(t!("(")) {
|
||||
let start = self.last_span();
|
||||
let mut args = Vec::new();
|
||||
loop {
|
||||
|
@ -171,21 +223,26 @@ impl Parser<'_> {
|
|||
pub async fn parse_idiom_expression(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
|
||||
let token = self.peek();
|
||||
let value = match token.kind {
|
||||
t!("..") => self.try_parse_range(ctx, None).await?.unwrap(),
|
||||
t!("NONE") => {
|
||||
self.pop_peek();
|
||||
Value::None
|
||||
let value = Value::None;
|
||||
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||
}
|
||||
t!("NULL") => {
|
||||
self.pop_peek();
|
||||
Value::Null
|
||||
let value = Value::Null;
|
||||
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||
}
|
||||
t!("true") => {
|
||||
self.pop_peek();
|
||||
Value::Bool(true)
|
||||
let value = Value::Bool(true);
|
||||
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||
}
|
||||
t!("false") => {
|
||||
self.pop_peek();
|
||||
Value::Bool(false)
|
||||
let value = Value::Bool(false);
|
||||
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||
}
|
||||
t!("<") => {
|
||||
self.pop_peek();
|
||||
|
@ -198,21 +255,21 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("r\"") => {
|
||||
self.pop_peek();
|
||||
let thing = self.parse_record_string(ctx, true).await?;
|
||||
Value::Thing(thing)
|
||||
let value = Value::Thing(self.parse_record_string(ctx, true).await?);
|
||||
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||
}
|
||||
t!("r'") => {
|
||||
self.pop_peek();
|
||||
let thing = self.parse_record_string(ctx, false).await?;
|
||||
Value::Thing(thing)
|
||||
let value = Value::Thing(self.parse_record_string(ctx, false).await?);
|
||||
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||
}
|
||||
t!("d\"") | t!("d'") => {
|
||||
let datetime = self.next_token_value()?;
|
||||
Value::Datetime(datetime)
|
||||
let value = Value::Datetime(self.next_token_value()?);
|
||||
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||
}
|
||||
t!("u\"") | t!("u'") => {
|
||||
let uuid = self.next_token_value()?;
|
||||
Value::Uuid(uuid)
|
||||
let value = Value::Uuid(self.next_token_value()?);
|
||||
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
|
||||
}
|
||||
t!("'") | t!("\"") | TokenKind::Strand => {
|
||||
let s = self.next_token_value::<Strand>()?;
|
||||
|
@ -221,18 +278,22 @@ impl Parser<'_> {
|
|||
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 => {
|
||||
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 => {
|
||||
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") => {
|
||||
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") => {
|
||||
self.pop_peek();
|
||||
|
@ -256,12 +317,14 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("[") => {
|
||||
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!("{") => {
|
||||
self.pop_peek();
|
||||
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!("|") => {
|
||||
self.pop_peek();
|
||||
|
@ -281,7 +344,8 @@ impl Parser<'_> {
|
|||
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)
|
||||
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!("RETURN")
|
||||
|
@ -749,6 +813,133 @@ impl Parser<'_> {
|
|||
.map_err(|(e, span)| ParseError::new(ParseErrorKind::InvalidToken(e), span))?;
|
||||
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)]
|
||||
|
|
|
@ -1664,7 +1664,7 @@ fn parse_delete_2() {
|
|||
dir: Dir::Out,
|
||||
from: Thing {
|
||||
tb: "a".to_owned(),
|
||||
id: Id::String("b".to_owned()),
|
||||
id: Id::from("b"),
|
||||
},
|
||||
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 {
|
||||
tb: "a".to_owned(),
|
||||
id: Id::String("b".to_owned()),
|
||||
id: Id::from("b"),
|
||||
}))),
|
||||
start: Some(Start(Value::Object(Object(
|
||||
[("a".to_owned(), Value::Bool(true))].into_iter().collect()
|
||||
|
@ -2191,7 +2191,7 @@ fn parse_relate() {
|
|||
only: true,
|
||||
kind: Value::Thing(Thing {
|
||||
tb: "a".to_owned(),
|
||||
id: Id::String("b".to_owned()),
|
||||
id: Id::from("b"),
|
||||
}),
|
||||
from: Value::Array(Array(vec![
|
||||
Value::Number(Number::Int(1)),
|
||||
|
|
|
@ -410,7 +410,7 @@ fn statements() -> Vec<Statement> {
|
|||
dir: Dir::Out,
|
||||
from: Thing {
|
||||
tb: "a".to_owned(),
|
||||
id: Id::String("b".to_owned()),
|
||||
id: Id::from("b"),
|
||||
},
|
||||
what: Tables::default(),
|
||||
}))),
|
||||
|
@ -519,7 +519,7 @@ fn statements() -> Vec<Statement> {
|
|||
}])),
|
||||
limit: Some(Limit(Value::Thing(Thing {
|
||||
tb: "a".to_owned(),
|
||||
id: Id::String("b".to_owned()),
|
||||
id: Id::from("b"),
|
||||
}))),
|
||||
start: Some(Start(Value::Object(Object(
|
||||
[("a".to_owned(), Value::Bool(true))].into_iter().collect(),
|
||||
|
@ -625,7 +625,7 @@ fn statements() -> Vec<Statement> {
|
|||
only: true,
|
||||
kind: Value::Thing(Thing {
|
||||
tb: "a".to_owned(),
|
||||
id: Id::String("b".to_owned()),
|
||||
id: Id::from("b"),
|
||||
}),
|
||||
from: Value::Array(Array(vec![
|
||||
Value::Number(Number::Int(1)),
|
||||
|
|
|
@ -93,13 +93,13 @@ fn parse_recursive_record_string() {
|
|||
res,
|
||||
Value::Thing(Thing {
|
||||
tb: "a".to_owned(),
|
||||
id: Id::Array(Array(vec![Value::Thing(Thing {
|
||||
id: Id::from(Array(vec![Value::Thing(Thing {
|
||||
tb: "b".to_owned(),
|
||||
id: Id::Object(Object(BTreeMap::from([(
|
||||
id: Id::from(Object(BTreeMap::from([(
|
||||
"c".to_owned(),
|
||||
Value::Thing(Thing {
|
||||
tb: "d".to_owned(),
|
||||
id: Id::Number(1)
|
||||
id: Id::from(1)
|
||||
})
|
||||
)])))
|
||||
})]))
|
||||
|
@ -114,7 +114,7 @@ fn parse_record_string_2() {
|
|||
res,
|
||||
Value::Thing(Thing {
|
||||
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 crate::{
|
||||
sql::{id::Gen, Id, Ident, Range, Thing, Value},
|
||||
sql::{
|
||||
id::{range::IdRange, Gen},
|
||||
Id, Ident, Range, Thing, Value,
|
||||
},
|
||||
syn::{
|
||||
parser::{
|
||||
mac::{expected, expected_whitespace, unexpected},
|
||||
|
@ -51,22 +54,24 @@ impl Parser<'_> {
|
|||
} else {
|
||||
Bound::Unbounded
|
||||
};
|
||||
return Ok(Value::Range(Box::new(Range {
|
||||
return Ok(Value::Thing(Thing {
|
||||
tb: ident,
|
||||
id: Id::Range(Box::new(IdRange {
|
||||
beg: Bound::Unbounded,
|
||||
end,
|
||||
})));
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
// 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 id = stk.run(|ctx| self.parse_id(ctx)).await?;
|
||||
let v = stk.run(|stk| self.parse_id(stk)).await?;
|
||||
|
||||
// check for exclusive
|
||||
if self.eat_whitespace(t!(">")) {
|
||||
Bound::Excluded(id)
|
||||
Bound::Excluded(v)
|
||||
} else {
|
||||
Bound::Included(id)
|
||||
Bound::Included(v)
|
||||
}
|
||||
} else {
|
||||
Bound::Unbounded
|
||||
|
@ -76,19 +81,21 @@ impl Parser<'_> {
|
|||
// If we already ate the exclusive it must be a range.
|
||||
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)
|
||||
} 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)
|
||||
} else {
|
||||
Bound::Unbounded
|
||||
};
|
||||
Ok(Value::Range(Box::new(Range {
|
||||
Ok(Value::Thing(Thing {
|
||||
tb: ident,
|
||||
id: Id::Range(Box::new(IdRange {
|
||||
beg,
|
||||
end,
|
||||
})))
|
||||
})),
|
||||
}))
|
||||
} else {
|
||||
let id = match beg {
|
||||
Bound::Unbounded => {
|
||||
|
@ -110,7 +117,8 @@ impl Parser<'_> {
|
|||
// we have matched a bounded id but we don't see an 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 {
|
||||
tb: ident,
|
||||
|
@ -121,18 +129,14 @@ impl Parser<'_> {
|
|||
|
||||
/// Parse an 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
|
||||
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!(">")) {
|
||||
Bound::Excluded(id)
|
||||
Bound::Excluded(v)
|
||||
} else {
|
||||
Bound::Included(id)
|
||||
Bound::Included(v)
|
||||
}
|
||||
} else {
|
||||
Bound::Unbounded
|
||||
|
@ -144,18 +148,17 @@ impl Parser<'_> {
|
|||
|
||||
// parse ending id.
|
||||
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 {
|
||||
Bound::Included(id)
|
||||
Bound::Included(v)
|
||||
} else {
|
||||
Bound::Excluded(id)
|
||||
Bound::Excluded(v)
|
||||
}
|
||||
} else {
|
||||
Bound::Unbounded
|
||||
};
|
||||
|
||||
Ok(Range {
|
||||
tb,
|
||||
beg,
|
||||
end,
|
||||
})
|
||||
|
@ -498,7 +501,7 @@ mod tests {
|
|||
out,
|
||||
Thing {
|
||||
tb: String::from("test"),
|
||||
id: Id::Object(Object::from(map! {
|
||||
id: Id::from(Object::from(map! {
|
||||
"location".to_string() => Value::from("GBR"),
|
||||
"year".to_string() => Value::from(2022),
|
||||
})),
|
||||
|
@ -516,7 +519,7 @@ mod tests {
|
|||
out,
|
||||
Thing {
|
||||
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()
|
||||
.unwrap_or_else(|_| panic!("failed on {}", ident))
|
||||
.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 r = stack
|
||||
|
@ -548,7 +551,7 @@ mod tests {
|
|||
sql::Query(sql::Statements(vec![sql::Statement::Value(sql::Value::Thing(
|
||||
sql::Thing {
|
||||
tb: "t".to_string(),
|
||||
id: Id::String(ident.to_string())
|
||||
id: Id::from(ident.to_string())
|
||||
}
|
||||
))]))
|
||||
)
|
||||
|
|
|
@ -142,6 +142,7 @@ keyword! {
|
|||
PostingsOrder => "POSTINGS_ORDER",
|
||||
Prune => "PRUNE",
|
||||
Punct => "PUNCT",
|
||||
Range => "RANGE",
|
||||
Readonly => "READONLY",
|
||||
Rebuild => "REBUILD",
|
||||
Relate => "RELATE",
|
||||
|
|
|
@ -71,7 +71,7 @@ fn bench_hash_trie_btree_thing(c: &mut Criterion) {
|
|||
const N: usize = 50_000;
|
||||
let mut samples = Vec::with_capacity(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));
|
||||
}
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ fn new_vectors_from_file(path: &str) -> Vec<(Thing, Array)> {
|
|||
let line = line_result.unwrap();
|
||||
let value = value(&line).unwrap();
|
||||
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 {
|
||||
panic!("Wrong value");
|
||||
}
|
||||
|
|
|
@ -553,7 +553,7 @@ async fn router(
|
|||
data,
|
||||
} => {
|
||||
let mut query = Query::default();
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
let statement = {
|
||||
let mut stmt = UpsertStatement::default();
|
||||
stmt.what = value_to_values(what);
|
||||
|
@ -571,7 +571,7 @@ async fn router(
|
|||
data,
|
||||
} => {
|
||||
let mut query = Query::default();
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
let statement = {
|
||||
let mut stmt = UpdateStatement::default();
|
||||
stmt.what = value_to_values(what);
|
||||
|
@ -607,7 +607,7 @@ async fn router(
|
|||
data,
|
||||
} => {
|
||||
let mut query = Query::default();
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
let statement = {
|
||||
let mut stmt = UpdateStatement::default();
|
||||
stmt.what = value_to_values(what);
|
||||
|
@ -625,7 +625,7 @@ async fn router(
|
|||
data,
|
||||
} => {
|
||||
let mut query = Query::default();
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
let statement = {
|
||||
let mut stmt = UpdateStatement::default();
|
||||
stmt.what = value_to_values(what);
|
||||
|
@ -642,7 +642,7 @@ async fn router(
|
|||
what,
|
||||
} => {
|
||||
let mut query = Query::default();
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
let statement = {
|
||||
let mut stmt = SelectStatement::default();
|
||||
stmt.what = value_to_values(what);
|
||||
|
@ -658,7 +658,7 @@ async fn router(
|
|||
what,
|
||||
} => {
|
||||
let mut query = Query::default();
|
||||
let one = what.is_thing();
|
||||
let one = what.is_thing_single();
|
||||
let statement = {
|
||||
let mut stmt = DeleteStatement::default();
|
||||
stmt.what = value_to_values(what);
|
||||
|
|
|
@ -14,13 +14,10 @@ use crate::method::Select;
|
|||
use crate::opt::Resource;
|
||||
use crate::sql::from_value;
|
||||
use crate::sql::statements::LiveStatement;
|
||||
use crate::sql::Cond;
|
||||
use crate::sql::Expression;
|
||||
use crate::sql::Field;
|
||||
use crate::sql::Fields;
|
||||
use crate::sql::Ident;
|
||||
use crate::sql::Idiom;
|
||||
use crate::sql::Operator;
|
||||
use crate::sql::Part;
|
||||
use crate::sql::Statement;
|
||||
use crate::sql::Table;
|
||||
|
@ -63,10 +60,10 @@ macro_rules! into_future {
|
|||
let mut table = Table::default();
|
||||
match range {
|
||||
Some(range) => {
|
||||
let range = resource?.with_range(range)?;
|
||||
table.0 = range.tb.clone();
|
||||
let record = resource?.with_range(range)?;
|
||||
table.0 = record.tb.clone();
|
||||
stmt.what = table.into();
|
||||
stmt.cond = range.to_cond();
|
||||
stmt.cond = record.to_cond();
|
||||
}
|
||||
None => match resource? {
|
||||
Resource::Table(table) => {
|
||||
|
@ -79,13 +76,7 @@ macro_rules! into_future {
|
|||
ident.0 = ID.to_owned();
|
||||
let mut idiom = Idiom::default();
|
||||
idiom.0 = vec![Part::from(ident)];
|
||||
let mut cond = Cond::default();
|
||||
cond.0 = Value::Expression(Box::new(Expression::new(
|
||||
idiom.into(),
|
||||
Operator::Equal,
|
||||
record.into(),
|
||||
)));
|
||||
stmt.cond = Some(cond);
|
||||
stmt.cond = record.to_cond();
|
||||
}
|
||||
Resource::Object(object) => return Err(Error::LiveOnObject(object).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::Response as QueryResponse;
|
||||
use crate::sql::to_value;
|
||||
use crate::sql::Id;
|
||||
use crate::sql::Thing;
|
||||
use crate::sql::Value;
|
||||
use channel::Receiver;
|
||||
|
||||
|
@ -61,10 +63,13 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
|
|||
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::Table(..) | Value::Array(..) | Value::Range(..) => {
|
||||
Ok(DbResponse::Other(Value::Array(Default::default())))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Command::Upsert {
|
||||
|
@ -83,10 +88,13 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
|
|||
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::Table(..) | Value::Array(..) | Value::Range(..) => {
|
||||
Ok(DbResponse::Other(Value::Array(Default::default())))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Command::Insert {
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::api::opt::Resource;
|
|||
use crate::api::Connection;
|
||||
use crate::api::Result;
|
||||
use crate::method::OnceLockExt;
|
||||
use crate::sql::to_value;
|
||||
use crate::sql::Id;
|
||||
use crate::sql::Value;
|
||||
use crate::Surreal;
|
||||
|
@ -17,7 +18,6 @@ use serde::Serialize;
|
|||
use std::borrow::Cow;
|
||||
use std::future::IntoFuture;
|
||||
use std::marker::PhantomData;
|
||||
use surrealdb_core::sql::to_value;
|
||||
|
||||
/// An update future
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::api::opt::Resource;
|
|||
use crate::api::Connection;
|
||||
use crate::api::Result;
|
||||
use crate::method::OnceLockExt;
|
||||
use crate::sql::to_value;
|
||||
use crate::sql::Id;
|
||||
use crate::sql::Value;
|
||||
use crate::Surreal;
|
||||
|
@ -17,7 +18,6 @@ use serde::Serialize;
|
|||
use std::borrow::Cow;
|
||||
use std::future::IntoFuture;
|
||||
use std::marker::PhantomData;
|
||||
use surrealdb_core::sql::to_value;
|
||||
|
||||
/// An upsert future
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -19,9 +19,12 @@ pub enum 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 {
|
||||
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::Object(object) => Err(Error::RangeOnObject(object).into()),
|
||||
Resource::Array(array) => Err(Error::RangeOnArray(array).into()),
|
||||
|
|
|
@ -62,3 +62,35 @@ async fn cast_to_record_table() -> Result<(), Error> {
|
|||
//
|
||||
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;
|
||||
|
||||
#[tokio::test]
|
||||
async fn modulo() -> Result<(), Error> {
|
||||
async fn expr_modulo() -> Result<(), Error> {
|
||||
Test::new("8 % 3").await?.expect_val("2")?;
|
||||
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(())
|
||||
}
|
||||
|
||||
#[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]
|
||||
async fn function_type_range() -> Result<(), Error> {
|
||||
let sql = r#"
|
||||
RETURN type::range('person');
|
||||
RETURN type::range('person',1);
|
||||
RETURN type::range('person',null,10);
|
||||
RETURN type::range('person',1,10);
|
||||
RETURN type::range('person',1,10, { begin: "excluded", end: "included"});
|
||||
RETURN type::range(..);
|
||||
RETURN type::range(1..2);
|
||||
RETURN type::range([1, 2]);
|
||||
"#;
|
||||
let mut test = Test::new(sql).await?;
|
||||
//
|
||||
let tmp = test.next()?.result?;
|
||||
let val = Value::parse("person:..");
|
||||
let val = Value::parse("..");
|
||||
assert_eq!(tmp, val);
|
||||
|
||||
//
|
||||
let tmp = test.next()?.result?;
|
||||
let val = Value::parse("person:1..");
|
||||
let val = Value::parse("1..2");
|
||||
assert_eq!(tmp, val);
|
||||
|
||||
//
|
||||
let tmp = test.next()?.result?;
|
||||
let val = Value::parse("person:..10");
|
||||
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");
|
||||
let val = Value::parse("1..2");
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue