Path processing improvements (#2250)
This commit is contained in:
parent
3a34821dc0
commit
a12348db8e
10 changed files with 386 additions and 121 deletions
|
@ -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, value, Part};
|
use crate::sql::part::{all, field, first, graph, index, last, part, start, Part};
|
||||||
use crate::sql::paths::{ID, IN, META, OUT};
|
use crate::sql::paths::{ID, IN, META, OUT};
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
use md5::Digest;
|
use md5::Digest;
|
||||||
|
@ -92,7 +92,9 @@ impl Idiom {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|p| matches!(p, Part::Field(_) | Part::Value(_) | Part::Graph(_)))
|
.filter(|p| {
|
||||||
|
matches!(p, Part::Field(_) | Part::Start(_) | Part::Value(_) | Part::Graph(_))
|
||||||
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
@ -137,7 +139,7 @@ impl Idiom {
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
match self.first() {
|
match self.first() {
|
||||||
// The starting part is a value
|
// The starting part is a value
|
||||||
Some(Part::Value(v)) => {
|
Some(Part::Start(v)) => {
|
||||||
v.compute(ctx, opt, txn, doc)
|
v.compute(ctx, opt, txn, doc)
|
||||||
.await?
|
.await?
|
||||||
.get(ctx, opt, txn, doc, self.as_ref().next())
|
.get(ctx, opt, txn, doc, self.as_ref().next())
|
||||||
|
@ -209,7 +211,7 @@ pub fn multi(i: &str) -> IResult<&str, Idiom> {
|
||||||
Ok((i, Idiom::from(v)))
|
Ok((i, Idiom::from(v)))
|
||||||
},
|
},
|
||||||
|i| {
|
|i| {
|
||||||
let (i, p) = alt((first, value))(i)?;
|
let (i, p) = alt((first, start))(i)?;
|
||||||
let (i, mut v) = many1(part)(i)?;
|
let (i, mut v) = many1(part)(i)?;
|
||||||
v.insert(0, p);
|
v.insert(0, p);
|
||||||
Ok((i, Idiom::from(v)))
|
Ok((i, Idiom::from(v)))
|
||||||
|
@ -381,7 +383,7 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
Idiom(vec![
|
Idiom(vec![
|
||||||
Part::Value(Param::from("test").into()),
|
Part::Start(Param::from("test").into()),
|
||||||
Part::from("temporary"),
|
Part::from("temporary"),
|
||||||
Part::Index(Number::Int(0)),
|
Part::Index(Number::Int(0)),
|
||||||
Part::from("embedded"),
|
Part::from("embedded"),
|
||||||
|
@ -399,7 +401,7 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
Idiom(vec![
|
Idiom(vec![
|
||||||
Part::Value(Thing::from(("person", "test")).into()),
|
Part::Start(Thing::from(("person", "test")).into()),
|
||||||
Part::from("friend"),
|
Part::from("friend"),
|
||||||
Part::Graph(Graph {
|
Part::Graph(Graph {
|
||||||
dir: Dir::Out,
|
dir: Dir::Out,
|
||||||
|
|
|
@ -5,16 +5,16 @@ use crate::sql::error::IResult;
|
||||||
use crate::sql::fmt::Fmt;
|
use crate::sql::fmt::Fmt;
|
||||||
use crate::sql::graph::{self, Graph};
|
use crate::sql::graph::{self, Graph};
|
||||||
use crate::sql::ident::{self, Ident};
|
use crate::sql::ident::{self, Ident};
|
||||||
use crate::sql::idiom::Idiom;
|
use crate::sql::idiom::{self, Idiom};
|
||||||
use crate::sql::number::{number, Number};
|
use crate::sql::number::{number, Number};
|
||||||
use crate::sql::strand::no_nul_bytes;
|
use crate::sql::param::{self};
|
||||||
|
use crate::sql::strand::{self, no_nul_bytes};
|
||||||
use crate::sql::value::{self, Value};
|
use crate::sql::value::{self, 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::{map, not, peek};
|
||||||
use nom::combinator::peek;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
@ -29,6 +29,7 @@ pub enum Part {
|
||||||
Where(Value),
|
Where(Value),
|
||||||
Graph(Graph),
|
Graph(Graph),
|
||||||
Value(Value),
|
Value(Value),
|
||||||
|
Start(Value),
|
||||||
Method(#[serde(with = "no_nul_bytes")] String, Vec<Value>),
|
Method(#[serde(with = "no_nul_bytes")] String, Vec<Value>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,12 +69,6 @@ impl From<Ident> for Part {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Value> for Part {
|
|
||||||
fn from(v: Value) -> Self {
|
|
||||||
Self::Where(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)
|
||||||
|
@ -93,6 +88,7 @@ impl Part {
|
||||||
/// Check if we require a writeable transaction
|
/// Check if we require a writeable transaction
|
||||||
pub(crate) fn writeable(&self) -> bool {
|
pub(crate) fn writeable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
Part::Start(v) => v.writeable(),
|
||||||
Part::Where(v) => v.writeable(),
|
Part::Where(v) => v.writeable(),
|
||||||
Part::Value(v) => v.writeable(),
|
Part::Value(v) => v.writeable(),
|
||||||
Part::Method(_, v) => v.iter().any(Value::writeable),
|
Part::Method(_, v) => v.iter().any(Value::writeable),
|
||||||
|
@ -114,11 +110,12 @@ impl fmt::Display for Part {
|
||||||
Part::All => f.write_str("[*]"),
|
Part::All => f.write_str("[*]"),
|
||||||
Part::Last => f.write_str("[$]"),
|
Part::Last => f.write_str("[$]"),
|
||||||
Part::First => f.write_str("[0]"),
|
Part::First => f.write_str("[0]"),
|
||||||
|
Part::Start(v) => write!(f, "{v}"),
|
||||||
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::Graph(v) => write!(f, "{v}"),
|
Part::Graph(v) => write!(f, "{v}"),
|
||||||
Part::Value(v) => write!(f, "{v}"),
|
Part::Value(v) => write!(f, "[{v}]"),
|
||||||
Part::Method(v, a) => write!(f, ".{v}({})", Fmt::comma_separated(a)),
|
Part::Method(v, a) => write!(f, ".{v}({})", Fmt::comma_separated(a)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,7 +139,7 @@ impl<'a> Next<'a> for &'a [Part] {
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
|
||||||
pub fn part(i: &str) -> IResult<&str, Part> {
|
pub fn part(i: &str) -> IResult<&str, Part> {
|
||||||
alt((all, last, index, field, graph, filter))(i)
|
alt((all, last, index, field, value, graph, filter))(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn first(i: &str) -> IResult<&str, Part> {
|
pub fn first(i: &str) -> IResult<&str, Part> {
|
||||||
|
@ -200,10 +197,21 @@ pub fn filter(i: &str) -> IResult<&str, Part> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(i: &str) -> IResult<&str, Part> {
|
pub fn value(i: &str) -> IResult<&str, Part> {
|
||||||
let (i, v) = value::start(i)?;
|
let (i, _) = openbracket(i)?;
|
||||||
|
let (i, v) = alt((
|
||||||
|
map(strand::strand, Value::Strand),
|
||||||
|
map(param::param, Value::Param),
|
||||||
|
map(idiom::basic, Value::Idiom),
|
||||||
|
))(i)?;
|
||||||
|
let (i, _) = closebracket(i)?;
|
||||||
Ok((i, Part::Value(v)))
|
Ok((i, Part::Value(v)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start(i: &str) -> IResult<&str, Part> {
|
||||||
|
let (i, v) = value::start(i)?;
|
||||||
|
Ok((i, Part::Start(v)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn graph(i: &str) -> IResult<&str, Part> {
|
pub fn graph(i: &str) -> IResult<&str, Part> {
|
||||||
let (i, v) = graph::graph(i)?;
|
let (i, v) = graph::graph(i)?;
|
||||||
Ok((i, Part::Graph(v)))
|
Ok((i, Part::Graph(v)))
|
||||||
|
|
|
@ -6,9 +6,9 @@ impl Value {
|
||||||
/// Synchronous method for deleting a field from a `Value`
|
/// Synchronous method for deleting a field from a `Value`
|
||||||
pub(crate) fn cut(&mut self, path: &[Part]) {
|
pub(crate) fn cut(&mut self, path: &[Part]) {
|
||||||
if let Some(p) = path.first() {
|
if let Some(p) = path.first() {
|
||||||
// Get the current path part
|
// Get the current value at path
|
||||||
match self {
|
match self {
|
||||||
// Current path part is an object
|
// Current value at path is an object
|
||||||
Value::Object(v) => match p {
|
Value::Object(v) => match p {
|
||||||
Part::Field(f) => match path.len() {
|
Part::Field(f) => match path.len() {
|
||||||
1 => {
|
1 => {
|
||||||
|
@ -32,7 +32,7 @@ impl Value {
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
// Current path part is an array
|
// Current value at path is an array
|
||||||
Value::Array(v) => match p {
|
Value::Array(v) => match p {
|
||||||
Part::All => match path.len() {
|
Part::All => match path.len() {
|
||||||
1 => {
|
1 => {
|
||||||
|
@ -96,127 +96,93 @@ impl Value {
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dbs::test::mock;
|
|
||||||
use crate::sql::idiom::Idiom;
|
use crate::sql::idiom::Idiom;
|
||||||
use crate::sql::test::Parse;
|
use crate::sql::test::Parse;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn del_none() {
|
async fn cut_none() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::default();
|
let idi = Idiom::default();
|
||||||
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
let res = Value::parse("{ test: { other: null, something: 123 } }");
|
let res = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.cut(&idi);
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn del_reset() {
|
async fn cut_reset() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test");
|
let idi = Idiom::parse("test");
|
||||||
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
let res = Value::parse("{ }");
|
let res = Value::parse("{ }");
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.cut(&idi);
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn del_basic() {
|
async fn cut_basic() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test.something");
|
let idi = Idiom::parse("test.something");
|
||||||
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
let res = Value::parse("{ test: { other: null } }");
|
let res = Value::parse("{ test: { other: null } }");
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.cut(&idi);
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn del_wrong() {
|
async fn cut_wrong() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test.something.wrong");
|
let idi = Idiom::parse("test.something.wrong");
|
||||||
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
let res = Value::parse("{ test: { other: null, something: 123 } }");
|
let res = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.cut(&idi);
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn del_other() {
|
async fn cut_other() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test.other.something");
|
let idi = Idiom::parse("test.other.something");
|
||||||
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
let mut val = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
let res = Value::parse("{ test: { other: null, something: 123 } }");
|
let res = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.cut(&idi);
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn del_array() {
|
async fn cut_array() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test.something[1]");
|
let idi = Idiom::parse("test.something[1]");
|
||||||
let mut val = Value::parse("{ test: { something: [123, 456, 789] } }");
|
let mut val = Value::parse("{ test: { something: [123, 456, 789] } }");
|
||||||
let res = Value::parse("{ test: { something: [123, 789] } }");
|
let res = Value::parse("{ test: { something: [123, 789] } }");
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.cut(&idi);
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn del_array_field() {
|
async fn cut_array_field() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test.something[1].age");
|
let idi = Idiom::parse("test.something[1].age");
|
||||||
let mut val = Value::parse(
|
let mut val = Value::parse(
|
||||||
"{ test: { something: [{ name: 'A', age: 34 }, { name: 'B', age: 36 }] } }",
|
"{ test: { something: [{ name: 'A', age: 34 }, { name: 'B', age: 36 }] } }",
|
||||||
);
|
);
|
||||||
let res = Value::parse("{ test: { something: [{ name: 'A', age: 34 }, { name: 'B' }] } }");
|
let res = Value::parse("{ test: { something: [{ name: 'A', age: 34 }, { name: 'B' }] } }");
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.cut(&idi);
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn del_array_fields() {
|
async fn cut_array_fields() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test.something[*].age");
|
let idi = Idiom::parse("test.something[*].age");
|
||||||
let mut val = Value::parse(
|
let mut val = Value::parse(
|
||||||
"{ test: { something: [{ name: 'A', age: 34 }, { name: 'B', age: 36 }] } }",
|
"{ test: { something: [{ name: 'A', age: 34 }, { name: 'B', age: 36 }] } }",
|
||||||
);
|
);
|
||||||
let res = Value::parse("{ test: { something: [{ name: 'A' }, { name: 'B' }] } }");
|
let res = Value::parse("{ test: { something: [{ name: 'A' }, { name: 'B' }] } }");
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.cut(&idi);
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn del_array_fields_flat() {
|
async fn cut_array_fields_flat() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test.something.age");
|
let idi = Idiom::parse("test.something.age");
|
||||||
let mut val = Value::parse(
|
let mut val = Value::parse(
|
||||||
"{ test: { something: [{ name: 'A', age: 34 }, { name: 'B', age: 36 }] } }",
|
"{ test: { something: [{ name: 'A', age: 34 }, { name: 'B', age: 36 }] } }",
|
||||||
);
|
);
|
||||||
let res = Value::parse("{ test: { something: [{ name: 'A' }, { name: 'B' }] } }");
|
let res = Value::parse("{ test: { something: [{ name: 'A' }, { name: 'B' }] } }");
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.cut(&idi);
|
||||||
assert_eq!(res, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn del_array_where_field() {
|
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test.something[WHERE age > 35].age");
|
|
||||||
let mut val = Value::parse(
|
|
||||||
"{ test: { something: [{ name: 'A', age: 34 }, { name: 'B', age: 36 }] } }",
|
|
||||||
);
|
|
||||||
let res = Value::parse("{ test: { something: [{ name: 'A', age: 34 }, { name: 'B' }] } }");
|
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
|
||||||
assert_eq!(res, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn del_array_where_fields() {
|
|
||||||
let (ctx, opt, txn) = mock().await;
|
|
||||||
let idi = Idiom::parse("test.something[WHERE age > 35]");
|
|
||||||
let mut val = Value::parse(
|
|
||||||
"{ test: { something: [{ name: 'A', age: 34 }, { name: 'B', age: 36 }] } }",
|
|
||||||
);
|
|
||||||
let res = Value::parse("{ test: { something: [{ name: 'A', age: 34 }] } }");
|
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,9 @@ impl Value {
|
||||||
path: &[Part],
|
path: &[Part],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
match path.first() {
|
match path.first() {
|
||||||
// Get the current path part
|
// Get the current value at path
|
||||||
Some(p) => match self {
|
Some(p) => match self {
|
||||||
// Current path part is an object
|
// Current value at path is an object
|
||||||
Value::Object(v) => match p {
|
Value::Object(v) => match p {
|
||||||
Part::Field(f) => match path.len() {
|
Part::Field(f) => match path.len() {
|
||||||
1 => {
|
1 => {
|
||||||
|
@ -46,9 +46,22 @@ impl Value {
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Part::Value(x) => match x.compute(ctx, opt, txn, None).await? {
|
||||||
|
Value::Strand(f) => match path.len() {
|
||||||
|
1 => {
|
||||||
|
v.remove(f.as_str());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => match v.get_mut(f.as_str()) {
|
||||||
|
Some(v) if v.is_some() => v.del(ctx, opt, txn, path.next()).await,
|
||||||
|
_ => Ok(()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => Ok(()),
|
||||||
|
},
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
},
|
},
|
||||||
// Current path part is an array
|
// Current value at path is an array
|
||||||
Value::Array(v) => match p {
|
Value::Array(v) => match p {
|
||||||
Part::All => match path.len() {
|
Part::All => match path.len() {
|
||||||
1 => {
|
1 => {
|
||||||
|
@ -114,16 +127,59 @@ impl Value {
|
||||||
v.abolish(|i| m.contains(&i));
|
v.abolish(|i| m.contains(&i));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => {
|
_ => match path.next().first() {
|
||||||
let path = path.next();
|
Some(Part::Index(_)) => {
|
||||||
for v in v.iter_mut() {
|
let mut a = Vec::new();
|
||||||
let cur = CursorDoc::new(None, None, v);
|
let mut p = Vec::new();
|
||||||
if w.compute(ctx, opt, txn, Some(&cur)).await?.is_truthy() {
|
// Store the elements and positions to update
|
||||||
v.del(ctx, opt, txn, path).await?;
|
for (i, o) in v.iter_mut().enumerate() {
|
||||||
|
let cur = CursorDoc::new(None, None, o);
|
||||||
|
if w.compute(ctx, opt, txn, Some(&cur)).await?.is_truthy() {
|
||||||
|
a.push(o.clone());
|
||||||
|
p.push(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Convert the matched elements array to a value
|
||||||
|
let mut a = Value::from(a);
|
||||||
|
// Set the new value on the matches elements
|
||||||
|
a.del(ctx, opt, txn, path.next()).await?;
|
||||||
|
// Push the new values into the original array
|
||||||
|
for (i, p) in p.into_iter().enumerate().rev() {
|
||||||
|
match a.pick(&[Part::Index(i.into())]) {
|
||||||
|
Value::None => {
|
||||||
|
v.remove(i);
|
||||||
|
}
|
||||||
|
x => v[p] = x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
_ => {
|
||||||
}
|
let path = path.next();
|
||||||
|
for v in v.iter_mut() {
|
||||||
|
let cur = CursorDoc::new(None, None, v);
|
||||||
|
if w.compute(ctx, opt, txn, Some(&cur)).await?.is_truthy() {
|
||||||
|
v.del(ctx, opt, txn, path).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Part::Value(x) => match x.compute(ctx, opt, txn, None).await? {
|
||||||
|
Value::Number(i) => match path.len() {
|
||||||
|
1 => {
|
||||||
|
if v.len().gt(&i.to_usize()) {
|
||||||
|
v.remove(i.to_usize());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => match v.get_mut(i.to_usize()) {
|
||||||
|
Some(v) => v.del(ctx, opt, txn, path.next()).await,
|
||||||
|
None => Ok(()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => Ok(()),
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let futs = v.iter_mut().map(|v| v.del(ctx, opt, txn, path));
|
let futs = v.iter_mut().map(|v| v.del(ctx, opt, txn, path));
|
||||||
|
@ -267,4 +323,16 @@ mod tests {
|
||||||
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn del_array_where_fields_array_index() {
|
||||||
|
let (ctx, opt, txn) = mock().await;
|
||||||
|
let idi = Idiom::parse("test.something[WHERE age > 30][0]");
|
||||||
|
let mut val = Value::parse(
|
||||||
|
"{ test: { something: [{ name: 'A', age: 34 }, { name: 'B', age: 36 }] } }",
|
||||||
|
);
|
||||||
|
let res = Value::parse("{ test: { something: [{ name: 'B', age: 36 }] } }");
|
||||||
|
val.del(&ctx, &opt, &txn, &idi).await.unwrap();
|
||||||
|
assert_eq!(res, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,9 @@ impl Value {
|
||||||
path: &[Part],
|
path: &[Part],
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
match path.first() {
|
match path.first() {
|
||||||
// Get the current path part
|
// Get the current value at the path
|
||||||
Some(p) => match self {
|
Some(p) => match self {
|
||||||
// Current path part is a geometry
|
// Current value at path is a geometry
|
||||||
Value::Geometry(v) => match p {
|
Value::Geometry(v) => match p {
|
||||||
// If this is the 'type' field then continue
|
// If this is the 'type' field then continue
|
||||||
Part::Field(f) if f.is_type() => {
|
Part::Field(f) if f.is_type() => {
|
||||||
|
@ -43,10 +43,10 @@ impl Value {
|
||||||
Part::Field(f) if f.is_geometries() && v.is_collection() => {
|
Part::Field(f) if f.is_geometries() && v.is_collection() => {
|
||||||
v.as_coordinates().get(ctx, opt, txn, doc, path.next()).await
|
v.as_coordinates().get(ctx, opt, txn, doc, path.next()).await
|
||||||
}
|
}
|
||||||
// otherwise return none
|
// Otherwise return none
|
||||||
_ => Ok(Value::None),
|
_ => Ok(Value::None),
|
||||||
},
|
},
|
||||||
// Current path part is a future
|
// Current value at path is a future
|
||||||
Value::Future(v) => {
|
Value::Future(v) => {
|
||||||
// Check how many path parts are remaining
|
// Check how many path parts are remaining
|
||||||
match path.len() {
|
match path.len() {
|
||||||
|
@ -63,10 +63,10 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Current path part is an object
|
// Current value at path is an object
|
||||||
Value::Object(v) => match p {
|
Value::Object(v) => match p {
|
||||||
// If requesting an `id` field, check if it is a complex Record ID
|
// If requesting an `id` field, check if it is a complex Record ID
|
||||||
Part::Field(f) if f.is_id() && path.len() > 1 => match v.get(f as &str) {
|
Part::Field(f) if f.is_id() && path.len() > 1 => match v.get(f.as_str()) {
|
||||||
Some(Value::Thing(Thing {
|
Some(Value::Thing(Thing {
|
||||||
id: Id::Object(v),
|
id: Id::Object(v),
|
||||||
..
|
..
|
||||||
|
@ -82,15 +82,27 @@ impl Value {
|
||||||
Some(v) => Value::Thing(v).get(ctx, opt, txn, doc, path).await,
|
Some(v) => Value::Thing(v).get(ctx, opt, txn, doc, path).await,
|
||||||
None => Ok(Value::None),
|
None => Ok(Value::None),
|
||||||
},
|
},
|
||||||
Part::Field(f) => match v.get(f as &str) {
|
Part::Field(f) => match v.get(f.as_str()) {
|
||||||
Some(v) => v.get(ctx, opt, txn, doc, path.next()).await,
|
Some(v) => v.get(ctx, opt, txn, doc, path.next()).await,
|
||||||
None => Ok(Value::None),
|
None => Ok(Value::None),
|
||||||
},
|
},
|
||||||
|
Part::Index(i) => match v.get(&i.to_string()) {
|
||||||
|
Some(v) => v.get(ctx, opt, txn, doc, path.next()).await,
|
||||||
|
None => Ok(Value::None),
|
||||||
|
},
|
||||||
|
Part::Value(x) => match x.compute(ctx, opt, txn, doc).await? {
|
||||||
|
Value::Strand(f) => match v.get(f.as_str()) {
|
||||||
|
Some(v) => v.get(ctx, opt, txn, doc, path.next()).await,
|
||||||
|
None => Ok(Value::None),
|
||||||
|
},
|
||||||
|
_ => Ok(Value::None),
|
||||||
|
},
|
||||||
Part::All => self.get(ctx, opt, txn, doc, path.next()).await,
|
Part::All => self.get(ctx, opt, txn, doc, path.next()).await,
|
||||||
_ => Ok(Value::None),
|
_ => Ok(Value::None),
|
||||||
},
|
},
|
||||||
// Current path part is an array
|
// Current value at path is an array
|
||||||
Value::Array(v) => match p {
|
Value::Array(v) => match p {
|
||||||
|
// Current path is an `*` part
|
||||||
Part::All => {
|
Part::All => {
|
||||||
let path = path.next();
|
let path = path.next();
|
||||||
let futs = v.iter().map(|v| v.get(ctx, opt, txn, doc, path));
|
let futs = v.iter().map(|v| v.get(ctx, opt, txn, doc, path));
|
||||||
|
@ -109,22 +121,28 @@ impl Value {
|
||||||
None => Ok(Value::None),
|
None => Ok(Value::None),
|
||||||
},
|
},
|
||||||
Part::Where(w) => {
|
Part::Where(w) => {
|
||||||
let path = path.next();
|
|
||||||
let mut a = Vec::new();
|
let mut a = Vec::new();
|
||||||
for v in v.iter() {
|
for v in v.iter() {
|
||||||
let cur = Some(CursorDoc::new(None, None, v));
|
let cur = Some(CursorDoc::new(None, None, v));
|
||||||
if w.compute(ctx, opt, txn, cur.as_ref()).await?.is_truthy() {
|
if w.compute(ctx, opt, txn, cur.as_ref()).await?.is_truthy() {
|
||||||
a.push(v.get(ctx, opt, txn, cur.as_ref(), path).await?)
|
a.push(v.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(a.into())
|
Value::from(a).get(ctx, opt, txn, doc, path.next()).await
|
||||||
}
|
}
|
||||||
|
Part::Value(x) => match x.compute(ctx, opt, txn, doc).await? {
|
||||||
|
Value::Number(i) => match v.get(i.to_usize()) {
|
||||||
|
Some(v) => v.get(ctx, opt, txn, doc, path.next()).await,
|
||||||
|
None => Ok(Value::None),
|
||||||
|
},
|
||||||
|
_ => Ok(Value::None),
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let futs = v.iter().map(|v| v.get(ctx, opt, txn, doc, path));
|
let futs = v.iter().map(|v| v.get(ctx, opt, txn, doc, path));
|
||||||
try_join_all_buffered(futs).await.map(Into::into)
|
try_join_all_buffered(futs).await.map(Into::into)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Current path part is an edges
|
// Current value at path is an edges
|
||||||
Value::Edges(v) => {
|
Value::Edges(v) => {
|
||||||
// Clone the thing
|
// Clone the thing
|
||||||
let val = v.clone();
|
let val = v.clone();
|
||||||
|
@ -147,7 +165,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Current path part is a thing
|
// Current value at path is a thing
|
||||||
Value::Thing(v) => {
|
Value::Thing(v) => {
|
||||||
// Clone the thing
|
// Clone the thing
|
||||||
let val = v.clone();
|
let val = v.clone();
|
||||||
|
@ -330,6 +348,20 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_array_where_fields_array_index() {
|
||||||
|
let (ctx, opt, txn) = mock().await;
|
||||||
|
let idi = Idiom::parse("test.something[WHERE age > 30][0]");
|
||||||
|
let val = Value::parse("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
|
||||||
|
let res = val.get(&ctx, &opt, &txn, None, &idi).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Value::from(map! {
|
||||||
|
"age".to_string() => Value::from(34),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_future_embedded_field() {
|
async fn get_future_embedded_field() {
|
||||||
let (ctx, opt, txn) = mock().await;
|
let (ctx, opt, txn) = mock().await;
|
||||||
|
|
|
@ -6,9 +6,9 @@ impl Value {
|
||||||
/// Synchronous method for getting a field from a `Value`
|
/// Synchronous method for getting a field from a `Value`
|
||||||
pub fn pick(&self, path: &[Part]) -> Self {
|
pub fn pick(&self, path: &[Part]) -> Self {
|
||||||
match path.first() {
|
match path.first() {
|
||||||
// Get the current path part
|
// Get the current value at path
|
||||||
Some(p) => match self {
|
Some(p) => match self {
|
||||||
// Current path part is an object
|
// Current value at path is an object
|
||||||
Value::Object(v) => match p {
|
Value::Object(v) => match p {
|
||||||
Part::Field(f) => match v.get(f as &str) {
|
Part::Field(f) => match v.get(f as &str) {
|
||||||
Some(v) => v.pick(path.next()),
|
Some(v) => v.pick(path.next()),
|
||||||
|
@ -21,7 +21,7 @@ impl Value {
|
||||||
Part::All => self.pick(path.next()),
|
Part::All => self.pick(path.next()),
|
||||||
_ => Value::None,
|
_ => Value::None,
|
||||||
},
|
},
|
||||||
// Current path part is an array
|
// Current value at path is an array
|
||||||
Value::Array(v) => match p {
|
Value::Array(v) => match p {
|
||||||
Part::All => v.iter().map(|v| v.pick(path.next())).collect::<Vec<_>>().into(),
|
Part::All => v.iter().map(|v| v.pick(path.next())).collect::<Vec<_>>().into(),
|
||||||
Part::First => match v.first() {
|
Part::First => match v.first() {
|
||||||
|
|
|
@ -6,9 +6,9 @@ impl Value {
|
||||||
/// Synchronous method for setting a field on a `Value`
|
/// Synchronous method for setting a field on a `Value`
|
||||||
pub fn put(&mut self, path: &[Part], val: Value) {
|
pub fn put(&mut self, path: &[Part], val: Value) {
|
||||||
match path.first() {
|
match path.first() {
|
||||||
// Get the current path part
|
// Get the current value at path
|
||||||
Some(p) => match self {
|
Some(p) => match self {
|
||||||
// Current path part is an object
|
// Current value at path is an object
|
||||||
Value::Object(v) => match p {
|
Value::Object(v) => match p {
|
||||||
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),
|
||||||
|
@ -36,7 +36,7 @@ impl Value {
|
||||||
},
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
// Current path part is an array
|
// Current value at path is an array
|
||||||
Value::Array(v) => match p {
|
Value::Array(v) => match p {
|
||||||
Part::All => {
|
Part::All => {
|
||||||
let path = path.next();
|
let path = path.next();
|
||||||
|
@ -61,12 +61,12 @@ impl Value {
|
||||||
v.iter_mut().for_each(|v| v.put(path, val.clone()));
|
v.iter_mut().for_each(|v| v.put(path, val.clone()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Current path part is empty
|
// Current value at path is empty
|
||||||
Value::Null => {
|
Value::Null => {
|
||||||
*self = Value::base();
|
*self = Value::base();
|
||||||
self.put(path, val)
|
self.put(path, val)
|
||||||
}
|
}
|
||||||
// Current path part is empty
|
// Current value at path is empty
|
||||||
Value::None => {
|
Value::None => {
|
||||||
*self = Value::base();
|
*self = Value::base();
|
||||||
self.put(path, val)
|
self.put(path, val)
|
||||||
|
|
|
@ -55,6 +55,7 @@ impl ser::Serializer for Serializer {
|
||||||
"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())?)),
|
||||||
"Graph" => Ok(Part::Graph(value.serialize(ser::graph::Serializer.wrap())?)),
|
"Graph" => Ok(Part::Graph(value.serialize(ser::graph::Serializer.wrap())?)),
|
||||||
|
"Start" => Ok(Part::Start(value.serialize(ser::value::Serializer.wrap())?)),
|
||||||
"Value" => Ok(Part::Value(value.serialize(ser::value::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}`")))
|
||||||
|
@ -119,6 +120,13 @@ mod tests {
|
||||||
assert_eq!(part, serialized);
|
assert_eq!(part, serialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start() {
|
||||||
|
let part = Part::Start(sql::thing("foo:bar").unwrap().into());
|
||||||
|
let serialized = part.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(part, serialized);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn value() {
|
fn value() {
|
||||||
let part = Part::Value(sql::thing("foo:bar").unwrap().into());
|
let part = Part::Value(sql::thing("foo:bar").unwrap().into());
|
||||||
|
|
|
@ -21,9 +21,9 @@ impl Value {
|
||||||
val: Value,
|
val: Value,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
match path.first() {
|
match path.first() {
|
||||||
// Get the current path part
|
// Get the current value at path
|
||||||
Some(p) => match self {
|
Some(p) => match self {
|
||||||
// Current path part is an object
|
// Current value at path is an object
|
||||||
Value::Object(v) => match p {
|
Value::Object(v) => match p {
|
||||||
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,
|
||||||
|
@ -34,7 +34,7 @@ impl Value {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Part::Field(f) => match v.get_mut(f as &str) {
|
Part::Field(f) => match v.get_mut(f.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,
|
||||||
_ => {
|
_ => {
|
||||||
let mut obj = Value::base();
|
let mut obj = Value::base();
|
||||||
|
@ -52,9 +52,21 @@ impl Value {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Part::Value(x) => match x.compute(ctx, opt, txn, None).await? {
|
||||||
|
Value::Strand(f) => match v.get_mut(f.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(f.to_string(), obj);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => Ok(()),
|
||||||
|
},
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
},
|
},
|
||||||
// Current path part is an array
|
// Current value at path is an array
|
||||||
Value::Array(v) => match p {
|
Value::Array(v) => match p {
|
||||||
Part::All => {
|
Part::All => {
|
||||||
let path = path.next();
|
let path = path.next();
|
||||||
|
@ -74,28 +86,58 @@ impl Value {
|
||||||
Some(v) => v.set(ctx, opt, txn, path.next(), val).await,
|
Some(v) => v.set(ctx, opt, txn, path.next(), val).await,
|
||||||
None => Ok(()),
|
None => Ok(()),
|
||||||
},
|
},
|
||||||
Part::Where(w) => {
|
Part::Where(w) => match path.next().first() {
|
||||||
let path = path.next();
|
Some(Part::Index(_)) => {
|
||||||
for v in v.iter_mut() {
|
let mut a = Vec::new();
|
||||||
let cur = CursorDoc::new(None, None, v);
|
let mut p = Vec::new();
|
||||||
if w.compute(ctx, opt, txn, Some(&cur)).await?.is_truthy() {
|
// Store the elements and positions to update
|
||||||
v.set(ctx, opt, txn, path, val.clone()).await?;
|
for (i, o) in v.iter_mut().enumerate() {
|
||||||
|
let cur = CursorDoc::new(None, None, o);
|
||||||
|
if w.compute(ctx, opt, txn, Some(&cur)).await?.is_truthy() {
|
||||||
|
a.push(o.clone());
|
||||||
|
p.push(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Convert the matched elements array to a value
|
||||||
|
let mut a = Value::from(a);
|
||||||
|
// Set the new value on the matches elements
|
||||||
|
a.set(ctx, opt, txn, path.next(), val.clone()).await?;
|
||||||
|
// Push the new values into the original array
|
||||||
|
for (i, p) in p.into_iter().enumerate() {
|
||||||
|
v[p] = a.pick(&[Part::Index(i.into())]);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
_ => {
|
||||||
}
|
let path = path.next();
|
||||||
|
for v in v.iter_mut() {
|
||||||
|
let cur = CursorDoc::new(None, None, v);
|
||||||
|
if w.compute(ctx, opt, txn, Some(&cur)).await?.is_truthy() {
|
||||||
|
v.set(ctx, opt, txn, path, val.clone()).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Part::Value(x) => match x.compute(ctx, opt, txn, None).await? {
|
||||||
|
Value::Number(i) => match v.get_mut(i.to_usize()) {
|
||||||
|
Some(v) => v.set(ctx, opt, txn, path.next(), val).await,
|
||||||
|
None => Ok(()),
|
||||||
|
},
|
||||||
|
_ => Ok(()),
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let futs = v.iter_mut().map(|v| v.set(ctx, opt, txn, path, val.clone()));
|
let futs = v.iter_mut().map(|v| v.set(ctx, opt, txn, path, val.clone()));
|
||||||
try_join_all_buffered(futs).await?;
|
try_join_all_buffered(futs).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Current path part is empty
|
// Current value at path is empty
|
||||||
Value::Null => {
|
Value::Null => {
|
||||||
*self = Value::base();
|
*self = Value::base();
|
||||||
self.set(ctx, opt, txn, path, val).await
|
self.set(ctx, opt, txn, path, val).await
|
||||||
}
|
}
|
||||||
// Current path part is empty
|
// Current value at path is empty
|
||||||
Value::None => {
|
Value::None => {
|
||||||
*self = Value::base();
|
*self = Value::base();
|
||||||
self.set(ctx, opt, txn, path, val).await
|
self.set(ctx, opt, txn, path, val).await
|
||||||
|
@ -259,4 +301,24 @@ mod tests {
|
||||||
val.set(&ctx, &opt, &txn, &idi, Value::from(21)).await.unwrap();
|
val.set(&ctx, &opt, &txn, &idi, Value::from(21)).await.unwrap();
|
||||||
assert_eq!(res, val);
|
assert_eq!(res, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn set_array_where_fields_array_index() {
|
||||||
|
let (ctx, opt, txn) = mock().await;
|
||||||
|
let idi = Idiom::parse("test.something[WHERE age > 30][0]");
|
||||||
|
let mut val = Value::parse("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
|
||||||
|
let res = Value::parse("{ test: { something: [21, { age: 36 }] } }");
|
||||||
|
val.set(&ctx, &opt, &txn, &idi, Value::from(21)).await.unwrap();
|
||||||
|
assert_eq!(res, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn set_array_where_fields_array_index_field() {
|
||||||
|
let (ctx, opt, txn) = mock().await;
|
||||||
|
let idi = Idiom::parse("test.something[WHERE age > 30][0].age");
|
||||||
|
let mut val = Value::parse("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
|
||||||
|
let res = Value::parse("{ test: { something: [{ age: 21 }, { age: 36 }] } }");
|
||||||
|
val.set(&ctx, &opt, &txn, &idi, Value::from(21)).await.unwrap();
|
||||||
|
assert_eq!(res, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,125 @@ async fn select_expression_value() -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_dynamic_array_keys_and_object_keys() -> Result<(), Error> {
|
||||||
|
let sql = "
|
||||||
|
LET $lang = 'en';
|
||||||
|
UPDATE documentation:test CONTENT {
|
||||||
|
primarylang: 'en',
|
||||||
|
languages: {
|
||||||
|
'en': 'this is english',
|
||||||
|
'es': 'esto es español',
|
||||||
|
'de': 'das ist Englisch',
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
{ type: 'library', value: 'client-side' },
|
||||||
|
{ type: 'library', value: 'server-side' },
|
||||||
|
{ type: 'environment', value: 'frontend' },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
-- An array filter, followed by an array index operation
|
||||||
|
SELECT tags[WHERE type = 'library'][0].value FROM documentation:test;
|
||||||
|
-- Selecting an object value or array index using a string as a key
|
||||||
|
SELECT languages['en'] AS content FROM documentation:test;
|
||||||
|
-- Updating an object value or array index using a string as a key
|
||||||
|
UPDATE documentation:test SET languages['en'] = 'my primary text';
|
||||||
|
-- Selecting an object value or array index using a parameter as a key
|
||||||
|
SELECT languages[$lang] AS content FROM documentation:test;
|
||||||
|
-- Updating an object value or array index using a parameter as a key
|
||||||
|
UPDATE documentation:test SET languages[$lang] = 'my secondary text';
|
||||||
|
-- Selecting an object or array index value using the value of another document field as a key
|
||||||
|
SELECT languages[primarylang] AS content FROM documentation;
|
||||||
|
";
|
||||||
|
let dbs = Datastore::new("memory").await?;
|
||||||
|
let ses = Session::for_kv().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 8);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: documentation:test,
|
||||||
|
languages: {
|
||||||
|
de: 'das ist Englisch',
|
||||||
|
en: 'this is english',
|
||||||
|
es: 'esto es español',
|
||||||
|
},
|
||||||
|
primarylang: 'en',
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
type: 'library',
|
||||||
|
value: 'client-side',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'library',
|
||||||
|
value: 'server-side',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'environment',
|
||||||
|
value: 'frontend',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
tags: {
|
||||||
|
value: 'client-side'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
content: 'this is english'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
content: 'my primary text'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
content: 'my secondary text'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_writeable_subqueries() -> Result<(), Error> {
|
async fn select_writeable_subqueries() -> Result<(), Error> {
|
||||||
let sql = "
|
let sql = "
|
||||||
|
@ -282,7 +401,7 @@ async fn select_where_field_is_bool() -> Result<(), Error> {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_field_is_thing_and_with_index() -> Result<(), Error> {
|
async fn select_where_field_is_thing_and_with_index() -> Result<(), Error> {
|
||||||
let sql = "
|
let sql = "
|
||||||
CREATE person:tobie SET name = 'Tobie';
|
CREATE person:tobie SET name = 'Tobie';
|
||||||
DEFINE INDEX author ON TABLE post COLUMNS author;
|
DEFINE INDEX author ON TABLE post COLUMNS author;
|
||||||
CREATE post:1 SET author = person:tobie;
|
CREATE post:1 SET author = person:tobie;
|
||||||
CREATE post:2 SET author = person:tobie;
|
CREATE post:2 SET author = person:tobie;
|
||||||
|
|
Loading…
Reference in a new issue