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![];
// Loop through all field statements
for fd in self.fd(opt, txn).await?.iter() {
// Loop over this field in the document
for k in self.current.each(&fd.name).into_iter() {
keys.push(k);
// Is this a schemaless field?
match fd.flex {
false => {
// Loop over this field in the document
for k in self.current.each(&fd.name).into_iter() {
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
for fd in self.current.every(true, true).iter() {
for fd in self.current.every(None, true, true).iter() {
if !keys.contains(fd) {
match fd {
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 {
/// Appends a part to the end of this 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 name: Idiom,
pub what: Ident,
pub flex: bool,
pub kind: Option<Kind>,
pub value: Option<Value>,
pub assert: Option<Value>,
@ -873,6 +874,9 @@ impl DefineFieldStatement {
impl fmt::Display for DefineFieldStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DEFINE FIELD {} ON {}", self.name, self.what)?;
if self.flex {
write!(f, " FLEXIBLE")?
}
if let Some(ref v) = self.kind {
write!(f, " TYPE {}", v)?
}
@ -906,6 +910,13 @@ fn field(i: &str) -> IResult<&str, DefineFieldStatement> {
DefineFieldStatement {
name,
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 {
DefineFieldOption::Kind(ref v) => Some(v.to_owned()),
_ => None,
@ -931,6 +942,7 @@ fn field(i: &str) -> IResult<&str, DefineFieldStatement> {
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub enum DefineFieldOption {
Flex,
Kind(Kind),
Value(Value),
Assert(Value),
@ -938,7 +950,13 @@ pub enum 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> {

View file

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