diff --git a/lib/src/doc/field.rs b/lib/src/doc/field.rs index 43e838df..aaf5af81 100644 --- a/lib/src/doc/field.rs +++ b/lib/src/doc/field.rs @@ -4,7 +4,6 @@ use crate::dbs::Statement; use crate::dbs::Transaction; use crate::doc::Document; use crate::err::Error; -use crate::sql::data::Data; use crate::sql::permission::Permission; use crate::sql::value::Value; @@ -14,7 +13,7 @@ impl<'a> Document<'a> { ctx: &Context<'_>, opt: &Options, txn: &Transaction, - stm: &Statement<'_>, + _stm: &Statement<'_>, ) -> Result<(), Error> { // Check fields if !opt.fields { @@ -22,6 +21,8 @@ impl<'a> Document<'a> { } // Get the record id let rid = self.id.as_ref().unwrap(); + // Get the user applied input + let inp = self.initial.changed(self.current.as_ref()); // Loop through all field statements for fd in self.fd(opt, txn).await?.iter() { // Loop over each field in document @@ -29,16 +30,7 @@ impl<'a> Document<'a> { // Get the initial value let old = self.initial.pick(&k); // Get the input value - let inp = match stm.data() { - Some(Data::MergeExpression(v)) => v.pick(&k), - Some(Data::ContentExpression(v)) => v.pick(&k), - Some(Data::ReplaceExpression(v)) => v.pick(&k), - Some(Data::SetExpression(v)) => match v.iter().find(|f| f.0.eq(&k)) { - Some((_, _, v)) => v.to_owned(), - _ => Value::None, - }, - _ => Value::None, - }; + let inp = inp.pick(&k); // Check for a TYPE clause if let Some(kind) = &fd.kind { if !val.is_none() { diff --git a/lib/src/sql/value/changed.rs b/lib/src/sql/value/changed.rs new file mode 100644 index 00000000..570f954b --- /dev/null +++ b/lib/src/sql/value/changed.rs @@ -0,0 +1,94 @@ +use crate::sql::idiom::Idiom; +use crate::sql::value::Value; + +impl Value { + pub(crate) fn changed(&self, val: &Value) -> Value { + match (self, val) { + (Value::Object(a), Value::Object(b)) => { + // Create an object + let mut chg = Value::base(); + // Loop over old keys + for (key, _) in a.iter() { + if !b.contains_key(key) { + let path = Idiom::from(key.clone()); + chg.put(&path, Value::None); + } + } + // Loop over new keys + for (key, val) in b.iter() { + match a.get(key) { + // Key did not exist + None => { + let path = Idiom::from(key.clone()); + chg.put(&path, val.clone()); + } + Some(old) => { + if old != val { + let path = Idiom::from(key.clone()); + chg.put(&path, old.changed(val)); + } + } + } + } + // + chg + } + (_, _) => val.clone(), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::sql::test::Parse; + + #[test] + fn changed_none() { + let old = Value::parse("{ test: true, text: 'text', other: { something: true } }"); + let now = Value::parse("{ test: true, text: 'text', other: { something: true } }"); + let res = Value::parse("{}"); + assert_eq!(res, old.changed(&now)); + } + + #[test] + fn changed_add() { + let old = Value::parse("{ test: true }"); + let now = Value::parse("{ test: true, other: 'test' }"); + let res = Value::parse("{ other: 'test' }"); + assert_eq!(res, old.changed(&now)); + } + + #[test] + fn changed_remove() { + let old = Value::parse("{ test: true, other: 'test' }"); + let now = Value::parse("{ test: true }"); + let res = Value::parse("{ other: NONE }]"); + assert_eq!(res, old.changed(&now)); + } + + #[test] + fn changed_add_array() { + let old = Value::parse("{ test: [1,2,3] }"); + let now = Value::parse("{ test: [1,2,3,4] }"); + let res = Value::parse("{ test: [1,2,3,4] }"); + assert_eq!(res, old.changed(&now)); + } + + #[test] + fn changed_replace_embedded() { + let old = Value::parse("{ test: { other: 'test' } }"); + let now = Value::parse("{ test: { other: false } }"); + let res = Value::parse("{ test: { other: false } }"); + assert_eq!(res, old.changed(&now)); + } + + #[test] + fn changed_change_text() { + let old = Value::parse("{ test: { other: 'test' } }"); + let now = Value::parse("{ test: { other: 'text' } }"); + let res = Value::parse("{ test: { other: 'text' } }"); + assert_eq!(res, old.changed(&now)); + } +} diff --git a/lib/src/sql/value/mod.rs b/lib/src/sql/value/mod.rs index c3f0f040..e3c31a67 100644 --- a/lib/src/sql/value/mod.rs +++ b/lib/src/sql/value/mod.rs @@ -6,6 +6,7 @@ pub(super) mod serde; mod value; mod all; +mod changed; mod clear; mod compare; mod cut; diff --git a/lib/tests/update.rs b/lib/tests/update.rs index d9305511..7233d5e7 100644 --- a/lib/tests/update.rs +++ b/lib/tests/update.rs @@ -6,7 +6,7 @@ use surrealdb::kvs::Datastore; use surrealdb::sql::Value; #[tokio::test] -async fn update_with_input() -> Result<(), Error> { +async fn update_simple_with_input() -> Result<(), Error> { let sql = " DEFINE FIELD name ON TABLE person ASSERT @@ -95,3 +95,41 @@ async fn update_with_input() -> Result<(), Error> { // Ok(()) } + +#[tokio::test] +async fn update_complex_with_input() -> Result<(), Error> { + let sql = " + DEFINE FIELD images ON product + TYPE array + ASSERT array::len($value) > 0 + ; + DEFINE FIELD images.* ON product TYPE string + VALUE string::trim($input) + ASSERT $input AND string::len($value) > 0 + ; + CREATE product:test SET images = [' test.png ']; + "; + 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(), 3); + // + 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: product:test, + images: ['test.png'], + } + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +}