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