From 078d3d48685cf17088e146c568ef707ae6f68ace Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Wed, 27 Jul 2022 18:48:43 +0100 Subject: [PATCH] Ensure field values are set even if the field is not specified --- lib/src/doc/clean.rs | 18 ++--- lib/src/doc/field.rs | 12 ++-- lib/src/sql/value/mod.rs | 1 + lib/src/sql/value/walk.rs | 143 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+), 16 deletions(-) create mode 100644 lib/src/sql/value/walk.rs diff --git a/lib/src/doc/clean.rs b/lib/src/doc/clean.rs index 938d0033..d7563ed2 100644 --- a/lib/src/doc/clean.rs +++ b/lib/src/doc/clean.rs @@ -26,15 +26,15 @@ impl<'a> Document<'a> { for k in self.current.each(&fd.name).into_iter() { keys.push(k); } - // Loop over every field in the document - for k in self.current.every().iter() { - if !keys.contains(k) { - match k { - k if k.is_id() => continue, - k if k.is_in() => continue, - k if k.is_out() => continue, - k => self.current.to_mut().del(ctx, opt, txn, k).await?, - } + } + // Loop over every field in the document + for fd in self.current.every().iter() { + if !keys.contains(fd) { + match fd { + fd if fd.is_id() => continue, + fd if fd.is_in() => continue, + fd if fd.is_out() => continue, + fd => self.current.to_mut().del(ctx, opt, txn, fd).await?, } } } diff --git a/lib/src/doc/field.rs b/lib/src/doc/field.rs index c3da8ea0..1d5bee91 100644 --- a/lib/src/doc/field.rs +++ b/lib/src/doc/field.rs @@ -18,11 +18,9 @@ impl<'a> Document<'a> { // Loop through all field statements for fd in self.fd(opt, txn).await?.iter() { // Loop over each field in document - for k in self.current.each(&fd.name).iter() { + for (k, mut val) in self.current.walk(&fd.name).into_iter() { // Get the initial value - let old = self.initial.pick(k); - // Get the current value - let mut val = self.current.pick(k); + let old = self.initial.pick(&k); // Check for a VALUE clause if let Some(expr) = &fd.value { // Configure the context @@ -82,9 +80,9 @@ impl<'a> Document<'a> { } // Set the value of the field match val { - Value::None => self.current.to_mut().del(ctx, opt, txn, k).await?, - Value::Void => self.current.to_mut().del(ctx, opt, txn, k).await?, - _ => self.current.to_mut().set(ctx, opt, txn, k, val).await?, + Value::None => self.current.to_mut().del(ctx, opt, txn, &k).await?, + Value::Void => self.current.to_mut().del(ctx, opt, txn, &k).await?, + _ => self.current.to_mut().set(ctx, opt, txn, &k, val).await?, }; } } diff --git a/lib/src/sql/value/mod.rs b/lib/src/sql/value/mod.rs index 304f2c2f..a29c1b91 100644 --- a/lib/src/sql/value/mod.rs +++ b/lib/src/sql/value/mod.rs @@ -26,3 +26,4 @@ mod replace; mod retable; mod set; mod single; +mod walk; diff --git a/lib/src/sql/value/walk.rs b/lib/src/sql/value/walk.rs new file mode 100644 index 00000000..78592e00 --- /dev/null +++ b/lib/src/sql/value/walk.rs @@ -0,0 +1,143 @@ +use crate::sql::idiom::Idiom; +use crate::sql::part::Next; +use crate::sql::part::Part; +use crate::sql::value::Value; + +impl Value { + pub fn walk(&self, path: &[Part]) -> Vec<(Idiom, Self)> { + self._walk(path, Idiom::default()) + } + fn _walk(&self, path: &[Part], prev: Idiom) -> Vec<(Idiom, 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.get(f as &str) { + Some(v) => v._walk(path.next(), prev.push(p.clone())), + None => vec![(prev.push(p.clone()), Value::None)], + }, + Part::All => self._walk(path.next(), prev.push(p.clone())), + _ => vec![], + }, + // Current path part is an array + Value::Array(v) => match p { + Part::All => v + .iter() + .enumerate() + .flat_map(|(i, v)| v._walk(path.next(), prev.clone().push(Part::from(i)))) + .collect::>(), + Part::First => match v.first() { + Some(v) => v._walk(path.next(), prev.push(p.clone())), + None => vec![], + }, + Part::Last => match v.last() { + Some(v) => v._walk(path.next(), prev.push(p.clone())), + None => vec![], + }, + Part::Index(i) => match v.get(i.to_usize()) { + Some(v) => v._walk(path.next(), prev.push(p.clone())), + None => vec![], + }, + _ => v + .iter() + .enumerate() + .flat_map(|(i, v)| v._walk(path.next(), prev.clone().push(Part::from(i)))) + .collect::>(), + }, + // Ignore everything else + _ => vec![], + }, + // No more parts so get the value + None => vec![(prev, self.clone())], + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::sql::idiom::Idiom; + use crate::sql::test::Parse; + + #[test] + fn walk_none() { + let idi = Idiom::default(); + let val = Value::parse("{ test: { other: null, something: 123 } }"); + let res = + vec![(Idiom::default(), Value::parse("{ test: { other: null, something: 123 } }"))]; + assert_eq!(res, val.walk(&idi)); + } + + #[test] + fn walk_empty() { + let idi = Idiom::parse("test.missing"); + let val = Value::parse("{ test: { other: null, something: 123 } }"); + let res = vec![(Idiom::parse("test.missing"), Value::None)]; + assert_eq!(res, val.walk(&idi)); + } + + #[test] + fn walk_basic() { + let idi = Idiom::parse("test.something"); + let val = Value::parse("{ test: { other: null, something: 123 } }"); + let res = vec![(Idiom::parse("test.something"), Value::from(123))]; + assert_eq!(res, val.walk(&idi)); + } + + #[test] + fn walk_array() { + let idi = Idiom::parse("test.something"); + let val = Value::parse("{ test: { something: [{ age: 34 }, { age: 36 }] } }"); + let res = + vec![(Idiom::parse("test.something"), Value::parse("[{ age: 34 }, { age: 36 }]"))]; + assert_eq!(res, val.walk(&idi)); + } + + #[test] + fn walk_array_field() { + let idi = Idiom::parse("test.something[*].age"); + let val = Value::parse("{ test: { something: [{ age: 34 }, { age: 36 }] } }"); + let res = vec![ + (Idiom::parse("test.something[0].age"), Value::from(34)), + (Idiom::parse("test.something[1].age"), Value::from(36)), + ]; + assert_eq!(res, val.walk(&idi)); + } + + #[test] + fn walk_array_field_embedded() { + let idi = Idiom::parse("test.something[*].tags"); + let val = Value::parse("{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"); + let res = vec![ + (Idiom::parse("test.something[0].tags"), Value::parse("['code', 'databases']")), + (Idiom::parse("test.something[1].tags"), Value::parse("['design', 'operations']")), + ]; + assert_eq!(res, val.walk(&idi)); + } + + #[test] + fn walk_array_field_embedded_index() { + let idi = Idiom::parse("test.something[*].tags[1]"); + let val = Value::parse("{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"); + let res = vec![ + (Idiom::parse("test.something[0].tags[1]"), Value::from("databases")), + (Idiom::parse("test.something[1].tags[1]"), Value::from("operations")), + ]; + assert_eq!(res, val.walk(&idi)); + } + + #[test] + fn walk_array_field_embedded_index_all() { + let idi = Idiom::parse("test.something[*].tags[*]"); + let val = Value::parse("{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"); + let res = vec![ + (Idiom::parse("test.something[0].tags[0]"), Value::from("code")), + (Idiom::parse("test.something[0].tags[1]"), Value::from("databases")), + (Idiom::parse("test.something[1].tags[0]"), Value::from("design")), + (Idiom::parse("test.something[1].tags[1]"), Value::from("operations")), + ]; + assert_eq!(res, val.walk(&idi)); + } +}