Improve SQL Idiom path parsing

Closes #1653
This commit is contained in:
Tobie Morgan Hitchcock 2023-03-31 16:42:29 +01:00
parent 6e6621565d
commit 2e0093c41d
17 changed files with 270 additions and 221 deletions

View file

@ -7,7 +7,7 @@ use crate::sql::comment::shouldbespace;
use crate::sql::common::commas; use crate::sql::common::commas;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::fmt::Fmt; use crate::sql::fmt::Fmt;
use crate::sql::idiom::{idiom, Idiom}; use crate::sql::idiom::{plain as idiom, Idiom};
use crate::sql::operator::{assigner, Operator}; use crate::sql::operator::{assigner, Operator};
use crate::sql::table::Table; use crate::sql::table::Table;
use crate::sql::thing::Thing; use crate::sql::thing::Thing;

View file

@ -24,7 +24,6 @@ pub fn number(i: &str) -> IResult<&str, ()> {
map(char('"'), |_| ()), map(char('"'), |_| ()),
map(char('\''), |_| ()), map(char('\''), |_| ()),
map(char(';'), |_| ()), map(char(';'), |_| ()),
map(char('.'), |_| ()),
map(char(','), |_| ()), map(char(','), |_| ()),
map(tag(".."), |_| ()), map(tag(".."), |_| ()),
map(eof, |_| ()), map(eof, |_| ()),

View file

@ -2,7 +2,7 @@ use crate::sql::comment::shouldbespace;
use crate::sql::common::commas; use crate::sql::common::commas;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::fmt::Fmt; use crate::sql::fmt::Fmt;
use crate::sql::idiom::{idiom, Idiom}; use crate::sql::idiom::{plain as idiom, Idiom};
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::multi::separated_list1; use nom::multi::separated_list1;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View file

@ -7,7 +7,7 @@ use crate::sql::common::commas;
use crate::sql::ending::field as ending; use crate::sql::ending::field as ending;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::fmt::Fmt; use crate::sql::fmt::Fmt;
use crate::sql::idiom::{idiom, Idiom}; use crate::sql::idiom::{plain as idiom, Idiom};
use crate::sql::part::Part; use crate::sql::part::Part;
use crate::sql::value::{value, Value}; use crate::sql::value::{value, Value};
use nom::branch::alt; use nom::branch::alt;

View file

@ -265,7 +265,7 @@ pub fn function(i: &str) -> IResult<&str, Function> {
alt((normal, custom, script, cast))(i) alt((normal, custom, script, cast))(i)
} }
fn normal(i: &str) -> IResult<&str, Function> { pub fn normal(i: &str) -> IResult<&str, Function> {
let (i, s) = function_names(i)?; let (i, s) = function_names(i)?;
let (i, _) = char('(')(i)?; let (i, _) = char('(')(i)?;
let (i, _) = mightbespace(i)?; let (i, _) = mightbespace(i)?;
@ -275,7 +275,7 @@ fn normal(i: &str) -> IResult<&str, Function> {
Ok((i, Function::Normal(s.to_string(), a))) Ok((i, Function::Normal(s.to_string(), a)))
} }
fn custom(i: &str) -> IResult<&str, Function> { pub fn custom(i: &str) -> IResult<&str, Function> {
let (i, _) = tag("fn::")(i)?; let (i, _) = tag("fn::")(i)?;
let (i, s) = take_while1(val_char)(i)?; let (i, s) = take_while1(val_char)(i)?;
let (i, _) = char('(')(i)?; let (i, _) = char('(')(i)?;

View file

@ -3,7 +3,7 @@ use crate::sql::comment::shouldbespace;
use crate::sql::cond::{cond, Cond}; use crate::sql::cond::{cond, Cond};
use crate::sql::dir::{dir, Dir}; use crate::sql::dir::{dir, Dir};
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::idiom::{idiom, Idiom}; use crate::sql::idiom::{plain as idiom, Idiom};
use crate::sql::table::{table, tables, Tables}; use crate::sql::table::{table, tables, Tables};
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;

View file

@ -24,14 +24,14 @@ const BACKTICK_ESC: &str = r#"\`"#;
pub struct Ident(pub String); pub struct Ident(pub String);
impl From<String> for Ident { impl From<String> for Ident {
fn from(s: String) -> Self { fn from(v: String) -> Self {
Self(s) Self(v)
} }
} }
impl From<&str> for Ident { impl From<&str> for Ident {
fn from(i: &str) -> Self { fn from(v: &str) -> Self {
Self::from(String::from(i)) Self::from(String::from(v))
} }
} }

View file

@ -6,7 +6,7 @@ use crate::sql::common::commas;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::fmt::{fmt_separated_by, Fmt}; use crate::sql::fmt::{fmt_separated_by, Fmt};
use crate::sql::part::Next; use crate::sql::part::Next;
use crate::sql::part::{all, field, first, graph, index, last, part, thing, Part}; use crate::sql::part::{all, field, first, graph, index, last, part, value, Part};
use crate::sql::paths::{ID, IN, OUT}; use crate::sql::paths::{ID, IN, OUT};
use crate::sql::serde::is_internal_serialization; use crate::sql::serde::is_internal_serialization;
use crate::sql::value::Value; use crate::sql::value::Value;
@ -92,7 +92,7 @@ impl Idiom {
self.0 self.0
.iter() .iter()
.cloned() .cloned()
.filter(|p| matches!(p, Part::Field(_) | Part::Thing(_) | Part::Graph(_))) .filter(|p| matches!(p, Part::Field(_) | Part::Value(_) | Part::Graph(_)))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into() .into()
} }
@ -127,14 +127,13 @@ impl Idiom {
doc: Option<&Value>, doc: Option<&Value>,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
match self.first() { match self.first() {
// The first part is a thing record // The starting part is a value
Some(Part::Thing(v)) => { Some(Part::Value(v)) => {
// Use the thing as the document v.compute(ctx, opt, txn, doc)
let v: Value = v.clone().into();
// Fetch the Idiom from the document
v.get(ctx, opt, txn, self.as_ref().next())
.await? .await?
.compute(ctx, opt, txn, Some(&v)) .get(ctx, opt, txn, self.as_ref().next())
.await?
.compute(ctx, opt, txn, doc)
.await .await
} }
// Otherwise use the current document // Otherwise use the current document
@ -148,7 +147,7 @@ impl Idiom {
} }
} }
impl fmt::Display for Idiom { impl Display for Idiom {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt( Display::fmt(
&Fmt::new( &Fmt::new(
@ -178,7 +177,7 @@ impl Serialize for Idiom {
} }
} }
/// Used in a DEFINE FIELD and DEFINE INDEX clauses /// Used in DEFINE FIELD and DEFINE INDEX clauses
pub fn local(i: &str) -> IResult<&str, Idiom> { pub fn local(i: &str) -> IResult<&str, Idiom> {
let (i, p) = first(i)?; let (i, p) = first(i)?;
let (i, mut v) = many0(alt((all, index, field)))(i)?; let (i, mut v) = many0(alt((all, index, field)))(i)?;
@ -194,35 +193,50 @@ pub fn basic(i: &str) -> IResult<&str, Idiom> {
Ok((i, Idiom::from(v))) Ok((i, Idiom::from(v)))
} }
/// Used in a $param definition /// A simple idiom with one or more parts
pub fn param(i: &str) -> IResult<&str, Idiom> { pub fn plain(i: &str) -> IResult<&str, Idiom> {
let (i, p) = alt((first, graph))(i)?;
let (i, mut v) = many0(part)(i)?;
v.insert(0, p);
Ok((i, Idiom::from(v)))
}
/// A complex idiom with graph or many parts
pub fn multi(i: &str) -> IResult<&str, Idiom> {
alt((
|i| {
let (i, p) = graph(i)?;
let (i, mut v) = many0(part)(i)?;
v.insert(0, p);
Ok((i, Idiom::from(v)))
},
|i| {
let (i, p) = first(i)?;
let (i, mut v) = many1(part)(i)?;
v.insert(0, p);
Ok((i, Idiom::from(v)))
},
|i| {
let (i, p) = value(i)?;
let (i, mut v) = many1(part)(i)?;
v.insert(0, p);
Ok((i, Idiom::from(v)))
},
))(i)
}
/// A simple field based idiom
pub fn path(i: &str) -> IResult<&str, Idiom> {
let (i, p) = first(i)?; let (i, p) = first(i)?;
let (i, mut v) = many0(part)(i)?; let (i, mut v) = many0(part)(i)?;
v.insert(0, p); v.insert(0, p);
Ok((i, Idiom::from(v))) Ok((i, Idiom::from(v)))
} }
/// Used in a RELATE statement /// A full complex idiom with any number of parts
pub fn plain(i: &str) -> IResult<&str, Idiom> { #[cfg(test)]
let (i, p) = first(i)?;
Ok((i, Idiom::from(vec![p])))
}
pub fn idiom(i: &str) -> IResult<&str, Idiom> { pub fn idiom(i: &str) -> IResult<&str, Idiom> {
alt(( alt((plain, multi))(i)
|i| {
let (i, p) = alt((thing, graph))(i)?;
let (i, mut v) = many1(part)(i)?;
v.insert(0, p);
Ok((i, Idiom::from(v)))
},
|i| {
let (i, p) = alt((first, graph))(i)?;
let (i, mut v) = many0(part)(i)?;
v.insert(0, p);
Ok((i, Idiom::from(v)))
},
))(i)
} }
#[cfg(test)] #[cfg(test)]
@ -232,10 +246,19 @@ mod tests {
use crate::sql::dir::Dir; use crate::sql::dir::Dir;
use crate::sql::expression::Expression; use crate::sql::expression::Expression;
use crate::sql::graph::Graph; use crate::sql::graph::Graph;
use crate::sql::number::Number;
use crate::sql::param::Param;
use crate::sql::table::Table; use crate::sql::table::Table;
use crate::sql::test::Parse; use crate::sql::test::Parse;
use crate::sql::thing::Thing; use crate::sql::thing::Thing;
#[test]
fn idiom_number() {
let sql = "13.495";
let res = idiom(sql);
assert!(res.is_err());
}
#[test] #[test]
fn idiom_normal() { fn idiom_normal() {
let sql = "test"; let sql = "test";
@ -273,7 +296,7 @@ mod tests {
assert!(res.is_ok()); assert!(res.is_ok());
let out = res.unwrap().1; let out = res.unwrap().1;
assert_eq!("test.temp", format!("{}", out)); assert_eq!("test.temp", format!("{}", out));
assert_eq!(out, Idiom(vec![Part::from("test"), Part::from("temp"),])); assert_eq!(out, Idiom(vec![Part::from("test"), Part::from("temp")]));
} }
#[test] #[test]
@ -283,7 +306,7 @@ mod tests {
assert!(res.is_ok()); assert!(res.is_ok());
let out = res.unwrap().1; let out = res.unwrap().1;
assert_eq!("test.`some key`", format!("{}", out)); assert_eq!("test.`some key`", format!("{}", out));
assert_eq!(out, Idiom(vec![Part::from("test"), Part::from("some key"),])); assert_eq!(out, Idiom(vec![Part::from("test"), Part::from("some key")]));
} }
#[test] #[test]
@ -293,7 +316,7 @@ mod tests {
assert!(res.is_ok()); assert!(res.is_ok());
let out = res.unwrap().1; let out = res.unwrap().1;
assert_eq!("test.temp[*]", format!("{}", out)); assert_eq!("test.temp[*]", format!("{}", out));
assert_eq!(out, Idiom(vec![Part::from("test"), Part::from("temp"), Part::All,])); assert_eq!(out, Idiom(vec![Part::from("test"), Part::from("temp"), Part::All]));
} }
#[test] #[test]
@ -303,7 +326,7 @@ mod tests {
assert!(res.is_ok()); assert!(res.is_ok());
let out = res.unwrap().1; let out = res.unwrap().1;
assert_eq!("test.temp[$]", format!("{}", out)); assert_eq!("test.temp[$]", format!("{}", out));
assert_eq!(out, Idiom(vec![Part::from("test"), Part::from("temp"), Part::Last,])); assert_eq!(out, Idiom(vec![Part::from("test"), Part::from("temp"), Part::Last]));
} }
#[test] #[test]
@ -331,7 +354,7 @@ mod tests {
Idiom(vec![ Idiom(vec![
Part::from("test"), Part::from("test"),
Part::from("temp"), Part::from("temp"),
Part::from(Value::from(Expression::parse("test = true"))), Part::Where(Value::from(Expression::parse("test = true"))),
Part::from("text") Part::from("text")
]) ])
); );
@ -349,12 +372,30 @@ mod tests {
Idiom(vec![ Idiom(vec![
Part::from("test"), Part::from("test"),
Part::from("temp"), Part::from("temp"),
Part::from(Value::from(Expression::parse("test = true"))), Part::Where(Value::from(Expression::parse("test = true"))),
Part::from("text") Part::from("text")
]) ])
); );
} }
#[test]
fn idiom_start_param_local_field() {
let sql = "$test.temporary[0].embedded";
let res = idiom(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("$test.temporary[0].embedded", format!("{}", out));
assert_eq!(
out,
Idiom(vec![
Part::Value(Param::from("test").into()),
Part::from("temporary"),
Part::Index(Number::Int(0)),
Part::from("embedded"),
])
);
}
#[test] #[test]
fn idiom_start_thing_remote_traversal() { fn idiom_start_thing_remote_traversal() {
let sql = "person:test.friend->like->person"; let sql = "person:test.friend->like->person";
@ -365,15 +406,15 @@ mod tests {
assert_eq!( assert_eq!(
out, out,
Idiom(vec![ Idiom(vec![
Part::from(Thing::from(("person", "test"))), Part::Value(Thing::from(("person", "test")).into()),
Part::from("friend"), Part::from("friend"),
Part::from(Graph { Part::Graph(Graph {
dir: Dir::Out, dir: Dir::Out,
what: Table::from("like").into(), what: Table::from("like").into(),
cond: None, cond: None,
alias: None, alias: None,
}), }),
Part::from(Graph { Part::Graph(Graph {
dir: Dir::Out, dir: Dir::Out,
what: Table::from("person").into(), what: Table::from("person").into(),
cond: None, cond: None,

View file

@ -3,10 +3,7 @@ use crate::dbs::Options;
use crate::dbs::Transaction; use crate::dbs::Transaction;
use crate::err::Error; use crate::err::Error;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::idiom; use crate::sql::ident::{ident, Ident};
use crate::sql::idiom::Idiom;
use crate::sql::part::Next;
use crate::sql::part::Part;
use crate::sql::serde::is_internal_serialization; use crate::sql::serde::is_internal_serialization;
use crate::sql::value::Value; use crate::sql::value::Value;
use nom::character::complete::char; use nom::character::complete::char;
@ -18,16 +15,28 @@ use std::str;
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Param"; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Param";
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Deserialize, Hash)] #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Deserialize, Hash)]
pub struct Param(pub Idiom); pub struct Param(pub Ident);
impl From<Idiom> for Param { impl From<Ident> for Param {
fn from(p: Idiom) -> Self { fn from(v: Ident) -> Self {
Self(p) Self(v)
}
}
impl From<String> for Param {
fn from(v: String) -> Self {
Self(v.into())
}
}
impl From<&str> for Param {
fn from(v: &str) -> Self {
Self(v.into())
} }
} }
impl Deref for Param { impl Deref for Param {
type Target = Idiom; type Target = Ident;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }
@ -41,54 +50,36 @@ impl Param {
txn: &Transaction, txn: &Transaction,
doc: Option<&Value>, doc: Option<&Value>,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
// Find a base variable by name // Find the variable by name
match self.first() { match self.as_str() {
// The first part will be a field // This is a special param
Some(Part::Field(v)) => match v.as_str() { "this" | "self" => match doc {
// This is a special param // The base document exists
"this" | "self" => match doc { Some(v) => v.compute(ctx, opt, txn, doc).await,
// The base document exists // The base document does not exist
Some(v) => { None => Ok(Value::None),
// Get the path parts },
let pth: &[Part] = self; // This is a normal param
// Process the parameter value v => match ctx.value(v) {
let res = v.compute(ctx, opt, txn, doc).await?; // The param has been set locally
// Return the desired field Some(v) => v.compute(ctx, opt, txn, doc).await,
res.get(ctx, opt, txn, pth.next()).await // The param has not been set locally
} None => {
// The base document does not exist // Clone transaction
None => Ok(Value::None), let run = txn.clone();
}, // Claim transaction
// This is a normal param let mut run = run.lock().await;
_ => match ctx.value(v) { // Get the param definition
// The param has been set locally let val = run.get_pa(opt.ns(), opt.db(), v).await;
Some(v) => { // Check if the param has been set globally
// Get the path parts match val {
let pth: &[Part] = self; // The param has been set globally
// Process the parameter value Ok(v) => Ok(v.value),
let res = v.compute(ctx, opt, txn, doc).await?; // The param has not been set globally
// Return the desired field Err(_) => Ok(Value::None),
res.get(ctx, opt, txn, pth.next()).await }
} }
// The param has not been set locally
None => {
// Clone transaction
let run = txn.clone();
// Claim transaction
let mut run = run.lock().await;
// Get the param definition
let val = run.get_pa(opt.ns(), opt.db(), v).await;
// Check if the param has been set globally
match val {
// The param has been set globally
Ok(v) => Ok(v.value),
// The param has not been set globally
Err(_) => Ok(Value::None),
}
}
},
}, },
_ => unreachable!(),
} }
} }
} }
@ -114,13 +105,7 @@ impl Serialize for Param {
pub fn param(i: &str) -> IResult<&str, Param> { pub fn param(i: &str) -> IResult<&str, Param> {
let (i, _) = char('$')(i)?; let (i, _) = char('$')(i)?;
let (i, v) = idiom::param(i)?; let (i, v) = ident(i)?;
Ok((i, Param::from(v)))
}
pub fn plain(i: &str) -> IResult<&str, Param> {
let (i, _) = char('$')(i)?;
let (i, v) = idiom::plain(i)?;
Ok((i, Param::from(v))) Ok((i, Param::from(v)))
} }
@ -149,14 +134,4 @@ mod tests {
assert_eq!("$test_and_deliver", format!("{}", out)); assert_eq!("$test_and_deliver", format!("{}", out));
assert_eq!(out, Param::parse("$test_and_deliver")); assert_eq!(out, Param::parse("$test_and_deliver"));
} }
#[test]
fn param_embedded() {
let sql = "$test.temporary[0].embedded";
let res = param(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("$test.temporary[0].embedded", format!("{}", out));
assert_eq!(out, Param::parse("$test.temporary[0].embedded"));
}
} }

View file

@ -1,16 +1,17 @@
use crate::sql::comment::shouldbespace; use crate::sql::comment::shouldbespace;
use crate::sql::ending::ident as ending; use crate::sql::ending::ident as ending;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::graph::{graph as graph_raw, Graph}; use crate::sql::graph::{self, Graph};
use crate::sql::ident::{ident, Ident}; use crate::sql::ident::{self, Ident};
use crate::sql::idiom::Idiom; use crate::sql::idiom::Idiom;
use crate::sql::number::{number, Number}; use crate::sql::number::{number, Number};
use crate::sql::thing::{thing as thing_raw, Thing}; use crate::sql::value::{self, Value};
use crate::sql::value::{value, Value};
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::char; use nom::character::complete::char;
use nom::combinator::not;
use nom::combinator::peek;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::str; use std::str;
@ -23,8 +24,8 @@ pub enum Part {
Field(Ident), Field(Ident),
Index(Number), Index(Number),
Where(Value), Where(Value),
Thing(Thing),
Graph(Graph), Graph(Graph),
Value(Value),
} }
impl From<i32> for Part { impl From<i32> for Part {
@ -45,6 +46,12 @@ impl From<usize> for Part {
} }
} }
impl From<String> for Part {
fn from(v: String) -> Self {
Self::Field(v.into())
}
}
impl From<Number> for Part { impl From<Number> for Part {
fn from(v: Number) -> Self { fn from(v: Number) -> Self {
Self::Index(v) Self::Index(v)
@ -63,24 +70,12 @@ impl From<Value> for Part {
} }
} }
impl From<Thing> for Part {
fn from(v: Thing) -> Self {
Self::Thing(v)
}
}
impl From<Graph> for Part { impl From<Graph> for Part {
fn from(v: Graph) -> Self { fn from(v: Graph) -> Self {
Self::Graph(v) Self::Graph(v)
} }
} }
impl From<String> for Part {
fn from(v: String) -> Self {
Self::Field(Ident(v))
}
}
impl From<&str> for Part { impl From<&str> for Part {
fn from(v: &str) -> Self { fn from(v: &str) -> Self {
match v.parse::<isize>() { match v.parse::<isize>() {
@ -109,8 +104,8 @@ impl fmt::Display for Part {
Part::Field(v) => write!(f, ".{v}"), Part::Field(v) => write!(f, ".{v}"),
Part::Index(v) => write!(f, "[{v}]"), Part::Index(v) => write!(f, "[{v}]"),
Part::Where(v) => write!(f, "[WHERE {v}]"), Part::Where(v) => write!(f, "[WHERE {v}]"),
Part::Thing(v) => write!(f, "{v}"),
Part::Graph(v) => write!(f, "{v}"), Part::Graph(v) => write!(f, "{v}"),
Part::Value(v) => write!(f, "{v}"),
} }
} }
} }
@ -137,7 +132,8 @@ pub fn part(i: &str) -> IResult<&str, Part> {
} }
pub fn first(i: &str) -> IResult<&str, Part> { pub fn first(i: &str) -> IResult<&str, Part> {
let (i, v) = ident(i)?; let (i, _) = peek(not(number))(i)?;
let (i, v) = ident::ident(i)?;
let (i, _) = ending(i)?; let (i, _) = ending(i)?;
Ok((i, Part::Field(v))) Ok((i, Part::Field(v)))
} }
@ -175,7 +171,7 @@ pub fn index(i: &str) -> IResult<&str, Part> {
pub fn field(i: &str) -> IResult<&str, Part> { pub fn field(i: &str) -> IResult<&str, Part> {
let (i, _) = char('.')(i)?; let (i, _) = char('.')(i)?;
let (i, v) = ident(i)?; let (i, v) = ident::ident(i)?;
let (i, _) = ending(i)?; let (i, _) = ending(i)?;
Ok((i, Part::Field(v))) Ok((i, Part::Field(v)))
} }
@ -184,18 +180,18 @@ pub fn filter(i: &str) -> IResult<&str, Part> {
let (i, _) = char('[')(i)?; let (i, _) = char('[')(i)?;
let (i, _) = alt((tag_no_case("WHERE"), tag("?")))(i)?; let (i, _) = alt((tag_no_case("WHERE"), tag("?")))(i)?;
let (i, _) = shouldbespace(i)?; let (i, _) = shouldbespace(i)?;
let (i, v) = value(i)?; let (i, v) = value::value(i)?;
let (i, _) = char(']')(i)?; let (i, _) = char(']')(i)?;
Ok((i, Part::Where(v))) Ok((i, Part::Where(v)))
} }
pub fn thing(i: &str) -> IResult<&str, Part> { pub fn value(i: &str) -> IResult<&str, Part> {
let (i, v) = thing_raw(i)?; let (i, v) = value::start(i)?;
Ok((i, Part::Thing(v))) Ok((i, Part::Value(v)))
} }
pub fn graph(i: &str) -> IResult<&str, Part> { pub fn graph(i: &str) -> IResult<&str, Part> {
let (i, v) = graph_raw(i)?; let (i, v) = graph::graph(i)?;
Ok((i, Part::Graph(v))) Ok((i, Part::Graph(v)))
} }

View file

@ -12,7 +12,7 @@ use crate::sql::comment::shouldbespace;
use crate::sql::data::{data, Data}; use crate::sql::data::{data, Data};
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::output::{output, Output}; use crate::sql::output::{output, Output};
use crate::sql::param::plain as param; use crate::sql::param::param;
use crate::sql::subquery::subquery; use crate::sql::subquery::subquery;
use crate::sql::table::table; use crate::sql::table::table;
use crate::sql::thing::thing; use crate::sql::thing::thing;

View file

@ -97,6 +97,29 @@ impl Value {
try_join_all(futs).await.map(Into::into) try_join_all(futs).await.map(Into::into)
} }
}, },
// Current path part is an edges
Value::Edges(v) => {
// Clone the thing
let val = v.clone();
// Check how many path parts are remaining
match path.len() {
// No remote embedded fields, so just return this
0 => Ok(Value::Edges(val)),
// Remote embedded field, so fetch the thing
_ => {
let stm = SelectStatement {
expr: Fields(vec![Field::All], false),
what: Values(vec![Value::from(val)]),
..SelectStatement::default()
};
stm.compute(ctx, opt, txn, None)
.await?
.first()
.get(ctx, opt, txn, path)
.await
}
}
}
// Current path part is a thing // Current path part is a thing
Value::Thing(v) => { Value::Thing(v) => {
// Clone the thing // Clone the thing

View file

@ -9,14 +9,6 @@ impl Value {
Some(p) => match self { Some(p) => match self {
// Current path part is an object // Current path part is an object
Value::Object(v) => match p { Value::Object(v) => match p {
Part::Thing(t) => match v.get_mut(t.to_raw().as_str()) {
Some(v) if v.is_some() => v.put(path.next(), val),
_ => {
let mut obj = Value::base();
obj.put(path.next(), val);
v.insert(t.to_raw(), obj);
}
},
Part::Graph(g) => match v.get_mut(g.to_raw().as_str()) { Part::Graph(g) => match v.get_mut(g.to_raw().as_str()) {
Some(v) if v.is_some() => v.put(path.next(), val), Some(v) if v.is_some() => v.put(path.next(), val),
_ => { _ => {

View file

@ -54,8 +54,8 @@ impl ser::Serializer for Serializer {
"Field" => Ok(Part::Field(Ident(value.serialize(ser::string::Serializer.wrap())?))), "Field" => Ok(Part::Field(Ident(value.serialize(ser::string::Serializer.wrap())?))),
"Index" => Ok(Part::Index(value.serialize(ser::number::Serializer.wrap())?)), "Index" => Ok(Part::Index(value.serialize(ser::number::Serializer.wrap())?)),
"Where" => Ok(Part::Where(value.serialize(ser::value::Serializer.wrap())?)), "Where" => Ok(Part::Where(value.serialize(ser::value::Serializer.wrap())?)),
"Thing" => Ok(Part::Thing(value.serialize(ser::thing::Serializer.wrap())?)),
"Graph" => Ok(Part::Graph(value.serialize(ser::graph::Serializer.wrap())?)), "Graph" => Ok(Part::Graph(value.serialize(ser::graph::Serializer.wrap())?)),
"Value" => Ok(Part::Value(value.serialize(ser::value::Serializer.wrap())?)),
variant => { variant => {
Err(Error::custom(format!("unexpected newtype variant `{name}::{variant}`"))) Err(Error::custom(format!("unexpected newtype variant `{name}::{variant}`")))
} }
@ -113,17 +113,17 @@ mod tests {
assert_eq!(part, serialized); assert_eq!(part, serialized);
} }
#[test]
fn thing() {
let part = Part::Thing(sql::thing("foo:bar").unwrap());
let serialized = serialize_internal(|| part.serialize(Serializer.wrap())).unwrap();
assert_eq!(part, serialized);
}
#[test] #[test]
fn graph() { fn graph() {
let part = Part::Graph(Default::default()); let part = Part::Graph(Default::default());
let serialized = serialize_internal(|| part.serialize(Serializer.wrap())).unwrap(); let serialized = serialize_internal(|| part.serialize(Serializer.wrap())).unwrap();
assert_eq!(part, serialized); assert_eq!(part, serialized);
} }
#[test]
fn value() {
let part = Part::Value(sql::thing("foo:bar").unwrap().into());
let serialized = serialize_internal(|| part.serialize(Serializer.wrap())).unwrap();
assert_eq!(part, serialized);
}
} }

View file

@ -13,6 +13,7 @@ use crate::sql::Block;
use crate::sql::Datetime; use crate::sql::Datetime;
use crate::sql::Duration; use crate::sql::Duration;
use crate::sql::Future; use crate::sql::Future;
use crate::sql::Ident;
use crate::sql::Idiom; use crate::sql::Idiom;
use crate::sql::Param; use crate::sql::Param;
use crate::sql::Regex; use crate::sql::Regex;
@ -222,7 +223,7 @@ impl ser::Serializer for Serializer {
Ok(Value::Idiom(Idiom(value.serialize(ser::part::vec::Serializer.wrap())?))) Ok(Value::Idiom(Idiom(value.serialize(ser::part::vec::Serializer.wrap())?)))
} }
sql::param::TOKEN => { sql::param::TOKEN => {
Ok(Value::Param(Param(Idiom(value.serialize(ser::part::vec::Serializer.wrap())?)))) Ok(Value::Param(Param(Ident(value.serialize(ser::string::Serializer.wrap())?))))
} }
sql::array::TOKEN => Ok(Value::Array(Array(value.serialize(vec::Serializer.wrap())?))), sql::array::TOKEN => Ok(Value::Array(Array(value.serialize(vec::Serializer.wrap())?))),
sql::object::TOKEN => { sql::object::TOKEN => {

View file

@ -24,15 +24,6 @@ impl Value {
Some(p) => match self { Some(p) => match self {
// Current path part is an object // Current path part is an object
Value::Object(v) => match p { Value::Object(v) => match p {
Part::Thing(t) => match v.get_mut(t.to_raw().as_str()) {
Some(v) if v.is_some() => v.set(ctx, opt, txn, path.next(), val).await,
_ => {
let mut obj = Value::base();
obj.set(ctx, opt, txn, path.next(), val).await?;
v.insert(t.to_raw(), obj);
Ok(())
}
},
Part::Graph(g) => match v.get_mut(g.to_raw().as_str()) { Part::Graph(g) => match v.get_mut(g.to_raw().as_str()) {
Some(v) if v.is_some() => v.set(ctx, opt, txn, path.next(), val).await, Some(v) if v.is_some() => v.set(ctx, opt, txn, path.next(), val).await,
_ => { _ => {

View file

@ -14,11 +14,11 @@ use crate::sql::edges::{edges, Edges};
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::expression::{expression, Expression}; use crate::sql::expression::{expression, Expression};
use crate::sql::fmt::{Fmt, Pretty}; use crate::sql::fmt::{Fmt, Pretty};
use crate::sql::function::{function, Function}; use crate::sql::function::{self, function, Function};
use crate::sql::future::{future, Future}; use crate::sql::future::{future, Future};
use crate::sql::geometry::{geometry, Geometry}; use crate::sql::geometry::{geometry, Geometry};
use crate::sql::id::Id; use crate::sql::id::Id;
use crate::sql::idiom::{idiom, Idiom}; use crate::sql::idiom::{self, Idiom};
use crate::sql::kind::Kind; use crate::sql::kind::Kind;
use crate::sql::model::{model, Model}; use crate::sql::model::{model, Model};
use crate::sql::number::{number, Number}; use crate::sql::number::{number, Number};
@ -280,6 +280,12 @@ impl From<Expression> for Value {
} }
} }
impl From<Box<Edges>> for Value {
fn from(v: Box<Edges>) -> Self {
Value::Edges(v)
}
}
impl From<i8> for Value { impl From<i8> for Value {
fn from(v: i8) -> Self { fn from(v: i8) -> Self {
Value::Number(Number::from(v)) Value::Number(Number::from(v))
@ -962,8 +968,8 @@ impl Value {
/// Converts this Value into a field name /// Converts this Value into a field name
pub fn to_idiom(&self) -> Idiom { pub fn to_idiom(&self) -> Idiom {
match self { match self {
Value::Param(v) => v.simplify(),
Value::Idiom(v) => v.simplify(), Value::Idiom(v) => v.simplify(),
Value::Param(v) => v.to_raw().into(),
Value::Strand(v) => v.0.to_string().into(), Value::Strand(v) => v.0.to_string().into(),
Value::Datetime(v) => v.0.to_string().into(), Value::Datetime(v) => v.0.to_string().into(),
Value::Future(_) => "future".to_string().into(), Value::Future(_) => "future".to_string().into(),
@ -1545,14 +1551,12 @@ impl ops::Div for Value {
} }
} }
/// Parse any `Value` including binary expressions
pub fn value(i: &str) -> IResult<&str, Value> { pub fn value(i: &str) -> IResult<&str, Value> {
alt((double, single))(i) alt((map(expression, Value::from), single))(i)
}
pub fn double(i: &str) -> IResult<&str, Value> {
map(expression, Value::from)(i)
} }
/// Parse any `Value` excluding binary expressions
pub fn single(i: &str) -> IResult<&str, Value> { pub fn single(i: &str) -> IResult<&str, Value> {
alt(( alt((
alt(( alt((
@ -1562,41 +1566,9 @@ pub fn single(i: &str) -> IResult<&str, Value> {
map(tag_no_case("false"), |_| Value::False), map(tag_no_case("false"), |_| Value::False),
)), )),
alt(( alt((
map(subquery, Value::from), map(idiom::multi, Value::from),
map(function, Value::from), map(function, Value::from),
map(constant, Value::from),
map(datetime, Value::from),
map(duration, Value::from),
map(geometry, Value::from),
map(future, Value::from),
map(unique, Value::from),
map(number, Value::from),
map(object, Value::from),
map(array, Value::from),
map(block, Value::from),
map(param, Value::from),
map(regex, Value::from),
map(model, Value::from),
map(idiom, Value::from),
map(range, Value::from),
map(thing, Value::from),
map(strand, Value::from),
)),
))(i)
}
pub fn select(i: &str) -> IResult<&str, Value> {
alt((
alt((
map(tag_no_case("NONE"), |_| Value::None),
map(tag_no_case("NULL"), |_| Value::Null),
map(tag_no_case("true"), |_| Value::True),
map(tag_no_case("false"), |_| Value::False),
)),
alt((
map(expression, Value::from),
map(subquery, Value::from), map(subquery, Value::from),
map(function, Value::from),
map(constant, Value::from), map(constant, Value::from),
map(datetime, Value::from), map(datetime, Value::from),
map(duration, Value::from), map(duration, Value::from),
@ -1613,17 +1585,75 @@ pub fn select(i: &str) -> IResult<&str, Value> {
map(edges, Value::from), map(edges, Value::from),
map(range, Value::from), map(range, Value::from),
map(thing, Value::from), map(thing, Value::from),
map(table, Value::from),
map(strand, Value::from), map(strand, Value::from),
map(idiom::path, Value::from),
)), )),
))(i) ))(i)
} }
pub fn select(i: &str) -> IResult<&str, Value> {
alt((
alt((
map(expression, Value::from),
map(tag_no_case("NONE"), |_| Value::None),
map(tag_no_case("NULL"), |_| Value::Null),
map(tag_no_case("true"), |_| Value::True),
map(tag_no_case("false"), |_| Value::False),
)),
alt((
map(idiom::multi, Value::from),
map(function, Value::from),
map(subquery, Value::from),
map(constant, Value::from),
map(datetime, Value::from),
map(duration, Value::from),
map(geometry, Value::from),
map(future, Value::from),
map(unique, Value::from),
map(number, Value::from),
map(strand, Value::from),
map(object, Value::from),
map(array, Value::from),
map(block, Value::from),
map(param, Value::from),
map(regex, Value::from),
map(model, Value::from),
map(edges, Value::from),
map(range, Value::from),
map(thing, Value::from),
map(table, Value::from),
)),
))(i)
}
/// Used as the starting part of a complex Idiom
pub fn start(i: &str) -> IResult<&str, Value> {
alt((
map(function::normal, Value::from),
map(function::custom, Value::from),
map(subquery, Value::from),
map(constant, Value::from),
map(datetime, Value::from),
map(duration, Value::from),
map(unique, Value::from),
map(number, Value::from),
map(strand, Value::from),
map(object, Value::from),
map(array, Value::from),
map(param, Value::from),
map(edges, Value::from),
map(thing, Value::from),
))(i)
}
/// Used in CREATE, UPDATE, and DELETE clauses
pub fn what(i: &str) -> IResult<&str, Value> { pub fn what(i: &str) -> IResult<&str, Value> {
alt(( alt((
map(subquery, Value::from),
map(function, Value::from), map(function, Value::from),
map(subquery, Value::from),
map(constant, Value::from), map(constant, Value::from),
map(datetime, Value::from),
map(duration, Value::from),
map(future, Value::from), map(future, Value::from),
map(block, Value::from), map(block, Value::from),
map(param, Value::from), map(param, Value::from),
@ -1635,6 +1665,7 @@ pub fn what(i: &str) -> IResult<&str, Value> {
))(i) ))(i)
} }
/// Used to parse any simple JSON-like value
pub fn json(i: &str) -> IResult<&str, Value> { pub fn json(i: &str) -> IResult<&str, Value> {
alt(( alt((
map(tag_no_case("NULL"), |_| Value::Null), map(tag_no_case("NULL"), |_| Value::Null),