Skip to main content

vortex_array/scalar_fn/fns/binary/
boolean.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::iter::repeat_n;
5
6use vortex_buffer::BitBuffer;
7use vortex_buffer::BufferMut;
8use vortex_buffer::read_u64_le;
9use vortex_error::VortexResult;
10use vortex_error::vortex_bail;
11use vortex_error::vortex_err;
12use vortex_mask::AllOr;
13use vortex_mask::Mask;
14
15use crate::ArrayRef;
16use crate::Canonical;
17use crate::ExecutionCtx;
18use crate::IntoArray;
19use crate::array::ArrayView;
20use crate::array::VTable;
21use crate::arrays::Bool;
22use crate::arrays::BoolArray;
23use crate::arrays::Constant;
24use crate::arrays::ConstantArray;
25use crate::arrays::ScalarFn;
26use crate::arrays::scalar_fn::ExactScalarFn;
27use crate::arrays::scalar_fn::ScalarFnArrayExt;
28use crate::arrays::scalar_fn::ScalarFnArrayView;
29use crate::builtins::ArrayBuiltins;
30use crate::dtype::DType;
31use crate::dtype::Nullability;
32use crate::kernel::ExecuteParentKernel;
33use crate::scalar::BoolScalar;
34use crate::scalar::Scalar;
35use crate::scalar_fn::fns::binary::Binary;
36use crate::scalar_fn::fns::operators::Operator;
37use crate::validity::Validity;
38
39/// Trait for encoding-specific boolean kernels that operate in encoded space.
40///
41/// Implementations receive the encoded array as the left operand. `rhs` may be any boolean array
42/// encoding or a constant; implementations should return `Ok(None)` when they cannot handle that
43/// operand without falling back to ordinary execution.
44///
45/// Vortex's boolean [`Operator::And`] and [`Operator::Or`] variants use Kleene semantics; there is
46/// no separate two-valued boolean operator path to dispatch here. Consequently, they are not
47/// strict: `false AND null` is `false`, and `true OR null` is `true`.
48pub trait BooleanKernel: VTable {
49    /// Execute `lhs <operator> rhs` using Kleene boolean semantics.
50    fn boolean(
51        lhs: ArrayView<'_, Self>,
52        rhs: &ArrayRef,
53        operator: Operator,
54        ctx: &mut ExecutionCtx,
55    ) -> VortexResult<Option<ArrayRef>>;
56}
57
58/// Adaptor that bridges [`BooleanKernel`] implementations to [`ExecuteParentKernel`].
59///
60/// When a `ScalarFnArray(Binary, And|Or)` wraps a child implementing [`BooleanKernel`], this
61/// adaptor extracts the other operand and delegates to the encoding-specific kernel.
62#[derive(Default, Debug)]
63pub struct BooleanExecuteAdaptor<V>(pub V);
64
65impl<V> ExecuteParentKernel<V> for BooleanExecuteAdaptor<V>
66where
67    V: BooleanKernel,
68{
69    type Parent = ExactScalarFn<Binary>;
70
71    fn execute_parent(
72        &self,
73        array: ArrayView<'_, V>,
74        parent: ScalarFnArrayView<'_, Binary>,
75        child_idx: usize,
76        ctx: &mut ExecutionCtx,
77    ) -> VortexResult<Option<ArrayRef>> {
78        let op = *parent.options;
79        if !is_boolean_operator(op) {
80            return Ok(None);
81        }
82
83        let Some(scalar_fn_array) = parent.as_opt::<ScalarFn>() else {
84            return Ok(None);
85        };
86        let other = match child_idx {
87            0 => scalar_fn_array.get_child(1),
88            1 => scalar_fn_array.get_child(0),
89            _ => return Ok(None),
90        };
91
92        if let Some(result) = constant_boolean(array.array(), other, op)? {
93            return Ok(Some(result));
94        }
95
96        V::boolean(array, other, op, ctx)
97    }
98}
99
100/// Point-wise Kleene logical _and_ between two Boolean arrays.
101#[deprecated(note = "Use `ArrayBuiltins::binary` instead")]
102pub fn and_kleene(lhs: &ArrayRef, rhs: &ArrayRef) -> VortexResult<ArrayRef> {
103    lhs.clone().binary(rhs.clone(), Operator::And)
104}
105
106/// Point-wise Kleene logical _or_ between two Boolean arrays.
107#[deprecated(note = "Use `ArrayBuiltins::binary` instead")]
108pub fn or_kleene(lhs: &ArrayRef, rhs: &ArrayRef) -> VortexResult<ArrayRef> {
109    lhs.clone().binary(rhs.clone(), Operator::Or)
110}
111
112/// Execute a Kleene boolean operation between two arrays.
113///
114/// This is the entry point for boolean operations from the binary expression.
115/// Handles constants and canonical boolean arrays directly, otherwise falls back to Arrow.
116pub(crate) fn execute_boolean(
117    lhs: ArrayRef,
118    rhs: ArrayRef,
119    op: Operator,
120    ctx: &mut ExecutionCtx,
121) -> VortexResult<ArrayRef> {
122    let nullable = boolean_nullability(&lhs, &rhs);
123
124    if lhs.is_empty() {
125        return Ok(Canonical::empty(&DType::Bool(nullable)).into_array());
126    }
127
128    if let Some(result) = constant_boolean(&lhs, &rhs, op)? {
129        return Ok(result);
130    }
131
132    let lhs = lhs.execute::<BoolArray>(ctx)?;
133    if let Some(result) = <Bool as BooleanKernel>::boolean(lhs.as_view(), &rhs, op, ctx)? {
134        return Ok(result);
135    }
136
137    let rhs = rhs.execute::<BoolArray>(ctx)?;
138    let Some(result) = <Bool as BooleanKernel>::boolean(rhs.as_view(), &lhs.into_array(), op, ctx)?
139    else {
140        vortex_bail!("No boolean kernel for two BoolArrays");
141    };
142    Ok(result)
143}
144
145/// Handles boolean operations where at least one operand is a constant array.
146fn constant_boolean(
147    lhs: &ArrayRef,
148    rhs: &ArrayRef,
149    op: Operator,
150) -> VortexResult<Option<ArrayRef>> {
151    let nullable = boolean_nullability(lhs, rhs);
152
153    match (lhs.as_opt::<Constant>(), rhs.as_opt::<Constant>()) {
154        (Some(lhs), Some(rhs)) => {
155            let result = boolean_scalar_scalar(
156                bool_scalar_value(lhs.scalar())?,
157                bool_scalar_value(rhs.scalar())?,
158                op,
159            )?;
160
161            Ok(Some(constant_bool_result(result, lhs.len(), nullable)))
162        }
163        (Some(lhs), None) => constant_array_boolean(lhs.scalar(), rhs, op, nullable),
164        (None, Some(rhs)) => constant_array_boolean(rhs.scalar(), lhs, op, nullable),
165        (None, None) => Ok(None),
166    }
167}
168
169fn constant_array_boolean(
170    constant: &Scalar,
171    array: &ArrayRef,
172    op: Operator,
173    nullability: Nullability,
174) -> VortexResult<Option<ArrayRef>> {
175    match (op, bool_scalar_value(constant)?) {
176        (Operator::And, Some(false)) => Ok(Some(constant_bool_result(
177            Some(false),
178            array.len(),
179            nullability,
180        ))),
181        (Operator::And, Some(true)) => Ok(Some(cast_bool_nullability(array, nullability)?)),
182        (Operator::Or, Some(true)) => Ok(Some(constant_bool_result(
183            Some(true),
184            array.len(),
185            nullability,
186        ))),
187        (Operator::Or, Some(false)) => Ok(Some(cast_bool_nullability(array, nullability)?)),
188        (Operator::And | Operator::Or, None) => Ok(None),
189        (other, _) => vortex_bail!("Not a boolean operator: {other}"),
190    }
191}
192
193fn boolean_scalar_scalar(
194    lhs: Option<bool>,
195    rhs: Option<bool>,
196    op: Operator,
197) -> VortexResult<Option<bool>> {
198    Ok(match op {
199        Operator::And => match (lhs, rhs) {
200            (Some(false), _) | (_, Some(false)) => Some(false),
201            (None, _) | (_, None) => None,
202            (Some(l), Some(r)) => Some(l & r),
203        },
204        Operator::Or => match (lhs, rhs) {
205            (Some(true), _) | (_, Some(true)) => Some(true),
206            (None, _) | (_, None) => None,
207            (Some(l), Some(r)) => Some(l | r),
208        },
209        other => vortex_bail!("Not a boolean operator: {other}"),
210    })
211}
212
213fn bool_scalar_value(scalar: &Scalar) -> VortexResult<Option<bool>> {
214    Ok(scalar
215        .as_bool_opt()
216        .ok_or_else(|| vortex_err!("expected boolean scalar"))?
217        .value())
218}
219
220/// Execute a Kleene boolean operation from boolean value bitmaps and validity values.
221pub fn kleene_boolean_buffers(
222    lhs_values: BitBuffer,
223    lhs_validity: Validity,
224    rhs_values: BitBuffer,
225    rhs_validity: Validity,
226    operator: Operator,
227    nullability: Nullability,
228    ctx: &mut ExecutionCtx,
229) -> VortexResult<ArrayRef> {
230    let len = lhs_values.len();
231    debug_assert_eq!(rhs_values.len(), len);
232
233    if lhs_validity.definitely_no_nulls() && rhs_validity.definitely_no_nulls() {
234        let values = match operator {
235            Operator::And => lhs_values & &rhs_values,
236            Operator::Or => lhs_values | &rhs_values,
237            other => vortex_bail!("Not a boolean operator: {other}"),
238        };
239        return Ok(BoolArray::try_new(values, Validity::from(nullability))?.into_array());
240    }
241
242    let lhs_valid = lhs_validity.execute_mask(len, ctx)?;
243    let rhs_valid = rhs_validity.execute_mask(len, ctx)?;
244    fused_boolean_buffers(
245        len,
246        &lhs_values,
247        &lhs_valid,
248        &rhs_values,
249        &rhs_valid,
250        operator,
251        nullability,
252    )
253}
254
255/// Execute a Kleene boolean operation between boolean value bits and a scalar.
256pub fn kleene_boolean_buffer_scalar(
257    values: BitBuffer,
258    validity: Validity,
259    scalar: &BoolScalar<'_>,
260    operator: Operator,
261    nullability: Nullability,
262    ctx: &mut ExecutionCtx,
263) -> VortexResult<ArrayRef> {
264    let scalar_value = scalar.value();
265    let len = values.len();
266    let result = match (operator, scalar_value) {
267        (Operator::And, Some(false)) => {
268            return Ok(constant_bool_result(Some(false), len, nullability));
269        }
270        (Operator::And, Some(true)) => {
271            return Ok(
272                BoolArray::try_new(values, validity.union_nullability(nullability))?.into_array(),
273            );
274        }
275        (Operator::Or, Some(true)) => {
276            return Ok(constant_bool_result(Some(true), len, nullability));
277        }
278        (Operator::Or, Some(false)) => {
279            return Ok(
280                BoolArray::try_new(values, validity.union_nullability(nullability))?.into_array(),
281            );
282        }
283        (Operator::And, None) => {
284            let valid = validity
285                .execute_mask(len, ctx)?
286                .bitand_not(&Mask::from_buffer(values));
287            BoolArray::try_new(
288                BitBuffer::new_unset(len),
289                Validity::from_mask(valid, nullability),
290            )?
291        }
292        (Operator::Or, None) => {
293            let valid = validity.execute_mask(len, ctx)? & &Mask::from_buffer(values);
294            BoolArray::try_new(
295                BitBuffer::new_set(len),
296                Validity::from_mask(valid, nullability),
297            )?
298        }
299        (other, _) => vortex_bail!("Not a boolean operator: {other}"),
300    };
301
302    Ok(result.into_array())
303}
304
305fn fused_boolean_buffers(
306    len: usize,
307    lhs_values: &BitBuffer,
308    lhs_validity: &Mask,
309    rhs_values: &BitBuffer,
310    rhs_validity: &Mask,
311    operator: Operator,
312    nullability: Nullability,
313) -> VortexResult<ArrayRef> {
314    if let Some(result) = fused_boolean_buffers_aligned(
315        len,
316        lhs_values,
317        lhs_validity,
318        rhs_values,
319        rhs_validity,
320        operator,
321        nullability,
322    )? {
323        return Ok(result);
324    }
325
326    let n_words = len.div_ceil(64);
327
328    macro_rules! fuse {
329        ($lhs_valid_words:expr, $rhs_valid_words:expr) => {
330            fused_boolean_words(
331                len,
332                lhs_values.chunks().iter_padded(),
333                rhs_values.chunks().iter_padded(),
334                $lhs_valid_words,
335                $rhs_valid_words,
336                operator,
337                nullability,
338            )
339        };
340    }
341
342    match (lhs_validity.bit_buffer(), rhs_validity.bit_buffer()) {
343        (AllOr::All, AllOr::All) => {
344            fuse!(repeat_n(u64::MAX, n_words), repeat_n(u64::MAX, n_words))
345        }
346        (AllOr::All, AllOr::None) => {
347            fuse!(repeat_n(u64::MAX, n_words), repeat_n(0, n_words))
348        }
349        (AllOr::All, AllOr::Some(rhs_validity)) => fuse!(
350            repeat_n(u64::MAX, n_words),
351            rhs_validity.chunks().iter_padded()
352        ),
353        (AllOr::None, AllOr::All) => {
354            fuse!(repeat_n(0, n_words), repeat_n(u64::MAX, n_words))
355        }
356        (AllOr::None, AllOr::None) => {
357            fuse!(repeat_n(0, n_words), repeat_n(0, n_words))
358        }
359        (AllOr::None, AllOr::Some(rhs_validity)) => {
360            fuse!(repeat_n(0, n_words), rhs_validity.chunks().iter_padded())
361        }
362        (AllOr::Some(lhs_validity), AllOr::All) => fuse!(
363            lhs_validity.chunks().iter_padded(),
364            repeat_n(u64::MAX, n_words)
365        ),
366        (AllOr::Some(lhs_validity), AllOr::None) => {
367            fuse!(lhs_validity.chunks().iter_padded(), repeat_n(0, n_words))
368        }
369        (AllOr::Some(lhs_validity), AllOr::Some(rhs_validity)) => fuse!(
370            lhs_validity.chunks().iter_padded(),
371            rhs_validity.chunks().iter_padded()
372        ),
373    }
374}
375
376#[derive(Clone, Copy)]
377enum WordSource<'a> {
378    Fill(u64),
379    Bytes(&'a [u8]),
380}
381
382impl WordSource<'_> {
383    #[inline]
384    fn word_at(self, byte_offset: usize, len: usize) -> u64 {
385        match self {
386            Self::Fill(word) => word,
387            Self::Bytes(bytes) => read_u64_le(&bytes[byte_offset..byte_offset + len]),
388        }
389    }
390}
391
392fn fused_boolean_buffers_aligned(
393    len: usize,
394    lhs_values: &BitBuffer,
395    lhs_validity: &Mask,
396    rhs_values: &BitBuffer,
397    rhs_validity: &Mask,
398    operator: Operator,
399    nullability: Nullability,
400) -> VortexResult<Option<ArrayRef>> {
401    let Some(lhs_values) = word_source_from_bit_buffer(lhs_values) else {
402        return Ok(None);
403    };
404    let Some(rhs_values) = word_source_from_bit_buffer(rhs_values) else {
405        return Ok(None);
406    };
407    let Some(lhs_validity) = word_source_from_mask(lhs_validity) else {
408        return Ok(None);
409    };
410    let Some(rhs_validity) = word_source_from_mask(rhs_validity) else {
411        return Ok(None);
412    };
413
414    Ok(Some(fused_boolean_word_sources(
415        len,
416        lhs_values,
417        rhs_values,
418        lhs_validity,
419        rhs_validity,
420        operator,
421        nullability,
422    )?))
423}
424
425fn word_source_from_bit_buffer(buffer: &BitBuffer) -> Option<WordSource<'_>> {
426    buffer.byte_aligned_bytes().map(WordSource::Bytes)
427}
428
429fn word_source_from_mask(mask: &Mask) -> Option<WordSource<'_>> {
430    match mask.bit_buffer() {
431        AllOr::All => Some(WordSource::Fill(u64::MAX)),
432        AllOr::None => Some(WordSource::Fill(0)),
433        AllOr::Some(buffer) => word_source_from_bit_buffer(buffer),
434    }
435}
436
437fn fused_boolean_word_sources(
438    len: usize,
439    lhs_words: WordSource<'_>,
440    rhs_words: WordSource<'_>,
441    lhs_valid_words: WordSource<'_>,
442    rhs_valid_words: WordSource<'_>,
443    operator: Operator,
444    nullability: Nullability,
445) -> VortexResult<ArrayRef> {
446    match operator {
447        Operator::And => fused_boolean_and_word_sources(
448            len,
449            lhs_words,
450            rhs_words,
451            lhs_valid_words,
452            rhs_valid_words,
453            nullability,
454        ),
455        Operator::Or => fused_boolean_or_word_sources(
456            len,
457            lhs_words,
458            rhs_words,
459            lhs_valid_words,
460            rhs_valid_words,
461            nullability,
462        ),
463        other => vortex_bail!("Not a boolean operator: {other}"),
464    }
465}
466
467fn fused_boolean_and_word_sources(
468    len: usize,
469    lhs_words: WordSource<'_>,
470    rhs_words: WordSource<'_>,
471    lhs_valid_words: WordSource<'_>,
472    rhs_valid_words: WordSource<'_>,
473    nullability: Nullability,
474) -> VortexResult<ArrayRef> {
475    let n_bytes = len.div_ceil(8);
476    let n_words = n_bytes.div_ceil(8);
477    let full_bytes = n_bytes - n_bytes % 8;
478    let mut values = BufferMut::<u64>::with_capacity(n_words);
479    let mut validity = BufferMut::<u64>::with_capacity(n_words);
480
481    for byte_offset in (0..full_bytes).step_by(8) {
482        let lhs = lhs_words.word_at(byte_offset, 8);
483        let rhs = rhs_words.word_at(byte_offset, 8);
484        let lhs_valid = lhs_valid_words.word_at(byte_offset, 8);
485        let rhs_valid = rhs_valid_words.word_at(byte_offset, 8);
486
487        // SAFETY: both buffers were allocated with exactly `n_words` capacity, and this
488        // loop plus the optional tail push emits at most `n_words` words.
489        unsafe {
490            values.push_unchecked(lhs & rhs);
491            validity
492                .push_unchecked((lhs_valid & rhs_valid) | (lhs_valid & !lhs) | (rhs_valid & !rhs));
493        }
494    }
495
496    if full_bytes != n_bytes {
497        let tail_len = n_bytes - full_bytes;
498        let lhs = lhs_words.word_at(full_bytes, tail_len);
499        let rhs = rhs_words.word_at(full_bytes, tail_len);
500        let lhs_valid = lhs_valid_words.word_at(full_bytes, tail_len);
501        let rhs_valid = rhs_valid_words.word_at(full_bytes, tail_len);
502
503        // SAFETY: see the loop safety comment above.
504        unsafe {
505            values.push_unchecked(lhs & rhs);
506            validity
507                .push_unchecked((lhs_valid & rhs_valid) | (lhs_valid & !lhs) | (rhs_valid & !rhs));
508        }
509    }
510
511    finish_fused_boolean_words(len, n_bytes, values, validity, nullability)
512}
513
514fn fused_boolean_or_word_sources(
515    len: usize,
516    lhs_words: WordSource<'_>,
517    rhs_words: WordSource<'_>,
518    lhs_valid_words: WordSource<'_>,
519    rhs_valid_words: WordSource<'_>,
520    nullability: Nullability,
521) -> VortexResult<ArrayRef> {
522    let n_bytes = len.div_ceil(8);
523    let n_words = n_bytes.div_ceil(8);
524    let full_bytes = n_bytes - n_bytes % 8;
525    let mut values = BufferMut::<u64>::with_capacity(n_words);
526    let mut validity = BufferMut::<u64>::with_capacity(n_words);
527
528    for byte_offset in (0..full_bytes).step_by(8) {
529        let lhs = lhs_words.word_at(byte_offset, 8);
530        let rhs = rhs_words.word_at(byte_offset, 8);
531        let lhs_valid = lhs_valid_words.word_at(byte_offset, 8);
532        let rhs_valid = rhs_valid_words.word_at(byte_offset, 8);
533
534        // SAFETY: both buffers were allocated with exactly `n_words` capacity, and this
535        // loop plus the optional tail push emits at most `n_words` words.
536        unsafe {
537            values.push_unchecked(lhs | rhs);
538            validity
539                .push_unchecked((lhs_valid & rhs_valid) | (lhs_valid & lhs) | (rhs_valid & rhs));
540        }
541    }
542
543    if full_bytes != n_bytes {
544        let tail_len = n_bytes - full_bytes;
545        let lhs = lhs_words.word_at(full_bytes, tail_len);
546        let rhs = rhs_words.word_at(full_bytes, tail_len);
547        let lhs_valid = lhs_valid_words.word_at(full_bytes, tail_len);
548        let rhs_valid = rhs_valid_words.word_at(full_bytes, tail_len);
549
550        // SAFETY: see the loop safety comment above.
551        unsafe {
552            values.push_unchecked(lhs | rhs);
553            validity
554                .push_unchecked((lhs_valid & rhs_valid) | (lhs_valid & lhs) | (rhs_valid & rhs));
555        }
556    }
557
558    finish_fused_boolean_words(len, n_bytes, values, validity, nullability)
559}
560
561fn finish_fused_boolean_words(
562    len: usize,
563    n_bytes: usize,
564    values: BufferMut<u64>,
565    validity: BufferMut<u64>,
566    nullability: Nullability,
567) -> VortexResult<ArrayRef> {
568    let mut values = values.into_byte_buffer();
569    values.truncate(n_bytes);
570    let mut validity = validity.into_byte_buffer();
571    validity.truncate(n_bytes);
572    Ok(BoolArray::try_new(
573        BitBuffer::new(values.freeze(), len),
574        Validity::from_mask(
575            Mask::from_buffer(BitBuffer::new(validity.freeze(), len)),
576            nullability,
577        ),
578    )?
579    .into_array())
580}
581
582fn fused_boolean_words<L, R, LV, RV>(
583    len: usize,
584    lhs_words: L,
585    rhs_words: R,
586    lhs_valid_words: LV,
587    rhs_valid_words: RV,
588    operator: Operator,
589    nullability: Nullability,
590) -> VortexResult<ArrayRef>
591where
592    L: Iterator<Item = u64>,
593    R: Iterator<Item = u64>,
594    LV: Iterator<Item = u64>,
595    RV: Iterator<Item = u64>,
596{
597    match operator {
598        Operator::And => fused_boolean_and_words(
599            len,
600            lhs_words,
601            rhs_words,
602            lhs_valid_words,
603            rhs_valid_words,
604            nullability,
605        ),
606        Operator::Or => fused_boolean_or_words(
607            len,
608            lhs_words,
609            rhs_words,
610            lhs_valid_words,
611            rhs_valid_words,
612            nullability,
613        ),
614        other => vortex_bail!("Not a boolean operator: {other}"),
615    }
616}
617
618fn fused_boolean_and_words<L, R, LV, RV>(
619    len: usize,
620    lhs_words: L,
621    rhs_words: R,
622    lhs_valid_words: LV,
623    rhs_valid_words: RV,
624    nullability: Nullability,
625) -> VortexResult<ArrayRef>
626where
627    L: Iterator<Item = u64>,
628    R: Iterator<Item = u64>,
629    LV: Iterator<Item = u64>,
630    RV: Iterator<Item = u64>,
631{
632    let n_words = len.div_ceil(64);
633    let mut values = BufferMut::<u64>::with_capacity(n_words);
634    let mut validity = BufferMut::<u64>::with_capacity(n_words);
635
636    for (((lhs, rhs), lhs_valid), rhs_valid) in lhs_words
637        .zip(rhs_words)
638        .zip(lhs_valid_words)
639        .zip(rhs_valid_words)
640        .take(n_words)
641    {
642        // SAFETY: both buffers were allocated with exactly `n_words` capacity, and this loop is
643        // capped at `n_words`.
644        unsafe {
645            values.push_unchecked(lhs & rhs);
646            validity
647                .push_unchecked((lhs_valid & rhs_valid) | (lhs_valid & !lhs) | (rhs_valid & !rhs));
648        }
649    }
650
651    finish_fused_boolean_words(len, len.div_ceil(8), values, validity, nullability)
652}
653
654fn fused_boolean_or_words<L, R, LV, RV>(
655    len: usize,
656    lhs_words: L,
657    rhs_words: R,
658    lhs_valid_words: LV,
659    rhs_valid_words: RV,
660    nullability: Nullability,
661) -> VortexResult<ArrayRef>
662where
663    L: Iterator<Item = u64>,
664    R: Iterator<Item = u64>,
665    LV: Iterator<Item = u64>,
666    RV: Iterator<Item = u64>,
667{
668    let n_words = len.div_ceil(64);
669    let mut values = BufferMut::<u64>::with_capacity(n_words);
670    let mut validity = BufferMut::<u64>::with_capacity(n_words);
671
672    for (((lhs, rhs), lhs_valid), rhs_valid) in lhs_words
673        .zip(rhs_words)
674        .zip(lhs_valid_words)
675        .zip(rhs_valid_words)
676        .take(n_words)
677    {
678        // SAFETY: both buffers were allocated with exactly `n_words` capacity, and this loop is
679        // capped at `n_words`.
680        unsafe {
681            values.push_unchecked(lhs | rhs);
682            validity
683                .push_unchecked((lhs_valid & rhs_valid) | (lhs_valid & lhs) | (rhs_valid & rhs));
684        }
685    }
686
687    finish_fused_boolean_words(len, len.div_ceil(8), values, validity, nullability)
688}
689
690fn constant_bool_result(value: Option<bool>, len: usize, nullability: Nullability) -> ArrayRef {
691    let scalar = value
692        .map(|b| Scalar::bool(b, nullability))
693        .unwrap_or_else(|| Scalar::null(DType::Bool(nullability)));
694
695    ConstantArray::new(scalar, len).into_array()
696}
697
698fn cast_bool_nullability(array: &ArrayRef, nullability: Nullability) -> VortexResult<ArrayRef> {
699    let dtype = DType::Bool(nullability);
700    if array.dtype() == &dtype {
701        Ok(array.clone())
702    } else {
703        array.cast(dtype)
704    }
705}
706
707fn boolean_nullability(lhs: &ArrayRef, rhs: &ArrayRef) -> Nullability {
708    lhs.dtype().nullability() | rhs.dtype().nullability()
709}
710
711#[inline]
712fn is_boolean_operator(operator: Operator) -> bool {
713    matches!(operator, Operator::And | Operator::Or)
714}
715
716#[cfg(test)]
717mod tests {
718    use rstest::rstest;
719    use vortex_error::VortexResult;
720
721    use crate::ArrayRef;
722    use crate::IntoArray;
723    use crate::VortexSessionExecute;
724    use crate::array_session;
725    use crate::arrays::BoolArray;
726    use crate::arrays::ConstantArray;
727    use crate::assert_arrays_eq;
728    use crate::builtins::ArrayBuiltins;
729    use crate::dtype::DType;
730    use crate::dtype::Nullability;
731    use crate::scalar::Scalar;
732    use crate::scalar_fn::fns::operators::Operator;
733
734    #[test]
735    fn test_kleene_truth_table() -> VortexResult<()> {
736        let mut ctx = array_session().create_execution_ctx();
737        let lhs = BoolArray::from_iter([
738            Some(true),
739            Some(true),
740            Some(true),
741            Some(false),
742            Some(false),
743            Some(false),
744            None,
745            None,
746            None,
747        ])
748        .into_array();
749        let rhs = BoolArray::from_iter([
750            Some(true),
751            Some(false),
752            None,
753            Some(true),
754            Some(false),
755            None,
756            Some(true),
757            Some(false),
758            None,
759        ])
760        .into_array();
761
762        assert_arrays_eq!(
763            lhs.binary(rhs.clone(), Operator::And)?,
764            BoolArray::from_iter([
765                Some(true),
766                Some(false),
767                None,
768                Some(false),
769                Some(false),
770                Some(false),
771                None,
772                Some(false),
773                None,
774            ]),
775            &mut ctx
776        );
777
778        assert_arrays_eq!(
779            lhs.binary(rhs, Operator::Or)?,
780            BoolArray::from_iter([
781                Some(true),
782                Some(true),
783                Some(true),
784                Some(true),
785                Some(false),
786                None,
787                Some(true),
788                None,
789                None,
790            ]),
791            &mut ctx
792        );
793
794        Ok(())
795    }
796
797    #[test]
798    fn test_null_constant_kleene() -> VortexResult<()> {
799        let mut ctx = array_session().create_execution_ctx();
800        let lhs = BoolArray::from_iter([Some(false), Some(true), None]).into_array();
801        let null = ConstantArray::new(Scalar::null(DType::Bool(Nullability::Nullable)), lhs.len())
802            .into_array();
803
804        assert_arrays_eq!(
805            lhs.binary(null.clone(), Operator::And)?,
806            BoolArray::from_iter([Some(false), None, None]),
807            &mut ctx
808        );
809        assert_arrays_eq!(
810            lhs.binary(null, Operator::Or)?,
811            BoolArray::from_iter([None, Some(true), None]),
812            &mut ctx
813        );
814
815        Ok(())
816    }
817
818    #[rstest]
819    #[case(
820        BoolArray::from_iter([Some(true), Some(true), Some(false), Some(false)]).into_array(),
821        BoolArray::from_iter([Some(true), Some(false), Some(true), Some(false)]).into_array(),
822    )]
823    #[case(
824        BoolArray::from_iter([Some(true), Some(false), Some(true), Some(false)]).into_array(),
825        BoolArray::from_iter([Some(true), Some(true), Some(false), Some(false)]).into_array(),
826    )]
827    fn test_or(#[case] lhs: ArrayRef, #[case] rhs: ArrayRef) {
828        let mut ctx = array_session().create_execution_ctx();
829        let r = lhs.binary(rhs, Operator::Or).unwrap();
830        let r = r.execute::<BoolArray>(&mut ctx).unwrap().into_array();
831
832        let v0 = r
833            .execute_scalar(0, &mut array_session().create_execution_ctx())
834            .unwrap()
835            .as_bool()
836            .value();
837        let v1 = r
838            .execute_scalar(1, &mut array_session().create_execution_ctx())
839            .unwrap()
840            .as_bool()
841            .value();
842        let v2 = r
843            .execute_scalar(2, &mut array_session().create_execution_ctx())
844            .unwrap()
845            .as_bool()
846            .value();
847        let v3 = r
848            .execute_scalar(3, &mut array_session().create_execution_ctx())
849            .unwrap()
850            .as_bool()
851            .value();
852
853        assert!(v0.unwrap());
854        assert!(v1.unwrap());
855        assert!(v2.unwrap());
856        assert!(!v3.unwrap());
857    }
858
859    #[rstest]
860    #[case(
861        BoolArray::from_iter([Some(true), Some(true), Some(false), Some(false)]).into_array(),
862        BoolArray::from_iter([Some(true), Some(false), Some(true), Some(false)]).into_array(),
863    )]
864    #[case(
865        BoolArray::from_iter([Some(true), Some(false), Some(true), Some(false)]).into_array(),
866        BoolArray::from_iter([Some(true), Some(true), Some(false), Some(false)]).into_array(),
867    )]
868    fn test_and(#[case] lhs: ArrayRef, #[case] rhs: ArrayRef) {
869        let mut ctx = array_session().create_execution_ctx();
870        let r = lhs
871            .binary(rhs, Operator::And)
872            .unwrap()
873            .execute::<BoolArray>(&mut ctx)
874            .unwrap()
875            .into_array();
876
877        let v0 = r
878            .execute_scalar(0, &mut array_session().create_execution_ctx())
879            .unwrap()
880            .as_bool()
881            .value();
882        let v1 = r
883            .execute_scalar(1, &mut array_session().create_execution_ctx())
884            .unwrap()
885            .as_bool()
886            .value();
887        let v2 = r
888            .execute_scalar(2, &mut array_session().create_execution_ctx())
889            .unwrap()
890            .as_bool()
891            .value();
892        let v3 = r
893            .execute_scalar(3, &mut array_session().create_execution_ctx())
894            .unwrap()
895            .as_bool()
896            .value();
897
898        assert!(v0.unwrap());
899        assert!(!v1.unwrap());
900        assert!(!v2.unwrap());
901        assert!(!v3.unwrap());
902    }
903}