From c0a78d8470973b971b09c0ae8a4c0bcef29207f2 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Thu, 9 Jun 2022 09:18:08 +0100 Subject: [PATCH] Implement graph traversal functionality --- lib/src/dbs/channel.rs | 232 ++++++++++++++++++++++++------- lib/src/dbs/iterate.rs | 232 ++++++++++++++++++++++++------- lib/src/dbs/iterator.rs | 2 + lib/src/sql/edges.rs | 106 ++++++++++++++ lib/src/sql/ending.rs | 42 ++++++ lib/src/sql/graph.rs | 6 + lib/src/sql/idiom.rs | 80 +++++++++-- lib/src/sql/mod.rs | 3 + lib/src/sql/number.rs | 33 +---- lib/src/sql/part.rs | 17 +++ lib/src/sql/statements/delete.rs | 2 + lib/src/sql/statements/select.rs | 2 + lib/src/sql/statements/update.rs | 2 + lib/src/sql/thing.rs | 15 ++ lib/src/sql/value/flatten.rs | 19 +++ lib/src/sql/value/get.rs | 79 ++++++++--- lib/src/sql/value/mod.rs | 1 + lib/src/sql/value/set.rs | 20 ++- lib/src/sql/value/value.rs | 121 +++++++++------- 19 files changed, 806 insertions(+), 208 deletions(-) create mode 100644 lib/src/sql/edges.rs create mode 100644 lib/src/sql/ending.rs create mode 100644 lib/src/sql/value/flatten.rs diff --git a/lib/src/dbs/channel.rs b/lib/src/dbs/channel.rs index e1de5d0b..603a879e 100644 --- a/lib/src/dbs/channel.rs +++ b/lib/src/dbs/channel.rs @@ -5,7 +5,9 @@ use crate::dbs::Options; use crate::dbs::Statement; use crate::dbs::Transaction; use crate::err::Error; +use crate::key::graph; use crate::key::thing; +use crate::sql::dir::Dir; use crate::sql::thing::Thing; use crate::sql::value::Value; use channel::Sender; @@ -39,55 +41,6 @@ impl Iterable { // Process the document record chn.send((Some(v), val)).await?; } - Iterable::Table(v) => { - let beg = thing::prefix(opt.ns(), opt.db(), &v); - let end = thing::suffix(opt.ns(), opt.db(), &v); - let mut nxt: Option> = None; - loop { - if ctx.is_ok() { - let res = match nxt { - None => { - let min = beg.clone(); - let max = end.clone(); - txn.clone().lock().await.scan(min..max, 1000).await? - } - Some(ref mut beg) => { - beg.push(0x00); - let min = beg.clone(); - let max = end.clone(); - txn.clone().lock().await.scan(min..max, 1000).await? - } - }; - if !res.is_empty() { - // Get total results - let n = res.len(); - // Exit when settled - if n == 0 { - break; - } - // Loop over results - for (i, (k, v)) in res.into_iter().enumerate() { - if ctx.is_ok() { - // Ready the next - if n == i + 1 { - nxt = Some(k.clone()); - } - // Parse the data from the store - let key: crate::key::thing::Thing = (&k).into(); - let val: crate::sql::value::Value = (&v).into(); - let rid = Thing::from((key.tb, key.id)); - // Create a new operable value - let val = Operable::Value(val); - // Process the record - chn.send((Some(rid), val)).await?; - } - } - continue; - } - } - break; - } - } Iterable::Mergeable(v, o) => { // Fetch the data from the store let key = thing::new(opt.ns(), opt.db(), &v.tb, &v.id); @@ -116,6 +69,187 @@ impl Iterable { // Process the document record chn.send((Some(v), val)).await?; } + Iterable::Table(v) => { + // Prepare the start and end keys + let beg = thing::prefix(opt.ns(), opt.db(), &v); + let end = thing::suffix(opt.ns(), opt.db(), &v); + // Prepare the next holder key + let mut nxt: Option> = None; + // Loop until no more keys + loop { + // Check if the context is finished + ctx.check()?; + // Get the next 1000 key-value entries + let res = match nxt { + None => { + let min = beg.clone(); + let max = end.clone(); + txn.clone().lock().await.scan(min..max, 1000).await? + } + Some(ref mut beg) => { + beg.push(0x00); + let min = beg.clone(); + let max = end.clone(); + txn.clone().lock().await.scan(min..max, 1000).await? + } + }; + // If there are key-value entries then fetch them + if !res.is_empty() { + // Get total results + let n = res.len(); + // Exit when settled + if n == 0 { + break; + } + // Loop over results + for (i, (k, v)) in res.into_iter().enumerate() { + // Check the context + ctx.check()?; + // Ready the next + if n == i + 1 { + nxt = Some(k.clone()); + } + // Parse the data from the store + let key: crate::key::thing::Thing = (&k).into(); + let val: crate::sql::value::Value = (&v).into(); + let rid = Thing::from((key.tb, key.id)); + // Create a new operable value + let val = Operable::Value(val); + // Process the record + chn.send((Some(rid), val)).await?; + } + continue; + } + break; + } + } + Iterable::Edges(e) => { + // Pull out options + let ns = opt.ns(); + let db = opt.db(); + let tb = &e.from.tb; + let id = &e.from.id; + // Fetch start and end key pairs + let keys = match e.what.len() { + 0 => match e.dir { + // /ns/db/tb/id + Dir::Both => { + vec![(graph::prefix(ns, db, tb, id), graph::suffix(ns, db, tb, id))] + } + // /ns/db/tb/id/IN + Dir::In => vec![( + graph::egprefix(ns, db, tb, id, &e.dir), + graph::egsuffix(ns, db, tb, id, &e.dir), + )], + // /ns/db/tb/id/OUT + Dir::Out => vec![( + graph::egprefix(ns, db, tb, id, &e.dir), + graph::egsuffix(ns, db, tb, id, &e.dir), + )], + }, + _ => match e.dir { + // /ns/db/tb/id/IN/TB + Dir::In => e + .what + .iter() + .map(|v| v.to_string()) + .map(|v| { + ( + graph::ftprefix(ns, db, tb, id, &e.dir, &v), + graph::ftsuffix(ns, db, tb, id, &e.dir, &v), + ) + }) + .collect::>(), + // /ns/db/tb/id/OUT/TB + Dir::Out => e + .what + .iter() + .map(|v| v.to_string()) + .map(|v| { + ( + graph::ftprefix(ns, db, tb, id, &e.dir, &v), + graph::ftsuffix(ns, db, tb, id, &e.dir, &v), + ) + }) + .collect::>(), + // /ns/db/tb/id/IN/TB, /ns/db/tb/id/OUT/TB + Dir::Both => e + .what + .iter() + .map(|v| v.to_string()) + .flat_map(|v| { + vec![ + ( + graph::ftprefix(ns, db, tb, id, &Dir::In, &v), + graph::ftsuffix(ns, db, tb, id, &Dir::In, &v), + ), + ( + graph::ftprefix(ns, db, tb, id, &Dir::Out, &v), + graph::ftsuffix(ns, db, tb, id, &Dir::Out, &v), + ), + ] + }) + .collect::>(), + }, + }; + // + for (beg, end) in keys.iter() { + // Prepare the next holder key + let mut nxt: Option> = None; + // Loop until no more keys + loop { + // Check if the context is finished + ctx.check()?; + // Get the next 1000 key-value entries + let res = match nxt { + None => { + let min = beg.clone(); + let max = end.clone(); + txn.clone().lock().await.scan(min..max, 1000).await? + } + Some(ref mut beg) => { + beg.push(0x00); + let min = beg.clone(); + let max = end.clone(); + txn.clone().lock().await.scan(min..max, 1000).await? + } + }; + // If there are key-value entries then fetch them + if !res.is_empty() { + // Get total results + let n = res.len(); + // Exit when settled + if n == 0 { + break; + } + // Loop over results + for (i, (k, _)) in res.into_iter().enumerate() { + // Check the context + ctx.check()?; + // Ready the next + if n == i + 1 { + nxt = Some(k.clone()); + } + // Parse the data from the store + let gra: crate::key::graph::Graph = (&k).into(); + // Fetch the data from the store + let key = thing::new(opt.ns(), opt.db(), &gra.ft, &gra.fk); + let val = txn.clone().lock().await.get(key).await?; + let rid = Thing::from((gra.ft, gra.fk)); + // Parse the data from the store + let val = Operable::Value(match val { + Some(v) => Value::from(v), + None => Value::None, + }); + // Process the record + chn.send((Some(rid), val)).await?; + } + continue; + } + break; + } + } + } } } Ok(()) diff --git a/lib/src/dbs/iterate.rs b/lib/src/dbs/iterate.rs index e4854aa7..dc1ed709 100644 --- a/lib/src/dbs/iterate.rs +++ b/lib/src/dbs/iterate.rs @@ -6,7 +6,9 @@ use crate::dbs::Options; use crate::dbs::Statement; use crate::dbs::Transaction; use crate::err::Error; +use crate::key::graph; use crate::key::thing; +use crate::sql::dir::Dir; use crate::sql::thing::Thing; use crate::sql::value::Value; @@ -39,55 +41,6 @@ impl Iterable { // Process the document record ite.process(ctx, opt, txn, stm, Some(v), val).await; } - Iterable::Table(v) => { - let beg = thing::prefix(opt.ns(), opt.db(), &v); - let end = thing::suffix(opt.ns(), opt.db(), &v); - let mut nxt: Option> = None; - loop { - if ctx.is_ok() { - let res = match nxt { - None => { - let min = beg.clone(); - let max = end.clone(); - txn.clone().lock().await.scan(min..max, 1000).await? - } - Some(ref mut beg) => { - beg.push(0x00); - let min = beg.clone(); - let max = end.clone(); - txn.clone().lock().await.scan(min..max, 1000).await? - } - }; - if !res.is_empty() { - // Get total results - let n = res.len(); - // Exit when settled - if n == 0 { - break; - } - // Loop over results - for (i, (k, v)) in res.into_iter().enumerate() { - if ctx.is_ok() { - // Ready the next - if n == i + 1 { - nxt = Some(k.clone()); - } - // Parse the data from the store - let key: crate::key::thing::Thing = (&k).into(); - let val: crate::sql::value::Value = (&v).into(); - let rid = Thing::from((key.tb, key.id)); - // Create a new operable value - let val = Operable::Value(val); - // Process the record - ite.process(ctx, opt, txn, stm, Some(rid), val).await; - } - } - continue; - } - } - break; - } - } Iterable::Mergeable(v, o) => { // Fetch the data from the store let key = thing::new(opt.ns(), opt.db(), &v.tb, &v.id); @@ -116,6 +69,187 @@ impl Iterable { // Process the document record ite.process(ctx, opt, txn, stm, Some(v), val).await; } + Iterable::Table(v) => { + // Prepare the start and end keys + let beg = thing::prefix(opt.ns(), opt.db(), &v); + let end = thing::suffix(opt.ns(), opt.db(), &v); + // Prepare the next holder key + let mut nxt: Option> = None; + // Loop until no more keys + loop { + // Check if the context is finished + ctx.check()?; + // Get the next 1000 key-value entries + let res = match nxt { + None => { + let min = beg.clone(); + let max = end.clone(); + txn.clone().lock().await.scan(min..max, 1000).await? + } + Some(ref mut beg) => { + beg.push(0x00); + let min = beg.clone(); + let max = end.clone(); + txn.clone().lock().await.scan(min..max, 1000).await? + } + }; + // If there are key-value entries then fetch them + if !res.is_empty() { + // Get total results + let n = res.len(); + // Exit when settled + if n == 0 { + break; + } + // Loop over results + for (i, (k, v)) in res.into_iter().enumerate() { + // Check the context + ctx.check()?; + // Ready the next + if n == i + 1 { + nxt = Some(k.clone()); + } + // Parse the data from the store + let key: crate::key::thing::Thing = (&k).into(); + let val: crate::sql::value::Value = (&v).into(); + let rid = Thing::from((key.tb, key.id)); + // Create a new operable value + let val = Operable::Value(val); + // Process the record + ite.process(ctx, opt, txn, stm, Some(rid), val).await; + } + continue; + } + break; + } + } + Iterable::Edges(e) => { + // Pull out options + let ns = opt.ns(); + let db = opt.db(); + let tb = &e.from.tb; + let id = &e.from.id; + // Fetch start and end key pairs + let keys = match e.what.len() { + 0 => match e.dir { + // /ns/db/tb/id + Dir::Both => { + vec![(graph::prefix(ns, db, tb, id), graph::suffix(ns, db, tb, id))] + } + // /ns/db/tb/id/IN + Dir::In => vec![( + graph::egprefix(ns, db, tb, id, &e.dir), + graph::egsuffix(ns, db, tb, id, &e.dir), + )], + // /ns/db/tb/id/OUT + Dir::Out => vec![( + graph::egprefix(ns, db, tb, id, &e.dir), + graph::egsuffix(ns, db, tb, id, &e.dir), + )], + }, + _ => match e.dir { + // /ns/db/tb/id/IN/TB + Dir::In => e + .what + .iter() + .map(|v| v.to_string()) + .map(|v| { + ( + graph::ftprefix(ns, db, tb, id, &e.dir, &v), + graph::ftsuffix(ns, db, tb, id, &e.dir, &v), + ) + }) + .collect::>(), + // /ns/db/tb/id/OUT/TB + Dir::Out => e + .what + .iter() + .map(|v| v.to_string()) + .map(|v| { + ( + graph::ftprefix(ns, db, tb, id, &e.dir, &v), + graph::ftsuffix(ns, db, tb, id, &e.dir, &v), + ) + }) + .collect::>(), + // /ns/db/tb/id/IN/TB, /ns/db/tb/id/OUT/TB + Dir::Both => e + .what + .iter() + .map(|v| v.to_string()) + .flat_map(|v| { + vec![ + ( + graph::ftprefix(ns, db, tb, id, &Dir::In, &v), + graph::ftsuffix(ns, db, tb, id, &Dir::In, &v), + ), + ( + graph::ftprefix(ns, db, tb, id, &Dir::Out, &v), + graph::ftsuffix(ns, db, tb, id, &Dir::Out, &v), + ), + ] + }) + .collect::>(), + }, + }; + // + for (beg, end) in keys.iter() { + // Prepare the next holder key + let mut nxt: Option> = None; + // Loop until no more keys + loop { + // Check if the context is finished + ctx.check()?; + // Get the next 1000 key-value entries + let res = match nxt { + None => { + let min = beg.clone(); + let max = end.clone(); + txn.clone().lock().await.scan(min..max, 1000).await? + } + Some(ref mut beg) => { + beg.push(0x00); + let min = beg.clone(); + let max = end.clone(); + txn.clone().lock().await.scan(min..max, 1000).await? + } + }; + // If there are key-value entries then fetch them + if !res.is_empty() { + // Get total results + let n = res.len(); + // Exit when settled + if n == 0 { + break; + } + // Loop over results + for (i, (k, _)) in res.into_iter().enumerate() { + // Check the context + ctx.check()?; + // Ready the next + if n == i + 1 { + nxt = Some(k.clone()); + } + // Parse the data from the store + let gra: crate::key::graph::Graph = (&k).into(); + // Fetch the data from the store + let key = thing::new(opt.ns(), opt.db(), &gra.ft, &gra.fk); + let val = txn.clone().lock().await.get(key).await?; + let rid = Thing::from((gra.ft, gra.fk)); + // Parse the data from the store + let val = Operable::Value(match val { + Some(v) => Value::from(v), + None => Value::None, + }); + // Process the record + ite.process(ctx, opt, txn, stm, Some(rid), val).await; + } + continue; + } + break; + } + } + } } } Ok(()) diff --git a/lib/src/dbs/iterator.rs b/lib/src/dbs/iterator.rs index 85414ad1..97fe77d8 100644 --- a/lib/src/dbs/iterator.rs +++ b/lib/src/dbs/iterator.rs @@ -6,6 +6,7 @@ use crate::dbs::Transaction; use crate::doc::Document; use crate::err::Error; use crate::sql::array::Array; +use crate::sql::edges::Edges; use crate::sql::field::Field; use crate::sql::part::Part; use crate::sql::table::Table; @@ -19,6 +20,7 @@ pub enum Iterable { Value(Value), Table(Table), Thing(Thing), + Edges(Edges), Mergeable(Thing, Value), Relatable(Thing, Thing, Thing), } diff --git a/lib/src/sql/edges.rs b/lib/src/sql/edges.rs new file mode 100644 index 00000000..f67f124c --- /dev/null +++ b/lib/src/sql/edges.rs @@ -0,0 +1,106 @@ +use crate::sql::comment::mightbespace; +use crate::sql::dir::{dir, Dir}; +use crate::sql::error::IResult; +use crate::sql::table::{table, tables, Tables}; +use crate::sql::thing::{thing, Thing}; +use nom::branch::alt; +use nom::character::complete::char; +use nom::combinator::map; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize)] +pub struct Edges { + pub dir: Dir, + pub from: Thing, + pub what: Tables, +} + +impl fmt::Display for Edges { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.what.len() { + 0 => write!(f, "{}{}?", self.from, self.dir,), + 1 => write!(f, "{}{}{}", self.from, self.dir, self.what), + _ => write!(f, "{}{}({})", self.from, self.dir, self.what), + } + } +} + +pub fn edges(i: &str) -> IResult<&str, Edges> { + let (i, from) = thing(i)?; + let (i, dir) = dir(i)?; + let (i, what) = alt((simple, custom))(i)?; + Ok(( + i, + Edges { + dir, + from, + what, + }, + )) +} + +fn simple(i: &str) -> IResult<&str, Tables> { + let (i, w) = alt((any, one))(i)?; + Ok((i, w)) +} + +fn custom(i: &str) -> IResult<&str, Tables> { + let (i, _) = char('(')(i)?; + let (i, _) = mightbespace(i)?; + let (i, w) = alt((any, tables))(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char(')')(i)?; + Ok((i, w)) +} + +fn one(i: &str) -> IResult<&str, Tables> { + let (i, v) = table(i)?; + Ok((i, Tables::from(v))) +} + +fn any(i: &str) -> IResult<&str, Tables> { + map(char('?'), |_| Tables::default())(i) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn edges_in() { + let sql = "person:test<-likes"; + let res = edges(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!("person:test<-likes", format!("{}", out)); + } + + #[test] + fn edges_out() { + let sql = "person:test->likes"; + let res = edges(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!("person:test->likes", format!("{}", out)); + } + + #[test] + fn edges_both() { + let sql = "person:test<->likes"; + let res = edges(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!("person:test<->likes", format!("{}", out)); + } + + #[test] + fn edges_multiple() { + let sql = "person:test->(likes, follows)"; + let res = edges(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!("person:test->(likes, follows)", format!("{}", out)); + } +} diff --git a/lib/src/sql/ending.rs b/lib/src/sql/ending.rs new file mode 100644 index 00000000..4f937bc5 --- /dev/null +++ b/lib/src/sql/ending.rs @@ -0,0 +1,42 @@ +use crate::sql::comment::comment; +use crate::sql::error::IResult; +use crate::sql::operator::{assigner, operator}; +use nom::branch::alt; +use nom::character::complete::char; +use nom::character::complete::multispace1; +use nom::combinator::eof; +use nom::combinator::map; +use nom::combinator::peek; + +pub fn number(i: &str) -> IResult<&str, ()> { + peek(alt(( + map(multispace1, |_| ()), + map(operator, |_| ()), + map(assigner, |_| ()), + map(comment, |_| ()), + map(char(')'), |_| ()), + map(char(']'), |_| ()), + map(char('}'), |_| ()), + map(char(';'), |_| ()), + map(char(','), |_| ()), + map(eof, |_| ()), + )))(i) +} + +pub fn ident(i: &str) -> IResult<&str, ()> { + peek(alt(( + map(multispace1, |_| ()), + map(operator, |_| ()), + map(assigner, |_| ()), + map(comment, |_| ()), + map(char(')'), |_| ()), + map(char(']'), |_| ()), + map(char('}'), |_| ()), + map(char(';'), |_| ()), + map(char(','), |_| ()), + map(char('.'), |_| ()), + map(char('['), |_| ()), + map(char('-'), |_| ()), + map(eof, |_| ()), + )))(i) +} diff --git a/lib/src/sql/graph.rs b/lib/src/sql/graph.rs index 190b0eb1..f1498952 100644 --- a/lib/src/sql/graph.rs +++ b/lib/src/sql/graph.rs @@ -21,6 +21,12 @@ pub struct Graph { pub alias: Option, } +impl Graph { + pub fn to_raw(&self) -> String { + self.to_string() + } +} + impl fmt::Display for Graph { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.what.0.len() <= 1 && self.cond.is_none() && self.alias.is_none() { diff --git a/lib/src/sql/idiom.rs b/lib/src/sql/idiom.rs index 2986acaf..39b680a1 100644 --- a/lib/src/sql/idiom.rs +++ b/lib/src/sql/idiom.rs @@ -4,11 +4,12 @@ use crate::dbs::Transaction; use crate::err::Error; use crate::sql::common::commas; use crate::sql::error::IResult; -use crate::sql::part::{all, field, first, graph, index, last, part, Part}; +use crate::sql::part::Next; +use crate::sql::part::{all, field, first, graph, index, last, part, thing, Part}; use crate::sql::value::Value; use nom::branch::alt; -use nom::multi::many0; use nom::multi::separated_list1; +use nom::multi::{many0, many1}; use serde::{Deserialize, Serialize}; use std::fmt; use std::ops::Deref; @@ -72,7 +73,7 @@ impl Idiom { self.0 .iter() .cloned() - .filter(|p| matches!(p, Part::Field(_) | Part::Graph(_))) + .filter(|p| matches!(p, Part::Field(_) | Part::Thing(_) | Part::Graph(_))) .collect::>() .into() } @@ -94,11 +95,24 @@ impl Idiom { txn: &Transaction, doc: Option<&Value>, ) -> Result { - match doc { - // There is a current document - Some(v) => v.get(ctx, opt, txn, self).await?.compute(ctx, opt, txn, doc).await, - // There isn't any document - None => Ok(Value::None), + match self.first() { + // The first part is a thing record + Some(Part::Thing(v)) => { + // Use the thing as the document + let v: Value = v.clone().into(); + // Fetch the Idiom from the document + v.get(ctx, opt, txn, self.as_ref().next()) + .await? + .compute(ctx, opt, txn, Some(&v)) + .await + } + // Otherwise use the current document + _ => match doc { + // There is a current document + Some(v) => v.get(ctx, opt, txn, self).await?.compute(ctx, opt, txn, doc).await, + // There isn't any document + None => Ok(Value::None), + }, } } } @@ -146,18 +160,32 @@ pub fn param(i: &str) -> IResult<&str, Idiom> { } pub fn idiom(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))) + alt(( + |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)] mod tests { use super::*; + use crate::sql::dir::Dir; use crate::sql::expression::Expression; + use crate::sql::graph::Graph; + use crate::sql::table::Table; use crate::sql::test::Parse; + use crate::sql::thing::Thing; #[test] fn idiom_normal() { @@ -277,4 +305,32 @@ mod tests { ]) ); } + + #[test] + fn idiom_start_thing_remote_traversal() { + let sql = "person:test.friend->like->person"; + let res = idiom(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!("person:test.friend->like->person", format!("{}", out)); + assert_eq!( + out, + Idiom(vec![ + Part::from(Thing::from(("person", "test"))), + Part::from("friend"), + Part::from(Graph { + dir: Dir::Out, + what: Table::from("like").into(), + cond: None, + alias: None, + }), + Part::from(Graph { + dir: Dir::Out, + what: Table::from("person").into(), + cond: None, + alias: None, + }), + ]) + ); + } } diff --git a/lib/src/sql/mod.rs b/lib/src/sql/mod.rs index b82826ea..a5c2657a 100644 --- a/lib/src/sql/mod.rs +++ b/lib/src/sql/mod.rs @@ -8,6 +8,8 @@ pub(crate) mod data; pub(crate) mod datetime; pub(crate) mod dir; pub(crate) mod duration; +pub(crate) mod edges; +pub(crate) mod ending; pub(crate) mod error; pub(crate) mod escape; pub(crate) mod expression; @@ -64,6 +66,7 @@ pub use self::data::Data; pub use self::datetime::Datetime; pub use self::dir::Dir; pub use self::duration::Duration; +pub use self::edges::Edges; pub use self::error::Error; pub use self::expression::Expression; pub use self::fetch::Fetch; diff --git a/lib/src/sql/number.rs b/lib/src/sql/number.rs index 521ec047..d070cfb6 100644 --- a/lib/src/sql/number.rs +++ b/lib/src/sql/number.rs @@ -1,17 +1,12 @@ -use crate::sql::comment::comment; +use crate::sql::ending::number as ending; use crate::sql::error::IResult; -use crate::sql::operator::{assigner, operator}; use crate::sql::serde::is_internal_serialization; use bigdecimal::BigDecimal; use bigdecimal::FromPrimitive; use bigdecimal::ToPrimitive; use nom::branch::alt; -use nom::character::complete::char; use nom::character::complete::i64; -use nom::character::complete::multispace1; -use nom::combinator::eof; use nom::combinator::map; -use nom::combinator::peek; use nom::number::complete::recognize_float; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -509,35 +504,13 @@ pub fn number(i: &str) -> IResult<&str, Number> { pub fn integer(i: &str) -> IResult<&str, i64> { let (i, v) = i64(i)?; - let (i, _) = peek(alt(( - map(multispace1, |_| ()), - map(operator, |_| ()), - map(assigner, |_| ()), - map(comment, |_| ()), - map(char(')'), |_| ()), - map(char(']'), |_| ()), - map(char('}'), |_| ()), - map(char(';'), |_| ()), - map(char(','), |_| ()), - map(eof, |_| ()), - )))(i)?; + let (i, _) = ending(i)?; Ok((i, v)) } pub fn decimal(i: &str) -> IResult<&str, &str> { let (i, v) = recognize_float(i)?; - let (i, _) = peek(alt(( - map(multispace1, |_| ()), - map(operator, |_| ()), - map(assigner, |_| ()), - map(comment, |_| ()), - map(char(')'), |_| ()), - map(char(']'), |_| ()), - map(char('}'), |_| ()), - map(char(';'), |_| ()), - map(char(','), |_| ()), - map(eof, |_| ()), - )))(i)?; + let (i, _) = ending(i)?; Ok((i, v)) } diff --git a/lib/src/sql/part.rs b/lib/src/sql/part.rs index 8b5b5b52..f3863c60 100644 --- a/lib/src/sql/part.rs +++ b/lib/src/sql/part.rs @@ -1,9 +1,11 @@ use crate::sql::comment::shouldbespace; +use crate::sql::ending::ident as ending; use crate::sql::error::IResult; use crate::sql::graph::{graph as graph_raw, Graph}; use crate::sql::ident::{ident, Ident}; use crate::sql::idiom::Idiom; use crate::sql::number::{number, Number}; +use crate::sql::thing::{thing as thing_raw, Thing}; use crate::sql::value::{value, Value}; use nom::branch::alt; use nom::bytes::complete::tag; @@ -21,6 +23,7 @@ pub enum Part { Field(Ident), Index(Number), Where(Value), + Thing(Thing), Graph(Graph), } @@ -60,6 +63,12 @@ impl From for Part { } } +impl From for Part { + fn from(v: Thing) -> Self { + Part::Thing(v) + } +} + impl From for Part { fn from(v: Graph) -> Self { Part::Graph(v) @@ -100,6 +109,7 @@ impl fmt::Display for Part { Part::Field(v) => write!(f, ".{}", v), Part::Index(v) => write!(f, "[{}]", v), Part::Where(v) => write!(f, "[WHERE {}]", v), + Part::Thing(v) => write!(f, "{}", v), Part::Graph(v) => write!(f, "{}", v), } } @@ -128,6 +138,7 @@ pub fn part(i: &str) -> IResult<&str, Part> { pub fn first(i: &str) -> IResult<&str, Part> { let (i, v) = ident(i)?; + let (i, _) = ending(i)?; Ok((i, Part::Field(v))) } @@ -165,6 +176,7 @@ pub fn index(i: &str) -> IResult<&str, Part> { pub fn field(i: &str) -> IResult<&str, Part> { let (i, _) = char('.')(i)?; let (i, v) = ident(i)?; + let (i, _) = ending(i)?; Ok((i, Part::Field(v))) } @@ -177,6 +189,11 @@ pub fn filter(i: &str) -> IResult<&str, Part> { Ok((i, Part::Where(v))) } +pub fn thing(i: &str) -> IResult<&str, Part> { + let (i, v) = thing_raw(i)?; + Ok((i, Part::Thing(v))) +} + pub fn graph(i: &str) -> IResult<&str, Part> { let (i, v) = graph_raw(i)?; Ok((i, Part::Graph(v))) diff --git a/lib/src/sql/statements/delete.rs b/lib/src/sql/statements/delete.rs index 2cec6ba2..c3d57833 100644 --- a/lib/src/sql/statements/delete.rs +++ b/lib/src/sql/statements/delete.rs @@ -53,6 +53,7 @@ impl DeleteStatement { match v { Value::Table(v) => i.ingest(Iterable::Table(v)), Value::Thing(v) => i.ingest(Iterable::Thing(v)), + Value::Edges(v) => i.ingest(Iterable::Edges(*v)), Value::Model(v) => { for v in v { i.ingest(Iterable::Thing(v)); @@ -63,6 +64,7 @@ impl DeleteStatement { match v { Value::Table(v) => i.ingest(Iterable::Table(v)), Value::Thing(v) => i.ingest(Iterable::Thing(v)), + Value::Edges(v) => i.ingest(Iterable::Edges(*v)), Value::Model(v) => { for v in v { i.ingest(Iterable::Thing(v)); diff --git a/lib/src/sql/statements/select.rs b/lib/src/sql/statements/select.rs index e5811aee..4cff5c86 100644 --- a/lib/src/sql/statements/select.rs +++ b/lib/src/sql/statements/select.rs @@ -92,6 +92,7 @@ impl SelectStatement { match v { Value::Table(v) => i.ingest(Iterable::Table(v)), Value::Thing(v) => i.ingest(Iterable::Thing(v)), + Value::Edges(v) => i.ingest(Iterable::Edges(*v)), Value::Model(v) => { for v in v { i.ingest(Iterable::Thing(v)); @@ -102,6 +103,7 @@ impl SelectStatement { match v { Value::Table(v) => i.ingest(Iterable::Table(v)), Value::Thing(v) => i.ingest(Iterable::Thing(v)), + Value::Edges(v) => i.ingest(Iterable::Edges(*v)), Value::Model(v) => { for v in v { i.ingest(Iterable::Thing(v)); diff --git a/lib/src/sql/statements/update.rs b/lib/src/sql/statements/update.rs index 210d0d3f..29902a16 100644 --- a/lib/src/sql/statements/update.rs +++ b/lib/src/sql/statements/update.rs @@ -54,6 +54,7 @@ impl UpdateStatement { match v { Value::Table(v) => i.ingest(Iterable::Table(v)), Value::Thing(v) => i.ingest(Iterable::Thing(v)), + Value::Edges(v) => i.ingest(Iterable::Edges(*v)), Value::Model(v) => { for v in v { i.ingest(Iterable::Thing(v)); @@ -64,6 +65,7 @@ impl UpdateStatement { match v { Value::Table(v) => i.ingest(Iterable::Table(v)), Value::Thing(v) => i.ingest(Iterable::Thing(v)), + Value::Edges(v) => i.ingest(Iterable::Edges(*v)), Value::Model(v) => { for v in v { i.ingest(Iterable::Thing(v)); diff --git a/lib/src/sql/thing.rs b/lib/src/sql/thing.rs index a0589a49..63c200ff 100644 --- a/lib/src/sql/thing.rs +++ b/lib/src/sql/thing.rs @@ -33,6 +33,21 @@ impl From<(String, String)> for Thing { } } +impl From<(&str, &str)> for Thing { + fn from(v: (&str, &str)) -> Self { + Thing { + tb: v.0.to_owned(), + id: Id::from(v.1), + } + } +} + +impl Thing { + pub fn to_raw(&self) -> String { + self.to_string() + } +} + impl fmt::Display for Thing { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}:{}", escape_ident(&self.tb), self.id) diff --git a/lib/src/sql/value/flatten.rs b/lib/src/sql/value/flatten.rs new file mode 100644 index 00000000..7f41357f --- /dev/null +++ b/lib/src/sql/value/flatten.rs @@ -0,0 +1,19 @@ +use crate::sql::array::Array; +use crate::sql::value::Value; + +impl Value { + pub fn flatten(self) -> Self { + match self { + Value::Array(v) => { + v.0.into_iter() + .flat_map(|v| match v { + Value::Array(v) => v, + _ => Array::from(v), + }) + .collect::>() + .into() + } + v => v, + } + } +} diff --git a/lib/src/sql/value/get.rs b/lib/src/sql/value/get.rs index dc74f6cb..bee0560d 100644 --- a/lib/src/sql/value/get.rs +++ b/lib/src/sql/value/get.rs @@ -2,6 +2,7 @@ use crate::ctx::Context; use crate::dbs::Options; use crate::dbs::Transaction; use crate::err::Error; +use crate::sql::edges::Edges; use crate::sql::field::{Field, Fields}; use crate::sql::part::Next; use crate::sql::part::Part; @@ -9,6 +10,9 @@ use crate::sql::statements::select::SelectStatement; use crate::sql::value::{Value, Values}; use async_recursion::async_recursion; use futures::future::try_join_all; +use once_cell::sync::Lazy; + +static ID: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("id")]); impl Value { #[cfg_attr(feature = "parallel", async_recursion)] @@ -25,6 +29,10 @@ impl Value { Some(p) => match self { // Current path part is an object Value::Object(v) => match p { + Part::Graph(_) => match v.rid() { + Some(v) => Value::Thing(v).get(ctx, opt, txn, path).await, + None => Ok(Value::None), + }, Part::Field(f) => match v.get(f as &str) { Some(v) => v.get(ctx, opt, txn, path.next()).await, None => Ok(Value::None), @@ -67,23 +75,62 @@ impl Value { } }, // Current path part is a thing - Value::Thing(v) => match path.len() { - // No remote embedded fields, so just return this - 0 => Ok(Value::Thing(v.clone())), - // Remote embedded field, so fetch the thing - _ => { - let stm = SelectStatement { - expr: Fields(vec![Field::All]), - what: Values(vec![Value::Thing(v.clone())]), - ..SelectStatement::default() - }; - stm.compute(ctx, opt, txn, None) - .await? - .first() - .get(ctx, opt, txn, path) - .await + Value::Thing(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::Thing(val)), + // Remote embedded field, so fetch the thing + _ => match p { + // This is a graph traversal expression + Part::Graph(g) => { + let stm = SelectStatement { + expr: Fields(vec![Field::All]), + what: Values(vec![Value::from(Edges { + from: val, + dir: g.dir.clone(), + what: g.what.clone(), + })]), + cond: g.cond.clone(), + ..SelectStatement::default() + }; + match path.len() { + 1 => stm + .compute(ctx, opt, txn, None) + .await? + .all() + .get(ctx, opt, txn, ID.as_ref()) + .await? + .flatten() + .ok(), + _ => stm + .compute(ctx, opt, txn, None) + .await? + .all() + .get(ctx, opt, txn, path.next()) + .await? + .flatten() + .ok(), + } + } + // This is a remote field expression + _ => { + let stm = SelectStatement { + expr: Fields(vec![Field::All]), + what: Values(vec![Value::from(val)]), + ..SelectStatement::default() + }; + stm.compute(ctx, opt, txn, None) + .await? + .first() + .get(ctx, opt, txn, path) + .await + } + }, } - }, + } // Ignore everything else _ => Ok(Value::None), }, diff --git a/lib/src/sql/value/mod.rs b/lib/src/sql/value/mod.rs index 02371ce1..304f2c2f 100644 --- a/lib/src/sql/value/mod.rs +++ b/lib/src/sql/value/mod.rs @@ -14,6 +14,7 @@ mod diff; mod each; mod every; mod first; +mod flatten; mod get; mod increment; mod last; diff --git a/lib/src/sql/value/set.rs b/lib/src/sql/value/set.rs index 2a98547f..f71f5ae4 100644 --- a/lib/src/sql/value/set.rs +++ b/lib/src/sql/value/set.rs @@ -24,7 +24,25 @@ impl Value { Some(p) => match self { // Current path part is an object Value::Object(v) => match p { - Part::Field(f) => match v.get_mut(f as &str) { + 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()) { + 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(g.to_raw(), obj); + Ok(()) + } + }, + Part::Field(f) => match v.get_mut(f.to_raw().as_str()) { Some(v) if v.is_some() => v.set(ctx, opt, txn, path.next(), val).await, _ => { let mut obj = Value::base(); diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index b305422a..25c9565a 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -8,6 +8,7 @@ use crate::sql::array::{array, Array}; use crate::sql::common::commas; use crate::sql::datetime::{datetime, Datetime}; use crate::sql::duration::{duration, Duration}; +use crate::sql::edges::{edges, Edges}; use crate::sql::error::IResult; use crate::sql::expression::{expression, Expression}; use crate::sql::function::{function, Function}; @@ -110,6 +111,7 @@ pub enum Value { Thing(Thing), Model(Model), Regex(Regex), + Edges(Box), Function(Box), Subquery(Box), Expression(Box), @@ -150,6 +152,12 @@ impl From for Value { } } +impl From for Value { + fn from(v: Model) -> Self { + Value::Model(v) + } +} + impl From for Value { fn from(v: Table) -> Self { Value::Table(v) @@ -210,21 +218,27 @@ impl From for Value { } } +impl From for Value { + fn from(v: Edges) -> Self { + Value::Edges(Box::new(v)) + } +} + impl From for Value { fn from(v: Function) -> Self { Value::Function(Box::new(v)) } } -impl From for Value { - fn from(v: Expression) -> Self { - Value::Expression(Box::new(v)) +impl From for Value { + fn from(v: Subquery) -> Self { + Value::Subquery(Box::new(v)) } } -impl From> for Value { - fn from(v: Box) -> Self { - Value::Expression(v) +impl From for Value { + fn from(v: Expression) -> Self { + Value::Expression(Box::new(v)) } } @@ -964,6 +978,7 @@ impl fmt::Display for Value { Value::Thing(v) => write!(f, "{}", v), Value::Model(v) => write!(f, "{}", v), Value::Regex(v) => write!(f, "{}", v), + Value::Edges(v) => write!(f, "{}", v), Value::Function(v) => write!(f, "{}", v), Value::Subquery(v) => write!(f, "{}", v), Value::Expression(v) => write!(f, "{}", v), @@ -1035,9 +1050,10 @@ impl Serialize for Value { Value::Thing(v) => s.serialize_newtype_variant("Value", 15, "Thing", v), Value::Model(v) => s.serialize_newtype_variant("Value", 16, "Model", v), Value::Regex(v) => s.serialize_newtype_variant("Value", 17, "Regex", v), - Value::Function(v) => s.serialize_newtype_variant("Value", 18, "Function", v), - Value::Subquery(v) => s.serialize_newtype_variant("Value", 19, "Subquery", v), - Value::Expression(v) => s.serialize_newtype_variant("Value", 20, "Expression", v), + Value::Edges(v) => s.serialize_newtype_variant("Value", 18, "Edges", v), + Value::Function(v) => s.serialize_newtype_variant("Value", 19, "Function", v), + Value::Subquery(v) => s.serialize_newtype_variant("Value", 20, "Subquery", v), + Value::Expression(v) => s.serialize_newtype_variant("Value", 21, "Expression", v), } } else { match self { @@ -1113,7 +1129,7 @@ pub fn value(i: &str) -> IResult<&str, Value> { } pub fn double(i: &str) -> IResult<&str, Value> { - map(expression, |v| Value::Expression(Box::new(v)))(i) + map(expression, Value::from)(i) } pub fn single(i: &str) -> IResult<&str, Value> { @@ -1123,20 +1139,20 @@ pub fn single(i: &str) -> IResult<&str, Value> { map(tag_no_case("NULL"), |_| Value::Null), map(tag_no_case("true"), |_| Value::True), map(tag_no_case("false"), |_| Value::False), - map(subquery, |v| Value::Subquery(Box::new(v))), - map(function, |v| Value::Function(Box::new(v))), - map(datetime, Value::Datetime), - map(duration, Value::Duration), - map(geometry, Value::Geometry), - map(number, Value::Number), - map(strand, Value::Strand), - map(object, Value::Object), - map(array, Value::Array), - map(param, Value::Param), - map(regex, Value::Regex), - map(thing, Value::Thing), - map(model, Value::Model), - map(idiom, Value::Idiom), + map(subquery, Value::from), + map(function, Value::from), + map(datetime, Value::from), + map(duration, Value::from), + map(geometry, Value::from), + map(number, Value::from), + map(strand, Value::from), + map(object, Value::from), + map(array, Value::from), + map(param, Value::from), + map(regex, Value::from), + map(model, Value::from), + map(idiom, Value::from), + map(thing, Value::from), ))(i) } @@ -1147,31 +1163,33 @@ pub fn select(i: &str) -> IResult<&str, Value> { map(tag_no_case("NULL"), |_| Value::Null), map(tag_no_case("true"), |_| Value::True), map(tag_no_case("false"), |_| Value::False), - map(subquery, |v| Value::Subquery(Box::new(v))), - map(function, |v| Value::Function(Box::new(v))), - map(datetime, Value::Datetime), - map(duration, Value::Duration), - map(geometry, Value::Geometry), - map(number, Value::Number), - map(strand, Value::Strand), - map(object, Value::Object), - map(array, Value::Array), - map(param, Value::Param), - map(regex, Value::Regex), - map(thing, Value::Thing), - map(model, Value::Model), - map(table, Value::Table), + map(subquery, Value::from), + map(function, Value::from), + map(datetime, Value::from), + map(duration, Value::from), + map(geometry, Value::from), + map(number, Value::from), + map(strand, Value::from), + map(object, Value::from), + map(array, Value::from), + map(param, Value::from), + map(regex, Value::from), + map(model, Value::from), + map(edges, Value::from), + map(thing, Value::from), + map(table, Value::from), ))(i) } pub fn what(i: &str) -> IResult<&str, Value> { alt(( - map(subquery, |v| Value::Subquery(Box::new(v))), - map(function, |v| Value::Function(Box::new(v))), - map(param, Value::Param), - map(model, Value::Model), - map(thing, Value::Thing), - map(table, Value::Table), + map(subquery, Value::from), + map(function, Value::from), + map(param, Value::from), + map(model, Value::from), + map(edges, Value::from), + map(thing, Value::from), + map(table, Value::from), ))(i) } @@ -1180,13 +1198,13 @@ pub fn json(i: &str) -> IResult<&str, Value> { map(tag_no_case("NULL"), |_| Value::Null), map(tag_no_case("true"), |_| Value::True), map(tag_no_case("false"), |_| Value::False), - map(datetime, Value::Datetime), - map(duration, Value::Duration), - map(geometry, Value::Geometry), - map(number, Value::Number), - map(object, Value::Object), - map(array, Value::Array), - map(strand, Value::Strand), + map(datetime, Value::from), + map(duration, Value::from), + map(geometry, Value::from), + map(number, Value::from), + map(object, Value::from), + map(array, Value::from), + map(strand, Value::from), ))(i) } @@ -1367,6 +1385,7 @@ mod tests { assert_eq!(56, std::mem::size_of::()); assert_eq!(48, std::mem::size_of::()); assert_eq!(24, std::mem::size_of::()); + assert_eq!(8, std::mem::size_of::>()); assert_eq!(8, std::mem::size_of::>()); assert_eq!(8, std::mem::size_of::>()); assert_eq!(8, std::mem::size_of::>());