Only allow simple fields for SPLIT, ORDER, and GROUP clauses

This commit is contained in:
Tobie Morgan Hitchcock 2022-03-23 11:51:36 +00:00
parent 652195032c
commit 3370b20c38
8 changed files with 161 additions and 12 deletions

View file

@ -161,7 +161,7 @@ impl Iterator {
// Loop over each value // Loop over each value
for obj in &res { for obj in &res {
// Get the value at the path // 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 // Set the value at the path
match val { match val {
Value::Array(v) => { Value::Array(v) => {

View file

@ -1,7 +1,7 @@
use crate::sql::comment::shouldbespace; use crate::sql::comment::shouldbespace;
use crate::sql::common::commas; use crate::sql::common::commas;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::idiom::{idiom, Idiom}; use crate::sql::idiom::{basic, Idiom};
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::combinator::opt; use nom::combinator::opt;
use nom::multi::separated_list1; 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> { fn group_raw(i: &str) -> IResult<&str, Group> {
let (i, v) = idiom(i)?; let (i, v) = basic(i)?;
Ok(( Ok((
i, i,
Group { Group {

View file

@ -4,7 +4,7 @@ use crate::dbs::Transaction;
use crate::err::Error; use crate::err::Error;
use crate::sql::common::commas; use crate::sql::common::commas;
use crate::sql::error::IResult; 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 crate::sql::value::Value;
use nom::branch::alt; use nom::branch::alt;
use nom::multi::many0; 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> { pub fn local(i: &str) -> IResult<&str, Idiom> {
let (i, p) = first(i)?; let (i, p) = first(i)?;
let (i, mut v) = many0(alt((all, index, field)))(i)?; let (i, mut v) = many0(alt((all, index, field)))(i)?;
@ -111,6 +111,14 @@ pub fn local(i: &str) -> IResult<&str, Idiom> {
Ok((i, Idiom::from(v))) 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 // Used in a $param definition
pub fn param(i: &str) -> IResult<&str, Idiom> { pub fn param(i: &str) -> IResult<&str, Idiom> {
let (i, p) = first(i)?; let (i, p) = first(i)?;

View file

@ -1,7 +1,7 @@
use crate::sql::comment::shouldbespace; use crate::sql::comment::shouldbespace;
use crate::sql::common::commas; use crate::sql::common::commas;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::idiom::{idiom, Idiom}; use crate::sql::idiom::{basic, Idiom};
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::combinator::{map, opt}; use nom::combinator::{map, opt};
@ -75,7 +75,7 @@ fn order_rand(i: &str) -> IResult<&str, Vec<Order>> {
} }
fn order_raw(i: &str) -> IResult<&str, Order> { 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, c) = opt(tuple((shouldbespace, tag_no_case("COLLATE"))))(i)?;
let (i, n) = opt(tuple((shouldbespace, tag_no_case("NUMERIC"))))(i)?; let (i, n) = opt(tuple((shouldbespace, tag_no_case("NUMERIC"))))(i)?;
let (i, d) = opt(alt(( let (i, d) = opt(alt((

View file

@ -115,9 +115,19 @@ pub fn first(i: &str) -> IResult<&str, Part> {
} }
pub fn all(i: &str) -> IResult<&str, Part> { pub fn all(i: &str) -> IResult<&str, Part> {
let (i, _) = char('[')(i)?; let (i, _) = alt((
let (i, _) = char('*')(i)?; |i| {
let (i, _) = char(']')(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)) Ok((i, Part::All))
} }

View file

@ -1,7 +1,7 @@
use crate::sql::comment::shouldbespace; use crate::sql::comment::shouldbespace;
use crate::sql::common::commas; use crate::sql::common::commas;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::idiom::{idiom, Idiom}; use crate::sql::idiom::{basic, Idiom};
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::combinator::opt; use nom::combinator::opt;
use nom::multi::separated_list1; 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> { fn split_raw(i: &str) -> IResult<&str, Split> {
let (i, v) = idiom(i)?; let (i, v) = basic(i)?;
Ok(( Ok((
i, i,
Split { Split {

View file

@ -13,6 +13,7 @@ mod last;
mod merge; mod merge;
mod object; mod object;
mod patch; mod patch;
mod pick;
mod replace; mod replace;
mod set; mod set;
mod single; mod single;

130
lib/src/sql/value/pick.rs Normal file
View file

@ -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::<Vec<_>>().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::<Vec<_>>().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]));
}
}