Ensure nested non-defined objects are not stored in SCHEMAFULL table

Closes #1342
This commit is contained in:
Tobie Morgan Hitchcock 2022-10-20 15:32:25 +01:00
parent 6ff1e250de
commit 0c4994b33b
4 changed files with 174 additions and 16 deletions

View file

@ -28,7 +28,7 @@ impl<'a> Document<'a> {
} }
} }
// Loop over every field in the document // 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) { if !keys.contains(fd) {
match fd { match fd {
fd if fd.is_id() => continue, fd if fd.is_id() => continue,

View file

@ -3,29 +3,42 @@ use crate::sql::part::Part;
use crate::sql::value::Value; use crate::sql::value::Value;
impl Value { impl Value {
pub fn every(&self, split: bool) -> Vec<Idiom> { pub fn every(&self, steps: bool, arrays: bool) -> Vec<Idiom> {
self._every(split, Idiom::default()) self._every(steps, arrays, Idiom::default())
} }
fn _every(&self, split: bool, prev: Idiom) -> Vec<Idiom> { fn _every(&self, steps: bool, arrays: bool, prev: Idiom) -> Vec<Idiom> {
match self { match self {
// Current path part is an object // Current path part is an object
Value::Object(v) => v 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::<Vec<_>>(),
// Let's not log intermediary nodes
_ => v
.iter() .iter()
.flat_map(|(k, v)| { .flat_map(|(k, v)| {
let p = Part::from(k.to_owned()); let p = Part::from(k.to_owned());
v._every(split, prev.clone().push(p)) v._every(steps, arrays, prev.clone().push(p))
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
},
// Current path part is an array // 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 true => v
.iter() .iter()
.enumerate() .enumerate()
.flat_map(|(i, v)| { .flat_map(|(i, v)| {
let p = Part::from(i.to_owned()); let p = Part::from(i.to_owned());
v._every(split, prev.clone().push(p)) v._every(steps, arrays, prev.clone().push(p))
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
// Let's not log individual array items
false => vec![prev], false => vec![prev],
}, },
// Process everything else // Process everything else
@ -45,7 +58,7 @@ mod tests {
fn every_without_array_indexes() { fn every_without_array_indexes() {
let val = Value::parse("{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"); let val = Value::parse("{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }");
let res = vec![Idiom::parse("test.something")]; let res = vec![Idiom::parse("test.something")];
assert_eq!(res, val.every(false)); assert_eq!(res, val.every(false, false));
} }
#[test] #[test]
@ -59,6 +72,30 @@ mod tests {
Idiom::parse("test.something[1].tags[0]"), Idiom::parse("test.something[1].tags[0]"),
Idiom::parse("test.something[1].tags[1]"), 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));
} }
} }

View file

@ -14,7 +14,7 @@ impl Value {
) -> Result<(), Error> { ) -> Result<(), Error> {
match val { match val {
v if v.is_object() => { 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? { match v.get(ctx, opt, txn, &k.0).await? {
Value::None => self.del(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?, 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);
}
}

View file

@ -95,3 +95,55 @@ async fn field_definition_value_assert_success() -> Result<(), Error> {
// //
Ok(()) 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(())
}