1use crate::error::{MongrelError, Result};
16use crate::memtable::Value;
17use serde::{Deserialize, Serialize};
18use std::cmp::Ordering;
19use std::collections::HashMap;
20
21#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
23pub struct TableConstraints {
24 #[serde(default)]
25 pub uniques: Vec<UniqueConstraint>,
26 #[serde(default)]
27 pub foreign_keys: Vec<ForeignKey>,
28 #[serde(default)]
29 pub checks: Vec<CheckConstraint>,
30}
31
32impl TableConstraints {
33 pub fn is_empty(&self) -> bool {
34 self.uniques.is_empty() && self.foreign_keys.is_empty() && self.checks.is_empty()
35 }
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub struct UniqueConstraint {
43 pub id: u16,
44 pub name: String,
45 pub columns: Vec<u16>,
46}
47
48#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
51pub enum FkAction {
52 #[default]
54 Restrict,
55 Cascade,
57 SetNull,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
65pub struct ForeignKey {
66 pub id: u16,
67 pub name: String,
68 pub columns: Vec<u16>,
69 pub ref_table: String,
70 pub ref_columns: Vec<u16>,
71 #[serde(default)]
72 pub on_delete: FkAction,
73 #[serde(default)]
74 pub on_update: FkAction,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80pub struct CheckConstraint {
81 pub id: u16,
82 pub name: String,
83 pub expr: CheckExpr,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
96pub enum CheckExpr {
97 True,
99 Col(u16),
101 Lit(Value),
103 Add(Box<CheckExpr>, Box<CheckExpr>),
104 Sub(Box<CheckExpr>, Box<CheckExpr>),
105 Mul(Box<CheckExpr>, Box<CheckExpr>),
106 Div(Box<CheckExpr>, Box<CheckExpr>),
107 Mod(Box<CheckExpr>, Box<CheckExpr>),
108 IsNull(u16),
109 IsNotNull(u16),
110 Eq(Box<CheckExpr>, Box<CheckExpr>),
111 Ne(Box<CheckExpr>, Box<CheckExpr>),
112 Lt(Box<CheckExpr>, Box<CheckExpr>),
113 Le(Box<CheckExpr>, Box<CheckExpr>),
114 Gt(Box<CheckExpr>, Box<CheckExpr>),
115 Ge(Box<CheckExpr>, Box<CheckExpr>),
116 And(Box<CheckExpr>, Box<CheckExpr>),
117 Or(Box<CheckExpr>, Box<CheckExpr>),
118 Not(Box<CheckExpr>),
119 Regex {
126 col: u16,
127 pattern: String,
128 negated: bool,
129 case_insensitive: bool,
130 #[serde(skip)]
131 cached: std::sync::OnceLock<regex::Regex>,
132 },
133}
134
135impl PartialEq for CheckExpr {
136 fn eq(&self, other: &Self) -> bool {
137 match (self, other) {
138 (Self::True, Self::True) => true,
139 (Self::Col(a), Self::Col(b)) => a == b,
140 (Self::Lit(a), Self::Lit(b)) => a == b,
141 (Self::Add(a1, a2), Self::Add(b1, b2))
142 | (Self::Sub(a1, a2), Self::Sub(b1, b2))
143 | (Self::Mul(a1, a2), Self::Mul(b1, b2))
144 | (Self::Div(a1, a2), Self::Div(b1, b2))
145 | (Self::Mod(a1, a2), Self::Mod(b1, b2)) => a1 == b1 && a2 == b2,
146 (Self::IsNull(a), Self::IsNull(b)) => a == b,
147 (Self::IsNotNull(a), Self::IsNotNull(b)) => a == b,
148 (Self::Eq(a1, a2), Self::Eq(b1, b2)) => a1 == b1 && a2 == b2,
149 (Self::Ne(a1, a2), Self::Ne(b1, b2)) => a1 == b1 && a2 == b2,
150 (Self::Lt(a1, a2), Self::Lt(b1, b2)) => a1 == b1 && a2 == b2,
151 (Self::Le(a1, a2), Self::Le(b1, b2)) => a1 == b1 && a2 == b2,
152 (Self::Gt(a1, a2), Self::Gt(b1, b2)) => a1 == b1 && a2 == b2,
153 (Self::Ge(a1, a2), Self::Ge(b1, b2)) => a1 == b1 && a2 == b2,
154 (Self::And(a1, a2), Self::And(b1, b2)) => a1 == b1 && a2 == b2,
155 (Self::Or(a1, a2), Self::Or(b1, b2)) => a1 == b1 && a2 == b2,
156 (Self::Not(a), Self::Not(b)) => a == b,
157 (
158 Self::Regex {
159 col: ac,
160 pattern: ap,
161 negated: an,
162 case_insensitive: ai,
163 ..
164 },
165 Self::Regex {
166 col: bc,
167 pattern: bp,
168 negated: bn,
169 case_insensitive: bi,
170 ..
171 },
172 ) => ac == bc && ap == bp && an == bn && ai == bi,
173 _ => false,
174 }
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180enum Tri {
181 True,
182 False,
183 Unknown,
184}
185
186impl CheckExpr {
187 pub fn satisfied(&self, cells: &HashMap<u16, Value>) -> bool {
190 !matches!(self.eval(cells), Tri::False)
191 }
192
193 pub fn validate(&self) -> Result<()> {
198 match self {
199 CheckExpr::Eq(a, b)
200 | CheckExpr::Ne(a, b)
201 | CheckExpr::Lt(a, b)
202 | CheckExpr::Le(a, b)
203 | CheckExpr::Gt(a, b)
204 | CheckExpr::Ge(a, b)
205 | CheckExpr::And(a, b)
206 | CheckExpr::Or(a, b)
207 | CheckExpr::Add(a, b)
208 | CheckExpr::Sub(a, b)
209 | CheckExpr::Mul(a, b)
210 | CheckExpr::Div(a, b)
211 | CheckExpr::Mod(a, b) => {
212 a.validate()?;
213 b.validate()?;
214 }
215 CheckExpr::Not(a) => a.validate()?,
216 CheckExpr::Regex {
217 pattern,
218 case_insensitive,
219 ..
220 } => {
221 let mut builder = regex::RegexBuilder::new(pattern);
222 builder.case_insensitive(*case_insensitive);
223 builder.build().map_err(|e| {
224 MongrelError::InvalidArgument(format!("invalid regex pattern: {e}"))
225 })?;
226 }
227 _ => {}
228 }
229 Ok(())
230 }
231
232 fn eval(&self, cells: &HashMap<u16, Value>) -> Tri {
233 match self {
234 CheckExpr::True => Tri::True,
235 CheckExpr::Col(id) => match cells.get(id) {
236 None | Some(Value::Null) => Tri::Unknown,
237 Some(v) => Tri::from_truthy(v),
238 },
239 CheckExpr::Lit(v) => Tri::from_truthy(v),
240 CheckExpr::Add(_, _)
241 | CheckExpr::Sub(_, _)
242 | CheckExpr::Mul(_, _)
243 | CheckExpr::Div(_, _)
244 | CheckExpr::Mod(_, _) => Tri::from_truthy(&self.eval_term(cells)),
245 CheckExpr::IsNull(id) => match cells.get(id) {
246 None | Some(Value::Null) => Tri::True,
247 Some(_) => Tri::False,
248 },
249 CheckExpr::IsNotNull(id) => match cells.get(id) {
250 None | Some(Value::Null) => Tri::False,
251 Some(_) => Tri::True,
252 },
253 CheckExpr::Eq(a, b) => compare(a.eval_term(cells), b.eval_term(cells), |o| {
254 o == Ordering::Equal
255 }),
256 CheckExpr::Ne(a, b) => compare(a.eval_term(cells), b.eval_term(cells), |o| {
257 o != Ordering::Equal
258 }),
259 CheckExpr::Lt(a, b) => compare(a.eval_term(cells), b.eval_term(cells), |o| {
260 o == Ordering::Less
261 }),
262 CheckExpr::Le(a, b) => compare(a.eval_term(cells), b.eval_term(cells), |o| {
263 o != Ordering::Greater
264 }),
265 CheckExpr::Gt(a, b) => compare(a.eval_term(cells), b.eval_term(cells), |o| {
266 o == Ordering::Greater
267 }),
268 CheckExpr::Ge(a, b) => compare(a.eval_term(cells), b.eval_term(cells), |o| {
269 o != Ordering::Less
270 }),
271 CheckExpr::And(a, b) => and3(a.eval(cells), b.eval(cells)),
272 CheckExpr::Or(a, b) => or3(a.eval(cells), b.eval(cells)),
273 CheckExpr::Not(a) => not3(a.eval(cells)),
274 CheckExpr::Regex {
275 col,
276 pattern,
277 negated,
278 case_insensitive,
279 cached,
280 } => match cells.get(col) {
281 None | Some(Value::Null) => Tri::Unknown,
282 Some(Value::Bytes(b)) => {
283 let re = cached.get_or_init(|| {
284 let mut builder = regex::RegexBuilder::new(pattern);
285 builder.case_insensitive(*case_insensitive);
286 builder
290 .build()
291 .unwrap_or_else(|_| regex::Regex::new("$^").unwrap())
292 });
293 let matched = re.is_match(std::str::from_utf8(b).unwrap_or(""));
294 match (*negated, matched) {
295 (false, true) | (true, false) => Tri::True,
296 (false, false) | (true, true) => Tri::False,
297 }
298 }
299 Some(_) => Tri::Unknown,
301 },
302 }
303 }
304
305 fn eval_term(&self, cells: &HashMap<u16, Value>) -> Value {
308 match self {
309 CheckExpr::Col(id) => cells.get(id).cloned().unwrap_or(Value::Null),
310 CheckExpr::Lit(v) => v.clone(),
311 CheckExpr::Add(a, b) => arithmetic(a.eval_term(cells), b.eval_term(cells), Arith::Add),
312 CheckExpr::Sub(a, b) => arithmetic(a.eval_term(cells), b.eval_term(cells), Arith::Sub),
313 CheckExpr::Mul(a, b) => arithmetic(a.eval_term(cells), b.eval_term(cells), Arith::Mul),
314 CheckExpr::Div(a, b) => arithmetic(a.eval_term(cells), b.eval_term(cells), Arith::Div),
315 CheckExpr::Mod(a, b) => arithmetic(a.eval_term(cells), b.eval_term(cells), Arith::Mod),
316 other => match other.eval(cells) {
317 Tri::True => Value::Int64(1),
318 Tri::False | Tri::Unknown => Value::Null,
319 },
320 }
321 }
322}
323
324#[derive(Clone, Copy)]
325enum Arith {
326 Add,
327 Sub,
328 Mul,
329 Div,
330 Mod,
331}
332
333fn arithmetic(left: Value, right: Value, op: Arith) -> Value {
334 match (left, right) {
335 (Value::Int64(left), Value::Int64(right)) => {
336 let value = match op {
337 Arith::Add => left.checked_add(right),
338 Arith::Sub => left.checked_sub(right),
339 Arith::Mul => left.checked_mul(right),
340 Arith::Div => left.checked_div(right),
341 Arith::Mod => left.checked_rem(right),
342 };
343 value.map(Value::Int64).unwrap_or(Value::Null)
344 }
345 (Value::Int64(left), Value::Float64(right)) => arithmetic_float(left as f64, right, op),
346 (Value::Float64(left), Value::Int64(right)) => arithmetic_float(left, right as f64, op),
347 (Value::Float64(left), Value::Float64(right)) => arithmetic_float(left, right, op),
348 _ => Value::Null,
349 }
350}
351
352fn arithmetic_float(left: f64, right: f64, op: Arith) -> Value {
353 if matches!(op, Arith::Div | Arith::Mod) && right == 0.0 {
354 return Value::Null;
355 }
356 let value = match op {
357 Arith::Add => left + right,
358 Arith::Sub => left - right,
359 Arith::Mul => left * right,
360 Arith::Div => left / right,
361 Arith::Mod => left % right,
362 };
363 if value.is_finite() {
364 Value::Float64(value)
365 } else {
366 Value::Null
367 }
368}
369
370impl Tri {
371 fn from_truthy(v: &Value) -> Tri {
372 match v {
373 Value::Null => Tri::Unknown,
374 Value::Bool(b) => {
375 if *b {
376 Tri::True
377 } else {
378 Tri::False
379 }
380 }
381 Value::Int64(n) => {
382 if *n != 0 {
383 Tri::True
384 } else {
385 Tri::False
386 }
387 }
388 Value::Float64(f) => {
389 if *f != 0.0 {
390 Tri::True
391 } else {
392 Tri::False
393 }
394 }
395 Value::Bytes(b) => {
396 if b.is_empty() {
397 Tri::False
398 } else {
399 Tri::True
400 }
401 }
402 Value::Embedding(v) => {
403 if v.is_empty() {
404 Tri::False
405 } else {
406 Tri::True
407 }
408 }
409 Value::Interval { .. } => Tri::Unknown,
410 Value::Uuid(_) | Value::Json(_) => Tri::Unknown,
411 Value::Decimal(d) => {
412 if *d != 0 {
413 Tri::True
414 } else {
415 Tri::False
416 }
417 }
418 }
419 }
420}
421
422fn and3(a: Tri, b: Tri) -> Tri {
423 match (a, b) {
424 (Tri::False, _) | (_, Tri::False) => Tri::False,
425 (Tri::Unknown, _) | (_, Tri::Unknown) => Tri::Unknown,
426 _ => Tri::True,
427 }
428}
429
430fn or3(a: Tri, b: Tri) -> Tri {
431 match (a, b) {
432 (Tri::True, _) | (_, Tri::True) => Tri::True,
433 (Tri::Unknown, _) | (_, Tri::Unknown) => Tri::Unknown,
434 _ => Tri::False,
435 }
436}
437
438fn not3(a: Tri) -> Tri {
439 match a {
440 Tri::True => Tri::False,
441 Tri::False => Tri::True,
442 Tri::Unknown => Tri::Unknown,
443 }
444}
445
446fn compare(a: Value, b: Value, pred: impl Fn(Ordering) -> bool) -> Tri {
450 if matches!(a, Value::Null) || matches!(b, Value::Null) {
451 return Tri::Unknown;
452 }
453 match value_cmp(&a, &b) {
454 Some(o) => {
455 if pred(o) {
456 Tri::True
457 } else {
458 Tri::False
459 }
460 }
461 None => Tri::Unknown,
462 }
463}
464
465pub(crate) fn value_cmp(a: &Value, b: &Value) -> Option<Ordering> {
469 match (a, b) {
470 (Value::Null, Value::Null) => Some(Ordering::Equal),
471 (Value::Bool(x), Value::Bool(y)) => Some((*x as u8).cmp(&(*y as u8))),
472 (Value::Int64(x), Value::Int64(y)) => Some(x.cmp(y)),
473 (Value::Float64(x), Value::Float64(y)) => x.partial_cmp(y),
474 (Value::Int64(x), Value::Float64(y)) => (*x as f64).partial_cmp(y),
475 (Value::Float64(x), Value::Int64(y)) => x.partial_cmp(&(*y as f64)),
476 (Value::Bytes(x), Value::Bytes(y)) => Some(x.cmp(y)),
477 (Value::Embedding(x), Value::Embedding(y)) => {
478 for (a, b) in x.iter().zip(y.iter()) {
479 match a.partial_cmp(b)? {
480 Ordering::Equal => continue,
481 non_eq => return Some(non_eq),
482 }
483 }
484 Some(x.len().cmp(&y.len()))
485 }
486 _ => None,
487 }
488}
489
490pub(crate) fn encode_composite_key(
495 columns: &[u16],
496 cells: &HashMap<u16, Value>,
497) -> Option<Vec<u8>> {
498 let mut out = Vec::new();
499 for cid in columns {
500 let v = cells.get(cid)?;
501 if matches!(v, Value::Null) {
502 return None;
503 }
504 let k = v.encode_key();
505 out.extend_from_slice(&(k.len() as u32).to_be_bytes());
507 out.extend_from_slice(&k);
508 }
509 Some(out)
510}
511
512pub(crate) fn validate_checks(
514 checks: &[CheckConstraint],
515 cells: &HashMap<u16, Value>,
516) -> Result<()> {
517 for c in checks {
518 if !c.expr.satisfied(cells) {
519 return Err(MongrelError::InvalidArgument(format!(
520 "CHECK constraint '{}' failed",
521 c.name
522 )));
523 }
524 }
525 Ok(())
526}
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531
532 fn m(cols: &[(u16, Value)]) -> HashMap<u16, Value> {
533 cols.iter().cloned().collect()
534 }
535
536 #[test]
537 fn check_eq_literal() {
538 let e = CheckExpr::Eq(
540 Box::new(CheckExpr::Col(1)),
541 Box::new(CheckExpr::Lit(Value::Int64(5))),
542 );
543 assert!(e.satisfied(&m(&[(1, Value::Int64(5))])));
544 assert!(!e.satisfied(&m(&[(1, Value::Int64(6))])));
545 assert!(e.satisfied(&m(&[(1, Value::Null)])));
547 }
548
549 #[test]
550 fn check_range_and() {
551 let e = CheckExpr::And(
553 Box::new(CheckExpr::Ge(
554 Box::new(CheckExpr::Col(1)),
555 Box::new(CheckExpr::Lit(Value::Int64(0))),
556 )),
557 Box::new(CheckExpr::Le(
558 Box::new(CheckExpr::Col(1)),
559 Box::new(CheckExpr::Lit(Value::Int64(100))),
560 )),
561 );
562 assert!(e.satisfied(&m(&[(1, Value::Int64(50))])));
563 assert!(!e.satisfied(&m(&[(1, Value::Int64(101))])));
564 assert!(!e.satisfied(&m(&[(1, Value::Int64(-1))])));
565 }
566
567 #[test]
568 fn check_numeric_cross_type() {
569 let e = CheckExpr::Lt(
570 Box::new(CheckExpr::Col(1)),
571 Box::new(CheckExpr::Lit(Value::Float64(10.0))),
572 );
573 assert!(e.satisfied(&m(&[(1, Value::Int64(5))])));
574 assert!(!e.satisfied(&m(&[(1, Value::Int64(20))])));
575 }
576
577 #[test]
578 fn check_arithmetic_terms() {
579 let e = CheckExpr::Le(
580 Box::new(CheckExpr::Mul(
581 Box::new(CheckExpr::Add(
582 Box::new(CheckExpr::Col(1)),
583 Box::new(CheckExpr::Lit(Value::Int64(2))),
584 )),
585 Box::new(CheckExpr::Col(2)),
586 )),
587 Box::new(CheckExpr::Lit(Value::Int64(20))),
588 );
589 assert!(e.satisfied(&m(&[(1, Value::Int64(3)), (2, Value::Int64(4))])));
590 assert!(!e.satisfied(&m(&[(1, Value::Int64(4)), (2, Value::Int64(4))])));
591 }
592
593 #[test]
594 fn check_arithmetic_error_is_unknown() {
595 let divide_by_zero = CheckExpr::Eq(
596 Box::new(CheckExpr::Div(
597 Box::new(CheckExpr::Col(1)),
598 Box::new(CheckExpr::Lit(Value::Int64(0))),
599 )),
600 Box::new(CheckExpr::Lit(Value::Int64(1))),
601 );
602 assert!(divide_by_zero.satisfied(&m(&[(1, Value::Int64(10))])));
603
604 let overflow = CheckExpr::Gt(
605 Box::new(CheckExpr::Add(
606 Box::new(CheckExpr::Col(1)),
607 Box::new(CheckExpr::Lit(Value::Int64(1))),
608 )),
609 Box::new(CheckExpr::Lit(Value::Int64(0))),
610 );
611 assert!(overflow.satisfied(&m(&[(1, Value::Int64(i64::MAX))])));
612 }
613
614 #[test]
615 fn check_not_incomparable_is_unknown_passes() {
616 let e = CheckExpr::Eq(
618 Box::new(CheckExpr::Col(1)),
619 Box::new(CheckExpr::Lit(Value::Bytes(b"x".to_vec()))),
620 );
621 assert!(e.satisfied(&m(&[(1, Value::Int64(5))])));
622 }
623
624 #[test]
625 fn encode_composite_key_skips_null() {
626 let k = encode_composite_key(&[1, 2], &m(&[(1, Value::Int64(5)), (2, Value::Null)]));
627 assert!(k.is_none());
628 let k = encode_composite_key(&[1, 2], &m(&[(1, Value::Int64(5)), (2, Value::Int64(7))]));
629 assert!(k.is_some());
630 let k2 = encode_composite_key(&[1, 2], &m(&[(1, Value::Int64(6)), (2, Value::Int64(7))]));
632 assert_ne!(k.unwrap(), k2.unwrap());
633 }
634
635 fn regex_expr(col: u16, pattern: &str, negated: bool, ci: bool) -> CheckExpr {
636 CheckExpr::Regex {
637 col,
638 pattern: pattern.to_string(),
639 negated,
640 case_insensitive: ci,
641 cached: std::sync::OnceLock::new(),
642 }
643 }
644
645 #[test]
646 fn check_regex_match() {
647 let e = regex_expr(1, r"^a\w+z$", false, false);
648 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"abz".to_vec()))])));
649 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"alfredz".to_vec()))])));
650 assert!(!e.satisfied(&m(&[(1, Value::Bytes(b"abc".to_vec()))])));
651 }
652
653 #[test]
654 fn check_regex_null_is_unknown() {
655 let e = regex_expr(1, r"^\d+$", false, false);
656 assert!(e.satisfied(&m(&[(1, Value::Null)])));
657 assert!(e.satisfied(&m(&[]))); }
659
660 #[test]
661 fn check_regex_negated() {
662 let e = regex_expr(1, r"^\d+$", true, false);
663 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"abc".to_vec()))])));
664 assert!(!e.satisfied(&m(&[(1, Value::Bytes(b"123".to_vec()))])));
665 }
666
667 #[test]
668 fn check_regex_case_insensitive() {
669 let e = regex_expr(1, r"^hello$", false, true);
670 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"HELLO".to_vec()))])));
671 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"Hello".to_vec()))])));
672 }
673
674 #[test]
675 fn check_regex_non_bytes_is_unknown() {
676 let e = regex_expr(1, r"\d+", false, false);
677 assert!(e.satisfied(&m(&[(1, Value::Int64(42))])));
679 }
680
681 #[test]
682 fn check_regex_validate_rejects_invalid_pattern() {
683 assert!(regex_expr(1, "[", false, false).validate().is_err());
684 assert!(regex_expr(1, r"^\d+$", false, false).validate().is_ok());
685 }
686
687 #[test]
688 fn check_regex_partial_eq_ignores_cache() {
689 let a = regex_expr(1, r"^\d+$", false, false);
690 let b = regex_expr(1, r"^\d+$", false, false);
691 assert_eq!(a, b);
692 a.satisfied(&m(&[(1, Value::Bytes(b"1".to_vec()))]));
694 assert_eq!(a, b);
695 }
696
697 #[test]
698 fn check_regex_serde_roundtrip() {
699 let e = regex_expr(1, r"^\w+@[\w.]+$", true, true);
700 let json = serde_json::to_string(&e).unwrap();
701 assert!(!json.contains("cached"));
703 let de: CheckExpr = serde_json::from_str(&json).unwrap();
704 assert_eq!(e, de);
705 assert!(de.satisfied(&m(&[(1, Value::Bytes(b"not-an-email".to_vec()))])));
707 assert!(!de.satisfied(&m(&[(1, Value::Bytes(b"a@b.com".to_vec()))])));
708 }
709
710 #[test]
711 fn check_regex_inside_logical_ops() {
712 let e = CheckExpr::And(
714 Box::new(regex_expr(1, r"^\d+$", false, false)),
715 Box::new(CheckExpr::IsNotNull(2)),
716 );
717 assert!(e.satisfied(&m(&[
718 (1, Value::Bytes(b"42".to_vec())),
719 (2, Value::Int64(1))
720 ])));
721 assert!(!e.satisfied(&m(&[(1, Value::Bytes(b"42".to_vec())), (2, Value::Null)])));
722 assert!(!e.satisfied(&m(&[
723 (1, Value::Bytes(b"abc".to_vec())),
724 (2, Value::Int64(1))
725 ])));
726 }
727}