Ensure nested non-defined objects are not stored in SCHEMAFULL table
Closes #1342
This commit is contained in:
parent
6ff1e250de
commit
0c4994b33b
4 changed files with 174 additions and 16 deletions
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
.iter()
|
// Let's log all intermediary nodes
|
||||||
.flat_map(|(k, v)| {
|
true if !prev.is_empty() => Some(prev.clone())
|
||||||
let p = Part::from(k.to_owned());
|
.into_iter()
|
||||||
v._every(split, prev.clone().push(p))
|
.chain(v.iter().flat_map(|(k, v)| {
|
||||||
})
|
let p = Part::from(k.to_owned());
|
||||||
.collect::<Vec<_>>(),
|
v._every(steps, arrays, prev.clone().push(p))
|
||||||
|
}))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
// 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::<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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue