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