1use crate::expressions::condition::parse_raw_path;
6use crate::expressions::tokenizer::{
7 Token, TokenStream, near_window_parser, near_window_tokenizer, tokenize,
8};
9use crate::expressions::{
10 PathElement, TrackedExpressionAttributes, format_path_for_error, remove_path, resolve_path,
11 resolve_path_elements, set_path,
12};
13use crate::types::AttributeValue;
14use std::collections::HashMap;
15
16#[derive(Debug)]
18pub struct UpdateExpr {
19 pub set_actions: Vec<SetAction>,
20 pub remove_actions: Vec<Vec<PathElement>>,
21 pub add_actions: Vec<AddAction>,
22 pub delete_actions: Vec<DeleteAction>,
23}
24
25#[derive(Debug)]
27pub struct SetAction {
28 pub path: Vec<PathElement>,
29 pub value: SetValue,
30}
31
32#[derive(Debug)]
34pub enum SetValue {
35 Operand(SetOperand),
37 Plus(SetOperand, SetOperand),
39 Minus(SetOperand, SetOperand),
41}
42
43#[derive(Debug)]
45pub enum SetOperand {
46 Path(Vec<PathElement>),
47 ValueRef(String),
48 IfNotExists(Vec<PathElement>, Box<SetOperand>),
49 ListAppend(Box<SetOperand>, Box<SetOperand>),
50 Group(Box<SetValue>),
52}
53
54#[derive(Debug)]
56pub struct AddAction {
57 pub path: Vec<PathElement>,
58 pub value_ref: String,
59}
60
61#[derive(Debug)]
63pub struct DeleteAction {
64 pub path: Vec<PathElement>,
65 pub value_ref: String,
66}
67
68pub fn parse(expr: &str) -> Result<UpdateExpr, String> {
70 super::check_expression_size(expr).map_err(|e| format!("Invalid UpdateExpression: {e}"))?;
74 let tokens = match tokenize(expr) {
75 Ok(t) => t,
76 Err(err) => {
77 let bad = &expr[err.position..err.position + err.bad_len];
81 let near = near_window_tokenizer(expr, err.position);
82 return Err(format!(
83 r#"Invalid UpdateExpression: Syntax error; token: "{bad}", near: "{near}""#
84 ));
85 }
86 };
87 let mut stream = TokenStream::new(tokens);
88
89 let mut set_actions = Vec::new();
90 let mut remove_actions = Vec::new();
91 let mut add_actions = Vec::new();
92 let mut delete_actions = Vec::new();
93
94 let mut seen_set = false;
95 let mut seen_remove = false;
96 let mut seen_add = false;
97 let mut seen_delete = false;
98
99 while !stream.at_end() {
100 match stream.peek() {
101 Some(Token::Set) => {
102 if seen_set {
103 return Err("Invalid UpdateExpression: The \"SET\" section can only be used once in an update expression;".to_string());
104 }
105 seen_set = true;
106 stream.next();
107 parse_set_clause(&mut stream, &mut set_actions).map_err(wrap_syntax_error)?;
108 }
109 Some(Token::Remove) => {
110 if seen_remove {
111 return Err("Invalid UpdateExpression: The \"REMOVE\" section can only be used once in an update expression;".to_string());
112 }
113 seen_remove = true;
114 stream.next();
115 parse_remove_clause(&mut stream, &mut remove_actions).map_err(wrap_syntax_error)?;
116 }
117 Some(Token::Add) => {
118 if seen_add {
119 return Err("Invalid UpdateExpression: The \"ADD\" section can only be used once in an update expression;".to_string());
120 }
121 seen_add = true;
122 stream.next();
123 parse_add_clause(&mut stream, &mut add_actions).map_err(wrap_syntax_error)?;
124 }
125 Some(Token::Delete) => {
126 if seen_delete {
127 return Err("Invalid UpdateExpression: The \"DELETE\" section can only be used once in an update expression;".to_string());
128 }
129 seen_delete = true;
130 stream.next();
131 parse_delete_clause(&mut stream, &mut delete_actions).map_err(wrap_syntax_error)?;
132 }
133 Some(_) => {
134 let offending_span = stream
138 .peek_span()
139 .expect("peek_span must yield when peek did");
140 let bad = &expr[offending_span.start..offending_span.end()];
141 stream.next();
142 let next_span = stream.peek_span();
143 let near = near_window_parser(expr, offending_span, next_span);
144 return Err(format!(
145 r#"Invalid UpdateExpression: Syntax error; token: "{bad}", near: "{near}""#
146 ));
147 }
148 None => break,
149 }
150 }
151
152 Ok(UpdateExpr {
153 set_actions,
154 remove_actions,
155 add_actions,
156 delete_actions,
157 })
158}
159
160fn wrap_syntax_error(err: String) -> String {
163 if err.starts_with("Invalid UpdateExpression:") {
164 err
165 } else if err.starts_with("Attribute name is a reserved keyword") {
166 format!("Invalid UpdateExpression: {err}")
167 } else {
168 format!("Invalid UpdateExpression: Syntax error; {err}")
169 }
170}
171
172pub fn track_references(
177 expr: &UpdateExpr,
178 tracker: &TrackedExpressionAttributes,
179) -> Result<(), String> {
180 let mut all_target_paths: Vec<Vec<PathElement>> = Vec::new();
182
183 for action in &expr.set_actions {
184 track_path_refs(&action.path, tracker)?;
185 track_set_value_refs(&action.value, tracker)?;
186 all_target_paths.push(resolve_tracked_path(&action.path, tracker));
187 }
188 for path in &expr.remove_actions {
189 track_path_refs(path, tracker)?;
190 all_target_paths.push(resolve_tracked_path(path, tracker));
191 }
192 for action in &expr.add_actions {
193 track_path_refs(&action.path, tracker)?;
194 let val = tracker.resolve_value(&action.value_ref)?;
195 validate_add_type(val)?;
197 all_target_paths.push(resolve_tracked_path(&action.path, tracker));
198 }
199 for action in &expr.delete_actions {
200 track_path_refs(&action.path, tracker)?;
201 let val = tracker.resolve_value(&action.value_ref)?;
202 validate_delete_type(val)?;
204 all_target_paths.push(resolve_tracked_path(&action.path, tracker));
205 }
206
207 for action in &expr.set_actions {
209 validate_set_value_types(&action.value, tracker)?;
210 }
211
212 check_path_overlaps(&all_target_paths)?;
214
215 Ok(())
216}
217
218fn validate_add_type(val: &crate::types::AttributeValue) -> Result<(), String> {
220 use crate::types::AttributeValue;
221 match val {
222 AttributeValue::N(_)
223 | AttributeValue::SS(_)
224 | AttributeValue::NS(_)
225 | AttributeValue::BS(_) => Ok(()),
226 _ => Err(format!(
227 "Invalid UpdateExpression: Incorrect operand type for operator or function; \
228 operator: ADD, operand type: {}",
229 dynamo_type_name(val)
230 )),
231 }
232}
233
234fn validate_delete_type(val: &crate::types::AttributeValue) -> Result<(), String> {
236 use crate::types::AttributeValue;
237 match val {
238 AttributeValue::SS(_) | AttributeValue::NS(_) | AttributeValue::BS(_) => Ok(()),
239 _ => Err(format!(
240 "Invalid UpdateExpression: Incorrect operand type for operator or function; \
241 operator: DELETE, operand type: {}",
242 dynamo_type_name(val)
243 )),
244 }
245}
246
247fn dynamo_type_name(val: &crate::types::AttributeValue) -> &'static str {
249 use crate::types::AttributeValue;
250 match val {
251 AttributeValue::S(_) => "STRING",
252 AttributeValue::N(_) => "NUMBER",
253 AttributeValue::B(_) => "BINARY",
254 AttributeValue::BOOL(_) => "BOOLEAN",
255 AttributeValue::NULL(_) => "NULL",
256 AttributeValue::SS(_) => "SS",
257 AttributeValue::NS(_) => "NS",
258 AttributeValue::BS(_) => "BS",
259 AttributeValue::L(_) => "LIST",
260 AttributeValue::M(_) => "MAP",
261 }
262}
263
264fn validate_set_value_types(
266 value: &SetValue,
267 tracker: &TrackedExpressionAttributes,
268) -> Result<(), String> {
269 match value {
270 SetValue::Operand(op) => validate_set_operand_types(op, tracker),
271 SetValue::Plus(left, right) => {
272 validate_arithmetic_operand(left, "+", tracker)?;
273 validate_arithmetic_operand(right, "+", tracker)
274 }
275 SetValue::Minus(left, right) => {
276 validate_arithmetic_operand(left, "-", tracker)?;
277 validate_arithmetic_operand(right, "-", tracker)
278 }
279 }
280}
281
282fn validate_arithmetic_operand(
284 operand: &SetOperand,
285 op: &str,
286 tracker: &TrackedExpressionAttributes,
287) -> Result<(), String> {
288 use crate::types::AttributeValue;
289 match operand {
290 SetOperand::ValueRef(name) => {
291 let val = tracker.resolve_value(name)?;
292 if !matches!(val, AttributeValue::N(_)) {
293 return Err(format!(
294 "Invalid UpdateExpression: Incorrect operand type for operator or function; \
295 operator or function: {op}, operand type: {}",
296 dynamo_type_name(val)
297 ));
298 }
299 Ok(())
300 }
301 SetOperand::IfNotExists(_, default) => validate_set_operand_types(default, tracker),
302 SetOperand::ListAppend(a, b) => {
303 validate_list_append_operand(a, tracker)?;
304 validate_list_append_operand(b, tracker)
305 }
306 SetOperand::Path(_) => Ok(()), SetOperand::Group(inner) => validate_set_value_types(inner, tracker),
310 }
311}
312
313fn validate_set_operand_types(
315 operand: &SetOperand,
316 tracker: &TrackedExpressionAttributes,
317) -> Result<(), String> {
318 match operand {
319 SetOperand::ListAppend(a, b) => {
320 validate_list_append_operand(a, tracker)?;
321 validate_list_append_operand(b, tracker)
322 }
323 SetOperand::IfNotExists(_, default) => validate_set_operand_types(default, tracker),
324 SetOperand::Group(inner) => validate_set_value_types(inner, tracker),
325 _ => Ok(()),
326 }
327}
328
329fn validate_list_append_operand(
331 operand: &SetOperand,
332 tracker: &TrackedExpressionAttributes,
333) -> Result<(), String> {
334 use crate::types::AttributeValue;
335 if let SetOperand::ValueRef(name) = operand {
336 let val = tracker.resolve_value(name)?;
337 if !matches!(val, AttributeValue::L(_)) {
338 return Err(format!(
339 "Invalid UpdateExpression: Incorrect operand type for operator or function; \
340 operator or function: list_append, operand type: {}",
341 dynamo_type_name(val)
342 ));
343 }
344 }
345 Ok(())
346}
347
348fn resolve_tracked_path(
350 path: &[PathElement],
351 tracker: &TrackedExpressionAttributes,
352) -> Vec<PathElement> {
353 path.iter()
354 .map(|elem| {
355 if let PathElement::Attribute(name) = elem {
356 if name.starts_with('#') {
357 if let Ok(resolved) = tracker.resolve_name(name) {
358 return PathElement::Attribute(resolved);
359 }
360 }
361 }
362 elem.clone()
363 })
364 .collect()
365}
366
367fn check_path_overlaps(paths: &[Vec<PathElement>]) -> Result<(), String> {
373 for i in 0..paths.len() {
374 for j in (i + 1)..paths.len() {
375 let a = &paths[i];
376 let b = &paths[j];
377 let min_len = a.len().min(b.len());
378
379 let mut common = 0;
381 for k in 0..min_len {
382 if a[k] == b[k] {
383 common += 1;
384 } else {
385 break;
386 }
387 }
388
389 if common == 0 {
390 continue;
391 }
392
393 if common == a.len() || common == b.len() {
395 let (shorter, longer) = if a.len() <= b.len() { (a, b) } else { (b, a) };
396 return Err(format!(
397 "Invalid UpdateExpression: Two document paths overlap with each other; \
398 must remove or rewrite one of these paths; \
399 path one: {}, path two: {}",
400 format_path_for_error(longer),
401 format_path_for_error(shorter)
402 ));
403 }
404
405 if common > 0 && common < min_len && a == b {
407 return Err(format!(
408 "Invalid UpdateExpression: Two document paths conflict with each other; \
409 must remove or rewrite one of these paths; \
410 path one: {}, path two: {}",
411 format_path_for_error(a),
412 format_path_for_error(b)
413 ));
414 }
415 }
416 }
417 Ok(())
418}
419
420fn track_path_refs(
421 path: &[PathElement],
422 tracker: &TrackedExpressionAttributes,
423) -> Result<(), String> {
424 for elem in path {
425 if let PathElement::Attribute(name) = elem {
426 if name.starts_with('#') {
427 tracker.resolve_name(name)?;
428 }
429 }
430 }
431 Ok(())
432}
433
434fn track_set_value_refs(
435 value: &SetValue,
436 tracker: &TrackedExpressionAttributes,
437) -> Result<(), String> {
438 match value {
439 SetValue::Operand(op) => track_set_operand_refs(op, tracker),
440 SetValue::Plus(left, right) | SetValue::Minus(left, right) => {
441 track_set_operand_refs(left, tracker)?;
442 track_set_operand_refs(right, tracker)
443 }
444 }
445}
446
447fn track_set_operand_refs(
448 operand: &SetOperand,
449 tracker: &TrackedExpressionAttributes,
450) -> Result<(), String> {
451 match operand {
452 SetOperand::Path(path) => track_path_refs(path, tracker),
453 SetOperand::ValueRef(name) => {
454 tracker.resolve_value(name)?;
455 Ok(())
456 }
457 SetOperand::IfNotExists(path, default) => {
458 track_path_refs(path, tracker)?;
459 track_set_operand_refs(default, tracker)
460 }
461 SetOperand::ListAppend(a, b) => {
462 track_set_operand_refs(a, tracker)?;
463 track_set_operand_refs(b, tracker)
464 }
465 SetOperand::Group(inner) => track_set_value_refs(inner, tracker),
466 }
467}
468
469pub fn apply(
471 item: &mut HashMap<String, AttributeValue>,
472 expr: &UpdateExpr,
473 tracker: &TrackedExpressionAttributes,
474) -> Result<(), String> {
475 let snapshot = item.clone();
484 for action in &expr.set_actions {
485 let resolved_path = resolve_path_elements(&action.path, tracker)?;
486 let value = evaluate_set_value(&action.value, &snapshot, tracker)?;
487 set_path(item, &resolved_path, value)?;
488 }
489
490 for path in &expr.remove_actions {
492 let resolved_path = resolve_path_elements(path, tracker)?;
493 remove_path(item, &resolved_path)?;
494 }
495
496 for action in &expr.add_actions {
498 let resolved_path = resolve_path_elements(&action.path, tracker)?;
499 let add_val = tracker.resolve_value(&action.value_ref)?.clone();
500 apply_add(item, &resolved_path, &add_val).map_err(|_| {
501 "An operand in the update expression has an incorrect data type".to_string()
502 })?;
503 }
504
505 for action in &expr.delete_actions {
507 let resolved_path = resolve_path_elements(&action.path, tracker)?;
508 let del_val = tracker.resolve_value(&action.value_ref)?.clone();
509 apply_delete(item, &resolved_path, &del_val).map_err(|_| {
510 "An operand in the update expression has an incorrect data type".to_string()
511 })?;
512 }
513
514 Ok(())
515}
516
517fn evaluate_set_value(
522 value: &SetValue,
523 item: &HashMap<String, AttributeValue>,
524 tracker: &TrackedExpressionAttributes,
525) -> Result<AttributeValue, String> {
526 match value {
527 SetValue::Operand(op) => evaluate_set_operand(op, item, tracker),
528 SetValue::Plus(left, right) => {
529 let lv = evaluate_set_operand(left, item, tracker)?;
530 let rv = evaluate_set_operand(right, item, tracker)?;
531 match (&lv, &rv) {
532 (AttributeValue::N(a), AttributeValue::N(b)) => {
533 use bigdecimal::BigDecimal;
534 use std::str::FromStr;
535 let da = BigDecimal::from_str(a).map_err(|_| format!("Invalid number: {a}"))?;
536 let db = BigDecimal::from_str(b).map_err(|_| format!("Invalid number: {b}"))?;
537 let result = &da + &db;
538 Ok(AttributeValue::N(format_number(&result)))
539 }
540 _ => Err("Operands for + must be numbers".to_string()),
541 }
542 }
543 SetValue::Minus(left, right) => {
544 let lv = evaluate_set_operand(left, item, tracker)?;
545 let rv = evaluate_set_operand(right, item, tracker)?;
546 match (&lv, &rv) {
547 (AttributeValue::N(a), AttributeValue::N(b)) => {
548 use bigdecimal::BigDecimal;
549 use std::str::FromStr;
550 let da = BigDecimal::from_str(a).map_err(|_| format!("Invalid number: {a}"))?;
551 let db = BigDecimal::from_str(b).map_err(|_| format!("Invalid number: {b}"))?;
552 let result = &da - &db;
553 Ok(AttributeValue::N(format_number(&result)))
554 }
555 _ => Err("Operands for - must be numbers".to_string()),
556 }
557 }
558 }
559}
560
561fn evaluate_set_operand(
562 operand: &SetOperand,
563 item: &HashMap<String, AttributeValue>,
564 tracker: &TrackedExpressionAttributes,
565) -> Result<AttributeValue, String> {
566 match operand {
567 SetOperand::Path(path) => {
568 let resolved = resolve_path_elements(path, tracker)?;
569 resolve_path(item, &resolved).ok_or_else(|| {
570 "The provided expression refers to an attribute that does not exist in the item"
571 .to_string()
572 })
573 }
574 SetOperand::ValueRef(name) => Ok(tracker.resolve_value(name)?.clone()),
575 SetOperand::IfNotExists(path, default) => {
576 let resolved = resolve_path_elements(path, tracker)?;
577 match resolve_path(item, &resolved) {
578 Some(existing) => Ok(existing),
579 None => evaluate_set_operand(default, item, tracker),
580 }
581 }
582 SetOperand::ListAppend(list1, list2) => {
583 let v1 = evaluate_set_operand(list1, item, tracker)?;
584 let v2 = evaluate_set_operand(list2, item, tracker)?;
585 match (v1, v2) {
586 (AttributeValue::L(mut a), AttributeValue::L(b)) => {
587 a.extend(b);
588 Ok(AttributeValue::L(a))
589 }
590 _ => Err("list_append requires two list operands".to_string()),
591 }
592 }
593 SetOperand::Group(inner) => evaluate_set_value(inner, item, tracker),
594 }
595}
596
597pub fn apply_add_public(
603 item: &mut HashMap<String, AttributeValue>,
604 path: &[PathElement],
605 add_val: &AttributeValue,
606) -> Result<(), String> {
607 apply_add(item, path, add_val)
608}
609
610fn apply_add(
611 item: &mut HashMap<String, AttributeValue>,
612 path: &[PathElement],
613 add_val: &AttributeValue,
614) -> Result<(), String> {
615 let existing = resolve_path(item, path);
616
617 match (existing, add_val) {
618 (Some(AttributeValue::N(existing_n)), AttributeValue::N(add_n)) => {
620 use bigdecimal::BigDecimal;
621 use std::str::FromStr;
622 let de = BigDecimal::from_str(&existing_n)
623 .map_err(|_| format!("Invalid number: {existing_n}"))?;
624 let da = BigDecimal::from_str(add_n).map_err(|_| format!("Invalid number: {add_n}"))?;
625 let result = &de + &da;
626 set_path(item, path, AttributeValue::N(format_number(&result)))
627 }
628 (None, AttributeValue::N(_)) => {
629 set_path(item, path, add_val.clone())
631 }
632
633 (Some(AttributeValue::SS(mut existing_set)), AttributeValue::SS(add_set)) => {
635 for s in add_set {
636 if !existing_set.contains(s) {
637 existing_set.push(s.clone());
638 }
639 }
640 set_path(item, path, AttributeValue::SS(existing_set))
641 }
642 (None, AttributeValue::SS(_)) => set_path(item, path, add_val.clone()),
643
644 (Some(AttributeValue::NS(mut existing_set)), AttributeValue::NS(add_set)) => {
646 for n in add_set {
647 if !existing_set.contains(n) {
648 existing_set.push(n.clone());
649 }
650 }
651 set_path(item, path, AttributeValue::NS(existing_set))
652 }
653 (None, AttributeValue::NS(_)) => set_path(item, path, add_val.clone()),
654
655 (Some(AttributeValue::BS(mut existing_set)), AttributeValue::BS(add_set)) => {
657 for b in add_set {
658 if !existing_set.contains(b) {
659 existing_set.push(b.clone());
660 }
661 }
662 set_path(item, path, AttributeValue::BS(existing_set))
663 }
664 (None, AttributeValue::BS(_)) => set_path(item, path, add_val.clone()),
665
666 (Some(AttributeValue::L(mut existing_list)), AttributeValue::L(add_list)) => {
668 existing_list.extend(add_list.iter().cloned());
669 set_path(item, path, AttributeValue::L(existing_list))
670 }
671 (None, AttributeValue::L(_)) => set_path(item, path, add_val.clone()),
672
673 _ => Err("Type mismatch for attribute to update".to_string()),
674 }
675}
676
677pub fn apply_delete_public(
683 item: &mut HashMap<String, AttributeValue>,
684 path: &[PathElement],
685 del_val: &AttributeValue,
686) -> Result<(), String> {
687 apply_delete(item, path, del_val)
688}
689
690fn apply_delete(
691 item: &mut HashMap<String, AttributeValue>,
692 path: &[PathElement],
693 del_val: &AttributeValue,
694) -> Result<(), String> {
695 let existing = resolve_path(item, path);
696
697 match (existing, del_val) {
698 (Some(AttributeValue::SS(existing_set)), AttributeValue::SS(del_set)) => {
699 let new_set: Vec<String> = existing_set
700 .into_iter()
701 .filter(|s| !del_set.contains(s))
702 .collect();
703 if new_set.is_empty() {
704 remove_path(item, path)
705 } else {
706 set_path(item, path, AttributeValue::SS(new_set))
707 }
708 }
709 (Some(AttributeValue::NS(existing_set)), AttributeValue::NS(del_set)) => {
710 let new_set: Vec<String> = existing_set
711 .into_iter()
712 .filter(|n| !del_set.contains(n))
713 .collect();
714 if new_set.is_empty() {
715 remove_path(item, path)
716 } else {
717 set_path(item, path, AttributeValue::NS(new_set))
718 }
719 }
720 (Some(AttributeValue::BS(existing_set)), AttributeValue::BS(del_set)) => {
721 let new_set: Vec<Vec<u8>> = existing_set
722 .into_iter()
723 .filter(|b| !del_set.contains(b))
724 .collect();
725 if new_set.is_empty() {
726 remove_path(item, path)
727 } else {
728 set_path(item, path, AttributeValue::BS(new_set))
729 }
730 }
731 (None, _) => Ok(()), _ => Err("Type mismatch for attribute to update".to_string()),
733 }
734}
735
736fn parse_set_clause(stream: &mut TokenStream, actions: &mut Vec<SetAction>) -> Result<(), String> {
741 actions.push(parse_set_action(stream)?);
742 while matches!(stream.peek(), Some(Token::Comma)) {
743 stream.next();
744 actions.push(parse_set_action(stream)?);
745 }
746 Ok(())
747}
748
749fn parse_set_action(stream: &mut TokenStream) -> Result<SetAction, String> {
750 let path = parse_raw_path(stream)?;
751 stream.expect(&Token::Eq)?;
752 let value = parse_set_value(stream)?;
753 Ok(SetAction { path, value })
754}
755
756fn parse_set_value(stream: &mut TokenStream) -> Result<SetValue, String> {
757 let left = parse_set_operand(stream)?;
758
759 match stream.peek() {
760 Some(Token::Plus) => {
761 stream.next();
762 let right = parse_set_operand(stream)?;
763 Ok(SetValue::Plus(left, right))
764 }
765 Some(Token::Minus) => {
766 stream.next();
767 let right = parse_set_operand(stream)?;
768 Ok(SetValue::Minus(left, right))
769 }
770 _ => Ok(SetValue::Operand(left)),
771 }
772}
773
774fn parse_set_operand(stream: &mut TokenStream) -> Result<SetOperand, String> {
775 if let Some(Token::Identifier(name)) = stream.peek() {
777 let func_name = name.to_lowercase();
778 let orig_name = name.clone();
779 match func_name.as_str() {
780 "if_not_exists" => {
781 stream.next();
782 stream.expect(&Token::LParen)?;
783
784 match stream.peek() {
786 Some(Token::ValueRef(_)) => {
787 return Err(
788 "Invalid UpdateExpression: Operator or function requires a document path; \
789 operator or function: if_not_exists".to_string()
790 );
791 }
792 Some(Token::Identifier(fname))
793 if fname.to_lowercase() == "if_not_exists"
794 || fname.to_lowercase() == "list_append" =>
795 {
796 return Err(
797 "Invalid UpdateExpression: Operator or function requires a document path; \
798 operator or function: if_not_exists".to_string()
799 );
800 }
801 _ => {}
802 }
803
804 let path = parse_raw_path(stream)?;
805
806 if !matches!(stream.peek(), Some(Token::Comma)) {
808 return Err(
809 "Invalid UpdateExpression: Incorrect number of operands for operator or function; \
810 operator or function: if_not_exists, number of operands: 1".to_string()
811 );
812 }
813 stream.expect(&Token::Comma)?;
814 let default = parse_set_operand(stream)?;
815 stream.expect(&Token::RParen)?;
816 return Ok(SetOperand::IfNotExists(path, Box::new(default)));
817 }
818 "list_append" => {
819 stream.next();
820 stream.expect(&Token::LParen)?;
821 let list1 = parse_set_operand(stream)?;
822
823 if !matches!(stream.peek(), Some(Token::Comma)) {
825 return Err(
826 "Invalid UpdateExpression: Incorrect number of operands for operator or function; \
827 operator or function: list_append, number of operands: 1".to_string()
828 );
829 }
830 stream.expect(&Token::Comma)?;
831 let list2 = parse_set_operand(stream)?;
832 stream.expect(&Token::RParen)?;
833 return Ok(SetOperand::ListAppend(Box::new(list1), Box::new(list2)));
834 }
835 _ => {
836 let saved_pos = stream.pos();
839 stream.next();
840 if matches!(stream.peek(), Some(Token::LParen)) {
841 return Err(format!(
842 "Invalid UpdateExpression: Invalid function name; function: {}",
843 orig_name
844 ));
845 }
846 stream.set_pos(saved_pos);
848 }
849 }
850 }
851
852 match stream.peek() {
853 Some(Token::LParen) => {
856 stream.next();
857 let inner = parse_set_value(stream)?;
858 stream.expect(&Token::RParen)?;
859 Ok(SetOperand::Group(Box::new(inner)))
860 }
861 Some(Token::ValueRef(_)) => {
862 if let Some(Token::ValueRef(name)) = stream.next().cloned() {
863 Ok(SetOperand::ValueRef(name))
864 } else {
865 unreachable!()
866 }
867 }
868 Some(Token::Identifier(_)) | Some(Token::NameRef(_)) => {
869 let path = parse_raw_path(stream)?;
870 Ok(SetOperand::Path(path))
871 }
872 Some(t) => Err(format!("Expected operand in SET, got {t}")),
873 None => Err("Expected operand in SET, got end of expression".to_string()),
874 }
875}
876
877fn parse_remove_clause(
878 stream: &mut TokenStream,
879 actions: &mut Vec<Vec<PathElement>>,
880) -> Result<(), String> {
881 actions.push(parse_raw_path(stream)?);
882 while matches!(stream.peek(), Some(Token::Comma)) {
883 stream.next();
884 actions.push(parse_raw_path(stream)?);
885 }
886 Ok(())
887}
888
889fn parse_add_clause(stream: &mut TokenStream, actions: &mut Vec<AddAction>) -> Result<(), String> {
890 actions.push(parse_add_action(stream)?);
891 while matches!(stream.peek(), Some(Token::Comma)) {
892 stream.next();
893 actions.push(parse_add_action(stream)?);
894 }
895 Ok(())
896}
897
898fn parse_add_action(stream: &mut TokenStream) -> Result<AddAction, String> {
899 let path = parse_raw_path(stream)?;
900 match stream.next() {
901 Some(Token::ValueRef(name)) => Ok(AddAction {
902 path,
903 value_ref: name.clone(),
904 }),
905 Some(t) => Err(format!("Expected value reference in ADD, got {t}")),
906 None => Err("Expected value reference in ADD, got end of expression".to_string()),
907 }
908}
909
910fn parse_delete_clause(
911 stream: &mut TokenStream,
912 actions: &mut Vec<DeleteAction>,
913) -> Result<(), String> {
914 actions.push(parse_delete_action(stream)?);
915 while matches!(stream.peek(), Some(Token::Comma)) {
916 stream.next();
917 actions.push(parse_delete_action(stream)?);
918 }
919 Ok(())
920}
921
922fn parse_delete_action(stream: &mut TokenStream) -> Result<DeleteAction, String> {
923 let path = parse_raw_path(stream)?;
924 match stream.next() {
925 Some(Token::ValueRef(name)) => Ok(DeleteAction {
926 path,
927 value_ref: name.clone(),
928 }),
929 Some(t) => Err(format!("Expected value reference in DELETE, got {t}")),
930 None => Err("Expected value reference in DELETE, got end of expression".to_string()),
931 }
932}
933
934fn format_number(n: &bigdecimal::BigDecimal) -> String {
937 let normalized = n.normalized();
938 if normalized.as_bigint_and_exponent().1 < 0 {
942 normalized.with_scale(0).to_string()
943 } else {
944 normalized.to_string()
945 }
946}
947
948#[cfg(test)]
949mod tests {
950 use super::*;
951
952 fn make_item(pairs: &[(&str, AttributeValue)]) -> HashMap<String, AttributeValue> {
953 pairs
954 .iter()
955 .map(|(k, v)| (k.to_string(), v.clone()))
956 .collect()
957 }
958
959 fn vals(pairs: &[(&str, AttributeValue)]) -> Option<HashMap<String, AttributeValue>> {
960 Some(make_item(pairs))
961 }
962
963 fn make_tracker<'a>(
964 names: &'a Option<HashMap<String, String>>,
965 values: &'a Option<HashMap<String, AttributeValue>>,
966 ) -> TrackedExpressionAttributes<'a> {
967 TrackedExpressionAttributes::new(names, values)
968 }
969
970 #[test]
971 fn test_set_simple() {
972 let expr = parse("SET label = :val").unwrap();
973 assert_eq!(expr.set_actions.len(), 1);
974 assert!(expr.remove_actions.is_empty());
975 }
976
977 #[test]
978 fn test_set_multiple() {
979 let expr = parse("SET a = :v1, b = :v2").unwrap();
980 assert_eq!(expr.set_actions.len(), 2);
981 }
982
983 #[test]
984 fn test_set_arithmetic_plus() {
985 let expr = parse("SET tally = tally + :inc").unwrap();
986 let mut item = make_item(&[
987 ("pk", AttributeValue::S("k".into())),
988 ("tally", AttributeValue::N("10".into())),
989 ]);
990 let av = vals(&[(":inc", AttributeValue::N("5".into()))]);
991 let no_names = None;
992 let tracker = make_tracker(&no_names, &av);
993 apply(&mut item, &expr, &tracker).unwrap();
994 assert_eq!(item["tally"], AttributeValue::N("15".into()));
995 }
996
997 #[test]
998 fn test_set_arithmetic_minus() {
999 let expr = parse("SET price = price - :discount").unwrap();
1000 let mut item = make_item(&[
1001 ("pk", AttributeValue::S("k".into())),
1002 ("price", AttributeValue::N("100".into())),
1003 ]);
1004 let av = vals(&[(":discount", AttributeValue::N("25".into()))]);
1005 let no_names = None;
1006 let tracker = make_tracker(&no_names, &av);
1007 apply(&mut item, &expr, &tracker).unwrap();
1008 assert_eq!(item["price"], AttributeValue::N("75".into()));
1009 }
1010
1011 #[test]
1012 fn test_set_if_not_exists() {
1013 let expr = parse("SET hits = if_not_exists(hits, :zero)").unwrap();
1014 let mut item = make_item(&[("pk", AttributeValue::S("k".into()))]);
1015 let av = vals(&[(":zero", AttributeValue::N("0".into()))]);
1016 let no_names = None;
1017 let tracker = make_tracker(&no_names, &av);
1018 apply(&mut item, &expr, &tracker).unwrap();
1019 assert_eq!(item["hits"], AttributeValue::N("0".into()));
1020
1021 let tracker2 = make_tracker(&no_names, &av);
1023 apply(&mut item, &expr, &tracker2).unwrap();
1024 assert_eq!(item["hits"], AttributeValue::N("0".into()));
1025 }
1026
1027 #[test]
1028 fn test_set_list_append() {
1029 let expr = parse("SET entries = list_append(entries, :new)").unwrap();
1030 let mut item = make_item(&[
1031 ("pk", AttributeValue::S("k".into())),
1032 (
1033 "entries",
1034 AttributeValue::L(vec![AttributeValue::S("a".into())]),
1035 ),
1036 ]);
1037 let av = vals(&[(
1038 ":new",
1039 AttributeValue::L(vec![AttributeValue::S("b".into())]),
1040 )]);
1041 let no_names = None;
1042 let tracker = make_tracker(&no_names, &av);
1043 apply(&mut item, &expr, &tracker).unwrap();
1044 if let AttributeValue::L(list) = &item["entries"] {
1045 assert_eq!(list.len(), 2);
1046 } else {
1047 panic!("Expected list");
1048 }
1049 }
1050
1051 #[test]
1052 fn test_remove() {
1053 let expr = parse("REMOVE attr1, attr2").unwrap();
1054 let mut item = make_item(&[
1055 ("pk", AttributeValue::S("k".into())),
1056 ("attr1", AttributeValue::S("a".into())),
1057 ("attr2", AttributeValue::S("b".into())),
1058 ("attr3", AttributeValue::S("c".into())),
1059 ]);
1060 let no_names = None;
1061 let no_values = None;
1062 let tracker = make_tracker(&no_names, &no_values);
1063 apply(&mut item, &expr, &tracker).unwrap();
1064 assert!(!item.contains_key("attr1"));
1065 assert!(!item.contains_key("attr2"));
1066 assert!(item.contains_key("attr3"));
1067 }
1068
1069 #[test]
1070 fn test_add_number() {
1071 let expr = parse("ADD tally :inc").unwrap();
1072 let mut item = make_item(&[
1073 ("pk", AttributeValue::S("k".into())),
1074 ("tally", AttributeValue::N("10".into())),
1075 ]);
1076 let av = vals(&[(":inc", AttributeValue::N("5".into()))]);
1077 let no_names = None;
1078 let tracker = make_tracker(&no_names, &av);
1079 apply(&mut item, &expr, &tracker).unwrap();
1080 assert_eq!(item["tally"], AttributeValue::N("15".into()));
1081 }
1082
1083 #[test]
1084 fn test_add_number_create() {
1085 let expr = parse("ADD tally :val").unwrap();
1086 let mut item = make_item(&[("pk", AttributeValue::S("k".into()))]);
1087 let av = vals(&[(":val", AttributeValue::N("1".into()))]);
1088 let no_names = None;
1089 let tracker = make_tracker(&no_names, &av);
1090 apply(&mut item, &expr, &tracker).unwrap();
1091 assert_eq!(item["tally"], AttributeValue::N("1".into()));
1092 }
1093
1094 #[test]
1095 fn test_add_string_set() {
1096 let expr = parse("ADD colors :new_colors").unwrap();
1097 let mut item = make_item(&[
1098 ("pk", AttributeValue::S("k".into())),
1099 (
1100 "colors",
1101 AttributeValue::SS(vec!["red".into(), "blue".into()]),
1102 ),
1103 ]);
1104 let av = vals(&[(
1105 ":new_colors",
1106 AttributeValue::SS(vec!["blue".into(), "green".into()]),
1107 )]);
1108 let no_names = None;
1109 let tracker = make_tracker(&no_names, &av);
1110 apply(&mut item, &expr, &tracker).unwrap();
1111 if let AttributeValue::SS(set) = &item["colors"] {
1112 assert_eq!(set.len(), 3); assert!(set.contains(&"green".to_string()));
1114 } else {
1115 panic!("Expected SS");
1116 }
1117 }
1118
1119 #[test]
1120 fn test_delete_string_set() {
1121 let expr = parse("DELETE colors :remove").unwrap();
1122 let mut item = make_item(&[
1123 ("pk", AttributeValue::S("k".into())),
1124 (
1125 "colors",
1126 AttributeValue::SS(vec!["red".into(), "blue".into(), "green".into()]),
1127 ),
1128 ]);
1129 let av = vals(&[(
1130 ":remove",
1131 AttributeValue::SS(vec!["blue".into(), "green".into()]),
1132 )]);
1133 let no_names = None;
1134 let tracker = make_tracker(&no_names, &av);
1135 apply(&mut item, &expr, &tracker).unwrap();
1136 if let AttributeValue::SS(set) = &item["colors"] {
1137 assert_eq!(set, &vec!["red".to_string()]);
1138 } else {
1139 panic!("Expected SS");
1140 }
1141 }
1142
1143 #[test]
1144 fn test_combined_set_remove() {
1145 let expr = parse("SET label = :name REMOVE old_attr").unwrap();
1146 assert_eq!(expr.set_actions.len(), 1);
1147 assert_eq!(expr.remove_actions.len(), 1);
1148 }
1149
1150 #[test]
1151 fn test_duplicate_clause_error() {
1152 let result = parse("SET a = :v SET b = :w");
1153 assert!(result.is_err());
1154 assert!(result.unwrap_err().contains("only be used once"));
1155 }
1156
1157 #[test]
1159 fn test_set_reads_pre_update_snapshot() {
1160 let expr = parse("SET a = :v, b = a").unwrap();
1161 let mut item = make_item(&[
1162 ("pk", AttributeValue::S("k".into())),
1163 ("a", AttributeValue::S("OLD".into())),
1164 ]);
1165 let av = vals(&[(":v", AttributeValue::S("NEW".into()))]);
1166 let no_names = None;
1167 let tracker = make_tracker(&no_names, &av);
1168 apply(&mut item, &expr, &tracker).unwrap();
1169 assert_eq!(item["a"], AttributeValue::S("NEW".into()));
1170 assert_eq!(item["b"], AttributeValue::S("OLD".into()));
1171 }
1172
1173 #[test]
1175 fn test_set_parenthesised_arithmetic() {
1176 let expr = parse("SET c = (c - :v)").unwrap();
1177 let mut item = make_item(&[
1178 ("pk", AttributeValue::S("k".into())),
1179 ("c", AttributeValue::N("10".into())),
1180 ]);
1181 let av = vals(&[(":v", AttributeValue::N("3".into()))]);
1182 let no_names = None;
1183 let tracker = make_tracker(&no_names, &av);
1184 apply(&mut item, &expr, &tracker).unwrap();
1185 assert_eq!(item["c"], AttributeValue::N("7".into()));
1186 }
1187
1188 #[test]
1190 fn test_set_parenthesised_arithmetic_bigdecimal() {
1191 let expr = parse("SET c = (c + :v)").unwrap();
1192 let mut item = make_item(&[
1193 ("pk", AttributeValue::S("k".into())),
1194 ("c", AttributeValue::N("100000000000000000000".into())),
1195 ]);
1196 let av = vals(&[(":v", AttributeValue::N("1".into()))]);
1197 let no_names = None;
1198 let tracker = make_tracker(&no_names, &av);
1199 apply(&mut item, &expr, &tracker).unwrap();
1200 assert_eq!(item["c"], AttributeValue::N("100000000000000000001".into()));
1201 }
1202}