Range values (#4506)

This commit is contained in:
Micha de Vries 2024-08-20 12:42:06 +01:00 committed by GitHub
parent b1e9af5d4e
commit e77df62114
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 1382 additions and 569 deletions

View file

@ -186,7 +186,7 @@ mod tests {
let mut tx1 = ds.transaction(Write, Optimistic).await.unwrap().inner(); let mut tx1 = ds.transaction(Write, Optimistic).await.unwrap().inner();
let thing_a = Thing { let thing_a = Thing {
tb: TB.to_owned(), tb: TB.to_owned(),
id: Id::String("A".to_string()), id: Id::from("A"),
}; };
let value_a: Value = "a".into(); let value_a: Value = "a".into();
let previous = Value::None; let previous = Value::None;
@ -205,7 +205,7 @@ mod tests {
let mut tx2 = ds.transaction(Write, Optimistic).await.unwrap().inner(); let mut tx2 = ds.transaction(Write, Optimistic).await.unwrap().inner();
let thing_c = Thing { let thing_c = Thing {
tb: TB.to_owned(), tb: TB.to_owned(),
id: Id::String("C".to_string()), id: Id::from("C"),
}; };
let value_c: Value = "c".into(); let value_c: Value = "c".into();
tx2.record_change( tx2.record_change(
@ -223,7 +223,7 @@ mod tests {
let mut tx3 = ds.transaction(Write, Optimistic).await.unwrap().inner(); let mut tx3 = ds.transaction(Write, Optimistic).await.unwrap().inner();
let thing_b = Thing { let thing_b = Thing {
tb: TB.to_owned(), tb: TB.to_owned(),
id: Id::String("B".to_string()), id: Id::from("B"),
}; };
let value_b: Value = "b".into(); let value_b: Value = "b".into();
tx3.record_change( tx3.record_change(
@ -237,7 +237,7 @@ mod tests {
); );
let thing_c2 = Thing { let thing_c2 = Thing {
tb: TB.to_owned(), tb: TB.to_owned(),
id: Id::String("C".to_string()), id: Id::from("C"),
}; };
let value_c2: Value = "c2".into(); let value_c2: Value = "c2".into();
tx3.record_change( tx3.record_change(
@ -434,7 +434,7 @@ mod tests {
ds.tick_at(ts.0.timestamp().try_into().unwrap()).await.unwrap(); ds.tick_at(ts.0.timestamp().try_into().unwrap()).await.unwrap();
let thing = Thing { let thing = Thing {
tb: TB.to_owned(), tb: TB.to_owned(),
id: Id::String("A".to_string()), id: Id::from("A"),
}; };
let ses = Session::owner().with_ns(NS).with_db(DB); let ses = Session::owner().with_ns(NS).with_db(DB);
let res = let res =
@ -531,7 +531,7 @@ mod tests {
async fn record_change_feed_entry(tx: Transaction, id: String) -> Thing { async fn record_change_feed_entry(tx: Transaction, id: String) -> Thing {
let thing = Thing { let thing = Thing {
tb: TB.to_owned(), tb: TB.to_owned(),
id: Id::String(id), id: Id::from(id),
}; };
let value_a: Value = "a".into(); let value_a: Value = "a".into();
let previous = Value::None.into(); let previous = Value::None.into();

View file

@ -68,8 +68,8 @@ pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> =
pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> = Lazy::new(|| true); pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> = Lazy::new(|| true);
/// Used to limit allocation for builtin functions /// Used to limit allocation for builtin functions
pub static FUNCTION_ALLOCATION_LIMIT: Lazy<usize> = once_cell::sync::Lazy::new(|| { pub static GENERATION_ALLOCATION_LIMIT: Lazy<usize> = once_cell::sync::Lazy::new(|| {
let n = std::env::var("SURREAL_FUNCTION_ALLOCATION_LIMIT") let n = std::env::var("SURREAL_GENERATION_ALLOCATION_LIMIT")
.map(|s| s.parse::<u32>().unwrap_or(20)) .map(|s| s.parse::<u32>().unwrap_or(20))
.unwrap_or(20); .unwrap_or(20);
2usize.pow(n) 2usize.pow(n)

View file

@ -12,10 +12,10 @@ use crate::err::Error;
use crate::idx::planner::iterators::{IteratorRecord, IteratorRef}; use crate::idx::planner::iterators::{IteratorRecord, IteratorRef};
use crate::idx::planner::IterationStage; use crate::idx::planner::IterationStage;
use crate::sql::edges::Edges; use crate::sql::edges::Edges;
use crate::sql::range::Range;
use crate::sql::table::Table; use crate::sql::table::Table;
use crate::sql::thing::Thing; use crate::sql::thing::Thing;
use crate::sql::value::Value; use crate::sql::value::Value;
use crate::sql::{Id, IdRange};
use reblessive::tree::Stk; use reblessive::tree::Stk;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use reblessive::TreeStack; use reblessive::TreeStack;
@ -27,7 +27,7 @@ pub(crate) enum Iterable {
Value(Value), Value(Value),
Table(Table), Table(Table),
Thing(Thing), Thing(Thing),
Range(Range), TableRange(String, IdRange),
Edges(Edges), Edges(Edges),
Defer(Thing), Defer(Thing),
Mergeable(Thing, Value), Mergeable(Thing, Value),
@ -152,6 +152,20 @@ impl Iterator {
} }
} }
// Add the record to the iterator // Add the record to the iterator
match &v.id {
Id::Range(r) => {
match stm {
Statement::Create(_) => {
return Err(Error::InvalidStatementTarget {
value: v.to_string(),
});
}
_ => {
self.ingest(Iterable::TableRange(v.tb, *r.to_owned()));
}
};
}
_ => {
match stm { match stm {
Statement::Create(_) => { Statement::Create(_) => {
self.ingest(Iterable::Defer(v)); self.ingest(Iterable::Defer(v));
@ -161,6 +175,8 @@ impl Iterator {
} }
}; };
} }
}
}
Value::Mock(v) => { Value::Mock(v) => {
// Check if there is a data clause // Check if there is a data clause
if let Some(data) = stm.data() { if let Some(data) = stm.data() {
@ -176,25 +192,6 @@ impl Iterator {
self.ingest(Iterable::Thing(v)) self.ingest(Iterable::Thing(v))
} }
} }
Value::Range(v) => {
// Check if this is a create statement
if let Statement::Create(_) = stm {
return Err(Error::InvalidStatementTarget {
value: v.to_string(),
});
}
// Check if there is a data clause
if let Some(data) = stm.data() {
// Check if there is an id field specified
if let Some(id) = data.rid(stk, ctx, opt).await? {
return Err(Error::IdMismatch {
value: id.to_string(),
});
}
}
// Add the record to the iterator
self.ingest(Iterable::Range(*v));
}
Value::Edges(v) => { Value::Edges(v) => {
// Check if this is a create statement // Check if this is a create statement
if let Statement::Create(_) = stm { if let Statement::Create(_) = stm {

View file

@ -105,9 +105,9 @@ impl ExplainItem {
name: "Iterate Defer".into(), name: "Iterate Defer".into(),
details: vec![("thing", Value::Thing(t.to_owned()))], details: vec![("thing", Value::Thing(t.to_owned()))],
}, },
Iterable::Range(r) => Self { Iterable::TableRange(tb, r) => Self {
name: "Iterate Range".into(), name: "Iterate Range".into(),
details: vec![("table", Value::from(r.tb.to_owned()))], details: vec![("table", tb.to_owned().into()), ("range", r.to_owned().into())],
}, },
Iterable::Edges(e) => Self { Iterable::Edges(e) => Self {
name: "Iterate Edges".into(), name: "Iterate Edges".into(),

View file

@ -10,7 +10,8 @@ use crate::idx::planner::IterationStage;
use crate::key::{graph, thing}; use crate::key::{graph, thing};
use crate::kvs::Transaction; use crate::kvs::Transaction;
use crate::sql::dir::Dir; use crate::sql::dir::Dir;
use crate::sql::{Edges, Range, Table, Thing, Value}; use crate::sql::id::range::IdRange;
use crate::sql::{Edges, Table, Thing, Value};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use channel::Sender; use channel::Sender;
use futures::StreamExt; use futures::StreamExt;
@ -123,7 +124,9 @@ impl<'a> Processor<'a> {
Iterable::Value(v) => self.process_value(stk, ctx, opt, stm, v).await?, Iterable::Value(v) => self.process_value(stk, ctx, opt, stm, v).await?,
Iterable::Thing(v) => self.process_thing(stk, ctx, opt, stm, v).await?, Iterable::Thing(v) => self.process_thing(stk, ctx, opt, stm, v).await?,
Iterable::Defer(v) => self.process_defer(stk, ctx, opt, stm, v).await?, Iterable::Defer(v) => self.process_defer(stk, ctx, opt, stm, v).await?,
Iterable::Range(v) => self.process_range(stk, ctx, opt, stm, v).await?, Iterable::TableRange(tb, v) => {
self.process_range(stk, ctx, opt, stm, tb, v).await?
}
Iterable::Edges(e) => self.process_edge(stk, ctx, opt, stm, e).await?, Iterable::Edges(e) => self.process_edge(stk, ctx, opt, stm, e).await?,
Iterable::Table(v) => { Iterable::Table(v) => {
if let Some(qp) = ctx.get_query_planner() { if let Some(qp) = ctx.get_query_planner() {
@ -343,28 +346,29 @@ impl<'a> Processor<'a> {
ctx: &Context, ctx: &Context,
opt: &Options, opt: &Options,
stm: &Statement<'_>, stm: &Statement<'_>,
v: Range, tb: String,
r: IdRange,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Get the transaction // Get the transaction
let txn = ctx.tx(); let txn = ctx.tx();
// Check that the table exists // Check that the table exists
txn.check_ns_db_tb(opt.ns()?, opt.db()?, &v.tb, opt.strict).await?; txn.check_ns_db_tb(opt.ns()?, opt.db()?, &tb, opt.strict).await?;
// Prepare the range start key // Prepare the range start key
let beg = match &v.beg { let beg = match &r.beg {
Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, &v.tb), Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, &tb),
Bound::Included(id) => thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(), Bound::Included(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(),
Bound::Excluded(id) => { Bound::Excluded(v) => {
let mut key = thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(); let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap();
key.push(0x00); key.push(0x00);
key key
} }
}; };
// Prepare the range end key // Prepare the range end key
let end = match &v.end { let end = match &r.end {
Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, &v.tb), Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, &tb),
Bound::Excluded(id) => thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(), Bound::Excluded(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(),
Bound::Included(id) => { Bound::Included(v) => {
let mut key = thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(); let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap();
key.push(0x00); key.push(0x00);
key key
} }

View file

@ -1081,6 +1081,19 @@ pub enum Error {
#[doc(hidden)] #[doc(hidden)]
#[error("The underlying datastore does not support versioned queries")] #[error("The underlying datastore does not support versioned queries")]
UnsupportedVersionedQueries, UnsupportedVersionedQueries,
/// Found an unexpected value in a range
#[error("Expected a range value of '{expected}', but found '{found}'")]
InvalidRangeValue {
expected: String,
found: String,
},
/// Found an unexpected value in a range
#[error("The range cannot exceed a size of {max} for this operation")]
RangeTooBig {
max: usize,
},
} }
impl From<Error> for String { impl From<Error> for String {

View file

@ -1,4 +1,4 @@
use crate::cnf::FUNCTION_ALLOCATION_LIMIT; use crate::cnf::GENERATION_ALLOCATION_LIMIT;
use crate::ctx::Context; use crate::ctx::Context;
use crate::dbs::Options; use crate::dbs::Options;
use crate::doc::CursorDoc; use crate::doc::CursorDoc;
@ -25,10 +25,10 @@ use std::mem::size_of_val;
/// Returns an error if an array of this length is too much to allocate. /// Returns an error if an array of this length is too much to allocate.
fn limit(name: &str, n: usize) -> Result<(), Error> { fn limit(name: &str, n: usize) -> Result<(), Error> {
if n > *FUNCTION_ALLOCATION_LIMIT { if n > *GENERATION_ALLOCATION_LIMIT {
Err(Error::InvalidArguments { Err(Error::InvalidArguments {
name: name.to_owned(), name: name.to_owned(),
message: format!("Output must not exceed {} bytes.", *FUNCTION_ALLOCATION_LIMIT), message: format!("Output must not exceed {} bytes.", *GENERATION_ALLOCATION_LIMIT),
}) })
} else { } else {
Ok(()) Ok(())

View file

@ -352,6 +352,7 @@ pub fn synchronous(
"time::from::secs" => time::from::secs, "time::from::secs" => time::from::secs,
"time::from::unix" => time::from::unix, "time::from::unix" => time::from::unix,
// //
"type::array" => r#type::array,
"type::bool" => r#type::bool, "type::bool" => r#type::bool,
"type::bytes" => r#type::bytes, "type::bytes" => r#type::bytes,
"type::datetime" => r#type::datetime, "type::datetime" => r#type::datetime,
@ -687,6 +688,7 @@ pub async fn idiom(
"is_string" => r#type::is::string, "is_string" => r#type::is::string,
"is_uuid" => r#type::is::uuid, "is_uuid" => r#type::is::uuid,
// //
"to_array" => r#type::array,
"to_bool" => r#type::bool, "to_bool" => r#type::bool,
"to_bytes" => r#type::bytes, "to_bytes" => r#type::bytes,
"to_datetime" => r#type::datetime, "to_datetime" => r#type::datetime,
@ -697,6 +699,7 @@ pub async fn idiom(
"to_int" => r#type::int, "to_int" => r#type::int,
"to_number" => r#type::number, "to_number" => r#type::number,
"to_point" => r#type::point, "to_point" => r#type::point,
"to_range" => r#type::range,
"to_record" => r#type::record, "to_record" => r#type::record,
"to_string" => r#type::string, "to_string" => r#type::string,
"to_uuid" => r#type::uuid, "to_uuid" => r#type::uuid,

View file

@ -11,6 +11,7 @@ pub struct Package;
impl_module_def!( impl_module_def!(
Package, Package,
"type", "type",
"array" => run,
"bool" => run, "bool" => run,
"bytes" => run, "bytes" => run,
"datetime" => run, "datetime" => run,

View file

@ -1,4 +1,4 @@
use crate::cnf::FUNCTION_ALLOCATION_LIMIT; use crate::cnf::GENERATION_ALLOCATION_LIMIT;
use crate::err::Error; use crate::err::Error;
use crate::fnc::util::string; use crate::fnc::util::string;
use crate::sql::value::Value; use crate::sql::value::Value;
@ -6,10 +6,10 @@ use crate::sql::Regex;
/// Returns `true` if a string of this length is too much to allocate. /// Returns `true` if a string of this length is too much to allocate.
fn limit(name: &str, n: usize) -> Result<(), Error> { fn limit(name: &str, n: usize) -> Result<(), Error> {
if n > *FUNCTION_ALLOCATION_LIMIT { if n > *GENERATION_ALLOCATION_LIMIT {
Err(Error::InvalidArguments { Err(Error::InvalidArguments {
name: name.to_owned(), name: name.to_owned(),
message: format!("Output must not exceed {} bytes.", *FUNCTION_ALLOCATION_LIMIT), message: format!("Output must not exceed {} bytes.", *GENERATION_ALLOCATION_LIMIT),
}) })
} else { } else {
Ok(()) Ok(())

View file

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

View file

@ -1,4 +1,5 @@
use crate::idx::planner::executor::KnnExpressions; use crate::idx::planner::executor::KnnExpressions;
use crate::sql::id::range::IdRange;
use crate::sql::part::DestructurePart; use crate::sql::part::DestructurePart;
use crate::sql::{ use crate::sql::{
Array, Cast, Cond, Expression, Function, Id, Idiom, Model, Object, Part, Range, Thing, Value, Array, Cast, Cond, Expression, Function, Id, Idiom, Model, Object, Part, Range, Thing, Value,
@ -127,6 +128,7 @@ impl<'a> KnnConditionRewriter<'a> {
Id::Number(_) | Id::String(_) | Id::Generate(_) => Some(id.clone()), Id::Number(_) | Id::String(_) | Id::Generate(_) => Some(id.clone()),
Id::Array(a) => self.eval_array(a).map(Id::Array), Id::Array(a) => self.eval_array(a).map(Id::Array),
Id::Object(o) => self.eval_object(o).map(Id::Object), Id::Object(o) => self.eval_object(o).map(Id::Object),
Id::Range(r) => self.eval_id_range(r).map(|v| Id::Range(Box::new(v))),
} }
} }
@ -178,7 +180,6 @@ impl<'a> KnnConditionRewriter<'a> {
fn eval_range(&self, r: &Range) -> Option<Range> { fn eval_range(&self, r: &Range) -> Option<Range> {
if let Some(beg) = self.eval_bound(&r.beg) { if let Some(beg) = self.eval_bound(&r.beg) {
self.eval_bound(&r.end).map(|end| Range { self.eval_bound(&r.end).map(|end| Range {
tb: r.tb.clone(),
beg, beg,
end, end,
}) })
@ -187,10 +188,29 @@ impl<'a> KnnConditionRewriter<'a> {
} }
} }
fn eval_bound(&self, b: &Bound<Id>) -> Option<Bound<Id>> { fn eval_bound(&self, b: &Bound<Value>) -> Option<Bound<Value>> {
match b { match b {
Bound::Included(id) => self.eval_id(id).map(Bound::Included), Bound::Included(v) => self.eval_value(v).map(Bound::Included),
Bound::Excluded(id) => self.eval_id(id).map(Bound::Excluded), Bound::Excluded(v) => self.eval_value(v).map(Bound::Excluded),
Bound::Unbounded => Some(Bound::Unbounded),
}
}
fn eval_id_range(&self, r: &IdRange) -> Option<IdRange> {
if let Some(beg) = self.eval_id_bound(&r.beg) {
self.eval_id_bound(&r.end).map(|end| IdRange {
beg,
end,
})
} else {
None
}
}
fn eval_id_bound(&self, b: &Bound<Id>) -> Option<Bound<Id>> {
match b {
Bound::Included(v) => self.eval_id(v).map(Bound::Included),
Bound::Excluded(v) => self.eval_id(v).map(Bound::Excluded),
Bound::Unbounded => Some(Bound::Unbounded), Bound::Unbounded => Some(Bound::Unbounded),
} }
} }

View file

@ -1,7 +1,7 @@
//! Stores a record document //! Stores a record document
use crate::key::category::Categorise; use crate::key::category::Categorise;
use crate::key::category::Category; use crate::key::category::Category;
use crate::sql::id::Id; use crate::sql::Id;
use derive::Key; use derive::Key;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View file

@ -2,14 +2,20 @@ use ciborium::Value as Data;
use geo::{LineString, Point, Polygon}; use geo::{LineString, Point, Polygon};
use geo_types::{MultiLineString, MultiPoint, MultiPolygon}; use geo_types::{MultiLineString, MultiPoint, MultiPolygon};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use std::collections::BTreeMap;
use std::iter::once; use std::iter::once;
use std::ops::Bound;
use std::ops::Deref; use std::ops::Deref;
use crate::sql::id::range::IdRange;
use crate::sql::Array;
use crate::sql::Datetime; use crate::sql::Datetime;
use crate::sql::Duration; use crate::sql::Duration;
use crate::sql::Geometry; use crate::sql::Geometry;
use crate::sql::Id; use crate::sql::Id;
use crate::sql::Number; use crate::sql::Number;
use crate::sql::Object;
use crate::sql::Range;
use crate::sql::Thing; use crate::sql::Thing;
use crate::sql::Uuid; use crate::sql::Uuid;
use crate::sql::Value; use crate::sql::Value;
@ -30,6 +36,11 @@ const TAG_CUSTOM_DATETIME: u64 = 12;
const TAG_STRING_DURATION: u64 = 13; const TAG_STRING_DURATION: u64 = 13;
const TAG_CUSTOM_DURATION: u64 = 14; const TAG_CUSTOM_DURATION: u64 = 14;
// Ranges
const TAG_RANGE: u64 = 49;
const TAG_BOUND_INCLUDED: u64 = 50;
const TAG_BOUND_EXCLUDED: u64 = 51;
// Custom Geometries // Custom Geometries
const TAG_GEOMETRY_POINT: u64 = 88; const TAG_GEOMETRY_POINT: u64 = 88;
const TAG_GEOMETRY_LINE: u64 = 89; const TAG_GEOMETRY_LINE: u64 = 89;
@ -52,17 +63,8 @@ impl TryFrom<Cbor> for Value {
Data::Float(v) => Ok(Value::from(v)), Data::Float(v) => Ok(Value::from(v)),
Data::Bytes(v) => Ok(Value::Bytes(v.into())), Data::Bytes(v) => Ok(Value::Bytes(v.into())),
Data::Text(v) => Ok(Value::from(v)), Data::Text(v) => Ok(Value::from(v)),
Data::Array(v) => { Data::Array(v) => Ok(Value::Array(Array::try_from(v)?)),
v.into_iter().map(|v| Value::try_from(Cbor(v))).collect::<Result<Value, &str>>() Data::Map(v) => Ok(Value::Object(Object::try_from(v)?)),
}
Data::Map(v) => v
.into_iter()
.map(|(k, v)| {
let k = Value::try_from(Cbor(k)).map(|k| k.as_raw_string());
let v = Value::try_from(Cbor(v));
Ok((k?, v?))
})
.collect::<Result<Value, &str>>(),
Data::Tag(t, v) => { Data::Tag(t, v) => {
match t { match t {
// A literal datetime // A literal datetime
@ -175,21 +177,12 @@ impl TryFrom<Cbor> for Value {
), ),
}; };
match Value::try_from(Cbor(v.remove(0))) { let id = Id::try_from(v.remove(0))?;
Ok(Value::Strand(id)) => {
Ok(Value::from(Thing::from((tb, Id::from(id))))) Ok(Value::Thing(Thing {
} tb,
Ok(Value::Number(Number::Int(id))) => { id,
Ok(Value::from(Thing::from((tb, Id::from(id))))) }))
}
Ok(Value::Array(id)) => {
Ok(Value::from(Thing::from((tb, Id::from(id)))))
}
Ok(Value::Object(id)) => {
Ok(Value::from(Thing::from((tb, Id::from(id)))))
}
_ => Err("Expected the id of a Record Id to be a String, Integer, Array or Object value"),
}
} }
_ => Err("Expected a CBOR text data type, or a CBOR array with 2 elements"), _ => Err("Expected a CBOR text data type, or a CBOR array with 2 elements"),
}, },
@ -198,6 +191,8 @@ impl TryFrom<Cbor> for Value {
Data::Text(v) => Ok(Value::Table(v.into())), Data::Text(v) => Ok(Value::Table(v.into())),
_ => Err("Expected a CBOR text data type"), _ => Err("Expected a CBOR text data type"),
}, },
// A range
TAG_RANGE => Ok(Value::Range(Box::new(Range::try_from(*v)?))),
TAG_GEOMETRY_POINT => match *v { TAG_GEOMETRY_POINT => match *v {
Data::Array(mut v) if v.len() == 2 => { Data::Array(mut v) if v.len() == 2 => {
let x = Value::try_from(Cbor(v.remove(0)))?; let x = Value::try_from(Cbor(v.remove(0)))?;
@ -393,11 +388,13 @@ impl TryFrom<Value> for Cbor {
Id::Generate(_) => { Id::Generate(_) => {
return Err("Cannot encode an ungenerated Record ID into CBOR") return Err("Cannot encode an ungenerated Record ID into CBOR")
} }
Id::Range(v) => Data::try_from(*v)?,
}, },
])), ])),
))), ))),
Value::Table(v) => Ok(Cbor(Data::Tag(TAG_TABLE, Box::new(Data::Text(v.0))))), Value::Table(v) => Ok(Cbor(Data::Tag(TAG_TABLE, Box::new(Data::Text(v.0))))),
Value::Geometry(v) => Ok(Cbor(encode_geometry(v)?)), Value::Geometry(v) => Ok(Cbor(encode_geometry(v)?)),
Value::Range(v) => Ok(Cbor(Data::try_from(*v)?)),
// We shouldn't reach here // We shouldn't reach here
_ => Err("Found unsupported SurrealQL value being encoded into a CBOR value"), _ => Err("Found unsupported SurrealQL value being encoded into a CBOR value"),
} }
@ -460,3 +457,136 @@ fn encode_geometry(v: Geometry) -> Result<Data, &'static str> {
} }
} }
} }
impl TryFrom<Data> for Range {
type Error = &'static str;
fn try_from(val: Data) -> Result<Self, &'static str> {
fn decode_bound(v: Data) -> Result<Bound<Value>, &'static str> {
match v {
Data::Tag(TAG_BOUND_INCLUDED, v) => Ok(Bound::Included(Value::try_from(Cbor(*v))?)),
Data::Tag(TAG_BOUND_EXCLUDED, v) => Ok(Bound::Excluded(Value::try_from(Cbor(*v))?)),
Data::Null => Ok(Bound::Unbounded),
_ => Err("Expected a bound tag"),
}
}
match val {
Data::Array(v) if v.len() == 2 => {
let mut v = v;
let beg = decode_bound(v.remove(0).to_owned())?;
let end = decode_bound(v.remove(0).to_owned())?;
Ok(Range::new(beg, end))
}
_ => Err("Expected a CBOR array with 2 bounds"),
}
}
}
impl TryFrom<Range> for Data {
type Error = &'static str;
fn try_from(r: Range) -> Result<Data, &'static str> {
fn encode(b: Bound<Value>) -> Result<Data, &'static str> {
match b {
Bound::Included(v) => {
Ok(Data::Tag(TAG_BOUND_INCLUDED, Box::new(Cbor::try_from(v)?.0)))
}
Bound::Excluded(v) => {
Ok(Data::Tag(TAG_BOUND_EXCLUDED, Box::new(Cbor::try_from(v)?.0)))
}
Bound::Unbounded => Ok(Data::Null),
}
}
Ok(Data::Array(vec![encode(r.beg)?, encode(r.end)?]))
}
}
impl TryFrom<Data> for IdRange {
type Error = &'static str;
fn try_from(val: Data) -> Result<Self, &'static str> {
fn decode_bound(v: Data) -> Result<Bound<Id>, &'static str> {
match v {
Data::Tag(TAG_BOUND_INCLUDED, v) => Ok(Bound::Included(Id::try_from(*v)?)),
Data::Tag(TAG_BOUND_EXCLUDED, v) => Ok(Bound::Excluded(Id::try_from(*v)?)),
Data::Null => Ok(Bound::Unbounded),
_ => Err("Expected a bound tag"),
}
}
match val {
Data::Array(v) if v.len() == 2 => {
let mut v = v;
let beg = decode_bound(v.remove(0).to_owned())?;
let end = decode_bound(v.remove(0).to_owned())?;
Ok(IdRange::try_from((beg, end))
.map_err(|_| "Found an invalid range with ranges as bounds")?)
}
_ => Err("Expected a CBOR array with 2 bounds"),
}
}
}
impl TryFrom<IdRange> for Data {
type Error = &'static str;
fn try_from(r: IdRange) -> Result<Data, &'static str> {
fn encode(b: Bound<Id>) -> Result<Data, &'static str> {
match b {
Bound::Included(v) => Ok(Data::Tag(TAG_BOUND_INCLUDED, Box::new(v.try_into()?))),
Bound::Excluded(v) => Ok(Data::Tag(TAG_BOUND_EXCLUDED, Box::new(v.try_into()?))),
Bound::Unbounded => Ok(Data::Null),
}
}
Ok(Data::Array(vec![encode(r.beg)?, encode(r.end)?]))
}
}
impl TryFrom<Data> for Id {
type Error = &'static str;
fn try_from(val: Data) -> Result<Self, &'static str> {
match val {
Data::Integer(v) => Ok(Id::Number(i128::from(v) as i64)),
Data::Text(v) => Ok(Id::String(v)),
Data::Array(v) => Ok(Id::Array(v.try_into()?)),
Data::Map(v) => Ok(Id::Object(v.try_into()?)),
Data::Tag(TAG_RANGE, v) => Ok(Id::Range(Box::new(IdRange::try_from(*v)?))),
_ => Err("Expected a CBOR integer, text, array or map"),
}
}
}
impl TryFrom<Id> for Data {
type Error = &'static str;
fn try_from(v: Id) -> Result<Data, &'static str> {
match v {
Id::Number(v) => Ok(Data::Integer(v.into())),
Id::String(v) => Ok(Data::Text(v)),
Id::Array(v) => Ok(Cbor::try_from(Value::from(v))?.0),
Id::Object(v) => Ok(Cbor::try_from(Value::from(v))?.0),
Id::Range(v) => Ok(Data::Tag(TAG_RANGE, Box::new(v.deref().to_owned().try_into()?))),
Id::Generate(_) => Err("Cannot encode an ungenerated Record ID into CBOR"),
}
}
}
impl TryFrom<Vec<Data>> for Array {
type Error = &'static str;
fn try_from(val: Vec<Data>) -> Result<Self, &'static str> {
val.into_iter().map(|v| Value::try_from(Cbor(v))).collect::<Result<Array, &str>>()
}
}
impl TryFrom<Vec<(Data, Data)>> for Object {
type Error = &'static str;
fn try_from(val: Vec<(Data, Data)>) -> Result<Self, &'static str> {
Ok(Object(
val.into_iter()
.map(|(k, v)| {
let k = Value::try_from(Cbor(k)).map(|k| k.as_raw_string());
let v = Value::try_from(Cbor(v));
Ok((k?, v?))
})
.collect::<Result<BTreeMap<String, Value>, &str>>()?,
))
}
}

View file

@ -267,7 +267,7 @@ pub trait RpcContext {
return Err(RpcError::InvalidParams); return Err(RpcError::InvalidParams);
}; };
// Return a single result? // Return a single result?
let one = what.is_thing(); let one = what.is_thing_single();
// Specify the SQL query string // Specify the SQL query string
let sql = "SELECT * FROM $what"; let sql = "SELECT * FROM $what";
// Specify the query parameters // Specify the query parameters
@ -295,7 +295,7 @@ pub trait RpcContext {
return Err(RpcError::InvalidParams); return Err(RpcError::InvalidParams);
}; };
// Return a single result? // Return a single result?
let one = what.is_thing(); let one = what.is_thing_single();
// Specify the SQL query string // Specify the SQL query string
let sql = "INSERT INTO $what $data RETURN AFTER"; let sql = "INSERT INTO $what $data RETURN AFTER";
// Specify the query parameters // Specify the query parameters
@ -324,7 +324,7 @@ pub trait RpcContext {
return Err(RpcError::InvalidParams); return Err(RpcError::InvalidParams);
}; };
// Return a single result? // Return a single result?
let one = what.is_thing(); let one = what.is_thing_single();
// Specify the SQL query string // Specify the SQL query string
let sql = if data.is_none_or_null() { let sql = if data.is_none_or_null() {
"CREATE $what RETURN AFTER" "CREATE $what RETURN AFTER"
@ -357,7 +357,7 @@ pub trait RpcContext {
return Err(RpcError::InvalidParams); return Err(RpcError::InvalidParams);
}; };
// Return a single result? // Return a single result?
let one = what.is_thing(); let one = what.is_thing_single();
// Specify the SQL query string // Specify the SQL query string
let sql = if data.is_none_or_null() { let sql = if data.is_none_or_null() {
"UPSERT $what RETURN AFTER" "UPSERT $what RETURN AFTER"
@ -390,7 +390,7 @@ pub trait RpcContext {
return Err(RpcError::InvalidParams); return Err(RpcError::InvalidParams);
}; };
// Return a single result? // Return a single result?
let one = what.is_thing(); let one = what.is_thing_single();
// Specify the SQL query string // Specify the SQL query string
let sql = if data.is_none_or_null() { let sql = if data.is_none_or_null() {
"UPDATE $what RETURN AFTER" "UPDATE $what RETURN AFTER"
@ -423,7 +423,7 @@ pub trait RpcContext {
return Err(RpcError::InvalidParams); return Err(RpcError::InvalidParams);
}; };
// Return a single result? // Return a single result?
let one = what.is_thing(); let one = what.is_thing_single();
// Specify the SQL query string // Specify the SQL query string
let sql = if data.is_none_or_null() { let sql = if data.is_none_or_null() {
"UPDATE $what RETURN AFTER" "UPDATE $what RETURN AFTER"
@ -456,7 +456,7 @@ pub trait RpcContext {
return Err(RpcError::InvalidParams); return Err(RpcError::InvalidParams);
}; };
// Return a single result? // Return a single result?
let one = what.is_thing(); let one = what.is_thing_single();
// Specify the SQL query string // Specify the SQL query string
let sql = match diff.is_true() { let sql = match diff.is_true() {
true => "UPDATE $what PATCH $data RETURN DIFF", true => "UPDATE $what PATCH $data RETURN DIFF",
@ -488,7 +488,7 @@ pub trait RpcContext {
return Err(RpcError::InvalidParams); return Err(RpcError::InvalidParams);
}; };
// Return a single result? // Return a single result?
let one = kind.is_thing(); let one = kind.is_thing_single();
// Specify the SQL query string // Specify the SQL query string
let sql = if data.is_none_or_null() { let sql = if data.is_none_or_null() {
"RELATE $from->$kind->$to" "RELATE $from->$kind->$to"
@ -523,7 +523,7 @@ pub trait RpcContext {
return Err(RpcError::InvalidParams); return Err(RpcError::InvalidParams);
}; };
// Return a single result? // Return a single result?
let one = what.is_thing(); let one = what.is_thing_single();
// Specify the SQL query string // Specify the SQL query string
let sql = "DELETE $what RETURN BEFORE"; let sql = "DELETE $what RETURN BEFORE";
// Specify the query parameters // Specify the query parameters

View file

@ -1,3 +1,4 @@
use super::Range;
use crate::cnf::ID_CHARS; use crate::cnf::ID_CHARS;
use crate::ctx::Context; use crate::ctx::Context;
use crate::dbs::Options; use crate::dbs::Options;
@ -6,13 +7,17 @@ use crate::err::Error;
use crate::sql::{escape::escape_rid, Array, Number, Object, Strand, Thing, Uuid, Value}; use crate::sql::{escape::escape_rid, Array, Number, Object, Strand, Thing, Uuid, Value};
use derive::Key; use derive::Key;
use nanoid::nanoid; use nanoid::nanoid;
use range::IdRange;
use reblessive::tree::Stk; use reblessive::tree::Stk;
use revision::revisioned; use revision::revisioned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::ops::{Bound, Deref};
use ulid::Ulid; use ulid::Ulid;
pub mod range;
#[revisioned(revision = 1)] #[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
@ -33,6 +38,7 @@ pub enum Id {
Array(Array), Array(Array),
Object(Object), Object(Object),
Generate(Gen), Generate(Gen),
Range(Box<IdRange>),
} }
impl From<i64> for Id { impl From<i64> for Id {
@ -97,25 +103,25 @@ impl From<&String> for Id {
impl From<Vec<&str>> for Id { impl From<Vec<&str>> for Id {
fn from(v: Vec<&str>) -> Self { fn from(v: Vec<&str>) -> Self {
Id::Array(v.into()) Self::Array(v.into())
} }
} }
impl From<Vec<String>> for Id { impl From<Vec<String>> for Id {
fn from(v: Vec<String>) -> Self { fn from(v: Vec<String>) -> Self {
Id::Array(v.into()) Self::Array(v.into())
} }
} }
impl From<Vec<Value>> for Id { impl From<Vec<Value>> for Id {
fn from(v: Vec<Value>) -> Self { fn from(v: Vec<Value>) -> Self {
Id::Array(v.into()) Self::Array(v.into())
} }
} }
impl From<BTreeMap<String, Value>> for Id { impl From<BTreeMap<String, Value>> for Id {
fn from(v: BTreeMap<String, Value>) -> Self { fn from(v: BTreeMap<String, Value>) -> Self {
Id::Object(v.into()) Self::Object(v.into())
} }
} }
@ -129,6 +135,42 @@ impl From<Number> for Id {
} }
} }
impl From<IdRange> for Id {
fn from(v: IdRange) -> Self {
Self::Range(Box::new(v))
}
}
impl TryFrom<(Bound<Id>, Bound<Id>)> for Id {
type Error = Error;
fn try_from(v: (Bound<Id>, Bound<Id>)) -> Result<Self, Self::Error> {
Ok(Self::Range(Box::new(v.try_into()?)))
}
}
impl TryFrom<Range> for Id {
type Error = Error;
fn try_from(v: Range) -> Result<Self, Self::Error> {
Ok(Id::Range(Box::new(v.try_into()?)))
}
}
impl TryFrom<Value> for Id {
type Error = Error;
fn try_from(v: Value) -> Result<Self, Self::Error> {
match v {
Value::Number(Number::Int(v)) => Ok(v.into()),
Value::Strand(v) => Ok(v.into()),
Value::Array(v) => Ok(v.into()),
Value::Object(v) => Ok(v.into()),
Value::Range(v) => v.deref().to_owned().try_into(),
v => Err(Error::IdInvalid {
value: v.kindof().to_string(),
}),
}
}
}
impl From<Thing> for Id { impl From<Thing> for Id {
fn from(v: Thing) -> Self { fn from(v: Thing) -> Self {
v.id v.id
@ -160,6 +202,7 @@ impl Id {
Gen::Ulid => "ulid()".to_string(), Gen::Ulid => "ulid()".to_string(),
Gen::Uuid => "uuid()".to_string(), Gen::Uuid => "uuid()".to_string(),
}, },
Self::Range(v) => v.to_string(),
} }
} }
} }
@ -176,6 +219,7 @@ impl Display for Id {
Gen::Ulid => Display::fmt("ulid()", f), Gen::Ulid => Display::fmt("ulid()", f),
Gen::Uuid => Display::fmt("uuid()", f), Gen::Uuid => Display::fmt("uuid()", f),
}, },
Self::Range(v) => Display::fmt(v, f),
} }
} }
} }
@ -205,6 +249,7 @@ impl Id {
Gen::Ulid => Ok(Self::ulid()), Gen::Ulid => Ok(Self::ulid()),
Gen::Uuid => Ok(Self::uuid()), Gen::Uuid => Ok(Self::uuid()),
}, },
Id::Range(v) => Ok(Id::Range(Box::new(v.compute(stk, ctx, opt, doc).await?))),
} }
} }
} }

185
core/src/sql/id/range.rs Normal file
View 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))
}
}

View file

@ -30,6 +30,7 @@ pub enum Kind {
Set(Box<Kind>, Option<u64>), Set(Box<Kind>, Option<u64>),
Array(Box<Kind>, Option<u64>), Array(Box<Kind>, Option<u64>),
Function(Option<Vec<Kind>>, Option<Box<Kind>>), Function(Option<Vec<Kind>>, Option<Box<Kind>>),
Range,
} }
impl Default for Kind { impl Default for Kind {
@ -73,7 +74,8 @@ impl Kind {
| Kind::Uuid | Kind::Uuid
| Kind::Record(_) | Kind::Record(_)
| Kind::Geometry(_) | Kind::Geometry(_)
| Kind::Function(_, _) => return None, | Kind::Function(_, _)
| Kind::Range => return None,
Kind::Option(x) => { Kind::Option(x) => {
this = x; this = x;
} }
@ -137,6 +139,7 @@ impl Display for Kind {
(k, Some(l)) => write!(f, "array<{k}, {l}>"), (k, Some(l)) => write!(f, "array<{k}, {l}>"),
}, },
Kind::Either(k) => write!(f, "{}", Fmt::verbar_separated(k)), Kind::Either(k) => write!(f, "{}", Fmt::verbar_separated(k)),
Kind::Range => f.write_str("range"),
} }
} }
} }

View file

@ -110,6 +110,7 @@ pub use self::geometry::Geometry;
pub use self::graph::Graph; pub use self::graph::Graph;
pub use self::group::Group; pub use self::group::Group;
pub use self::group::Groups; pub use self::group::Groups;
pub use self::id::range::IdRange;
pub use self::id::Id; pub use self::id::Id;
pub use self::ident::Ident; pub use self::ident::Ident;
pub use self::idiom::Idiom; pub use self::idiom::Idiom;

View file

@ -1,15 +1,9 @@
use crate::cnf::GENERATION_ALLOCATION_LIMIT;
use crate::ctx::Context; use crate::ctx::Context;
use crate::dbs::Options; use crate::dbs::Options;
use crate::doc::CursorDoc; use crate::doc::CursorDoc;
use crate::err::Error; use crate::err::Error;
use crate::sql::Cond; use crate::sql::{Number, Subquery, Value};
use crate::sql::Expression;
use crate::sql::Ident;
use crate::sql::Idiom;
use crate::sql::Operator;
use crate::sql::Part;
use crate::sql::Thing;
use crate::sql::{strand::no_nul_bytes, Id, Value};
use crate::syn; use crate::syn;
use reblessive::tree::Stk; use reblessive::tree::Stk;
use revision::revisioned; use revision::revisioned;
@ -19,7 +13,8 @@ use std::fmt;
use std::ops::Bound; use std::ops::Bound;
use std::str::FromStr; use std::str::FromStr;
const ID: &str = "id"; use super::Id;
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range"; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range";
#[revisioned(revision = 1)] #[revisioned(revision = 1)]
@ -28,10 +23,8 @@ pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range";
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive] #[non_exhaustive]
pub struct Range { pub struct Range {
#[serde(with = "no_nul_bytes")] pub beg: Bound<Value>,
pub tb: String, pub end: Bound<Value>,
pub beg: Bound<Id>,
pub end: Bound<Id>,
} }
impl FromStr for Range { impl FromStr for Range {
@ -51,108 +44,54 @@ impl TryFrom<&str> for Range {
} }
} }
impl Range { impl From<(Bound<Id>, Bound<Id>)> for Range {
/// Construct a new range fn from(v: (Bound<Id>, Bound<Id>)) -> Self {
pub fn new(tb: String, beg: Bound<Id>, end: Bound<Id>) -> Self { fn convert(v: Bound<Id>) -> Bound<Value> {
Self { match v {
tb, Bound::Included(v) => Bound::Included(v.into()),
beg, Bound::Excluded(v) => Bound::Excluded(v.into()),
end, Bound::Unbounded => Bound::Unbounded,
} }
} }
/// Convert `Range` to `Cond` Self {
pub fn to_cond(self) -> Option<Cond> { beg: convert(v.0),
match (self.beg, self.end) { end: convert(v.1),
(Bound::Unbounded, Bound::Unbounded) => None,
(Bound::Unbounded, Bound::Excluded(id)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThan,
Thing::from((self.tb, id)).into(),
)))))
} }
(Bound::Unbounded, Bound::Included(id)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThanOrEqual,
Thing::from((self.tb, id)).into(),
)))))
} }
(Bound::Excluded(id), Bound::Unbounded) => { }
Some(Cond(Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), impl TryInto<std::ops::Range<i64>> for Range {
Operator::MoreThan, type Error = Error;
Thing::from((self.tb, id)).into(), fn try_into(self) -> Result<std::ops::Range<i64>, Self::Error> {
))))) let beg = match self.beg {
Bound::Unbounded => i64::MIN,
Bound::Included(beg) => to_i64(beg)?,
Bound::Excluded(beg) => to_i64(beg)? + 1,
};
let end = match self.end {
Bound::Unbounded => i64::MAX,
Bound::Included(end) => to_i64(end)? + 1,
Bound::Excluded(end) => to_i64(end)?,
};
if (beg + *GENERATION_ALLOCATION_LIMIT as i64) < end {
Err(Error::RangeTooBig {
max: *GENERATION_ALLOCATION_LIMIT,
})
} else {
Ok(beg..end)
} }
(Bound::Included(id), Bound::Unbounded) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThanOrEqual,
Thing::from((self.tb, id)).into(),
)))))
}
(Bound::Included(lid), Bound::Included(rid)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThanOrEqual,
Thing::from((self.tb.clone(), lid)).into(),
))),
Operator::And,
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThanOrEqual,
Thing::from((self.tb, rid)).into(),
))),
)))))
}
(Bound::Included(lid), Bound::Excluded(rid)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThanOrEqual,
Thing::from((self.tb.clone(), lid)).into(),
))),
Operator::And,
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThan,
Thing::from((self.tb, rid)).into(),
))),
)))))
}
(Bound::Excluded(lid), Bound::Included(rid)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThan,
Thing::from((self.tb.clone(), lid)).into(),
))),
Operator::And,
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThanOrEqual,
Thing::from((self.tb, rid)).into(),
))),
)))))
}
(Bound::Excluded(lid), Bound::Excluded(rid)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThan,
Thing::from((self.tb.clone(), lid)).into(),
))),
Operator::And,
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThan,
Thing::from((self.tb, rid)).into(),
))),
)))))
} }
}
impl Range {
/// Construct a new range
pub fn new(beg: Bound<Value>, end: Bound<Value>) -> Self {
Self {
beg,
end,
} }
} }
@ -165,15 +104,14 @@ impl Range {
doc: Option<&CursorDoc>, doc: Option<&CursorDoc>,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
Ok(Value::Range(Box::new(Range { Ok(Value::Range(Box::new(Range {
tb: self.tb.clone(),
beg: match &self.beg { beg: match &self.beg {
Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?), Bound::Included(v) => Bound::Included(v.compute(stk, ctx, opt, doc).await?),
Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?), Bound::Excluded(v) => Bound::Excluded(v.compute(stk, ctx, opt, doc).await?),
Bound::Unbounded => Bound::Unbounded, Bound::Unbounded => Bound::Unbounded,
}, },
end: match &self.end { end: match &self.end {
Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?), Bound::Included(v) => Bound::Included(v.compute(stk, ctx, opt, doc).await?),
Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?), Bound::Excluded(v) => Bound::Excluded(v.compute(stk, ctx, opt, doc).await?),
Bound::Unbounded => Bound::Unbounded, Bound::Unbounded => Bound::Unbounded,
}, },
}))) })))
@ -182,74 +120,94 @@ impl Range {
impl PartialOrd for Range { impl PartialOrd for Range {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.tb.partial_cmp(&other.tb) { Some(self.cmp(other))
Some(Ordering::Equal) => match &self.beg { }
}
impl Ord for Range {
fn cmp(&self, other: &Self) -> Ordering {
match &self.beg {
Bound::Unbounded => match &other.beg { Bound::Unbounded => match &other.beg {
Bound::Unbounded => Some(Ordering::Equal), Bound::Unbounded => Ordering::Equal,
_ => Some(Ordering::Less), _ => Ordering::Less,
}, },
Bound::Included(v) => match &other.beg { Bound::Included(v) => match &other.beg {
Bound::Unbounded => Some(Ordering::Greater), Bound::Unbounded => Ordering::Greater,
Bound::Included(w) => match v.partial_cmp(w) { Bound::Included(w) => match v.cmp(w) {
Some(Ordering::Equal) => match &self.end { Ordering::Equal => match &self.end {
Bound::Unbounded => match &other.end { Bound::Unbounded => match &other.end {
Bound::Unbounded => Some(Ordering::Equal), Bound::Unbounded => Ordering::Equal,
_ => Some(Ordering::Greater), _ => Ordering::Greater,
}, },
Bound::Included(v) => match &other.end { Bound::Included(v) => match &other.end {
Bound::Unbounded => Some(Ordering::Less), Bound::Unbounded => Ordering::Less,
Bound::Included(w) => v.partial_cmp(w), Bound::Included(w) => v.cmp(w),
_ => Some(Ordering::Greater), _ => Ordering::Greater,
}, },
Bound::Excluded(v) => match &other.end { Bound::Excluded(v) => match &other.end {
Bound::Excluded(w) => v.partial_cmp(w), Bound::Excluded(w) => v.cmp(w),
_ => Some(Ordering::Less), _ => Ordering::Less,
}, },
}, },
ordering => ordering, ordering => ordering,
}, },
_ => Some(Ordering::Less), _ => Ordering::Less,
}, },
Bound::Excluded(v) => match &other.beg { Bound::Excluded(v) => match &other.beg {
Bound::Excluded(w) => match v.partial_cmp(w) { Bound::Excluded(w) => match v.cmp(w) {
Some(Ordering::Equal) => match &self.end { Ordering::Equal => match &self.end {
Bound::Unbounded => match &other.end { Bound::Unbounded => match &other.end {
Bound::Unbounded => Some(Ordering::Equal), Bound::Unbounded => Ordering::Equal,
_ => Some(Ordering::Greater), _ => Ordering::Greater,
}, },
Bound::Included(v) => match &other.end { Bound::Included(v) => match &other.end {
Bound::Unbounded => Some(Ordering::Less), Bound::Unbounded => Ordering::Less,
Bound::Included(w) => v.partial_cmp(w), Bound::Included(w) => v.cmp(w),
_ => Some(Ordering::Greater), _ => Ordering::Greater,
}, },
Bound::Excluded(v) => match &other.end { Bound::Excluded(v) => match &other.end {
Bound::Excluded(w) => v.partial_cmp(w), Bound::Excluded(w) => v.cmp(w),
_ => Some(Ordering::Less), _ => Ordering::Less,
}, },
}, },
ordering => ordering, ordering => ordering,
}, },
_ => Some(Ordering::Greater), _ => Ordering::Greater,
}, },
},
ordering => ordering,
} }
} }
} }
impl fmt::Display for Range { impl fmt::Display for Range {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:", self.tb)?; fn bound_value(v: &Value) -> Value {
if v.can_be_range_bound() {
v.to_owned()
} else {
Value::Subquery(Box::new(Subquery::Value(v.to_owned())))
}
}
match &self.beg { match &self.beg {
Bound::Unbounded => write!(f, ""), Bound::Unbounded => write!(f, ""),
Bound::Included(id) => write!(f, "{id}"), Bound::Included(v) => write!(f, "{}", bound_value(v)),
Bound::Excluded(id) => write!(f, "{id}>"), Bound::Excluded(v) => write!(f, "{}>", bound_value(v)),
}?; }?;
match &self.end { match &self.end {
Bound::Unbounded => write!(f, ".."), Bound::Unbounded => write!(f, ".."),
Bound::Excluded(id) => write!(f, "..{id}"), Bound::Excluded(v) => write!(f, "..{}", bound_value(v)),
Bound::Included(id) => write!(f, "..={id}"), Bound::Included(v) => write!(f, "..={}", bound_value(v)),
}?; }?;
Ok(()) Ok(())
} }
} }
fn to_i64(v: Value) -> Result<i64, Error> {
match v {
Value::Number(Number::Int(v)) => Ok(v),
v => Err(Error::InvalidRangeValue {
expected: "int".to_string(),
found: v.kindof().to_string(),
}),
}
}

View file

@ -63,7 +63,7 @@ impl AlterTableStatement {
dt.changefeed = *changefeed; dt.changefeed = *changefeed;
} }
if let Some(ref comment) = &self.comment { if let Some(ref comment) = &self.comment {
dt.comment = comment.clone(); dt.comment.clone_from(comment);
} }
if let Some(ref kind) = &self.kind { if let Some(ref kind) = &self.kind {
dt.kind = kind.clone(); dt.kind = kind.clone();

View file

@ -8,6 +8,7 @@ use reblessive::tree::Stk;
use revision::revisioned; use revision::revisioned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::ops::Deref;
#[revisioned(revision = 1)] #[revisioned(revision = 1)]
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
@ -19,6 +20,22 @@ pub struct ForeachStatement {
pub block: Block, pub block: Block,
} }
enum ForeachIter {
Array(std::vec::IntoIter<Value>),
Range(std::iter::Map<std::ops::Range<i64>, fn(i64) -> Value>),
}
impl Iterator for ForeachIter {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
match self {
ForeachIter::Array(iter) => iter.next(),
ForeachIter::Range(iter) => iter.next(),
}
}
}
impl ForeachStatement { impl ForeachStatement {
/// Check if we require a writeable transaction /// Check if we require a writeable transaction
pub(crate) fn writeable(&self) -> bool { pub(crate) fn writeable(&self) -> bool {
@ -35,10 +52,23 @@ impl ForeachStatement {
doc: Option<&CursorDoc>, doc: Option<&CursorDoc>,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
// Check the loop data // Check the loop data
match &self.range.compute(stk, ctx, opt, doc).await? { let data = self.range.compute(stk, ctx, opt, doc).await?;
Value::Array(arr) => { let iter = match data {
Value::Array(arr) => ForeachIter::Array(arr.into_iter()),
Value::Range(r) => {
let r: std::ops::Range<i64> = r.deref().to_owned().try_into()?;
ForeachIter::Range(r.map(Value::from))
}
v => {
return Err(Error::InvalidStatementTarget {
value: v.to_string(),
})
}
};
// Loop over the values // Loop over the values
'foreach: for v in arr.iter() { 'foreach: for v in iter {
// Duplicate context // Duplicate context
let ctx = MutableContext::new(ctx).freeze(); let ctx = MutableContext::new(ctx).freeze();
// Set the current parameter // Set the current parameter
@ -61,9 +91,7 @@ impl ForeachStatement {
Entry::Value(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, Entry::Value(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
Entry::Break(v) => v.compute(&ctx, opt, doc).await, Entry::Break(v) => v.compute(&ctx, opt, doc).await,
Entry::Continue(v) => v.compute(&ctx, opt, doc).await, Entry::Continue(v) => v.compute(&ctx, opt, doc).await,
Entry::Foreach(v) => { Entry::Foreach(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await
}
Entry::Ifelse(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, Entry::Ifelse(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
Entry::Select(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, Entry::Select(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
Entry::Create(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, Entry::Create(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await,
@ -95,11 +123,6 @@ impl ForeachStatement {
// Ok all good // Ok all good
Ok(Value::None) Ok(Value::None)
} }
v => Err(Error::InvalidStatementTarget {
value: v.to_string(),
}),
}
}
} }
impl Display for ForeachStatement { impl Display for ForeachStatement {

View file

@ -4,8 +4,8 @@ use crate::doc::CursorDoc;
use crate::err::Error; use crate::err::Error;
use crate::idx::planner::QueryPlanner; use crate::idx::planner::QueryPlanner;
use crate::sql::{ use crate::sql::{
Cond, Explain, Fetchs, Field, Fields, Groups, Idioms, Limit, Orders, Splits, Start, Timeout, Cond, Explain, Fetchs, Field, Fields, Groups, Id, Idioms, Limit, Orders, Splits, Start,
Value, Values, Version, With, Timeout, Value, Values, Version, With,
}; };
use derive::Store; use derive::Store;
use reblessive::tree::Stk; use reblessive::tree::Stk;
@ -101,14 +101,10 @@ impl SelectStatement {
planner.add_iterables(stk, ctx, t, &mut i).await?; planner.add_iterables(stk, ctx, t, &mut i).await?;
} }
Value::Thing(v) => i.ingest(Iterable::Thing(v)), Value::Thing(v) => match &v.id {
Value::Range(v) => { Id::Range(r) => i.ingest(Iterable::TableRange(v.tb, *r.to_owned())),
if self.only && !limit_is_one_or_zero { _ => i.ingest(Iterable::Thing(v)),
return Err(Error::SingleOnlyOutput); },
}
i.ingest(Iterable::Range(*v))
}
Value::Edges(v) => { Value::Edges(v) => {
if self.only && !limit_is_one_or_zero { if self.only && !limit_is_one_or_zero {
return Err(Error::SingleOnlyOutput); return Err(Error::SingleOnlyOutput);

View file

@ -1,3 +1,5 @@
use super::id::range::IdRange;
use super::{Cond, Expression, Ident, Idiom, Operator, Part, Table};
use crate::ctx::Context; use crate::ctx::Context;
use crate::dbs::Options; use crate::dbs::Options;
use crate::doc::CursorDoc; use crate::doc::CursorDoc;
@ -9,10 +11,10 @@ use reblessive::tree::Stk;
use revision::revisioned; use revision::revisioned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::ops::Bound;
use std::str::FromStr; use std::str::FromStr;
use super::Table; const ID: &str = "id";
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing"; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing";
#[revisioned(revision = 1)] #[revisioned(revision = 1)]
@ -26,6 +28,110 @@ pub struct Thing {
pub id: Id, pub id: Id,
} }
impl Thing {
/// Convert `Thing` to `Cond`
pub fn to_cond(self) -> Option<Cond> {
match &self.id {
Id::Range(r) => match (&r.beg, &r.end) {
(Bound::Unbounded, Bound::Unbounded) => None,
(Bound::Unbounded, Bound::Excluded(id)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThan,
Thing::from((self.tb, id.to_owned())).into(),
)))))
}
(Bound::Unbounded, Bound::Included(id)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThanOrEqual,
Thing::from((self.tb, id.to_owned())).into(),
)))))
}
(Bound::Excluded(id), Bound::Unbounded) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThan,
Thing::from((self.tb, id.to_owned())).into(),
)))))
}
(Bound::Included(id), Bound::Unbounded) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThanOrEqual,
Thing::from((self.tb, id.to_owned())).into(),
)))))
}
(Bound::Included(lid), Bound::Included(rid)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThanOrEqual,
Thing::from((self.tb.clone(), lid.to_owned())).into(),
))),
Operator::And,
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThanOrEqual,
Thing::from((self.tb, rid.to_owned())).into(),
))),
)))))
}
(Bound::Included(lid), Bound::Excluded(rid)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThanOrEqual,
Thing::from((self.tb.clone(), lid.to_owned())).into(),
))),
Operator::And,
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThan,
Thing::from((self.tb, rid.to_owned())).into(),
))),
)))))
}
(Bound::Excluded(lid), Bound::Included(rid)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThan,
Thing::from((self.tb.clone(), lid.to_owned())).into(),
))),
Operator::And,
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThanOrEqual,
Thing::from((self.tb, rid.to_owned())).into(),
))),
)))))
}
(Bound::Excluded(lid), Bound::Excluded(rid)) => {
Some(Cond(Value::Expression(Box::new(Expression::new(
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::MoreThan,
Thing::from((self.tb.clone(), lid.to_owned())).into(),
))),
Operator::And,
Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::LessThan,
Thing::from((self.tb, rid.to_owned())).into(),
))),
)))))
}
},
_ => Some(Cond(Value::Expression(Box::new(Expression::new(
Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
Operator::Equal,
Thing::from((self.tb, self.id)).into(),
))))),
}
}
}
impl From<(&str, Id)> for Thing { impl From<(&str, Id)> for Thing {
fn from((tb, id): (&str, Id)) -> Self { fn from((tb, id): (&str, Id)) -> Self {
Self { Self {
@ -44,6 +150,24 @@ impl From<(String, Id)> for Thing {
} }
} }
impl From<(&str, IdRange)> for Thing {
fn from((tb, id): (&str, IdRange)) -> Self {
Self {
tb: tb.to_owned(),
id: id.into(),
}
}
}
impl From<(String, IdRange)> for Thing {
fn from((tb, id): (String, IdRange)) -> Self {
Self {
tb,
id: id.into(),
}
}
}
impl From<(String, String)> for Thing { impl From<(String, String)> for Thing {
fn from((tb, id): (String, String)) -> Self { fn from((tb, id): (String, String)) -> Self {
Self::from((tb, Id::from(id))) Self::from((tb, Id::from(id)))

View file

@ -388,7 +388,6 @@ mod tests {
#[test] #[test]
fn range() { fn range() {
let range = Box::new(Range { let range = Box::new(Range {
tb: "foo".to_owned(),
beg: Bound::Included("foo".into()), beg: Bound::Included("foo".into()),
end: Bound::Unbounded, end: Bound::Unbounded,
}); });

View file

@ -5,6 +5,7 @@ use crate::dbs::Options;
use crate::doc::CursorDoc; use crate::doc::CursorDoc;
use crate::err::Error; use crate::err::Error;
use crate::fnc::util::string::fuzzy::Fuzzy; use crate::fnc::util::string::fuzzy::Fuzzy;
use crate::sql::id::range::IdRange;
use crate::sql::statements::info::InfoStructure; use crate::sql::statements::info::InfoStructure;
use crate::sql::Closure; use crate::sql::Closure;
use crate::sql::{ use crate::sql::{
@ -28,7 +29,7 @@ use std::cmp::Ordering;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Display, Formatter, Write}; use std::fmt::{self, Display, Formatter, Write};
use std::ops::Deref; use std::ops::{Bound, Deref};
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Value"; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Value";
@ -263,6 +264,12 @@ impl From<Range> for Value {
} }
} }
impl From<Box<Range>> for Value {
fn from(v: Box<Range>) -> Self {
Value::Range(v)
}
}
impl From<Edges> for Value { impl From<Edges> for Value {
fn from(v: Edges) -> Self { fn from(v: Edges) -> Self {
Value::Edges(Box::new(v)) Value::Edges(Box::new(v))
@ -566,6 +573,27 @@ impl From<Option<Datetime>> for Value {
} }
} }
impl From<IdRange> for Value {
fn from(v: IdRange) -> Self {
let beg = match v.beg {
Bound::Included(beg) => Bound::Included(Value::from(beg)),
Bound::Excluded(beg) => Bound::Excluded(Value::from(beg)),
Bound::Unbounded => Bound::Unbounded,
};
let end = match v.end {
Bound::Included(end) => Bound::Included(Value::from(end)),
Bound::Excluded(end) => Bound::Excluded(Value::from(end)),
Bound::Unbounded => Bound::Unbounded,
};
Value::Range(Box::new(Range {
beg,
end,
}))
}
}
impl From<Id> for Value { impl From<Id> for Value {
fn from(v: Id) -> Self { fn from(v: Id) -> Self {
match v { match v {
@ -578,6 +606,7 @@ impl From<Id> for Value {
Gen::Ulid => Id::ulid().into(), Gen::Ulid => Id::ulid().into(),
Gen::Uuid => Id::uuid().into(), Gen::Uuid => Id::uuid().into(),
}, },
Id::Range(v) => v.deref().to_owned().into(),
} }
} }
} }
@ -923,6 +952,25 @@ impl Value {
matches!(self, Value::Thing(_)) matches!(self, Value::Thing(_))
} }
/// Check if this Value is a single Thing
pub fn is_thing_single(&self) -> bool {
match self {
Value::Thing(t) => !matches!(t.id, Id::Range(_)),
_ => false,
}
}
/// Check if this Value is a single Thing
pub fn is_thing_range(&self) -> bool {
matches!(
self,
Value::Thing(Thing {
id: Id::Range(_),
..
})
)
}
/// Check if this Value is a Mock /// Check if this Value is a Mock
pub fn is_mock(&self) -> bool { pub fn is_mock(&self) -> bool {
matches!(self, Value::Mock(_)) matches!(self, Value::Mock(_))
@ -1223,6 +1271,7 @@ impl Value {
Self::Geometry(Geometry::MultiPolygon(_)) => "geometry<multipolygon>", Self::Geometry(Geometry::MultiPolygon(_)) => "geometry<multipolygon>",
Self::Geometry(Geometry::Collection(_)) => "geometry<collection>", Self::Geometry(Geometry::Collection(_)) => "geometry<collection>",
Self::Bytes(_) => "bytes", Self::Bytes(_) => "bytes",
Self::Range(_) => "range",
_ => "incorrect type", _ => "incorrect type",
} }
} }
@ -1249,6 +1298,7 @@ impl Value {
Kind::Point => self.coerce_to_point().map(Value::from), Kind::Point => self.coerce_to_point().map(Value::from),
Kind::Bytes => self.coerce_to_bytes().map(Value::from), Kind::Bytes => self.coerce_to_bytes().map(Value::from),
Kind::Uuid => self.coerce_to_uuid().map(Value::from), Kind::Uuid => self.coerce_to_uuid().map(Value::from),
Kind::Range => self.coerce_to_range().map(Value::from),
Kind::Function(_, _) => self.coerce_to_function().map(Value::from), Kind::Function(_, _) => self.coerce_to_function().map(Value::from),
Kind::Set(t, l) => match l { Kind::Set(t, l) => match l {
Some(l) => self.coerce_to_set_type_len(t, l).map(Value::from), Some(l) => self.coerce_to_set_type_len(t, l).map(Value::from),
@ -1643,6 +1693,19 @@ impl Value {
} }
} }
/// Try to coerce this value to a `Range`
pub(crate) fn coerce_to_range(self) -> Result<Range, Error> {
match self {
// Ranges are allowed
Value::Range(v) => Ok(*v),
// Anything else raises an error
_ => Err(Error::CoerceTo {
from: self,
into: "range".into(),
}),
}
}
/// Try to coerce this value to an `Geometry` point /// Try to coerce this value to an `Geometry` point
pub(crate) fn coerce_to_point(self) -> Result<Geometry, Error> { pub(crate) fn coerce_to_point(self) -> Result<Geometry, Error> {
match self { match self {
@ -1818,6 +1881,7 @@ impl Value {
Kind::Point => self.convert_to_point().map(Value::from), Kind::Point => self.convert_to_point().map(Value::from),
Kind::Bytes => self.convert_to_bytes().map(Value::from), Kind::Bytes => self.convert_to_bytes().map(Value::from),
Kind::Uuid => self.convert_to_uuid().map(Value::from), Kind::Uuid => self.convert_to_uuid().map(Value::from),
Kind::Range => self.convert_to_range().map(Value::from),
Kind::Function(_, _) => self.convert_to_function().map(Value::from), Kind::Function(_, _) => self.convert_to_function().map(Value::from),
Kind::Set(t, l) => match l { Kind::Set(t, l) => match l {
Some(l) => self.convert_to_set_type_len(t, l).map(Value::from), Some(l) => self.convert_to_set_type_len(t, l).map(Value::from),
@ -2206,6 +2270,10 @@ impl Value {
match self { match self {
// Arrays are allowed // Arrays are allowed
Value::Array(v) => Ok(v), Value::Array(v) => Ok(v),
Value::Range(r) => {
let range: std::ops::Range<i64> = r.deref().to_owned().try_into()?;
Ok(range.into_iter().map(Value::from).collect::<Vec<Value>>().into())
}
// Anything else raises an error // Anything else raises an error
_ => Err(Error::ConvertTo { _ => Err(Error::ConvertTo {
from: self, from: self,
@ -2214,6 +2282,26 @@ impl Value {
} }
} }
/// Try to convert this value to a `Range`
pub(crate) fn convert_to_range(self) -> Result<Range, Error> {
match self {
// Arrays with two elements are allowed
Value::Array(v) if v.len() == 2 => {
let mut v = v;
Ok(Range {
beg: Bound::Included(v.remove(0)),
end: Bound::Excluded(v.remove(0)),
})
}
Value::Range(r) => Ok(*r),
// Anything else raises an error
_ => Err(Error::ConvertTo {
from: self,
into: "range".into(),
}),
}
}
/// Try to convert this value to an `Geometry` point /// Try to convert this value to an `Geometry` point
pub(crate) fn convert_to_point(self) -> Result<Geometry, Error> { pub(crate) fn convert_to_point(self) -> Result<Geometry, Error> {
match self { match self {
@ -2588,6 +2676,19 @@ impl Value {
Value::Geometry(w) => v.contains(w), Value::Geometry(w) => v.contains(w),
_ => false, _ => false,
}, },
Value::Range(r) => {
let beg = match &r.beg {
Bound::Unbounded => true,
Bound::Included(beg) => beg.le(other),
Bound::Excluded(beg) => beg.lt(other),
};
beg && match &r.end {
Bound::Unbounded => true,
Bound::Included(end) => end.ge(other),
Bound::Excluded(end) => end.gt(other),
}
}
_ => false, _ => false,
} }
} }
@ -2654,6 +2755,25 @@ impl Value {
_ => self.partial_cmp(other), _ => self.partial_cmp(other),
} }
} }
pub fn can_be_range_bound(&self) -> bool {
matches!(
self,
Value::None
| Value::Null | Value::Array(_)
| Value::Block(_)
| Value::Bool(_) | Value::Datetime(_)
| Value::Duration(_)
| Value::Geometry(_)
| Value::Number(_)
| Value::Object(_)
| Value::Param(_)
| Value::Strand(_)
| Value::Subquery(_)
| Value::Table(_)
| Value::Uuid(_)
)
}
} }
impl fmt::Display for Value { impl fmt::Display for Value {

View file

@ -183,6 +183,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
UniCase::ascii("POSTINGS_ORDER") => TokenKind::Keyword(Keyword::PostingsOrder), UniCase::ascii("POSTINGS_ORDER") => TokenKind::Keyword(Keyword::PostingsOrder),
UniCase::ascii("PRUNE") => TokenKind::Keyword(Keyword::Prune), UniCase::ascii("PRUNE") => TokenKind::Keyword(Keyword::Prune),
UniCase::ascii("PUNCT") => TokenKind::Keyword(Keyword::Punct), UniCase::ascii("PUNCT") => TokenKind::Keyword(Keyword::Punct),
UniCase::ascii("RANGE") => TokenKind::Keyword(Keyword::Range),
UniCase::ascii("READONLY") => TokenKind::Keyword(Keyword::Readonly), UniCase::ascii("READONLY") => TokenKind::Keyword(Keyword::Readonly),
UniCase::ascii("RELATE") => TokenKind::Keyword(Keyword::Relate), UniCase::ascii("RELATE") => TokenKind::Keyword(Keyword::Relate),
UniCase::ascii("RELATION") => TokenKind::Keyword(Keyword::Relation), UniCase::ascii("RELATION") => TokenKind::Keyword(Keyword::Relation),

View file

@ -339,6 +339,7 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
UniCase::ascii("time::from::secs") => PathKind::Function, UniCase::ascii("time::from::secs") => PathKind::Function,
UniCase::ascii("time::from::unix") => PathKind::Function, UniCase::ascii("time::from::unix") => PathKind::Function,
// //
UniCase::ascii("type::array") => PathKind::Function,
UniCase::ascii("type::bool") => PathKind::Function, UniCase::ascii("type::bool") => PathKind::Function,
UniCase::ascii("type::bytes") => PathKind::Function, UniCase::ascii("type::bytes") => PathKind::Function,
UniCase::ascii("type::datetime") => PathKind::Function, UniCase::ascii("type::datetime") => PathKind::Function,

View file

@ -912,7 +912,7 @@ mod tests {
Value::from(Idiom(vec![ Value::from(Idiom(vec![
Part::Start(Value::Thing(Thing { Part::Start(Value::Thing(Thing {
tb: "test".to_owned(), tb: "test".to_owned(),
id: Id::Number(1), id: Id::from(1),
})), })),
Part::from("foo"), Part::from("foo"),
])) ]))
@ -928,7 +928,7 @@ mod tests {
Value::from(Idiom(vec![ Value::from(Idiom(vec![
Part::Start(Value::Thing(Thing { Part::Start(Value::Thing(Thing {
tb: "test".to_owned(), tb: "test".to_owned(),
id: Id::Number(1), id: Id::from(1),
})), })),
Part::Value(Value::Strand(Strand("foo".to_owned()))), Part::Value(Value::Strand(Strand("foo".to_owned()))),
])) ]))
@ -944,7 +944,7 @@ mod tests {
Value::from(Idiom(vec![ Value::from(Idiom(vec![
Part::Start(Value::Thing(Thing { Part::Start(Value::Thing(Thing {
tb: "test".to_owned(), tb: "test".to_owned(),
id: Id::Number(1), id: Id::from(1),
})), })),
Part::All Part::All
])) ]))

View file

@ -82,6 +82,7 @@ impl Parser<'_> {
t!("POINT") => Ok(Kind::Point), t!("POINT") => Ok(Kind::Point),
t!("STRING") => Ok(Kind::String), t!("STRING") => Ok(Kind::String),
t!("UUID") => Ok(Kind::Uuid), t!("UUID") => Ok(Kind::Uuid),
t!("RANGE") => Ok(Kind::Range),
t!("FUNCTION") => Ok(Kind::Function(Default::default(), Default::default())), t!("FUNCTION") => Ok(Kind::Function(Default::default(), Default::default())),
t!("RECORD") => { t!("RECORD") => {
let span = self.peek().span; let span = self.peek().span;

View file

@ -1,3 +1,5 @@
use std::ops::Bound;
use geo::Point; use geo::Point;
use reblessive::Stk; use reblessive::Stk;
@ -6,14 +8,14 @@ use crate::{
enter_object_recursion, enter_query_recursion, enter_object_recursion, enter_query_recursion,
sql::{ sql::{
Array, Closure, Dir, Function, Geometry, Ident, Idiom, Kind, Mock, Number, Param, Part, Array, Closure, Dir, Function, Geometry, Ident, Idiom, Kind, Mock, Number, Param, Part,
Script, Strand, Subquery, Table, Value, Range, Script, Strand, Subquery, Table, Value,
}, },
syn::{ syn::{
parser::{ parser::{
mac::{expected, unexpected}, mac::{expected, expected_whitespace, unexpected},
ParseError, ParseErrorKind, ParseError, ParseErrorKind,
}, },
token::{t, Span, TokenKind}, token::{t, DurationSuffix, Span, TokenKind},
}, },
}; };
@ -23,27 +25,29 @@ impl Parser<'_> {
/// What's are values which are more restricted in what expressions they can contain. /// What's are values which are more restricted in what expressions they can contain.
pub async fn parse_what_primary(&mut self, ctx: &mut Stk) -> ParseResult<Value> { pub async fn parse_what_primary(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
match self.peek_kind() { match self.peek_kind() {
t!("..") => Ok(self.try_parse_range(ctx, None).await?.unwrap()),
t!("r\"") => { t!("r\"") => {
self.pop_peek(); self.pop_peek();
let thing = self.parse_record_string(ctx, true).await?; let value = Value::Thing(self.parse_record_string(ctx, true).await?);
Ok(Value::Thing(thing)) Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
} }
t!("r'") => { t!("r'") => {
self.pop_peek(); self.pop_peek();
let thing = self.parse_record_string(ctx, false).await?; let value = Value::Thing(self.parse_record_string(ctx, false).await?);
Ok(Value::Thing(thing)) Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
} }
t!("d\"") | t!("d'") => { t!("d\"") | t!("d'") => {
let datetime = self.next_token_value()?; let value = Value::Datetime(self.next_token_value()?);
Ok(Value::Datetime(datetime)) Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
} }
t!("u\"") | t!("u'") => { t!("u\"") | t!("u'") => {
let uuid = self.next_token_value()?; let value = Value::Uuid(self.next_token_value()?);
Ok(Value::Uuid(uuid)) Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
} }
t!("$param") => { t!("$param") => {
let value = Value::Param(self.next_token_value()?); let value = Value::Param(self.next_token_value()?);
Ok(self.try_parse_inline(ctx, &value).await?.unwrap_or(value)) let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value);
Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value))
} }
t!("FUNCTION") => { t!("FUNCTION") => {
self.pop_peek(); self.pop_peek();
@ -128,12 +132,60 @@ impl Parser<'_> {
} }
} }
pub async fn try_parse_range(
&mut self,
ctx: &mut Stk,
subject: Option<&Value>,
) -> ParseResult<Option<Value>> {
// The ">" can also mean a comparison.
// If the token after is not "..", then return
if self.peek_whitespace().kind == t!(">")
&& self.peek_whitespace_token_at(1).kind != t!("..")
{
return Ok(None);
}
let beg = if let Some(subject) = subject {
if self.eat_whitespace(t!(">")) {
expected_whitespace!(self, t!(".."));
Bound::Excluded(subject.to_owned())
} else {
if !self.eat_whitespace(t!("..")) {
return Ok(None);
}
Bound::Included(subject.to_owned())
}
} else {
if !self.eat_whitespace(t!("..")) {
return Ok(None);
}
Bound::Unbounded
};
let end = if self.eat_whitespace(t!("=")) {
let id = ctx.run(|ctx| self.parse_simple_value(ctx)).await?;
Bound::Included(id)
} else if Self::tokenkind_can_start_simple_value(self.peek_whitespace().kind) {
let id = ctx.run(|ctx| self.parse_simple_value(ctx)).await?;
Bound::Excluded(id)
} else {
Bound::Unbounded
};
Ok(Some(Value::Range(Box::new(Range {
beg,
end,
}))))
}
pub async fn try_parse_inline( pub async fn try_parse_inline(
&mut self, &mut self,
ctx: &mut Stk, ctx: &mut Stk,
subject: &Value, subject: &Value,
) -> ParseResult<Option<Value>> { ) -> ParseResult<Option<Value>> {
if self.eat(t!("(")) { if self.eat_whitespace(t!("(")) {
let start = self.last_span(); let start = self.last_span();
let mut args = Vec::new(); let mut args = Vec::new();
loop { loop {
@ -171,21 +223,26 @@ impl Parser<'_> {
pub async fn parse_idiom_expression(&mut self, ctx: &mut Stk) -> ParseResult<Value> { pub async fn parse_idiom_expression(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
let token = self.peek(); let token = self.peek();
let value = match token.kind { let value = match token.kind {
t!("..") => self.try_parse_range(ctx, None).await?.unwrap(),
t!("NONE") => { t!("NONE") => {
self.pop_peek(); self.pop_peek();
Value::None let value = Value::None;
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("NULL") => { t!("NULL") => {
self.pop_peek(); self.pop_peek();
Value::Null let value = Value::Null;
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("true") => { t!("true") => {
self.pop_peek(); self.pop_peek();
Value::Bool(true) let value = Value::Bool(true);
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("false") => { t!("false") => {
self.pop_peek(); self.pop_peek();
Value::Bool(false) let value = Value::Bool(false);
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("<") => { t!("<") => {
self.pop_peek(); self.pop_peek();
@ -198,21 +255,21 @@ impl Parser<'_> {
} }
t!("r\"") => { t!("r\"") => {
self.pop_peek(); self.pop_peek();
let thing = self.parse_record_string(ctx, true).await?; let value = Value::Thing(self.parse_record_string(ctx, true).await?);
Value::Thing(thing) self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("r'") => { t!("r'") => {
self.pop_peek(); self.pop_peek();
let thing = self.parse_record_string(ctx, false).await?; let value = Value::Thing(self.parse_record_string(ctx, false).await?);
Value::Thing(thing) self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("d\"") | t!("d'") => { t!("d\"") | t!("d'") => {
let datetime = self.next_token_value()?; let value = Value::Datetime(self.next_token_value()?);
Value::Datetime(datetime) self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("u\"") | t!("u'") => { t!("u\"") | t!("u'") => {
let uuid = self.next_token_value()?; let value = Value::Uuid(self.next_token_value()?);
Value::Uuid(uuid) self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("'") | t!("\"") | TokenKind::Strand => { t!("'") | t!("\"") | TokenKind::Strand => {
let s = self.next_token_value::<Strand>()?; let s = self.next_token_value::<Strand>()?;
@ -221,18 +278,22 @@ impl Parser<'_> {
return Ok(x); return Ok(x);
} }
} }
Value::Strand(s) let value = Value::Strand(s);
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("+") | t!("-") | TokenKind::Number(_) | TokenKind::Digits | TokenKind::Duration => { t!("+") | t!("-") | TokenKind::Number(_) | TokenKind::Digits | TokenKind::Duration => {
self.parse_number_like_prime()? let value = self.parse_number_like_prime()?;
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
TokenKind::NaN => { TokenKind::NaN => {
self.pop_peek(); self.pop_peek();
Value::Number(Number::Float(f64::NAN)) let value = Value::Number(Number::Float(f64::NAN));
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("$param") => { t!("$param") => {
let value = Value::Param(self.next_token_value()?); let value = Value::Param(self.next_token_value()?);
self.try_parse_inline(ctx, &value).await?.unwrap_or(value) let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value);
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("FUNCTION") => { t!("FUNCTION") => {
self.pop_peek(); self.pop_peek();
@ -256,12 +317,14 @@ impl Parser<'_> {
} }
t!("[") => { t!("[") => {
self.pop_peek(); self.pop_peek();
self.parse_array(ctx, token.span).await.map(Value::Array)? let value = self.parse_array(ctx, token.span).await.map(Value::Array)?;
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("{") => { t!("{") => {
self.pop_peek(); self.pop_peek();
let value = self.parse_object_like(ctx, token.span).await?; let value = self.parse_object_like(ctx, token.span).await?;
self.try_parse_inline(ctx, &value).await?.unwrap_or(value) let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value);
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("|") => { t!("|") => {
self.pop_peek(); self.pop_peek();
@ -281,7 +344,8 @@ impl Parser<'_> {
t!("(") => { t!("(") => {
self.pop_peek(); self.pop_peek();
let value = self.parse_inner_subquery_or_coordinate(ctx, token.span).await?; let value = self.parse_inner_subquery_or_coordinate(ctx, token.span).await?;
self.try_parse_inline(ctx, &value).await?.unwrap_or(value) let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value);
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
} }
t!("/") => self.next_token_value().map(Value::Regex)?, t!("/") => self.next_token_value().map(Value::Regex)?,
t!("RETURN") t!("RETURN")
@ -749,6 +813,133 @@ impl Parser<'_> {
.map_err(|(e, span)| ParseError::new(ParseErrorKind::InvalidToken(e), span))?; .map_err(|(e, span)| ParseError::new(ParseErrorKind::InvalidToken(e), span))?;
Ok(Function::Script(Script(body), args)) Ok(Function::Script(Script(body), args))
} }
/// Parse a simple singular value
pub async fn parse_simple_value(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
let token = self.peek();
let value = match token.kind {
t!("NONE") => {
self.pop_peek();
let value = Value::None;
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
}
t!("NULL") => {
self.pop_peek();
let value = Value::Null;
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
}
t!("true") => {
self.pop_peek();
let value = Value::Bool(true);
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
}
t!("false") => {
self.pop_peek();
let value = Value::Bool(false);
self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)
}
t!("r\"") => {
self.pop_peek();
let thing = self.parse_record_string(ctx, true).await?;
Value::Thing(thing)
}
t!("r'") => {
self.pop_peek();
let thing = self.parse_record_string(ctx, false).await?;
Value::Thing(thing)
}
t!("d\"") | t!("d'") => {
let datetime = self.next_token_value()?;
Value::Datetime(datetime)
}
t!("u\"") | t!("u'") => {
let uuid = self.next_token_value()?;
Value::Uuid(uuid)
}
t!("'") | t!("\"") | TokenKind::Strand => {
let s = self.next_token_value::<Strand>()?;
if self.legacy_strands {
if let Some(x) = self.reparse_legacy_strand(ctx, &s.0).await {
return Ok(x);
}
}
Value::Strand(s)
}
t!("+") | t!("-") | TokenKind::Number(_) | TokenKind::Digits | TokenKind::Duration => {
self.parse_number_like_prime()?
}
TokenKind::NaN => {
self.pop_peek();
Value::Number(Number::Float(f64::NAN))
}
t!("$param") => {
let value = Value::Param(self.next_token_value()?);
self.try_parse_inline(ctx, &value).await?.unwrap_or(value)
}
t!("[") => {
self.pop_peek();
self.parse_array(ctx, token.span).await.map(Value::Array)?
}
t!("{") => {
self.pop_peek();
let value = self.parse_object_like(ctx, token.span).await?;
self.try_parse_inline(ctx, &value).await?.unwrap_or(value)
}
t!("(") => {
self.pop_peek();
let value = self.parse_inner_subquery_or_coordinate(ctx, token.span).await?;
self.try_parse_inline(ctx, &value).await?.unwrap_or(value)
}
_ => {
self.glue()?;
let x = self.peek_token_at(1).kind;
if x.has_data() {
unexpected!(self, x, "a value");
} else if self.table_as_field {
Value::Idiom(Idiom(vec![Part::Field(self.next_token_value()?)]))
} else {
Value::Table(self.next_token_value()?)
}
}
};
Ok(value)
}
pub fn tokenkind_can_start_simple_value(t: TokenKind) -> bool {
matches!(
t,
t!("NONE")
| t!("NULL") | t!("true")
| t!("false") | t!("r\"")
| t!("r'") | t!("d\"")
| t!("d'") | t!("u\"")
| t!("u'") | t!("\"")
| t!("'") | t!("+")
| t!("-") | TokenKind::Number(_)
| TokenKind::Digits
| TokenKind::Duration
| TokenKind::NaN | t!("$param")
| t!("[") | t!("{")
| t!("(") | TokenKind::Keyword(_)
| TokenKind::Language(_)
| TokenKind::Algorithm(_)
| TokenKind::Distance(_)
| TokenKind::VectorType(_)
| TokenKind::Identifier
| TokenKind::Exponent
| TokenKind::DatetimeChars(_)
| TokenKind::NumberSuffix(_)
| TokenKind::DurationSuffix(
// All except Micro unicode
DurationSuffix::Nano
| DurationSuffix::Micro | DurationSuffix::Milli
| DurationSuffix::Second | DurationSuffix::Minute
| DurationSuffix::Hour | DurationSuffix::Day
| DurationSuffix::Week | DurationSuffix::Year
)
)
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1664,7 +1664,7 @@ fn parse_delete_2() {
dir: Dir::Out, dir: Dir::Out,
from: Thing { from: Thing {
tb: "a".to_owned(), tb: "a".to_owned(),
id: Id::String("b".to_owned()), id: Id::from("b"),
}, },
what: Tables::default(), what: Tables::default(),
}))), }))),
@ -1867,7 +1867,7 @@ SELECT bar as foo,[1,2],bar OMIT bar FROM ONLY a,1
}])), }])),
limit: Some(Limit(Value::Thing(Thing { limit: Some(Limit(Value::Thing(Thing {
tb: "a".to_owned(), tb: "a".to_owned(),
id: Id::String("b".to_owned()), id: Id::from("b"),
}))), }))),
start: Some(Start(Value::Object(Object( start: Some(Start(Value::Object(Object(
[("a".to_owned(), Value::Bool(true))].into_iter().collect() [("a".to_owned(), Value::Bool(true))].into_iter().collect()
@ -2191,7 +2191,7 @@ fn parse_relate() {
only: true, only: true,
kind: Value::Thing(Thing { kind: Value::Thing(Thing {
tb: "a".to_owned(), tb: "a".to_owned(),
id: Id::String("b".to_owned()), id: Id::from("b"),
}), }),
from: Value::Array(Array(vec![ from: Value::Array(Array(vec![
Value::Number(Number::Int(1)), Value::Number(Number::Int(1)),

View file

@ -410,7 +410,7 @@ fn statements() -> Vec<Statement> {
dir: Dir::Out, dir: Dir::Out,
from: Thing { from: Thing {
tb: "a".to_owned(), tb: "a".to_owned(),
id: Id::String("b".to_owned()), id: Id::from("b"),
}, },
what: Tables::default(), what: Tables::default(),
}))), }))),
@ -519,7 +519,7 @@ fn statements() -> Vec<Statement> {
}])), }])),
limit: Some(Limit(Value::Thing(Thing { limit: Some(Limit(Value::Thing(Thing {
tb: "a".to_owned(), tb: "a".to_owned(),
id: Id::String("b".to_owned()), id: Id::from("b"),
}))), }))),
start: Some(Start(Value::Object(Object( start: Some(Start(Value::Object(Object(
[("a".to_owned(), Value::Bool(true))].into_iter().collect(), [("a".to_owned(), Value::Bool(true))].into_iter().collect(),
@ -625,7 +625,7 @@ fn statements() -> Vec<Statement> {
only: true, only: true,
kind: Value::Thing(Thing { kind: Value::Thing(Thing {
tb: "a".to_owned(), tb: "a".to_owned(),
id: Id::String("b".to_owned()), id: Id::from("b"),
}), }),
from: Value::Array(Array(vec![ from: Value::Array(Array(vec![
Value::Number(Number::Int(1)), Value::Number(Number::Int(1)),

View file

@ -93,13 +93,13 @@ fn parse_recursive_record_string() {
res, res,
Value::Thing(Thing { Value::Thing(Thing {
tb: "a".to_owned(), tb: "a".to_owned(),
id: Id::Array(Array(vec![Value::Thing(Thing { id: Id::from(Array(vec![Value::Thing(Thing {
tb: "b".to_owned(), tb: "b".to_owned(),
id: Id::Object(Object(BTreeMap::from([( id: Id::from(Object(BTreeMap::from([(
"c".to_owned(), "c".to_owned(),
Value::Thing(Thing { Value::Thing(Thing {
tb: "d".to_owned(), tb: "d".to_owned(),
id: Id::Number(1) id: Id::from(1)
}) })
)]))) )])))
})])) })]))
@ -114,7 +114,7 @@ fn parse_record_string_2() {
res, res,
Value::Thing(Thing { Value::Thing(Thing {
tb: "a".to_owned(), tb: "a".to_owned(),
id: Id::Array(Array(vec![Value::Strand(Strand("foo".to_owned()))])) id: Id::from(Array(vec![Value::Strand(Strand("foo".to_owned()))]))
}) })
) )
} }

View file

@ -2,7 +2,10 @@ use reblessive::Stk;
use super::{ParseResult, Parser}; use super::{ParseResult, Parser};
use crate::{ use crate::{
sql::{id::Gen, Id, Ident, Range, Thing, Value}, sql::{
id::{range::IdRange, Gen},
Id, Ident, Range, Thing, Value,
},
syn::{ syn::{
parser::{ parser::{
mac::{expected, expected_whitespace, unexpected}, mac::{expected, expected_whitespace, unexpected},
@ -51,22 +54,24 @@ impl Parser<'_> {
} else { } else {
Bound::Unbounded Bound::Unbounded
}; };
return Ok(Value::Range(Box::new(Range { return Ok(Value::Thing(Thing {
tb: ident, tb: ident,
id: Id::Range(Box::new(IdRange {
beg: Bound::Unbounded, beg: Bound::Unbounded,
end, end,
}))); })),
}));
} }
// Didn't eat range yet so we need to parse the id. // Didn't eat range yet so we need to parse the id.
let beg = if Self::kind_cast_start_id(self.peek_whitespace().kind) { let beg = if Self::kind_cast_start_id(self.peek_whitespace().kind) {
let id = stk.run(|ctx| self.parse_id(ctx)).await?; let v = stk.run(|stk| self.parse_id(stk)).await?;
// check for exclusive // check for exclusive
if self.eat_whitespace(t!(">")) { if self.eat_whitespace(t!(">")) {
Bound::Excluded(id) Bound::Excluded(v)
} else { } else {
Bound::Included(id) Bound::Included(v)
} }
} else { } else {
Bound::Unbounded Bound::Unbounded
@ -76,19 +81,21 @@ impl Parser<'_> {
// If we already ate the exclusive it must be a range. // If we already ate the exclusive it must be a range.
if self.eat_whitespace(t!("..")) { if self.eat_whitespace(t!("..")) {
let end = if self.eat_whitespace(t!("=")) { let end = if self.eat_whitespace(t!("=")) {
let id = stk.run(|ctx| self.parse_id(ctx)).await?; let id = stk.run(|stk| self.parse_id(stk)).await?;
Bound::Included(id) Bound::Included(id)
} else if Self::kind_cast_start_id(self.peek_whitespace().kind) { } else if Self::kind_cast_start_id(self.peek_whitespace().kind) {
let id = stk.run(|ctx| self.parse_id(ctx)).await?; let id = stk.run(|stk| self.parse_id(stk)).await?;
Bound::Excluded(id) Bound::Excluded(id)
} else { } else {
Bound::Unbounded Bound::Unbounded
}; };
Ok(Value::Range(Box::new(Range { Ok(Value::Thing(Thing {
tb: ident, tb: ident,
id: Id::Range(Box::new(IdRange {
beg, beg,
end, end,
}))) })),
}))
} else { } else {
let id = match beg { let id = match beg {
Bound::Unbounded => { Bound::Unbounded => {
@ -110,7 +117,8 @@ impl Parser<'_> {
// we have matched a bounded id but we don't see an range operator. // we have matched a bounded id but we don't see an range operator.
unexpected!(self, self.peek_whitespace().kind, "the range operator `..`") unexpected!(self, self.peek_whitespace().kind, "the range operator `..`")
} }
Bound::Included(id) => id, // We previously converted the `Id` value to `Value` so it's safe to unwrap here.
Bound::Included(v) => v,
}; };
Ok(Value::Thing(Thing { Ok(Value::Thing(Thing {
tb: ident, tb: ident,
@ -121,18 +129,14 @@ impl Parser<'_> {
/// Parse an range /// Parse an range
pub async fn parse_range(&mut self, ctx: &mut Stk) -> ParseResult<Range> { pub async fn parse_range(&mut self, ctx: &mut Stk) -> ParseResult<Range> {
let tb = self.next_token_value::<Ident>()?.0;
expected_whitespace!(self, t!(":"));
// Check for beginning id // Check for beginning id
let beg = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) { let beg = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) {
let id = ctx.run(|ctx| self.parse_id(ctx)).await?; let v = ctx.run(|ctx| self.parse_value(ctx)).await?;
if self.eat_whitespace(t!(">")) { if self.eat_whitespace(t!(">")) {
Bound::Excluded(id) Bound::Excluded(v)
} else { } else {
Bound::Included(id) Bound::Included(v)
} }
} else { } else {
Bound::Unbounded Bound::Unbounded
@ -144,18 +148,17 @@ impl Parser<'_> {
// parse ending id. // parse ending id.
let end = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) { let end = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) {
let id = ctx.run(|ctx| self.parse_id(ctx)).await?; let v = ctx.run(|ctx| self.parse_value(ctx)).await?;
if inclusive { if inclusive {
Bound::Included(id) Bound::Included(v)
} else { } else {
Bound::Excluded(id) Bound::Excluded(v)
} }
} else { } else {
Bound::Unbounded Bound::Unbounded
}; };
Ok(Range { Ok(Range {
tb,
beg, beg,
end, end,
}) })
@ -498,7 +501,7 @@ mod tests {
out, out,
Thing { Thing {
tb: String::from("test"), tb: String::from("test"),
id: Id::Object(Object::from(map! { id: Id::from(Object::from(map! {
"location".to_string() => Value::from("GBR"), "location".to_string() => Value::from("GBR"),
"year".to_string() => Value::from(2022), "year".to_string() => Value::from(2022),
})), })),
@ -516,7 +519,7 @@ mod tests {
out, out,
Thing { Thing {
tb: String::from("test"), tb: String::from("test"),
id: Id::Array(Array::from(vec![Value::from("GBR"), Value::from(2022)])), id: Id::from(Array::from(vec![Value::from("GBR"), Value::from(2022)])),
} }
); );
} }
@ -535,7 +538,7 @@ mod tests {
.finish() .finish()
.unwrap_or_else(|_| panic!("failed on {}", ident)) .unwrap_or_else(|_| panic!("failed on {}", ident))
.id; .id;
assert_eq!(r, Id::String(ident.to_string()),); assert_eq!(r, Id::from(ident.to_string()),);
let mut parser = Parser::new(thing.as_bytes()); let mut parser = Parser::new(thing.as_bytes());
let r = stack let r = stack
@ -548,7 +551,7 @@ mod tests {
sql::Query(sql::Statements(vec![sql::Statement::Value(sql::Value::Thing( sql::Query(sql::Statements(vec![sql::Statement::Value(sql::Value::Thing(
sql::Thing { sql::Thing {
tb: "t".to_string(), tb: "t".to_string(),
id: Id::String(ident.to_string()) id: Id::from(ident.to_string())
} }
))])) ))]))
) )

View file

@ -142,6 +142,7 @@ keyword! {
PostingsOrder => "POSTINGS_ORDER", PostingsOrder => "POSTINGS_ORDER",
Prune => "PRUNE", Prune => "PRUNE",
Punct => "PUNCT", Punct => "PUNCT",
Range => "RANGE",
Readonly => "READONLY", Readonly => "READONLY",
Rebuild => "REBUILD", Rebuild => "REBUILD",
Relate => "RELATE", Relate => "RELATE",

View file

@ -71,7 +71,7 @@ fn bench_hash_trie_btree_thing(c: &mut Criterion) {
const N: usize = 50_000; const N: usize = 50_000;
let mut samples = Vec::with_capacity(N); let mut samples = Vec::with_capacity(N);
for i in 0..N { for i in 0..N {
let key = Thing::from(("test", Id::Array(Array::from(vec![i as i32; 5])))); let key = Thing::from(("test", Id::from(Array::from(vec![i as i32; 5]))));
samples.push((key, i)); samples.push((key, i));
} }

View file

@ -174,7 +174,7 @@ fn new_vectors_from_file(path: &str) -> Vec<(Thing, Array)> {
let line = line_result.unwrap(); let line = line_result.unwrap();
let value = value(&line).unwrap(); let value = value(&line).unwrap();
if let Value::Array(a) = value { if let Value::Array(a) = value {
res.push((Thing::from(("e", Id::Number(i as i64))), a)); res.push((Thing::from(("e", Id::from(i as i64))), a));
} else { } else {
panic!("Wrong value"); panic!("Wrong value");
} }

View file

@ -553,7 +553,7 @@ async fn router(
data, data,
} => { } => {
let mut query = Query::default(); let mut query = Query::default();
let one = what.is_thing(); let one = what.is_thing_single();
let statement = { let statement = {
let mut stmt = UpsertStatement::default(); let mut stmt = UpsertStatement::default();
stmt.what = value_to_values(what); stmt.what = value_to_values(what);
@ -571,7 +571,7 @@ async fn router(
data, data,
} => { } => {
let mut query = Query::default(); let mut query = Query::default();
let one = what.is_thing(); let one = what.is_thing_single();
let statement = { let statement = {
let mut stmt = UpdateStatement::default(); let mut stmt = UpdateStatement::default();
stmt.what = value_to_values(what); stmt.what = value_to_values(what);
@ -607,7 +607,7 @@ async fn router(
data, data,
} => { } => {
let mut query = Query::default(); let mut query = Query::default();
let one = what.is_thing(); let one = what.is_thing_single();
let statement = { let statement = {
let mut stmt = UpdateStatement::default(); let mut stmt = UpdateStatement::default();
stmt.what = value_to_values(what); stmt.what = value_to_values(what);
@ -625,7 +625,7 @@ async fn router(
data, data,
} => { } => {
let mut query = Query::default(); let mut query = Query::default();
let one = what.is_thing(); let one = what.is_thing_single();
let statement = { let statement = {
let mut stmt = UpdateStatement::default(); let mut stmt = UpdateStatement::default();
stmt.what = value_to_values(what); stmt.what = value_to_values(what);
@ -642,7 +642,7 @@ async fn router(
what, what,
} => { } => {
let mut query = Query::default(); let mut query = Query::default();
let one = what.is_thing(); let one = what.is_thing_single();
let statement = { let statement = {
let mut stmt = SelectStatement::default(); let mut stmt = SelectStatement::default();
stmt.what = value_to_values(what); stmt.what = value_to_values(what);
@ -658,7 +658,7 @@ async fn router(
what, what,
} => { } => {
let mut query = Query::default(); let mut query = Query::default();
let one = what.is_thing(); let one = what.is_thing_single();
let statement = { let statement = {
let mut stmt = DeleteStatement::default(); let mut stmt = DeleteStatement::default();
stmt.what = value_to_values(what); stmt.what = value_to_values(what);

View file

@ -14,13 +14,10 @@ use crate::method::Select;
use crate::opt::Resource; use crate::opt::Resource;
use crate::sql::from_value; use crate::sql::from_value;
use crate::sql::statements::LiveStatement; use crate::sql::statements::LiveStatement;
use crate::sql::Cond;
use crate::sql::Expression;
use crate::sql::Field; use crate::sql::Field;
use crate::sql::Fields; use crate::sql::Fields;
use crate::sql::Ident; use crate::sql::Ident;
use crate::sql::Idiom; use crate::sql::Idiom;
use crate::sql::Operator;
use crate::sql::Part; use crate::sql::Part;
use crate::sql::Statement; use crate::sql::Statement;
use crate::sql::Table; use crate::sql::Table;
@ -63,10 +60,10 @@ macro_rules! into_future {
let mut table = Table::default(); let mut table = Table::default();
match range { match range {
Some(range) => { Some(range) => {
let range = resource?.with_range(range)?; let record = resource?.with_range(range)?;
table.0 = range.tb.clone(); table.0 = record.tb.clone();
stmt.what = table.into(); stmt.what = table.into();
stmt.cond = range.to_cond(); stmt.cond = record.to_cond();
} }
None => match resource? { None => match resource? {
Resource::Table(table) => { Resource::Table(table) => {
@ -79,13 +76,7 @@ macro_rules! into_future {
ident.0 = ID.to_owned(); ident.0 = ID.to_owned();
let mut idiom = Idiom::default(); let mut idiom = Idiom::default();
idiom.0 = vec![Part::from(ident)]; idiom.0 = vec![Part::from(ident)];
let mut cond = Cond::default(); stmt.cond = record.to_cond();
cond.0 = Value::Expression(Box::new(Expression::new(
idiom.into(),
Operator::Equal,
record.into(),
)));
stmt.cond = Some(cond);
} }
Resource::Object(object) => return Err(Error::LiveOnObject(object).into()), Resource::Object(object) => return Err(Error::LiveOnObject(object).into()),
Resource::Array(array) => return Err(Error::LiveOnArray(array).into()), Resource::Array(array) => return Err(Error::LiveOnArray(array).into()),

View file

@ -4,6 +4,8 @@ use crate::api::conn::DbResponse;
use crate::api::conn::Route; use crate::api::conn::Route;
use crate::api::Response as QueryResponse; use crate::api::Response as QueryResponse;
use crate::sql::to_value; use crate::sql::to_value;
use crate::sql::Id;
use crate::sql::Thing;
use crate::sql::Value; use crate::sql::Value;
use channel::Receiver; use channel::Receiver;
@ -61,10 +63,13 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
what, what,
.. ..
} => match what { } => match what {
Value::Table(..)
| Value::Array(..)
| Value::Thing(Thing {
id: Id::Range(_),
..
}) => Ok(DbResponse::Other(Value::Array(Default::default()))),
Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())), Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())),
Value::Table(..) | Value::Array(..) | Value::Range(..) => {
Ok(DbResponse::Other(Value::Array(Default::default())))
}
_ => unreachable!(), _ => unreachable!(),
}, },
Command::Upsert { Command::Upsert {
@ -83,10 +88,13 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
what, what,
.. ..
} => match what { } => match what {
Value::Table(..)
| Value::Array(..)
| Value::Thing(Thing {
id: Id::Range(_),
..
}) => Ok(DbResponse::Other(Value::Array(Default::default()))),
Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())), Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())),
Value::Table(..) | Value::Array(..) | Value::Range(..) => {
Ok(DbResponse::Other(Value::Array(Default::default())))
}
_ => unreachable!(), _ => unreachable!(),
}, },
Command::Insert { Command::Insert {

View file

@ -9,6 +9,7 @@ use crate::api::opt::Resource;
use crate::api::Connection; use crate::api::Connection;
use crate::api::Result; use crate::api::Result;
use crate::method::OnceLockExt; use crate::method::OnceLockExt;
use crate::sql::to_value;
use crate::sql::Id; use crate::sql::Id;
use crate::sql::Value; use crate::sql::Value;
use crate::Surreal; use crate::Surreal;
@ -17,7 +18,6 @@ use serde::Serialize;
use std::borrow::Cow; use std::borrow::Cow;
use std::future::IntoFuture; use std::future::IntoFuture;
use std::marker::PhantomData; use std::marker::PhantomData;
use surrealdb_core::sql::to_value;
/// An update future /// An update future
#[derive(Debug)] #[derive(Debug)]

View file

@ -9,6 +9,7 @@ use crate::api::opt::Resource;
use crate::api::Connection; use crate::api::Connection;
use crate::api::Result; use crate::api::Result;
use crate::method::OnceLockExt; use crate::method::OnceLockExt;
use crate::sql::to_value;
use crate::sql::Id; use crate::sql::Id;
use crate::sql::Value; use crate::sql::Value;
use crate::Surreal; use crate::Surreal;
@ -17,7 +18,6 @@ use serde::Serialize;
use std::borrow::Cow; use std::borrow::Cow;
use std::future::IntoFuture; use std::future::IntoFuture;
use std::marker::PhantomData; use std::marker::PhantomData;
use surrealdb_core::sql::to_value;
/// An upsert future /// An upsert future
#[derive(Debug)] #[derive(Debug)]

View file

@ -19,9 +19,12 @@ pub enum Resource {
} }
impl Resource { impl Resource {
pub(crate) fn with_range(self, range: Range<Id>) -> Result<sql::Range> { pub(crate) fn with_range(self, range: Range<Id>) -> Result<sql::Thing> {
match self { match self {
Resource::Table(table) => Ok(sql::Range::new(table.0, range.start, range.end)), Resource::Table(table) => Ok(sql::Thing::from((
table.0,
sql::Id::Range(Box::new(sql::IdRange::try_from((range.start, range.end))?)),
))),
Resource::RecordId(record_id) => Err(Error::RangeOnRecordId(record_id).into()), Resource::RecordId(record_id) => Err(Error::RangeOnRecordId(record_id).into()),
Resource::Object(object) => Err(Error::RangeOnObject(object).into()), Resource::Object(object) => Err(Error::RangeOnObject(object).into()),
Resource::Array(array) => Err(Error::RangeOnArray(array).into()), Resource::Array(array) => Err(Error::RangeOnArray(array).into()),

View file

@ -62,3 +62,35 @@ async fn cast_to_record_table() -> Result<(), Error> {
// //
Ok(()) Ok(())
} }
#[tokio::test]
async fn cast_range_to_array() -> Result<(), Error> {
let sql = r#"
<array> 1..5;
<array> 1>..5;
<array> 1..=5;
<array> 1>..=5;
"#;
let dbs = new_ds().await?;
let ses = Session::owner().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 4);
//
let tmp = res.remove(0).result?;
let val = Value::parse("[1, 2, 3, 4]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("[2, 3, 4]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("[1, 2, 3, 4, 5]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("[2, 3, 4, 5]");
assert_eq!(tmp, val);
//
Ok(())
}

View file

@ -3,7 +3,23 @@ use helpers::Test;
use surrealdb::err::Error; use surrealdb::err::Error;
#[tokio::test] #[tokio::test]
async fn modulo() -> Result<(), Error> { async fn expr_modulo() -> Result<(), Error> {
Test::new("8 % 3").await?.expect_val("2")?; Test::new("8 % 3").await?.expect_val("2")?;
Ok(()) Ok(())
} }
#[tokio::test]
async fn expr_value_in_range() -> Result<(), Error> {
Test::new(
"
1 in 1..2;
'a' in 'a'..'b';
0 in 1..2;
",
)
.await?
.expect_val("true")?
.expect_val("true")?
.expect_val("false")?;
Ok(())
}

View file

@ -199,3 +199,36 @@ async fn foreach_nested() -> Result<(), Error> {
// //
Ok(()) Ok(())
} }
#[tokio::test]
async fn foreach_range() -> Result<(), Error> {
let sql = "
FOR $test IN 1..4 {
IF $test == 2 {
BREAK;
};
UPSERT type::thing('person', $test) SET test = $test;
};
SELECT * FROM person;
";
let dbs = new_ds().await?;
let ses = Session::owner().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 2);
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: person:1,
test: 1,
},
]",
);
assert_eq!(tmp, val);
//
Ok(())
}

View file

@ -5527,33 +5527,24 @@ async fn function_type_thing() -> Result<(), Error> {
#[tokio::test] #[tokio::test]
async fn function_type_range() -> Result<(), Error> { async fn function_type_range() -> Result<(), Error> {
let sql = r#" let sql = r#"
RETURN type::range('person'); RETURN type::range(..);
RETURN type::range('person',1); RETURN type::range(1..2);
RETURN type::range('person',null,10); RETURN type::range([1, 2]);
RETURN type::range('person',1,10);
RETURN type::range('person',1,10, { begin: "excluded", end: "included"});
"#; "#;
let mut test = Test::new(sql).await?; let mut test = Test::new(sql).await?;
// //
let tmp = test.next()?.result?; let tmp = test.next()?.result?;
let val = Value::parse("person:.."); let val = Value::parse("..");
assert_eq!(tmp, val); assert_eq!(tmp, val);
//
let tmp = test.next()?.result?; let tmp = test.next()?.result?;
let val = Value::parse("person:1.."); let val = Value::parse("1..2");
assert_eq!(tmp, val); assert_eq!(tmp, val);
//
let tmp = test.next()?.result?; let tmp = test.next()?.result?;
let val = Value::parse("person:..10"); let val = Value::parse("1..2");
assert_eq!(tmp, val);
let tmp = test.next()?.result?;
let val = Value::parse("person:1..10");
assert_eq!(tmp, val);
let tmp = test.next()?.result?;
let val = Value::parse("person:1>..=10");
assert_eq!(tmp, val); assert_eq!(tmp, val);
//
Ok(()) Ok(())
} }