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

vortex-data / vortex / 16331938722

16 Jul 2025 10:49PM UTC coverage: 80.702% (-0.9%) from 81.557%
16331938722

push

github

web-flow
feat: build with stable rust (#3881)

120 of 173 new or added lines in 28 files covered. (69.36%)

174 existing lines in 102 files now uncovered.

41861 of 51871 relevant lines covered (80.7%)

157487.71 hits per line

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

84.94
/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, vortex_err};
10
use vortex_mask::Mask;
11
use vortex_scalar::{ListScalar, NumericOperator};
12

13
use crate::arrays::{ConstantArray, ListArray, OffsetPType};
14
use crate::builders::lazy_validity_builder::LazyNullBufferBuilder;
15
use crate::builders::{ArrayBuilder, ArrayBuilderExt, PrimitiveBuilder, builder_with_capacity};
16
use crate::compute::{cast, numeric};
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: LazyNullBufferBuilder,
23
    nullability: Nullability,
24
    dtype: DType,
25
}
26

27
impl<O: OffsetPType> ListBuilder<O> {
28
    // TODO(joe): add value + index capacity ctor.
29
    pub fn with_capacity(
215✔
30
        value_dtype: Arc<DType>,
215✔
31
        nullability: Nullability,
215✔
32
        capacity: usize,
215✔
33
    ) -> Self {
215✔
34
        // I would expect the list to have more than one value per index
35
        let value_builder = builder_with_capacity(value_dtype.as_ref(), 2 * capacity);
215✔
36
        let mut index_builder = PrimitiveBuilder::with_capacity(NonNullable, capacity);
215✔
37

38
        // The first index of the list, which is always 0 and represents an empty list.
39
        index_builder.append_zero();
215✔
40

41
        Self {
215✔
42
            value_builder,
215✔
43
            index_builder,
215✔
44
            nulls: LazyNullBufferBuilder::new(capacity),
215✔
45
            nullability,
215✔
46
            dtype: DType::List(value_dtype, nullability),
215✔
47
        }
215✔
48
    }
215✔
49

50
    pub fn append_value(&mut self, value: ListScalar) -> VortexResult<()> {
216✔
51
        match value.elements() {
216✔
52
            None => {
53
                if self.nullability == NonNullable {
2✔
54
                    vortex_bail!("Cannot append null value to non-nullable list");
1✔
55
                }
1✔
56
                self.append_null();
1✔
57
                Ok(())
1✔
58
            }
59
            Some(elements) => {
214✔
60
                for scalar in elements {
652✔
61
                    // TODO(joe): This is slow, we should be able to append multiple values at once,
62
                    // or the list scalar should hold an Array
63
                    self.value_builder.append_scalar(&scalar)?;
438✔
64
                }
65
                self.nulls.append_non_null();
214✔
66
                self.append_index(
214✔
67
                    O::from_usize(self.value_builder.len())
214✔
68
                        .vortex_expect("Failed to convert from usize to O"),
214✔
69
                )
70
            }
71
        }
72
    }
216✔
73

74
    fn append_index(&mut self, index: O) -> VortexResult<()> {
383✔
75
        self.index_builder.append_scalar(&index.into())
383✔
76
    }
383✔
77
}
78

79
impl<O: OffsetPType> ArrayBuilder for ListBuilder<O> {
80
    fn as_any(&self) -> &dyn Any {
×
81
        self
×
82
    }
×
83

84
    fn as_any_mut(&mut self) -> &mut dyn Any {
×
85
        self
×
86
    }
×
87

88
    fn dtype(&self) -> &DType {
×
89
        &self.dtype
×
90
    }
×
91

92
    fn len(&self) -> usize {
×
93
        self.nulls.len()
×
94
    }
×
95

96
    fn append_zeros(&mut self, n: usize) {
×
97
        let count = self.value_builder.len();
×
98
        self.value_builder.append_zeros(n);
×
99
        for i in 0..n {
×
100
            self.append_index(
×
101
                O::from_usize(count + i + 1).vortex_expect("Failed to convert from usize to <O>"),
×
102
            )
×
103
            .vortex_expect("Failed to append index");
×
104
        }
×
105
        self.nulls.append_n_non_nulls(n);
×
106
    }
×
107

108
    fn append_nulls(&mut self, n: usize) {
169✔
109
        let count = self.value_builder.len();
169✔
110
        for _ in 0..n {
169✔
111
            // A list with a null element is can be a list with a zero-span offset and a validity
169✔
112
            // bit set
169✔
113
            self.append_index(
169✔
114
                O::from_usize(count).vortex_expect("Failed to convert from usize to <O>"),
169✔
115
            )
169✔
116
            .vortex_expect("Failed to append index");
169✔
117
        }
169✔
118
        self.nulls.append_n_nulls(n);
169✔
119
    }
169✔
120

121
    fn extend_from_array(&mut self, array: &dyn Array) -> VortexResult<()> {
16✔
122
        self.nulls.append_validity_mask(array.validity_mask()?);
16✔
123

124
        let list = array.to_list()?;
16✔
125

126
        let cursor_usize = self.value_builder.len();
16✔
127
        let cursor = O::from_usize(cursor_usize).ok_or_else(|| {
16✔
128
            vortex_err!(
×
129
                "cannot convert length {} to type {:?}",
×
130
                cursor_usize,
131
                O::PTYPE
132
            )
UNCOV
133
        })?;
×
134

135
        let offsets = numeric(
16✔
136
            &cast(
16✔
137
                &list.offsets().slice(1, list.offsets().len())?,
16✔
138
                &DType::Primitive(O::PTYPE, NonNullable),
16✔
139
            )?,
×
140
            ConstantArray::new(cursor, list.len()).as_ref(),
16✔
141
            NumericOperator::Add,
16✔
142
        )?;
×
143
        self.index_builder.extend_from_array(&offsets)?;
16✔
144

145
        if !list.is_empty() {
16✔
146
            let last_used_index = self.index_builder.values().last().vortex_expect("there must be at least one index because we just extended a non-zero list of offsets");
16✔
147
            let sliced_values = list
16✔
148
                .elements()
16✔
149
                .slice(0, last_used_index.as_() - cursor_usize)?;
16✔
150
            self.value_builder.ensure_capacity(sliced_values.len());
16✔
151
            self.value_builder.extend_from_array(&sliced_values)?;
16✔
152
        }
×
153

154
        Ok(())
16✔
155
    }
16✔
156

157
    fn ensure_capacity(&mut self, capacity: usize) {
×
158
        self.index_builder.ensure_capacity(capacity);
×
159
        self.value_builder.ensure_capacity(capacity);
×
160
        self.nulls.ensure_capacity(capacity);
×
161
    }
×
162

163
    fn set_validity(&mut self, validity: Mask) {
×
164
        self.nulls = LazyNullBufferBuilder::new(validity.len());
×
165
        self.nulls.append_validity_mask(validity);
×
166
    }
×
167

168
    fn finish(&mut self) -> ArrayRef {
214✔
169
        assert_eq!(
214✔
170
            self.index_builder.len(),
214✔
171
            self.nulls.len() + 1,
214✔
172
            "Indices length must be one more than nulls length."
×
173
        );
174

175
        ListArray::try_new(
214✔
176
            self.value_builder.finish(),
214✔
177
            self.index_builder.finish(),
214✔
178
            self.nulls.finish_with_nullability(self.nullability),
214✔
179
        )
214✔
180
        .vortex_expect("Buffer, offsets, and validity must have same length.")
214✔
181
        .into_array()
214✔
182
    }
214✔
183
}
184

185
#[cfg(test)]
186
mod tests {
187
    use std::sync::Arc;
188

189
    use Nullability::{NonNullable, Nullable};
190
    use vortex_buffer::buffer;
191
    use vortex_dtype::PType::I32;
192
    use vortex_dtype::{DType, Nullability};
193
    use vortex_scalar::Scalar;
194

195
    use crate::array::Array;
196
    use crate::arrays::{ChunkedArray, ListArray, OffsetPType};
197
    use crate::builders::ArrayBuilder;
198
    use crate::builders::list::ListBuilder;
199
    use crate::validity::Validity;
200
    use crate::vtable::ValidityHelper;
201
    use crate::{IntoArray as _, ToCanonical};
202

203
    #[test]
204
    fn test_empty() {
1✔
205
        let mut builder = ListBuilder::<u32>::with_capacity(Arc::new(I32.into()), NonNullable, 0);
1✔
206

207
        let list = builder.finish();
1✔
208
        assert_eq!(list.len(), 0);
1✔
209
    }
1✔
210

211
    #[test]
212
    fn test_values() {
1✔
213
        let dtype: Arc<DType> = Arc::new(I32.into());
1✔
214
        let mut builder = ListBuilder::<u32>::with_capacity(dtype.clone(), NonNullable, 0);
1✔
215

216
        builder
1✔
217
            .append_value(
1✔
218
                Scalar::list(
1✔
219
                    dtype.clone(),
1✔
220
                    vec![1i32.into(), 2i32.into(), 3i32.into()],
1✔
221
                    NonNullable,
1✔
222
                )
1✔
223
                .as_list(),
1✔
224
            )
225
            .unwrap();
1✔
226

227
        builder
1✔
228
            .append_value(
1✔
229
                Scalar::list(
1✔
230
                    dtype,
1✔
231
                    vec![4i32.into(), 5i32.into(), 6i32.into()],
1✔
232
                    NonNullable,
1✔
233
                )
1✔
234
                .as_list(),
1✔
235
            )
236
            .unwrap();
1✔
237

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

241
        let list_array = list.to_list().unwrap();
1✔
242

243
        assert_eq!(list_array.elements_at(0).unwrap().len(), 3);
1✔
244
        assert_eq!(list_array.elements_at(1).unwrap().len(), 3);
1✔
245
    }
1✔
246

247
    #[test]
248
    fn test_non_null_fails() {
1✔
249
        let dtype: Arc<DType> = Arc::new(I32.into());
1✔
250
        let mut builder = ListBuilder::<u32>::with_capacity(dtype.clone(), NonNullable, 0);
1✔
251

252
        assert!(
1✔
253
            builder
1✔
254
                .append_value(Scalar::list_empty(dtype, NonNullable).as_list())
1✔
255
                .is_err()
1✔
256
        )
257
    }
1✔
258

259
    #[test]
260
    fn test_nullable_values() {
1✔
261
        let dtype: Arc<DType> = Arc::new(I32.into());
1✔
262
        let mut builder = ListBuilder::<u32>::with_capacity(dtype.clone(), Nullable, 0);
1✔
263

264
        builder
1✔
265
            .append_value(
1✔
266
                Scalar::list(
1✔
267
                    dtype.clone(),
1✔
268
                    vec![1i32.into(), 2i32.into(), 3i32.into()],
1✔
269
                    NonNullable,
1✔
270
                )
1✔
271
                .as_list(),
1✔
272
            )
273
            .unwrap();
1✔
274

275
        builder
1✔
276
            .append_value(Scalar::list_empty(dtype.clone(), NonNullable).as_list())
1✔
277
            .unwrap();
1✔
278

279
        builder
1✔
280
            .append_value(
1✔
281
                Scalar::list(
1✔
282
                    dtype,
1✔
283
                    vec![4i32.into(), 5i32.into(), 6i32.into()],
1✔
284
                    NonNullable,
1✔
285
                )
1✔
286
                .as_list(),
1✔
287
            )
288
            .unwrap();
1✔
289

290
        let list = builder.finish();
1✔
291
        assert_eq!(list.len(), 3);
1✔
292

293
        let list_array = list.to_list().unwrap();
1✔
294

295
        assert_eq!(list_array.elements_at(0).unwrap().len(), 3);
1✔
296
        assert_eq!(list_array.elements_at(1).unwrap().len(), 0);
1✔
297
        assert_eq!(list_array.elements_at(2).unwrap().len(), 3);
1✔
298
    }
1✔
299

300
    fn test_extend_builder_gen<O: OffsetPType>() {
8✔
301
        let list = ListArray::from_iter_opt_slow::<O, _, _>(
8✔
302
            [Some(vec![0, 1, 2]), None, Some(vec![4, 5])],
8✔
303
            Arc::new(I32.into()),
8✔
304
        )
305
        .unwrap();
8✔
306

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

309
        builder.extend_from_array(&list).unwrap();
8✔
310
        builder.extend_from_array(&list).unwrap();
8✔
311

312
        let expect = ListArray::from_iter_opt_slow::<O, _, _>(
8✔
313
            [
8✔
314
                Some(vec![0, 1, 2]),
8✔
315
                None,
8✔
316
                Some(vec![4, 5]),
8✔
317
                Some(vec![0, 1, 2]),
8✔
318
                None,
8✔
319
                Some(vec![4, 5]),
8✔
320
            ],
8✔
321
            Arc::new(DType::Primitive(I32, NonNullable)),
8✔
322
        )
8✔
323
        .unwrap()
8✔
324
        .to_list()
8✔
325
        .unwrap();
8✔
326

327
        let res = builder
8✔
328
            .finish()
8✔
329
            .to_canonical()
8✔
330
            .unwrap()
8✔
331
            .into_list()
8✔
332
            .unwrap();
8✔
333

334
        assert_eq!(
8✔
335
            res.elements().to_primitive().unwrap().as_slice::<i32>(),
8✔
336
            expect.elements().to_primitive().unwrap().as_slice::<i32>()
8✔
337
        );
338

339
        assert_eq!(
8✔
340
            res.offsets().to_primitive().unwrap().as_slice::<O>(),
8✔
341
            expect.offsets().to_primitive().unwrap().as_slice::<O>()
8✔
342
        );
343

344
        assert_eq!(res.validity(), expect.validity())
8✔
345
    }
8✔
346

347
    #[test]
348
    fn test_extend_builder() {
1✔
349
        test_extend_builder_gen::<i8>();
1✔
350
        test_extend_builder_gen::<i16>();
1✔
351
        test_extend_builder_gen::<i32>();
1✔
352
        test_extend_builder_gen::<i64>();
1✔
353

354
        test_extend_builder_gen::<u8>();
1✔
355
        test_extend_builder_gen::<u16>();
1✔
356
        test_extend_builder_gen::<u32>();
1✔
357
        test_extend_builder_gen::<u64>();
1✔
358
    }
1✔
359

360
    #[test]
361
    pub fn test_array_with_gap() {
1✔
362
        let one_trailing_unused_element = ListArray::try_new(
1✔
363
            buffer![1, 2, 3, 4].into_array(),
1✔
364
            buffer![0, 3].into_array(),
1✔
365
            Validity::NonNullable,
1✔
366
        )
367
        .unwrap();
1✔
368

369
        let second_array = ListArray::try_new(
1✔
370
            buffer![5, 6].into_array(),
1✔
371
            buffer![0, 2].into_array(),
1✔
372
            Validity::NonNullable,
1✔
373
        )
374
        .unwrap();
1✔
375

376
        let chunked_list = ChunkedArray::try_new(
1✔
377
            vec![
1✔
378
                one_trailing_unused_element.clone().into_array(),
1✔
379
                second_array.clone().into_array(),
1✔
380
            ],
381
            DType::List(Arc::new(DType::Primitive(I32, NonNullable)), NonNullable),
1✔
382
        );
383

384
        let canon_values = chunked_list.unwrap().to_list().unwrap();
1✔
385

386
        assert_eq!(
1✔
387
            one_trailing_unused_element.scalar_at(0).unwrap(),
1✔
388
            canon_values.scalar_at(0).unwrap()
1✔
389
        );
390
        assert_eq!(
1✔
391
            second_array.scalar_at(0).unwrap(),
1✔
392
            canon_values.scalar_at(1).unwrap()
1✔
393
        );
394
    }
1✔
395
}
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