• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16980200873

15 Aug 2025 12:53AM UTC coverage: 49.805%. First build
16980200873

Pull #2456

github

web-flow
Merge aff477380 into aaf3e36ad
Pull Request #2456: feat: basic BoolBuffer / BoolBufferMut

574 of 1074 new or added lines in 84 files covered. (53.45%)

20158 of 40474 relevant lines covered (49.8%)

238516.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

48.44
/vortex-array/src/arrays/bool/array.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use arrow_array::BooleanArray;
5
use vortex_buffer::{BitBuffer, BitBufferMut};
6
use vortex_dtype::DType;
7
use vortex_error::{VortexResult, vortex_panic};
8

9
use crate::Canonical;
10
use crate::arrays::{BoolVTable, bool};
11
use crate::builders::ArrayBuilder;
12
use crate::stats::{ArrayStats, StatsSetRef};
13
use crate::validity::Validity;
14
use crate::vtable::{ArrayVTable, CanonicalVTable, ValidityHelper};
15

16
/// A boolean array that stores true/false values in a compact bit-packed format.
17
///
18
/// This mirrors the Apache Arrow Boolean array encoding, where each boolean value
19
/// is stored as a single bit rather than a full byte.
20
///
21
/// The data layout uses:
22
/// - A bit-packed buffer where each bit represents one boolean value (0 = false, 1 = true)
23
/// - An optional validity child array, which must be of type `Bool(NonNullable)`, where true values
24
///   indicate valid and false indicates null. if the i-th value is null in the validity child,
25
///   the i-th packed bit in the buffer may be 0 or 1, i.e. it is undefined.
26
/// - Bit-level slicing is supported with minimal overhead
27
///
28
/// # Examples
29
///
30
/// ```
31
/// use vortex_array::arrays::BoolArray;
32
/// use vortex_array::IntoArray;
33
///
34
/// // Create from iterator using FromIterator impl
35
/// let array: BoolArray = [true, false, true, false].into_iter().collect();
36
///
37
/// // Slice the array
38
/// let sliced = array.slice(1, 3).unwrap();
39
/// assert_eq!(sliced.len(), 2);
40
///
41
/// // Access individual values
42
/// let value = array.scalar_at(0).unwrap();
43
/// assert_eq!(value, true.into());
44
/// ```
45
#[derive(Clone, Debug)]
46
pub struct BoolArray {
47
    dtype: DType,
48
    buffer: BitBuffer,
49
    pub(crate) validity: Validity,
50
    pub(crate) stats_set: ArrayStats,
51
}
52

53
impl BoolArray {
54
    /// Create a new [`BoolArray`] from a set of indices, a length and a [`Validity`].
55
    /// All indices must be less than the length.
56
    pub fn from_indices<I: IntoIterator<Item = usize>>(
×
57
        length: usize,
×
58
        indices: I,
×
59
        validity: Validity,
×
60
    ) -> Self {
×
NEW
61
        let mut buffer = BitBufferMut::new_unset(length);
×
62

NEW
63
        indices.into_iter().for_each(|idx| buffer.set(idx));
×
NEW
64
        Self::new(buffer.freeze(), validity)
×
65
    }
×
66

67
    /// Creates a new [`BoolArray`] from a [`BitBuffer`] and [`Validity`], without checking
68
    /// any invariants.
69
    pub fn new(buffer: BitBuffer, validity: Validity) -> Self {
10,385✔
70
        if let Some(len) = validity.maybe_len()
10,385✔
71
            && buffer.len() != len
×
72
        {
73
            vortex_panic!(
×
74
                "Buffer and validity length mismatch: buffer={}, validity={}",
×
75
                buffer.len(),
×
76
                len
77
            );
78
        }
10,385✔
79

80
        // Shrink the buffer to remove any whole bytes.
81
        let buffer = buffer.shrink_offset();
10,385✔
82
        Self {
10,385✔
83
            dtype: DType::Bool(validity.nullability()),
10,385✔
84
            buffer,
10,385✔
85
            validity,
10,385✔
86
            stats_set: ArrayStats::default(),
10,385✔
87
        }
10,385✔
88
    }
10,385✔
89

90
    /// Returns the underlying [`BitBuffer`] of the array.
91
    pub fn bit_buffer(&self) -> &BitBuffer {
21,088✔
92
        assert!(
21,088✔
93
            self.buffer.offset() < 8,
21,088✔
94
            "Offset must be <8, did we forget to call shrink_offset? Found {}",
×
95
            self.buffer.offset()
×
96
        );
97
        &self.buffer
21,088✔
98
    }
21,088✔
99

100
    /// Returns the underlying [`BitBuffer`] ofthe array
NEW
101
    pub fn into_bit_buffer(self) -> BitBuffer {
×
NEW
102
        self.buffer
×
103
    }
×
104
}
105

106
impl From<BitBuffer> for BoolArray {
NEW
107
    fn from(value: BitBuffer) -> Self {
×
108
        Self::new(value, Validity::NonNullable)
×
109
    }
×
110
}
111

112
impl FromIterator<bool> for BoolArray {
113
    fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
×
NEW
114
        Self::new(BitBuffer::from_iter(iter), Validity::NonNullable)
×
115
    }
×
116
}
117

118
impl FromIterator<Option<bool>> for BoolArray {
119
    fn from_iter<I: IntoIterator<Item = Option<bool>>>(iter: I) -> Self {
×
120
        let (buffer, nulls) = BooleanArray::from_iter(iter).into_parts();
×
121

122
        Self::new(
×
NEW
123
            buffer.into(),
×
124
            nulls.map(Validity::from).unwrap_or(Validity::AllValid),
×
125
        )
126
    }
×
127
}
128

129
impl ValidityHelper for BoolArray {
130
    fn validity(&self) -> &Validity {
31,875✔
131
        &self.validity
31,875✔
132
    }
31,875✔
133
}
134

135
impl ArrayVTable<BoolVTable> for BoolVTable {
136
    fn len(array: &BoolArray) -> usize {
128,208✔
137
        array.buffer.len()
128,208✔
138
    }
128,208✔
139

140
    fn dtype(array: &BoolArray) -> &DType {
132,211✔
141
        &array.dtype
132,211✔
142
    }
132,211✔
143

144
    fn stats(array: &BoolArray) -> StatsSetRef<'_> {
61,447✔
145
        array.stats_set.to_ref(array.as_ref())
61,447✔
146
    }
61,447✔
147
}
148

149
impl CanonicalVTable<BoolVTable> for BoolVTable {
150
    fn canonicalize(array: &BoolArray) -> VortexResult<Canonical> {
9,141✔
151
        Ok(Canonical::Bool(array.clone()))
9,141✔
152
    }
9,141✔
153

154
    fn append_to_builder(array: &BoolArray, builder: &mut dyn ArrayBuilder) -> VortexResult<()> {
×
155
        builder.extend_from_array(array.as_ref())
×
156
    }
×
157
}
158

159
#[cfg(test)]
160
mod tests {
161
    use vortex_buffer::{BitBuffer, BitBufferMut, buffer};
162

163
    use crate::arrays::{BoolArray, PrimitiveArray};
164
    use crate::patches::Patches;
165
    use crate::validity::Validity;
166
    use crate::vtable::ValidityHelper;
167
    use crate::{Array, IntoArray, ToCanonical};
168

169
    #[test]
170
    fn bool_array() {
171
        let arr = BoolArray::from_iter([true, false, true]);
172
        let scalar = bool::try_from(&arr.scalar_at(0).unwrap()).unwrap();
173
        assert!(scalar);
174
    }
175

176
    #[test]
177
    fn test_all_some_iter() {
178
        let arr = BoolArray::from_iter([Some(true), Some(false)]);
179

180
        assert!(matches!(arr.validity(), Validity::AllValid));
181

182
        let scalar = bool::try_from(&arr.scalar_at(0).unwrap()).unwrap();
183
        assert!(scalar);
184
        let scalar = bool::try_from(&arr.scalar_at(1).unwrap()).unwrap();
185
        assert!(!scalar);
186
    }
187

188
    #[test]
189
    fn test_bool_from_iter() {
190
        let arr = BoolArray::from_iter([Some(true), Some(true), None, Some(false), None]);
191

192
        let scalar = bool::try_from(&arr.scalar_at(0).unwrap()).unwrap();
193
        assert!(scalar);
194

195
        let scalar = bool::try_from(&arr.scalar_at(1).unwrap()).unwrap();
196
        assert!(scalar);
197

198
        let scalar = arr.scalar_at(2).unwrap();
199
        assert!(scalar.is_null());
200

201
        let scalar = bool::try_from(&arr.scalar_at(3).unwrap()).unwrap();
202
        assert!(!scalar);
203

204
        let scalar = arr.scalar_at(4).unwrap();
205
        assert!(scalar.is_null());
206
    }
207

208
    #[test]
209
    fn patch_sliced_bools() {
210
        let arr = {
211
            let mut builder = BitBufferMut::new_unset(12);
212
            (1..12).for_each(|i| builder.set(i));
213
            BoolArray::from(builder.freeze())
214
        };
215
        let sliced = arr.slice(4, 12).unwrap();
216
        let sliced_len = sliced.len();
217
        let (values, offset) = sliced.to_bool().unwrap().into_bit_buffer().into_mut();
218
        assert_eq!(offset, 4);
219
        assert_eq!(values.as_slice(), &[254, 15]);
220

221
        // patch the underlying array
222
        let patches = Patches::new(
223
            arr.len(),
224
            0,
225
            buffer![4u32].into_array(), // This creates a non-nullable array
226
            BoolArray::from(BitBuffer::new_unset(1)).into_array(),
227
        );
228
        let arr = arr.patch(&patches).unwrap();
229
        let arr_len = arr.len();
230
        let (values, offset) = arr.to_bool().unwrap().into_bit_buffer().into_mut();
231
        assert_eq!(offset, 0);
232
        assert_eq!(values.len(), arr_len);
233
        assert_eq!(values.as_slice(), &[238, 15]);
234

235
        // the slice should be unchanged
236
        let (values, offset) = sliced.to_bool().unwrap().into_bit_buffer().into_mut();
237
        assert_eq!(offset, 4);
238
        assert_eq!(values.len(), sliced_len);
239
        assert_eq!(values.as_slice(), &[254, 15]); // unchanged
240
    }
241

242
    #[test]
243
    fn slice_array_in_middle() {
244
        let arr = BoolArray::from(BitBuffer::new_set(16));
245
        let sliced = arr.slice(4, 12).unwrap();
246
        let sliced_len = sliced.len();
247
        let (values, offset) = sliced.to_bool().unwrap().into_bit_buffer().into_mut();
248
        assert_eq!(offset, 4);
249
        assert_eq!(values.len(), sliced_len);
250
        assert_eq!(values.as_slice(), &[255, 255]);
251
    }
252

253
    #[test]
254
    fn patch_bools_owned() {
255
        let arr = BoolArray::from(BitBuffer::new_set(16));
256
        let buf_ptr = arr.bit_buffer().inner().as_ptr();
257

258
        let patches = Patches::new(
259
            arr.len(),
260
            0,
261
            PrimitiveArray::new(buffer![0u32], Validity::NonNullable).into_array(),
262
            BoolArray::from(BitBuffer::new_unset(1)).into_array(),
263
        );
264
        let arr = arr.patch(&patches).unwrap();
265
        assert_eq!(arr.bit_buffer().inner().as_ptr(), buf_ptr);
266

267
        let values = arr.into_bit_buffer();
268
        assert_eq!(values.inner().as_slice(), &[254, 255]);
269
    }
270
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc