parent
98a5d75d63
commit
f5b21eb363
6 changed files with 168 additions and 12 deletions
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?,
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue