1use crate::expressions::tokenizer::{Token, TokenStream, tokenize};
6use crate::expressions::{
7 PathElement, TrackedExpressionAttributes, resolve_path, resolve_path_elements,
8};
9use crate::types::AttributeValue;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
14pub enum ConditionExpr {
15 Comparison {
17 left: Operand,
18 op: CompOp,
19 right: Operand,
20 },
21 Between {
23 operand: Operand,
24 lo: Operand,
25 hi: Operand,
26 },
27 In {
29 operand: Operand,
30 values: Vec<Operand>,
31 },
32 AttributeExists(Vec<PathElement>),
34 AttributeNotExists(Vec<PathElement>),
36 AttributeType(Vec<PathElement>, Operand),
38 BeginsWith(Operand, Operand),
40 Contains(Operand, Operand),
42 And(Box<ConditionExpr>, Box<ConditionExpr>),
47 Or(Box<ConditionExpr>, Box<ConditionExpr>),
49 Not(Box<ConditionExpr>),
51}
52
53#[derive(Debug, Clone, PartialEq)]
55pub enum CompOp {
56 Eq,
57 Ne,
58 Lt,
59 Le,
60 Gt,
61 Ge,
62}
63
64#[derive(Debug, Clone)]
66pub enum Operand {
67 Path(Vec<PathElement>),
69 ValueRef(String),
71 Size(Vec<PathElement>),
73}
74
75pub fn parse(expr: &str) -> Result<ConditionExpr, String> {
80 let tokens = tokenize(expr).map_err(|e| e.to_string())?;
81 let mut stream = TokenStream::new(tokens);
82 let result = parse_or(&mut stream)?;
83 if !stream.at_end() {
84 return Err(format!(
85 "Syntax error; token: \"{}\"",
86 stream.peek().unwrap()
87 ));
88 }
89 Ok(result)
90}
91
92pub fn evaluate(
95 expr: &ConditionExpr,
96 item: &HashMap<String, AttributeValue>,
97 tracker: &TrackedExpressionAttributes,
98) -> Result<bool, String> {
99 match expr {
100 ConditionExpr::Comparison { left, op, right } => {
101 let lv = resolve_operand(left, item, tracker)?;
102 let rv = resolve_operand(right, item, tracker)?;
103 match (lv, rv) {
104 (Some(l), Some(r)) => Ok(compare_values(&l, op, &r)),
105 _ => Ok(false),
106 }
107 }
108
109 ConditionExpr::Between { operand, lo, hi } => {
110 let val = resolve_operand(operand, item, tracker)?;
111 let lo_val = resolve_operand(lo, item, tracker)?;
112 let hi_val = resolve_operand(hi, item, tracker)?;
113 match (val, lo_val, hi_val) {
114 (Some(v), Some(l), Some(h)) => {
115 Ok(compare_values(&v, &CompOp::Ge, &l) && compare_values(&v, &CompOp::Le, &h))
116 }
117 _ => Ok(false),
118 }
119 }
120
121 ConditionExpr::In { operand, values } => {
122 let val = resolve_operand(operand, item, tracker)?;
123 match val {
124 Some(v) => {
125 for candidate in values {
126 let cv = resolve_operand(candidate, item, tracker)?;
127 if let Some(c) = cv {
128 if compare_values(&v, &CompOp::Eq, &c) {
129 return Ok(true);
130 }
131 }
132 }
133 Ok(false)
134 }
135 None => Ok(false),
136 }
137 }
138
139 ConditionExpr::AttributeExists(path) => {
140 let resolved = resolve_path_elements(path, tracker)?;
141 Ok(resolve_path(item, &resolved).is_some())
142 }
143
144 ConditionExpr::AttributeNotExists(path) => {
145 let resolved = resolve_path_elements(path, tracker)?;
146 Ok(resolve_path(item, &resolved).is_none())
147 }
148
149 ConditionExpr::AttributeType(path, type_operand) => {
150 let resolved = resolve_path_elements(path, tracker)?;
151 let val = resolve_path(item, &resolved);
152 let type_val = resolve_operand(type_operand, item, tracker)?;
153 match (val, type_val) {
154 (Some(v), Some(AttributeValue::S(type_name))) => Ok(v.type_name() == type_name),
155 _ => Ok(false),
156 }
157 }
158
159 ConditionExpr::BeginsWith(path_op, prefix_op) => {
160 let val = resolve_operand(path_op, item, tracker)?;
161 let prefix = resolve_operand(prefix_op, item, tracker)?;
162 match (val, prefix) {
163 (Some(AttributeValue::S(s)), Some(AttributeValue::S(p))) => Ok(s.starts_with(&p)),
164 (Some(AttributeValue::B(b)), Some(AttributeValue::B(p))) => Ok(b.starts_with(&p)),
165 _ => Ok(false),
166 }
167 }
168
169 ConditionExpr::Contains(path_op, search_op) => {
170 let val = resolve_operand(path_op, item, tracker)?;
171 let search = resolve_operand(search_op, item, tracker)?;
172 match (val, search) {
173 (Some(AttributeValue::S(s)), Some(AttributeValue::S(sub))) => Ok(s.contains(&sub)),
174 (Some(AttributeValue::B(b)), Some(AttributeValue::B(sub))) => {
175 Ok(sub.is_empty() || b.windows(sub.len()).any(|w| w == sub.as_slice()))
176 }
177 (Some(AttributeValue::SS(set)), Some(AttributeValue::S(elem))) => {
178 Ok(set.contains(&elem))
179 }
180 (Some(AttributeValue::NS(set)), Some(AttributeValue::N(elem))) => {
181 Ok(set.contains(&elem))
182 }
183 (Some(AttributeValue::BS(set)), Some(AttributeValue::B(elem))) => {
184 Ok(set.contains(&elem))
185 }
186 (Some(AttributeValue::L(list)), Some(search_val)) => Ok(list
187 .iter()
188 .any(|v| compare_values(v, &CompOp::Eq, &search_val))),
189 _ => Ok(false),
190 }
191 }
192
193 ConditionExpr::And(left, right) => {
194 if !evaluate(left, item, tracker)? {
195 return Ok(false); }
197 evaluate(right, item, tracker)
198 }
199
200 ConditionExpr::Or(left, right) => {
201 if evaluate(left, item, tracker)? {
202 return Ok(true); }
204 evaluate(right, item, tracker)
205 }
206
207 ConditionExpr::Not(inner) => {
208 let v = evaluate(inner, item, tracker)?;
209 Ok(!v)
210 }
211 }
212}
213
214fn parse_or(stream: &mut TokenStream) -> Result<ConditionExpr, String> {
219 let mut left = parse_and(stream)?;
220 while matches!(stream.peek(), Some(Token::Or)) {
221 stream.next();
222 let right = parse_and(stream)?;
223 left = ConditionExpr::Or(Box::new(left), Box::new(right));
224 }
225 Ok(left)
226}
227
228fn parse_and(stream: &mut TokenStream) -> Result<ConditionExpr, String> {
229 let mut left = parse_not(stream)?;
230 while matches!(stream.peek(), Some(Token::And)) {
231 stream.next();
232 let right = parse_not(stream)?;
233 left = ConditionExpr::And(Box::new(left), Box::new(right));
234 }
235 Ok(left)
236}
237
238fn parse_not(stream: &mut TokenStream) -> Result<ConditionExpr, String> {
239 if matches!(stream.peek(), Some(Token::Not)) {
240 stream.next();
241 let inner = parse_not(stream)?;
242 return Ok(ConditionExpr::Not(Box::new(inner)));
243 }
244 parse_primary(stream)
245}
246
247fn parse_primary(stream: &mut TokenStream) -> Result<ConditionExpr, String> {
248 if matches!(stream.peek(), Some(Token::LParen)) {
250 stream.next();
251 let expr = parse_or(stream)?;
252 stream.expect(&Token::RParen)?;
253 return Ok(expr);
254 }
255
256 if let Some(Token::Identifier(name)) = stream.peek() {
258 let name_owned = name.clone();
259 let func_name = name_owned.to_lowercase();
260
261 let is_function_call = {
263 let saved = stream.pos();
264 stream.next(); let is_lparen = matches!(stream.peek(), Some(Token::LParen));
266 stream.set_pos(saved); is_lparen
268 };
269
270 if is_function_call {
271 match func_name.as_str() {
273 "attribute_exists" => {
274 stream.next();
275 stream.expect(&Token::LParen)?;
276 let path = parse_raw_path(stream)?;
277 stream.expect(&Token::RParen)?;
278 return Ok(ConditionExpr::AttributeExists(path));
279 }
280 "attribute_not_exists" => {
281 stream.next();
282 stream.expect(&Token::LParen)?;
283 let path = parse_raw_path(stream)?;
284 stream.expect(&Token::RParen)?;
285 return Ok(ConditionExpr::AttributeNotExists(path));
286 }
287 "attribute_type" => {
288 stream.next();
289 stream.expect(&Token::LParen)?;
290 let path = parse_raw_path(stream)?;
291 stream.expect(&Token::Comma)?;
292 let type_val = parse_operand(stream)?;
293 stream.expect(&Token::RParen)?;
294 return Ok(ConditionExpr::AttributeType(path, type_val));
295 }
296 "begins_with" => {
297 stream.next();
298 stream.expect(&Token::LParen)?;
299 let path_op = parse_operand(stream)?;
300 stream.expect(&Token::Comma)?;
301 let prefix_op = parse_operand(stream)?;
302 stream.expect(&Token::RParen)?;
303 return Ok(ConditionExpr::BeginsWith(path_op, prefix_op));
304 }
305 "contains" => {
306 stream.next();
307 stream.expect(&Token::LParen)?;
308 let path_op = parse_operand(stream)?;
309 stream.expect(&Token::Comma)?;
310 let search_op = parse_operand(stream)?;
311 stream.expect(&Token::RParen)?;
312 return Ok(ConditionExpr::Contains(path_op, search_op));
313 }
314 "size" => {
315 }
318 _ => {
319 return Err(format!("Invalid function name; function: {}", name_owned));
321 }
322 }
323 } else {
324 }
327 }
328
329 let left = parse_operand(stream)?;
331
332 match stream.peek() {
333 Some(Token::Eq) => {
334 stream.next();
335 let right = parse_operand(stream)?;
336 Ok(ConditionExpr::Comparison {
337 left,
338 op: CompOp::Eq,
339 right,
340 })
341 }
342 Some(Token::Ne) => {
343 stream.next();
344 let right = parse_operand(stream)?;
345 Ok(ConditionExpr::Comparison {
346 left,
347 op: CompOp::Ne,
348 right,
349 })
350 }
351 Some(Token::Lt) => {
352 stream.next();
353 let right = parse_operand(stream)?;
354 Ok(ConditionExpr::Comparison {
355 left,
356 op: CompOp::Lt,
357 right,
358 })
359 }
360 Some(Token::Le) => {
361 stream.next();
362 let right = parse_operand(stream)?;
363 Ok(ConditionExpr::Comparison {
364 left,
365 op: CompOp::Le,
366 right,
367 })
368 }
369 Some(Token::Gt) => {
370 stream.next();
371 let right = parse_operand(stream)?;
372 Ok(ConditionExpr::Comparison {
373 left,
374 op: CompOp::Gt,
375 right,
376 })
377 }
378 Some(Token::Ge) => {
379 stream.next();
380 let right = parse_operand(stream)?;
381 Ok(ConditionExpr::Comparison {
382 left,
383 op: CompOp::Ge,
384 right,
385 })
386 }
387 Some(Token::Between) => {
388 stream.next();
389 let lo = parse_operand(stream)?;
390 stream.expect(&Token::And)?;
391 let hi = parse_operand(stream)?;
392 Ok(ConditionExpr::Between {
393 operand: left,
394 lo,
395 hi,
396 })
397 }
398 Some(Token::In) => {
399 stream.next();
400 stream.expect(&Token::LParen)?;
401 let mut values = vec![parse_operand(stream)?];
402 while matches!(stream.peek(), Some(Token::Comma)) {
403 stream.next();
404 values.push(parse_operand(stream)?);
405 }
406 stream.expect(&Token::RParen)?;
407 Ok(ConditionExpr::In {
408 operand: left,
409 values,
410 })
411 }
412 _ => Err("Expected comparison operator, BETWEEN, or IN".to_string()),
413 }
414}
415
416fn parse_operand(stream: &mut TokenStream) -> Result<Operand, String> {
418 if let Some(Token::Identifier(name)) = stream.peek() {
420 if name.to_lowercase() == "size" {
421 stream.next();
422 stream.expect(&Token::LParen)?;
423 let path = parse_raw_path(stream)?;
424 stream.expect(&Token::RParen)?;
425 return Ok(Operand::Size(path));
426 }
427 }
428
429 match stream.peek() {
430 Some(Token::ValueRef(_)) => {
431 if let Some(Token::ValueRef(name)) = stream.next().cloned() {
432 Ok(Operand::ValueRef(name))
433 } else {
434 unreachable!()
435 }
436 }
437 Some(Token::Identifier(_)) | Some(Token::NameRef(_)) => {
438 let path = parse_raw_path(stream)?;
439 Ok(Operand::Path(path))
440 }
441 Some(t) => Err(format!("Expected operand, got {t}")),
442 None => Err("Expected operand, got end of expression".to_string()),
443 }
444}
445
446pub fn parse_raw_path(stream: &mut TokenStream) -> Result<Vec<PathElement>, String> {
449 let first = match stream.next() {
450 Some(Token::Identifier(name)) => {
451 if super::reserved::is_reserved_keyword(name) {
452 return Err(format!(
453 "Attribute name is a reserved keyword; reserved keyword: {name}"
454 ));
455 }
456 PathElement::Attribute(name.clone())
457 }
458 Some(Token::NameRef(name)) => PathElement::Attribute(name.clone()),
459 Some(t) => return Err(format!("Expected attribute name, got {t}")),
460 None => return Err("Expected attribute name, got end of expression".to_string()),
461 };
462
463 let mut path = vec![first];
464
465 loop {
466 match stream.peek() {
467 Some(Token::Dot) => {
468 stream.next();
469 match stream.next() {
470 Some(Token::Identifier(name)) => {
471 if super::reserved::is_reserved_keyword(name) {
472 return Err(format!(
473 "Attribute name is a reserved keyword; reserved keyword: {name}"
474 ));
475 }
476 path.push(PathElement::Attribute(name.clone()));
477 }
478 Some(Token::NameRef(name)) => {
479 path.push(PathElement::Attribute(name.clone()));
480 }
481 Some(t) => return Err(format!("Expected attribute name after '.', got {t}")),
482 None => return Err("Expected attribute name after '.'".to_string()),
483 }
484 }
485 Some(Token::LBracket) => {
486 stream.next();
487 match stream.next() {
488 Some(Token::Number(n)) => {
489 let idx: usize = n.parse().map_err(|_| format!("Invalid index: {n}"))?;
490 path.push(PathElement::Index(idx));
491 }
492 Some(t) => return Err(format!("Expected number in brackets, got {t}")),
493 None => return Err("Expected number in brackets".to_string()),
494 }
495 stream.expect(&Token::RBracket)?;
496 }
497 _ => break,
498 }
499 }
500
501 Ok(path)
502}
503
504fn resolve_operand(
510 operand: &Operand,
511 item: &HashMap<String, AttributeValue>,
512 tracker: &TrackedExpressionAttributes,
513) -> Result<Option<AttributeValue>, String> {
514 match operand {
515 Operand::Path(path) => {
516 let resolved = resolve_path_elements(path, tracker)?;
517 Ok(resolve_path(item, &resolved))
518 }
519 Operand::ValueRef(name) => {
520 let val = tracker.resolve_value(name)?;
521 Ok(Some(val.clone()))
522 }
523 Operand::Size(path) => {
524 let resolved = resolve_path_elements(path, tracker)?;
525 match resolve_path(item, &resolved) {
526 Some(val) => {
527 let size = match &val {
528 AttributeValue::S(s) => s.len(),
529 AttributeValue::B(b) => b.len(),
530 AttributeValue::SS(set) => set.len(),
531 AttributeValue::NS(set) => set.len(),
532 AttributeValue::BS(set) => set.len(),
533 AttributeValue::L(list) => list.len(),
534 AttributeValue::M(map) => map.len(),
535 _ => return Ok(None),
538 };
539 Ok(Some(AttributeValue::N(size.to_string())))
540 }
541 None => Ok(None),
542 }
543 }
544 }
545}
546
547fn compare_values(left: &AttributeValue, op: &CompOp, right: &AttributeValue) -> bool {
553 match (left, right) {
554 (AttributeValue::S(a), AttributeValue::S(b)) => compare_ord(a, b, op),
556
557 (AttributeValue::N(a), AttributeValue::N(b)) => {
559 if can_use_f64(a) && can_use_f64(b) {
561 if let (Ok(fa), Ok(fb)) = (a.parse::<f64>(), b.parse::<f64>()) {
562 if fa.is_finite() && fb.is_finite() {
563 return compare_ord(&fa, &fb, op);
564 }
565 }
566 }
567 use bigdecimal::BigDecimal;
569 use std::str::FromStr;
570 match (BigDecimal::from_str(a), BigDecimal::from_str(b)) {
571 (Ok(da), Ok(db)) => compare_ord(&da, &db, op),
572 _ => false,
573 }
574 }
575
576 (AttributeValue::B(a), AttributeValue::B(b)) => compare_ord(a, b, op),
578
579 (AttributeValue::BOOL(a), AttributeValue::BOOL(b)) => match op {
581 CompOp::Eq => a == b,
582 CompOp::Ne => a != b,
583 _ => false,
584 },
585
586 (AttributeValue::NULL(a), AttributeValue::NULL(b)) => match op {
588 CompOp::Eq => a == b,
589 CompOp::Ne => a != b,
590 _ => false,
591 },
592
593 (AttributeValue::SS(a), AttributeValue::SS(b)) => {
595 let mut sa = a.clone();
596 let mut sb = b.clone();
597 sa.sort();
598 sb.sort();
599 match op {
600 CompOp::Eq => sa == sb,
601 CompOp::Ne => sa != sb,
602 _ => false,
603 }
604 }
605
606 (AttributeValue::NS(a), AttributeValue::NS(b)) => {
608 if a.len() != b.len() {
609 return matches!(op, CompOp::Ne);
610 }
611 let mut fa: Vec<f64> = match a.iter().map(|n| n.parse::<f64>()).collect() {
612 Ok(v) => v,
613 Err(_) => return false,
614 };
615 let mut fb: Vec<f64> = match b.iter().map(|n| n.parse::<f64>()).collect() {
616 Ok(v) => v,
617 Err(_) => return false,
618 };
619 fa.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
620 fb.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
621 match op {
622 CompOp::Eq => fa == fb,
623 CompOp::Ne => fa != fb,
624 _ => false,
625 }
626 }
627
628 (AttributeValue::BS(a), AttributeValue::BS(b)) => {
630 let mut sa = a.clone();
631 let mut sb = b.clone();
632 sa.sort();
633 sb.sort();
634 match op {
635 CompOp::Eq => sa == sb,
636 CompOp::Ne => sa != sb,
637 _ => false,
638 }
639 }
640
641 _ => matches!(op, CompOp::Ne),
643 }
644}
645
646fn compare_ord<T: PartialOrd>(a: &T, b: &T, op: &CompOp) -> bool {
647 match op {
648 CompOp::Eq => a == b,
649 CompOp::Ne => a != b,
650 CompOp::Lt => a < b,
651 CompOp::Le => a <= b,
652 CompOp::Gt => a > b,
653 CompOp::Ge => a >= b,
654 }
655}
656
657pub fn track_references(
660 expr: &ConditionExpr,
661 tracker: &TrackedExpressionAttributes,
662) -> Result<(), String> {
663 match expr {
664 ConditionExpr::Comparison { left, op: _, right } => {
665 track_operand_refs(left, tracker)?;
666 track_operand_refs(right, tracker)
667 }
668 ConditionExpr::Between { operand, lo, hi } => {
669 track_operand_refs(operand, tracker)?;
670 track_operand_refs(lo, tracker)?;
671 track_operand_refs(hi, tracker)
672 }
673 ConditionExpr::In { operand, values } => {
674 track_operand_refs(operand, tracker)?;
675 for v in values {
676 track_operand_refs(v, tracker)?;
677 }
678 Ok(())
679 }
680 ConditionExpr::AttributeExists(path) | ConditionExpr::AttributeNotExists(path) => {
681 track_cond_path_refs(path, tracker)
682 }
683 ConditionExpr::AttributeType(path, type_op) => {
684 track_cond_path_refs(path, tracker)?;
685 track_operand_refs(type_op, tracker)
686 }
687 ConditionExpr::BeginsWith(a, b) | ConditionExpr::Contains(a, b) => {
688 track_operand_refs(a, tracker)?;
689 track_operand_refs(b, tracker)
690 }
691 ConditionExpr::And(left, right) | ConditionExpr::Or(left, right) => {
692 track_references(left, tracker)?;
693 track_references(right, tracker)
694 }
695 ConditionExpr::Not(inner) => track_references(inner, tracker),
696 }
697}
698
699fn track_operand_refs(
700 operand: &Operand,
701 tracker: &TrackedExpressionAttributes,
702) -> Result<(), String> {
703 match operand {
704 Operand::Path(path) => track_cond_path_refs(path, tracker),
705 Operand::ValueRef(name) => {
706 tracker.resolve_value(name)?;
707 Ok(())
708 }
709 Operand::Size(path) => track_cond_path_refs(path, tracker),
710 }
711}
712
713fn track_cond_path_refs(
714 path: &[PathElement],
715 tracker: &TrackedExpressionAttributes,
716) -> Result<(), String> {
717 for elem in path {
718 if let PathElement::Attribute(name) = elem {
719 if name.starts_with('#') {
720 tracker.resolve_name(name)?;
721 }
722 }
723 }
724 Ok(())
725}
726
727pub fn validate_static(
735 expr: &ConditionExpr,
736 values: &Option<HashMap<String, AttributeValue>>,
737) -> Result<(), String> {
738 match expr {
739 ConditionExpr::Between { operand: _, lo, hi } => {
740 if let (Operand::ValueRef(lo_name), Operand::ValueRef(hi_name)) = (lo, hi) {
742 if let Some(vals) = values {
743 let lo_val = vals.get(lo_name.as_str());
744 let hi_val = vals.get(hi_name.as_str());
745 if let (Some(lo_v), Some(hi_v)) = (lo_val, hi_val) {
746 if std::mem::discriminant(lo_v) != std::mem::discriminant(hi_v) {
748 return Err(format!(
749 "Invalid ConditionExpression: The BETWEEN operator requires same data type for lower and upper bounds; \
750 lower bound operand: AttributeValue: {{{}}}, upper bound operand: AttributeValue: {{{}}}",
751 format_av_for_error(lo_v),
752 format_av_for_error(hi_v),
753 ));
754 }
755 if compare_values(lo_v, &CompOp::Gt, hi_v) {
757 return Err(format!(
758 "Invalid ConditionExpression: The BETWEEN operator requires upper bound to be greater than or equal to lower bound; \
759 lower bound operand: AttributeValue: {{{}}}, upper bound operand: AttributeValue: {{{}}}",
760 format_av_for_error(lo_v),
761 format_av_for_error(hi_v),
762 ));
763 }
764 }
765 }
766 }
767 Ok(())
768 }
769 ConditionExpr::And(left, right) | ConditionExpr::Or(left, right) => {
770 validate_static(left, values)?;
771 validate_static(right, values)
772 }
773 ConditionExpr::Not(inner) => validate_static(inner, values),
774 _ => Ok(()),
775 }
776}
777
778fn format_av_for_error(av: &AttributeValue) -> String {
780 match av {
781 AttributeValue::S(s) => format!("S:{s}"),
782 AttributeValue::N(n) => format!("N:{n}"),
783 AttributeValue::B(b) => {
784 use base64::Engine;
785 format!("B:{}", base64::engine::general_purpose::STANDARD.encode(b))
786 }
787 AttributeValue::BOOL(b) => format!("BOOL:{b}"),
788 AttributeValue::NULL(_) => "NULL:true".to_string(),
789 AttributeValue::SS(set) => format!("SS:{set:?}"),
790 AttributeValue::NS(set) => format!("NS:{set:?}"),
791 AttributeValue::BS(_) => "BS:[...]".to_string(),
792 AttributeValue::L(_) => "L:[...]".to_string(),
793 AttributeValue::M(_) => "M:{...}".to_string(),
794 }
795}
796
797pub fn check_non_scalar_key_access(
805 expr: &ConditionExpr,
806 attr_names: &Option<HashMap<String, String>>,
807 key_attrs: &[String],
808 index_key_attrs: &[String],
809) -> Option<(String, bool)> {
810 let mut result = None;
812 check_non_scalar_key_access_inner(expr, attr_names, key_attrs, index_key_attrs, &mut result);
813 result
814}
815
816fn check_non_scalar_key_access_inner(
817 expr: &ConditionExpr,
818 attr_names: &Option<HashMap<String, String>>,
819 key_attrs: &[String],
820 index_key_attrs: &[String],
821 result: &mut Option<(String, bool)>,
822) {
823 if result.is_some() {
824 return;
825 }
826 match expr {
827 ConditionExpr::Comparison { left, right, .. } => {
828 check_operand_non_scalar(left, attr_names, key_attrs, index_key_attrs, result);
829 check_operand_non_scalar(right, attr_names, key_attrs, index_key_attrs, result);
830 }
831 ConditionExpr::Between { operand, lo, hi } => {
832 check_operand_non_scalar(operand, attr_names, key_attrs, index_key_attrs, result);
833 check_operand_non_scalar(lo, attr_names, key_attrs, index_key_attrs, result);
834 check_operand_non_scalar(hi, attr_names, key_attrs, index_key_attrs, result);
835 }
836 ConditionExpr::In { operand, values } => {
837 check_operand_non_scalar(operand, attr_names, key_attrs, index_key_attrs, result);
838 for v in values {
839 check_operand_non_scalar(v, attr_names, key_attrs, index_key_attrs, result);
840 }
841 }
842 ConditionExpr::AttributeExists(path) | ConditionExpr::AttributeNotExists(path) => {
843 check_path_non_scalar(path, attr_names, key_attrs, index_key_attrs, result);
844 }
845 ConditionExpr::AttributeType(path, _) => {
846 check_path_non_scalar(path, attr_names, key_attrs, index_key_attrs, result);
847 }
848 ConditionExpr::BeginsWith(a, b) | ConditionExpr::Contains(a, b) => {
849 check_operand_non_scalar(a, attr_names, key_attrs, index_key_attrs, result);
850 check_operand_non_scalar(b, attr_names, key_attrs, index_key_attrs, result);
851 }
852 ConditionExpr::And(a, b) | ConditionExpr::Or(a, b) => {
853 check_non_scalar_key_access_inner(a, attr_names, key_attrs, index_key_attrs, result);
854 check_non_scalar_key_access_inner(b, attr_names, key_attrs, index_key_attrs, result);
855 }
856 ConditionExpr::Not(inner) => {
857 check_non_scalar_key_access_inner(
858 inner,
859 attr_names,
860 key_attrs,
861 index_key_attrs,
862 result,
863 );
864 }
865 }
866}
867
868fn check_operand_non_scalar(
869 operand: &Operand,
870 attr_names: &Option<HashMap<String, String>>,
871 key_attrs: &[String],
872 index_key_attrs: &[String],
873 result: &mut Option<(String, bool)>,
874) {
875 if result.is_some() {
876 return;
877 }
878 match operand {
879 Operand::Path(path) | Operand::Size(path) => {
880 check_path_non_scalar(path, attr_names, key_attrs, index_key_attrs, result);
881 }
882 Operand::ValueRef(_) => {}
883 }
884}
885
886fn check_path_non_scalar(
887 path: &[PathElement],
888 attr_names: &Option<HashMap<String, String>>,
889 key_attrs: &[String],
890 index_key_attrs: &[String],
891 result: &mut Option<(String, bool)>,
892) {
893 if result.is_some() || path.len() <= 1 {
894 return; }
896 if let Some(name) = resolve_top_level_path(path, attr_names) {
897 if key_attrs.contains(&name) {
898 *result = Some((name, false));
899 } else if index_key_attrs.contains(&name) {
900 *result = Some((name, true));
901 }
902 }
903}
904
905pub fn extract_top_level_attributes(
911 expr: &ConditionExpr,
912 attr_names: &Option<HashMap<String, String>>,
913) -> Vec<String> {
914 let mut attrs = Vec::new();
915 collect_top_level_attrs(expr, attr_names, &mut attrs);
916 attrs.sort();
917 attrs.dedup();
918 attrs
919}
920
921fn collect_top_level_attrs(
922 expr: &ConditionExpr,
923 attr_names: &Option<HashMap<String, String>>,
924 out: &mut Vec<String>,
925) {
926 match expr {
927 ConditionExpr::Comparison { left, right, .. } => {
928 collect_operand_top_attr(left, attr_names, out);
929 collect_operand_top_attr(right, attr_names, out);
930 }
931 ConditionExpr::Between { operand, lo, hi } => {
932 collect_operand_top_attr(operand, attr_names, out);
933 collect_operand_top_attr(lo, attr_names, out);
934 collect_operand_top_attr(hi, attr_names, out);
935 }
936 ConditionExpr::In { operand, values } => {
937 collect_operand_top_attr(operand, attr_names, out);
938 for v in values {
939 collect_operand_top_attr(v, attr_names, out);
940 }
941 }
942 ConditionExpr::AttributeExists(path) | ConditionExpr::AttributeNotExists(path) => {
943 if let Some(name) = resolve_top_level_path(path, attr_names) {
944 out.push(name);
945 }
946 }
947 ConditionExpr::AttributeType(path, _) => {
948 if let Some(name) = resolve_top_level_path(path, attr_names) {
949 out.push(name);
950 }
951 }
952 ConditionExpr::BeginsWith(a, b) | ConditionExpr::Contains(a, b) => {
953 collect_operand_top_attr(a, attr_names, out);
954 collect_operand_top_attr(b, attr_names, out);
955 }
956 ConditionExpr::And(a, b) | ConditionExpr::Or(a, b) => {
957 collect_top_level_attrs(a, attr_names, out);
958 collect_top_level_attrs(b, attr_names, out);
959 }
960 ConditionExpr::Not(inner) => {
961 collect_top_level_attrs(inner, attr_names, out);
962 }
963 }
964}
965
966fn collect_operand_top_attr(
967 operand: &Operand,
968 attr_names: &Option<HashMap<String, String>>,
969 out: &mut Vec<String>,
970) {
971 match operand {
972 Operand::Path(path) => {
973 if let Some(name) = resolve_top_level_path(path, attr_names) {
974 out.push(name);
975 }
976 }
977 Operand::Size(path) => {
978 if let Some(name) = resolve_top_level_path(path, attr_names) {
979 out.push(name);
980 }
981 }
982 Operand::ValueRef(_) => {}
983 }
984}
985
986fn resolve_top_level_path(
987 path: &[PathElement],
988 attr_names: &Option<HashMap<String, String>>,
989) -> Option<String> {
990 match path.first() {
991 Some(PathElement::Attribute(name)) => {
992 if name.starts_with('#') {
993 attr_names
994 .as_ref()
995 .and_then(|m| m.get(name.as_str()))
996 .cloned()
997 } else {
998 Some(name.clone())
999 }
1000 }
1001 _ => None,
1002 }
1003}
1004
1005pub fn validate_name_refs(
1009 expr: &ConditionExpr,
1010 attr_names: &Option<HashMap<String, String>>,
1011) -> Result<(), String> {
1012 let mut undefined = Vec::new();
1013 collect_undefined_name_refs(expr, attr_names, &mut undefined);
1014 if let Some(name) = undefined.first() {
1015 Err(format!(
1016 "An expression attribute name used in the document path is not defined; attribute name: {}",
1017 name
1018 ))
1019 } else {
1020 Ok(())
1021 }
1022}
1023
1024fn collect_undefined_name_refs(
1025 expr: &ConditionExpr,
1026 attr_names: &Option<HashMap<String, String>>,
1027 out: &mut Vec<String>,
1028) {
1029 match expr {
1030 ConditionExpr::Comparison { left, right, .. } => {
1031 collect_operand_undefined_refs(left, attr_names, out);
1032 collect_operand_undefined_refs(right, attr_names, out);
1033 }
1034 ConditionExpr::Between { operand, lo, hi } => {
1035 collect_operand_undefined_refs(operand, attr_names, out);
1036 collect_operand_undefined_refs(lo, attr_names, out);
1037 collect_operand_undefined_refs(hi, attr_names, out);
1038 }
1039 ConditionExpr::In { operand, values } => {
1040 collect_operand_undefined_refs(operand, attr_names, out);
1041 for v in values {
1042 collect_operand_undefined_refs(v, attr_names, out);
1043 }
1044 }
1045 ConditionExpr::AttributeExists(path) | ConditionExpr::AttributeNotExists(path) => {
1046 collect_path_undefined_refs(path, attr_names, out);
1047 }
1048 ConditionExpr::AttributeType(path, operand) => {
1049 collect_path_undefined_refs(path, attr_names, out);
1050 collect_operand_undefined_refs(operand, attr_names, out);
1051 }
1052 ConditionExpr::BeginsWith(a, b) | ConditionExpr::Contains(a, b) => {
1053 collect_operand_undefined_refs(a, attr_names, out);
1054 collect_operand_undefined_refs(b, attr_names, out);
1055 }
1056 ConditionExpr::And(a, b) | ConditionExpr::Or(a, b) => {
1057 collect_undefined_name_refs(a, attr_names, out);
1058 collect_undefined_name_refs(b, attr_names, out);
1059 }
1060 ConditionExpr::Not(inner) => {
1061 collect_undefined_name_refs(inner, attr_names, out);
1062 }
1063 }
1064}
1065
1066fn collect_operand_undefined_refs(
1067 operand: &Operand,
1068 attr_names: &Option<HashMap<String, String>>,
1069 out: &mut Vec<String>,
1070) {
1071 match operand {
1072 Operand::Path(path) | Operand::Size(path) => {
1073 collect_path_undefined_refs(path, attr_names, out);
1074 }
1075 Operand::ValueRef(_) => {}
1076 }
1077}
1078
1079fn collect_path_undefined_refs(
1080 path: &[PathElement],
1081 attr_names: &Option<HashMap<String, String>>,
1082 out: &mut Vec<String>,
1083) {
1084 for elem in path {
1085 if let PathElement::Attribute(name) = elem {
1086 if name.starts_with('#') {
1087 let defined = attr_names
1088 .as_ref()
1089 .is_some_and(|m| m.contains_key(name.as_str()));
1090 if !defined && !out.contains(name) {
1091 out.push(name.clone());
1092 }
1093 }
1094 }
1095 }
1096}
1097
1098fn can_use_f64(s: &str) -> bool {
1102 if s.contains('E') || s.contains('e') {
1104 return false;
1105 }
1106 let digit_count = s.bytes().filter(|b| b.is_ascii_digit()).count();
1109 digit_count <= 15
1110}
1111
1112#[cfg(test)]
1113mod tests {
1114 use super::*;
1115 use crate::expressions::evaluate_without_tracking;
1116
1117 fn make_item(pairs: &[(&str, AttributeValue)]) -> HashMap<String, AttributeValue> {
1118 pairs
1119 .iter()
1120 .map(|(k, v)| (k.to_string(), v.clone()))
1121 .collect()
1122 }
1123
1124 fn vals(pairs: &[(&str, AttributeValue)]) -> Option<HashMap<String, AttributeValue>> {
1125 Some(make_item(pairs))
1126 }
1127
1128 fn names(pairs: &[(&str, &str)]) -> Option<HashMap<String, String>> {
1129 Some(
1130 pairs
1131 .iter()
1132 .map(|(k, v)| (k.to_string(), v.to_string()))
1133 .collect(),
1134 )
1135 }
1136
1137 #[test]
1138 fn test_simple_equality() {
1139 let expr = parse("pk = :val").unwrap();
1140 let item = make_item(&[("pk", AttributeValue::S("hello".into()))]);
1141 let av = vals(&[(":val", AttributeValue::S("hello".into()))]);
1142 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1143 }
1144
1145 #[test]
1146 fn test_inequality() {
1147 let expr = parse("pk <> :val").unwrap();
1148 let item = make_item(&[("pk", AttributeValue::S("hello".into()))]);
1149 let av = vals(&[(":val", AttributeValue::S("world".into()))]);
1150 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1151 }
1152
1153 #[test]
1154 fn test_numeric_comparison() {
1155 let expr = parse("price > :min").unwrap();
1156 let item = make_item(&[("price", AttributeValue::N("42".into()))]);
1157 let av = vals(&[(":min", AttributeValue::N("10".into()))]);
1158 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1159 }
1160
1161 #[test]
1162 fn test_between() {
1163 let expr = parse("age BETWEEN :lo AND :hi").unwrap();
1164 let item = make_item(&[("age", AttributeValue::N("25".into()))]);
1165 let av = vals(&[
1166 (":lo", AttributeValue::N("18".into())),
1167 (":hi", AttributeValue::N("65".into())),
1168 ]);
1169 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1170 }
1171
1172 #[test]
1173 fn test_in_operator() {
1174 let expr = parse("state_val IN (:s1, :s2, :s3)").unwrap();
1175 let item = make_item(&[("state_val", AttributeValue::S("active".into()))]);
1176 let av = vals(&[
1177 (":s1", AttributeValue::S("active".into())),
1178 (":s2", AttributeValue::S("pending".into())),
1179 (":s3", AttributeValue::S("closed".into())),
1180 ]);
1181 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1182 }
1183
1184 #[test]
1185 fn test_attribute_exists() {
1186 let expr = parse("attribute_exists(email)").unwrap();
1187 let item = make_item(&[("email", AttributeValue::S("a@b.com".into()))]);
1188 assert!(evaluate_without_tracking(&expr, &item, &None, &None).unwrap());
1189
1190 let empty_item: HashMap<String, AttributeValue> = HashMap::new();
1191 assert!(!evaluate_without_tracking(&expr, &empty_item, &None, &None).unwrap());
1192 }
1193
1194 #[test]
1195 fn test_attribute_not_exists() {
1196 let expr = parse("attribute_not_exists(email)").unwrap();
1197 let item: HashMap<String, AttributeValue> = HashMap::new();
1198 assert!(evaluate_without_tracking(&expr, &item, &None, &None).unwrap());
1199 }
1200
1201 #[test]
1202 fn test_begins_with() {
1203 let expr = parse("begins_with(sk, :prefix)").unwrap();
1204 let item = make_item(&[("sk", AttributeValue::S("user#123".into()))]);
1205 let av = vals(&[(":prefix", AttributeValue::S("user#".into()))]);
1206 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1207 }
1208
1209 #[test]
1210 fn test_contains_string() {
1211 let expr = parse("contains(description, :sub)").unwrap();
1212 let item = make_item(&[("description", AttributeValue::S("hello world".into()))]);
1213 let av = vals(&[(":sub", AttributeValue::S("world".into()))]);
1214 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1215 }
1216
1217 #[test]
1218 fn test_contains_string_set() {
1219 let expr = parse("contains(tags, :tag)").unwrap();
1220 let item = make_item(&[(
1221 "tags",
1222 AttributeValue::SS(vec!["rust".into(), "dynamo".into()]),
1223 )]);
1224 let av = vals(&[(":tag", AttributeValue::S("rust".into()))]);
1225 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1226 }
1227
1228 #[test]
1229 fn test_size_function() {
1230 let expr = parse("size(label) > :len").unwrap();
1231 let item = make_item(&[("label", AttributeValue::S("Alice".into()))]);
1232 let av = vals(&[(":len", AttributeValue::N("3".into()))]);
1233 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1234 }
1235
1236 #[test]
1237 fn test_and_operator() {
1238 let expr = parse("price > :min AND price < :max").unwrap();
1239 let item = make_item(&[("price", AttributeValue::N("50".into()))]);
1240 let av = vals(&[
1241 (":min", AttributeValue::N("10".into())),
1242 (":max", AttributeValue::N("100".into())),
1243 ]);
1244 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1245 }
1246
1247 #[test]
1248 fn test_or_operator() {
1249 let expr = parse("state_val = :s1 OR state_val = :s2").unwrap();
1250 let item = make_item(&[("state_val", AttributeValue::S("pending".into()))]);
1251 let av = vals(&[
1252 (":s1", AttributeValue::S("active".into())),
1253 (":s2", AttributeValue::S("pending".into())),
1254 ]);
1255 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1256 }
1257
1258 #[test]
1259 fn test_not_operator() {
1260 let expr = parse("NOT state_val = :val").unwrap();
1261 let item = make_item(&[("state_val", AttributeValue::S("active".into()))]);
1262 let av = vals(&[(":val", AttributeValue::S("closed".into()))]);
1263 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1264 }
1265
1266 #[test]
1267 fn test_expression_attribute_names() {
1268 let expr = parse("#s = :val").unwrap();
1269 let item = make_item(&[("status", AttributeValue::S("active".into()))]);
1270 let an = names(&[("#s", "status")]);
1271 let av = vals(&[(":val", AttributeValue::S("active".into()))]);
1272 assert!(evaluate_without_tracking(&expr, &item, &an, &av).unwrap());
1273 }
1274
1275 #[test]
1276 fn test_nested_path() {
1277 let expr = parse("profile.label = :val").unwrap();
1278 let mut nested = HashMap::new();
1279 nested.insert("label".to_string(), AttributeValue::S("Alice".into()));
1280 let item = make_item(&[("profile", AttributeValue::M(nested))]);
1281 let av = vals(&[(":val", AttributeValue::S("Alice".into()))]);
1282 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1283 }
1284
1285 #[test]
1286 fn test_parenthesized() {
1287 let expr = parse("(a = :x OR b = :y) AND c = :z").unwrap();
1288 let item = make_item(&[
1289 ("a", AttributeValue::S("1".into())),
1290 ("b", AttributeValue::S("2".into())),
1291 ("c", AttributeValue::S("3".into())),
1292 ]);
1293 let av = vals(&[
1294 (":x", AttributeValue::S("wrong".into())),
1295 (":y", AttributeValue::S("2".into())),
1296 (":z", AttributeValue::S("3".into())),
1297 ]);
1298 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1299 }
1300
1301 #[test]
1302 fn test_missing_attribute_is_false() {
1303 let expr = parse("nonexistent = :val").unwrap();
1304 let item: HashMap<String, AttributeValue> = HashMap::new();
1305 let av = vals(&[(":val", AttributeValue::S("x".into()))]);
1306 assert!(!evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1307 }
1308}