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