Skip to main content

vortex_array/arrays/bool/
array.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Display;
5use std::fmt::Formatter;
6
7use smallvec::smallvec;
8use vortex_buffer::BitBuffer;
9use vortex_buffer::BitBufferMeta;
10use vortex_buffer::BitBufferMut;
11use vortex_buffer::BitBufferView;
12use vortex_error::VortexExpect;
13use vortex_error::VortexResult;
14use vortex_error::vortex_ensure;
15use vortex_error::vortex_ensure_eq;
16use vortex_mask::Mask;
17
18use crate::ArrayRef;
19use crate::ArraySlots;
20use crate::ExecutionCtx;
21use crate::IntoArray;
22use crate::array::Array;
23use crate::array::ArrayParts;
24use crate::array::TypedArrayRef;
25use crate::array::child_to_validity;
26use crate::array::validity_to_child;
27use crate::array_slots;
28use crate::arrays::Bool;
29use crate::arrays::BoolArray;
30use crate::buffer::BufferHandle;
31use crate::dtype::DType;
32use crate::validity::Validity;
33
34#[array_slots(Bool)]
35pub struct BoolSlots {
36    /// The validity bitmap indicating which elements are non-null.
37    #[slot(0)]
38    pub validity: Option<ArrayRef>,
39}
40
41/// Inner data for a boolean array that stores true/false values in a compact bit-packed format.
42///
43/// This mirrors the Apache Arrow Boolean array encoding, where each boolean value
44/// is stored as a single bit rather than a full byte.
45///
46/// The data layout uses:
47/// - A bit-packed buffer where each bit represents one boolean value (0 = false, 1 = true)
48/// - An optional validity child array, which must be of type `Bool(NonNullable)`, where true values
49///   indicate valid and false indicates null. if the i-th value is null in the validity child,
50///   the i-th packed bit in the buffer may be 0 or 1, i.e. it is undefined.
51/// - Bit-level slicing is supported with minimal overhead
52///
53/// # Examples
54///
55/// ```
56/// # fn main() -> vortex_error::VortexResult<()> {
57/// use vortex_array::arrays::BoolArray;
58/// use vortex_array::{IntoArray, array_session, VortexSessionExecute};
59///
60/// // Create from iterator using FromIterator impl
61/// let array: BoolArray = [true, false, true, false].into_iter().collect();
62///
63/// // Slice the array
64/// let sliced = array.slice(1..3)?;
65/// assert_eq!(sliced.len(), 2);
66///
67/// // Access individual values
68/// let mut ctx = array_session().create_execution_ctx();
69/// let value = array.execute_scalar(0, &mut ctx).unwrap();
70/// assert_eq!(value, true.into());
71/// # Ok(())
72/// # }
73/// ```
74#[derive(Clone, Debug)]
75pub struct BoolData {
76    pub(super) bits: BufferHandle,
77    pub(super) meta: BitBufferMeta,
78}
79
80impl Display for BoolData {
81    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82        write!(f, "offset: {}", self.meta.offset())
83    }
84}
85
86pub struct BoolDataParts {
87    pub bits: BufferHandle,
88    pub meta: BitBufferMeta,
89}
90
91pub trait BoolArrayExt: TypedArrayRef<Bool> {
92    fn nullability(&self) -> crate::dtype::Nullability {
93        match self.as_ref().dtype() {
94            DType::Bool(nullability) => *nullability,
95            _ => unreachable!("BoolArrayExt requires a bool dtype"),
96        }
97    }
98
99    fn validity(&self) -> Validity {
100        child_to_validity(
101            self.as_ref().slots()[BoolSlots::VALIDITY].as_ref(),
102            self.nullability(),
103        )
104    }
105
106    fn to_bit_buffer(&self) -> BitBuffer {
107        let buffer = self.bits.as_host().clone();
108        BitBuffer::new_with_offset(buffer, self.meta.len(), self.meta.offset())
109    }
110
111    /// Borrow the array's packed bits as a [`BitBufferView`] without cloning the backing buffer.
112    fn bit_buffer_view(&self) -> BitBufferView<'_> {
113        BitBufferView::from_meta(self.bits.as_host().as_slice(), self.meta)
114    }
115
116    fn maybe_execute_mask(&self, ctx: &mut ExecutionCtx) -> VortexResult<Option<Mask>> {
117        let all_valid = match &BoolArrayExt::validity(self) {
118            Validity::NonNullable | Validity::AllValid => true,
119            Validity::AllInvalid => false,
120            Validity::Array(a) => a.statistics().compute_min::<bool>(ctx).unwrap_or(false),
121        };
122        Ok(all_valid.then(|| Mask::from_buffer(self.to_bit_buffer())))
123    }
124
125    fn execute_mask(&self, ctx: &mut ExecutionCtx) -> Mask {
126        self.maybe_execute_mask(ctx)
127            .vortex_expect("failed to check validity")
128            .vortex_expect("cannot convert nullable boolean array to mask")
129    }
130
131    fn to_mask_fill_null_false(&self, ctx: &mut ExecutionCtx) -> Mask {
132        let validity_mask = BoolArrayExt::validity(self)
133            .execute_mask(self.as_ref().len(), ctx)
134            .vortex_expect("Failed to compute validity mask");
135        let buffer = match validity_mask {
136            Mask::AllTrue(_) => self.to_bit_buffer(),
137            Mask::AllFalse(_) => return Mask::new_false(self.as_ref().len()),
138            Mask::Values(validity) => validity.bit_buffer() & self.to_bit_buffer(),
139        };
140        Mask::from_buffer(buffer)
141    }
142}
143impl<T: TypedArrayRef<Bool>> BoolArrayExt for T {}
144
145/// Field accessors and non-consuming methods on the inner bool data.
146impl BoolData {
147    /// Splits into owned parts
148    #[inline]
149    pub fn into_parts(self, len: usize) -> BoolDataParts {
150        BoolDataParts {
151            bits: self.bits,
152            meta: BitBufferMeta::new(self.meta.offset(), len),
153        }
154    }
155
156    pub(crate) fn make_slots(validity: &Validity, len: usize) -> ArraySlots {
157        smallvec![validity_to_child(validity, len)]
158    }
159}
160
161/// Constructors and consuming methods for [`BoolArray`].
162impl Array<Bool> {
163    /// Constructs a new [`BoolArray`].
164    ///
165    /// # Panics
166    ///
167    /// Panics if the validity length is not equal to the bit buffer length.
168    pub fn new(bits: BitBuffer, validity: Validity) -> Self {
169        Self::try_new(bits, validity).vortex_expect("Failed to create BoolArray")
170    }
171
172    /// Constructs a new [`BoolArray`] from a [`BufferHandle`].
173    ///
174    /// # Panics
175    ///
176    /// Panics if the validity length is not equal to the bit buffer length.
177    pub fn new_handle(handle: BufferHandle, offset: usize, len: usize, validity: Validity) -> Self {
178        Self::try_new_from_handle(handle, offset, len, validity)
179            .vortex_expect("Failed to create BoolArray from BufferHandle")
180    }
181
182    /// Constructs a new `BoolArray`.
183    ///
184    /// # Errors
185    ///
186    /// Returns an error if the provided components do not satisfy the invariants.
187    pub fn try_new(bits: BitBuffer, validity: Validity) -> VortexResult<Self> {
188        let dtype = DType::Bool(validity.nullability());
189        let len = bits.len();
190        let slots = BoolData::make_slots(&validity, len);
191        let data = BoolData::try_new(bits, validity)?;
192        Ok(unsafe { Array::from_parts_unchecked(ArrayParts::new(Bool, dtype, len, data, slots)) })
193    }
194
195    /// Build a new bool array from a `BufferHandle`, returning an error if the offset is
196    /// too large or the buffer is not large enough to hold the values.
197    pub fn try_new_from_handle(
198        bits: BufferHandle,
199        offset: usize,
200        len: usize,
201        validity: Validity,
202    ) -> VortexResult<Self> {
203        let dtype = DType::Bool(validity.nullability());
204        let slots = BoolData::make_slots(&validity, len);
205        let data = BoolData::try_new_from_handle(bits, offset, len, validity)?;
206        Ok(unsafe { Array::from_parts_unchecked(ArrayParts::new(Bool, dtype, len, data, slots)) })
207    }
208
209    /// Creates a new [`BoolArray`] without validation.
210    ///
211    /// # Safety
212    ///
213    /// The caller must ensure that the validity length is equal to the bit buffer length.
214    pub unsafe fn new_unchecked(bits: BitBuffer, validity: Validity) -> Self {
215        let dtype = DType::Bool(validity.nullability());
216        let len = bits.len();
217        let slots = BoolData::make_slots(&validity, len);
218        // SAFETY: caller guarantees validity length equals bit buffer length.
219        let data = unsafe { BoolData::new_unchecked(bits, validity) };
220        unsafe { Array::from_parts_unchecked(ArrayParts::new(Bool, dtype, len, data, slots)) }
221    }
222
223    /// Validates the components that would be used to create a [`BoolArray`].
224    pub fn validate(bits: &BitBuffer, validity: &Validity) -> VortexResult<()> {
225        BoolData::validate(bits, validity)
226    }
227
228    /// Create a new BoolArray from a set of indices and a length.
229    ///
230    /// All indices must be less than the length.
231    pub fn from_indices<I: IntoIterator<Item = usize>>(
232        length: usize,
233        indices: I,
234        validity: Validity,
235    ) -> Self {
236        let mut buffer = BitBufferMut::new_unset(length);
237        indices.into_iter().for_each(|idx| buffer.set(idx));
238        Self::new(buffer.freeze(), validity)
239    }
240
241    /// Returns the underlying [`BitBuffer`] of the array, consuming self.
242    pub fn into_bit_buffer(self) -> BitBuffer {
243        let len = self.len();
244        let data = self.into_data();
245        let buffer = data.bits.unwrap_host();
246        BitBuffer::new_with_offset(buffer, len, data.meta.offset())
247    }
248}
249
250// Internal constructors on BoolData (used by [`BoolArray`] constructors and [`VTable::build`]).
251impl BoolData {
252    pub(super) fn try_new(bits: BitBuffer, validity: Validity) -> VortexResult<Self> {
253        let bits = bits.shrink_offset();
254        Self::validate(&bits, &validity)?;
255
256        let (offset, len, buffer) = bits.into_inner();
257
258        Ok(Self {
259            bits: BufferHandle::new_host(buffer),
260            meta: BitBufferMeta::new(offset, len),
261        })
262    }
263
264    pub(super) fn try_new_from_handle(
265        bits: BufferHandle,
266        offset: usize,
267        len: usize,
268        validity: Validity,
269    ) -> VortexResult<Self> {
270        vortex_ensure!(offset < 8, "BitBuffer offset must be <8, got {}", offset);
271        if let Some(validity_len) = validity.maybe_len() {
272            vortex_ensure_eq!(
273                validity_len,
274                len,
275                "BoolArray validity length does not match array length",
276            );
277        }
278
279        vortex_ensure!(
280            bits.len() * 8 >= (len + offset),
281            "provided BufferHandle with offset {offset} len {len} had size {} bits",
282            bits.len() * 8,
283        );
284
285        Ok(Self {
286            bits,
287            meta: BitBufferMeta::new(offset, len),
288        })
289    }
290
291    pub(super) unsafe fn new_unchecked(bits: BitBuffer, validity: Validity) -> Self {
292        if cfg!(debug_assertions) {
293            Self::try_new(bits, validity).vortex_expect("Failed to create BoolData")
294        } else {
295            let (offset, len, buffer) = bits.into_inner();
296
297            Self {
298                bits: BufferHandle::new_host(buffer),
299                meta: BitBufferMeta::new(offset, len),
300            }
301        }
302    }
303
304    pub(super) fn validate(bits: &BitBuffer, validity: &Validity) -> VortexResult<()> {
305        vortex_ensure!(
306            bits.offset() < 8,
307            "BitBuffer offset must be <8, got {}",
308            bits.offset()
309        );
310
311        if let Some(validity_len) = validity.maybe_len() {
312            vortex_ensure_eq!(
313                validity_len,
314                bits.len(),
315                "BoolArray validity length does not match array length",
316            );
317        }
318
319        Ok(())
320    }
321}
322
323impl From<BitBuffer> for BoolArray {
324    fn from(value: BitBuffer) -> Self {
325        BoolArray::new(value, Validity::NonNullable)
326    }
327}
328
329impl FromIterator<bool> for BoolArray {
330    fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
331        BoolArray::from(BitBuffer::from_iter(iter))
332    }
333}
334
335impl FromIterator<Option<bool>> for BoolArray {
336    fn from_iter<I: IntoIterator<Item = Option<bool>>>(iter: I) -> Self {
337        let iter = iter.into_iter();
338        let capacity = iter.size_hint().0;
339        let mut bits = BitBufferMut::with_capacity(capacity);
340        let mut validity = BitBufferMut::with_capacity(capacity);
341        for value in iter {
342            bits.append(value.unwrap_or_default());
343            validity.append(value.is_some());
344        }
345
346        BoolArray::new(bits.freeze(), Validity::from(validity.freeze()))
347    }
348}
349
350impl IntoArray for BitBuffer {
351    fn into_array(self) -> ArrayRef {
352        BoolArray::new(self, Validity::NonNullable).into_array()
353    }
354}
355
356impl IntoArray for BitBufferMut {
357    fn into_array(self) -> ArrayRef {
358        self.freeze().into_array()
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use std::iter::once;
365    use std::iter::repeat_n;
366
367    use vortex_buffer::Alignment;
368    use vortex_buffer::BitBuffer;
369    use vortex_buffer::BitBufferMut;
370    use vortex_buffer::ByteBuffer;
371    use vortex_buffer::buffer;
372    use vortex_error::VortexResult;
373
374    use crate::IntoArray;
375    use crate::VortexSessionExecute;
376    use crate::array_session;
377    use crate::arrays::BoolArray;
378    use crate::arrays::PrimitiveArray;
379    use crate::arrays::bool::BoolArrayExt;
380    use crate::assert_arrays_eq;
381    use crate::buffer::BufferHandle;
382    use crate::patches::Patches;
383    use crate::validity::Validity;
384
385    #[test]
386    fn bool_array() {
387        let mut ctx = array_session().create_execution_ctx();
388
389        let arr = BoolArray::from_iter([true, false, true]);
390        let scalar = bool::try_from(&arr.execute_scalar(0, &mut ctx).unwrap()).unwrap();
391        assert!(scalar);
392    }
393
394    #[test]
395    fn test_all_some_iter() {
396        let mut ctx = array_session().create_execution_ctx();
397
398        let arr = BoolArray::from_iter([Some(true), Some(false)]);
399
400        assert!(matches!(arr.validity(), Ok(Validity::AllValid)));
401
402        let scalar = bool::try_from(&arr.execute_scalar(0, &mut ctx).unwrap()).unwrap();
403        assert!(scalar);
404        let scalar = bool::try_from(&arr.execute_scalar(1, &mut ctx).unwrap()).unwrap();
405        assert!(!scalar);
406    }
407
408    #[test]
409    fn test_bool_from_iter() {
410        let mut ctx = array_session().create_execution_ctx();
411        let arr = BoolArray::from_iter([Some(true), Some(true), None, Some(false), None]);
412
413        let scalar = bool::try_from(&arr.execute_scalar(0, &mut ctx).unwrap()).unwrap();
414        assert!(scalar);
415
416        let scalar = bool::try_from(&arr.execute_scalar(1, &mut ctx).unwrap()).unwrap();
417        assert!(scalar);
418
419        let scalar = arr.execute_scalar(2, &mut ctx).unwrap();
420        assert!(scalar.is_null());
421
422        let scalar = bool::try_from(&arr.execute_scalar(3, &mut ctx).unwrap()).unwrap();
423        assert!(!scalar);
424
425        let scalar = arr.execute_scalar(4, &mut ctx).unwrap();
426        assert!(scalar.is_null());
427    }
428
429    #[test]
430    fn patch_sliced_bools() {
431        let mut ctx = array_session().create_execution_ctx();
432        let arr = BoolArray::from(BitBuffer::new_set(12));
433        let sliced = arr.slice(4..12).unwrap();
434        assert_arrays_eq!(sliced, BoolArray::from_iter([true; 8]), &mut ctx);
435
436        let arr = {
437            let mut builder = BitBufferMut::new_unset(12);
438            (1..12).for_each(|i| builder.set(i));
439            BoolArray::from(builder.freeze())
440        };
441        let sliced = arr.slice(4..12).unwrap();
442        let expected_slice: Vec<bool> = (4..12).map(|i| (1..12).contains(&i)).collect();
443        assert_arrays_eq!(
444            sliced,
445            BoolArray::from_iter(expected_slice.clone()),
446            &mut ctx
447        );
448
449        // patch the underlying array at index 4 to false
450        let patches = Patches::new(
451            arr.len(),
452            0,
453            buffer![4u32].into_array(),
454            BoolArray::from(BitBuffer::new_unset(1)).into_array(),
455            None,
456        )
457        .unwrap();
458        let arr = arr.patch(&patches, &mut ctx).unwrap();
459        // After patching index 4 to false: indices 1-3 and 5-11 are true, index 0 and 4 are false
460        let expected_patched: Vec<bool> = (0..12).map(|i| (1..12).contains(&i) && i != 4).collect();
461        assert_arrays_eq!(arr, BoolArray::from_iter(expected_patched), &mut ctx);
462
463        // the slice should be unchanged (still has original values before patch)
464        assert_arrays_eq!(sliced, BoolArray::from_iter(expected_slice), &mut ctx);
465    }
466
467    #[test]
468    fn slice_array_in_middle() {
469        let mut ctx = array_session().create_execution_ctx();
470        let arr = BoolArray::from(BitBuffer::new_set(16));
471        let sliced = arr.slice(4..12).unwrap();
472        assert_arrays_eq!(sliced, BoolArray::from_iter([true; 8]), &mut ctx);
473    }
474
475    #[test]
476    fn slice_aligned_host_handle_at_unaligned_byte() -> VortexResult<()> {
477        let bits: ByteBuffer = buffer![0b1010_1100_u8, 0b0110_1001, 0];
478        let bits = bits.aligned(Alignment::of::<u64>());
479        let array =
480            BoolArray::new_handle(BufferHandle::new_host(bits), 0, 16, Validity::NonNullable)
481                .into_array();
482
483        let sliced = array.slice(9..15)?;
484
485        let mut ctx = array_session().create_execution_ctx();
486        assert_arrays_eq!(
487            sliced,
488            BoolArray::from_iter([false, false, true, false, true, true]),
489            &mut ctx
490        );
491        Ok(())
492    }
493
494    #[test]
495    fn patch_bools_owned() {
496        let mut ctx = array_session().create_execution_ctx();
497        let arr = BoolArray::from(BitBuffer::new_set(16));
498        let buf_ptr = arr.to_bit_buffer().inner().as_ptr();
499
500        let patches = Patches::new(
501            arr.len(),
502            0,
503            PrimitiveArray::new(buffer![0u32], Validity::NonNullable).into_array(),
504            BoolArray::from(BitBuffer::new_unset(1)).into_array(),
505            None,
506        )
507        .unwrap();
508        let arr = arr.patch(&patches, &mut ctx).unwrap();
509        // Verify buffer was reused in place
510        assert_eq!(arr.to_bit_buffer().inner().as_ptr(), buf_ptr);
511
512        // After patching index 0 to false: [false, true, true, ..., true] (16 values)
513        let expected: BoolArray = once(false).chain(repeat_n(true, 15)).collect();
514        assert_arrays_eq!(arr, expected, &mut ctx);
515    }
516
517    #[test]
518    fn patch_sliced_bools_offset() {
519        let mut ctx = array_session().create_execution_ctx();
520        let arr = BoolArray::from(BitBuffer::new_set(15));
521        let sliced = arr.slice(4..15).unwrap();
522        assert_arrays_eq!(sliced, BoolArray::from_iter([true; 11]), &mut ctx);
523    }
524}