Add operator for unique array add

Related to #1690
This commit is contained in:
Tobie Morgan Hitchcock 2023-03-25 23:17:33 +00:00
parent 4dba9fc675
commit 1102a2c6da
7 changed files with 192 additions and 11 deletions

View file

@ -56,6 +56,9 @@ impl<'a> Document<'a> {
Operator::Dec => { Operator::Dec => {
self.current.to_mut().decrement(ctx, opt, txn, &x.0, v).await? 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!(), _ => unreachable!(),
} }
} }

View file

@ -24,6 +24,7 @@ pub enum Operator {
Pow, // ** Pow, // **
Inc, // += Inc, // +=
Dec, // -= Dec, // -=
Ext, // +?=
// //
Equal, // = Equal, // =
Exact, // == Exact, // ==
@ -93,6 +94,7 @@ impl fmt::Display for Operator {
Self::Pow => "**", Self::Pow => "**",
Self::Inc => "+=", Self::Inc => "+=",
Self::Dec => "-=", Self::Dec => "-=",
Self::Ext => "+=?",
Self::Equal => "=", Self::Equal => "=",
Self::Exact => "==", Self::Exact => "==",
Self::NotEqual => "!=", Self::NotEqual => "!=",
@ -127,6 +129,7 @@ pub fn assigner(i: &str) -> IResult<&str, Operator> {
map(char('='), |_| Operator::Equal), map(char('='), |_| Operator::Equal),
map(tag("+="), |_| Operator::Inc), map(tag("+="), |_| Operator::Inc),
map(tag("-="), |_| Operator::Dec), map(tag("-="), |_| Operator::Dec),
map(tag("+?="), |_| Operator::Ext),
))(i) ))(i)
} }

View file

@ -44,7 +44,7 @@ mod tests {
use crate::sql::test::Parse; use crate::sql::test::Parse;
#[tokio::test] #[tokio::test]
async fn dec_none() { async fn decrement_none() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("other"); let idi = Idiom::parse("other");
let mut val = Value::parse("{ test: 100 }"); let mut val = Value::parse("{ test: 100 }");
@ -54,7 +54,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn dec_number() { async fn decrement_number() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("test"); let idi = Idiom::parse("test");
let mut val = Value::parse("{ test: 100 }"); let mut val = Value::parse("{ test: 100 }");
@ -64,7 +64,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn dec_array_number() { async fn decrement_array_number() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("test[1]"); let idi = Idiom::parse("test[1]");
let mut val = Value::parse("{ test: [100, 200, 300] }"); let mut val = Value::parse("{ test: [100, 200, 300] }");
@ -74,7 +74,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn dec_array_value() { async fn decrement_array_value() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("test"); let idi = Idiom::parse("test");
let mut val = Value::parse("{ test: [100, 200, 300] }"); let mut val = Value::parse("{ test: [100, 200, 300] }");
@ -84,7 +84,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn dec_array_array() { async fn decrement_array_array() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("test"); let idi = Idiom::parse("test");
let mut val = Value::parse("{ test: [100, 200, 300] }"); let mut val = Value::parse("{ test: [100, 200, 300] }");

View file

@ -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);
}
}

View file

@ -45,7 +45,7 @@ mod tests {
use crate::sql::test::Parse; use crate::sql::test::Parse;
#[tokio::test] #[tokio::test]
async fn inc_none() { async fn increment_none() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("other"); let idi = Idiom::parse("other");
let mut val = Value::parse("{ test: 100 }"); let mut val = Value::parse("{ test: 100 }");
@ -55,7 +55,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn inc_number() { async fn increment_number() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("test"); let idi = Idiom::parse("test");
let mut val = Value::parse("{ test: 100 }"); let mut val = Value::parse("{ test: 100 }");
@ -65,7 +65,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn inc_array_number() { async fn increment_array_number() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("test[1]"); let idi = Idiom::parse("test[1]");
let mut val = Value::parse("{ test: [100, 200, 300] }"); let mut val = Value::parse("{ test: [100, 200, 300] }");
@ -75,7 +75,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn inc_array_value() { async fn increment_array_value() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("test"); let idi = Idiom::parse("test");
let mut val = Value::parse("{ test: [100, 200, 300] }"); let mut val = Value::parse("{ test: [100, 200, 300] }");
@ -85,7 +85,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn inc_array_array() { async fn increment_array_array() {
let (ctx, opt, txn) = mock().await; let (ctx, opt, txn) = mock().await;
let idi = Idiom::parse("test"); let idi = Idiom::parse("test");
let mut val = Value::parse("{ test: [100, 200, 300] }"); let mut val = Value::parse("{ test: [100, 200, 300] }");

View file

@ -13,6 +13,7 @@ mod del;
mod diff; mod diff;
mod each; mod each;
mod every; mod every;
mod extend;
mod fetch; mod fetch;
mod first; mod first;
mod flatten; mod flatten;

View file

@ -102,7 +102,122 @@ async fn subquery_select() -> Result<(), Error> {
} }
#[tokio::test] #[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 = " let sql = "
-- Check if the record exists -- Check if the record exists
LET $record = (SELECT *, count() AS count FROM person:test); LET $record = (SELECT *, count() AS count FROM person:test);