Implement graph traversal functionality

This commit is contained in:
Tobie Morgan Hitchcock 2022-06-09 09:18:08 +01:00
parent 8ce5d01727
commit c0a78d8470
19 changed files with 806 additions and 208 deletions

View file

@ -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<Vec<u8>> = 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<Vec<u8>> = 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::<Vec<_>>(),
// /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::<Vec<_>>(),
// /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::<Vec<_>>(),
},
};
//
for (beg, end) in keys.iter() {
// Prepare the next holder key
let mut nxt: Option<Vec<u8>> = 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(())

View file

@ -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<Vec<u8>> = 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<Vec<u8>> = 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::<Vec<_>>(),
// /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::<Vec<_>>(),
// /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::<Vec<_>>(),
},
};
//
for (beg, end) in keys.iter() {
// Prepare the next holder key
let mut nxt: Option<Vec<u8>> = 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(())

View file

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

106
lib/src/sql/edges.rs Normal file
View file

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

42
lib/src/sql/ending.rs Normal file
View file

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

View file

@ -21,6 +21,12 @@ pub struct Graph {
pub alias: Option<Idiom>,
}
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() {

View file

@ -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::<Vec<_>>()
.into()
}
@ -94,11 +95,24 @@ impl Idiom {
txn: &Transaction,
doc: Option<&Value>,
) -> Result<Value, Error> {
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,
}),
])
);
}
}

View file

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

View file

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

View file

@ -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<Value> for Part {
}
}
impl From<Thing> for Part {
fn from(v: Thing) -> Self {
Part::Thing(v)
}
}
impl From<Graph> 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)))

View file

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

View file

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

View file

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

View file

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

View file

@ -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::<Vec<_>>()
.into()
}
v => v,
}
}
}

View file

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

View file

@ -14,6 +14,7 @@ mod diff;
mod each;
mod every;
mod first;
mod flatten;
mod get;
mod increment;
mod last;

View file

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

View file

@ -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<Edges>),
Function(Box<Function>),
Subquery(Box<Subquery>),
Expression(Box<Expression>),
@ -150,6 +152,12 @@ impl From<Idiom> for Value {
}
}
impl From<Model> for Value {
fn from(v: Model) -> Self {
Value::Model(v)
}
}
impl From<Table> for Value {
fn from(v: Table) -> Self {
Value::Table(v)
@ -210,21 +218,27 @@ impl From<Duration> for Value {
}
}
impl From<Edges> for Value {
fn from(v: Edges) -> Self {
Value::Edges(Box::new(v))
}
}
impl From<Function> for Value {
fn from(v: Function) -> Self {
Value::Function(Box::new(v))
}
}
impl From<Expression> for Value {
fn from(v: Expression) -> Self {
Value::Expression(Box::new(v))
impl From<Subquery> for Value {
fn from(v: Subquery) -> Self {
Value::Subquery(Box::new(v))
}
}
impl From<Box<Expression>> for Value {
fn from(v: Box<Expression>) -> Self {
Value::Expression(v)
impl From<Expression> 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::<crate::sql::thing::Thing>());
assert_eq!(48, std::mem::size_of::<crate::sql::model::Model>());
assert_eq!(24, std::mem::size_of::<crate::sql::regex::Regex>());
assert_eq!(8, std::mem::size_of::<Box<crate::sql::edges::Edges>>());
assert_eq!(8, std::mem::size_of::<Box<crate::sql::function::Function>>());
assert_eq!(8, std::mem::size_of::<Box<crate::sql::subquery::Subquery>>());
assert_eq!(8, std::mem::size_of::<Box<crate::sql::expression::Expression>>());