From 1102a2c6da62d50440417109a795870e1552a4b4 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Sat, 25 Mar 2023 23:17:33 +0000 Subject: [PATCH] Add operator for unique array add Related to #1690 --- lib/src/doc/alter.rs | 3 + lib/src/sql/operator.rs | 3 + lib/src/sql/value/decrement.rs | 10 +-- lib/src/sql/value/extend.rs | 59 +++++++++++++++++ lib/src/sql/value/increment.rs | 10 +-- lib/src/sql/value/mod.rs | 1 + lib/tests/subquery.rs | 117 ++++++++++++++++++++++++++++++++- 7 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 lib/src/sql/value/extend.rs diff --git a/lib/src/doc/alter.rs b/lib/src/doc/alter.rs index 5cd434e3..2aaa769d 100644 --- a/lib/src/doc/alter.rs +++ b/lib/src/doc/alter.rs @@ -56,6 +56,9 @@ impl<'a> Document<'a> { Operator::Dec => { self.current.to_mut().decrement(ctx, opt, txn, &x.0, v).await? } + Operator::Ext => { + self.current.to_mut().extend(ctx, opt, txn, &x.0, v).await? + } _ => unreachable!(), } } diff --git a/lib/src/sql/operator.rs b/lib/src/sql/operator.rs index 18adbb4f..651c5adc 100644 --- a/lib/src/sql/operator.rs +++ b/lib/src/sql/operator.rs @@ -24,6 +24,7 @@ pub enum Operator { Pow, // ** Inc, // += Dec, // -= + Ext, // +?= // Equal, // = Exact, // == @@ -93,6 +94,7 @@ impl fmt::Display for Operator { Self::Pow => "**", Self::Inc => "+=", Self::Dec => "-=", + Self::Ext => "+=?", Self::Equal => "=", Self::Exact => "==", Self::NotEqual => "!=", @@ -127,6 +129,7 @@ pub fn assigner(i: &str) -> IResult<&str, Operator> { map(char('='), |_| Operator::Equal), map(tag("+="), |_| Operator::Inc), map(tag("-="), |_| Operator::Dec), + map(tag("+?="), |_| Operator::Ext), ))(i) } diff --git a/lib/src/sql/value/decrement.rs b/lib/src/sql/value/decrement.rs index 8d622313..f8b71030 100644 --- a/lib/src/sql/value/decrement.rs +++ b/lib/src/sql/value/decrement.rs @@ -44,7 +44,7 @@ mod tests { use crate::sql::test::Parse; #[tokio::test] - async fn dec_none() { + async fn decrement_none() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("other"); let mut val = Value::parse("{ test: 100 }"); @@ -54,7 +54,7 @@ mod tests { } #[tokio::test] - async fn dec_number() { + async fn decrement_number() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("test"); let mut val = Value::parse("{ test: 100 }"); @@ -64,7 +64,7 @@ mod tests { } #[tokio::test] - async fn dec_array_number() { + async fn decrement_array_number() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("test[1]"); let mut val = Value::parse("{ test: [100, 200, 300] }"); @@ -74,7 +74,7 @@ mod tests { } #[tokio::test] - async fn dec_array_value() { + async fn decrement_array_value() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("test"); let mut val = Value::parse("{ test: [100, 200, 300] }"); @@ -84,7 +84,7 @@ mod tests { } #[tokio::test] - async fn dec_array_array() { + async fn decrement_array_array() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("test"); let mut val = Value::parse("{ test: [100, 200, 300] }"); diff --git a/lib/src/sql/value/extend.rs b/lib/src/sql/value/extend.rs new file mode 100644 index 00000000..a6f03b80 --- /dev/null +++ b/lib/src/sql/value/extend.rs @@ -0,0 +1,59 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::sql::array::Uniq; +use crate::sql::part::Part; +use crate::sql::value::Value; + +impl Value { + pub async fn extend( + &mut self, + ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + path: &[Part], + val: Value, + ) -> Result<(), Error> { + match self.get(ctx, opt, txn, path).await? { + Value::Array(v) => match val { + Value::Array(x) => self.set(ctx, opt, txn, path, Value::from((v + x).uniq())).await, + x => self.set(ctx, opt, txn, path, Value::from((v + x).uniq())).await, + }, + Value::None => match val { + Value::Array(x) => self.set(ctx, opt, txn, path, Value::from(x)).await, + x => self.set(ctx, opt, txn, path, Value::from(vec![x])).await, + }, + _ => Ok(()), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::dbs::test::mock; + use crate::sql::idiom::Idiom; + use crate::sql::test::Parse; + + #[tokio::test] + async fn extend_array_value() { + let (ctx, opt, txn) = mock().await; + let idi = Idiom::parse("test"); + let mut val = Value::parse("{ test: [100, 200, 300] }"); + let res = Value::parse("{ test: [100, 200, 300] }"); + val.extend(&ctx, &opt, &txn, &idi, Value::from(200)).await.unwrap(); + assert_eq!(res, val); + } + + #[tokio::test] + async fn extend_array_array() { + let (ctx, opt, txn) = mock().await; + let idi = Idiom::parse("test"); + let mut val = Value::parse("{ test: [100, 200, 300] }"); + let res = Value::parse("{ test: [100, 200, 300, 400, 500] }"); + val.extend(&ctx, &opt, &txn, &idi, Value::parse("[100, 300, 400, 500]")).await.unwrap(); + assert_eq!(res, val); + } +} diff --git a/lib/src/sql/value/increment.rs b/lib/src/sql/value/increment.rs index be91bb25..5481a077 100644 --- a/lib/src/sql/value/increment.rs +++ b/lib/src/sql/value/increment.rs @@ -45,7 +45,7 @@ mod tests { use crate::sql::test::Parse; #[tokio::test] - async fn inc_none() { + async fn increment_none() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("other"); let mut val = Value::parse("{ test: 100 }"); @@ -55,7 +55,7 @@ mod tests { } #[tokio::test] - async fn inc_number() { + async fn increment_number() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("test"); let mut val = Value::parse("{ test: 100 }"); @@ -65,7 +65,7 @@ mod tests { } #[tokio::test] - async fn inc_array_number() { + async fn increment_array_number() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("test[1]"); let mut val = Value::parse("{ test: [100, 200, 300] }"); @@ -75,7 +75,7 @@ mod tests { } #[tokio::test] - async fn inc_array_value() { + async fn increment_array_value() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("test"); let mut val = Value::parse("{ test: [100, 200, 300] }"); @@ -85,7 +85,7 @@ mod tests { } #[tokio::test] - async fn inc_array_array() { + async fn increment_array_array() { let (ctx, opt, txn) = mock().await; let idi = Idiom::parse("test"); let mut val = Value::parse("{ test: [100, 200, 300] }"); diff --git a/lib/src/sql/value/mod.rs b/lib/src/sql/value/mod.rs index 66ac8b39..42a39e96 100644 --- a/lib/src/sql/value/mod.rs +++ b/lib/src/sql/value/mod.rs @@ -13,6 +13,7 @@ mod del; mod diff; mod each; mod every; +mod extend; mod fetch; mod first; mod flatten; diff --git a/lib/tests/subquery.rs b/lib/tests/subquery.rs index 56fc1fc1..af7c63c4 100644 --- a/lib/tests/subquery.rs +++ b/lib/tests/subquery.rs @@ -102,7 +102,122 @@ async fn subquery_select() -> Result<(), Error> { } #[tokio::test] -async fn subquery_ifelse() -> Result<(), Error> { +async fn subquery_ifelse_set() -> Result<(), Error> { + let sql = " + -- Check if the record exists + LET $record = (SELECT *, count() AS count FROM person:test); + -- Return the specified record + RETURN $record; + -- Update the record field if it exists + IF $record.count THEN + (UPDATE person:test SET sport +?= 'football' RETURN sport) + ELSE + (UPDATE person:test SET sport = ['basketball'] RETURN sport) + END; + -- Check if the record exists + LET $record = SELECT *, count() AS count FROM person:test; + -- Return the specified record + RETURN $record; + -- Update the record field if it exists + IF $record.count THEN + UPDATE person:test SET sport +?= 'football' RETURN sport + ELSE + UPDATE person:test SET sport = ['basketball'] RETURN sport + END; + -- Check if the record exists + LET $record = SELECT *, count() AS count FROM person:test; + -- Return the specified record + RETURN $record; + -- Update the record field if it exists + IF $record.count THEN + UPDATE person:test SET sport +?= 'football' RETURN sport; + ELSE + UPDATE person:test SET sport = ['basketball'] RETURN sport; + END; + "; + 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(), 9); + // + let tmp = res.remove(0).result?; + let val = Value::None; + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::None; + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + sport: [ + 'basketball', + ] + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::None; + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + count: 1, + id: person:test, + sport: [ + 'basketball', + ] + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + sport: [ + 'basketball', + 'football', + ] + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::None; + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + count: 1, + id: person:test, + sport: [ + 'basketball', + 'football', + ] + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + sport: [ + 'basketball', + 'football', + ] + }", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn subquery_ifelse_array() -> Result<(), Error> { let sql = " -- Check if the record exists LET $record = (SELECT *, count() AS count FROM person:test);