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 {
|
||||
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 {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::idx::planner::executor::KnnExpressions;
|
||||
use crate::sql::part::DestructurePart;
|
||||
use crate::sql::{
|
||||
Array, Cast, Cond, Expression, Function, Id, Idiom, Model, Object, Part, Range, Thing, Value,
|
||||
};
|
||||
|
@ -70,6 +71,30 @@ impl<'a> KnnConditionRewriter<'a> {
|
|||
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> {
|
||||
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::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::Destructure(p) => self.eval_destructure_parts(p).map(Part::Destructure),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,12 @@ use geo_types::{MultiLineString, MultiPoint, MultiPolygon};
|
|||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::once;
|
||||
use std::{fmt, hash};
|
||||
|
||||
use super::Object;
|
||||
|
||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Geometry";
|
||||
|
||||
#[revisioned(revision = 1)]
|
||||
|
@ -116,6 +119,21 @@ impl Geometry {
|
|||
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 {
|
||||
|
|
|
@ -2,9 +2,12 @@ use crate::sql::{fmt::Fmt, strand::no_nul_bytes, Graph, Ident, Idiom, Number, Va
|
|||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
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)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
#[non_exhaustive]
|
||||
|
@ -20,6 +23,8 @@ pub enum Part {
|
|||
Value(Value),
|
||||
Start(Value),
|
||||
Method(#[serde(with = "no_nul_bytes")] String, Vec<Value>),
|
||||
#[revision(start = 2)]
|
||||
Destructure(Vec<DestructurePart>),
|
||||
}
|
||||
|
||||
impl From<i32> for Part {
|
||||
|
@ -107,6 +112,22 @@ impl fmt::Display for Part {
|
|||
Part::Graph(v) => write!(f, "{v}"),
|
||||
Part::Value(v) => write!(f, "[{v}]"),
|
||||
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::exe::try_join_all_buffered;
|
||||
use crate::sql::array::Abolish;
|
||||
use crate::sql::part::DestructurePart;
|
||||
use crate::sql::part::Next;
|
||||
use crate::sql::part::Part;
|
||||
use crate::sql::value::Value;
|
||||
|
@ -25,6 +26,19 @@ impl Value {
|
|||
Some(p) => match self {
|
||||
// Current value at path is an object
|
||||
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() {
|
||||
1 => {
|
||||
v.remove(f.as_str());
|
||||
|
@ -64,6 +78,20 @@ impl Value {
|
|||
},
|
||||
_ => 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(()),
|
||||
},
|
||||
// Current value at path is an array
|
||||
|
|
|
@ -40,6 +40,14 @@ impl Value {
|
|||
None => Ok(()),
|
||||
},
|
||||
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(()),
|
||||
},
|
||||
// Current path part is an array
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::cnf::MAX_COMPUTATION_DEPTH;
|
||||
use crate::ctx::Context;
|
||||
use crate::dbs::Options;
|
||||
|
@ -51,6 +53,10 @@ impl Value {
|
|||
let v = v.as_coordinates();
|
||||
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
|
||||
_ => Ok(Value::None),
|
||||
},
|
||||
|
@ -115,6 +121,19 @@ impl Value {
|
|||
_ => Ok(Value::None),
|
||||
},
|
||||
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),
|
||||
},
|
||||
// Current value at path is an array
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use reblessive::Stk;
|
||||
|
||||
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},
|
||||
};
|
||||
|
||||
|
@ -252,10 +255,61 @@ impl Parser<'_> {
|
|||
self.pop_peek();
|
||||
Part::All
|
||||
}
|
||||
t!("{") => {
|
||||
self.pop_peek();
|
||||
self.parse_destructure_part()?
|
||||
}
|
||||
_ => Part::Field(self.next_token_value()?),
|
||||
};
|
||||
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
|
||||
pub async fn parse_bracket_part(&mut self, ctx: &mut Stk, start: Span) -> ParseResult<Part> {
|
||||
let res = match self.peek_kind() {
|
||||
|
|
|
@ -1155,3 +1155,72 @@ async fn select_issue_3510() -> Result<(), Error> {
|
|||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
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