From 3370b20c38e692fa1f3ede1bdecdfeca651f11b4 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Wed, 23 Mar 2022 11:51:36 +0000 Subject: [PATCH] Only allow simple fields for SPLIT, ORDER, and GROUP clauses --- lib/src/dbs/iterator.rs | 2 +- lib/src/sql/group.rs | 4 +- lib/src/sql/idiom.rs | 12 +++- lib/src/sql/order.rs | 4 +- lib/src/sql/part.rs | 16 ++++- lib/src/sql/split.rs | 4 +- lib/src/sql/value/mod.rs | 1 + lib/src/sql/value/pick.rs | 130 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 lib/src/sql/value/pick.rs diff --git a/lib/src/dbs/iterator.rs b/lib/src/dbs/iterator.rs index ca3ecd7e..c47bb6fe 100644 --- a/lib/src/dbs/iterator.rs +++ b/lib/src/dbs/iterator.rs @@ -161,7 +161,7 @@ impl Iterator { // Loop over each value for obj in &res { // Get the value at the path - let val = obj.get(ctx, opt, txn, &split.split).await?; + let val = obj.pick(&split.split); // Set the value at the path match val { Value::Array(v) => { diff --git a/lib/src/sql/group.rs b/lib/src/sql/group.rs index 078fa399..6a797cdb 100644 --- a/lib/src/sql/group.rs +++ b/lib/src/sql/group.rs @@ -1,7 +1,7 @@ use crate::sql::comment::shouldbespace; use crate::sql::common::commas; use crate::sql::error::IResult; -use crate::sql::idiom::{idiom, Idiom}; +use crate::sql::idiom::{basic, Idiom}; use nom::bytes::complete::tag_no_case; use nom::combinator::opt; use nom::multi::separated_list1; @@ -42,7 +42,7 @@ pub fn group(i: &str) -> IResult<&str, Groups> { } fn group_raw(i: &str) -> IResult<&str, Group> { - let (i, v) = idiom(i)?; + let (i, v) = basic(i)?; Ok(( i, Group { diff --git a/lib/src/sql/idiom.rs b/lib/src/sql/idiom.rs index 87b10966..96ff1781 100644 --- a/lib/src/sql/idiom.rs +++ b/lib/src/sql/idiom.rs @@ -4,7 +4,7 @@ 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, part, Part}; +use crate::sql::part::{all, field, first, graph, index, last, part, Part}; use crate::sql::value::Value; use nom::branch::alt; use nom::multi::many0; @@ -103,7 +103,7 @@ impl fmt::Display for Idiom { } } -// Used in a DEFINE FIELD and DEFINE INDEX clause +// Used in a DEFINE FIELD and DEFINE INDEX clauses pub fn local(i: &str) -> IResult<&str, Idiom> { let (i, p) = first(i)?; let (i, mut v) = many0(alt((all, index, field)))(i)?; @@ -111,6 +111,14 @@ pub fn local(i: &str) -> IResult<&str, Idiom> { Ok((i, Idiom::from(v))) } +// Used in a SPLIT, ORDER, and GROUP clauses +pub fn basic(i: &str) -> IResult<&str, Idiom> { + let (i, p) = first(i)?; + let (i, mut v) = many0(alt((all, last, index, field)))(i)?; + v.insert(0, p); + Ok((i, Idiom::from(v))) +} + // Used in a $param definition pub fn param(i: &str) -> IResult<&str, Idiom> { let (i, p) = first(i)?; diff --git a/lib/src/sql/order.rs b/lib/src/sql/order.rs index 45b970bb..a5d6e6cb 100644 --- a/lib/src/sql/order.rs +++ b/lib/src/sql/order.rs @@ -1,7 +1,7 @@ use crate::sql::comment::shouldbespace; use crate::sql::common::commas; use crate::sql::error::IResult; -use crate::sql::idiom::{idiom, Idiom}; +use crate::sql::idiom::{basic, Idiom}; use nom::branch::alt; use nom::bytes::complete::tag_no_case; use nom::combinator::{map, opt}; @@ -75,7 +75,7 @@ fn order_rand(i: &str) -> IResult<&str, Vec> { } fn order_raw(i: &str) -> IResult<&str, Order> { - let (i, v) = idiom(i)?; + let (i, v) = basic(i)?; let (i, c) = opt(tuple((shouldbespace, tag_no_case("COLLATE"))))(i)?; let (i, n) = opt(tuple((shouldbespace, tag_no_case("NUMERIC"))))(i)?; let (i, d) = opt(alt(( diff --git a/lib/src/sql/part.rs b/lib/src/sql/part.rs index 289ba00e..bb737042 100644 --- a/lib/src/sql/part.rs +++ b/lib/src/sql/part.rs @@ -115,9 +115,19 @@ pub fn first(i: &str) -> IResult<&str, Part> { } pub fn all(i: &str) -> IResult<&str, Part> { - let (i, _) = char('[')(i)?; - let (i, _) = char('*')(i)?; - let (i, _) = char(']')(i)?; + let (i, _) = alt(( + |i| { + let (i, _) = char('.')(i)?; + let (i, _) = char('*')(i)?; + Ok((i, ())) + }, + |i| { + let (i, _) = char('[')(i)?; + let (i, _) = char('*')(i)?; + let (i, _) = char(']')(i)?; + Ok((i, ())) + }, + ))(i)?; Ok((i, Part::All)) } diff --git a/lib/src/sql/split.rs b/lib/src/sql/split.rs index 0869bd6d..69486ade 100644 --- a/lib/src/sql/split.rs +++ b/lib/src/sql/split.rs @@ -1,7 +1,7 @@ use crate::sql::comment::shouldbespace; use crate::sql::common::commas; use crate::sql::error::IResult; -use crate::sql::idiom::{idiom, Idiom}; +use crate::sql::idiom::{basic, Idiom}; use nom::bytes::complete::tag_no_case; use nom::combinator::opt; use nom::multi::separated_list1; @@ -42,7 +42,7 @@ pub fn split(i: &str) -> IResult<&str, Splits> { } fn split_raw(i: &str) -> IResult<&str, Split> { - let (i, v) = idiom(i)?; + let (i, v) = basic(i)?; Ok(( i, Split { diff --git a/lib/src/sql/value/mod.rs b/lib/src/sql/value/mod.rs index 3a028cd3..28e31fe0 100644 --- a/lib/src/sql/value/mod.rs +++ b/lib/src/sql/value/mod.rs @@ -13,6 +13,7 @@ mod last; mod merge; mod object; mod patch; +mod pick; mod replace; mod set; mod single; diff --git a/lib/src/sql/value/pick.rs b/lib/src/sql/value/pick.rs new file mode 100644 index 00000000..c14f1872 --- /dev/null +++ b/lib/src/sql/value/pick.rs @@ -0,0 +1,130 @@ +use crate::sql::part::Next; +use crate::sql::part::Part; +use crate::sql::value::Value; + +impl Value { + pub fn pick(&self, path: &[Part]) -> Self { + match path.first() { + // Get the current path part + Some(p) => match self { + // Current path part is an object + Value::Object(v) => match p { + Part::Field(f) => match v.value.get(&f.name) { + Some(v) => v.pick(path.next()), + None => Value::None, + }, + _ => Value::None, + }, + // Current path part is an array + Value::Array(v) => match p { + Part::All => { + v.value.iter().map(|v| v.pick(path.next())).collect::>().into() + } + Part::First => match v.value.first() { + Some(v) => v.pick(path.next()), + None => Value::None, + }, + Part::Last => match v.value.last() { + Some(v) => v.pick(path.next()), + None => Value::None, + }, + Part::Index(i) => match v.value.get(i.to_usize()) { + Some(v) => v.pick(path.next()), + None => Value::None, + }, + _ => v.value.iter().map(|v| v.pick(path)).collect::>().into(), + }, + // Ignore everything else + _ => Value::None, + }, + // No more parts so get the value + None => self.clone(), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::sql::id::Id; + use crate::sql::idiom::Idiom; + use crate::sql::test::Parse; + use crate::sql::thing::Thing; + + #[test] + fn pick_none() { + let idi = Idiom::default(); + let val = Value::parse("{ test: { other: null, something: 123 } }"); + let res = val.pick(&idi); + assert_eq!(res, val); + } + + #[test] + fn pick_basic() { + let idi = Idiom::parse("test.something"); + let val = Value::parse("{ test: { other: null, something: 123 } }"); + let res = val.pick(&idi); + assert_eq!(res, Value::from(123)); + } + + #[test] + fn pick_thing() { + let idi = Idiom::parse("test.other"); + let val = Value::parse("{ test: { other: test:tobie, something: 123 } }"); + let res = val.pick(&idi); + assert_eq!( + res, + Value::from(Thing { + tb: String::from("test"), + id: Id::from("tobie") + }) + ); + } + + #[test] + fn pick_array() { + let idi = Idiom::parse("test.something[1]"); + let val = Value::parse("{ test: { something: [123, 456, 789] } }"); + let res = val.pick(&idi); + assert_eq!(res, Value::from(456)); + } + + #[test] + fn pick_array_thing() { + let idi = Idiom::parse("test.something[1]"); + let val = Value::parse("{ test: { something: [test:tobie, test:jaime] } }"); + let res = val.pick(&idi); + assert_eq!( + res, + Value::from(Thing { + tb: String::from("test"), + id: Id::from("jaime") + }) + ); + } + + #[test] + fn pick_array_field() { + let idi = Idiom::parse("test.something[1].age"); + let val = Value::parse("{ test: { something: [{ age: 34 }, { age: 36 }] } }"); + let res = val.pick(&idi); + assert_eq!(res, Value::from(36)); + } + + #[test] + fn pick_array_fields() { + let idi = Idiom::parse("test.something[*].age"); + let val = Value::parse("{ test: { something: [{ age: 34 }, { age: 36 }] } }"); + let res = val.pick(&idi); + assert_eq!(res, Value::from(vec![34, 36])); + } + + #[test] + fn pick_array_fields_flat() { + let idi = Idiom::parse("test.something.age"); + let val = Value::parse("{ test: { something: [{ age: 34 }, { age: 36 }] } }"); + let res = val.pick(&idi); + assert_eq!(res, Value::from(vec![34, 36])); + } +}