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

vortex-data / vortex / 16992684502

15 Aug 2025 02:56PM UTC coverage: 87.875% (+0.2%) from 87.72%
16992684502

Pull #2456

github

web-flow
Merge 2d540e578 into 4a23f65b3
Pull Request #2456: feat: basic BoolBuffer / BoolBufferMut

1275 of 1428 new or added lines in 110 files covered. (89.29%)

334 existing lines in 31 files now uncovered.

57169 of 65057 relevant lines covered (87.88%)

658056.52 hits per line

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

87.72
/vortex-array/src/builders/list.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::any::Any;
5
use std::sync::Arc;
6

7
use vortex_dtype::Nullability::NonNullable;
8
use vortex_dtype::{DType, NativePType, Nullability};
9
use vortex_error::{VortexExpect, VortexResult, vortex_bail};
10
use vortex_mask::Mask;
11
use vortex_scalar::ListScalar;
12

13
use crate::arrays::{ListArray, OffsetPType};
14
use crate::builders::lazy_validity_builder::LazyBitBufferBuilder;
15
use crate::builders::{ArrayBuilder, ArrayBuilderExt, PrimitiveBuilder, builder_with_capacity};
16
use crate::compute::{add_scalar, cast, sub_scalar};
17
use crate::{Array, ArrayRef, IntoArray, ToCanonical};
18

19
pub struct ListBuilder<O: NativePType> {
20
    value_builder: Box<dyn ArrayBuilder>,
21
    index_builder: PrimitiveBuilder<O>,
22
    nulls: LazyBitBufferBuilder,
23
    nullability: Nullability,
24
    dtype: DType,
25
}
26

27
impl<O: OffsetPType> ListBuilder<O> {
28
    /// Create a ListBuilder with the specified capacity for indices.
29
    ///
30
    /// # Notes
31
    ///
32
    /// The number of indices is one more than the number of lists in the array!
33
    ///
34
    /// See also: [ListBuilder::with_values_and_index_capacity].
35
    pub fn with_capacity(
270✔
36
        value_dtype: Arc<DType>,
270✔
37
        nullability: Nullability,
270✔
38
        index_capacity: usize,
270✔
39
    ) -> Self {
270✔
40
        // I would expect the list to have more than one value per index
41
        Self::with_values_and_index_capacity(
270✔
42
            value_dtype,
270✔
43
            nullability,
270✔
44
            2 * index_capacity,
270✔
45
            index_capacity,
270✔
46
        )
47
    }
270✔
48

49
    /// Create a ListBuilder with the specified capacity for indices and values.
50
    ///
51
    /// # Notes
52
    ///
53
    /// The number of indices is one more than the number of lists in the array!
54
    pub fn with_values_and_index_capacity(
310✔
55
        value_dtype: Arc<DType>,
310✔
56
        nullability: Nullability,
310✔
57
        values_capacity: usize,
310✔
58
        index_capacity: usize,
310✔
59
    ) -> Self {
310✔
60
        let value_builder = builder_with_capacity(value_dtype.as_ref(), values_capacity);
310✔
61
        let mut index_builder = PrimitiveBuilder::with_capacity(NonNullable, index_capacity);
310✔
62

63
        // The first index of the list, which is always 0 and represents an empty list.
64
        index_builder.append_zero();
310✔
65

66
        Self {
310✔
67
            value_builder,
310✔
68
            index_builder,
310✔
69
            nulls: LazyBitBufferBuilder::new(index_capacity),
310✔
70
            nullability,
310✔
71
            dtype: DType::List(value_dtype, nullability),
310✔
72
        }
310✔
73
    }
310✔
74

75
    pub fn append_value(&mut self, value: ListScalar) -> VortexResult<()> {
241✔
76
        match value.elements() {
241✔
77
            None => {
78
                if self.nullability == NonNullable {
2✔
79
                    vortex_bail!("Cannot append null value to non-nullable list");
1✔
80
                }
1✔
81
                self.append_null();
1✔
82
                Ok(())
1✔
83
            }
84
            Some(elements) => {
239✔
85
                for scalar in elements {
732✔
86
                    // TODO(joe): This is slow, we should be able to append multiple values at once,
87
                    // or the list scalar should hold an Array
88
                    self.value_builder.append_scalar(&scalar)?;
493✔
89
                }
90
                self.nulls.append_non_null();
239✔
91
                self.append_index(
239✔
92
                    O::from_usize(self.value_builder.len())
239✔
93
                        .vortex_expect("Failed to convert from usize to O"),
239✔
94
                )
95
            }
96
        }
97
    }
241✔
98

99
    fn append_index(&mut self, index: O) -> VortexResult<()> {
428✔
100
        self.index_builder.append_scalar(&index.into())
428✔
101
    }
428✔
102
}
103

104
impl<O: OffsetPType> ArrayBuilder for ListBuilder<O> {
105
    fn as_any(&self) -> &dyn Any {
×
106
        self
×
107
    }
×
108

109
    fn as_any_mut(&mut self) -> &mut dyn Any {
×
110
        self
×
111
    }
×
112

113
    fn dtype(&self) -> &DType {
×
114
        &self.dtype
×
115
    }
×
116

117
    fn len(&self) -> usize {
×
118
        self.nulls.len()
×
119
    }
×
120

121
    fn append_zeros(&mut self, n: usize) {
×
122
        let count = self.value_builder.len();
×
123
        self.value_builder.append_zeros(n);
×
124
        for i in 0..n {
×
125
            self.append_index(
×
126
                O::from_usize(count + i + 1).vortex_expect("Failed to convert from usize to <O>"),
×
127
            )
×
128
            .vortex_expect("Failed to append index");
×
129
        }
×
130
        self.nulls.append_n_non_nulls(n);
×
131
    }
×
132

133
    fn append_nulls(&mut self, n: usize) {
189✔
134
        let count = self.value_builder.len();
189✔
135
        for _ in 0..n {
189✔
136
            // A list with a null element is can be a list with a zero-span offset and a validity
189✔
137
            // bit set
189✔
138
            self.append_index(
189✔
139
                O::from_usize(count).vortex_expect("Failed to convert from usize to <O>"),
189✔
140
            )
189✔
141
            .vortex_expect("Failed to append index");
189✔
142
        }
189✔
143
        self.nulls.append_n_nulls(n);
189✔
144
    }
189✔
145

146
    fn extend_from_array(&mut self, array: &dyn Array) -> VortexResult<()> {
43✔
147
        let list = array.to_list()?;
43✔
148
        if list.is_empty() {
43✔
149
            return Ok(());
8✔
150
        }
35✔
151

152
        let n_already_added_values = self.value_builder.len();
35✔
153
        let Some(n_already_added_values) = O::from_usize(n_already_added_values) else {
35✔
154
            vortex_bail!(
×
155
                "cannot convert length {} to type {:?}",
×
156
                n_already_added_values,
157
                O::PTYPE
158
            )
159
        };
160

161
        let offsets = list.offsets();
35✔
162
        let elements = list.elements();
35✔
163

164
        let index_dtype = self.index_builder.dtype();
35✔
165

166
        let n_leading_junk_values_scalar = offsets.scalar_at(0)?.cast(index_dtype)?;
35✔
167
        let n_leading_junk_values = usize::try_from(&n_leading_junk_values_scalar)?;
35✔
168

169
        let casted_offsets = cast(&offsets.slice(1, offsets.len())?, index_dtype)?;
35✔
170
        let offsets_without_leading_junk =
35✔
171
            sub_scalar(&casted_offsets, n_leading_junk_values_scalar)?;
35✔
172
        let offsets_into_builder =
35✔
173
            add_scalar(&offsets_without_leading_junk, n_already_added_values.into())?;
35✔
174

175
        let last_offset = offsets.scalar_at(offsets.len() - 1)?;
35✔
176
        let last_offset = usize::try_from(&last_offset)?;
35✔
177
        let non_junk_values = elements.slice(n_leading_junk_values, last_offset)?;
35✔
178

179
        self.nulls.append_validity_mask(array.validity_mask()?);
35✔
180
        self.index_builder
35✔
181
            .extend_from_array(&offsets_into_builder)?;
35✔
182
        self.value_builder.ensure_capacity(non_junk_values.len());
35✔
183
        self.value_builder.extend_from_array(&non_junk_values)?;
35✔
184

185
        Ok(())
35✔
186
    }
43✔
187

188
    fn ensure_capacity(&mut self, capacity: usize) {
×
189
        self.index_builder.ensure_capacity(capacity);
×
190
        self.value_builder.ensure_capacity(capacity);
×
191
        self.nulls.ensure_capacity(capacity);
×
192
    }
×
193

194
    fn set_validity(&mut self, validity: Mask) {
×
NEW
195
        self.nulls = LazyBitBufferBuilder::new(validity.len());
×
196
        self.nulls.append_validity_mask(validity);
×
197
    }
×
198

199
    fn finish(&mut self) -> ArrayRef {
309✔
200
        assert_eq!(
309✔
201
            self.index_builder.len(),
309✔
202
            self.nulls.len() + 1,
309✔
203
            "Indices length must be one more than nulls length."
×
204
        );
205

206
        ListArray::try_new(
309✔
207
            self.value_builder.finish(),
309✔
208
            self.index_builder.finish(),
309✔
209
            self.nulls.finish_with_nullability(self.nullability),
309✔
210
        )
309✔
211
        .vortex_expect("Buffer, offsets, and validity must have same length.")
309✔
212
        .into_array()
309✔
213
    }
309✔
214
}
215

216
#[cfg(test)]
217
mod tests {
218
    use std::sync::Arc;
219

220
    use Nullability::{NonNullable, Nullable};
221
    use vortex_buffer::buffer;
222
    use vortex_dtype::PType::I32;
223
    use vortex_dtype::{DType, Nullability};
224
    use vortex_scalar::Scalar;
225

226
    use crate::array::Array;
227
    use crate::arrays::{ChunkedArray, ListArray, OffsetPType};
228
    use crate::builders::ArrayBuilder;
229
    use crate::builders::list::ListBuilder;
230
    use crate::validity::Validity;
231
    use crate::vtable::ValidityHelper;
232
    use crate::{IntoArray as _, ToCanonical};
233

234
    #[test]
235
    fn test_empty() {
1✔
236
        let mut builder = ListBuilder::<u32>::with_capacity(Arc::new(I32.into()), NonNullable, 0);
1✔
237

238
        let list = builder.finish();
1✔
239
        assert_eq!(list.len(), 0);
1✔
240
    }
1✔
241

242
    #[test]
243
    fn test_values() {
1✔
244
        let dtype: Arc<DType> = Arc::new(I32.into());
1✔
245
        let mut builder = ListBuilder::<u32>::with_capacity(dtype.clone(), NonNullable, 0);
1✔
246

247
        builder
1✔
248
            .append_value(
1✔
249
                Scalar::list(
1✔
250
                    dtype.clone(),
1✔
251
                    vec![1i32.into(), 2i32.into(), 3i32.into()],
1✔
252
                    NonNullable,
1✔
253
                )
1✔
254
                .as_list(),
1✔
255
            )
256
            .unwrap();
1✔
257

258
        builder
1✔
259
            .append_value(
1✔
260
                Scalar::list(
1✔
261
                    dtype,
1✔
262
                    vec![4i32.into(), 5i32.into(), 6i32.into()],
1✔
263
                    NonNullable,
1✔
264
                )
1✔
265
                .as_list(),
1✔
266
            )
267
            .unwrap();
1✔
268

269
        let list = builder.finish();
1✔
270
        assert_eq!(list.len(), 2);
1✔
271

272
        let list_array = list.to_list().unwrap();
1✔
273

274
        assert_eq!(list_array.elements_at(0).unwrap().len(), 3);
1✔
275
        assert_eq!(list_array.elements_at(1).unwrap().len(), 3);
1✔
276
    }
1✔
277

278
    #[test]
279
    fn test_non_null_fails() {
1✔
280
        let dtype: Arc<DType> = Arc::new(I32.into());
1✔
281
        let mut builder = ListBuilder::<u32>::with_capacity(dtype.clone(), NonNullable, 0);
1✔
282

283
        assert!(
1✔
284
            builder
1✔
285
                .append_value(Scalar::list_empty(dtype, NonNullable).as_list())
1✔
286
                .is_err()
1✔
287
        )
288
    }
1✔
289

290
    #[test]
291
    fn test_nullable_values() {
1✔
292
        let dtype: Arc<DType> = Arc::new(I32.into());
1✔
293
        let mut builder = ListBuilder::<u32>::with_capacity(dtype.clone(), Nullable, 0);
1✔
294

295
        builder
1✔
296
            .append_value(
1✔
297
                Scalar::list(
1✔
298
                    dtype.clone(),
1✔
299
                    vec![1i32.into(), 2i32.into(), 3i32.into()],
1✔
300
                    NonNullable,
1✔
301
                )
1✔
302
                .as_list(),
1✔
303
            )
304
            .unwrap();
1✔
305

306
        builder
1✔
307
            .append_value(Scalar::list_empty(dtype.clone(), NonNullable).as_list())
1✔
308
            .unwrap();
1✔
309

310
        builder
1✔
311
            .append_value(
1✔
312
                Scalar::list(
1✔
313
                    dtype,
1✔
314
                    vec![4i32.into(), 5i32.into(), 6i32.into()],
1✔
315
                    NonNullable,
1✔
316
                )
1✔
317
                .as_list(),
1✔
318
            )
319
            .unwrap();
1✔
320

321
        let list = builder.finish();
1✔
322
        assert_eq!(list.len(), 3);
1✔
323

324
        let list_array = list.to_list().unwrap();
1✔
325

326
        assert_eq!(list_array.elements_at(0).unwrap().len(), 3);
1✔
327
        assert_eq!(list_array.elements_at(1).unwrap().len(), 0);
1✔
328
        assert_eq!(list_array.elements_at(2).unwrap().len(), 3);
1✔
329
    }
1✔
330

331
    fn test_extend_builder_gen<O: OffsetPType>() {
8✔
332
        let list = ListArray::from_iter_opt_slow::<O, _, _>(
8✔
333
            [Some(vec![0, 1, 2]), None, Some(vec![4, 5])],
8✔
334
            Arc::new(I32.into()),
8✔
335
        )
336
        .unwrap();
8✔
337

338
        let mut builder = ListBuilder::<O>::with_capacity(Arc::new(I32.into()), Nullable, 6);
8✔
339

340
        builder.extend_from_array(&list).unwrap();
8✔
341
        builder.extend_from_array(&list).unwrap();
8✔
342
        builder
8✔
343
            .extend_from_array(&list.slice(0, 0).unwrap())
8✔
344
            .unwrap();
8✔
345
        builder
8✔
346
            .extend_from_array(&list.slice(1, 3).unwrap())
8✔
347
            .unwrap();
8✔
348

349
        let expected = ListArray::from_iter_opt_slow::<O, _, _>(
8✔
350
            [
8✔
351
                Some(vec![0, 1, 2]),
8✔
352
                None,
8✔
353
                Some(vec![4, 5]),
8✔
354
                Some(vec![0, 1, 2]),
8✔
355
                None,
8✔
356
                Some(vec![4, 5]),
8✔
357
                None,
8✔
358
                Some(vec![4, 5]),
8✔
359
            ],
8✔
360
            Arc::new(DType::Primitive(I32, NonNullable)),
8✔
361
        )
8✔
362
        .unwrap()
8✔
363
        .to_list()
8✔
364
        .unwrap();
8✔
365

366
        let actual = builder
8✔
367
            .finish()
8✔
368
            .to_canonical()
8✔
369
            .unwrap()
8✔
370
            .into_list()
8✔
371
            .unwrap();
8✔
372

373
        assert_eq!(
8✔
374
            actual.elements().to_primitive().unwrap().as_slice::<i32>(),
8✔
375
            expected
8✔
376
                .elements()
8✔
377
                .to_primitive()
8✔
378
                .unwrap()
8✔
379
                .as_slice::<i32>()
8✔
380
        );
381

382
        assert_eq!(
8✔
383
            actual.offsets().to_primitive().unwrap().as_slice::<O>(),
8✔
384
            expected.offsets().to_primitive().unwrap().as_slice::<O>()
8✔
385
        );
386

387
        assert_eq!(actual.validity(), expected.validity())
8✔
388
    }
8✔
389

390
    #[test]
391
    fn test_extend_builder() {
1✔
392
        test_extend_builder_gen::<i8>();
1✔
393
        test_extend_builder_gen::<i16>();
1✔
394
        test_extend_builder_gen::<i32>();
1✔
395
        test_extend_builder_gen::<i64>();
1✔
396

397
        test_extend_builder_gen::<u8>();
1✔
398
        test_extend_builder_gen::<u16>();
1✔
399
        test_extend_builder_gen::<u32>();
1✔
400
        test_extend_builder_gen::<u64>();
1✔
401
    }
1✔
402

403
    #[test]
404
    pub fn test_array_with_gap() {
1✔
405
        let one_trailing_unused_element = ListArray::try_new(
1✔
406
            buffer![1, 2, 3, 4].into_array(),
1✔
407
            buffer![0, 3].into_array(),
1✔
408
            Validity::NonNullable,
1✔
409
        )
410
        .unwrap();
1✔
411

412
        let second_array = ListArray::try_new(
1✔
413
            buffer![5, 6].into_array(),
1✔
414
            buffer![0, 2].into_array(),
1✔
415
            Validity::NonNullable,
1✔
416
        )
417
        .unwrap();
1✔
418

419
        let chunked_list = ChunkedArray::try_new(
1✔
420
            vec![
1✔
421
                one_trailing_unused_element.clone().into_array(),
1✔
422
                second_array.clone().into_array(),
1✔
423
            ],
424
            DType::List(Arc::new(DType::Primitive(I32, NonNullable)), NonNullable),
1✔
425
        );
426

427
        let canon_values = chunked_list.unwrap().to_list().unwrap();
1✔
428

429
        assert_eq!(
1✔
430
            one_trailing_unused_element.scalar_at(0).unwrap(),
1✔
431
            canon_values.scalar_at(0).unwrap()
1✔
432
        );
433
        assert_eq!(
1✔
434
            second_array.scalar_at(0).unwrap(),
1✔
435
            canon_values.scalar_at(1).unwrap()
1✔
436
        );
437
    }
1✔
438
}
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