Add support for FLEXIBLE fields on SCHEMAFULL tables

Closes #1341
This commit is contained in:
Tobie Morgan Hitchcock 2023-01-04 09:24:14 +00:00
parent 98a5d75d63
commit f5b21eb363
6 changed files with 168 additions and 12 deletions

View file

@ -22,13 +22,24 @@ impl<'a> Document<'a> {
let mut keys: Vec<Idiom> = vec![]; let mut keys: Vec<Idiom> = vec![];
// Loop through all field statements // Loop through all field statements
for fd in self.fd(opt, txn).await?.iter() { for fd in self.fd(opt, txn).await?.iter() {
// Is this a schemaless field?
match fd.flex {
false => {
// Loop over this field in the document // Loop over this field in the document
for k in self.current.each(&fd.name).into_iter() { for k in self.current.each(&fd.name).into_iter() {
keys.push(k); keys.push(k);
} }
} }
true => {
// Loop over every field under this field in the document
for k in self.current.every(Some(&fd.name), true, true).into_iter() {
keys.push(k);
}
}
}
}
// Loop over every field in the document // Loop over every field in the document
for fd in self.current.every(true, true).iter() { for fd in self.current.every(None, 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

@ -62,6 +62,12 @@ impl From<Vec<Part>> for Idiom {
} }
} }
impl From<&[Part]> for Idiom {
fn from(v: &[Part]) -> Self {
Self(v.to_vec())
}
}
impl Idiom { impl Idiom {
/// Appends a part to the end of this Idiom /// Appends a part to the end of this Idiom
pub(crate) fn push(mut self, n: Part) -> Idiom { pub(crate) fn push(mut self, n: Part) -> Idiom {

View file

@ -834,6 +834,7 @@ fn event(i: &str) -> IResult<&str, DefineEventStatement> {
pub struct DefineFieldStatement { pub struct DefineFieldStatement {
pub name: Idiom, pub name: Idiom,
pub what: Ident, pub what: Ident,
pub flex: bool,
pub kind: Option<Kind>, pub kind: Option<Kind>,
pub value: Option<Value>, pub value: Option<Value>,
pub assert: Option<Value>, pub assert: Option<Value>,
@ -873,6 +874,9 @@ impl DefineFieldStatement {
impl fmt::Display for DefineFieldStatement { impl fmt::Display for DefineFieldStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DEFINE FIELD {} ON {}", self.name, self.what)?; write!(f, "DEFINE FIELD {} ON {}", self.name, self.what)?;
if self.flex {
write!(f, " FLEXIBLE")?
}
if let Some(ref v) = self.kind { if let Some(ref v) = self.kind {
write!(f, " TYPE {}", v)? write!(f, " TYPE {}", v)?
} }
@ -906,6 +910,13 @@ fn field(i: &str) -> IResult<&str, DefineFieldStatement> {
DefineFieldStatement { DefineFieldStatement {
name, name,
what, what,
flex: opts
.iter()
.find_map(|x| match x {
DefineFieldOption::Flex => Some(true),
_ => None,
})
.unwrap_or_default(),
kind: opts.iter().find_map(|x| match x { kind: opts.iter().find_map(|x| match x {
DefineFieldOption::Kind(ref v) => Some(v.to_owned()), DefineFieldOption::Kind(ref v) => Some(v.to_owned()),
_ => None, _ => None,
@ -931,6 +942,7 @@ fn field(i: &str) -> IResult<&str, DefineFieldStatement> {
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub enum DefineFieldOption { pub enum DefineFieldOption {
Flex,
Kind(Kind), Kind(Kind),
Value(Value), Value(Value),
Assert(Value), Assert(Value),
@ -938,7 +950,13 @@ pub enum DefineFieldOption {
} }
fn field_opts(i: &str) -> IResult<&str, DefineFieldOption> { fn field_opts(i: &str) -> IResult<&str, DefineFieldOption> {
alt((field_kind, field_value, field_assert, field_permissions))(i) alt((field_flex, field_kind, field_value, field_assert, field_permissions))(i)
}
fn field_flex(i: &str) -> IResult<&str, DefineFieldOption> {
let (i, _) = shouldbespace(i)?;
let (i, _) = alt((tag_no_case("FLEXIBLE"), tag_no_case("FLEXI"), tag_no_case("FLEX")))(i)?;
Ok((i, DefineFieldOption::Flex))
} }
fn field_kind(i: &str) -> IResult<&str, DefineFieldOption> { fn field_kind(i: &str) -> IResult<&str, DefineFieldOption> {

View file

@ -3,8 +3,11 @@ use crate::sql::part::Part;
use crate::sql::value::Value; use crate::sql::value::Value;
impl Value { impl Value {
pub fn every(&self, steps: bool, arrays: bool) -> Vec<Idiom> { pub fn every(&self, path: Option<&[Part]>, steps: bool, arrays: bool) -> Vec<Idiom> {
self._every(steps, arrays, Idiom::default()) match path {
Some(path) => self.pick(path)._every(steps, arrays, Idiom::from(path)),
None => self._every(steps, arrays, Idiom::default()),
}
} }
fn _every(&self, steps: bool, arrays: bool, prev: Idiom) -> Vec<Idiom> { fn _every(&self, steps: bool, arrays: bool, prev: Idiom) -> Vec<Idiom> {
match self { match self {
@ -56,7 +59,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, false)); assert_eq!(res, val.every(None, false, false));
} }
#[test] #[test]
@ -73,14 +76,14 @@ mod tests {
Idiom::parse("test.something[0].tags[1]"), Idiom::parse("test.something[0].tags[1]"),
Idiom::parse("test.something[0].tags[0]"), Idiom::parse("test.something[0].tags[0]"),
]; ];
assert_eq!(res, val.every(false, true)); assert_eq!(res, val.every(None, false, true));
} }
#[test] #[test]
fn every_including_intermediary_nodes_without_array_indexes() { 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 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")]; let res = vec![Idiom::parse("test"), Idiom::parse("test.something")];
assert_eq!(res, val.every(true, false)); assert_eq!(res, val.every(None, true, false));
} }
#[test] #[test]
@ -100,6 +103,6 @@ mod tests {
Idiom::parse("test.something[0].tags[1]"), Idiom::parse("test.something[0].tags[1]"),
Idiom::parse("test.something[0].tags[0]"), Idiom::parse("test.something[0].tags[0]"),
]; ];
assert_eq!(res, val.every(true, true)); assert_eq!(res, val.every(None, 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, false).iter() { for k in v.every(None, 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?,

View file

@ -147,3 +147,121 @@ async fn field_definition_empty_nested_objects() -> Result<(), Error> {
// //
Ok(()) Ok(())
} }
#[tokio::test]
async fn field_definition_empty_nested_arrays() -> Result<(), Error> {
let sql = "
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD settings on person TYPE object;
UPDATE person:test CONTENT {
settings: {
nested: [
1,
2,
3,
4,
5
]
}
};
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(())
}
#[tokio::test]
async fn field_definition_empty_nested_flexible() -> Result<(), Error> {
let sql = "
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD settings on person FLEXIBLE 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: {
nested: {
object: {
thing: 'test'
}
}
},
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: person:test,
settings: {
nested: {
object: {
thing: 'test'
}
}
},
}
]",
);
assert_eq!(tmp, val);
//
Ok(())
}