Skip to main content

vortex_array/builders/
bool.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5use std::mem;
6
7use vortex_buffer::BitBufferMut;
8use vortex_error::VortexExpect;
9use vortex_error::VortexResult;
10use vortex_error::vortex_ensure;
11use vortex_mask::Mask;
12
13use crate::ArrayRef;
14use crate::IntoArray;
15use crate::LEGACY_SESSION;
16use crate::VortexSessionExecute;
17use crate::arrays::BoolArray;
18use crate::arrays::bool::BoolArrayExt;
19use crate::builders::ArrayBuilder;
20use crate::builders::DEFAULT_BUILDER_CAPACITY;
21use crate::builders::LazyBitBufferBuilder;
22use crate::canonical::Canonical;
23#[expect(deprecated)]
24use crate::canonical::ToCanonical as _;
25use crate::dtype::DType;
26use crate::dtype::Nullability;
27use crate::scalar::Scalar;
28
29pub struct BoolBuilder {
30    dtype: DType,
31    inner: BitBufferMut,
32    nulls: LazyBitBufferBuilder,
33}
34
35impl BoolBuilder {
36    pub fn new(nullability: Nullability) -> Self {
37        Self::with_capacity(nullability, DEFAULT_BUILDER_CAPACITY)
38    }
39
40    pub fn with_capacity(nullability: Nullability, capacity: usize) -> Self {
41        Self {
42            inner: BitBufferMut::with_capacity(capacity),
43            nulls: LazyBitBufferBuilder::new(capacity),
44            dtype: DType::Bool(nullability),
45        }
46    }
47
48    /// Appends a boolean value to the builder.
49    pub fn append_value(&mut self, value: bool) {
50        self.append_values(value, 1)
51    }
52
53    /// Appends the same boolean value multiple times to the builder.
54    ///
55    /// This method appends the given boolean value `n` times.
56    pub fn append_values(&mut self, value: bool, n: usize) {
57        self.inner.append_n(value, n);
58        self.nulls.append_n_non_nulls(n)
59    }
60
61    /// Finishes the builder directly into a [`BoolArray`].
62    pub fn finish_into_bool(&mut self) -> BoolArray {
63        assert_eq!(
64            self.nulls.len(),
65            self.inner.len(),
66            "Null count and value count should match when calling BoolBuilder::finish."
67        );
68
69        BoolArray::new(
70            mem::take(&mut self.inner).freeze(),
71            self.nulls.finish_with_nullability(self.dtype.nullability()),
72        )
73    }
74}
75
76impl ArrayBuilder for BoolBuilder {
77    fn as_any(&self) -> &dyn Any {
78        self
79    }
80
81    fn as_any_mut(&mut self) -> &mut dyn Any {
82        self
83    }
84
85    fn dtype(&self) -> &DType {
86        &self.dtype
87    }
88
89    fn len(&self) -> usize {
90        self.inner.len()
91    }
92
93    fn append_zeros(&mut self, n: usize) {
94        self.append_values(false, n)
95    }
96
97    unsafe fn append_nulls_unchecked(&mut self, n: usize) {
98        self.inner.append_n(false, n);
99        self.nulls.append_n_nulls(n)
100    }
101
102    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
103        vortex_ensure!(
104            scalar.dtype() == self.dtype(),
105            "BoolBuilder expected scalar with dtype {}, got {}",
106            self.dtype(),
107            scalar.dtype()
108        );
109
110        match scalar.as_bool().value() {
111            Some(value) => self.append_value(value),
112            None => self.append_null(),
113        }
114
115        Ok(())
116    }
117
118    unsafe fn extend_from_array_unchecked(&mut self, array: &ArrayRef) {
119        #[expect(deprecated)]
120        let bool_array = array.to_bool();
121
122        self.inner.append_buffer(&bool_array.to_bit_buffer());
123        self.nulls.append_validity_mask(
124            bool_array
125                .as_ref()
126                .validity()
127                .vortex_expect("validity_mask")
128                .execute_mask(
129                    bool_array.as_ref().len(),
130                    &mut LEGACY_SESSION.create_execution_ctx(),
131                )
132                .vortex_expect("Failed to compute validity mask"),
133        );
134    }
135
136    fn reserve_exact(&mut self, additional: usize) {
137        self.inner.reserve(additional);
138        self.nulls.reserve_exact(additional);
139    }
140
141    unsafe fn set_validity_unchecked(&mut self, validity: Mask) {
142        self.nulls = LazyBitBufferBuilder::new(validity.len());
143        self.nulls.append_validity_mask(validity);
144    }
145
146    fn finish(&mut self) -> ArrayRef {
147        self.finish_into_bool().into_array()
148    }
149
150    fn finish_into_canonical(&mut self) -> Canonical {
151        Canonical::Bool(self.finish_into_bool())
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use rand::RngExt;
158    use rand::SeedableRng;
159    use rand::prelude::StdRng;
160    use vortex_error::VortexResult;
161
162    use crate::ArrayRef;
163    use crate::IntoArray;
164    use crate::LEGACY_SESSION;
165    use crate::VortexSessionExecute;
166    use crate::arrays::ChunkedArray;
167    use crate::arrays::bool::BoolArrayExt;
168    use crate::assert_arrays_eq;
169    use crate::builders::ArrayBuilder;
170    use crate::builders::BoolBuilder;
171    use crate::builders::bool::BoolArray;
172    use crate::builders::builder_with_capacity;
173    #[expect(deprecated)]
174    use crate::canonical::ToCanonical as _;
175    use crate::dtype::DType;
176    use crate::dtype::Nullability;
177    use crate::scalar::Scalar;
178
179    fn make_opt_bool_chunks(len: usize, chunk_count: usize) -> ArrayRef {
180        let mut rng = StdRng::seed_from_u64(0);
181
182        (0..chunk_count)
183            .map(|_| {
184                BoolArray::from_iter((0..len).map(|_| match rng.random_range::<u8, _>(0..=2) {
185                    0 => Some(false),
186                    1 => Some(true),
187                    2 => None,
188                    _ => unreachable!(),
189                }))
190                .into_array()
191            })
192            .collect::<ChunkedArray>()
193            .into_array()
194    }
195
196    #[test]
197    fn tests() -> VortexResult<()> {
198        let len = 1000;
199        let chunk_count = 10;
200        let chunk = make_opt_bool_chunks(len, chunk_count);
201
202        let mut ctx = LEGACY_SESSION.create_execution_ctx();
203        let mut builder = builder_with_capacity(chunk.dtype(), len * chunk_count);
204        chunk
205            .clone()
206            .append_to_builder(builder.as_mut(), &mut ctx)?;
207
208        #[expect(deprecated)]
209        let canon_into = builder.finish().to_bool();
210        #[expect(deprecated)]
211        let into_canon = chunk.to_bool();
212
213        assert!(
214            canon_into
215                .validity()?
216                .mask_eq(&into_canon.validity()?, &mut ctx)?
217        );
218        assert_eq!(canon_into.to_bit_buffer(), into_canon.to_bit_buffer());
219        Ok(())
220    }
221
222    #[test]
223    fn test_append_scalar() {
224        let mut builder = BoolBuilder::with_capacity(Nullability::Nullable, 10);
225
226        // Test appending true value.
227        let true_scalar = Scalar::bool(true, Nullability::Nullable);
228        builder.append_scalar(&true_scalar).unwrap();
229
230        // Test appending false value.
231        let false_scalar = Scalar::bool(false, Nullability::Nullable);
232        builder.append_scalar(&false_scalar).unwrap();
233
234        // Test appending null value.
235        let null_scalar = Scalar::null(DType::Bool(Nullability::Nullable));
236        builder.append_scalar(&null_scalar).unwrap();
237
238        let array = builder.finish_into_bool();
239        let expected = BoolArray::from_iter([Some(true), Some(false), None]);
240        assert_arrays_eq!(&array, &expected);
241
242        // Test wrong dtype error.
243        let mut builder = BoolBuilder::with_capacity(Nullability::NonNullable, 10);
244        let wrong_scalar = Scalar::from(42i32);
245        assert!(builder.append_scalar(&wrong_scalar).is_err());
246    }
247}