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