vortex_compute/logical/
kleene.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Kleene three-valued logical operations: AND KLEENE, OR KLEENE.
5//!
6//! These operations implement Kleene's three-valued logic (K3), also used by SQL:
7//! - `FALSE AND NULL = FALSE` (false absorbs null)
8//! - `TRUE OR NULL = TRUE` (true absorbs null)
9//!
10//! For simple null-propagating operations, see the [`binary`](super::binary) module.
11
12use std::ops::BitAnd;
13use std::ops::BitOr;
14use std::ops::Not;
15
16use vortex_buffer::BitBuffer;
17use vortex_mask::Mask;
18use vortex_vector::BoolDatum;
19use vortex_vector::ScalarOps;
20use vortex_vector::VectorMutOps;
21use vortex_vector::VectorOps;
22use vortex_vector::bool::BoolScalar;
23use vortex_vector::bool::BoolVector;
24
25use super::LogicalAndKleene;
26use super::LogicalOp;
27use super::LogicalOrKleene;
28
29/// Marker type for the Kleene AND operation.
30pub struct KleeneAnd;
31
32/// Marker type for the Kleene OR operation.
33pub struct KleeneOr;
34
35/// Trait for Kleene three-valued logical binary operations.
36///
37/// Absorbing values produce a valid result regardless of the other operand:
38/// - For AND: `FALSE` absorbs nulls (`FALSE AND NULL = FALSE`)
39/// - For OR: `TRUE` absorbs nulls (`TRUE OR NULL = TRUE`)
40pub trait KleeneBinaryOp {
41    /// Apply the operation to two [`BitBuffer`]s.
42    fn bit_op(lhs: &BitBuffer, rhs: &BitBuffer) -> BitBuffer;
43
44    /// Returns a mask of positions with absorbing values.
45    ///
46    /// - AND: `FALSE` absorbs, so return `bits.not()` (false positions).
47    /// - OR: `TRUE` absorbs, so return `bits.clone()` (true positions).
48    fn absorb_bits(bits: &BitBuffer) -> BitBuffer;
49
50    /// Apply the operation to two scalar `Option<bool>` values with Kleene semantics.
51    fn scalar_op(lhs: Option<bool>, rhs: Option<bool>) -> Option<bool>;
52}
53
54impl KleeneBinaryOp for KleeneAnd {
55    fn bit_op(lhs: &BitBuffer, rhs: &BitBuffer) -> BitBuffer {
56        lhs.bitand(rhs)
57    }
58
59    fn absorb_bits(bits: &BitBuffer) -> BitBuffer {
60        bits.not() // `false` absorbs nulls.
61    }
62
63    fn scalar_op(lhs: Option<bool>, rhs: Option<bool>) -> Option<bool> {
64        match (lhs, rhs) {
65            (Some(false), _) | (_, Some(false)) => Some(false),
66            (Some(true), Some(true)) => Some(true),
67            _ => None,
68        }
69    }
70}
71
72impl KleeneBinaryOp for KleeneOr {
73    fn bit_op(lhs: &BitBuffer, rhs: &BitBuffer) -> BitBuffer {
74        lhs.bitor(rhs)
75    }
76
77    fn absorb_bits(bits: &BitBuffer) -> BitBuffer {
78        bits.clone() // `true` absorbs nulls.
79    }
80
81    fn scalar_op(lhs: Option<bool>, rhs: Option<bool>) -> Option<bool> {
82        match (lhs, rhs) {
83            (Some(true), _) | (_, Some(true)) => Some(true),
84            (Some(false), Some(false)) => Some(false),
85            _ => None,
86        }
87    }
88}
89
90////////////////////////////////////////////////////////////////////////////////////////////////////
91// Generic `LogicalOp` implementations
92////////////////////////////////////////////////////////////////////////////////////////////////////
93
94impl LogicalOp<KleeneAnd> for &BoolScalar {
95    type Output = BoolScalar;
96
97    fn op(self, rhs: &BoolScalar) -> BoolScalar {
98        kleene_scalar_op::<KleeneAnd>(self, rhs)
99    }
100}
101
102impl LogicalOp<KleeneOr> for &BoolScalar {
103    type Output = BoolScalar;
104
105    fn op(self, rhs: &BoolScalar) -> BoolScalar {
106        kleene_scalar_op::<KleeneOr>(self, rhs)
107    }
108}
109
110impl LogicalOp<KleeneAnd> for &BoolVector {
111    type Output = BoolVector;
112
113    fn op(self, rhs: &BoolVector) -> BoolVector {
114        kleene_vector_op::<KleeneAnd>(self, rhs)
115    }
116}
117
118impl LogicalOp<KleeneOr> for &BoolVector {
119    type Output = BoolVector;
120
121    fn op(self, rhs: &BoolVector) -> BoolVector {
122        kleene_vector_op::<KleeneOr>(self, rhs)
123    }
124}
125
126impl LogicalOp<KleeneAnd, &BoolDatum> for &BoolDatum {
127    type Output = BoolDatum;
128
129    fn op(self, rhs: &BoolDatum) -> BoolDatum {
130        kleene_datum_op::<KleeneAnd>(self, rhs)
131    }
132}
133
134impl LogicalOp<KleeneOr, &BoolDatum> for &BoolDatum {
135    type Output = BoolDatum;
136
137    fn op(self, rhs: &BoolDatum) -> BoolDatum {
138        kleene_datum_op::<KleeneOr>(self, rhs)
139    }
140}
141
142////////////////////////////////////////////////////////////////////////////////////////////////////
143// Kleene helper functions
144////////////////////////////////////////////////////////////////////////////////////////////////////
145
146fn kleene_scalar_op<Op: KleeneBinaryOp>(lhs: &BoolScalar, rhs: &BoolScalar) -> BoolScalar {
147    BoolScalar::new(Op::scalar_op(lhs.value(), rhs.value()))
148}
149
150fn kleene_vector_op<Op: KleeneBinaryOp>(lhs: &BoolVector, rhs: &BoolVector) -> BoolVector {
151    assert_eq!(lhs.len(), rhs.len());
152    let len = lhs.len();
153
154    match (lhs.validity(), rhs.validity()) {
155        // Everything is valid, so we can just do a simple logical AND over all bits.
156        (Mask::AllTrue(_), Mask::AllTrue(_)) => {
157            let result_bits = Op::bit_op(lhs.bits(), rhs.bits());
158            BoolVector::new(result_bits, Mask::new_true(len))
159        }
160
161        // Everything is null, so the entire result vector is null.
162        (Mask::AllFalse(_), Mask::AllFalse(_)) => {
163            // Since everything in null, we can just reuse the LHS bits.
164            let result_bits = lhs.bits().clone();
165            BoolVector::new(result_bits, Mask::new_false(len))
166        }
167
168        // LHS is all valid, RHS is all null.
169        (Mask::AllTrue(_), Mask::AllFalse(_)) => {
170            // The result vector is valid where the LHS has an absorbing value. Since only
171            // absorbing values produce valid results, and absorbing values equal the result of the
172            // operation, we can reuse the LHS bits directly.
173            let result_bits = lhs.bits().clone();
174            let validity = Op::absorb_bits(lhs.bits());
175            BoolVector::new(result_bits, Mask::from(validity))
176        }
177
178        // LHS is all null, RHS is all valid.
179        (Mask::AllFalse(_), Mask::AllTrue(_)) => {
180            // The result vector is valid where the RHS has an absorbing value. Since only
181            // absorbing values produce valid results, and absorbing values equal the result of the
182            // operation, we can reuse the RHS bits directly.
183            let result_bits = rhs.bits().clone();
184            let validity = Op::absorb_bits(rhs.bits());
185            BoolVector::new(result_bits, Mask::from(validity))
186        }
187
188        // LHS is all valid, RHS has specific validity.
189        (Mask::AllTrue(_), Mask::Values(rhs_values)) => {
190            // The result vector is valid where the RHS is valid OR the LHS has an absorbing value.
191            let result_bits = Op::bit_op(lhs.bits(), rhs.bits());
192            let validity = rhs_values.bit_buffer().bitor(&Op::absorb_bits(lhs.bits()));
193            BoolVector::new(result_bits, Mask::from(validity))
194        }
195
196        // LHS has specific validity, RHS is all valid.
197        (Mask::Values(lhs_values), Mask::AllTrue(_)) => {
198            // The result vector is valid where the LHS is valid OR the RHS has an absorbing value.
199            let result_bits = Op::bit_op(lhs.bits(), rhs.bits());
200            let validity = lhs_values.bit_buffer().bitor(&Op::absorb_bits(rhs.bits()));
201            BoolVector::new(result_bits, Mask::from(validity))
202        }
203
204        // LHS is all null, RHS has specific validity.
205        (Mask::AllFalse(_), Mask::Values(rhs_values)) => {
206            // The result vector is valid where the RHS is valid AND has an absorbing value. Since
207            // only absorbing values produce valid results, we can reuse the RHS bits directly.
208            let result_bits = rhs.bits().clone();
209            let validity = rhs_values.bit_buffer().bitand(&Op::absorb_bits(rhs.bits()));
210            BoolVector::new(result_bits, Mask::from(validity))
211        }
212
213        // LHS has specific validity, RHS is all null.
214        (Mask::Values(lhs_values), Mask::AllFalse(_)) => {
215            // The result vector is valid where the LHS is valid AND has an absorbing value. Since
216            // only absorbing values produce valid results, we can reuse the LHS bits directly.
217            let result_bits = lhs.bits().clone();
218            let validity = lhs_values.bit_buffer().bitand(&Op::absorb_bits(lhs.bits()));
219            BoolVector::new(result_bits, Mask::from(validity))
220        }
221
222        // Both sides have specific validity.
223        (Mask::Values(lhs_values), Mask::Values(rhs_values)) => {
224            // The result is valid at position `i` iff:
225            // 1. Both lhs[i] and rhs[i] are valid (standard case), OR
226            // 2. lhs[i] is null but rhs[i] is valid AND has an absorbing value, OR
227            // 3. rhs[i] is null but lhs[i] is valid AND has an absorbing value.
228            //
229            // Absorbing values in Kleene logic:
230            // - AND: false absorbs null (false AND null = false).
231            // - OR: true absorbs null (true OR null = true).
232            //
233            // This simplifies to the gosition is valid iff:
234            //   - (lhs_valid OR rhs_absorbs) AND
235            //   - (rhs_valid OR lhs_absorbs).
236            //
237            // In other words, each side must either be valid or have an absorbing value that
238            // "covers" the other side's null.
239            let result_bits = Op::bit_op(lhs.bits(), rhs.bits());
240
241            let lhs_valid_or_rhs_absorbs =
242                lhs_values.bit_buffer().bitor(&Op::absorb_bits(rhs.bits()));
243            let rhs_valid_or_lhs_absorbs =
244                rhs_values.bit_buffer().bitor(&Op::absorb_bits(lhs.bits()));
245            let validity = lhs_valid_or_rhs_absorbs.bitand(&rhs_valid_or_lhs_absorbs);
246
247            BoolVector::new(result_bits, Mask::from(validity))
248        }
249    }
250}
251
252fn kleene_datum_op<Op: KleeneBinaryOp>(lhs: &BoolDatum, rhs: &BoolDatum) -> BoolDatum
253where
254    for<'a> &'a BoolScalar: LogicalOp<Op, Output = BoolScalar>,
255    for<'a> &'a BoolVector: LogicalOp<Op, Output = BoolVector>,
256{
257    match (lhs, rhs) {
258        (BoolDatum::Vector(lhs), BoolDatum::Vector(rhs)) => {
259            BoolDatum::Vector(<&BoolVector as LogicalOp<Op>>::op(lhs, rhs))
260        }
261        (BoolDatum::Scalar(lhs), BoolDatum::Scalar(rhs)) => {
262            BoolDatum::Scalar(<&BoolScalar as LogicalOp<Op>>::op(lhs, rhs))
263        }
264        // TODO: Specialize this instead of using `repeat`.
265        (BoolDatum::Scalar(sc), BoolDatum::Vector(vec)) => {
266            let expanded = sc.repeat(vec.len()).freeze().into_bool();
267            BoolDatum::Vector(<&BoolVector as LogicalOp<Op>>::op(&expanded, vec))
268        }
269        // TODO: Specialize this instead of using `repeat`.
270        (BoolDatum::Vector(vec), BoolDatum::Scalar(sc)) => {
271            let expanded = sc.repeat(vec.len()).freeze().into_bool();
272            BoolDatum::Vector(<&BoolVector as LogicalOp<Op>>::op(vec, &expanded))
273        }
274    }
275}
276
277////////////////////////////////////////////////////////////////////////////////////////////////////
278// Convenience trait implementations
279////////////////////////////////////////////////////////////////////////////////////////////////////
280
281impl LogicalAndKleene for &BoolScalar {
282    type Output = BoolScalar;
283
284    fn and_kleene(self, rhs: &BoolScalar) -> BoolScalar {
285        kleene_scalar_op::<KleeneAnd>(self, rhs)
286    }
287}
288
289impl LogicalAndKleene for &BoolVector {
290    type Output = BoolVector;
291
292    fn and_kleene(self, rhs: &BoolVector) -> BoolVector {
293        kleene_vector_op::<KleeneAnd>(self, rhs)
294    }
295}
296
297impl LogicalAndKleene<&BoolDatum> for &BoolDatum {
298    type Output = BoolDatum;
299
300    fn and_kleene(self, rhs: &BoolDatum) -> BoolDatum {
301        <&BoolDatum as LogicalOp<KleeneAnd, &BoolDatum>>::op(self, rhs)
302    }
303}
304
305impl LogicalOrKleene for &BoolScalar {
306    type Output = BoolScalar;
307
308    fn or_kleene(self, rhs: &BoolScalar) -> BoolScalar {
309        kleene_scalar_op::<KleeneOr>(self, rhs)
310    }
311}
312
313impl LogicalOrKleene for &BoolVector {
314    type Output = BoolVector;
315
316    fn or_kleene(self, rhs: &BoolVector) -> BoolVector {
317        kleene_vector_op::<KleeneOr>(self, rhs)
318    }
319}
320
321impl LogicalOrKleene<&BoolDatum> for &BoolDatum {
322    type Output = BoolDatum;
323
324    fn or_kleene(self, rhs: &BoolDatum) -> BoolDatum {
325        <&BoolDatum as LogicalOp<KleeneOr, &BoolDatum>>::op(self, rhs)
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use vortex_buffer::bitbuffer;
332    use vortex_mask::Mask;
333    use vortex_vector::bool::BoolScalar;
334    use vortex_vector::bool::BoolVector;
335
336    use super::*;
337
338    // AND KLEENE vector tests.
339
340    #[test]
341    fn test_and_kleene_all_valid() {
342        // When both sides are all valid, behaves like regular AND.
343        let left = BoolVector::new(bitbuffer![1 0 1], Mask::new_true(3));
344        let right = BoolVector::new(bitbuffer![1 1 0], Mask::new_true(3));
345
346        let result = left.and_kleene(&right);
347        assert_eq!(result.bits(), &bitbuffer![1 0 0]);
348        assert_eq!(result.validity(), &Mask::new_true(3));
349    }
350
351    #[test]
352    fn test_and_kleene_all_null() {
353        // When both are null, result is all null.
354        let left = BoolVector::new(bitbuffer![1 1], Mask::new_false(2));
355        let right = BoolVector::new(bitbuffer![1 1], Mask::new_false(2));
356
357        let result = left.and_kleene(&right);
358        assert_eq!(result.validity(), &Mask::new_false(2));
359    }
360
361    #[test]
362    fn test_and_kleene_false_and_null() {
363        // false AND null = false (Kleene logic).
364        let left = BoolVector::new(bitbuffer![0], Mask::new_true(1));
365        let right = BoolVector::new(bitbuffer![1], Mask::new_false(1));
366
367        let result = left.and_kleene(&right);
368        assert_eq!(result.bits(), &bitbuffer![0]);
369        // Result should be valid because false AND anything is false.
370        assert_eq!(result.validity(), &Mask::new_true(1));
371    }
372
373    // AND KLEENE scalar tests.
374
375    #[test]
376    fn test_scalar_and_kleene_true_true() {
377        let left = BoolScalar::new(Some(true));
378        let right = BoolScalar::new(Some(true));
379        assert_eq!(left.and_kleene(&right).value(), Some(true));
380    }
381
382    #[test]
383    fn test_scalar_and_kleene_true_false() {
384        let left = BoolScalar::new(Some(true));
385        let right = BoolScalar::new(Some(false));
386        assert_eq!(left.and_kleene(&right).value(), Some(false));
387    }
388
389    #[test]
390    fn test_scalar_and_kleene_false_null() {
391        // false AND null = false (Kleene logic).
392        let left = BoolScalar::new(Some(false));
393        let right = BoolScalar::new(None);
394        assert_eq!(left.and_kleene(&right).value(), Some(false));
395    }
396
397    #[test]
398    fn test_scalar_and_kleene_null_false() {
399        // null AND false = false (Kleene logic).
400        let left = BoolScalar::new(None);
401        let right = BoolScalar::new(Some(false));
402        assert_eq!(left.and_kleene(&right).value(), Some(false));
403    }
404
405    #[test]
406    fn test_scalar_and_kleene_true_null() {
407        // true AND null = null.
408        let left = BoolScalar::new(Some(true));
409        let right = BoolScalar::new(None);
410        assert_eq!(left.and_kleene(&right).value(), None);
411    }
412
413    #[test]
414    fn test_scalar_and_kleene_null_null() {
415        let left = BoolScalar::new(None);
416        let right = BoolScalar::new(None);
417        assert_eq!(left.and_kleene(&right).value(), None);
418    }
419
420    // OR KLEENE vector tests.
421
422    #[test]
423    fn test_or_kleene_all_valid() {
424        // When both sides are all valid, behaves like regular OR.
425        let left = BoolVector::new(bitbuffer![1 0 0], Mask::new_true(3));
426        let right = BoolVector::new(bitbuffer![0 1 0], Mask::new_true(3));
427
428        let result = left.or_kleene(&right);
429        assert_eq!(result.bits(), &bitbuffer![1 1 0]);
430        assert_eq!(result.validity(), &Mask::new_true(3));
431    }
432
433    #[test]
434    fn test_or_kleene_all_null() {
435        // When both are null, result is all null.
436        let left = BoolVector::new(bitbuffer![0 0], Mask::new_false(2));
437        let right = BoolVector::new(bitbuffer![0 0], Mask::new_false(2));
438
439        let result = left.or_kleene(&right);
440        assert_eq!(result.validity(), &Mask::new_false(2));
441    }
442
443    #[test]
444    fn test_or_kleene_true_and_null() {
445        // true OR null = true (Kleene logic).
446        let left = BoolVector::new(bitbuffer![1], Mask::new_true(1));
447        let right = BoolVector::new(bitbuffer![0], Mask::new_false(1));
448
449        let result = left.or_kleene(&right);
450        assert_eq!(result.bits(), &bitbuffer![1]);
451        // Result should be valid because true OR anything is true.
452        assert_eq!(result.validity(), &Mask::new_true(1));
453    }
454
455    // OR KLEENE scalar tests.
456
457    #[test]
458    fn test_scalar_or_kleene_true_true() {
459        let left = BoolScalar::new(Some(true));
460        let right = BoolScalar::new(Some(true));
461        assert_eq!(left.or_kleene(&right).value(), Some(true));
462    }
463
464    #[test]
465    fn test_scalar_or_kleene_true_false() {
466        let left = BoolScalar::new(Some(true));
467        let right = BoolScalar::new(Some(false));
468        assert_eq!(left.or_kleene(&right).value(), Some(true));
469    }
470
471    #[test]
472    fn test_scalar_or_kleene_false_false() {
473        let left = BoolScalar::new(Some(false));
474        let right = BoolScalar::new(Some(false));
475        assert_eq!(left.or_kleene(&right).value(), Some(false));
476    }
477
478    #[test]
479    fn test_scalar_or_kleene_true_null() {
480        // true OR null = true (Kleene logic).
481        let left = BoolScalar::new(Some(true));
482        let right = BoolScalar::new(None);
483        assert_eq!(left.or_kleene(&right).value(), Some(true));
484    }
485
486    #[test]
487    fn test_scalar_or_kleene_null_true() {
488        // null OR true = true (Kleene logic).
489        let left = BoolScalar::new(None);
490        let right = BoolScalar::new(Some(true));
491        assert_eq!(left.or_kleene(&right).value(), Some(true));
492    }
493
494    #[test]
495    fn test_scalar_or_kleene_false_null() {
496        // false OR null = null.
497        let left = BoolScalar::new(Some(false));
498        let right = BoolScalar::new(None);
499        assert_eq!(left.or_kleene(&right).value(), None);
500    }
501
502    #[test]
503    fn test_scalar_or_kleene_null_null() {
504        let left = BoolScalar::new(None);
505        let right = BoolScalar::new(None);
506        assert_eq!(left.or_kleene(&right).value(), None);
507    }
508
509    // Datum tests.
510
511    #[test]
512    fn test_datum_and_kleene_vector_vector() {
513        let left = BoolDatum::Vector(BoolVector::new(bitbuffer![0], Mask::new_true(1)));
514        let right = BoolDatum::Vector(BoolVector::new(bitbuffer![1], Mask::new_false(1)));
515
516        let result = left.and_kleene(&right);
517        let BoolDatum::Vector(vec) = result else {
518            panic!("Expected Vector");
519        };
520        // false AND null = false.
521        assert_eq!(vec.bits(), &bitbuffer![0]);
522        assert_eq!(vec.validity(), &Mask::new_true(1));
523    }
524
525    #[test]
526    fn test_datum_and_kleene_scalar_scalar() {
527        let left = BoolDatum::Scalar(BoolScalar::new(Some(false)));
528        let right = BoolDatum::Scalar(BoolScalar::new(None));
529
530        let result = left.and_kleene(&right);
531        let BoolDatum::Scalar(sc) = result else {
532            panic!("Expected Scalar");
533        };
534        // false AND null = false.
535        assert_eq!(sc.value(), Some(false));
536    }
537
538    #[test]
539    fn test_datum_or_kleene_scalar_vector() {
540        let left = BoolDatum::Scalar(BoolScalar::new(Some(true)));
541        let right = BoolDatum::Vector(BoolVector::new(bitbuffer![0 0], Mask::new_false(2)));
542
543        let result = left.or_kleene(&right);
544        let BoolDatum::Vector(vec) = result else {
545            panic!("Expected Vector");
546        };
547        // true OR null = true.
548        assert_eq!(vec.bits(), &bitbuffer![1 1]);
549        assert_eq!(vec.validity(), &Mask::new_true(2));
550    }
551}