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::GeneratedEmbedding(v) => {
410 if v.vector.is_empty() {
411 Tri::False
412 } else {
413 Tri::True
414 }
415 }
416 Value::Interval { .. } => Tri::Unknown,
417 Value::Uuid(_) | Value::Json(_) => Tri::Unknown,
418 Value::Decimal(d) => {
419 if *d != 0 {
420 Tri::True
421 } else {
422 Tri::False
423 }
424 }
425 }
426 }
427}
428
429fn and3(a: Tri, b: Tri) -> Tri {
430 match (a, b) {
431 (Tri::False, _) | (_, Tri::False) => Tri::False,
432 (Tri::Unknown, _) | (_, Tri::Unknown) => Tri::Unknown,
433 _ => Tri::True,
434 }
435}
436
437fn or3(a: Tri, b: Tri) -> Tri {
438 match (a, b) {
439 (Tri::True, _) | (_, Tri::True) => Tri::True,
440 (Tri::Unknown, _) | (_, Tri::Unknown) => Tri::Unknown,
441 _ => Tri::False,
442 }
443}
444
445fn not3(a: Tri) -> Tri {
446 match a {
447 Tri::True => Tri::False,
448 Tri::False => Tri::True,
449 Tri::Unknown => Tri::Unknown,
450 }
451}
452
453fn compare(a: Value, b: Value, pred: impl Fn(Ordering) -> bool) -> Tri {
457 if matches!(a, Value::Null) || matches!(b, Value::Null) {
458 return Tri::Unknown;
459 }
460 match value_cmp(&a, &b) {
461 Some(o) => {
462 if pred(o) {
463 Tri::True
464 } else {
465 Tri::False
466 }
467 }
468 None => Tri::Unknown,
469 }
470}
471
472pub(crate) fn value_cmp(a: &Value, b: &Value) -> Option<Ordering> {
476 match (a, b) {
477 (Value::Null, Value::Null) => Some(Ordering::Equal),
478 (Value::Bool(x), Value::Bool(y)) => Some((*x as u8).cmp(&(*y as u8))),
479 (Value::Int64(x), Value::Int64(y)) => Some(x.cmp(y)),
480 (Value::Float64(x), Value::Float64(y)) => x.partial_cmp(y),
481 (Value::Int64(x), Value::Float64(y)) => (*x as f64).partial_cmp(y),
482 (Value::Float64(x), Value::Int64(y)) => x.partial_cmp(&(*y as f64)),
483 (Value::Bytes(x), Value::Bytes(y)) => Some(x.cmp(y)),
484 (x, y) if x.as_embedding().is_some() && y.as_embedding().is_some() => {
485 let x = x.as_embedding().unwrap();
486 let y = y.as_embedding().unwrap();
487 for (a, b) in x.iter().zip(y.iter()) {
488 match a.partial_cmp(b)? {
489 Ordering::Equal => continue,
490 non_eq => return Some(non_eq),
491 }
492 }
493 Some(x.len().cmp(&y.len()))
494 }
495 _ => None,
496 }
497}
498
499pub(crate) fn encode_composite_key(
504 columns: &[u16],
505 cells: &HashMap<u16, Value>,
506) -> Option<Vec<u8>> {
507 let mut out = Vec::new();
508 for cid in columns {
509 let v = cells.get(cid)?;
510 if matches!(v, Value::Null) {
511 return None;
512 }
513 let k = v.encode_key();
514 out.extend_from_slice(&(k.len() as u32).to_be_bytes());
516 out.extend_from_slice(&k);
517 }
518 Some(out)
519}
520
521pub(crate) fn validate_checks(
523 checks: &[CheckConstraint],
524 cells: &HashMap<u16, Value>,
525) -> Result<()> {
526 for c in checks {
527 if !c.expr.satisfied(cells) {
528 return Err(MongrelError::InvalidArgument(format!(
529 "CHECK constraint '{}' failed",
530 c.name
531 )));
532 }
533 }
534 Ok(())
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540
541 fn m(cols: &[(u16, Value)]) -> HashMap<u16, Value> {
542 cols.iter().cloned().collect()
543 }
544
545 #[test]
546 fn check_eq_literal() {
547 let e = CheckExpr::Eq(
549 Box::new(CheckExpr::Col(1)),
550 Box::new(CheckExpr::Lit(Value::Int64(5))),
551 );
552 assert!(e.satisfied(&m(&[(1, Value::Int64(5))])));
553 assert!(!e.satisfied(&m(&[(1, Value::Int64(6))])));
554 assert!(e.satisfied(&m(&[(1, Value::Null)])));
556 }
557
558 #[test]
559 fn check_range_and() {
560 let e = CheckExpr::And(
562 Box::new(CheckExpr::Ge(
563 Box::new(CheckExpr::Col(1)),
564 Box::new(CheckExpr::Lit(Value::Int64(0))),
565 )),
566 Box::new(CheckExpr::Le(
567 Box::new(CheckExpr::Col(1)),
568 Box::new(CheckExpr::Lit(Value::Int64(100))),
569 )),
570 );
571 assert!(e.satisfied(&m(&[(1, Value::Int64(50))])));
572 assert!(!e.satisfied(&m(&[(1, Value::Int64(101))])));
573 assert!(!e.satisfied(&m(&[(1, Value::Int64(-1))])));
574 }
575
576 #[test]
577 fn check_numeric_cross_type() {
578 let e = CheckExpr::Lt(
579 Box::new(CheckExpr::Col(1)),
580 Box::new(CheckExpr::Lit(Value::Float64(10.0))),
581 );
582 assert!(e.satisfied(&m(&[(1, Value::Int64(5))])));
583 assert!(!e.satisfied(&m(&[(1, Value::Int64(20))])));
584 }
585
586 #[test]
587 fn check_arithmetic_terms() {
588 let e = CheckExpr::Le(
589 Box::new(CheckExpr::Mul(
590 Box::new(CheckExpr::Add(
591 Box::new(CheckExpr::Col(1)),
592 Box::new(CheckExpr::Lit(Value::Int64(2))),
593 )),
594 Box::new(CheckExpr::Col(2)),
595 )),
596 Box::new(CheckExpr::Lit(Value::Int64(20))),
597 );
598 assert!(e.satisfied(&m(&[(1, Value::Int64(3)), (2, Value::Int64(4))])));
599 assert!(!e.satisfied(&m(&[(1, Value::Int64(4)), (2, Value::Int64(4))])));
600 }
601
602 #[test]
603 fn check_arithmetic_error_is_unknown() {
604 let divide_by_zero = CheckExpr::Eq(
605 Box::new(CheckExpr::Div(
606 Box::new(CheckExpr::Col(1)),
607 Box::new(CheckExpr::Lit(Value::Int64(0))),
608 )),
609 Box::new(CheckExpr::Lit(Value::Int64(1))),
610 );
611 assert!(divide_by_zero.satisfied(&m(&[(1, Value::Int64(10))])));
612
613 let overflow = CheckExpr::Gt(
614 Box::new(CheckExpr::Add(
615 Box::new(CheckExpr::Col(1)),
616 Box::new(CheckExpr::Lit(Value::Int64(1))),
617 )),
618 Box::new(CheckExpr::Lit(Value::Int64(0))),
619 );
620 assert!(overflow.satisfied(&m(&[(1, Value::Int64(i64::MAX))])));
621 }
622
623 #[test]
624 fn check_not_incomparable_is_unknown_passes() {
625 let e = CheckExpr::Eq(
627 Box::new(CheckExpr::Col(1)),
628 Box::new(CheckExpr::Lit(Value::Bytes(b"x".to_vec()))),
629 );
630 assert!(e.satisfied(&m(&[(1, Value::Int64(5))])));
631 }
632
633 #[test]
634 fn encode_composite_key_skips_null() {
635 let k = encode_composite_key(&[1, 2], &m(&[(1, Value::Int64(5)), (2, Value::Null)]));
636 assert!(k.is_none());
637 let k = encode_composite_key(&[1, 2], &m(&[(1, Value::Int64(5)), (2, Value::Int64(7))]));
638 assert!(k.is_some());
639 let k2 = encode_composite_key(&[1, 2], &m(&[(1, Value::Int64(6)), (2, Value::Int64(7))]));
641 assert_ne!(k.unwrap(), k2.unwrap());
642 }
643
644 fn regex_expr(col: u16, pattern: &str, negated: bool, ci: bool) -> CheckExpr {
645 CheckExpr::Regex {
646 col,
647 pattern: pattern.to_string(),
648 negated,
649 case_insensitive: ci,
650 cached: std::sync::OnceLock::new(),
651 }
652 }
653
654 #[test]
655 fn check_regex_match() {
656 let e = regex_expr(1, r"^a\w+z$", false, false);
657 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"abz".to_vec()))])));
658 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"alfredz".to_vec()))])));
659 assert!(!e.satisfied(&m(&[(1, Value::Bytes(b"abc".to_vec()))])));
660 }
661
662 #[test]
663 fn check_regex_null_is_unknown() {
664 let e = regex_expr(1, r"^\d+$", false, false);
665 assert!(e.satisfied(&m(&[(1, Value::Null)])));
666 assert!(e.satisfied(&m(&[]))); }
668
669 #[test]
670 fn check_regex_negated() {
671 let e = regex_expr(1, r"^\d+$", true, false);
672 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"abc".to_vec()))])));
673 assert!(!e.satisfied(&m(&[(1, Value::Bytes(b"123".to_vec()))])));
674 }
675
676 #[test]
677 fn check_regex_case_insensitive() {
678 let e = regex_expr(1, r"^hello$", false, true);
679 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"HELLO".to_vec()))])));
680 assert!(e.satisfied(&m(&[(1, Value::Bytes(b"Hello".to_vec()))])));
681 }
682
683 #[test]
684 fn check_regex_non_bytes_is_unknown() {
685 let e = regex_expr(1, r"\d+", false, false);
686 assert!(e.satisfied(&m(&[(1, Value::Int64(42))])));
688 }
689
690 #[test]
691 fn check_regex_validate_rejects_invalid_pattern() {
692 assert!(regex_expr(1, "[", false, false).validate().is_err());
693 assert!(regex_expr(1, r"^\d+$", false, false).validate().is_ok());
694 }
695
696 #[test]
697 fn check_regex_partial_eq_ignores_cache() {
698 let a = regex_expr(1, r"^\d+$", false, false);
699 let b = regex_expr(1, r"^\d+$", false, false);
700 assert_eq!(a, b);
701 a.satisfied(&m(&[(1, Value::Bytes(b"1".to_vec()))]));
703 assert_eq!(a, b);
704 }
705
706 #[test]
707 fn check_regex_serde_roundtrip() {
708 let e = regex_expr(1, r"^\w+@[\w.]+$", true, true);
709 let json = serde_json::to_string(&e).unwrap();
710 assert!(!json.contains("cached"));
712 let de: CheckExpr = serde_json::from_str(&json).unwrap();
713 assert_eq!(e, de);
714 assert!(de.satisfied(&m(&[(1, Value::Bytes(b"not-an-email".to_vec()))])));
716 assert!(!de.satisfied(&m(&[(1, Value::Bytes(b"a@b.com".to_vec()))])));
717 }
718
719 #[test]
720 fn check_regex_inside_logical_ops() {
721 let e = CheckExpr::And(
723 Box::new(regex_expr(1, r"^\d+$", false, false)),
724 Box::new(CheckExpr::IsNotNull(2)),
725 );
726 assert!(e.satisfied(&m(&[
727 (1, Value::Bytes(b"42".to_vec())),
728 (2, Value::Int64(1))
729 ])));
730 assert!(!e.satisfied(&m(&[(1, Value::Bytes(b"42".to_vec())), (2, Value::Null)])));
731 assert!(!e.satisfied(&m(&[
732 (1, Value::Bytes(b"abc".to_vec())),
733 (2, Value::Int64(1))
734 ])));
735 }
736}