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