use core::fmt;
use std::borrow::Cow;
use super::{
super::IntoAttributeValue, ApplyExpressionAttributes, ApplyUpdate, AttrNames, AttrValues,
AttributeValue, Expression, UpdatableBuilder, fmt_attr_maps, resolve_expression,
utils::resolve_attr_path,
};
#[derive(Debug, Clone)]
enum UpdateSetRhsInner<'a> {
Value {
value: AttributeValue,
},
Attribute {
attr: Cow<'a, str>,
},
IfNotExists {
attr: Cow<'a, str>,
value: AttributeValue,
},
Add {
lhs: Box<UpdateSetRhs<'a>>,
rhs: Box<UpdateSetRhs<'a>>,
},
Sub {
lhs: Box<UpdateSetRhs<'a>>,
rhs: Box<UpdateSetRhs<'a>>,
},
}
#[derive(Debug, Clone)]
#[must_use = "expression does nothing until applied to a request"]
pub struct UpdateSetRhs<'a>(UpdateSetRhsInner<'a>);
impl<'a> UpdateSetRhs<'a> {
pub fn value(value: impl IntoAttributeValue) -> Self {
Self(UpdateSetRhsInner::Value {
value: value.into_attribute_value(),
})
}
pub fn attr(attr: impl Into<Cow<'a, str>>) -> Self {
Self(UpdateSetRhsInner::Attribute { attr: attr.into() })
}
pub fn if_not_exists(attr: impl Into<Cow<'a, str>>, value: impl IntoAttributeValue) -> Self {
Self(UpdateSetRhsInner::IfNotExists {
attr: attr.into(),
value: value.into_attribute_value(),
})
}
fn rhs_expr(self, counter: &mut usize) -> (Expression, AttrNames, AttrValues) {
match self.0 {
UpdateSetRhsInner::IfNotExists { attr, value } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let value_ph = format!(":u{val_id}");
(
format!("if_not_exists({attr_expr}, {value_ph})"),
names,
vec![(value_ph, value)],
)
}
UpdateSetRhsInner::Value { value } => {
let val_id = *counter;
*counter += 1;
let value_ph = format!(":u{val_id}");
(value_ph.clone(), vec![], vec![(value_ph, value)])
}
UpdateSetRhsInner::Attribute { attr } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
(attr_expr.into_owned(), names, vec![])
}
UpdateSetRhsInner::Add { lhs, rhs } => {
let mut lhs = lhs.rhs_expr(counter);
let rhs = rhs.rhs_expr(counter);
lhs.1.extend(rhs.1);
lhs.2.extend(rhs.2);
(format!("{} + {}", lhs.0, rhs.0), lhs.1, lhs.2)
}
UpdateSetRhsInner::Sub { lhs, rhs } => {
let mut lhs = lhs.rhs_expr(counter);
let rhs = rhs.rhs_expr(counter);
lhs.1.extend(rhs.1);
lhs.2.extend(rhs.2);
(format!("{} - {}", lhs.0, rhs.0), lhs.1, lhs.2)
}
}
}
}
impl<'a> core::ops::Add<UpdateSetRhs<'a>> for UpdateSetRhs<'a> {
type Output = UpdateSetRhs<'a>;
fn add(self, rhs: UpdateSetRhs<'a>) -> Self::Output {
Self(UpdateSetRhsInner::Add {
lhs: Box::new(self),
rhs: Box::new(rhs),
})
}
}
impl<'a> core::ops::Sub<UpdateSetRhs<'a>> for UpdateSetRhs<'a> {
type Output = UpdateSetRhs<'a>;
fn sub(self, rhs: UpdateSetRhs<'a>) -> Self::Output {
Self(UpdateSetRhsInner::Sub {
lhs: Box::new(self),
rhs: Box::new(rhs),
})
}
}
#[derive(Debug, Clone)]
enum UpdateInner<'a> {
Combine(Vec<Update<'a>>),
Set {
attr: Cow<'a, str>,
value: AttributeValue,
},
SetIfNotExists {
attr: Cow<'a, str>,
value: AttributeValue,
},
Increment {
attr: Cow<'a, str>,
by: AttributeValue,
},
Decrement {
attr: Cow<'a, str>,
by: AttributeValue,
},
InitIncrement {
attr: Cow<'a, str>,
initial: AttributeValue,
by: AttributeValue,
},
InitDecrement {
attr: Cow<'a, str>,
initial: AttributeValue,
by: AttributeValue,
},
ListAppend {
attr: Cow<'a, str>,
value: AttributeValue,
},
ListPrepend {
attr: Cow<'a, str>,
value: AttributeValue,
},
SetCustom {
attr: Cow<'a, str>,
rhs: UpdateSetRhs<'a>,
},
Remove {
attr: Cow<'a, str>,
},
ListRemove {
attr: Cow<'a, str>,
index: usize,
},
Add {
attr: Cow<'a, str>,
value: AttributeValue,
},
Delete {
attr: Cow<'a, str>,
set: AttributeValue,
},
}
#[derive(Debug, Clone)]
#[must_use = "expression does nothing until applied to a request"]
pub struct Update<'a>(UpdateInner<'a>);
impl<'a> Update<'a> {
pub fn set_custom(attr: impl Into<Cow<'a, str>>, rhs: UpdateSetRhs<'a>) -> Self {
Self(UpdateInner::SetCustom {
attr: attr.into(),
rhs,
})
}
pub fn set(attr: impl Into<Cow<'a, str>>, value: impl IntoAttributeValue) -> Self {
Self(UpdateInner::Set {
attr: attr.into(),
value: value.into_attribute_value(),
})
}
pub fn set_if_not_exists(
attr: impl Into<Cow<'a, str>>,
value: impl IntoAttributeValue,
) -> Self {
Self(UpdateInner::SetIfNotExists {
attr: attr.into(),
value: value.into_attribute_value(),
})
}
pub fn increment(attr: impl Into<Cow<'a, str>>, by: impl IntoAttributeValue) -> Self {
Self(UpdateInner::Increment {
attr: attr.into(),
by: by.into_attribute_value(),
})
}
pub fn decrement(attr: impl Into<Cow<'a, str>>, by: impl IntoAttributeValue) -> Self {
Self(UpdateInner::Decrement {
attr: attr.into(),
by: by.into_attribute_value(),
})
}
pub fn init_increment(
attr: impl Into<Cow<'a, str>>,
initial: impl IntoAttributeValue,
by: impl IntoAttributeValue,
) -> Self {
Self(UpdateInner::InitIncrement {
attr: attr.into(),
initial: initial.into_attribute_value(),
by: by.into_attribute_value(),
})
}
pub fn init_decrement(
attr: impl Into<Cow<'a, str>>,
initial: impl IntoAttributeValue,
by: impl IntoAttributeValue,
) -> Self {
Self(UpdateInner::InitDecrement {
attr: attr.into(),
initial: initial.into_attribute_value(),
by: by.into_attribute_value(),
})
}
pub fn list_append(attr: impl Into<Cow<'a, str>>, value: impl IntoAttributeValue) -> Self {
Self(UpdateInner::ListAppend {
attr: attr.into(),
value: value.into_attribute_value(),
})
}
pub fn list_prepend(attr: impl Into<Cow<'a, str>>, value: impl IntoAttributeValue) -> Self {
Self(UpdateInner::ListPrepend {
attr: attr.into(),
value: value.into_attribute_value(),
})
}
pub fn remove(attr: impl Into<Cow<'a, str>>) -> Self {
Self(UpdateInner::Remove { attr: attr.into() })
}
pub fn list_remove(attr: impl Into<Cow<'a, str>>, index: usize) -> Self {
Self(UpdateInner::ListRemove {
attr: attr.into(),
index,
})
}
pub fn add(attr: impl Into<Cow<'a, str>>, value: impl IntoAttributeValue) -> Self {
Self(UpdateInner::Add {
attr: attr.into(),
value: value.into_attribute_value(),
})
}
pub fn delete(attr: impl Into<Cow<'a, str>>, value: impl IntoAttributeValue) -> Self {
Self(UpdateInner::Delete {
attr: attr.into(),
set: value.into_attribute_value(),
})
}
pub fn and(self, other: Update<'a>) -> Self {
Self(UpdateInner::Combine(match self.0 {
UpdateInner::Combine(mut updates) => {
updates.push(other);
updates
}
_ => vec![self, other],
}))
}
pub fn combine(updates: impl IntoIterator<Item = Update<'a>>) -> Self {
let updates: Vec<_> = updates.into_iter().collect();
assert!(
!updates.is_empty(),
"Update::combine requires at least one update"
);
Self(UpdateInner::Combine(updates))
}
pub fn try_combine(updates: impl IntoIterator<Item = Update<'a>>) -> Option<Self> {
let updates: Vec<_> = updates.into_iter().collect();
if updates.is_empty() {
None
} else {
Some(Self(UpdateInner::Combine(updates)))
}
}
}
#[derive(Debug, Default)]
struct BuiltUpdate {
set_actions: Vec<String>,
remove_actions: Vec<String>,
add_actions: Vec<String>,
delete_actions: Vec<String>,
names: AttrNames,
values: AttrValues,
}
impl BuiltUpdate {
fn merge(&mut self, other: BuiltUpdate) {
self.set_actions.extend(other.set_actions);
self.remove_actions.extend(other.remove_actions);
self.add_actions.extend(other.add_actions);
self.delete_actions.extend(other.delete_actions);
self.names.extend(other.names);
self.values.extend(other.values);
}
fn into_expression(self) -> (String, AttrNames, AttrValues) {
use core::fmt::Write;
let mut expression = String::new();
if !self.set_actions.is_empty() {
let _ = write!(expression, "SET {}", self.set_actions.join(", "));
}
if !self.remove_actions.is_empty() {
if !expression.is_empty() {
let _ = write!(expression, " ");
}
let _ = write!(expression, "REMOVE {}", self.remove_actions.join(", "));
}
if !self.add_actions.is_empty() {
if !expression.is_empty() {
let _ = write!(expression, " ");
}
let _ = write!(expression, "ADD {}", self.add_actions.join(", "));
}
if !self.delete_actions.is_empty() {
if !expression.is_empty() {
let _ = write!(expression, " ");
}
let _ = write!(expression, "DELETE {}", self.delete_actions.join(", "));
}
(expression, self.names, self.values)
}
}
impl Update<'_> {
fn build(self, counter: &mut usize) -> BuiltUpdate {
match self.0 {
UpdateInner::Combine(updates) => {
let mut result = BuiltUpdate::default();
for update in updates {
result.merge(update.build(counter));
}
result
}
UpdateInner::SetCustom { attr, rhs } => {
let (attr_expr, mut names) = resolve_attr_path(&attr, "u", counter);
let (rhs_expr, rhs_names, rhs_values) = rhs.rhs_expr(counter);
names.extend(rhs_names);
BuiltUpdate {
set_actions: vec![format!("{attr_expr} = {rhs_expr}")],
names,
values: rhs_values,
..Default::default()
}
}
UpdateInner::Set { attr, value } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let value_ph = format!(":u{val_id}");
BuiltUpdate {
set_actions: vec![format!("{attr_expr} = {value_ph}")],
names,
values: vec![(value_ph, value)],
..Default::default()
}
}
UpdateInner::SetIfNotExists { attr, value } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let value_ph = format!(":u{val_id}");
BuiltUpdate {
set_actions: vec![format!(
"{attr_expr} = if_not_exists({attr_expr}, {value_ph})"
)],
names,
values: vec![(value_ph, value)],
..Default::default()
}
}
UpdateInner::Increment { attr, by } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let by_ph = format!(":u{val_id}");
BuiltUpdate {
set_actions: vec![format!("{attr_expr} = {attr_expr} + {by_ph}")],
names,
values: vec![(by_ph, by)],
..Default::default()
}
}
UpdateInner::Decrement { attr, by } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let by_ph = format!(":u{val_id}");
BuiltUpdate {
set_actions: vec![format!("{attr_expr} = {attr_expr} - {by_ph}")],
names,
values: vec![(by_ph, by)],
..Default::default()
}
}
UpdateInner::InitIncrement { attr, initial, by } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let by_ph = format!(":u{val_id}");
let init_ph = format!(":u{val_id}init");
BuiltUpdate {
set_actions: vec![format!(
"{attr_expr} = if_not_exists({attr_expr}, {init_ph}) + {by_ph}"
)],
names,
values: vec![(init_ph, initial), (by_ph, by)],
..Default::default()
}
}
UpdateInner::InitDecrement { attr, initial, by } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let by_ph = format!(":u{val_id}");
let init_ph = format!(":u{val_id}init");
BuiltUpdate {
set_actions: vec![format!(
"{attr_expr} = if_not_exists({attr_expr}, {init_ph}) - {by_ph}"
)],
names,
values: vec![(init_ph, initial), (by_ph, by)],
..Default::default()
}
}
UpdateInner::ListAppend { attr, value } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let val_ph = format!(":u{val_id}");
BuiltUpdate {
set_actions: vec![format!("{attr_expr} = list_append({attr_expr}, {val_ph})")],
names,
values: vec![(val_ph, value)],
..Default::default()
}
}
UpdateInner::ListPrepend { attr, value } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let val_ph = format!(":u{val_id}");
BuiltUpdate {
set_actions: vec![format!("{attr_expr} = list_append({val_ph}, {attr_expr})")],
names,
values: vec![(val_ph, value)],
..Default::default()
}
}
UpdateInner::Remove { attr } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
BuiltUpdate {
remove_actions: vec![attr_expr.into_owned()],
names,
..Default::default()
}
}
UpdateInner::ListRemove { attr, index } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
BuiltUpdate {
remove_actions: vec![format!("{attr_expr}[{index}]")],
names,
..Default::default()
}
}
UpdateInner::Add { attr, value } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let val_ph = format!(":u{val_id}");
BuiltUpdate {
add_actions: vec![format!("{attr_expr} {val_ph}")],
names,
values: vec![(val_ph, value)],
..Default::default()
}
}
UpdateInner::Delete { attr, set } => {
let (attr_expr, names) = resolve_attr_path(&attr, "u", counter);
let val_id = *counter;
*counter += 1;
let set_ph = format!(":u{val_id}");
BuiltUpdate {
delete_actions: vec![format!("{attr_expr} {set_ph}")],
names,
values: vec![(set_ph, set)],
..Default::default()
}
}
}
}
}
impl fmt::Display for Update<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut counter = 0;
let (expression, names, values) = self.clone().build(&mut counter).into_expression();
if f.alternate() {
f.write_str(&expression)?;
fmt_attr_maps(f, &names, &values)
} else {
f.write_str(&resolve_expression(&expression, &names, &values))
}
}
}
impl<B: UpdatableBuilder> ApplyUpdate<B> for Update<'_> {
fn apply(self, builder: B) -> B {
let mut counter = 0;
let (expression, names, values) = self.build(&mut counter).into_expression();
builder
.update_expression(expression)
.apply_names_and_values(names, values)
}
}
impl<B: UpdatableBuilder> ApplyUpdate<B> for Option<Update<'_>> {
fn apply(self, builder: B) -> B {
match self {
Some(u) => u.apply(builder),
None => builder,
}
}
}
#[cfg(test)]
mod tests {
use super::super::super::values::AsSet;
use super::*;
#[test]
fn test_update_display_default_set() {
let u = Update::set("balance", 100u32);
let display = format!("{u}");
assert_eq!(display, r#"SET balance = N("100")"#);
}
#[test]
fn test_update_display_default_set_reserved_word() {
let u = Update::set("Status", "active");
let display = format!("{u}");
assert_eq!(display, r#"SET Status = S("active")"#);
}
#[test]
fn test_update_display_default_increment() {
let u = Update::increment("login_count", 1u32);
let display = format!("{u}");
assert_eq!(display, r#"SET login_count = login_count + N("1")"#);
}
#[test]
fn test_update_display_default_remove() {
let u = Update::remove("legacy_field");
let display = format!("{u}");
assert_eq!(display, "REMOVE legacy_field");
}
#[test]
fn test_update_display_default_combined() {
let u = Update::combine([
Update::set("balance", 100u32),
Update::increment("login_count", 1u32),
Update::remove("legacy_field"),
]);
let display = format!("{u}");
assert_eq!(
display,
r#"SET balance = N("100"), login_count = login_count + N("1") REMOVE legacy_field"#
);
}
#[test]
fn test_update_display_default_add_action() {
let u = Update::add("visitor_count", 5u32);
let display = format!("{u}");
assert_eq!(display, r#"ADD visitor_count N("5")"#);
}
#[test]
fn test_update_display_default_set_if_not_exists() {
let u = Update::set_if_not_exists("created_at", "2024-01-01");
let display = format!("{u}");
assert_eq!(
display,
r#"SET created_at = if_not_exists(created_at, S("2024-01-01"))"#
);
}
#[test]
fn test_update_display_default_init_increment() {
let u = Update::init_increment("counter", 0u32, 1u32);
let display = format!("{u}");
assert_eq!(
display,
r#"SET counter = if_not_exists(counter, N("0")) + N("1")"#
);
}
#[test]
fn test_update_display_alternate_set() {
let u = Update::set("balance", 100u32);
let display = format!("{u:#}");
assert_eq!(display, "SET balance = :u0\n values: { :u0 = N(\"100\") }");
}
#[test]
fn test_update_display_alternate_reserved_word() {
let u = Update::set("Status", "active");
let display = format!("{u:#}");
assert_eq!(
display,
"SET #u0 = :u1\n names: { #u0 = Status }\n values: { :u1 = S(\"active\") }"
);
}
#[test]
fn test_update_display_alternate_remove() {
let u = Update::remove("legacy_field");
let display = format!("{u:#}");
assert_eq!(display, "REMOVE legacy_field");
}
#[test]
fn test_update_display_alternate_combined() {
let u = Update::combine([Update::set("balance", 100u32), Update::remove("Name")]);
let display = format!("{u:#}");
assert_eq!(
display,
"SET balance = :u0 REMOVE #u1\n names: { #u1 = Name }\n values: { :u0 = N(\"100\") }"
);
}
#[test]
fn test_decrement_display_default() {
let u = Update::decrement("credits", 1u32);
assert_eq!(format!("{u}"), r#"SET credits = credits - N("1")"#);
}
#[test]
fn test_decrement_display_alternate() {
let u = Update::decrement("credits", 1u32);
assert_eq!(
format!("{u:#}"),
"SET credits = credits - :u0\n values: { :u0 = N(\"1\") }"
);
}
#[test]
fn test_decrement_display_default_reserved_word() {
let u = Update::decrement("Count", 1u32);
assert_eq!(format!("{u}"), r#"SET Count = Count - N("1")"#);
}
#[test]
fn test_decrement_display_alternate_reserved_word() {
let u = Update::decrement("Count", 1u32);
assert_eq!(
format!("{u:#}"),
"SET #u0 = #u0 - :u1\n names: { #u0 = Count }\n values: { :u1 = N(\"1\") }"
);
}
#[test]
fn test_init_decrement_display_default() {
let u = Update::init_decrement("credits", 100u32, 1u32);
assert_eq!(
format!("{u}"),
r#"SET credits = if_not_exists(credits, N("100")) - N("1")"#
);
}
#[test]
fn test_init_decrement_display_alternate() {
let u = Update::init_decrement("credits", 100u32, 1u32);
assert_eq!(
format!("{u:#}"),
"SET credits = if_not_exists(credits, :u0init) - :u0\n values: { :u0init = N(\"100\"), :u0 = N(\"1\") }"
);
}
#[test]
fn test_list_append_display_default() {
let u = Update::list_append("tags", vec!["a", "b"]);
assert_eq!(
format!("{u}"),
r#"SET tags = list_append(tags, L([S("a"), S("b")]))"#
);
}
#[test]
fn test_list_append_display_alternate() {
let u = Update::list_append("tags", vec!["a", "b"]);
assert_eq!(
format!("{u:#}"),
"SET tags = list_append(tags, :u0)\n values: { :u0 = L([S(\"a\"), S(\"b\")]) }"
);
}
#[test]
fn test_list_prepend_display_default() {
let u = Update::list_prepend("tags", vec!["a", "b"]);
assert_eq!(
format!("{u}"),
r#"SET tags = list_append(L([S("a"), S("b")]), tags)"#
);
}
#[test]
fn test_list_prepend_display_alternate() {
let u = Update::list_prepend("tags", vec!["a", "b"]);
assert_eq!(
format!("{u:#}"),
"SET tags = list_append(:u0, tags)\n values: { :u0 = L([S(\"a\"), S(\"b\")]) }"
);
}
#[test]
fn test_list_remove_display_default() {
let u = Update::list_remove("tags", 0);
assert_eq!(format!("{u}"), "REMOVE tags[0]");
}
#[test]
fn test_list_remove_display_alternate() {
let u = Update::list_remove("tags", 0);
assert_eq!(format!("{u:#}"), "REMOVE tags[0]");
}
#[test]
fn test_delete_display_default() {
let u = Update::delete("tag_set", AsSet(vec!["old".to_owned()]));
assert_eq!(format!("{u}"), r#"DELETE tag_set Ss(["old"])"#);
}
#[test]
fn test_delete_display_alternate() {
let u = Update::delete("tag_set", AsSet(vec!["old".to_owned()]));
assert_eq!(
format!("{u:#}"),
"DELETE tag_set :u0\n values: { :u0 = Ss([\"old\"]) }"
);
}
#[test]
fn test_set_custom_value_display_default() {
let u = Update::set_custom("score", UpdateSetRhs::value("Alice"));
assert_eq!(format!("{u}"), r#"SET score = S("Alice")"#);
}
#[test]
fn test_set_custom_value_display_alternate() {
let u = Update::set_custom("score", UpdateSetRhs::value("Alice"));
assert_eq!(
format!("{u:#}"),
"SET score = :u0\n values: { :u0 = S(\"Alice\") }"
);
}
#[test]
fn test_set_custom_attr_display_default() {
let u = Update::set_custom("display_name", UpdateSetRhs::attr("name"));
assert_eq!(format!("{u}"), "SET display_name = name");
}
#[test]
fn test_set_custom_attr_display_alternate() {
let u = Update::set_custom("display_name", UpdateSetRhs::attr("name"));
assert_eq!(
format!("{u:#}"),
"SET display_name = #u0\n names: { #u0 = name }"
);
}
#[test]
fn test_set_custom_if_not_exists_display_default() {
let u = Update::set_custom("score", UpdateSetRhs::if_not_exists("score", 0u32));
assert_eq!(
format!("{u}"),
r#"SET score = if_not_exists(score, N("0"))"#
);
}
#[test]
fn test_set_custom_if_not_exists_display_alternate() {
let u = Update::set_custom("score", UpdateSetRhs::if_not_exists("score", 0u32));
assert_eq!(
format!("{u:#}"),
"SET score = if_not_exists(score, :u0)\n values: { :u0 = N(\"0\") }"
);
}
#[test]
fn test_set_custom_composite_rhs_display_default() {
let rhs = UpdateSetRhs::attr("a") + UpdateSetRhs::value(5u32) - UpdateSetRhs::attr("b");
let u = Update::set_custom("total", rhs);
assert_eq!(format!("{u}"), r#"SET total = a + N("5") - b"#);
}
#[test]
#[should_panic(expected = "Update::combine requires at least one update")]
fn test_update_combine_empty_panics() {
let _ = Update::combine(std::iter::empty::<Update>());
}
#[test]
fn test_update_try_combine_empty_none() {
let result = Update::try_combine(std::iter::empty::<Update>());
assert!(result.is_none());
}
#[test]
fn test_update_try_combine_non_empty_some() {
let result = Update::try_combine([Update::set("score", 1u32)]);
assert!(result.is_some());
assert_eq!(format!("{}", result.unwrap()), r#"SET score = N("1")"#);
}
#[test]
fn test_update_and_flattens_nested_combine() {
let u = Update::set("score", 1u32)
.and(Update::set("balance", 2u32))
.and(Update::remove("legacy_field"));
assert_eq!(
format!("{u}"),
r#"SET score = N("1"), balance = N("2") REMOVE legacy_field"#
);
}
}