1use std::{collections::BTreeSet, ops::Not};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub struct BindingId(u32);
22
23impl BindingId {
24 pub const fn new(value: u32) -> Self {
25 Self(value)
26 }
27
28 pub const fn get(self) -> u32 {
29 self.0
30 }
31}
32
33#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct BindingSet(BTreeSet<BindingId>);
36
37impl BindingSet {
38 pub const fn new() -> Self {
39 Self(BTreeSet::new())
40 }
41
42 pub fn insert(&mut self, binding: BindingId) -> bool {
43 self.0.insert(binding)
44 }
45
46 pub fn contains(&self, binding: BindingId) -> bool {
47 self.0.contains(&binding)
48 }
49
50 pub fn is_empty(&self) -> bool {
51 self.0.is_empty()
52 }
53
54 pub fn len(&self) -> usize {
55 self.0.len()
56 }
57
58 pub fn is_subset(&self, other: &Self) -> bool {
59 self.0.is_subset(&other.0)
60 }
61
62 pub fn intersects(&self, other: &Self) -> bool {
63 self.0.iter().any(|binding| other.contains(*binding))
64 }
65
66 pub fn iter(&self) -> impl Iterator<Item = BindingId> + '_ {
67 self.0.iter().copied()
68 }
69
70 pub fn without(&self, binding: BindingId) -> Self {
71 Self(
72 self.0
73 .iter()
74 .copied()
75 .filter(|candidate| *candidate != binding)
76 .collect(),
77 )
78 }
79
80 fn extend(&mut self, other: &Self) {
81 self.0.extend(other.iter());
82 }
83}
84
85impl From<BindingId> for BindingSet {
86 fn from(binding: BindingId) -> Self {
87 Self::from([binding])
88 }
89}
90
91impl<const N: usize> From<[BindingId; N]> for BindingSet {
92 fn from(bindings: [BindingId; N]) -> Self {
93 Self(bindings.into_iter().collect())
94 }
95}
96
97impl FromIterator<BindingId> for BindingSet {
98 fn from_iter<T: IntoIterator<Item = BindingId>>(iter: T) -> Self {
99 Self(iter.into_iter().collect())
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
105pub enum TruthValue {
106 True,
107 False,
108 Unknown,
109}
110
111impl TruthValue {
112 pub const fn and(self, other: Self) -> Self {
113 match (self, other) {
114 (Self::False, _) | (_, Self::False) => Self::False,
115 (Self::True, Self::True) => Self::True,
116 _ => Self::Unknown,
117 }
118 }
119
120 pub const fn or(self, other: Self) -> Self {
121 match (self, other) {
122 (Self::True, _) | (_, Self::True) => Self::True,
123 (Self::False, Self::False) => Self::False,
124 _ => Self::Unknown,
125 }
126 }
127}
128
129impl Not for TruthValue {
130 type Output = Self;
131
132 fn not(self) -> Self::Output {
133 match self {
134 Self::True => Self::False,
135 Self::False => Self::True,
136 Self::Unknown => Self::Unknown,
137 }
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
143pub struct TruthSet(u8);
144
145impl TruthSet {
146 const TRUE_BIT: u8 = 1 << 0;
147 const FALSE_BIT: u8 = 1 << 1;
148 const UNKNOWN_BIT: u8 = 1 << 2;
149
150 pub const fn empty() -> Self {
151 Self(0)
152 }
153
154 pub const fn all() -> Self {
155 Self(Self::TRUE_BIT | Self::FALSE_BIT | Self::UNKNOWN_BIT)
156 }
157
158 pub const fn singleton(value: TruthValue) -> Self {
159 Self(Self::bit(value))
160 }
161
162 pub const fn contains(self, value: TruthValue) -> bool {
163 self.0 & Self::bit(value) != 0
164 }
165
166 pub const fn union(self, other: Self) -> Self {
167 Self(self.0 | other.0)
168 }
169
170 pub fn and(self, other: Self) -> Self {
171 self.combine(other, TruthValue::and)
172 }
173
174 pub fn or(self, other: Self) -> Self {
175 self.combine(other, TruthValue::or)
176 }
177
178 const fn bit(value: TruthValue) -> u8 {
179 match value {
180 TruthValue::True => Self::TRUE_BIT,
181 TruthValue::False => Self::FALSE_BIT,
182 TruthValue::Unknown => Self::UNKNOWN_BIT,
183 }
184 }
185
186 fn map(self, operation: impl Fn(TruthValue) -> TruthValue) -> Self {
187 let mut result = Self::empty();
188 for value in truth_values() {
189 if self.contains(value) {
190 result = result.union(Self::singleton(operation(value)));
191 }
192 }
193 result
194 }
195
196 fn combine(
197 self,
198 other: Self,
199 operation: impl Fn(TruthValue, TruthValue) -> TruthValue,
200 ) -> Self {
201 let mut result = Self::empty();
202 for left in truth_values() {
203 if !self.contains(left) {
204 continue;
205 }
206 for right in truth_values() {
207 if other.contains(right) {
208 result = result.union(Self::singleton(operation(left, right)));
209 }
210 }
211 }
212 result
213 }
214}
215
216impl Not for TruthSet {
217 type Output = Self;
218
219 fn not(self) -> Self::Output {
220 self.map(Not::not)
221 }
222}
223
224fn truth_values() -> [TruthValue; 3] {
225 [TruthValue::True, TruthValue::False, TruthValue::Unknown]
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum ScalarNullability {
231 AlwaysNull,
232 AlwaysNonNull,
233 MaybeNull,
234}
235
236impl ScalarNullability {
237 pub const fn can_be_null(self) -> bool {
238 matches!(self, Self::AlwaysNull | Self::MaybeNull)
239 }
240
241 pub const fn can_be_non_null(self) -> bool {
242 matches!(self, Self::AlwaysNonNull | Self::MaybeNull)
243 }
244
245 fn from_possibilities(can_be_null: bool, can_be_non_null: bool) -> Self {
246 match (can_be_null, can_be_non_null) {
247 (true, false) => Self::AlwaysNull,
248 (false, true) => Self::AlwaysNonNull,
249 (true, true) => Self::MaybeNull,
250 (false, false) => unreachable!("a scalar expression must have a possible value"),
251 }
252 }
253}
254
255#[derive(Debug, Clone, PartialEq, Eq)]
261pub enum BoundScalarExpression {
262 BindingValue {
263 binding: BindingId,
264 nullability: ScalarNullability,
265 },
266 LiteralNull,
267 LiteralNonNull,
268 Parameter {
269 nullability: ScalarNullability,
270 },
271 StrictCall {
272 arguments: Vec<Self>,
273 result_nullability_when_arguments_non_null: ScalarNullability,
274 },
275 Coalesce(Vec<Self>),
276 Opaque {
277 referenced_bindings: BindingSet,
278 nullability: ScalarNullability,
279 },
280}
281
282impl BoundScalarExpression {
283 pub fn referenced_bindings(&self) -> BindingSet {
284 match self {
285 Self::BindingValue { binding, .. } => (*binding).into(),
286 Self::LiteralNull | Self::LiteralNonNull | Self::Parameter { .. } => BindingSet::new(),
287 Self::StrictCall { arguments, .. } | Self::Coalesce(arguments) => {
288 referenced_scalar_bindings(arguments)
289 }
290 Self::Opaque {
291 referenced_bindings,
292 ..
293 } => referenced_bindings.clone(),
294 }
295 }
296
297 fn nullability_when_null_extended(&self, null_bindings: &BindingSet) -> ScalarNullability {
298 match self {
299 Self::BindingValue {
300 binding,
301 nullability,
302 } => {
303 if null_bindings.contains(*binding) {
304 ScalarNullability::AlwaysNull
305 } else {
306 *nullability
307 }
308 }
309 Self::LiteralNull => ScalarNullability::AlwaysNull,
310 Self::LiteralNonNull => ScalarNullability::AlwaysNonNull,
311 Self::Parameter { nullability } => *nullability,
312 Self::StrictCall {
313 arguments,
314 result_nullability_when_arguments_non_null,
315 } => strict_call_nullability(
316 arguments,
317 *result_nullability_when_arguments_non_null,
318 null_bindings,
319 ),
320 Self::Coalesce(arguments) => {
321 let nullabilities = arguments
322 .iter()
323 .map(|argument| argument.nullability_when_null_extended(null_bindings));
324 ScalarNullability::from_possibilities(
325 nullabilities.clone().all(ScalarNullability::can_be_null),
326 nullabilities
327 .clone()
328 .any(ScalarNullability::can_be_non_null),
329 )
330 }
331 Self::Opaque { nullability, .. } => *nullability,
332 }
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Eq)]
338pub enum BoundPredicate {
339 And(Vec<Self>),
340 Or(Vec<Self>),
341 Not(Box<Self>),
342 Constant(TruthValue),
343 Comparison {
344 left: BoundScalarExpression,
345 right: BoundScalarExpression,
346 },
347 IsNull(BoundScalarExpression),
348 IsNotNull(BoundScalarExpression),
349 InList {
350 expression: BoundScalarExpression,
351 values: Vec<BoundScalarExpression>,
352 negated: bool,
353 },
354 IsDistinctFrom {
355 left: BoundScalarExpression,
356 right: BoundScalarExpression,
357 },
358 IsNotDistinctFrom {
359 left: BoundScalarExpression,
360 right: BoundScalarExpression,
361 },
362 BindingsPresent {
367 required_bindings: BindingSet,
368 },
369 Opaque {
370 referenced_bindings: BindingSet,
371 },
372}
373
374impl BoundPredicate {
375 pub fn referenced_bindings(&self) -> BindingSet {
376 match self {
377 Self::And(predicates) | Self::Or(predicates) => {
378 let mut bindings = BindingSet::new();
379 for predicate in predicates {
380 bindings.extend(&predicate.referenced_bindings());
381 }
382 bindings
383 }
384 Self::Not(predicate) => predicate.referenced_bindings(),
385 Self::Constant(_) => BindingSet::new(),
386 Self::Comparison { left, right }
387 | Self::IsDistinctFrom { left, right }
388 | Self::IsNotDistinctFrom { left, right } => binary_scalar_bindings(left, right),
389 Self::IsNull(expression) | Self::IsNotNull(expression) => {
390 expression.referenced_bindings()
391 }
392 Self::InList {
393 expression, values, ..
394 } => {
395 let mut bindings = expression.referenced_bindings();
396 bindings.extend(&referenced_scalar_bindings(values));
397 bindings
398 }
399 Self::BindingsPresent { required_bindings }
400 | Self::Opaque {
401 referenced_bindings: required_bindings,
402 } => required_bindings.clone(),
403 }
404 }
405
406 fn possible_truths_when_null_extended(&self, null_bindings: &BindingSet) -> TruthSet {
407 match self {
408 Self::And(predicates) => predicates.iter().fold(
409 TruthSet::singleton(TruthValue::True),
410 |truths, predicate| {
411 truths.and(predicate.possible_truths_when_null_extended(null_bindings))
412 },
413 ),
414 Self::Or(predicates) => predicates.iter().fold(
415 TruthSet::singleton(TruthValue::False),
416 |truths, predicate| {
417 truths.or(predicate.possible_truths_when_null_extended(null_bindings))
418 },
419 ),
420 Self::Not(predicate) => predicate
421 .possible_truths_when_null_extended(null_bindings)
422 .not(),
423 Self::Constant(value) => TruthSet::singleton(*value),
424 Self::Comparison { left, right } => strict_comparison_truths(
425 left.nullability_when_null_extended(null_bindings),
426 right.nullability_when_null_extended(null_bindings),
427 ),
428 Self::IsNull(expression) => {
429 is_null_truths(expression.nullability_when_null_extended(null_bindings))
430 }
431 Self::IsNotNull(expression) => {
432 !is_null_truths(expression.nullability_when_null_extended(null_bindings))
433 }
434 Self::InList {
435 expression,
436 values,
437 negated,
438 } => {
439 let truths = in_list_truths(expression, values, null_bindings);
440 if *negated {
441 !truths
442 } else {
443 truths
444 }
445 }
446 Self::IsDistinctFrom { left, right } => null_safe_comparison_truths(
447 left.nullability_when_null_extended(null_bindings),
448 right.nullability_when_null_extended(null_bindings),
449 ),
450 Self::IsNotDistinctFrom { left, right } => null_safe_comparison_truths(
451 left.nullability_when_null_extended(null_bindings),
452 right.nullability_when_null_extended(null_bindings),
453 )
454 .not(),
455 Self::BindingsPresent { required_bindings } => {
456 if required_bindings.intersects(null_bindings) {
457 TruthSet::singleton(TruthValue::False)
458 } else {
459 TruthSet::singleton(TruthValue::True)
460 .union(TruthSet::singleton(TruthValue::False))
461 }
462 }
463 Self::Opaque { .. } => TruthSet::all(),
464 }
465 }
466}
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
474pub enum NullRejectionProof {
475 Proven { possible_truths: TruthSet },
476 NotProven { possible_truths: TruthSet },
477}
478
479impl NullRejectionProof {
480 pub const fn is_proven(self) -> bool {
481 matches!(self, Self::Proven { .. })
482 }
483
484 pub const fn possible_truths(self) -> TruthSet {
485 match self {
486 Self::Proven { possible_truths } | Self::NotProven { possible_truths } => {
487 possible_truths
488 }
489 }
490 }
491}
492
493pub fn prove_null_rejecting(
498 predicate: &BoundPredicate,
499 null_bindings: &BindingSet,
500) -> NullRejectionProof {
501 let possible_truths = predicate.possible_truths_when_null_extended(null_bindings);
502 if possible_truths.contains(TruthValue::True) {
503 NullRejectionProof::NotProven { possible_truths }
504 } else {
505 NullRejectionProof::Proven { possible_truths }
506 }
507}
508
509fn referenced_scalar_bindings(expressions: &[BoundScalarExpression]) -> BindingSet {
510 let mut bindings = BindingSet::new();
511 for expression in expressions {
512 bindings.extend(&expression.referenced_bindings());
513 }
514 bindings
515}
516
517fn binary_scalar_bindings(
518 left: &BoundScalarExpression,
519 right: &BoundScalarExpression,
520) -> BindingSet {
521 let mut bindings = left.referenced_bindings();
522 bindings.extend(&right.referenced_bindings());
523 bindings
524}
525
526fn strict_call_nullability(
527 arguments: &[BoundScalarExpression],
528 result_when_non_null: ScalarNullability,
529 null_bindings: &BindingSet,
530) -> ScalarNullability {
531 let nullabilities = arguments
532 .iter()
533 .map(|argument| argument.nullability_when_null_extended(null_bindings));
534 let arguments_can_all_be_non_null = nullabilities
535 .clone()
536 .all(ScalarNullability::can_be_non_null);
537 if !arguments_can_all_be_non_null {
538 return ScalarNullability::AlwaysNull;
539 }
540
541 let an_argument_can_be_null = nullabilities.clone().any(ScalarNullability::can_be_null);
542 ScalarNullability::from_possibilities(
543 an_argument_can_be_null || result_when_non_null.can_be_null(),
544 result_when_non_null.can_be_non_null(),
545 )
546}
547
548fn strict_comparison_truths(left: ScalarNullability, right: ScalarNullability) -> TruthSet {
549 let mut truths = TruthSet::empty();
550 if left.can_be_null() || right.can_be_null() {
551 truths = truths.union(TruthSet::singleton(TruthValue::Unknown));
552 }
553 if left.can_be_non_null() && right.can_be_non_null() {
554 truths = truths
555 .union(TruthSet::singleton(TruthValue::True))
556 .union(TruthSet::singleton(TruthValue::False));
557 }
558 truths
559}
560
561fn is_null_truths(nullability: ScalarNullability) -> TruthSet {
562 let mut truths = TruthSet::empty();
563 if nullability.can_be_null() {
564 truths = truths.union(TruthSet::singleton(TruthValue::True));
565 }
566 if nullability.can_be_non_null() {
567 truths = truths.union(TruthSet::singleton(TruthValue::False));
568 }
569 truths
570}
571
572fn in_list_truths(
573 expression: &BoundScalarExpression,
574 values: &[BoundScalarExpression],
575 null_bindings: &BindingSet,
576) -> TruthSet {
577 let expression = expression.nullability_when_null_extended(null_bindings);
578 let value_nullabilities: Vec<_> = values
579 .iter()
580 .map(|value| value.nullability_when_null_extended(null_bindings))
581 .collect();
582 let mut truths = TruthSet::empty();
583
584 if expression.can_be_null() {
585 truths = truths.union(TruthSet::singleton(TruthValue::Unknown));
586 }
587 if !expression.can_be_non_null() {
588 return truths;
589 }
590 if value_nullabilities.is_empty() {
591 return truths.union(TruthSet::singleton(TruthValue::False));
592 }
593 if value_nullabilities
594 .iter()
595 .any(|value| value.can_be_non_null())
596 {
597 truths = truths.union(TruthSet::singleton(TruthValue::True));
598 }
599 if value_nullabilities.iter().any(|value| value.can_be_null()) {
600 truths = truths.union(TruthSet::singleton(TruthValue::Unknown));
601 }
602 if value_nullabilities
603 .iter()
604 .all(|value| value.can_be_non_null())
605 {
606 truths = truths.union(TruthSet::singleton(TruthValue::False));
607 }
608 truths
609}
610
611fn null_safe_comparison_truths(left: ScalarNullability, right: ScalarNullability) -> TruthSet {
612 let both_null = left.can_be_null() && right.can_be_null();
613 let exactly_one_null = (left.can_be_null() && right.can_be_non_null())
614 || (left.can_be_non_null() && right.can_be_null());
615 let both_non_null = left.can_be_non_null() && right.can_be_non_null();
616 let mut truths = TruthSet::empty();
617 if both_null || both_non_null {
618 truths = truths.union(TruthSet::singleton(TruthValue::False));
619 }
620 if exactly_one_null || both_non_null {
621 truths = truths.union(TruthSet::singleton(TruthValue::True));
622 }
623 truths
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 const LEFT: BindingId = BindingId::new(1);
631 const RIGHT: BindingId = BindingId::new(2);
632
633 fn binding_value(binding: BindingId) -> BoundScalarExpression {
634 BoundScalarExpression::BindingValue {
635 binding,
636 nullability: ScalarNullability::MaybeNull,
637 }
638 }
639
640 fn non_null_literal() -> BoundScalarExpression {
641 BoundScalarExpression::LiteralNonNull
642 }
643
644 fn comparison(binding: BindingId) -> BoundPredicate {
645 BoundPredicate::Comparison {
646 left: binding_value(binding),
647 right: non_null_literal(),
648 }
649 }
650
651 fn assert_proven(predicate: &BoundPredicate, bindings: impl Into<BindingSet>) {
652 assert!(prove_null_rejecting(predicate, &bindings.into()).is_proven());
653 }
654
655 fn assert_not_proven(predicate: &BoundPredicate, bindings: impl Into<BindingSet>) {
656 assert!(!prove_null_rejecting(predicate, &bindings.into()).is_proven());
657 }
658
659 #[test]
660 fn truth_value_implements_sql_three_valued_logic() {
661 let values = [TruthValue::True, TruthValue::False, TruthValue::Unknown];
662 let expected_not = [TruthValue::False, TruthValue::True, TruthValue::Unknown];
663 let expected_and = [
664 [TruthValue::True, TruthValue::False, TruthValue::Unknown],
665 [TruthValue::False, TruthValue::False, TruthValue::False],
666 [TruthValue::Unknown, TruthValue::False, TruthValue::Unknown],
667 ];
668 let expected_or = [
669 [TruthValue::True, TruthValue::True, TruthValue::True],
670 [TruthValue::True, TruthValue::False, TruthValue::Unknown],
671 [TruthValue::True, TruthValue::Unknown, TruthValue::Unknown],
672 ];
673
674 for (left_index, left) in values.into_iter().enumerate() {
675 assert_eq!(!left, expected_not[left_index]);
676 for (right_index, right) in values.into_iter().enumerate() {
677 assert_eq!(left.and(right), expected_and[left_index][right_index]);
678 assert_eq!(left.or(right), expected_or[left_index][right_index]);
679 }
680 }
681
682 let true_or_unknown =
683 TruthSet::singleton(TruthValue::True).union(TruthSet::singleton(TruthValue::Unknown));
684 assert_eq!(
685 !true_or_unknown,
686 TruthSet::singleton(TruthValue::False).union(TruthSet::singleton(TruthValue::Unknown))
687 );
688 }
689
690 #[test]
691 fn strict_predicates_reject_null_extended_binding() {
692 assert_proven(&comparison(RIGHT), RIGHT);
693 assert_proven(&BoundPredicate::Not(Box::new(comparison(RIGHT))), RIGHT);
694 assert_proven(&BoundPredicate::IsNotNull(binding_value(RIGHT)), RIGHT);
695 assert_not_proven(&BoundPredicate::IsNull(binding_value(RIGHT)), RIGHT);
696 }
697
698 #[test]
699 fn conjunction_and_disjunction_preserve_possible_true_rows() {
700 let left_comparison = comparison(LEFT);
701 let right_comparison = comparison(RIGHT);
702 assert_proven(
703 &BoundPredicate::And(vec![left_comparison.clone(), right_comparison.clone()]),
704 RIGHT,
705 );
706 assert_not_proven(
707 &BoundPredicate::Or(vec![left_comparison, right_comparison]),
708 RIGHT,
709 );
710 }
711
712 #[test]
713 fn coalesce_can_make_a_null_extended_value_non_null() {
714 let predicate = BoundPredicate::Comparison {
715 left: BoundScalarExpression::Coalesce(vec![binding_value(RIGHT), non_null_literal()]),
716 right: non_null_literal(),
717 };
718
719 assert_not_proven(&predicate, RIGHT);
720 }
721
722 #[test]
723 fn null_safe_comparisons_distinguish_null_and_non_null_values() {
724 let distinct = BoundPredicate::IsDistinctFrom {
725 left: binding_value(RIGHT),
726 right: non_null_literal(),
727 };
728 let not_distinct = BoundPredicate::IsNotDistinctFrom {
729 left: binding_value(RIGHT),
730 right: non_null_literal(),
731 };
732 let nulls_not_distinct = BoundPredicate::IsNotDistinctFrom {
733 left: binding_value(RIGHT),
734 right: BoundScalarExpression::LiteralNull,
735 };
736
737 assert_not_proven(&distinct, RIGHT);
738 assert_proven(¬_distinct, RIGHT);
739 assert_not_proven(&nulls_not_distinct, RIGHT);
740 }
741
742 #[test]
743 fn in_and_not_in_reject_a_null_search_value() {
744 let in_list = BoundPredicate::InList {
745 expression: binding_value(RIGHT),
746 values: vec![non_null_literal(), BoundScalarExpression::LiteralNull],
747 negated: false,
748 };
749 let not_in_list = BoundPredicate::InList {
750 expression: binding_value(RIGHT),
751 values: vec![non_null_literal(), BoundScalarExpression::LiteralNull],
752 negated: true,
753 };
754
755 assert_proven(&in_list, RIGHT);
756 assert_proven(¬_in_list, RIGHT);
757 }
758
759 #[test]
760 fn a_binding_set_can_be_rejected_when_each_singleton_is_not() {
761 let predicate = BoundPredicate::Or(vec![
762 BoundPredicate::IsNotNull(binding_value(LEFT)),
763 BoundPredicate::IsNotNull(binding_value(RIGHT)),
764 ]);
765
766 assert_not_proven(&predicate, LEFT);
767 assert_not_proven(&predicate, RIGHT);
768 assert_proven(&predicate, [LEFT, RIGHT]);
769 }
770
771 #[test]
772 fn existence_tracks_null_extended_bindings() {
773 let exists = BoundPredicate::BindingsPresent {
774 required_bindings: RIGHT.into(),
775 };
776
777 assert_proven(&exists, RIGHT);
778 assert_not_proven(&BoundPredicate::Not(Box::new(exists)), RIGHT);
779 }
780
781 #[test]
782 fn opaque_predicates_never_claim_a_proof() {
783 let predicate = BoundPredicate::Opaque {
784 referenced_bindings: RIGHT.into(),
785 };
786
787 let proof = prove_null_rejecting(&predicate, &RIGHT.into());
788 assert_eq!(
789 proof,
790 NullRejectionProof::NotProven {
791 possible_truths: TruthSet::all(),
792 }
793 );
794 }
795
796 #[test]
797 fn referenced_bindings_remain_separate_from_null_rejection() {
798 let constant_false = BoundPredicate::Constant(TruthValue::False);
799
800 assert!(constant_false.referenced_bindings().iter().next().is_none());
801 assert_proven(&constant_false, RIGHT);
802 }
803
804 #[test]
805 fn strict_calls_do_not_assume_non_null_output() {
806 let maybe_null_call = BoundScalarExpression::StrictCall {
807 arguments: vec![non_null_literal()],
808 result_nullability_when_arguments_non_null: ScalarNullability::MaybeNull,
809 };
810 let nullable_result = BoundPredicate::IsNull(maybe_null_call);
811 let null_argument = BoundPredicate::Comparison {
812 left: BoundScalarExpression::StrictCall {
813 arguments: vec![binding_value(RIGHT)],
814 result_nullability_when_arguments_non_null: ScalarNullability::AlwaysNonNull,
815 },
816 right: non_null_literal(),
817 };
818
819 assert_not_proven(&nullable_result, RIGHT);
820 assert_proven(&null_argument, RIGHT);
821 }
822}