diff --git a/lib/src/doc/clean.rs b/lib/src/doc/clean.rs index 04059f86..823bf520 100644 --- a/lib/src/doc/clean.rs +++ b/lib/src/doc/clean.rs @@ -28,7 +28,7 @@ impl<'a> Document<'a> { } } // Loop over every field in the document - for fd in self.current.every(true).iter() { + for fd in self.current.every(true, true).iter() { if !keys.contains(fd) { match fd { fd if fd.is_id() => continue, diff --git a/lib/src/sql/value/every.rs b/lib/src/sql/value/every.rs index ce27ace6..2d037f07 100644 --- a/lib/src/sql/value/every.rs +++ b/lib/src/sql/value/every.rs @@ -3,29 +3,42 @@ use crate::sql::part::Part; use crate::sql::value::Value; impl Value { - pub fn every(&self, split: bool) -> Vec { - self._every(split, Idiom::default()) + pub fn every(&self, steps: bool, arrays: bool) -> Vec { + self._every(steps, arrays, Idiom::default()) } - fn _every(&self, split: bool, prev: Idiom) -> Vec { + fn _every(&self, steps: bool, arrays: bool, prev: Idiom) -> Vec { match self { // Current path part is an object - Value::Object(v) => v - .iter() - .flat_map(|(k, v)| { - let p = Part::from(k.to_owned()); - v._every(split, prev.clone().push(p)) - }) - .collect::>(), + Value::Object(v) => match steps { + // Let's log all intermediary nodes + true if !prev.is_empty() => Some(prev.clone()) + .into_iter() + .chain(v.iter().flat_map(|(k, v)| { + let p = Part::from(k.to_owned()); + v._every(steps, arrays, prev.clone().push(p)) + })) + .collect::>(), + // Let's not log intermediary nodes + _ => v + .iter() + .flat_map(|(k, v)| { + let p = Part::from(k.to_owned()); + v._every(steps, arrays, prev.clone().push(p)) + }) + .collect::>(), + }, // Current path part is an array - Value::Array(v) => match split { + Value::Array(v) => match arrays { + // Let's log all individual array items true => v .iter() .enumerate() .flat_map(|(i, v)| { let p = Part::from(i.to_owned()); - v._every(split, prev.clone().push(p)) + v._every(steps, arrays, prev.clone().push(p)) }) .collect::>(), + // Let's not log individual array items false => vec![prev], }, // Process everything else @@ -45,7 +58,7 @@ mod tests { fn every_without_array_indexes() { let val = Value::parse("{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"); let res = vec![Idiom::parse("test.something")]; - assert_eq!(res, val.every(false)); + assert_eq!(res, val.every(false, false)); } #[test] @@ -59,6 +72,30 @@ mod tests { Idiom::parse("test.something[1].tags[0]"), Idiom::parse("test.something[1].tags[1]"), ]; - assert_eq!(res, val.every(true)); + assert_eq!(res, val.every(false, true)); + } + + #[test] + fn every_including_intermediary_nodes_without_array_indexes() { + let val = Value::parse("{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"); + let res = vec![Idiom::parse("test"), Idiom::parse("test.something")]; + assert_eq!(res, val.every(true, false)); + } + + #[test] + fn every_including_intermediary_nodes_including_array_indexes() { + let val = Value::parse("{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"); + let res = vec![ + Idiom::parse("test"), + Idiom::parse("test.something[0]"), + Idiom::parse("test.something[0].age"), + Idiom::parse("test.something[0].tags[0]"), + Idiom::parse("test.something[0].tags[1]"), + Idiom::parse("test.something[1]"), + Idiom::parse("test.something[1].age"), + Idiom::parse("test.something[1].tags[0]"), + Idiom::parse("test.something[1].tags[1]"), + ]; + assert_eq!(res, val.every(true, true)); } } diff --git a/lib/src/sql/value/merge.rs b/lib/src/sql/value/merge.rs index 29928c70..4f86af6e 100644 --- a/lib/src/sql/value/merge.rs +++ b/lib/src/sql/value/merge.rs @@ -14,7 +14,7 @@ impl Value { ) -> Result<(), Error> { match val { v if v.is_object() => { - for k in v.every(false).iter() { + for k in v.every(false, false).iter() { match v.get(ctx, opt, txn, &k.0).await? { Value::None => self.del(ctx, opt, txn, &k.0).await?, v => self.set(ctx, opt, txn, &k.0, v).await?, @@ -26,3 +26,72 @@ impl Value { } } } + +#[cfg(test)] +mod tests { + + use super::*; + use crate::dbs::test::mock; + use crate::sql::test::Parse; + + #[tokio::test] + async fn merge_none() { + let (ctx, opt, txn) = mock().await; + let mut res = Value::parse( + "{ + name: { + first: 'Tobie', + last: 'Morgan Hitchcock', + initials: 'TMH', + }, + }", + ); + let mrg = Value::None; + let val = Value::parse( + "{ + name: { + first: 'Tobie', + last: 'Morgan Hitchcock', + initials: 'TMH', + }, + }", + ); + res.merge(&ctx, &opt, &txn, mrg).await.unwrap(); + assert_eq!(res, val); + } + + #[tokio::test] + async fn merge_basic() { + let (ctx, opt, txn) = mock().await; + let mut res = Value::parse( + "{ + name: { + first: 'Tobie', + last: 'Morgan Hitchcock', + initials: 'TMH', + }, + }", + ); + let mrg = Value::parse( + "{ + name: { + title: 'Mr', + initials: NONE, + }, + tags: ['Rust', 'Golang', 'JavaScript'], + }", + ); + let val = Value::parse( + "{ + name: { + title: 'Mr', + first: 'Tobie', + last: 'Morgan Hitchcock', + }, + tags: ['Rust', 'Golang', 'JavaScript'], + }", + ); + res.merge(&ctx, &opt, &txn, mrg).await.unwrap(); + assert_eq!(res, val); + } +} diff --git a/lib/tests/field.rs b/lib/tests/field.rs index ddc1440f..4f4725b2 100644 --- a/lib/tests/field.rs +++ b/lib/tests/field.rs @@ -95,3 +95,55 @@ async fn field_definition_value_assert_success() -> Result<(), Error> { // Ok(()) } + +#[tokio::test] +async fn field_definition_empty_nested_objects() -> Result<(), Error> { + let sql = " + DEFINE TABLE person SCHEMAFULL; + DEFINE FIELD settings on person TYPE object; + UPDATE person:test CONTENT { + settings: { + nested: { + object: { + thing: 'test' + } + } + } + }; + SELECT * FROM person; + "; + 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, false).await?; + assert_eq!(res.len(), 4); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + id: person:test, + settings: {}, + } + ]", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + id: person:test, + settings: {}, + } + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +}