Range values (#4506)

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

View file

@ -186,7 +186,7 @@ mod tests {
let mut tx1 = ds.transaction(Write, Optimistic).await.unwrap().inner();
let 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();

View file

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

View file

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

View file

@ -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(),

View file

@ -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
}

View file

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

View file

@ -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(())

View file

@ -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,

View file

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

View file

@ -1,4 +1,4 @@
use crate::cnf::FUNCTION_ALLOCATION_LIMIT;
use crate::cnf::GENERATION_ALLOCATION_LIMIT;
use crate::err::Error;
use crate::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(())

View file

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

View file

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

View file

@ -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};

View file

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

View file

@ -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

View file

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

185
core/src/sql/id/range.rs Normal file
View file

@ -0,0 +1,185 @@
use super::Id;
use crate::{
ctx::Context,
dbs::Options,
doc::CursorDoc,
err::Error,
sql::{Range, Value},
};
use reblessive::tree::Stk;
use revision::revisioned;
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt, ops::Bound};
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct IdRange {
pub beg: Bound<Id>,
pub end: Bound<Id>,
}
impl TryFrom<(Bound<Id>, Bound<Id>)> for IdRange {
type Error = Error;
fn try_from((beg, end): (Bound<Id>, Bound<Id>)) -> Result<Self, Self::Error> {
if matches!(beg, Bound::Included(Id::Range(_)) | Bound::Excluded(Id::Range(_))) {
return Err(Error::IdInvalid {
value: "range".into(),
});
}
if matches!(end, Bound::Included(Id::Range(_)) | Bound::Excluded(Id::Range(_))) {
return Err(Error::IdInvalid {
value: "range".into(),
});
}
Ok(IdRange {
beg,
end,
})
}
}
impl TryFrom<Range> for IdRange {
type Error = Error;
fn try_from(v: Range) -> Result<Self, Self::Error> {
let beg = match v.beg {
Bound::Included(beg) => Bound::Included(Id::try_from(beg)?),
Bound::Excluded(beg) => Bound::Excluded(Id::try_from(beg)?),
Bound::Unbounded => Bound::Unbounded,
};
let end = match v.end {
Bound::Included(end) => Bound::Included(Id::try_from(end)?),
Bound::Excluded(end) => Bound::Excluded(Id::try_from(end)?),
Bound::Unbounded => Bound::Unbounded,
};
// The TryFrom implementation ensures that the bounds do not contain an `Id::Range` value
IdRange::try_from((beg, end))
}
}
impl TryFrom<Value> for IdRange {
type Error = Error;
fn try_from(v: Value) -> Result<Self, Self::Error> {
match v {
Value::Range(v) => IdRange::try_from(*v),
v => Err(Error::IdInvalid {
value: v.kindof().to_string(),
}),
}
}
}
impl PartialOrd for IdRange {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for IdRange {
fn cmp(&self, other: &Self) -> Ordering {
match &self.beg {
Bound::Unbounded => match &other.beg {
Bound::Unbounded => Ordering::Equal,
_ => Ordering::Less,
},
Bound::Included(v) => match &other.beg {
Bound::Unbounded => Ordering::Greater,
Bound::Included(w) => match v.cmp(w) {
Ordering::Equal => match &self.end {
Bound::Unbounded => match &other.end {
Bound::Unbounded => Ordering::Equal,
_ => Ordering::Greater,
},
Bound::Included(v) => match &other.end {
Bound::Unbounded => Ordering::Less,
Bound::Included(w) => v.cmp(w),
_ => Ordering::Greater,
},
Bound::Excluded(v) => match &other.end {
Bound::Excluded(w) => v.cmp(w),
_ => Ordering::Less,
},
},
ordering => ordering,
},
_ => Ordering::Less,
},
Bound::Excluded(v) => match &other.beg {
Bound::Excluded(w) => match v.cmp(w) {
Ordering::Equal => match &self.end {
Bound::Unbounded => match &other.end {
Bound::Unbounded => Ordering::Equal,
_ => Ordering::Greater,
},
Bound::Included(v) => match &other.end {
Bound::Unbounded => Ordering::Less,
Bound::Included(w) => v.cmp(w),
_ => Ordering::Greater,
},
Bound::Excluded(v) => match &other.end {
Bound::Excluded(w) => v.cmp(w),
_ => Ordering::Less,
},
},
ordering => ordering,
},
_ => Ordering::Greater,
},
}
}
}
impl fmt::Display for IdRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.beg {
Bound::Unbounded => write!(f, ""),
Bound::Included(v) => write!(f, "{v}"),
Bound::Excluded(v) => write!(f, "{v}>"),
}?;
match &self.end {
Bound::Unbounded => write!(f, ".."),
Bound::Excluded(v) => write!(f, "..{v}"),
Bound::Included(v) => write!(f, "..={v}"),
}?;
Ok(())
}
}
impl IdRange {
/// Process the values in the bounds for this IdRange
pub(crate) async fn compute(
&self,
stk: &mut Stk,
ctx: &Context,
opt: &Options,
doc: Option<&CursorDoc>,
) -> Result<IdRange, Error> {
let beg = match &self.beg {
Bound::Included(beg) => {
Bound::Included(stk.run(|stk| beg.compute(stk, ctx, opt, doc)).await?)
}
Bound::Excluded(beg) => {
Bound::Excluded(stk.run(|stk| beg.compute(stk, ctx, opt, doc)).await?)
}
Bound::Unbounded => Bound::Unbounded,
};
let end = match &self.end {
Bound::Included(end) => {
Bound::Included(stk.run(|stk| end.compute(stk, ctx, opt, doc)).await?)
}
Bound::Excluded(end) => {
Bound::Excluded(stk.run(|stk| end.compute(stk, ctx, opt, doc)).await?)
}
Bound::Unbounded => Bound::Unbounded,
};
// The TryFrom implementation ensures that the bounds do not contain an `Id::Range` value
IdRange::try_from((beg, end))
}
}

View file

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

View file

@ -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;

View file

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

View file

@ -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();

View file

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

View file

@ -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);

View file

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

View file

@ -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,
});

View file

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

View file

@ -183,6 +183,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
UniCase::ascii("POSTINGS_ORDER") => TokenKind::Keyword(Keyword::PostingsOrder),
UniCase::ascii("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),

View file

@ -339,6 +339,7 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
UniCase::ascii("time::from::secs") => PathKind::Function,
UniCase::ascii("time::from::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,

View file

@ -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
]))

View file

@ -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;

View file

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

View file

@ -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)),

View file

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

View file

@ -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()))]))
})
)
}

View file

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

View file

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

View file

@ -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));
}

View file

@ -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");
}

View file

@ -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);

View file

@ -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()),

View file

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

View file

@ -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)]

View file

@ -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)]

View file

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

View file

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

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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(())
}