Idiom destructuring (#4443)
This commit is contained in:
parent
8f7392983f
commit
eab548c5a3
9 changed files with 299 additions and 2 deletions
|
@ -1013,6 +1013,12 @@ pub enum Error {
|
||||||
Return {
|
Return {
|
||||||
value: Value,
|
value: Value,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A destructuring variant was used in a context where it is not supported
|
||||||
|
#[error("{variant} destructuring method is not supported here")]
|
||||||
|
UnsupportedDestructure {
|
||||||
|
variant: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for String {
|
impl From<Error> for String {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::idx::planner::executor::KnnExpressions;
|
use crate::idx::planner::executor::KnnExpressions;
|
||||||
|
use crate::sql::part::DestructurePart;
|
||||||
use crate::sql::{
|
use crate::sql::{
|
||||||
Array, Cast, Cond, Expression, Function, Id, Idiom, Model, Object, Part, Range, Thing, Value,
|
Array, Cast, Cond, Expression, Function, Id, Idiom, Model, Object, Part, Range, Thing, Value,
|
||||||
};
|
};
|
||||||
|
@ -70,6 +71,30 @@ impl<'a> KnnConditionRewriter<'a> {
|
||||||
Some(new_vec)
|
Some(new_vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn eval_destructure_part(&self, part: &DestructurePart) -> Option<DestructurePart> {
|
||||||
|
match part {
|
||||||
|
DestructurePart::Aliased(f, v) => {
|
||||||
|
self.eval_idiom(v).map(|v| DestructurePart::Aliased(f.clone(), v))
|
||||||
|
}
|
||||||
|
DestructurePart::Destructure(f, v) => {
|
||||||
|
self.eval_destructure_parts(v).map(|v| DestructurePart::Destructure(f.clone(), v))
|
||||||
|
}
|
||||||
|
p => Some(p.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_destructure_parts(&self, parts: &[DestructurePart]) -> Option<Vec<DestructurePart>> {
|
||||||
|
let mut new_vec = Vec::with_capacity(parts.len());
|
||||||
|
for part in parts {
|
||||||
|
if let Some(part) = self.eval_destructure_part(part) {
|
||||||
|
new_vec.push(part);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(new_vec)
|
||||||
|
}
|
||||||
|
|
||||||
fn eval_value_object(&self, o: &Object) -> Option<Value> {
|
fn eval_value_object(&self, o: &Object) -> Option<Value> {
|
||||||
self.eval_object(o).map(|o| o.into())
|
self.eval_object(o).map(|o| o.into())
|
||||||
}
|
}
|
||||||
|
@ -132,6 +157,7 @@ impl<'a> KnnConditionRewriter<'a> {
|
||||||
Part::Value(v) => self.eval_value(v).map(Part::Value),
|
Part::Value(v) => self.eval_value(v).map(Part::Value),
|
||||||
Part::Start(v) => self.eval_value(v).map(Part::Start),
|
Part::Start(v) => self.eval_value(v).map(Part::Start),
|
||||||
Part::Method(n, p) => self.eval_values(p).map(|v| Part::Method(n.clone(), v)),
|
Part::Method(n, p) => self.eval_values(p).map(|v| Part::Method(n.clone(), v)),
|
||||||
|
Part::Destructure(p) => self.eval_destructure_parts(p).map(Part::Destructure),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,12 @@ use geo_types::{MultiLineString, MultiPoint, MultiPolygon};
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
use std::{fmt, hash};
|
use std::{fmt, hash};
|
||||||
|
|
||||||
|
use super::Object;
|
||||||
|
|
||||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Geometry";
|
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Geometry";
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 1)]
|
||||||
|
@ -116,6 +119,21 @@ impl Geometry {
|
||||||
Self::Collection(v) => collection(v),
|
Self::Collection(v) => collection(v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Get the GeoJSON object representation for this geometry
|
||||||
|
pub fn as_object(&self) -> Object {
|
||||||
|
let mut obj = BTreeMap::<String, Value>::new();
|
||||||
|
obj.insert("type".into(), self.as_type().into());
|
||||||
|
obj.insert(
|
||||||
|
match self {
|
||||||
|
Self::Collection(_) => "geometries",
|
||||||
|
_ => "coordinates",
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
self.as_coordinates(),
|
||||||
|
);
|
||||||
|
|
||||||
|
obj.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for Geometry {
|
impl PartialOrd for Geometry {
|
||||||
|
|
|
@ -2,9 +2,12 @@ use crate::sql::{fmt::Fmt, strand::no_nul_bytes, Graph, Ident, Idiom, Number, Va
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::Write;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
use super::fmt::{is_pretty, pretty_indent};
|
||||||
|
|
||||||
|
#[revisioned(revision = 2)]
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -20,6 +23,8 @@ pub enum Part {
|
||||||
Value(Value),
|
Value(Value),
|
||||||
Start(Value),
|
Start(Value),
|
||||||
Method(#[serde(with = "no_nul_bytes")] String, Vec<Value>),
|
Method(#[serde(with = "no_nul_bytes")] String, Vec<Value>),
|
||||||
|
#[revision(start = 2)]
|
||||||
|
Destructure(Vec<DestructurePart>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i32> for Part {
|
impl From<i32> for Part {
|
||||||
|
@ -107,6 +112,22 @@ impl fmt::Display for Part {
|
||||||
Part::Graph(v) => write!(f, "{v}"),
|
Part::Graph(v) => write!(f, "{v}"),
|
||||||
Part::Value(v) => write!(f, "[{v}]"),
|
Part::Value(v) => write!(f, "[{v}]"),
|
||||||
Part::Method(v, a) => write!(f, ".{v}({})", Fmt::comma_separated(a)),
|
Part::Method(v, a) => write!(f, ".{v}({})", Fmt::comma_separated(a)),
|
||||||
|
Part::Destructure(v) => {
|
||||||
|
f.write_str(".{")?;
|
||||||
|
if !is_pretty() {
|
||||||
|
f.write_char(' ')?;
|
||||||
|
}
|
||||||
|
if !v.is_empty() {
|
||||||
|
let indent = pretty_indent();
|
||||||
|
write!(f, "{}", Fmt::pretty_comma_separated(v))?;
|
||||||
|
drop(indent);
|
||||||
|
}
|
||||||
|
if is_pretty() {
|
||||||
|
f.write_char('}')
|
||||||
|
} else {
|
||||||
|
f.write_str(" }")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,3 +146,51 @@ impl<'a> Next<'a> for &'a [Part] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum DestructurePart {
|
||||||
|
All(Ident),
|
||||||
|
Field(Ident),
|
||||||
|
Aliased(Ident, Idiom),
|
||||||
|
Destructure(Ident, Vec<DestructurePart>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DestructurePart {
|
||||||
|
pub fn field(&self) -> &Ident {
|
||||||
|
match self {
|
||||||
|
DestructurePart::All(v) => v,
|
||||||
|
DestructurePart::Field(v) => v,
|
||||||
|
DestructurePart::Aliased(v, _) => v,
|
||||||
|
DestructurePart::Destructure(v, _) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> Vec<Part> {
|
||||||
|
match self {
|
||||||
|
DestructurePart::All(v) => vec![Part::Field(v.clone()), Part::All],
|
||||||
|
DestructurePart::Field(v) => vec![Part::Field(v.clone())],
|
||||||
|
DestructurePart::Aliased(_, v) => v.0.clone(),
|
||||||
|
DestructurePart::Destructure(f, d) => {
|
||||||
|
vec![Part::Field(f.clone()), Part::Destructure(d.clone())]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DestructurePart {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
DestructurePart::All(fd) => write!(f, "{fd}.*"),
|
||||||
|
DestructurePart::Field(fd) => write!(f, "{fd}"),
|
||||||
|
DestructurePart::Aliased(fd, v) => write!(f, "{fd}: {v}"),
|
||||||
|
DestructurePart::Destructure(fd, d) => {
|
||||||
|
write!(f, "{fd}{}", Part::Destructure(d.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::dbs::Options;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::exe::try_join_all_buffered;
|
use crate::exe::try_join_all_buffered;
|
||||||
use crate::sql::array::Abolish;
|
use crate::sql::array::Abolish;
|
||||||
|
use crate::sql::part::DestructurePart;
|
||||||
use crate::sql::part::Next;
|
use crate::sql::part::Next;
|
||||||
use crate::sql::part::Part;
|
use crate::sql::part::Part;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
|
@ -25,6 +26,19 @@ impl Value {
|
||||||
Some(p) => match self {
|
Some(p) => match self {
|
||||||
// Current value at path is an object
|
// Current value at path is an object
|
||||||
Value::Object(v) => match p {
|
Value::Object(v) => match p {
|
||||||
|
Part::All => match path.len() {
|
||||||
|
1 => {
|
||||||
|
v.clear();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let path = path.next();
|
||||||
|
for v in v.values_mut() {
|
||||||
|
stk.run(|stk| v.del(stk, ctx, opt, path)).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
Part::Field(f) => match path.len() {
|
Part::Field(f) => match path.len() {
|
||||||
1 => {
|
1 => {
|
||||||
v.remove(f.as_str());
|
v.remove(f.as_str());
|
||||||
|
@ -64,6 +78,20 @@ impl Value {
|
||||||
},
|
},
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
},
|
},
|
||||||
|
Part::Destructure(parts) => {
|
||||||
|
for part in parts {
|
||||||
|
if matches!(part, DestructurePart::Aliased(_, _)) {
|
||||||
|
return Err(Error::UnsupportedDestructure {
|
||||||
|
variant: "An aliased".into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = [part.path().as_slice(), path.next()].concat();
|
||||||
|
stk.run(|stk| self.del(stk, ctx, opt, &path)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
},
|
},
|
||||||
// Current value at path is an array
|
// Current value at path is an array
|
||||||
|
|
|
@ -40,6 +40,14 @@ impl Value {
|
||||||
None => Ok(()),
|
None => Ok(()),
|
||||||
},
|
},
|
||||||
Part::All => stk.run(|stk| self.fetch(stk, ctx, opt, path.next())).await,
|
Part::All => stk.run(|stk| self.fetch(stk, ctx, opt, path.next())).await,
|
||||||
|
Part::Destructure(p) => {
|
||||||
|
for p in p.iter() {
|
||||||
|
let path = [(p.path().as_slice()), path].concat();
|
||||||
|
stk.run(|stk| self.fetch(stk, ctx, opt, &path)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
},
|
},
|
||||||
// Current path part is an array
|
// Current path part is an array
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::cnf::MAX_COMPUTATION_DEPTH;
|
use crate::cnf::MAX_COMPUTATION_DEPTH;
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::dbs::Options;
|
use crate::dbs::Options;
|
||||||
|
@ -51,6 +53,10 @@ impl Value {
|
||||||
let v = v.as_coordinates();
|
let v = v.as_coordinates();
|
||||||
stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await
|
stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await
|
||||||
}
|
}
|
||||||
|
Part::Destructure(_) => {
|
||||||
|
let obj = Value::Object(v.as_object());
|
||||||
|
stk.run(|stk| obj.get(stk, ctx, opt, doc, path)).await
|
||||||
|
}
|
||||||
// Otherwise return none
|
// Otherwise return none
|
||||||
_ => Ok(Value::None),
|
_ => Ok(Value::None),
|
||||||
},
|
},
|
||||||
|
@ -115,6 +121,19 @@ impl Value {
|
||||||
_ => Ok(Value::None),
|
_ => Ok(Value::None),
|
||||||
},
|
},
|
||||||
Part::All => stk.run(|stk| self.get(stk, ctx, opt, doc, path.next())).await,
|
Part::All => stk.run(|stk| self.get(stk, ctx, opt, doc, path.next())).await,
|
||||||
|
Part::Destructure(p) => {
|
||||||
|
let mut obj = BTreeMap::<String, Value>::new();
|
||||||
|
for p in p.iter() {
|
||||||
|
let path = p.path();
|
||||||
|
let v = stk
|
||||||
|
.run(|stk| self.get(stk, ctx, opt, doc, path.as_slice()))
|
||||||
|
.await?;
|
||||||
|
obj.insert(p.field().to_raw(), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj = Value::from(obj);
|
||||||
|
stk.run(|stk| obj.get(stk, ctx, opt, doc, path.next())).await
|
||||||
|
}
|
||||||
_ => Ok(Value::None),
|
_ => Ok(Value::None),
|
||||||
},
|
},
|
||||||
// Current value at path is an array
|
// Current value at path is an array
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use reblessive::Stk;
|
use reblessive::Stk;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
sql::{Dir, Edges, Field, Fields, Graph, Ident, Idiom, Part, Table, Tables, Value},
|
sql::{
|
||||||
|
part::DestructurePart, Dir, Edges, Field, Fields, Graph, Ident, Idiom, Part, Table, Tables,
|
||||||
|
Value,
|
||||||
|
},
|
||||||
syn::token::{t, Span, TokenKind},
|
syn::token::{t, Span, TokenKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -252,10 +255,61 @@ impl Parser<'_> {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
Part::All
|
Part::All
|
||||||
}
|
}
|
||||||
|
t!("{") => {
|
||||||
|
self.pop_peek();
|
||||||
|
self.parse_destructure_part()?
|
||||||
|
}
|
||||||
_ => Part::Field(self.next_token_value()?),
|
_ => Part::Field(self.next_token_value()?),
|
||||||
};
|
};
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
/// Parse the part after the `.{` in an idiom
|
||||||
|
pub fn parse_destructure_part(&mut self) -> ParseResult<Part> {
|
||||||
|
let start = self.last_span();
|
||||||
|
let mut destructured: Vec<DestructurePart> = Vec::new();
|
||||||
|
loop {
|
||||||
|
if self.eat(t!("}")) {
|
||||||
|
// We've reached the end of the destructure
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let field: Ident = self.next_token_value()?;
|
||||||
|
let part = match self.peek_kind() {
|
||||||
|
t!(":") => {
|
||||||
|
self.pop_peek();
|
||||||
|
DestructurePart::Aliased(field, self.parse_local_idiom()?)
|
||||||
|
}
|
||||||
|
t!(".") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let found = self.peek_kind();
|
||||||
|
match self.parse_dot_part()? {
|
||||||
|
Part::All => DestructurePart::All(field),
|
||||||
|
Part::Destructure(v) => DestructurePart::Destructure(field, v),
|
||||||
|
_ => {
|
||||||
|
return Err(ParseError::new(
|
||||||
|
ParseErrorKind::Unexpected {
|
||||||
|
found,
|
||||||
|
expected: "a star or a destructuring",
|
||||||
|
},
|
||||||
|
self.last_span(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => DestructurePart::Field(field),
|
||||||
|
};
|
||||||
|
|
||||||
|
destructured.push(part);
|
||||||
|
|
||||||
|
if !self.eat(t!(",")) {
|
||||||
|
// We've reached the end of the destructure
|
||||||
|
self.expect_closing_delimiter(t!("}"), start)?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Part::Destructure(destructured))
|
||||||
|
}
|
||||||
/// Parse the part after the `[` in a idiom
|
/// Parse the part after the `[` in a idiom
|
||||||
pub async fn parse_bracket_part(&mut self, ctx: &mut Stk, start: Span) -> ParseResult<Part> {
|
pub async fn parse_bracket_part(&mut self, ctx: &mut Stk, start: Span) -> ParseResult<Part> {
|
||||||
let res = match self.peek_kind() {
|
let res = match self.peek_kind() {
|
||||||
|
|
|
@ -1155,3 +1155,72 @@ async fn select_issue_3510() -> Result<(), Error> {
|
||||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_destructure() -> Result<(), Error> {
|
||||||
|
let sql = "
|
||||||
|
CREATE person:1 SET name = 'John', age = 21, obj = { a: 1, b: 2, c: { d: 3, e: 4, f: 5 } };
|
||||||
|
SELECT obj.{ a, c.{ e, f } } FROM person;
|
||||||
|
SELECT * OMIT obj.c.{ d, f } 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(), 3);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:1,
|
||||||
|
name: 'John',
|
||||||
|
age: 21,
|
||||||
|
obj: {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
c: {
|
||||||
|
d: 3,
|
||||||
|
e: 4,
|
||||||
|
f: 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
obj: {
|
||||||
|
a: 1,
|
||||||
|
c: {
|
||||||
|
e: 4,
|
||||||
|
f: 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:1,
|
||||||
|
name: 'John',
|
||||||
|
age: 21,
|
||||||
|
obj: {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
c: { e: 4 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue