diff --git a/core/src/cf/writer.rs b/core/src/cf/writer.rs index 2fc7a29c..1756845b 100644 --- a/core/src/cf/writer.rs +++ b/core/src/cf/writer.rs @@ -186,7 +186,7 @@ mod tests { let mut tx1 = ds.transaction(Write, Optimistic).await.unwrap().inner(); let thing_a = Thing { tb: TB.to_owned(), - id: Id::String("A".to_string()), + id: Id::from("A"), }; let value_a: Value = "a".into(); let previous = Value::None; @@ -205,7 +205,7 @@ mod tests { let mut tx2 = ds.transaction(Write, Optimistic).await.unwrap().inner(); let thing_c = Thing { tb: TB.to_owned(), - id: Id::String("C".to_string()), + id: Id::from("C"), }; let value_c: Value = "c".into(); tx2.record_change( @@ -223,7 +223,7 @@ mod tests { let mut tx3 = ds.transaction(Write, Optimistic).await.unwrap().inner(); let thing_b = Thing { tb: TB.to_owned(), - id: Id::String("B".to_string()), + id: Id::from("B"), }; let value_b: Value = "b".into(); tx3.record_change( @@ -237,7 +237,7 @@ mod tests { ); let thing_c2 = Thing { tb: TB.to_owned(), - id: Id::String("C".to_string()), + id: Id::from("C"), }; let value_c2: Value = "c2".into(); tx3.record_change( @@ -434,7 +434,7 @@ mod tests { ds.tick_at(ts.0.timestamp().try_into().unwrap()).await.unwrap(); let thing = Thing { tb: TB.to_owned(), - id: Id::String("A".to_string()), + id: Id::from("A"), }; let ses = Session::owner().with_ns(NS).with_db(DB); let res = @@ -531,7 +531,7 @@ mod tests { async fn record_change_feed_entry(tx: Transaction, id: String) -> Thing { let thing = Thing { tb: TB.to_owned(), - id: Id::String(id), + id: Id::from(id), }; let value_a: Value = "a".into(); let previous = Value::None.into(); diff --git a/core/src/cnf/mod.rs b/core/src/cnf/mod.rs index 2c51b1d5..c8cf3cd9 100644 --- a/core/src/cnf/mod.rs +++ b/core/src/cnf/mod.rs @@ -68,8 +68,8 @@ pub static EXPERIMENTAL_BEARER_ACCESS: Lazy = pub static EXPERIMENTAL_BEARER_ACCESS: Lazy = Lazy::new(|| true); /// Used to limit allocation for builtin functions -pub static FUNCTION_ALLOCATION_LIMIT: Lazy = once_cell::sync::Lazy::new(|| { - let n = std::env::var("SURREAL_FUNCTION_ALLOCATION_LIMIT") +pub static GENERATION_ALLOCATION_LIMIT: Lazy = once_cell::sync::Lazy::new(|| { + let n = std::env::var("SURREAL_GENERATION_ALLOCATION_LIMIT") .map(|s| s.parse::().unwrap_or(20)) .unwrap_or(20); 2usize.pow(n) diff --git a/core/src/dbs/iterator.rs b/core/src/dbs/iterator.rs index 1b5e96fa..53f2ac45 100644 --- a/core/src/dbs/iterator.rs +++ b/core/src/dbs/iterator.rs @@ -12,10 +12,10 @@ use crate::err::Error; use crate::idx::planner::iterators::{IteratorRecord, IteratorRef}; use crate::idx::planner::IterationStage; use crate::sql::edges::Edges; -use crate::sql::range::Range; use crate::sql::table::Table; use crate::sql::thing::Thing; use crate::sql::value::Value; +use crate::sql::{Id, IdRange}; use reblessive::tree::Stk; #[cfg(not(target_arch = "wasm32"))] use reblessive::TreeStack; @@ -27,7 +27,7 @@ pub(crate) enum Iterable { Value(Value), Table(Table), Thing(Thing), - Range(Range), + TableRange(String, IdRange), Edges(Edges), Defer(Thing), Mergeable(Thing, Value), @@ -152,14 +152,30 @@ impl Iterator { } } // Add the record to the iterator - match stm { - Statement::Create(_) => { - self.ingest(Iterable::Defer(v)); + 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())); + } + }; } _ => { - self.ingest(Iterable::Thing(v)); + match stm { + Statement::Create(_) => { + self.ingest(Iterable::Defer(v)); + } + _ => { + self.ingest(Iterable::Thing(v)); + } + }; } - }; + } } Value::Mock(v) => { // Check if there is a data clause @@ -176,25 +192,6 @@ impl Iterator { self.ingest(Iterable::Thing(v)) } } - Value::Range(v) => { - // Check if this is a create statement - if let Statement::Create(_) = stm { - return Err(Error::InvalidStatementTarget { - value: v.to_string(), - }); - } - // Check if there is a data clause - if let Some(data) = stm.data() { - // Check if there is an id field specified - if let Some(id) = data.rid(stk, ctx, opt).await? { - return Err(Error::IdMismatch { - value: id.to_string(), - }); - } - } - // Add the record to the iterator - self.ingest(Iterable::Range(*v)); - } Value::Edges(v) => { // Check if this is a create statement if let Statement::Create(_) = stm { diff --git a/core/src/dbs/plan.rs b/core/src/dbs/plan.rs index 7e858174..e0704531 100644 --- a/core/src/dbs/plan.rs +++ b/core/src/dbs/plan.rs @@ -105,9 +105,9 @@ impl ExplainItem { name: "Iterate Defer".into(), details: vec![("thing", Value::Thing(t.to_owned()))], }, - Iterable::Range(r) => Self { + Iterable::TableRange(tb, r) => Self { name: "Iterate Range".into(), - details: vec![("table", Value::from(r.tb.to_owned()))], + details: vec![("table", tb.to_owned().into()), ("range", r.to_owned().into())], }, Iterable::Edges(e) => Self { name: "Iterate Edges".into(), diff --git a/core/src/dbs/processor.rs b/core/src/dbs/processor.rs index ba75f0c4..c1b5b4f4 100644 --- a/core/src/dbs/processor.rs +++ b/core/src/dbs/processor.rs @@ -10,7 +10,8 @@ use crate::idx::planner::IterationStage; use crate::key::{graph, thing}; use crate::kvs::Transaction; use crate::sql::dir::Dir; -use crate::sql::{Edges, Range, Table, Thing, Value}; +use crate::sql::id::range::IdRange; +use crate::sql::{Edges, Table, Thing, Value}; #[cfg(not(target_arch = "wasm32"))] use channel::Sender; use futures::StreamExt; @@ -123,7 +124,9 @@ impl<'a> Processor<'a> { Iterable::Value(v) => self.process_value(stk, ctx, opt, stm, v).await?, Iterable::Thing(v) => self.process_thing(stk, ctx, opt, stm, v).await?, Iterable::Defer(v) => self.process_defer(stk, ctx, opt, stm, v).await?, - Iterable::Range(v) => self.process_range(stk, ctx, opt, stm, v).await?, + Iterable::TableRange(tb, v) => { + self.process_range(stk, ctx, opt, stm, tb, v).await? + } Iterable::Edges(e) => self.process_edge(stk, ctx, opt, stm, e).await?, Iterable::Table(v) => { if let Some(qp) = ctx.get_query_planner() { @@ -343,28 +346,29 @@ impl<'a> Processor<'a> { ctx: &Context, opt: &Options, stm: &Statement<'_>, - v: Range, + tb: String, + r: IdRange, ) -> Result<(), Error> { // Get the transaction let txn = ctx.tx(); // Check that the table exists - txn.check_ns_db_tb(opt.ns()?, opt.db()?, &v.tb, opt.strict).await?; + txn.check_ns_db_tb(opt.ns()?, opt.db()?, &tb, opt.strict).await?; // Prepare the range start key - let beg = match &v.beg { - Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, &v.tb), - Bound::Included(id) => thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(), - Bound::Excluded(id) => { - let mut key = thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(); + let beg = match &r.beg { + Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, &tb), + Bound::Included(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(), + Bound::Excluded(v) => { + let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(); key.push(0x00); key } }; // Prepare the range end key - let end = match &v.end { - Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, &v.tb), - Bound::Excluded(id) => thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(), - Bound::Included(id) => { - let mut key = thing::new(opt.ns()?, opt.db()?, &v.tb, id).encode().unwrap(); + let end = match &r.end { + Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, &tb), + Bound::Excluded(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(), + Bound::Included(v) => { + let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(); key.push(0x00); key } diff --git a/core/src/err/mod.rs b/core/src/err/mod.rs index 9c46a278..dfa2a20f 100644 --- a/core/src/err/mod.rs +++ b/core/src/err/mod.rs @@ -1081,6 +1081,19 @@ pub enum Error { #[doc(hidden)] #[error("The underlying datastore does not support versioned queries")] UnsupportedVersionedQueries, + + /// Found an unexpected value in a range + #[error("Expected a range value of '{expected}', but found '{found}'")] + InvalidRangeValue { + expected: String, + found: String, + }, + + /// Found an unexpected value in a range + #[error("The range cannot exceed a size of {max} for this operation")] + RangeTooBig { + max: usize, + }, } impl From for String { diff --git a/core/src/fnc/array.rs b/core/src/fnc/array.rs index 7635c14c..08e3323c 100644 --- a/core/src/fnc/array.rs +++ b/core/src/fnc/array.rs @@ -1,4 +1,4 @@ -use crate::cnf::FUNCTION_ALLOCATION_LIMIT; +use crate::cnf::GENERATION_ALLOCATION_LIMIT; use crate::ctx::Context; use crate::dbs::Options; use crate::doc::CursorDoc; @@ -25,10 +25,10 @@ use std::mem::size_of_val; /// Returns an error if an array of this length is too much to allocate. fn limit(name: &str, n: usize) -> Result<(), Error> { - if n > *FUNCTION_ALLOCATION_LIMIT { + if n > *GENERATION_ALLOCATION_LIMIT { Err(Error::InvalidArguments { name: name.to_owned(), - message: format!("Output must not exceed {} bytes.", *FUNCTION_ALLOCATION_LIMIT), + message: format!("Output must not exceed {} bytes.", *GENERATION_ALLOCATION_LIMIT), }) } else { Ok(()) diff --git a/core/src/fnc/mod.rs b/core/src/fnc/mod.rs index 441fadeb..dd447ba1 100644 --- a/core/src/fnc/mod.rs +++ b/core/src/fnc/mod.rs @@ -352,6 +352,7 @@ pub fn synchronous( "time::from::secs" => time::from::secs, "time::from::unix" => time::from::unix, // + "type::array" => r#type::array, "type::bool" => r#type::bool, "type::bytes" => r#type::bytes, "type::datetime" => r#type::datetime, @@ -687,6 +688,7 @@ pub async fn idiom( "is_string" => r#type::is::string, "is_uuid" => r#type::is::uuid, // + "to_array" => r#type::array, "to_bool" => r#type::bool, "to_bytes" => r#type::bytes, "to_datetime" => r#type::datetime, @@ -697,6 +699,7 @@ pub async fn idiom( "to_int" => r#type::int, "to_number" => r#type::number, "to_point" => r#type::point, + "to_range" => r#type::range, "to_record" => r#type::record, "to_string" => r#type::string, "to_uuid" => r#type::uuid, diff --git a/core/src/fnc/script/modules/surrealdb/functions/type.rs b/core/src/fnc/script/modules/surrealdb/functions/type.rs index 4a8a3c1d..0eb70b79 100644 --- a/core/src/fnc/script/modules/surrealdb/functions/type.rs +++ b/core/src/fnc/script/modules/surrealdb/functions/type.rs @@ -11,6 +11,7 @@ pub struct Package; impl_module_def!( Package, "type", + "array" => run, "bool" => run, "bytes" => run, "datetime" => run, diff --git a/core/src/fnc/string.rs b/core/src/fnc/string.rs index 7f4a7b45..208f15b3 100644 --- a/core/src/fnc/string.rs +++ b/core/src/fnc/string.rs @@ -1,4 +1,4 @@ -use crate::cnf::FUNCTION_ALLOCATION_LIMIT; +use crate::cnf::GENERATION_ALLOCATION_LIMIT; use crate::err::Error; use crate::fnc::util::string; use crate::sql::value::Value; @@ -6,10 +6,10 @@ use crate::sql::Regex; /// Returns `true` if a string of this length is too much to allocate. fn limit(name: &str, n: usize) -> Result<(), Error> { - if n > *FUNCTION_ALLOCATION_LIMIT { + if n > *GENERATION_ALLOCATION_LIMIT { Err(Error::InvalidArguments { name: name.to_owned(), - message: format!("Output must not exceed {} bytes.", *FUNCTION_ALLOCATION_LIMIT), + message: format!("Output must not exceed {} bytes.", *GENERATION_ALLOCATION_LIMIT), }) } else { Ok(()) diff --git a/core/src/fnc/type.rs b/core/src/fnc/type.rs index 15db070c..815766d3 100644 --- a/core/src/fnc/type.rs +++ b/core/src/fnc/type.rs @@ -1,5 +1,3 @@ -use std::ops::Bound; - use crate::ctx::Context; use crate::dbs::Options; use crate::doc::CursorDoc; @@ -7,10 +5,14 @@ use crate::err::Error; use crate::sql::table::Table; use crate::sql::thing::Thing; use crate::sql::value::Value; -use crate::sql::{Id, Kind, Range, Strand}; +use crate::sql::{Kind, Strand}; use crate::syn; use reblessive::tree::Stk; +pub fn array((val,): (Value,)) -> Result { + val.convert_to_array().map(Value::from) +} + pub fn bool((val,): (Value,)) -> Result { val.convert_to_bool().map(Value::from) } @@ -91,91 +93,8 @@ pub fn point((val,): (Value,)) -> Result { val.convert_to_point().map(Value::from) } -pub fn range(args: Vec) -> Result { - if args.len() > 4 || args.is_empty() { - return Err(Error::InvalidArguments { - name: "type::range".to_owned(), - message: "Expected atleast 1 and at most 4 arguments".to_owned(), - }); - } - let mut args = args.into_iter(); - - // Unwrap will never trigger since length is checked above. - let id = args.next().unwrap().as_string(); - let start = args.next().and_then(|x| match x { - Value::Thing(v) => Some(v.id), - Value::Array(v) => Some(v.into()), - Value::Object(v) => Some(v.into()), - Value::Number(v) => Some(v.into()), - Value::Null | Value::None => None, - v => Some(Id::from(v.as_string())), - }); - let end = args.next().and_then(|x| match x { - Value::Thing(v) => Some(v.id), - Value::Array(v) => Some(v.into()), - Value::Object(v) => Some(v.into()), - Value::Number(v) => Some(v.into()), - Value::Null | Value::None => None, - v => Some(Id::from(v.as_string())), - }); - let (begin, end) = if let Some(x) = args.next() { - let Value::Object(x) = x else { - return Err(Error::ConvertTo { - from: x, - into: "object".to_owned(), - }); - }; - let begin = if let Some(x) = x.get("begin") { - let start = start.ok_or_else(|| Error::InvalidArguments { - name: "type::range".to_string(), - message: "Can't define an inclusion for begin if there is no begin bound" - .to_string(), - })?; - match x { - Value::Strand(Strand(x)) if x == "included" => Bound::Included(start), - Value::Strand(Strand(x)) if x == "excluded" => Bound::Excluded(start), - x => { - return Err(Error::ConvertTo { - from: x.clone(), - into: r#""included" | "excluded""#.to_owned(), - }) - } - } - } else { - start.map(Bound::Included).unwrap_or(Bound::Unbounded) - }; - let end = if let Some(x) = x.get("end") { - let end = end.ok_or_else(|| Error::InvalidArguments { - name: "type::range".to_string(), - message: "Can't define an inclusion for end if there is no end bound".to_string(), - })?; - match x { - Value::Strand(Strand(x)) if x == "included" => Bound::Included(end), - Value::Strand(Strand(x)) if x == "excluded" => Bound::Excluded(end), - x => { - return Err(Error::ConvertTo { - from: x.clone(), - into: r#""included" | "excluded""#.to_owned(), - }) - } - } - } else { - end.map(Bound::Excluded).unwrap_or(Bound::Unbounded) - }; - (begin, end) - } else { - ( - start.map(Bound::Included).unwrap_or(Bound::Unbounded), - end.map(Bound::Excluded).unwrap_or(Bound::Unbounded), - ) - }; - - Ok(Range { - tb: id, - beg: begin, - end, - } - .into()) +pub fn range((val,): (Value,)) -> Result { + val.convert_to_range().map(Value::from) } pub fn record((rid, tb): (Value, Option)) -> Result { diff --git a/core/src/idx/planner/rewriter.rs b/core/src/idx/planner/rewriter.rs index 6d3ae807..0c812743 100644 --- a/core/src/idx/planner/rewriter.rs +++ b/core/src/idx/planner/rewriter.rs @@ -1,4 +1,5 @@ use crate::idx::planner::executor::KnnExpressions; +use crate::sql::id::range::IdRange; use crate::sql::part::DestructurePart; use crate::sql::{ Array, Cast, Cond, Expression, Function, Id, Idiom, Model, Object, Part, Range, Thing, Value, @@ -127,6 +128,7 @@ impl<'a> KnnConditionRewriter<'a> { Id::Number(_) | Id::String(_) | Id::Generate(_) => Some(id.clone()), Id::Array(a) => self.eval_array(a).map(Id::Array), Id::Object(o) => self.eval_object(o).map(Id::Object), + Id::Range(r) => self.eval_id_range(r).map(|v| Id::Range(Box::new(v))), } } @@ -178,7 +180,6 @@ impl<'a> KnnConditionRewriter<'a> { fn eval_range(&self, r: &Range) -> Option { if let Some(beg) = self.eval_bound(&r.beg) { self.eval_bound(&r.end).map(|end| Range { - tb: r.tb.clone(), beg, end, }) @@ -187,10 +188,29 @@ impl<'a> KnnConditionRewriter<'a> { } } - fn eval_bound(&self, b: &Bound) -> Option> { + fn eval_bound(&self, b: &Bound) -> Option> { match b { - Bound::Included(id) => self.eval_id(id).map(Bound::Included), - Bound::Excluded(id) => self.eval_id(id).map(Bound::Excluded), + Bound::Included(v) => self.eval_value(v).map(Bound::Included), + Bound::Excluded(v) => self.eval_value(v).map(Bound::Excluded), + Bound::Unbounded => Some(Bound::Unbounded), + } + } + + fn eval_id_range(&self, r: &IdRange) -> Option { + 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) -> Option> { + 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), } } diff --git a/core/src/key/thing/mod.rs b/core/src/key/thing/mod.rs index 96285b52..d7145a6c 100644 --- a/core/src/key/thing/mod.rs +++ b/core/src/key/thing/mod.rs @@ -1,7 +1,7 @@ //! Stores a record document use crate::key::category::Categorise; use crate::key::category::Category; -use crate::sql::id::Id; +use crate::sql::Id; use derive::Key; use serde::{Deserialize, Serialize}; diff --git a/core/src/rpc/format/cbor/convert.rs b/core/src/rpc/format/cbor/convert.rs index 65324a3c..d9827b8f 100644 --- a/core/src/rpc/format/cbor/convert.rs +++ b/core/src/rpc/format/cbor/convert.rs @@ -2,14 +2,20 @@ use ciborium::Value as Data; use geo::{LineString, Point, Polygon}; use geo_types::{MultiLineString, MultiPoint, MultiPolygon}; use rust_decimal::Decimal; +use std::collections::BTreeMap; use std::iter::once; +use std::ops::Bound; use std::ops::Deref; +use crate::sql::id::range::IdRange; +use crate::sql::Array; use crate::sql::Datetime; use crate::sql::Duration; use crate::sql::Geometry; use crate::sql::Id; use crate::sql::Number; +use crate::sql::Object; +use crate::sql::Range; use crate::sql::Thing; use crate::sql::Uuid; use crate::sql::Value; @@ -30,6 +36,11 @@ const TAG_CUSTOM_DATETIME: u64 = 12; const TAG_STRING_DURATION: u64 = 13; const TAG_CUSTOM_DURATION: u64 = 14; +// Ranges +const TAG_RANGE: u64 = 49; +const TAG_BOUND_INCLUDED: u64 = 50; +const TAG_BOUND_EXCLUDED: u64 = 51; + // Custom Geometries const TAG_GEOMETRY_POINT: u64 = 88; const TAG_GEOMETRY_LINE: u64 = 89; @@ -52,17 +63,8 @@ impl TryFrom for Value { Data::Float(v) => Ok(Value::from(v)), Data::Bytes(v) => Ok(Value::Bytes(v.into())), Data::Text(v) => Ok(Value::from(v)), - Data::Array(v) => { - v.into_iter().map(|v| Value::try_from(Cbor(v))).collect::>() - } - 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::>(), + Data::Array(v) => Ok(Value::Array(Array::try_from(v)?)), + Data::Map(v) => Ok(Value::Object(Object::try_from(v)?)), Data::Tag(t, v) => { match t { // A literal datetime @@ -175,21 +177,12 @@ impl TryFrom for Value { ), }; - match Value::try_from(Cbor(v.remove(0))) { - Ok(Value::Strand(id)) => { - Ok(Value::from(Thing::from((tb, Id::from(id))))) - } - Ok(Value::Number(Number::Int(id))) => { - Ok(Value::from(Thing::from((tb, Id::from(id))))) - } - Ok(Value::Array(id)) => { - Ok(Value::from(Thing::from((tb, Id::from(id))))) - } - Ok(Value::Object(id)) => { - Ok(Value::from(Thing::from((tb, Id::from(id))))) - } - _ => Err("Expected the id of a Record Id to be a String, Integer, Array or Object value"), - } + let id = Id::try_from(v.remove(0))?; + + Ok(Value::Thing(Thing { + tb, + id, + })) } _ => Err("Expected a CBOR text data type, or a CBOR array with 2 elements"), }, @@ -198,6 +191,8 @@ impl TryFrom for Value { Data::Text(v) => Ok(Value::Table(v.into())), _ => Err("Expected a CBOR text data type"), }, + // A range + TAG_RANGE => Ok(Value::Range(Box::new(Range::try_from(*v)?))), TAG_GEOMETRY_POINT => match *v { Data::Array(mut v) if v.len() == 2 => { let x = Value::try_from(Cbor(v.remove(0)))?; @@ -393,11 +388,13 @@ impl TryFrom for Cbor { Id::Generate(_) => { return Err("Cannot encode an ungenerated Record ID into CBOR") } + Id::Range(v) => Data::try_from(*v)?, }, ])), ))), Value::Table(v) => Ok(Cbor(Data::Tag(TAG_TABLE, Box::new(Data::Text(v.0))))), Value::Geometry(v) => Ok(Cbor(encode_geometry(v)?)), + Value::Range(v) => Ok(Cbor(Data::try_from(*v)?)), // We shouldn't reach here _ => Err("Found unsupported SurrealQL value being encoded into a CBOR value"), } @@ -460,3 +457,136 @@ fn encode_geometry(v: Geometry) -> Result { } } } + +impl TryFrom for Range { + type Error = &'static str; + fn try_from(val: Data) -> Result { + fn decode_bound(v: Data) -> Result, &'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 for Data { + type Error = &'static str; + fn try_from(r: Range) -> Result { + fn encode(b: Bound) -> Result { + 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 for IdRange { + type Error = &'static str; + fn try_from(val: Data) -> Result { + fn decode_bound(v: Data) -> Result, &'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 for Data { + type Error = &'static str; + fn try_from(r: IdRange) -> Result { + fn encode(b: Bound) -> Result { + 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 for Id { + type Error = &'static str; + fn try_from(val: Data) -> Result { + 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 for Data { + type Error = &'static str; + fn try_from(v: Id) -> Result { + 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> for Array { + type Error = &'static str; + fn try_from(val: Vec) -> Result { + val.into_iter().map(|v| Value::try_from(Cbor(v))).collect::>() + } +} + +impl TryFrom> for Object { + type Error = &'static str; + fn try_from(val: Vec<(Data, Data)>) -> Result { + 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::, &str>>()?, + )) + } +} diff --git a/core/src/rpc/rpc_context.rs b/core/src/rpc/rpc_context.rs index e1b49c83..ae577793 100644 --- a/core/src/rpc/rpc_context.rs +++ b/core/src/rpc/rpc_context.rs @@ -267,7 +267,7 @@ pub trait RpcContext { return Err(RpcError::InvalidParams); }; // Return a single result? - let one = what.is_thing(); + let one = what.is_thing_single(); // Specify the SQL query string let sql = "SELECT * FROM $what"; // Specify the query parameters @@ -295,7 +295,7 @@ pub trait RpcContext { return Err(RpcError::InvalidParams); }; // Return a single result? - let one = what.is_thing(); + let one = what.is_thing_single(); // Specify the SQL query string let sql = "INSERT INTO $what $data RETURN AFTER"; // Specify the query parameters @@ -324,7 +324,7 @@ pub trait RpcContext { return Err(RpcError::InvalidParams); }; // Return a single result? - let one = what.is_thing(); + let one = what.is_thing_single(); // Specify the SQL query string let sql = if data.is_none_or_null() { "CREATE $what RETURN AFTER" @@ -357,7 +357,7 @@ pub trait RpcContext { return Err(RpcError::InvalidParams); }; // Return a single result? - let one = what.is_thing(); + let one = what.is_thing_single(); // Specify the SQL query string let sql = if data.is_none_or_null() { "UPSERT $what RETURN AFTER" @@ -390,7 +390,7 @@ pub trait RpcContext { return Err(RpcError::InvalidParams); }; // Return a single result? - let one = what.is_thing(); + let one = what.is_thing_single(); // Specify the SQL query string let sql = if data.is_none_or_null() { "UPDATE $what RETURN AFTER" @@ -423,7 +423,7 @@ pub trait RpcContext { return Err(RpcError::InvalidParams); }; // Return a single result? - let one = what.is_thing(); + let one = what.is_thing_single(); // Specify the SQL query string let sql = if data.is_none_or_null() { "UPDATE $what RETURN AFTER" @@ -456,7 +456,7 @@ pub trait RpcContext { return Err(RpcError::InvalidParams); }; // Return a single result? - let one = what.is_thing(); + let one = what.is_thing_single(); // Specify the SQL query string let sql = match diff.is_true() { true => "UPDATE $what PATCH $data RETURN DIFF", @@ -488,7 +488,7 @@ pub trait RpcContext { return Err(RpcError::InvalidParams); }; // Return a single result? - let one = kind.is_thing(); + let one = kind.is_thing_single(); // Specify the SQL query string let sql = if data.is_none_or_null() { "RELATE $from->$kind->$to" @@ -523,7 +523,7 @@ pub trait RpcContext { return Err(RpcError::InvalidParams); }; // Return a single result? - let one = what.is_thing(); + let one = what.is_thing_single(); // Specify the SQL query string let sql = "DELETE $what RETURN BEFORE"; // Specify the query parameters diff --git a/core/src/sql/id.rs b/core/src/sql/id/mod.rs similarity index 78% rename from core/src/sql/id.rs rename to core/src/sql/id/mod.rs index ce6cf11c..229c3508 100644 --- a/core/src/sql/id.rs +++ b/core/src/sql/id/mod.rs @@ -1,3 +1,4 @@ +use super::Range; use crate::cnf::ID_CHARS; use crate::ctx::Context; use crate::dbs::Options; @@ -6,13 +7,17 @@ use crate::err::Error; use crate::sql::{escape::escape_rid, Array, Number, Object, Strand, Thing, Uuid, Value}; use derive::Key; use nanoid::nanoid; +use range::IdRange; use reblessive::tree::Stk; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::fmt::{self, Display, Formatter}; +use std::ops::{Bound, Deref}; use ulid::Ulid; +pub mod range; + #[revisioned(revision = 1)] #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] @@ -33,6 +38,7 @@ pub enum Id { Array(Array), Object(Object), Generate(Gen), + Range(Box), } impl From for Id { @@ -97,25 +103,25 @@ impl From<&String> for Id { impl From> for Id { fn from(v: Vec<&str>) -> Self { - Id::Array(v.into()) + Self::Array(v.into()) } } impl From> for Id { fn from(v: Vec) -> Self { - Id::Array(v.into()) + Self::Array(v.into()) } } impl From> for Id { fn from(v: Vec) -> Self { - Id::Array(v.into()) + Self::Array(v.into()) } } impl From> for Id { fn from(v: BTreeMap) -> Self { - Id::Object(v.into()) + Self::Object(v.into()) } } @@ -129,6 +135,42 @@ impl From for Id { } } +impl From for Id { + fn from(v: IdRange) -> Self { + Self::Range(Box::new(v)) + } +} + +impl TryFrom<(Bound, Bound)> for Id { + type Error = Error; + fn try_from(v: (Bound, Bound)) -> Result { + Ok(Self::Range(Box::new(v.try_into()?))) + } +} + +impl TryFrom for Id { + type Error = Error; + fn try_from(v: Range) -> Result { + Ok(Id::Range(Box::new(v.try_into()?))) + } +} + +impl TryFrom for Id { + type Error = Error; + fn try_from(v: Value) -> Result { + 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 for Id { fn from(v: Thing) -> Self { v.id @@ -160,6 +202,7 @@ impl Id { Gen::Ulid => "ulid()".to_string(), Gen::Uuid => "uuid()".to_string(), }, + Self::Range(v) => v.to_string(), } } } @@ -176,6 +219,7 @@ impl Display for Id { Gen::Ulid => Display::fmt("ulid()", f), Gen::Uuid => Display::fmt("uuid()", f), }, + Self::Range(v) => Display::fmt(v, f), } } } @@ -205,6 +249,7 @@ impl Id { Gen::Ulid => Ok(Self::ulid()), Gen::Uuid => Ok(Self::uuid()), }, + Id::Range(v) => Ok(Id::Range(Box::new(v.compute(stk, ctx, opt, doc).await?))), } } } diff --git a/core/src/sql/id/range.rs b/core/src/sql/id/range.rs new file mode 100644 index 00000000..482b8d78 --- /dev/null +++ b/core/src/sql/id/range.rs @@ -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, + pub end: Bound, +} + +impl TryFrom<(Bound, Bound)> for IdRange { + type Error = Error; + fn try_from((beg, end): (Bound, Bound)) -> Result { + 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 for IdRange { + type Error = Error; + fn try_from(v: Range) -> Result { + 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 for IdRange { + type Error = Error; + fn try_from(v: Value) -> Result { + 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 { + 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 { + 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)) + } +} diff --git a/core/src/sql/kind.rs b/core/src/sql/kind.rs index b57e1ab2..dba8ff1e 100644 --- a/core/src/sql/kind.rs +++ b/core/src/sql/kind.rs @@ -30,6 +30,7 @@ pub enum Kind { Set(Box, Option), Array(Box, Option), Function(Option>, Option>), + Range, } impl Default for Kind { @@ -73,7 +74,8 @@ impl Kind { | Kind::Uuid | Kind::Record(_) | Kind::Geometry(_) - | Kind::Function(_, _) => return None, + | Kind::Function(_, _) + | Kind::Range => return None, Kind::Option(x) => { this = x; } @@ -137,6 +139,7 @@ impl Display for Kind { (k, Some(l)) => write!(f, "array<{k}, {l}>"), }, Kind::Either(k) => write!(f, "{}", Fmt::verbar_separated(k)), + Kind::Range => f.write_str("range"), } } } diff --git a/core/src/sql/mod.rs b/core/src/sql/mod.rs index 6cda0127..1fcd6dc5 100644 --- a/core/src/sql/mod.rs +++ b/core/src/sql/mod.rs @@ -110,6 +110,7 @@ pub use self::geometry::Geometry; pub use self::graph::Graph; pub use self::group::Group; pub use self::group::Groups; +pub use self::id::range::IdRange; pub use self::id::Id; pub use self::ident::Ident; pub use self::idiom::Idiom; diff --git a/core/src/sql/range.rs b/core/src/sql/range.rs index d11ec3ad..07a67732 100644 --- a/core/src/sql/range.rs +++ b/core/src/sql/range.rs @@ -1,15 +1,9 @@ +use crate::cnf::GENERATION_ALLOCATION_LIMIT; use crate::ctx::Context; use crate::dbs::Options; use crate::doc::CursorDoc; use crate::err::Error; -use crate::sql::Cond; -use crate::sql::Expression; -use crate::sql::Ident; -use crate::sql::Idiom; -use crate::sql::Operator; -use crate::sql::Part; -use crate::sql::Thing; -use crate::sql::{strand::no_nul_bytes, Id, Value}; +use crate::sql::{Number, Subquery, Value}; use crate::syn; use reblessive::tree::Stk; use revision::revisioned; @@ -19,7 +13,8 @@ use std::fmt; use std::ops::Bound; use std::str::FromStr; -const ID: &str = "id"; +use super::Id; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range"; #[revisioned(revision = 1)] @@ -28,10 +23,8 @@ pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range"; #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[non_exhaustive] pub struct Range { - #[serde(with = "no_nul_bytes")] - pub tb: String, - pub beg: Bound, - pub end: Bound, + pub beg: Bound, + pub end: Bound, } impl FromStr for Range { @@ -51,108 +44,54 @@ impl TryFrom<&str> for Range { } } -impl Range { - /// Construct a new range - pub fn new(tb: String, beg: Bound, end: Bound) -> Self { +impl From<(Bound, Bound)> for Range { + fn from(v: (Bound, Bound)) -> Self { + fn convert(v: Bound) -> Bound { + match v { + Bound::Included(v) => Bound::Included(v.into()), + Bound::Excluded(v) => Bound::Excluded(v.into()), + Bound::Unbounded => Bound::Unbounded, + } + } + Self { - tb, - beg, - end, + beg: convert(v.0), + end: convert(v.1), } } +} - /// Convert `Range` to `Cond` - pub fn to_cond(self) -> Option { - match (self.beg, self.end) { - (Bound::Unbounded, Bound::Unbounded) => None, - (Bound::Unbounded, Bound::Excluded(id)) => { - Some(Cond(Value::Expression(Box::new(Expression::new( - Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), - Operator::LessThan, - Thing::from((self.tb, id)).into(), - ))))) - } - (Bound::Unbounded, Bound::Included(id)) => { - Some(Cond(Value::Expression(Box::new(Expression::new( - Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), - Operator::LessThanOrEqual, - Thing::from((self.tb, id)).into(), - ))))) - } - (Bound::Excluded(id), Bound::Unbounded) => { - Some(Cond(Value::Expression(Box::new(Expression::new( - Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), - Operator::MoreThan, - Thing::from((self.tb, id)).into(), - ))))) - } - (Bound::Included(id), Bound::Unbounded) => { - Some(Cond(Value::Expression(Box::new(Expression::new( - Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), - Operator::MoreThanOrEqual, - Thing::from((self.tb, id)).into(), - ))))) - } - (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 TryInto> for Range { + type Error = Error; + fn try_into(self) -> Result, 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) + } + } +} + +impl Range { + /// Construct a new range + pub fn new(beg: Bound, end: Bound) -> Self { + Self { + beg, + end, } } @@ -165,15 +104,14 @@ impl Range { doc: Option<&CursorDoc>, ) -> Result { Ok(Value::Range(Box::new(Range { - tb: self.tb.clone(), beg: match &self.beg { - Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?), - Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?), + Bound::Included(v) => Bound::Included(v.compute(stk, ctx, opt, doc).await?), + Bound::Excluded(v) => Bound::Excluded(v.compute(stk, ctx, opt, doc).await?), Bound::Unbounded => Bound::Unbounded, }, end: match &self.end { - Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?), - Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?), + Bound::Included(v) => Bound::Included(v.compute(stk, ctx, opt, doc).await?), + Bound::Excluded(v) => Bound::Excluded(v.compute(stk, ctx, opt, doc).await?), Bound::Unbounded => Bound::Unbounded, }, }))) @@ -182,74 +120,94 @@ impl Range { impl PartialOrd for Range { fn partial_cmp(&self, other: &Self) -> Option { - match self.tb.partial_cmp(&other.tb) { - Some(Ordering::Equal) => match &self.beg { - Bound::Unbounded => match &other.beg { - Bound::Unbounded => Some(Ordering::Equal), - _ => Some(Ordering::Less), - }, - Bound::Included(v) => match &other.beg { - Bound::Unbounded => Some(Ordering::Greater), - Bound::Included(w) => match v.partial_cmp(w) { - Some(Ordering::Equal) => match &self.end { - Bound::Unbounded => match &other.end { - Bound::Unbounded => Some(Ordering::Equal), - _ => Some(Ordering::Greater), - }, - Bound::Included(v) => match &other.end { - Bound::Unbounded => Some(Ordering::Less), - Bound::Included(w) => v.partial_cmp(w), - _ => Some(Ordering::Greater), - }, - Bound::Excluded(v) => match &other.end { - Bound::Excluded(w) => v.partial_cmp(w), - _ => Some(Ordering::Less), - }, - }, - ordering => ordering, - }, - _ => Some(Ordering::Less), - }, - Bound::Excluded(v) => match &other.beg { - Bound::Excluded(w) => match v.partial_cmp(w) { - Some(Ordering::Equal) => match &self.end { - Bound::Unbounded => match &other.end { - Bound::Unbounded => Some(Ordering::Equal), - _ => Some(Ordering::Greater), - }, - Bound::Included(v) => match &other.end { - Bound::Unbounded => Some(Ordering::Less), - Bound::Included(w) => v.partial_cmp(w), - _ => Some(Ordering::Greater), - }, - Bound::Excluded(v) => match &other.end { - Bound::Excluded(w) => v.partial_cmp(w), - _ => Some(Ordering::Less), - }, - }, - ordering => ordering, - }, - _ => Some(Ordering::Greater), - }, + Some(self.cmp(other)) + } +} + +impl Ord for Range { + 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, }, - ordering => ordering, } } } impl fmt::Display for Range { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}:", self.tb)?; + fn bound_value(v: &Value) -> Value { + if v.can_be_range_bound() { + v.to_owned() + } else { + Value::Subquery(Box::new(Subquery::Value(v.to_owned()))) + } + } + match &self.beg { Bound::Unbounded => write!(f, ""), - Bound::Included(id) => write!(f, "{id}"), - Bound::Excluded(id) => write!(f, "{id}>"), + Bound::Included(v) => write!(f, "{}", bound_value(v)), + Bound::Excluded(v) => write!(f, "{}>", bound_value(v)), }?; match &self.end { Bound::Unbounded => write!(f, ".."), - Bound::Excluded(id) => write!(f, "..{id}"), - Bound::Included(id) => write!(f, "..={id}"), + Bound::Excluded(v) => write!(f, "..{}", bound_value(v)), + Bound::Included(v) => write!(f, "..={}", bound_value(v)), }?; Ok(()) } } + +fn to_i64(v: Value) -> Result { + match v { + Value::Number(Number::Int(v)) => Ok(v), + v => Err(Error::InvalidRangeValue { + expected: "int".to_string(), + found: v.kindof().to_string(), + }), + } +} diff --git a/core/src/sql/statements/alter/table.rs b/core/src/sql/statements/alter/table.rs index acfc25b1..ed8825ad 100644 --- a/core/src/sql/statements/alter/table.rs +++ b/core/src/sql/statements/alter/table.rs @@ -63,7 +63,7 @@ impl AlterTableStatement { dt.changefeed = *changefeed; } if let Some(ref comment) = &self.comment { - dt.comment = comment.clone(); + dt.comment.clone_from(comment); } if let Some(ref kind) = &self.kind { dt.kind = kind.clone(); diff --git a/core/src/sql/statements/foreach.rs b/core/src/sql/statements/foreach.rs index 85ae7268..1a09ff82 100644 --- a/core/src/sql/statements/foreach.rs +++ b/core/src/sql/statements/foreach.rs @@ -8,6 +8,7 @@ use reblessive::tree::Stk; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; +use std::ops::Deref; #[revisioned(revision = 1)] #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] @@ -19,6 +20,22 @@ pub struct ForeachStatement { pub block: Block, } +enum ForeachIter { + Array(std::vec::IntoIter), + Range(std::iter::Map, fn(i64) -> Value>), +} + +impl Iterator for ForeachIter { + type Item = Value; + + fn next(&mut self) -> Option { + match self { + ForeachIter::Array(iter) => iter.next(), + ForeachIter::Range(iter) => iter.next(), + } + } +} + impl ForeachStatement { /// Check if we require a writeable transaction pub(crate) fn writeable(&self) -> bool { @@ -35,70 +52,76 @@ impl ForeachStatement { doc: Option<&CursorDoc>, ) -> Result { // Check the loop data - match &self.range.compute(stk, ctx, opt, doc).await? { - Value::Array(arr) => { - // Loop over the values - 'foreach: for v in arr.iter() { - // Duplicate context - let ctx = MutableContext::new(ctx).freeze(); - // Set the current parameter - let key = self.param.0.to_raw(); - let val = stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await?; - let mut ctx = MutableContext::unfreeze(ctx)?; - ctx.add_value(key, val.into()); - let mut ctx = ctx.freeze(); - // Loop over the code block statements - for v in self.block.iter() { - // Compute each block entry - let res = match v { - Entry::Set(v) => { - let val = stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await?; - let mut c = MutableContext::unfreeze(ctx)?; - c.add_value(v.name.to_owned(), val.into()); - ctx = c.freeze(); - Ok(Value::None) - } - Entry::Value(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, - Entry::Break(v) => v.compute(&ctx, opt, doc).await, - Entry::Continue(v) => v.compute(&ctx, opt, doc).await, - Entry::Foreach(v) => { - stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await - } - Entry::Ifelse(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, - Entry::Select(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, - Entry::Create(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, - Entry::Upsert(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, - Entry::Update(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, - Entry::Delete(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, - Entry::Relate(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, - Entry::Insert(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, - Entry::Define(v) => v.compute(stk, &ctx, opt, doc).await, - Entry::Alter(v) => v.compute(stk, &ctx, opt, doc).await, - Entry::Rebuild(v) => v.compute(stk, &ctx, opt, doc).await, - Entry::Remove(v) => v.compute(&ctx, opt, doc).await, - Entry::Output(v) => { - return stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await; - } - Entry::Throw(v) => { - return stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await; - } - }; - // Catch any special errors - match res { - Err(Error::Continue) => continue 'foreach, - Err(Error::Break) => return Ok(Value::None), - Err(err) => return Err(err), - _ => (), - }; - } - } - // Ok all good - Ok(Value::None) + let data = self.range.compute(stk, ctx, opt, doc).await?; + let iter = match data { + Value::Array(arr) => ForeachIter::Array(arr.into_iter()), + Value::Range(r) => { + let r: std::ops::Range = r.deref().to_owned().try_into()?; + ForeachIter::Range(r.map(Value::from)) + } + + v => { + return Err(Error::InvalidStatementTarget { + value: v.to_string(), + }) + } + }; + + // Loop over the values + 'foreach: for v in iter { + // Duplicate context + let ctx = MutableContext::new(ctx).freeze(); + // Set the current parameter + let key = self.param.0.to_raw(); + let val = stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await?; + let mut ctx = MutableContext::unfreeze(ctx)?; + ctx.add_value(key, val.into()); + let mut ctx = ctx.freeze(); + // Loop over the code block statements + for v in self.block.iter() { + // Compute each block entry + let res = match v { + Entry::Set(v) => { + let val = stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await?; + let mut c = MutableContext::unfreeze(ctx)?; + c.add_value(v.name.to_owned(), val.into()); + ctx = c.freeze(); + Ok(Value::None) + } + Entry::Value(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Break(v) => v.compute(&ctx, opt, doc).await, + Entry::Continue(v) => v.compute(&ctx, opt, doc).await, + Entry::Foreach(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Ifelse(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Select(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Create(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Upsert(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Update(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Delete(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Relate(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Insert(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, + Entry::Define(v) => v.compute(stk, &ctx, opt, doc).await, + Entry::Alter(v) => v.compute(stk, &ctx, opt, doc).await, + Entry::Rebuild(v) => v.compute(stk, &ctx, opt, doc).await, + Entry::Remove(v) => v.compute(&ctx, opt, doc).await, + Entry::Output(v) => { + return stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await; + } + Entry::Throw(v) => { + return stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await; + } + }; + // Catch any special errors + match res { + Err(Error::Continue) => continue 'foreach, + Err(Error::Break) => return Ok(Value::None), + Err(err) => return Err(err), + _ => (), + }; } - v => Err(Error::InvalidStatementTarget { - value: v.to_string(), - }), } + // Ok all good + Ok(Value::None) } } diff --git a/core/src/sql/statements/select.rs b/core/src/sql/statements/select.rs index 8cb9af16..51e8068d 100644 --- a/core/src/sql/statements/select.rs +++ b/core/src/sql/statements/select.rs @@ -4,8 +4,8 @@ use crate::doc::CursorDoc; use crate::err::Error; use crate::idx::planner::QueryPlanner; use crate::sql::{ - Cond, Explain, Fetchs, Field, Fields, Groups, Idioms, Limit, Orders, Splits, Start, Timeout, - Value, Values, Version, With, + Cond, Explain, Fetchs, Field, Fields, Groups, Id, Idioms, Limit, Orders, Splits, Start, + Timeout, Value, Values, Version, With, }; use derive::Store; use reblessive::tree::Stk; @@ -101,14 +101,10 @@ impl SelectStatement { planner.add_iterables(stk, ctx, t, &mut i).await?; } - Value::Thing(v) => i.ingest(Iterable::Thing(v)), - Value::Range(v) => { - if self.only && !limit_is_one_or_zero { - return Err(Error::SingleOnlyOutput); - } - - i.ingest(Iterable::Range(*v)) - } + Value::Thing(v) => match &v.id { + Id::Range(r) => i.ingest(Iterable::TableRange(v.tb, *r.to_owned())), + _ => i.ingest(Iterable::Thing(v)), + }, Value::Edges(v) => { if self.only && !limit_is_one_or_zero { return Err(Error::SingleOnlyOutput); diff --git a/core/src/sql/thing.rs b/core/src/sql/thing.rs index ca16db48..317c66b3 100644 --- a/core/src/sql/thing.rs +++ b/core/src/sql/thing.rs @@ -1,3 +1,5 @@ +use super::id::range::IdRange; +use super::{Cond, Expression, Ident, Idiom, Operator, Part, Table}; use crate::ctx::Context; use crate::dbs::Options; use crate::doc::CursorDoc; @@ -9,10 +11,10 @@ use reblessive::tree::Stk; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt; +use std::ops::Bound; use std::str::FromStr; -use super::Table; - +const ID: &str = "id"; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing"; #[revisioned(revision = 1)] @@ -26,6 +28,110 @@ pub struct Thing { pub id: Id, } +impl Thing { + /// Convert `Thing` to `Cond` + pub fn to_cond(self) -> Option { + match &self.id { + Id::Range(r) => match (&r.beg, &r.end) { + (Bound::Unbounded, Bound::Unbounded) => None, + (Bound::Unbounded, Bound::Excluded(id)) => { + Some(Cond(Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::LessThan, + Thing::from((self.tb, id.to_owned())).into(), + ))))) + } + (Bound::Unbounded, Bound::Included(id)) => { + Some(Cond(Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::LessThanOrEqual, + Thing::from((self.tb, id.to_owned())).into(), + ))))) + } + (Bound::Excluded(id), Bound::Unbounded) => { + Some(Cond(Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::MoreThan, + Thing::from((self.tb, id.to_owned())).into(), + ))))) + } + (Bound::Included(id), Bound::Unbounded) => { + Some(Cond(Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::MoreThanOrEqual, + Thing::from((self.tb, id.to_owned())).into(), + ))))) + } + (Bound::Included(lid), Bound::Included(rid)) => { + Some(Cond(Value::Expression(Box::new(Expression::new( + Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::MoreThanOrEqual, + Thing::from((self.tb.clone(), lid.to_owned())).into(), + ))), + Operator::And, + Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::LessThanOrEqual, + Thing::from((self.tb, rid.to_owned())).into(), + ))), + ))))) + } + (Bound::Included(lid), Bound::Excluded(rid)) => { + Some(Cond(Value::Expression(Box::new(Expression::new( + Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::MoreThanOrEqual, + Thing::from((self.tb.clone(), lid.to_owned())).into(), + ))), + Operator::And, + Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::LessThan, + Thing::from((self.tb, rid.to_owned())).into(), + ))), + ))))) + } + (Bound::Excluded(lid), Bound::Included(rid)) => { + Some(Cond(Value::Expression(Box::new(Expression::new( + Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::MoreThan, + Thing::from((self.tb.clone(), lid.to_owned())).into(), + ))), + Operator::And, + Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::LessThanOrEqual, + Thing::from((self.tb, rid.to_owned())).into(), + ))), + ))))) + } + (Bound::Excluded(lid), Bound::Excluded(rid)) => { + Some(Cond(Value::Expression(Box::new(Expression::new( + Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::MoreThan, + Thing::from((self.tb.clone(), lid.to_owned())).into(), + ))), + Operator::And, + Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::LessThan, + Thing::from((self.tb, rid.to_owned())).into(), + ))), + ))))) + } + }, + _ => Some(Cond(Value::Expression(Box::new(Expression::new( + Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(), + Operator::Equal, + Thing::from((self.tb, self.id)).into(), + ))))), + } + } +} + impl From<(&str, Id)> for Thing { fn from((tb, id): (&str, Id)) -> Self { Self { @@ -44,6 +150,24 @@ impl From<(String, Id)> for Thing { } } +impl From<(&str, IdRange)> for Thing { + fn from((tb, id): (&str, IdRange)) -> Self { + Self { + tb: tb.to_owned(), + id: id.into(), + } + } +} + +impl From<(String, IdRange)> for Thing { + fn from((tb, id): (String, IdRange)) -> Self { + Self { + tb, + id: id.into(), + } + } +} + impl From<(String, String)> for Thing { fn from((tb, id): (String, String)) -> Self { Self::from((tb, Id::from(id))) diff --git a/core/src/sql/value/serde/ser/mod.rs b/core/src/sql/value/serde/ser/mod.rs index dd6ec46c..d194725c 100644 --- a/core/src/sql/value/serde/ser/mod.rs +++ b/core/src/sql/value/serde/ser/mod.rs @@ -388,7 +388,6 @@ mod tests { #[test] fn range() { let range = Box::new(Range { - tb: "foo".to_owned(), beg: Bound::Included("foo".into()), end: Bound::Unbounded, }); diff --git a/core/src/sql/value/value.rs b/core/src/sql/value/value.rs index e5f6ce95..fa16857b 100644 --- a/core/src/sql/value/value.rs +++ b/core/src/sql/value/value.rs @@ -5,6 +5,7 @@ use crate::dbs::Options; use crate::doc::CursorDoc; use crate::err::Error; use crate::fnc::util::string::fuzzy::Fuzzy; +use crate::sql::id::range::IdRange; use crate::sql::statements::info::InfoStructure; use crate::sql::Closure; use crate::sql::{ @@ -28,7 +29,7 @@ use std::cmp::Ordering; use std::collections::BTreeMap; use std::collections::HashMap; use std::fmt::{self, Display, Formatter, Write}; -use std::ops::Deref; +use std::ops::{Bound, Deref}; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Value"; @@ -263,6 +264,12 @@ impl From for Value { } } +impl From> for Value { + fn from(v: Box) -> Self { + Value::Range(v) + } +} + impl From for Value { fn from(v: Edges) -> Self { Value::Edges(Box::new(v)) @@ -566,6 +573,27 @@ impl From> for Value { } } +impl From 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 for Value { fn from(v: Id) -> Self { match v { @@ -578,6 +606,7 @@ impl From for Value { Gen::Ulid => Id::ulid().into(), Gen::Uuid => Id::uuid().into(), }, + Id::Range(v) => v.deref().to_owned().into(), } } } @@ -923,6 +952,25 @@ impl Value { matches!(self, Value::Thing(_)) } + /// Check if this Value is a single Thing + pub fn is_thing_single(&self) -> bool { + match self { + Value::Thing(t) => !matches!(t.id, Id::Range(_)), + _ => false, + } + } + + /// Check if this Value is a single Thing + pub fn is_thing_range(&self) -> bool { + matches!( + self, + Value::Thing(Thing { + id: Id::Range(_), + .. + }) + ) + } + /// Check if this Value is a Mock pub fn is_mock(&self) -> bool { matches!(self, Value::Mock(_)) @@ -1223,6 +1271,7 @@ impl Value { Self::Geometry(Geometry::MultiPolygon(_)) => "geometry", Self::Geometry(Geometry::Collection(_)) => "geometry", Self::Bytes(_) => "bytes", + Self::Range(_) => "range", _ => "incorrect type", } } @@ -1249,6 +1298,7 @@ impl Value { Kind::Point => self.coerce_to_point().map(Value::from), Kind::Bytes => self.coerce_to_bytes().map(Value::from), Kind::Uuid => self.coerce_to_uuid().map(Value::from), + Kind::Range => self.coerce_to_range().map(Value::from), Kind::Function(_, _) => self.coerce_to_function().map(Value::from), Kind::Set(t, l) => match l { Some(l) => self.coerce_to_set_type_len(t, l).map(Value::from), @@ -1643,6 +1693,19 @@ impl Value { } } + /// Try to coerce this value to a `Range` + pub(crate) fn coerce_to_range(self) -> Result { + match self { + // Ranges are allowed + Value::Range(v) => Ok(*v), + // Anything else raises an error + _ => Err(Error::CoerceTo { + from: self, + into: "range".into(), + }), + } + } + /// Try to coerce this value to an `Geometry` point pub(crate) fn coerce_to_point(self) -> Result { match self { @@ -1818,6 +1881,7 @@ impl Value { Kind::Point => self.convert_to_point().map(Value::from), Kind::Bytes => self.convert_to_bytes().map(Value::from), Kind::Uuid => self.convert_to_uuid().map(Value::from), + Kind::Range => self.convert_to_range().map(Value::from), Kind::Function(_, _) => self.convert_to_function().map(Value::from), Kind::Set(t, l) => match l { Some(l) => self.convert_to_set_type_len(t, l).map(Value::from), @@ -2206,6 +2270,10 @@ impl Value { match self { // Arrays are allowed Value::Array(v) => Ok(v), + Value::Range(r) => { + let range: std::ops::Range = r.deref().to_owned().try_into()?; + Ok(range.into_iter().map(Value::from).collect::>().into()) + } // Anything else raises an error _ => Err(Error::ConvertTo { from: self, @@ -2214,6 +2282,26 @@ impl Value { } } + /// Try to convert this value to a `Range` + pub(crate) fn convert_to_range(self) -> Result { + match self { + // Arrays with two elements are allowed + Value::Array(v) if v.len() == 2 => { + let mut v = v; + Ok(Range { + beg: Bound::Included(v.remove(0)), + end: Bound::Excluded(v.remove(0)), + }) + } + Value::Range(r) => Ok(*r), + // Anything else raises an error + _ => Err(Error::ConvertTo { + from: self, + into: "range".into(), + }), + } + } + /// Try to convert this value to an `Geometry` point pub(crate) fn convert_to_point(self) -> Result { match self { @@ -2588,6 +2676,19 @@ impl Value { Value::Geometry(w) => v.contains(w), _ => false, }, + Value::Range(r) => { + let beg = match &r.beg { + Bound::Unbounded => true, + Bound::Included(beg) => beg.le(other), + Bound::Excluded(beg) => beg.lt(other), + }; + + beg && match &r.end { + Bound::Unbounded => true, + Bound::Included(end) => end.ge(other), + Bound::Excluded(end) => end.gt(other), + } + } _ => false, } } @@ -2654,6 +2755,25 @@ impl Value { _ => self.partial_cmp(other), } } + + pub fn can_be_range_bound(&self) -> bool { + matches!( + self, + Value::None + | Value::Null | Value::Array(_) + | Value::Block(_) + | Value::Bool(_) | Value::Datetime(_) + | Value::Duration(_) + | Value::Geometry(_) + | Value::Number(_) + | Value::Object(_) + | Value::Param(_) + | Value::Strand(_) + | Value::Subquery(_) + | Value::Table(_) + | Value::Uuid(_) + ) + } } impl fmt::Display for Value { diff --git a/core/src/syn/lexer/keywords.rs b/core/src/syn/lexer/keywords.rs index c2aaba97..82e3341d 100644 --- a/core/src/syn/lexer/keywords.rs +++ b/core/src/syn/lexer/keywords.rs @@ -183,6 +183,7 @@ pub(crate) static KEYWORDS: phf::Map, TokenKind> = phf_map UniCase::ascii("POSTINGS_ORDER") => TokenKind::Keyword(Keyword::PostingsOrder), UniCase::ascii("PRUNE") => TokenKind::Keyword(Keyword::Prune), UniCase::ascii("PUNCT") => TokenKind::Keyword(Keyword::Punct), + UniCase::ascii("RANGE") => TokenKind::Keyword(Keyword::Range), UniCase::ascii("READONLY") => TokenKind::Keyword(Keyword::Readonly), UniCase::ascii("RELATE") => TokenKind::Keyword(Keyword::Relate), UniCase::ascii("RELATION") => TokenKind::Keyword(Keyword::Relation), diff --git a/core/src/syn/parser/builtin.rs b/core/src/syn/parser/builtin.rs index 445207cb..474fa5c8 100644 --- a/core/src/syn/parser/builtin.rs +++ b/core/src/syn/parser/builtin.rs @@ -339,6 +339,7 @@ pub(crate) static PATHS: phf::Map, PathKind> = phf_map! { UniCase::ascii("time::from::secs") => PathKind::Function, UniCase::ascii("time::from::unix") => PathKind::Function, // + UniCase::ascii("type::array") => PathKind::Function, UniCase::ascii("type::bool") => PathKind::Function, UniCase::ascii("type::bytes") => PathKind::Function, UniCase::ascii("type::datetime") => PathKind::Function, diff --git a/core/src/syn/parser/idiom.rs b/core/src/syn/parser/idiom.rs index 97682a09..c6fb7a21 100644 --- a/core/src/syn/parser/idiom.rs +++ b/core/src/syn/parser/idiom.rs @@ -912,7 +912,7 @@ mod tests { Value::from(Idiom(vec![ Part::Start(Value::Thing(Thing { tb: "test".to_owned(), - id: Id::Number(1), + id: Id::from(1), })), Part::from("foo"), ])) @@ -928,7 +928,7 @@ mod tests { Value::from(Idiom(vec![ Part::Start(Value::Thing(Thing { tb: "test".to_owned(), - id: Id::Number(1), + id: Id::from(1), })), Part::Value(Value::Strand(Strand("foo".to_owned()))), ])) @@ -944,7 +944,7 @@ mod tests { Value::from(Idiom(vec![ Part::Start(Value::Thing(Thing { tb: "test".to_owned(), - id: Id::Number(1), + id: Id::from(1), })), Part::All ])) diff --git a/core/src/syn/parser/kind.rs b/core/src/syn/parser/kind.rs index 767bece5..fa8898ac 100644 --- a/core/src/syn/parser/kind.rs +++ b/core/src/syn/parser/kind.rs @@ -82,6 +82,7 @@ impl Parser<'_> { t!("POINT") => Ok(Kind::Point), t!("STRING") => Ok(Kind::String), t!("UUID") => Ok(Kind::Uuid), + t!("RANGE") => Ok(Kind::Range), t!("FUNCTION") => Ok(Kind::Function(Default::default(), Default::default())), t!("RECORD") => { let span = self.peek().span; diff --git a/core/src/syn/parser/prime.rs b/core/src/syn/parser/prime.rs index c93638e2..017f6fc6 100644 --- a/core/src/syn/parser/prime.rs +++ b/core/src/syn/parser/prime.rs @@ -1,3 +1,5 @@ +use std::ops::Bound; + use geo::Point; use reblessive::Stk; @@ -6,14 +8,14 @@ use crate::{ enter_object_recursion, enter_query_recursion, sql::{ Array, Closure, Dir, Function, Geometry, Ident, Idiom, Kind, Mock, Number, Param, Part, - Script, Strand, Subquery, Table, Value, + Range, Script, Strand, Subquery, Table, Value, }, syn::{ parser::{ - mac::{expected, unexpected}, + mac::{expected, expected_whitespace, unexpected}, ParseError, ParseErrorKind, }, - token::{t, Span, TokenKind}, + token::{t, DurationSuffix, Span, TokenKind}, }, }; @@ -23,27 +25,29 @@ impl Parser<'_> { /// What's are values which are more restricted in what expressions they can contain. pub async fn parse_what_primary(&mut self, ctx: &mut Stk) -> ParseResult { match self.peek_kind() { + t!("..") => Ok(self.try_parse_range(ctx, None).await?.unwrap()), t!("r\"") => { self.pop_peek(); - let thing = self.parse_record_string(ctx, true).await?; - Ok(Value::Thing(thing)) + let value = Value::Thing(self.parse_record_string(ctx, true).await?); + Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)) } t!("r'") => { self.pop_peek(); - let thing = self.parse_record_string(ctx, false).await?; - Ok(Value::Thing(thing)) + let value = Value::Thing(self.parse_record_string(ctx, false).await?); + Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)) } t!("d\"") | t!("d'") => { - let datetime = self.next_token_value()?; - Ok(Value::Datetime(datetime)) + let value = Value::Datetime(self.next_token_value()?); + Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)) } t!("u\"") | t!("u'") => { - let uuid = self.next_token_value()?; - Ok(Value::Uuid(uuid)) + let value = Value::Uuid(self.next_token_value()?); + Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)) } t!("$param") => { let value = Value::Param(self.next_token_value()?); - Ok(self.try_parse_inline(ctx, &value).await?.unwrap_or(value)) + let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value); + Ok(self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value)) } t!("FUNCTION") => { self.pop_peek(); @@ -128,12 +132,60 @@ impl Parser<'_> { } } + pub async fn try_parse_range( + &mut self, + ctx: &mut Stk, + subject: Option<&Value>, + ) -> ParseResult> { + // The ">" can also mean a comparison. + // If the token after is not "..", then return + if self.peek_whitespace().kind == t!(">") + && self.peek_whitespace_token_at(1).kind != t!("..") + { + return Ok(None); + } + + let beg = if let Some(subject) = subject { + if self.eat_whitespace(t!(">")) { + expected_whitespace!(self, t!("..")); + Bound::Excluded(subject.to_owned()) + } else { + if !self.eat_whitespace(t!("..")) { + return Ok(None); + } + + Bound::Included(subject.to_owned()) + } + } else { + if !self.eat_whitespace(t!("..")) { + return Ok(None); + } + + Bound::Unbounded + }; + + let end = if self.eat_whitespace(t!("=")) { + let id = ctx.run(|ctx| self.parse_simple_value(ctx)).await?; + Bound::Included(id) + } else if Self::tokenkind_can_start_simple_value(self.peek_whitespace().kind) { + let id = ctx.run(|ctx| self.parse_simple_value(ctx)).await?; + Bound::Excluded(id) + } else { + Bound::Unbounded + }; + + Ok(Some(Value::Range(Box::new(Range { + beg, + end, + })))) + } + pub async fn try_parse_inline( &mut self, ctx: &mut Stk, subject: &Value, ) -> ParseResult> { - if self.eat(t!("(")) { + if self.eat_whitespace(t!("(")) { let start = self.last_span(); let mut args = Vec::new(); loop { @@ -171,21 +223,26 @@ impl Parser<'_> { pub async fn parse_idiom_expression(&mut self, ctx: &mut Stk) -> ParseResult { let token = self.peek(); let value = match token.kind { + t!("..") => self.try_parse_range(ctx, None).await?.unwrap(), t!("NONE") => { self.pop_peek(); - Value::None + let value = Value::None; + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("NULL") => { self.pop_peek(); - Value::Null + let value = Value::Null; + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("true") => { self.pop_peek(); - Value::Bool(true) + let value = Value::Bool(true); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("false") => { self.pop_peek(); - Value::Bool(false) + let value = Value::Bool(false); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("<") => { self.pop_peek(); @@ -198,21 +255,21 @@ impl Parser<'_> { } t!("r\"") => { self.pop_peek(); - let thing = self.parse_record_string(ctx, true).await?; - Value::Thing(thing) + let value = Value::Thing(self.parse_record_string(ctx, true).await?); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("r'") => { self.pop_peek(); - let thing = self.parse_record_string(ctx, false).await?; - Value::Thing(thing) + let value = Value::Thing(self.parse_record_string(ctx, false).await?); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("d\"") | t!("d'") => { - let datetime = self.next_token_value()?; - Value::Datetime(datetime) + let value = Value::Datetime(self.next_token_value()?); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("u\"") | t!("u'") => { - let uuid = self.next_token_value()?; - Value::Uuid(uuid) + let value = Value::Uuid(self.next_token_value()?); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("'") | t!("\"") | TokenKind::Strand => { let s = self.next_token_value::()?; @@ -221,18 +278,22 @@ impl Parser<'_> { return Ok(x); } } - Value::Strand(s) + let value = Value::Strand(s); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("+") | t!("-") | TokenKind::Number(_) | TokenKind::Digits | TokenKind::Duration => { - self.parse_number_like_prime()? + let value = self.parse_number_like_prime()?; + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } TokenKind::NaN => { self.pop_peek(); - Value::Number(Number::Float(f64::NAN)) + let value = Value::Number(Number::Float(f64::NAN)); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("$param") => { let value = Value::Param(self.next_token_value()?); - self.try_parse_inline(ctx, &value).await?.unwrap_or(value) + let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("FUNCTION") => { self.pop_peek(); @@ -256,12 +317,14 @@ impl Parser<'_> { } t!("[") => { self.pop_peek(); - self.parse_array(ctx, token.span).await.map(Value::Array)? + let value = self.parse_array(ctx, token.span).await.map(Value::Array)?; + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("{") => { self.pop_peek(); let value = self.parse_object_like(ctx, token.span).await?; - self.try_parse_inline(ctx, &value).await?.unwrap_or(value) + let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("|") => { self.pop_peek(); @@ -281,7 +344,8 @@ impl Parser<'_> { t!("(") => { self.pop_peek(); let value = self.parse_inner_subquery_or_coordinate(ctx, token.span).await?; - self.try_parse_inline(ctx, &value).await?.unwrap_or(value) + let value = self.try_parse_inline(ctx, &value).await?.unwrap_or(value); + self.try_parse_range(ctx, Some(&value)).await?.unwrap_or(value) } t!("/") => self.next_token_value().map(Value::Regex)?, t!("RETURN") @@ -749,6 +813,133 @@ impl Parser<'_> { .map_err(|(e, span)| ParseError::new(ParseErrorKind::InvalidToken(e), span))?; Ok(Function::Script(Script(body), args)) } + + /// Parse a simple singular value + pub async fn parse_simple_value(&mut self, ctx: &mut Stk) -> ParseResult { + 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::()?; + 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)] diff --git a/core/src/syn/parser/test/stmt.rs b/core/src/syn/parser/test/stmt.rs index 451a9b9e..d9c3681f 100644 --- a/core/src/syn/parser/test/stmt.rs +++ b/core/src/syn/parser/test/stmt.rs @@ -1664,7 +1664,7 @@ fn parse_delete_2() { dir: Dir::Out, from: Thing { tb: "a".to_owned(), - id: Id::String("b".to_owned()), + id: Id::from("b"), }, what: Tables::default(), }))), @@ -1867,7 +1867,7 @@ SELECT bar as foo,[1,2],bar OMIT bar FROM ONLY a,1 }])), limit: Some(Limit(Value::Thing(Thing { tb: "a".to_owned(), - id: Id::String("b".to_owned()), + id: Id::from("b"), }))), start: Some(Start(Value::Object(Object( [("a".to_owned(), Value::Bool(true))].into_iter().collect() @@ -2191,7 +2191,7 @@ fn parse_relate() { only: true, kind: Value::Thing(Thing { tb: "a".to_owned(), - id: Id::String("b".to_owned()), + id: Id::from("b"), }), from: Value::Array(Array(vec![ Value::Number(Number::Int(1)), diff --git a/core/src/syn/parser/test/streaming.rs b/core/src/syn/parser/test/streaming.rs index 594d520c..67747057 100644 --- a/core/src/syn/parser/test/streaming.rs +++ b/core/src/syn/parser/test/streaming.rs @@ -410,7 +410,7 @@ fn statements() -> Vec { dir: Dir::Out, from: Thing { tb: "a".to_owned(), - id: Id::String("b".to_owned()), + id: Id::from("b"), }, what: Tables::default(), }))), @@ -519,7 +519,7 @@ fn statements() -> Vec { }])), limit: Some(Limit(Value::Thing(Thing { tb: "a".to_owned(), - id: Id::String("b".to_owned()), + id: Id::from("b"), }))), start: Some(Start(Value::Object(Object( [("a".to_owned(), Value::Bool(true))].into_iter().collect(), @@ -625,7 +625,7 @@ fn statements() -> Vec { only: true, kind: Value::Thing(Thing { tb: "a".to_owned(), - id: Id::String("b".to_owned()), + id: Id::from("b"), }), from: Value::Array(Array(vec![ Value::Number(Number::Int(1)), diff --git a/core/src/syn/parser/test/value.rs b/core/src/syn/parser/test/value.rs index 5e6c2113..44c5bc8a 100644 --- a/core/src/syn/parser/test/value.rs +++ b/core/src/syn/parser/test/value.rs @@ -93,13 +93,13 @@ fn parse_recursive_record_string() { res, Value::Thing(Thing { tb: "a".to_owned(), - id: Id::Array(Array(vec![Value::Thing(Thing { + id: Id::from(Array(vec![Value::Thing(Thing { tb: "b".to_owned(), - id: Id::Object(Object(BTreeMap::from([( + id: Id::from(Object(BTreeMap::from([( "c".to_owned(), Value::Thing(Thing { tb: "d".to_owned(), - id: Id::Number(1) + id: Id::from(1) }) )]))) })])) @@ -114,7 +114,7 @@ fn parse_record_string_2() { res, Value::Thing(Thing { tb: "a".to_owned(), - id: Id::Array(Array(vec![Value::Strand(Strand("foo".to_owned()))])) + id: Id::from(Array(vec![Value::Strand(Strand("foo".to_owned()))])) }) ) } diff --git a/core/src/syn/parser/thing.rs b/core/src/syn/parser/thing.rs index 73ee8387..65b3876e 100644 --- a/core/src/syn/parser/thing.rs +++ b/core/src/syn/parser/thing.rs @@ -2,7 +2,10 @@ use reblessive::Stk; use super::{ParseResult, Parser}; use crate::{ - sql::{id::Gen, Id, Ident, Range, Thing, Value}, + sql::{ + id::{range::IdRange, Gen}, + Id, Ident, Range, Thing, Value, + }, syn::{ parser::{ mac::{expected, expected_whitespace, unexpected}, @@ -51,22 +54,24 @@ impl Parser<'_> { } else { Bound::Unbounded }; - return Ok(Value::Range(Box::new(Range { + return Ok(Value::Thing(Thing { tb: ident, - beg: Bound::Unbounded, - end, - }))); + id: Id::Range(Box::new(IdRange { + beg: Bound::Unbounded, + end, + })), + })); } // Didn't eat range yet so we need to parse the id. let beg = if Self::kind_cast_start_id(self.peek_whitespace().kind) { - let id = stk.run(|ctx| self.parse_id(ctx)).await?; + let v = stk.run(|stk| self.parse_id(stk)).await?; // check for exclusive if self.eat_whitespace(t!(">")) { - Bound::Excluded(id) + Bound::Excluded(v) } else { - Bound::Included(id) + Bound::Included(v) } } else { Bound::Unbounded @@ -76,31 +81,33 @@ impl Parser<'_> { // If we already ate the exclusive it must be a range. if self.eat_whitespace(t!("..")) { let end = if self.eat_whitespace(t!("=")) { - let id = stk.run(|ctx| self.parse_id(ctx)).await?; + let id = stk.run(|stk| self.parse_id(stk)).await?; Bound::Included(id) } else if Self::kind_cast_start_id(self.peek_whitespace().kind) { - let id = stk.run(|ctx| self.parse_id(ctx)).await?; + let id = stk.run(|stk| self.parse_id(stk)).await?; Bound::Excluded(id) } else { Bound::Unbounded }; - Ok(Value::Range(Box::new(Range { + Ok(Value::Thing(Thing { tb: ident, - beg, - end, - }))) + id: Id::Range(Box::new(IdRange { + beg, + end, + })), + })) } else { let id = match beg { Bound::Unbounded => { if self.peek_whitespace().kind == t!("$param") { return Err(ParseError::new( - ParseErrorKind::UnexpectedExplain { - found: t!("$param"), - expected: "a record-id id", - explain: "you can create a record-id from a param with the function 'type::thing'", - }, - self.recent_span(), - )); + ParseErrorKind::UnexpectedExplain { + found: t!("$param"), + expected: "a record-id id", + explain: "you can create a record-id from a param with the function 'type::thing'", + }, + self.recent_span(), + )); } // we haven't matched anythong so far so we still want any type of id. @@ -110,7 +117,8 @@ impl Parser<'_> { // we have matched a bounded id but we don't see an range operator. unexpected!(self, self.peek_whitespace().kind, "the range operator `..`") } - Bound::Included(id) => id, + // We previously converted the `Id` value to `Value` so it's safe to unwrap here. + Bound::Included(v) => v, }; Ok(Value::Thing(Thing { tb: ident, @@ -121,18 +129,14 @@ impl Parser<'_> { /// Parse an range pub async fn parse_range(&mut self, ctx: &mut Stk) -> ParseResult { - let tb = self.next_token_value::()?.0; - - expected_whitespace!(self, t!(":")); - // Check for beginning id let beg = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) { - let id = ctx.run(|ctx| self.parse_id(ctx)).await?; + let v = ctx.run(|ctx| self.parse_value(ctx)).await?; if self.eat_whitespace(t!(">")) { - Bound::Excluded(id) + Bound::Excluded(v) } else { - Bound::Included(id) + Bound::Included(v) } } else { Bound::Unbounded @@ -144,18 +148,17 @@ impl Parser<'_> { // parse ending id. let end = if Self::tokenkind_can_start_ident(self.peek_whitespace().kind) { - let id = ctx.run(|ctx| self.parse_id(ctx)).await?; + let v = ctx.run(|ctx| self.parse_value(ctx)).await?; if inclusive { - Bound::Included(id) + Bound::Included(v) } else { - Bound::Excluded(id) + Bound::Excluded(v) } } else { Bound::Unbounded }; Ok(Range { - tb, beg, end, }) @@ -498,7 +501,7 @@ mod tests { out, Thing { tb: String::from("test"), - id: Id::Object(Object::from(map! { + id: Id::from(Object::from(map! { "location".to_string() => Value::from("GBR"), "year".to_string() => Value::from(2022), })), @@ -516,7 +519,7 @@ mod tests { out, Thing { tb: String::from("test"), - id: Id::Array(Array::from(vec![Value::from("GBR"), Value::from(2022)])), + id: Id::from(Array::from(vec![Value::from("GBR"), Value::from(2022)])), } ); } @@ -535,7 +538,7 @@ mod tests { .finish() .unwrap_or_else(|_| panic!("failed on {}", ident)) .id; - assert_eq!(r, Id::String(ident.to_string()),); + assert_eq!(r, Id::from(ident.to_string()),); let mut parser = Parser::new(thing.as_bytes()); let r = stack @@ -548,7 +551,7 @@ mod tests { sql::Query(sql::Statements(vec![sql::Statement::Value(sql::Value::Thing( sql::Thing { tb: "t".to_string(), - id: Id::String(ident.to_string()) + id: Id::from(ident.to_string()) } ))])) ) diff --git a/core/src/syn/token/keyword.rs b/core/src/syn/token/keyword.rs index bb1b9fb8..c8e00ff2 100644 --- a/core/src/syn/token/keyword.rs +++ b/core/src/syn/token/keyword.rs @@ -142,6 +142,7 @@ keyword! { PostingsOrder => "POSTINGS_ORDER", Prune => "PRUNE", Punct => "PUNCT", + Range => "RANGE", Readonly => "READONLY", Rebuild => "REBUILD", Relate => "RELATE", diff --git a/lib/benches/hash_trie_btree.rs b/lib/benches/hash_trie_btree.rs index 5c33e723..19a5d8b1 100644 --- a/lib/benches/hash_trie_btree.rs +++ b/lib/benches/hash_trie_btree.rs @@ -71,7 +71,7 @@ fn bench_hash_trie_btree_thing(c: &mut Criterion) { const N: usize = 50_000; let mut samples = Vec::with_capacity(N); for i in 0..N { - let key = Thing::from(("test", Id::Array(Array::from(vec![i as i32; 5])))); + let key = Thing::from(("test", Id::from(Array::from(vec![i as i32; 5])))); samples.push((key, i)); } diff --git a/lib/benches/index_hnsw.rs b/lib/benches/index_hnsw.rs index c96e248c..6ee0fbf0 100644 --- a/lib/benches/index_hnsw.rs +++ b/lib/benches/index_hnsw.rs @@ -174,7 +174,7 @@ fn new_vectors_from_file(path: &str) -> Vec<(Thing, Array)> { let line = line_result.unwrap(); let value = value(&line).unwrap(); if let Value::Array(a) = value { - res.push((Thing::from(("e", Id::Number(i as i64))), a)); + res.push((Thing::from(("e", Id::from(i as i64))), a)); } else { panic!("Wrong value"); } diff --git a/lib/src/api/engine/local/mod.rs b/lib/src/api/engine/local/mod.rs index 2b4e8689..b7becf2d 100644 --- a/lib/src/api/engine/local/mod.rs +++ b/lib/src/api/engine/local/mod.rs @@ -553,7 +553,7 @@ async fn router( data, } => { let mut query = Query::default(); - let one = what.is_thing(); + let one = what.is_thing_single(); let statement = { let mut stmt = UpsertStatement::default(); stmt.what = value_to_values(what); @@ -571,7 +571,7 @@ async fn router( data, } => { let mut query = Query::default(); - let one = what.is_thing(); + let one = what.is_thing_single(); let statement = { let mut stmt = UpdateStatement::default(); stmt.what = value_to_values(what); @@ -607,7 +607,7 @@ async fn router( data, } => { let mut query = Query::default(); - let one = what.is_thing(); + let one = what.is_thing_single(); let statement = { let mut stmt = UpdateStatement::default(); stmt.what = value_to_values(what); @@ -625,7 +625,7 @@ async fn router( data, } => { let mut query = Query::default(); - let one = what.is_thing(); + let one = what.is_thing_single(); let statement = { let mut stmt = UpdateStatement::default(); stmt.what = value_to_values(what); @@ -642,7 +642,7 @@ async fn router( what, } => { let mut query = Query::default(); - let one = what.is_thing(); + let one = what.is_thing_single(); let statement = { let mut stmt = SelectStatement::default(); stmt.what = value_to_values(what); @@ -658,7 +658,7 @@ async fn router( what, } => { let mut query = Query::default(); - let one = what.is_thing(); + let one = what.is_thing_single(); let statement = { let mut stmt = DeleteStatement::default(); stmt.what = value_to_values(what); diff --git a/lib/src/api/method/live.rs b/lib/src/api/method/live.rs index cd5f41c6..dc70dfda 100644 --- a/lib/src/api/method/live.rs +++ b/lib/src/api/method/live.rs @@ -14,13 +14,10 @@ use crate::method::Select; use crate::opt::Resource; use crate::sql::from_value; use crate::sql::statements::LiveStatement; -use crate::sql::Cond; -use crate::sql::Expression; use crate::sql::Field; use crate::sql::Fields; use crate::sql::Ident; use crate::sql::Idiom; -use crate::sql::Operator; use crate::sql::Part; use crate::sql::Statement; use crate::sql::Table; @@ -63,10 +60,10 @@ macro_rules! into_future { let mut table = Table::default(); match range { Some(range) => { - let range = resource?.with_range(range)?; - table.0 = range.tb.clone(); + let record = resource?.with_range(range)?; + table.0 = record.tb.clone(); stmt.what = table.into(); - stmt.cond = range.to_cond(); + stmt.cond = record.to_cond(); } None => match resource? { Resource::Table(table) => { @@ -79,13 +76,7 @@ macro_rules! into_future { ident.0 = ID.to_owned(); let mut idiom = Idiom::default(); idiom.0 = vec![Part::from(ident)]; - let mut cond = Cond::default(); - cond.0 = Value::Expression(Box::new(Expression::new( - idiom.into(), - Operator::Equal, - record.into(), - ))); - stmt.cond = Some(cond); + stmt.cond = record.to_cond(); } Resource::Object(object) => return Err(Error::LiveOnObject(object).into()), Resource::Array(array) => return Err(Error::LiveOnArray(array).into()), diff --git a/lib/src/api/method/tests/server.rs b/lib/src/api/method/tests/server.rs index e9eb4210..eb5a757b 100644 --- a/lib/src/api/method/tests/server.rs +++ b/lib/src/api/method/tests/server.rs @@ -4,6 +4,8 @@ use crate::api::conn::DbResponse; use crate::api::conn::Route; use crate::api::Response as QueryResponse; use crate::sql::to_value; +use crate::sql::Id; +use crate::sql::Thing; use crate::sql::Value; use channel::Receiver; @@ -61,10 +63,13 @@ pub(super) fn mock(route_rx: Receiver) { what, .. } => match what { + Value::Table(..) + | Value::Array(..) + | Value::Thing(Thing { + id: Id::Range(_), + .. + }) => Ok(DbResponse::Other(Value::Array(Default::default()))), Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())), - Value::Table(..) | Value::Array(..) | Value::Range(..) => { - Ok(DbResponse::Other(Value::Array(Default::default()))) - } _ => unreachable!(), }, Command::Upsert { @@ -83,10 +88,13 @@ pub(super) fn mock(route_rx: Receiver) { what, .. } => match what { + Value::Table(..) + | Value::Array(..) + | Value::Thing(Thing { + id: Id::Range(_), + .. + }) => Ok(DbResponse::Other(Value::Array(Default::default()))), Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())), - Value::Table(..) | Value::Array(..) | Value::Range(..) => { - Ok(DbResponse::Other(Value::Array(Default::default()))) - } _ => unreachable!(), }, Command::Insert { diff --git a/lib/src/api/method/update.rs b/lib/src/api/method/update.rs index 04eb72ac..e3c49a03 100644 --- a/lib/src/api/method/update.rs +++ b/lib/src/api/method/update.rs @@ -9,6 +9,7 @@ use crate::api::opt::Resource; use crate::api::Connection; use crate::api::Result; use crate::method::OnceLockExt; +use crate::sql::to_value; use crate::sql::Id; use crate::sql::Value; use crate::Surreal; @@ -17,7 +18,6 @@ use serde::Serialize; use std::borrow::Cow; use std::future::IntoFuture; use std::marker::PhantomData; -use surrealdb_core::sql::to_value; /// An update future #[derive(Debug)] diff --git a/lib/src/api/method/upsert.rs b/lib/src/api/method/upsert.rs index 781c3190..cce7eba2 100644 --- a/lib/src/api/method/upsert.rs +++ b/lib/src/api/method/upsert.rs @@ -9,6 +9,7 @@ use crate::api::opt::Resource; use crate::api::Connection; use crate::api::Result; use crate::method::OnceLockExt; +use crate::sql::to_value; use crate::sql::Id; use crate::sql::Value; use crate::Surreal; @@ -17,7 +18,6 @@ use serde::Serialize; use std::borrow::Cow; use std::future::IntoFuture; use std::marker::PhantomData; -use surrealdb_core::sql::to_value; /// An upsert future #[derive(Debug)] diff --git a/lib/src/api/opt/resource.rs b/lib/src/api/opt/resource.rs index e0efa25a..71526025 100644 --- a/lib/src/api/opt/resource.rs +++ b/lib/src/api/opt/resource.rs @@ -19,9 +19,12 @@ pub enum Resource { } impl Resource { - pub(crate) fn with_range(self, range: Range) -> Result { + pub(crate) fn with_range(self, range: Range) -> Result { match self { - Resource::Table(table) => Ok(sql::Range::new(table.0, range.start, range.end)), + Resource::Table(table) => Ok(sql::Thing::from(( + table.0, + sql::Id::Range(Box::new(sql::IdRange::try_from((range.start, range.end))?)), + ))), Resource::RecordId(record_id) => Err(Error::RangeOnRecordId(record_id).into()), Resource::Object(object) => Err(Error::RangeOnObject(object).into()), Resource::Array(array) => Err(Error::RangeOnArray(array).into()), diff --git a/lib/tests/cast.rs b/lib/tests/cast.rs index 02fd8ccc..d56490b5 100644 --- a/lib/tests/cast.rs +++ b/lib/tests/cast.rs @@ -62,3 +62,35 @@ async fn cast_to_record_table() -> Result<(), Error> { // Ok(()) } + +#[tokio::test] +async fn cast_range_to_array() -> Result<(), Error> { + let sql = r#" + 1..5; + 1>..5; + 1..=5; + 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(()) +} diff --git a/lib/tests/expression.rs b/lib/tests/expression.rs index 7f4bdc1f..338d352e 100644 --- a/lib/tests/expression.rs +++ b/lib/tests/expression.rs @@ -3,7 +3,23 @@ use helpers::Test; use surrealdb::err::Error; #[tokio::test] -async fn modulo() -> Result<(), Error> { +async fn expr_modulo() -> Result<(), Error> { Test::new("8 % 3").await?.expect_val("2")?; Ok(()) } + +#[tokio::test] +async fn expr_value_in_range() -> Result<(), Error> { + Test::new( + " + 1 in 1..2; + 'a' in 'a'..'b'; + 0 in 1..2; + ", + ) + .await? + .expect_val("true")? + .expect_val("true")? + .expect_val("false")?; + Ok(()) +} diff --git a/lib/tests/foreach.rs b/lib/tests/foreach.rs index b7795a9e..f7df9b4a 100644 --- a/lib/tests/foreach.rs +++ b/lib/tests/foreach.rs @@ -199,3 +199,36 @@ async fn foreach_nested() -> Result<(), Error> { // Ok(()) } + +#[tokio::test] +async fn foreach_range() -> Result<(), Error> { + let sql = " + FOR $test IN 1..4 { + IF $test == 2 { + BREAK; + }; + UPSERT type::thing('person', $test) SET test = $test; + }; + SELECT * FROM person; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 2); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + id: person:1, + test: 1, + }, + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +} diff --git a/lib/tests/function.rs b/lib/tests/function.rs index 4cc21b93..25edf653 100644 --- a/lib/tests/function.rs +++ b/lib/tests/function.rs @@ -5527,33 +5527,24 @@ async fn function_type_thing() -> Result<(), Error> { #[tokio::test] async fn function_type_range() -> Result<(), Error> { let sql = r#" - RETURN type::range('person'); - RETURN type::range('person',1); - RETURN type::range('person',null,10); - RETURN type::range('person',1,10); - RETURN type::range('person',1,10, { begin: "excluded", end: "included"}); + RETURN type::range(..); + RETURN type::range(1..2); + RETURN type::range([1, 2]); "#; let mut test = Test::new(sql).await?; // let tmp = test.next()?.result?; - let val = Value::parse("person:.."); + let val = Value::parse(".."); assert_eq!(tmp, val); - + // let tmp = test.next()?.result?; - let val = Value::parse("person:1.."); + let val = Value::parse("1..2"); assert_eq!(tmp, val); - + // let tmp = test.next()?.result?; - let val = Value::parse("person:..10"); - assert_eq!(tmp, val); - - let tmp = test.next()?.result?; - let val = Value::parse("person:1..10"); - assert_eq!(tmp, val); - - let tmp = test.next()?.result?; - let val = Value::parse("person:1>..=10"); + let val = Value::parse("1..2"); assert_eq!(tmp, val); + // Ok(()) }