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