1use crate::expressions::tokenizer::{Token, TokenStream, check_redundant_parens, 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, PartialEq)]
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 check_redundant_parens(&tokens)?;
82 let mut stream = TokenStream::new(tokens);
83 let result = parse_or(&mut stream)?;
84 if !stream.at_end() {
85 return Err(format!(
86 "Syntax error; token: \"{}\"",
87 stream.peek().unwrap()
88 ));
89 }
90 Ok(result)
91}
92
93pub fn evaluate(
96 expr: &ConditionExpr,
97 item: &HashMap<String, AttributeValue>,
98 tracker: &TrackedExpressionAttributes,
99) -> Result<bool, String> {
100 match expr {
101 ConditionExpr::Comparison { left, op, right } => {
102 let lv = resolve_operand(left, item, tracker)?;
103 let rv = resolve_operand(right, item, tracker)?;
104 match (lv, rv) {
105 (Some(l), Some(r)) => Ok(compare_values(&l, op, &r)),
106 _ => Ok(matches!(op, CompOp::Ne)),
111 }
112 }
113
114 ConditionExpr::Between { operand, lo, hi } => {
115 let val = resolve_operand(operand, item, tracker)?;
116 let lo_val = resolve_operand(lo, item, tracker)?;
117 let hi_val = resolve_operand(hi, item, tracker)?;
118 match (val, lo_val, hi_val) {
119 (Some(v), Some(l), Some(h)) => {
120 Ok(compare_values(&v, &CompOp::Ge, &l) && compare_values(&v, &CompOp::Le, &h))
121 }
122 _ => Ok(false),
123 }
124 }
125
126 ConditionExpr::In { operand, values } => {
127 let val = resolve_operand(operand, item, tracker)?;
128 match val {
129 Some(v) => {
130 for candidate in values {
131 let cv = resolve_operand(candidate, item, tracker)?;
132 if let Some(c) = cv {
133 if compare_values(&v, &CompOp::Eq, &c) {
134 return Ok(true);
135 }
136 }
137 }
138 Ok(false)
139 }
140 None => Ok(false),
141 }
142 }
143
144 ConditionExpr::AttributeExists(path) => {
145 let resolved = resolve_path_elements(path, tracker)?;
146 Ok(resolve_path(item, &resolved).is_some())
147 }
148
149 ConditionExpr::AttributeNotExists(path) => {
150 let resolved = resolve_path_elements(path, tracker)?;
151 Ok(resolve_path(item, &resolved).is_none())
152 }
153
154 ConditionExpr::AttributeType(path, type_operand) => {
155 let resolved = resolve_path_elements(path, tracker)?;
156 let val = resolve_path(item, &resolved);
157 let type_val = resolve_operand(type_operand, item, tracker)?;
158 match (val, type_val) {
159 (Some(v), Some(AttributeValue::S(type_name))) => Ok(v.type_name() == type_name),
160 _ => Ok(false),
161 }
162 }
163
164 ConditionExpr::BeginsWith(path_op, prefix_op) => {
165 let val = resolve_operand(path_op, item, tracker)?;
166 let prefix = resolve_operand(prefix_op, item, tracker)?;
167 match (val, prefix) {
168 (Some(AttributeValue::S(s)), Some(AttributeValue::S(p))) => Ok(s.starts_with(&p)),
169 (Some(AttributeValue::B(b)), Some(AttributeValue::B(p))) => Ok(b.starts_with(&p)),
170 _ => Ok(false),
171 }
172 }
173
174 ConditionExpr::Contains(path_op, search_op) => {
175 let val = resolve_operand(path_op, item, tracker)?;
176 let search = resolve_operand(search_op, item, tracker)?;
177 match (val, search) {
178 (Some(AttributeValue::S(s)), Some(AttributeValue::S(sub))) => Ok(s.contains(&sub)),
179 (Some(AttributeValue::B(b)), Some(AttributeValue::B(sub))) => {
180 Ok(sub.is_empty() || b.windows(sub.len()).any(|w| w == sub.as_slice()))
181 }
182 (Some(AttributeValue::SS(set)), Some(AttributeValue::S(elem))) => {
183 Ok(set.contains(&elem))
184 }
185 (Some(AttributeValue::NS(set)), Some(AttributeValue::N(elem))) => {
186 Ok(set.contains(&elem))
187 }
188 (Some(AttributeValue::BS(set)), Some(AttributeValue::B(elem))) => {
189 Ok(set.contains(&elem))
190 }
191 (Some(AttributeValue::L(list)), Some(search_val)) => Ok(list
192 .iter()
193 .any(|v| compare_values(v, &CompOp::Eq, &search_val))),
194 _ => Ok(false),
195 }
196 }
197
198 ConditionExpr::And(left, right) => {
199 if !evaluate(left, item, tracker)? {
200 return Ok(false); }
202 evaluate(right, item, tracker)
203 }
204
205 ConditionExpr::Or(left, right) => {
206 if evaluate(left, item, tracker)? {
207 return Ok(true); }
209 evaluate(right, item, tracker)
210 }
211
212 ConditionExpr::Not(inner) => {
213 let v = evaluate(inner, item, tracker)?;
214 Ok(!v)
215 }
216 }
217}
218
219fn parse_or(stream: &mut TokenStream) -> Result<ConditionExpr, String> {
224 let mut left = parse_and(stream)?;
225 while matches!(stream.peek(), Some(Token::Or)) {
226 stream.next();
227 let right = parse_and(stream)?;
228 left = ConditionExpr::Or(Box::new(left), Box::new(right));
229 }
230 Ok(left)
231}
232
233fn parse_and(stream: &mut TokenStream) -> Result<ConditionExpr, String> {
234 let mut left = parse_not(stream)?;
235 while matches!(stream.peek(), Some(Token::And)) {
236 stream.next();
237 let right = parse_not(stream)?;
238 left = ConditionExpr::And(Box::new(left), Box::new(right));
239 }
240 Ok(left)
241}
242
243fn parse_not(stream: &mut TokenStream) -> Result<ConditionExpr, String> {
244 if matches!(stream.peek(), Some(Token::Not)) {
245 stream.next();
246 let inner = parse_not(stream)?;
247 return Ok(ConditionExpr::Not(Box::new(inner)));
248 }
249 parse_primary(stream)
250}
251
252fn parse_primary(stream: &mut TokenStream) -> Result<ConditionExpr, String> {
253 if matches!(stream.peek(), Some(Token::LParen)) {
255 stream.next();
256 let expr = parse_or(stream)?;
257 stream.expect(&Token::RParen)?;
258 return Ok(expr);
259 }
260
261 if let Some(Token::Identifier(name)) = stream.peek() {
263 let name_owned = name.clone();
264 let func_name = name_owned.to_lowercase();
265
266 let is_function_call = {
268 let saved = stream.pos();
269 stream.next(); let is_lparen = matches!(stream.peek(), Some(Token::LParen));
271 stream.set_pos(saved); is_lparen
273 };
274
275 if is_function_call {
276 match func_name.as_str() {
278 "attribute_exists" => {
279 stream.next();
280 stream.expect(&Token::LParen)?;
281 let path = parse_raw_path(stream)?;
282 stream.expect(&Token::RParen)?;
283 return Ok(ConditionExpr::AttributeExists(path));
284 }
285 "attribute_not_exists" => {
286 stream.next();
287 stream.expect(&Token::LParen)?;
288 let path = parse_raw_path(stream)?;
289 stream.expect(&Token::RParen)?;
290 return Ok(ConditionExpr::AttributeNotExists(path));
291 }
292 "attribute_type" => {
293 stream.next();
294 stream.expect(&Token::LParen)?;
295 let path = parse_raw_path(stream)?;
296 stream.expect(&Token::Comma)?;
297 let type_val = parse_operand(stream)?;
298 stream.expect(&Token::RParen)?;
299 return Ok(ConditionExpr::AttributeType(path, type_val));
300 }
301 "begins_with" => {
302 stream.next();
303 stream.expect(&Token::LParen)?;
304 let path_op = parse_operand(stream)?;
305 stream.expect(&Token::Comma)?;
306 let prefix_op = parse_operand(stream)?;
307 stream.expect(&Token::RParen)?;
308 return Ok(ConditionExpr::BeginsWith(path_op, prefix_op));
309 }
310 "contains" => {
311 stream.next();
312 stream.expect(&Token::LParen)?;
313 let path_op = parse_operand(stream)?;
314 stream.expect(&Token::Comma)?;
315 let search_op = parse_operand(stream)?;
316 stream.expect(&Token::RParen)?;
317 return Ok(ConditionExpr::Contains(path_op, search_op));
318 }
319 "size" => {
320 }
323 _ => {
324 return Err(format!("Invalid function name; function: {}", name_owned));
326 }
327 }
328 } else {
329 }
332 }
333
334 let left = parse_operand(stream)?;
336
337 match stream.peek() {
338 Some(Token::Eq) => {
339 stream.next();
340 let right = parse_operand(stream)?;
341 Ok(ConditionExpr::Comparison {
342 left,
343 op: CompOp::Eq,
344 right,
345 })
346 }
347 Some(Token::Ne) => {
348 stream.next();
349 let right = parse_operand(stream)?;
350 Ok(ConditionExpr::Comparison {
351 left,
352 op: CompOp::Ne,
353 right,
354 })
355 }
356 Some(Token::Lt) => {
357 stream.next();
358 let right = parse_operand(stream)?;
359 Ok(ConditionExpr::Comparison {
360 left,
361 op: CompOp::Lt,
362 right,
363 })
364 }
365 Some(Token::Le) => {
366 stream.next();
367 let right = parse_operand(stream)?;
368 Ok(ConditionExpr::Comparison {
369 left,
370 op: CompOp::Le,
371 right,
372 })
373 }
374 Some(Token::Gt) => {
375 stream.next();
376 let right = parse_operand(stream)?;
377 Ok(ConditionExpr::Comparison {
378 left,
379 op: CompOp::Gt,
380 right,
381 })
382 }
383 Some(Token::Ge) => {
384 stream.next();
385 let right = parse_operand(stream)?;
386 Ok(ConditionExpr::Comparison {
387 left,
388 op: CompOp::Ge,
389 right,
390 })
391 }
392 Some(Token::Between) => {
393 stream.next();
394 let lo = parse_operand(stream)?;
395 stream.expect(&Token::And)?;
396 let hi = parse_operand(stream)?;
397 Ok(ConditionExpr::Between {
398 operand: left,
399 lo,
400 hi,
401 })
402 }
403 Some(Token::In) => {
404 stream.next();
405 stream.expect(&Token::LParen)?;
406 let mut values = vec![parse_operand(stream)?];
407 while matches!(stream.peek(), Some(Token::Comma)) {
408 stream.next();
409 values.push(parse_operand(stream)?);
410 }
411 stream.expect(&Token::RParen)?;
412 Ok(ConditionExpr::In {
413 operand: left,
414 values,
415 })
416 }
417 _ => Err("Expected comparison operator, BETWEEN, or IN".to_string()),
418 }
419}
420
421fn parse_operand(stream: &mut TokenStream) -> Result<Operand, String> {
423 if let Some(Token::Identifier(name)) = stream.peek() {
425 if name.to_lowercase() == "size" {
426 stream.next();
427 stream.expect(&Token::LParen)?;
428 let path = parse_raw_path(stream)?;
429 stream.expect(&Token::RParen)?;
430 return Ok(Operand::Size(path));
431 }
432 }
433
434 match stream.peek() {
435 Some(Token::ValueRef(_)) => {
436 if let Some(Token::ValueRef(name)) = stream.next().cloned() {
437 Ok(Operand::ValueRef(name))
438 } else {
439 unreachable!()
440 }
441 }
442 Some(Token::Identifier(_)) | Some(Token::NameRef(_)) => {
443 let path = parse_raw_path(stream)?;
444 Ok(Operand::Path(path))
445 }
446 Some(t) => Err(format!("Expected operand, got {t}")),
447 None => Err("Expected operand, got end of expression".to_string()),
448 }
449}
450
451pub fn parse_raw_path(stream: &mut TokenStream) -> Result<Vec<PathElement>, String> {
454 let first = match stream.next() {
455 Some(Token::Identifier(name)) => {
456 if super::reserved::is_reserved_keyword(name) {
457 return Err(format!(
458 "Attribute name is a reserved keyword; reserved keyword: {name}"
459 ));
460 }
461 PathElement::Attribute(name.clone())
462 }
463 Some(Token::NameRef(name)) => PathElement::Attribute(name.clone()),
464 Some(t) => return Err(format!("Expected attribute name, got {t}")),
465 None => return Err("Expected attribute name, got end of expression".to_string()),
466 };
467
468 let mut path = vec![first];
469
470 loop {
471 match stream.peek() {
472 Some(Token::Dot) => {
473 stream.next();
474 match stream.next() {
475 Some(Token::Identifier(name)) => {
476 if super::reserved::is_reserved_keyword(name) {
477 return Err(format!(
478 "Attribute name is a reserved keyword; reserved keyword: {name}"
479 ));
480 }
481 path.push(PathElement::Attribute(name.clone()));
482 }
483 Some(Token::NameRef(name)) => {
484 path.push(PathElement::Attribute(name.clone()));
485 }
486 Some(t) => return Err(format!("Expected attribute name after '.', got {t}")),
487 None => return Err("Expected attribute name after '.'".to_string()),
488 }
489 }
490 Some(Token::LBracket) => {
491 stream.next();
492 match stream.next() {
493 Some(Token::Number(n)) => {
494 let idx: usize = n.parse().map_err(|_| format!("Invalid index: {n}"))?;
495 path.push(PathElement::Index(idx));
496 }
497 Some(t) => return Err(format!("Expected number in brackets, got {t}")),
498 None => return Err("Expected number in brackets".to_string()),
499 }
500 stream.expect(&Token::RBracket)?;
501 }
502 _ => break,
503 }
504 }
505
506 Ok(path)
507}
508
509fn resolve_operand(
515 operand: &Operand,
516 item: &HashMap<String, AttributeValue>,
517 tracker: &TrackedExpressionAttributes,
518) -> Result<Option<AttributeValue>, String> {
519 match operand {
520 Operand::Path(path) => {
521 let resolved = resolve_path_elements(path, tracker)?;
522 Ok(resolve_path(item, &resolved))
523 }
524 Operand::ValueRef(name) => {
525 let val = tracker.resolve_value(name)?;
526 Ok(Some(val.clone()))
527 }
528 Operand::Size(path) => {
529 let resolved = resolve_path_elements(path, tracker)?;
530 match resolve_path(item, &resolved) {
531 Some(val) => {
532 let size = match &val {
533 AttributeValue::S(s) => s.encode_utf16().count(),
536 AttributeValue::B(b) => b.len(),
537 AttributeValue::SS(set) => set.len(),
538 AttributeValue::NS(set) => set.len(),
539 AttributeValue::BS(set) => set.len(),
540 AttributeValue::L(list) => list.len(),
541 AttributeValue::M(map) => map.len(),
542 _ => return Ok(None),
545 };
546 Ok(Some(AttributeValue::N(size.to_string())))
547 }
548 None => Ok(None),
549 }
550 }
551 }
552}
553
554fn compare_values(left: &AttributeValue, op: &CompOp, right: &AttributeValue) -> bool {
560 match (left, right) {
561 (AttributeValue::S(a), AttributeValue::S(b)) => compare_ord(a, b, op),
563
564 (AttributeValue::N(a), AttributeValue::N(b)) => {
566 if can_use_f64(a) && can_use_f64(b) {
568 if let (Ok(fa), Ok(fb)) = (a.parse::<f64>(), b.parse::<f64>()) {
569 if fa.is_finite() && fb.is_finite() {
570 return compare_ord(&fa, &fb, op);
571 }
572 }
573 }
574 use bigdecimal::BigDecimal;
576 use std::str::FromStr;
577 match (BigDecimal::from_str(a), BigDecimal::from_str(b)) {
578 (Ok(da), Ok(db)) => compare_ord(&da, &db, op),
579 _ => false,
580 }
581 }
582
583 (AttributeValue::B(a), AttributeValue::B(b)) => compare_ord(a, b, op),
585
586 (AttributeValue::BOOL(a), AttributeValue::BOOL(b)) => match op {
588 CompOp::Eq => a == b,
589 CompOp::Ne => a != b,
590 _ => false,
591 },
592
593 (AttributeValue::NULL(a), AttributeValue::NULL(b)) => match op {
595 CompOp::Eq => a == b,
596 CompOp::Ne => a != b,
597 _ => false,
598 },
599
600 (AttributeValue::SS(a), AttributeValue::SS(b)) => {
602 let mut sa = a.clone();
603 let mut sb = b.clone();
604 sa.sort();
605 sb.sort();
606 match op {
607 CompOp::Eq => sa == sb,
608 CompOp::Ne => sa != sb,
609 _ => false,
610 }
611 }
612
613 (AttributeValue::NS(a), AttributeValue::NS(b)) => {
618 if a.len() != b.len() {
619 return matches!(op, CompOp::Ne);
620 }
621 let mut na: Vec<String> = a
622 .iter()
623 .map(|n| crate::types::normalize_dynamo_number(n))
624 .collect();
625 let mut nb: Vec<String> = b
626 .iter()
627 .map(|n| crate::types::normalize_dynamo_number(n))
628 .collect();
629 na.sort();
630 nb.sort();
631 match op {
632 CompOp::Eq => na == nb,
633 CompOp::Ne => na != nb,
634 _ => false,
635 }
636 }
637
638 (AttributeValue::BS(a), AttributeValue::BS(b)) => {
640 let mut sa = a.clone();
641 let mut sb = b.clone();
642 sa.sort();
643 sb.sort();
644 match op {
645 CompOp::Eq => sa == sb,
646 CompOp::Ne => sa != sb,
647 _ => false,
648 }
649 }
650
651 (AttributeValue::L(a), AttributeValue::L(b)) => {
655 let equal = a.len() == b.len()
656 && a.iter()
657 .zip(b.iter())
658 .all(|(x, y)| compare_values(x, &CompOp::Eq, y));
659 match op {
660 CompOp::Eq => equal,
661 CompOp::Ne => !equal,
662 _ => false,
663 }
664 }
665
666 (AttributeValue::M(a), AttributeValue::M(b)) => {
669 let equal = a.len() == b.len()
670 && a.iter().all(|(k, av)| {
671 b.get(k)
672 .is_some_and(|bv| compare_values(av, &CompOp::Eq, bv))
673 });
674 match op {
675 CompOp::Eq => equal,
676 CompOp::Ne => !equal,
677 _ => false,
678 }
679 }
680
681 _ => matches!(op, CompOp::Ne),
683 }
684}
685
686fn compare_ord<T: PartialOrd>(a: &T, b: &T, op: &CompOp) -> bool {
687 match op {
688 CompOp::Eq => a == b,
689 CompOp::Ne => a != b,
690 CompOp::Lt => a < b,
691 CompOp::Le => a <= b,
692 CompOp::Gt => a > b,
693 CompOp::Ge => a >= b,
694 }
695}
696
697pub fn track_references(
700 expr: &ConditionExpr,
701 tracker: &TrackedExpressionAttributes,
702) -> Result<(), String> {
703 match expr {
704 ConditionExpr::Comparison { left, op: _, right } => {
705 track_operand_refs(left, tracker)?;
706 track_operand_refs(right, tracker)
707 }
708 ConditionExpr::Between { operand, lo, hi } => {
709 track_operand_refs(operand, tracker)?;
710 track_operand_refs(lo, tracker)?;
711 track_operand_refs(hi, tracker)
712 }
713 ConditionExpr::In { operand, values } => {
714 track_operand_refs(operand, tracker)?;
715 for v in values {
716 track_operand_refs(v, tracker)?;
717 }
718 Ok(())
719 }
720 ConditionExpr::AttributeExists(path) | ConditionExpr::AttributeNotExists(path) => {
721 track_cond_path_refs(path, tracker)
722 }
723 ConditionExpr::AttributeType(path, type_op) => {
724 track_cond_path_refs(path, tracker)?;
725 track_operand_refs(type_op, tracker)
726 }
727 ConditionExpr::BeginsWith(a, b) | ConditionExpr::Contains(a, b) => {
728 track_operand_refs(a, tracker)?;
729 track_operand_refs(b, tracker)
730 }
731 ConditionExpr::And(left, right) | ConditionExpr::Or(left, right) => {
732 track_references(left, tracker)?;
733 track_references(right, tracker)
734 }
735 ConditionExpr::Not(inner) => track_references(inner, tracker),
736 }
737}
738
739fn track_operand_refs(
740 operand: &Operand,
741 tracker: &TrackedExpressionAttributes,
742) -> Result<(), String> {
743 match operand {
744 Operand::Path(path) => track_cond_path_refs(path, tracker),
745 Operand::ValueRef(name) => {
746 tracker.resolve_value(name)?;
747 Ok(())
748 }
749 Operand::Size(path) => track_cond_path_refs(path, tracker),
750 }
751}
752
753fn track_cond_path_refs(
754 path: &[PathElement],
755 tracker: &TrackedExpressionAttributes,
756) -> Result<(), String> {
757 for elem in path {
758 if let PathElement::Attribute(name) = elem {
759 if name.starts_with('#') {
760 tracker.resolve_name(name)?;
761 }
762 }
763 }
764 Ok(())
765}
766
767pub fn validate_operand_semantics(
780 expr: &ConditionExpr,
781 names: &Option<HashMap<String, String>>,
782 values: &Option<HashMap<String, AttributeValue>>,
783) -> Result<(), String> {
784 match expr {
785 ConditionExpr::Contains(first, rest) => {
786 if first == rest {
787 return Err(format!(
788 "The first operand must be distinct from the remaining operands for this operator or function; operator: contains, first operand: [{}]",
789 render_operand_name(first, names)
790 ));
791 }
792 Ok(())
793 }
794 ConditionExpr::BeginsWith(_, prefix) => {
795 if let Operand::ValueRef(vname) = prefix {
796 if let Some(v) = values.as_ref().and_then(|m| m.get(vname.as_str())) {
797 if !matches!(v, AttributeValue::S(_) | AttributeValue::B(_)) {
798 return Err(format!(
799 "Incorrect operand type for operator or function; operator or function: begins_with, operand type: {}",
800 v.type_name()
801 ));
802 }
803 }
804 }
805 Ok(())
806 }
807 ConditionExpr::And(a, b) | ConditionExpr::Or(a, b) => {
808 validate_operand_semantics(a, names, values)?;
809 validate_operand_semantics(b, names, values)
810 }
811 ConditionExpr::Not(inner) => validate_operand_semantics(inner, names, values),
812 _ => Ok(()),
813 }
814}
815
816fn render_operand_name(op: &Operand, names: &Option<HashMap<String, String>>) -> String {
818 let elems = match op {
819 Operand::Path(elems) | Operand::Size(elems) => elems,
820 Operand::ValueRef(name) => return name.clone(),
821 };
822 elems
823 .iter()
824 .map(|e| match e {
825 PathElement::Attribute(a) if a.starts_with('#') => names
826 .as_ref()
827 .and_then(|m| m.get(a))
828 .cloned()
829 .unwrap_or_else(|| a.clone()),
830 PathElement::Attribute(a) => a.clone(),
831 PathElement::Index(i) => format!("[{i}]"),
832 })
833 .collect::<Vec<_>>()
834 .join(".")
835}
836
837pub fn validate_static(
838 expr: &ConditionExpr,
839 values: &Option<HashMap<String, AttributeValue>>,
840) -> Result<(), String> {
841 match expr {
842 ConditionExpr::Between { operand: _, lo, hi } => {
843 if let (Operand::ValueRef(lo_name), Operand::ValueRef(hi_name)) = (lo, hi) {
845 if let Some(vals) = values {
846 let lo_val = vals.get(lo_name.as_str());
847 let hi_val = vals.get(hi_name.as_str());
848 if let (Some(lo_v), Some(hi_v)) = (lo_val, hi_val) {
849 if std::mem::discriminant(lo_v) != std::mem::discriminant(hi_v) {
851 return Err(format!(
852 "Invalid ConditionExpression: The BETWEEN operator requires same data type for lower and upper bounds; \
853 lower bound operand: AttributeValue: {{{}}}, upper bound operand: AttributeValue: {{{}}}",
854 format_av_for_error(lo_v),
855 format_av_for_error(hi_v),
856 ));
857 }
858 if compare_values(lo_v, &CompOp::Gt, hi_v) {
860 return Err(format!(
861 "Invalid ConditionExpression: The BETWEEN operator requires upper bound to be greater than or equal to lower bound; \
862 lower bound operand: AttributeValue: {{{}}}, upper bound operand: AttributeValue: {{{}}}",
863 format_av_for_error(lo_v),
864 format_av_for_error(hi_v),
865 ));
866 }
867 }
868 }
869 }
870 Ok(())
871 }
872 ConditionExpr::And(left, right) | ConditionExpr::Or(left, right) => {
873 validate_static(left, values)?;
874 validate_static(right, values)
875 }
876 ConditionExpr::Not(inner) => validate_static(inner, values),
877 _ => Ok(()),
878 }
879}
880
881fn format_av_for_error(av: &AttributeValue) -> String {
883 match av {
884 AttributeValue::S(s) => format!("S:{s}"),
885 AttributeValue::N(n) => format!("N:{n}"),
886 AttributeValue::B(b) => {
887 use base64::Engine;
888 format!("B:{}", base64::engine::general_purpose::STANDARD.encode(b))
889 }
890 AttributeValue::BOOL(b) => format!("BOOL:{b}"),
891 AttributeValue::NULL(_) => "NULL:true".to_string(),
892 AttributeValue::SS(set) => format!("SS:{set:?}"),
893 AttributeValue::NS(set) => format!("NS:{set:?}"),
894 AttributeValue::BS(_) => "BS:[...]".to_string(),
895 AttributeValue::L(_) => "L:[...]".to_string(),
896 AttributeValue::M(_) => "M:{...}".to_string(),
897 }
898}
899
900pub fn check_non_scalar_key_access(
908 expr: &ConditionExpr,
909 attr_names: &Option<HashMap<String, String>>,
910 key_attrs: &[String],
911 index_key_attrs: &[String],
912) -> Option<(String, bool)> {
913 let mut result = None;
915 check_non_scalar_key_access_inner(expr, attr_names, key_attrs, index_key_attrs, &mut result);
916 result
917}
918
919fn check_non_scalar_key_access_inner(
920 expr: &ConditionExpr,
921 attr_names: &Option<HashMap<String, String>>,
922 key_attrs: &[String],
923 index_key_attrs: &[String],
924 result: &mut Option<(String, bool)>,
925) {
926 if result.is_some() {
927 return;
928 }
929 match expr {
930 ConditionExpr::Comparison { left, right, .. } => {
931 check_operand_non_scalar(left, attr_names, key_attrs, index_key_attrs, result);
932 check_operand_non_scalar(right, attr_names, key_attrs, index_key_attrs, result);
933 }
934 ConditionExpr::Between { operand, lo, hi } => {
935 check_operand_non_scalar(operand, attr_names, key_attrs, index_key_attrs, result);
936 check_operand_non_scalar(lo, attr_names, key_attrs, index_key_attrs, result);
937 check_operand_non_scalar(hi, attr_names, key_attrs, index_key_attrs, result);
938 }
939 ConditionExpr::In { operand, values } => {
940 check_operand_non_scalar(operand, attr_names, key_attrs, index_key_attrs, result);
941 for v in values {
942 check_operand_non_scalar(v, attr_names, key_attrs, index_key_attrs, result);
943 }
944 }
945 ConditionExpr::AttributeExists(path) | ConditionExpr::AttributeNotExists(path) => {
946 check_path_non_scalar(path, attr_names, key_attrs, index_key_attrs, result);
947 }
948 ConditionExpr::AttributeType(path, _) => {
949 check_path_non_scalar(path, attr_names, key_attrs, index_key_attrs, result);
950 }
951 ConditionExpr::BeginsWith(a, b) | ConditionExpr::Contains(a, b) => {
952 check_operand_non_scalar(a, attr_names, key_attrs, index_key_attrs, result);
953 check_operand_non_scalar(b, attr_names, key_attrs, index_key_attrs, result);
954 }
955 ConditionExpr::And(a, b) | ConditionExpr::Or(a, b) => {
956 check_non_scalar_key_access_inner(a, attr_names, key_attrs, index_key_attrs, result);
957 check_non_scalar_key_access_inner(b, attr_names, key_attrs, index_key_attrs, result);
958 }
959 ConditionExpr::Not(inner) => {
960 check_non_scalar_key_access_inner(
961 inner,
962 attr_names,
963 key_attrs,
964 index_key_attrs,
965 result,
966 );
967 }
968 }
969}
970
971fn check_operand_non_scalar(
972 operand: &Operand,
973 attr_names: &Option<HashMap<String, String>>,
974 key_attrs: &[String],
975 index_key_attrs: &[String],
976 result: &mut Option<(String, bool)>,
977) {
978 if result.is_some() {
979 return;
980 }
981 match operand {
982 Operand::Path(path) | Operand::Size(path) => {
983 check_path_non_scalar(path, attr_names, key_attrs, index_key_attrs, result);
984 }
985 Operand::ValueRef(_) => {}
986 }
987}
988
989fn check_path_non_scalar(
990 path: &[PathElement],
991 attr_names: &Option<HashMap<String, String>>,
992 key_attrs: &[String],
993 index_key_attrs: &[String],
994 result: &mut Option<(String, bool)>,
995) {
996 if result.is_some() || path.len() <= 1 {
997 return; }
999 if let Some(name) = resolve_top_level_path(path, attr_names) {
1000 if key_attrs.contains(&name) {
1001 *result = Some((name, false));
1002 } else if index_key_attrs.contains(&name) {
1003 *result = Some((name, true));
1004 }
1005 }
1006}
1007
1008pub fn extract_top_level_attributes(
1014 expr: &ConditionExpr,
1015 attr_names: &Option<HashMap<String, String>>,
1016) -> Vec<String> {
1017 let mut attrs = Vec::new();
1018 collect_top_level_attrs(expr, attr_names, &mut attrs);
1019 attrs.sort();
1020 attrs.dedup();
1021 attrs
1022}
1023
1024fn collect_top_level_attrs(
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_top_attr(left, attr_names, out);
1032 collect_operand_top_attr(right, attr_names, out);
1033 }
1034 ConditionExpr::Between { operand, lo, hi } => {
1035 collect_operand_top_attr(operand, attr_names, out);
1036 collect_operand_top_attr(lo, attr_names, out);
1037 collect_operand_top_attr(hi, attr_names, out);
1038 }
1039 ConditionExpr::In { operand, values } => {
1040 collect_operand_top_attr(operand, attr_names, out);
1041 for v in values {
1042 collect_operand_top_attr(v, attr_names, out);
1043 }
1044 }
1045 ConditionExpr::AttributeExists(path) | ConditionExpr::AttributeNotExists(path) => {
1046 if let Some(name) = resolve_top_level_path(path, attr_names) {
1047 out.push(name);
1048 }
1049 }
1050 ConditionExpr::AttributeType(path, _) => {
1051 if let Some(name) = resolve_top_level_path(path, attr_names) {
1052 out.push(name);
1053 }
1054 }
1055 ConditionExpr::BeginsWith(a, b) | ConditionExpr::Contains(a, b) => {
1056 collect_operand_top_attr(a, attr_names, out);
1057 collect_operand_top_attr(b, attr_names, out);
1058 }
1059 ConditionExpr::And(a, b) | ConditionExpr::Or(a, b) => {
1060 collect_top_level_attrs(a, attr_names, out);
1061 collect_top_level_attrs(b, attr_names, out);
1062 }
1063 ConditionExpr::Not(inner) => {
1064 collect_top_level_attrs(inner, attr_names, out);
1065 }
1066 }
1067}
1068
1069fn collect_operand_top_attr(
1070 operand: &Operand,
1071 attr_names: &Option<HashMap<String, String>>,
1072 out: &mut Vec<String>,
1073) {
1074 match operand {
1075 Operand::Path(path) => {
1076 if let Some(name) = resolve_top_level_path(path, attr_names) {
1077 out.push(name);
1078 }
1079 }
1080 Operand::Size(path) => {
1081 if let Some(name) = resolve_top_level_path(path, attr_names) {
1082 out.push(name);
1083 }
1084 }
1085 Operand::ValueRef(_) => {}
1086 }
1087}
1088
1089fn resolve_top_level_path(
1090 path: &[PathElement],
1091 attr_names: &Option<HashMap<String, String>>,
1092) -> Option<String> {
1093 match path.first() {
1094 Some(PathElement::Attribute(name)) => {
1095 if name.starts_with('#') {
1096 attr_names
1097 .as_ref()
1098 .and_then(|m| m.get(name.as_str()))
1099 .cloned()
1100 } else {
1101 Some(name.clone())
1102 }
1103 }
1104 _ => None,
1105 }
1106}
1107
1108pub fn validate_name_refs(
1112 expr: &ConditionExpr,
1113 attr_names: &Option<HashMap<String, String>>,
1114) -> Result<(), String> {
1115 let mut undefined = Vec::new();
1116 collect_undefined_name_refs(expr, attr_names, &mut undefined);
1117 if let Some(name) = undefined.first() {
1118 Err(format!(
1119 "An expression attribute name used in the document path is not defined; attribute name: {}",
1120 name
1121 ))
1122 } else {
1123 Ok(())
1124 }
1125}
1126
1127fn collect_undefined_name_refs(
1128 expr: &ConditionExpr,
1129 attr_names: &Option<HashMap<String, String>>,
1130 out: &mut Vec<String>,
1131) {
1132 match expr {
1133 ConditionExpr::Comparison { left, right, .. } => {
1134 collect_operand_undefined_refs(left, attr_names, out);
1135 collect_operand_undefined_refs(right, attr_names, out);
1136 }
1137 ConditionExpr::Between { operand, lo, hi } => {
1138 collect_operand_undefined_refs(operand, attr_names, out);
1139 collect_operand_undefined_refs(lo, attr_names, out);
1140 collect_operand_undefined_refs(hi, attr_names, out);
1141 }
1142 ConditionExpr::In { operand, values } => {
1143 collect_operand_undefined_refs(operand, attr_names, out);
1144 for v in values {
1145 collect_operand_undefined_refs(v, attr_names, out);
1146 }
1147 }
1148 ConditionExpr::AttributeExists(path) | ConditionExpr::AttributeNotExists(path) => {
1149 collect_path_undefined_refs(path, attr_names, out);
1150 }
1151 ConditionExpr::AttributeType(path, operand) => {
1152 collect_path_undefined_refs(path, attr_names, out);
1153 collect_operand_undefined_refs(operand, attr_names, out);
1154 }
1155 ConditionExpr::BeginsWith(a, b) | ConditionExpr::Contains(a, b) => {
1156 collect_operand_undefined_refs(a, attr_names, out);
1157 collect_operand_undefined_refs(b, attr_names, out);
1158 }
1159 ConditionExpr::And(a, b) | ConditionExpr::Or(a, b) => {
1160 collect_undefined_name_refs(a, attr_names, out);
1161 collect_undefined_name_refs(b, attr_names, out);
1162 }
1163 ConditionExpr::Not(inner) => {
1164 collect_undefined_name_refs(inner, attr_names, out);
1165 }
1166 }
1167}
1168
1169fn collect_operand_undefined_refs(
1170 operand: &Operand,
1171 attr_names: &Option<HashMap<String, String>>,
1172 out: &mut Vec<String>,
1173) {
1174 match operand {
1175 Operand::Path(path) | Operand::Size(path) => {
1176 collect_path_undefined_refs(path, attr_names, out);
1177 }
1178 Operand::ValueRef(_) => {}
1179 }
1180}
1181
1182fn collect_path_undefined_refs(
1183 path: &[PathElement],
1184 attr_names: &Option<HashMap<String, String>>,
1185 out: &mut Vec<String>,
1186) {
1187 for elem in path {
1188 if let PathElement::Attribute(name) = elem {
1189 if name.starts_with('#') {
1190 let defined = attr_names
1191 .as_ref()
1192 .is_some_and(|m| m.contains_key(name.as_str()));
1193 if !defined && !out.contains(name) {
1194 out.push(name.clone());
1195 }
1196 }
1197 }
1198 }
1199}
1200
1201fn can_use_f64(s: &str) -> bool {
1205 if s.contains('E') || s.contains('e') {
1207 return false;
1208 }
1209 let digit_count = s.bytes().filter(|b| b.is_ascii_digit()).count();
1212 digit_count <= 15
1213}
1214
1215#[cfg(test)]
1216mod tests {
1217 use super::*;
1218 use crate::expressions::evaluate_without_tracking;
1219
1220 fn make_item(pairs: &[(&str, AttributeValue)]) -> HashMap<String, AttributeValue> {
1221 pairs
1222 .iter()
1223 .map(|(k, v)| (k.to_string(), v.clone()))
1224 .collect()
1225 }
1226
1227 fn vals(pairs: &[(&str, AttributeValue)]) -> Option<HashMap<String, AttributeValue>> {
1228 Some(make_item(pairs))
1229 }
1230
1231 fn names(pairs: &[(&str, &str)]) -> Option<HashMap<String, String>> {
1232 Some(
1233 pairs
1234 .iter()
1235 .map(|(k, v)| (k.to_string(), v.to_string()))
1236 .collect(),
1237 )
1238 }
1239
1240 #[test]
1241 fn test_simple_equality() {
1242 let expr = parse("pk = :val").unwrap();
1243 let item = make_item(&[("pk", AttributeValue::S("hello".into()))]);
1244 let av = vals(&[(":val", AttributeValue::S("hello".into()))]);
1245 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1246 }
1247
1248 #[test]
1249 fn test_inequality() {
1250 let expr = parse("pk <> :val").unwrap();
1251 let item = make_item(&[("pk", AttributeValue::S("hello".into()))]);
1252 let av = vals(&[(":val", AttributeValue::S("world".into()))]);
1253 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1254 }
1255
1256 #[test]
1257 fn test_numeric_comparison() {
1258 let expr = parse("price > :min").unwrap();
1259 let item = make_item(&[("price", AttributeValue::N("42".into()))]);
1260 let av = vals(&[(":min", AttributeValue::N("10".into()))]);
1261 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1262 }
1263
1264 #[test]
1265 fn test_between() {
1266 let expr = parse("age BETWEEN :lo AND :hi").unwrap();
1267 let item = make_item(&[("age", AttributeValue::N("25".into()))]);
1268 let av = vals(&[
1269 (":lo", AttributeValue::N("18".into())),
1270 (":hi", AttributeValue::N("65".into())),
1271 ]);
1272 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1273 }
1274
1275 #[test]
1276 fn test_in_operator() {
1277 let expr = parse("state_val IN (:s1, :s2, :s3)").unwrap();
1278 let item = make_item(&[("state_val", AttributeValue::S("active".into()))]);
1279 let av = vals(&[
1280 (":s1", AttributeValue::S("active".into())),
1281 (":s2", AttributeValue::S("pending".into())),
1282 (":s3", AttributeValue::S("closed".into())),
1283 ]);
1284 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1285 }
1286
1287 #[test]
1288 fn test_attribute_exists() {
1289 let expr = parse("attribute_exists(email)").unwrap();
1290 let item = make_item(&[("email", AttributeValue::S("a@b.com".into()))]);
1291 assert!(evaluate_without_tracking(&expr, &item, &None, &None).unwrap());
1292
1293 let empty_item: HashMap<String, AttributeValue> = HashMap::new();
1294 assert!(!evaluate_without_tracking(&expr, &empty_item, &None, &None).unwrap());
1295 }
1296
1297 #[test]
1298 fn test_attribute_not_exists() {
1299 let expr = parse("attribute_not_exists(email)").unwrap();
1300 let item: HashMap<String, AttributeValue> = HashMap::new();
1301 assert!(evaluate_without_tracking(&expr, &item, &None, &None).unwrap());
1302 }
1303
1304 #[test]
1305 fn test_begins_with() {
1306 let expr = parse("begins_with(sk, :prefix)").unwrap();
1307 let item = make_item(&[("sk", AttributeValue::S("user#123".into()))]);
1308 let av = vals(&[(":prefix", AttributeValue::S("user#".into()))]);
1309 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1310 }
1311
1312 #[test]
1313 fn test_contains_string() {
1314 let expr = parse("contains(description, :sub)").unwrap();
1315 let item = make_item(&[("description", AttributeValue::S("hello world".into()))]);
1316 let av = vals(&[(":sub", AttributeValue::S("world".into()))]);
1317 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1318 }
1319
1320 #[test]
1321 fn test_contains_string_set() {
1322 let expr = parse("contains(tags, :tag)").unwrap();
1323 let item = make_item(&[(
1324 "tags",
1325 AttributeValue::SS(vec!["rust".into(), "dynamo".into()]),
1326 )]);
1327 let av = vals(&[(":tag", AttributeValue::S("rust".into()))]);
1328 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1329 }
1330
1331 #[test]
1332 fn test_size_function() {
1333 let expr = parse("size(label) > :len").unwrap();
1334 let item = make_item(&[("label", AttributeValue::S("Alice".into()))]);
1335 let av = vals(&[(":len", AttributeValue::N("3".into()))]);
1336 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1337 }
1338
1339 #[test]
1340 fn test_and_operator() {
1341 let expr = parse("price > :min AND price < :max").unwrap();
1342 let item = make_item(&[("price", AttributeValue::N("50".into()))]);
1343 let av = vals(&[
1344 (":min", AttributeValue::N("10".into())),
1345 (":max", AttributeValue::N("100".into())),
1346 ]);
1347 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1348 }
1349
1350 #[test]
1351 fn test_or_operator() {
1352 let expr = parse("state_val = :s1 OR state_val = :s2").unwrap();
1353 let item = make_item(&[("state_val", AttributeValue::S("pending".into()))]);
1354 let av = vals(&[
1355 (":s1", AttributeValue::S("active".into())),
1356 (":s2", AttributeValue::S("pending".into())),
1357 ]);
1358 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1359 }
1360
1361 #[test]
1362 fn test_not_operator() {
1363 let expr = parse("NOT state_val = :val").unwrap();
1364 let item = make_item(&[("state_val", AttributeValue::S("active".into()))]);
1365 let av = vals(&[(":val", AttributeValue::S("closed".into()))]);
1366 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1367 }
1368
1369 #[test]
1370 fn test_expression_attribute_names() {
1371 let expr = parse("#s = :val").unwrap();
1372 let item = make_item(&[("status", AttributeValue::S("active".into()))]);
1373 let an = names(&[("#s", "status")]);
1374 let av = vals(&[(":val", AttributeValue::S("active".into()))]);
1375 assert!(evaluate_without_tracking(&expr, &item, &an, &av).unwrap());
1376 }
1377
1378 #[test]
1379 fn test_nested_path() {
1380 let expr = parse("profile.label = :val").unwrap();
1381 let mut nested = HashMap::new();
1382 nested.insert("label".to_string(), AttributeValue::S("Alice".into()));
1383 let item = make_item(&[("profile", AttributeValue::M(nested))]);
1384 let av = vals(&[(":val", AttributeValue::S("Alice".into()))]);
1385 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1386 }
1387
1388 #[test]
1389 fn test_parenthesized() {
1390 let expr = parse("(a = :x OR b = :y) AND c = :z").unwrap();
1391 let item = make_item(&[
1392 ("a", AttributeValue::S("1".into())),
1393 ("b", AttributeValue::S("2".into())),
1394 ("c", AttributeValue::S("3".into())),
1395 ]);
1396 let av = vals(&[
1397 (":x", AttributeValue::S("wrong".into())),
1398 (":y", AttributeValue::S("2".into())),
1399 (":z", AttributeValue::S("3".into())),
1400 ]);
1401 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1402 }
1403
1404 #[test]
1405 fn test_missing_attribute_is_false() {
1406 let expr = parse("nonexistent = :val").unwrap();
1407 let item: HashMap<String, AttributeValue> = HashMap::new();
1408 let av = vals(&[(":val", AttributeValue::S("x".into()))]);
1409 assert!(!evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1410 }
1411
1412 #[test]
1413 fn test_missing_attribute_ne_is_true() {
1414 let item: HashMap<String, AttributeValue> = HashMap::new();
1415 let av = vals(&[(":val", AttributeValue::S("working".into()))]);
1416 let expr = parse("nonexistent <> :val").unwrap();
1417 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1418 }
1419
1420 #[test]
1421 fn test_missing_attribute_comparisons() {
1422 let item: HashMap<String, AttributeValue> = HashMap::new();
1423 let av = vals(&[(":val", AttributeValue::S("x".into()))]);
1424 for (op, expected) in [
1425 ("=", false),
1426 ("<>", true),
1427 ("<", false),
1428 ("<=", false),
1429 (">", false),
1430 (">=", false),
1431 ] {
1432 let expr = parse(&format!("nonexistent {} :val", op)).unwrap();
1433 assert_eq!(
1434 evaluate_without_tracking(&expr, &item, &None, &av).unwrap(),
1435 expected,
1436 "operator {} on missing attribute should be {}",
1437 op,
1438 expected
1439 );
1440 }
1441 }
1442
1443 fn map_of(pairs: &[(&str, AttributeValue)]) -> AttributeValue {
1444 AttributeValue::M(
1445 pairs
1446 .iter()
1447 .map(|(k, v)| (k.to_string(), v.clone()))
1448 .collect(),
1449 )
1450 }
1451
1452 #[test]
1455 fn test_map_equality_true() {
1456 let expr = parse("#s = :p").unwrap();
1457 let item = make_item(&[(
1458 "Status",
1459 map_of(&[("Union_Case", AttributeValue::S("Passive".into()))]),
1460 )]);
1461 let an = names(&[("#s", "Status")]);
1462 let av = vals(&[(
1463 ":p",
1464 map_of(&[("Union_Case", AttributeValue::S("Passive".into()))]),
1465 )]);
1466 assert!(evaluate_without_tracking(&expr, &item, &an, &av).unwrap());
1467 }
1468
1469 #[test]
1470 fn test_map_equality_false_and_ne_true() {
1471 let item = make_item(&[(
1472 "Status",
1473 map_of(&[("Union_Case", AttributeValue::S("Passive".into()))]),
1474 )]);
1475 let an = names(&[("#s", "Status")]);
1476 let av = vals(&[(
1477 ":p",
1478 map_of(&[("Union_Case", AttributeValue::S("Active".into()))]),
1479 )]);
1480
1481 let eq = parse("#s = :p").unwrap();
1482 assert!(!evaluate_without_tracking(&eq, &item, &an, &av).unwrap());
1483
1484 let ne = parse("#s <> :p").unwrap();
1485 assert!(evaluate_without_tracking(&ne, &item, &an, &av).unwrap());
1486 }
1487
1488 #[test]
1489 fn test_map_equality_is_key_order_independent() {
1490 let item = make_item(&[(
1491 "m",
1492 map_of(&[
1493 ("a", AttributeValue::S("1".into())),
1494 ("b", AttributeValue::S("2".into())),
1495 ]),
1496 )]);
1497 let av = vals(&[(
1499 ":p",
1500 map_of(&[
1501 ("b", AttributeValue::S("2".into())),
1502 ("a", AttributeValue::S("1".into())),
1503 ]),
1504 )]);
1505 let expr = parse("m = :p").unwrap();
1506 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1507 }
1508
1509 #[test]
1510 fn test_map_equality_differing_key_sets() {
1511 let item = make_item(&[("m", map_of(&[("a", AttributeValue::N("1".into()))]))]);
1513 let av = vals(&[(":p", map_of(&[("b", AttributeValue::N("1".into()))]))]);
1514 let expr = parse("m = :p").unwrap();
1515 assert!(!evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1516 }
1517
1518 #[test]
1519 fn test_nested_map_equality() {
1520 let item = make_item(&[(
1521 "m",
1522 map_of(&[("inner", map_of(&[("x", AttributeValue::S("v".into()))]))]),
1523 )]);
1524 let av = vals(&[(
1525 ":p",
1526 map_of(&[("inner", map_of(&[("x", AttributeValue::S("v".into()))]))]),
1527 )]);
1528 let expr = parse("m = :p").unwrap();
1529 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1530 }
1531
1532 #[test]
1533 fn test_map_equality_normalises_nested_numbers() {
1534 let item = make_item(&[("m", map_of(&[("n", AttributeValue::N("1".into()))]))]);
1536 let av = vals(&[(":p", map_of(&[("n", AttributeValue::N("1.0".into()))]))]);
1537 let expr = parse("m = :p").unwrap();
1538 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1539 }
1540
1541 #[test]
1542 fn test_list_equality_true() {
1543 let item = make_item(&[(
1544 "l",
1545 AttributeValue::L(vec![
1546 AttributeValue::S("a".into()),
1547 AttributeValue::N("1".into()),
1548 ]),
1549 )]);
1550 let av = vals(&[(
1551 ":p",
1552 AttributeValue::L(vec![
1553 AttributeValue::S("a".into()),
1554 AttributeValue::N("1".into()),
1555 ]),
1556 )]);
1557 let expr = parse("l = :p").unwrap();
1558 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1559 }
1560
1561 #[test]
1562 fn test_list_equality_is_order_sensitive() {
1563 let item = make_item(&[(
1564 "l",
1565 AttributeValue::L(vec![
1566 AttributeValue::S("a".into()),
1567 AttributeValue::S("b".into()),
1568 ]),
1569 )]);
1570 let av = vals(&[(
1571 ":p",
1572 AttributeValue::L(vec![
1573 AttributeValue::S("b".into()),
1574 AttributeValue::S("a".into()),
1575 ]),
1576 )]);
1577 let eq = parse("l = :p").unwrap();
1578 assert!(!evaluate_without_tracking(&eq, &item, &None, &av).unwrap());
1579 let ne = parse("l <> :p").unwrap();
1580 assert!(evaluate_without_tracking(&ne, &item, &None, &av).unwrap());
1581 }
1582
1583 #[test]
1584 fn test_map_ordering_operators_are_false() {
1585 let item = make_item(&[("m", map_of(&[("a", AttributeValue::S("1".into()))]))]);
1586 let av = vals(&[(":p", map_of(&[("a", AttributeValue::S("1".into()))]))]);
1587 for op in ["<", "<=", ">", ">="] {
1588 let expr = parse(&format!("m {} :p", op)).unwrap();
1589 assert!(
1590 !evaluate_without_tracking(&expr, &item, &None, &av).unwrap(),
1591 "ordering operator {} on maps should be false",
1592 op
1593 );
1594 }
1595 }
1596
1597 #[test]
1598 fn test_list_ordering_operators_are_false() {
1599 let item = make_item(&[("l", AttributeValue::L(vec![AttributeValue::S("a".into())]))]);
1600 let av = vals(&[(":p", AttributeValue::L(vec![AttributeValue::S("a".into())]))]);
1601 for op in ["<", "<=", ">", ">="] {
1602 let expr = parse(&format!("l {} :p", op)).unwrap();
1603 assert!(
1604 !evaluate_without_tracking(&expr, &item, &None, &av).unwrap(),
1605 "ordering operator {} on lists should be false",
1606 op
1607 );
1608 }
1609 }
1610
1611 #[test]
1614 fn test_ne_on_equal_map_and_list_is_false() {
1615 let map_item = make_item(&[("m", map_of(&[("a", AttributeValue::S("1".into()))]))]);
1616 let map_av = vals(&[(":p", map_of(&[("a", AttributeValue::S("1".into()))]))]);
1617 let map_ne = parse("m <> :p").unwrap();
1618 assert!(!evaluate_without_tracking(&map_ne, &map_item, &None, &map_av).unwrap());
1619
1620 let list_item = make_item(&[("l", AttributeValue::L(vec![AttributeValue::S("a".into())]))]);
1621 let list_av = vals(&[(":p", AttributeValue::L(vec![AttributeValue::S("a".into())]))]);
1622 let list_ne = parse("l <> :p").unwrap();
1623 assert!(!evaluate_without_tracking(&list_ne, &list_item, &None, &list_av).unwrap());
1624 }
1625
1626 #[test]
1628 fn test_ne_on_differing_key_sets_is_true() {
1629 let item = make_item(&[("m", map_of(&[("a", AttributeValue::N("1".into()))]))]);
1630 let av = vals(&[(":p", map_of(&[("b", AttributeValue::N("1".into()))]))]);
1631 let expr = parse("m <> :p").unwrap();
1632 assert!(evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1633 }
1634
1635 #[test]
1636 fn test_empty_map_equality() {
1637 let item = make_item(&[("m", AttributeValue::M(HashMap::new()))]);
1638 let av = vals(&[(":p", AttributeValue::M(HashMap::new()))]);
1639 let eq = parse("m = :p").unwrap();
1640 assert!(evaluate_without_tracking(&eq, &item, &None, &av).unwrap());
1641 let ne = parse("m <> :p").unwrap();
1642 assert!(!evaluate_without_tracking(&ne, &item, &None, &av).unwrap());
1643 }
1644
1645 #[test]
1646 fn test_empty_list_equality() {
1647 let item = make_item(&[("l", AttributeValue::L(vec![]))]);
1648 let av = vals(&[(":p", AttributeValue::L(vec![]))]);
1649 let eq = parse("l = :p").unwrap();
1650 assert!(evaluate_without_tracking(&eq, &item, &None, &av).unwrap());
1651 let ne = parse("l <> :p").unwrap();
1652 assert!(!evaluate_without_tracking(&ne, &item, &None, &av).unwrap());
1653 }
1654
1655 #[test]
1658 fn test_map_equality_type_mismatch_at_shared_key() {
1659 let item = make_item(&[("m", map_of(&[("x", AttributeValue::S("1".into()))]))]);
1660 let av = vals(&[(":p", map_of(&[("x", AttributeValue::N("1".into()))]))]);
1661 let eq = parse("m = :p").unwrap();
1662 assert!(!evaluate_without_tracking(&eq, &item, &None, &av).unwrap());
1663 let ne = parse("m <> :p").unwrap();
1664 assert!(evaluate_without_tracking(&ne, &item, &None, &av).unwrap());
1665 }
1666
1667 #[test]
1668 fn test_list_length_mismatch_is_not_equal() {
1669 let item = make_item(&[("l", AttributeValue::L(vec![AttributeValue::S("a".into())]))]);
1670 let av = vals(&[(
1671 ":p",
1672 AttributeValue::L(vec![
1673 AttributeValue::S("a".into()),
1674 AttributeValue::S("b".into()),
1675 ]),
1676 )]);
1677 let expr = parse("l = :p").unwrap();
1678 assert!(!evaluate_without_tracking(&expr, &item, &None, &av).unwrap());
1679 }
1680
1681 #[test]
1683 fn test_in_operator_with_map_operands() {
1684 let item = make_item(&[(
1685 "m",
1686 map_of(&[("status", AttributeValue::S("active".into()))]),
1687 )]);
1688 let av = vals(&[
1689 (
1690 ":p1",
1691 map_of(&[("status", AttributeValue::S("closed".into()))]),
1692 ),
1693 (
1694 ":p2",
1695 map_of(&[("status", AttributeValue::S("active".into()))]),
1696 ),
1697 ]);
1698 let hit = parse("m IN (:p1, :p2)").unwrap();
1699 assert!(evaluate_without_tracking(&hit, &item, &None, &av).unwrap());
1700
1701 let miss_av = vals(&[
1702 (
1703 ":p1",
1704 map_of(&[("status", AttributeValue::S("closed".into()))]),
1705 ),
1706 (
1707 ":p2",
1708 map_of(&[("status", AttributeValue::S("pending".into()))]),
1709 ),
1710 ]);
1711 let miss = parse("m IN (:p1, :p2)").unwrap();
1712 assert!(!evaluate_without_tracking(&miss, &item, &None, &miss_av).unwrap());
1713 }
1714
1715 #[test]
1718 fn test_contains_list_of_maps() {
1719 let item = make_item(&[(
1720 "l",
1721 AttributeValue::L(vec![
1722 map_of(&[("role", AttributeValue::S("admin".into()))]),
1723 map_of(&[("role", AttributeValue::S("viewer".into()))]),
1724 ]),
1725 )]);
1726 let hit_av = vals(&[(":p", map_of(&[("role", AttributeValue::S("admin".into()))]))]);
1727 let hit = parse("contains(l, :p)").unwrap();
1728 assert!(evaluate_without_tracking(&hit, &item, &None, &hit_av).unwrap());
1729
1730 let miss_av = vals(&[(
1731 ":p",
1732 map_of(&[("role", AttributeValue::S("unknown".into()))]),
1733 )]);
1734 let miss = parse("contains(l, :p)").unwrap();
1735 assert!(!evaluate_without_tracking(&miss, &item, &None, &miss_av).unwrap());
1736 }
1737
1738 #[test]
1741 fn test_number_set_equality_full_precision() {
1742 let item = make_item(&[("ns", AttributeValue::NS(vec!["100000000000000001".into()]))]);
1743
1744 let same = vals(&[(":p", AttributeValue::NS(vec!["100000000000000001".into()]))]);
1745 let eq = parse("ns = :p").unwrap();
1746 assert!(evaluate_without_tracking(&eq, &item, &None, &same).unwrap());
1747
1748 let off_by_one = vals(&[(":p", AttributeValue::NS(vec!["100000000000000002".into()]))]);
1749 let eq2 = parse("ns = :p").unwrap();
1750 assert!(!evaluate_without_tracking(&eq2, &item, &None, &off_by_one).unwrap());
1751 let ne = parse("ns <> :p").unwrap();
1752 assert!(evaluate_without_tracking(&ne, &item, &None, &off_by_one).unwrap());
1753 }
1754
1755 #[test]
1757 fn test_number_set_equality_normalises_members() {
1758 let item = make_item(&[("ns", AttributeValue::NS(vec!["1".into(), "2".into()]))]);
1759 let av = vals(&[(":p", AttributeValue::NS(vec!["1.0".into(), "2.00".into()]))]);
1760 let eq = parse("ns = :p").unwrap();
1761 assert!(evaluate_without_tracking(&eq, &item, &None, &av).unwrap());
1762 }
1763}