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

vortex-data / vortex / 17075133033

19 Aug 2025 04:01PM UTC coverage: 87.949% (+0.09%) from 87.856%
17075133033

push

github

web-flow
feat: ArrayOperations infallible, eager validation + new_unchecked (#4177)

ArrayOperations currently return VortexResult<>, but they really should
just be infallible. A failed array op is generally indicative of
programmer or encoding error. There's really nothing interesting we can
do to handle an out-of-bounds slice() or scalar_at.

There's a lot that falls out of this, like fixing a bunch of tests,
tweaking our scalar value casting to return Option instead of Result,
etc.

---------

Signed-off-by: Andrew Duffy <andrew@a10y.dev>

1744 of 1985 new or added lines in 195 files covered. (87.86%)

36 existing lines in 27 files now uncovered.

56745 of 64520 relevant lines covered (87.95%)

624082.56 hits per line

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

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

4
//! Array validity and nullability behavior, used by arrays and compute functions.
5

6
use std::fmt::Debug;
7
use std::ops::{BitAnd, Not};
8

9
use arrow_buffer::{BooleanBuffer, NullBuffer};
10
use vortex_dtype::{DType, Nullability};
11
use vortex_error::{VortexExpect as _, VortexResult, vortex_bail, vortex_err, vortex_panic};
12
use vortex_mask::{AllOr, Mask, MaskValues};
13
use vortex_scalar::Scalar;
14

15
use crate::arrays::{BoolArray, ConstantArray};
16
use crate::compute::{fill_null, filter, sum, take};
17
use crate::patches::Patches;
18
use crate::{Array, ArrayRef, IntoArray, ToCanonical};
19

20
/// Validity information for an array
21
#[derive(Clone, Debug)]
22
pub enum Validity {
23
    /// Items *can't* be null
24
    NonNullable,
25
    /// All items are valid
26
    AllValid,
27
    /// All items are null
28
    AllInvalid,
29
    /// Specified items are null
30
    Array(ArrayRef),
31
}
32

33
impl Validity {
34
    /// The [`DType`] of the underlying validity array (if it exists).
35
    pub const DTYPE: DType = DType::Bool(Nullability::NonNullable);
36

37
    pub fn null_count(&self, length: usize) -> VortexResult<usize> {
×
38
        match self {
×
39
            Self::NonNullable | Self::AllValid => Ok(0),
×
40
            Self::AllInvalid => Ok(length),
×
41
            Self::Array(a) => {
×
42
                let validity_len = a.len();
×
43
                if validity_len != length {
×
44
                    vortex_bail!(
×
45
                        "Validity array length {} doesn't match array length {}",
×
46
                        validity_len,
47
                        length
48
                    )
49
                }
×
50
                let true_count = sum(a)?
×
51
                    .as_primitive()
×
NEW
52
                    .as_::<usize>()
×
53
                    .ok_or_else(|| vortex_err!("Failed to compute true count"))?;
×
54
                Ok(length - true_count)
×
55
            }
56
        }
57
    }
×
58

59
    /// If Validity is [`Validity::Array`], returns the array, otherwise returns `None`.
60
    pub fn into_array(self) -> Option<ArrayRef> {
×
61
        match self {
×
62
            Self::Array(a) => Some(a),
×
63
            _ => None,
×
64
        }
65
    }
×
66

67
    /// If Validity is [`Validity::Array`], returns a reference to the array array, otherwise returns `None`.
68
    pub fn as_array(&self) -> Option<&ArrayRef> {
×
69
        match self {
×
70
            Self::Array(a) => Some(a),
×
71
            _ => None,
×
72
        }
73
    }
×
74

75
    pub fn nullability(&self) -> Nullability {
5,042,627✔
76
        match self {
5,042,627✔
77
            Self::NonNullable => Nullability::NonNullable,
4,061,710✔
78
            _ => Nullability::Nullable,
980,917✔
79
        }
80
    }
5,042,627✔
81

82
    /// The union nullability and validity.
83
    pub fn union_nullability(self, nullability: Nullability) -> Self {
907✔
84
        match nullability {
907✔
85
            Nullability::NonNullable => self,
406✔
86
            Nullability::Nullable => self.into_nullable(),
501✔
87
        }
88
    }
907✔
89

90
    pub fn all_valid(&self) -> VortexResult<bool> {
19,425,584✔
91
        Ok(match self {
19,425,584✔
92
            Validity::NonNullable | Validity::AllValid => true,
19,413,970✔
93
            Validity::AllInvalid => false,
46✔
94
            Validity::Array(array) => sum(array)
11,568✔
95
                .map(|v| {
11,568✔
96
                    v.as_primitive()
11,568✔
97
                        .typed_value::<u64>()
11,568✔
98
                        .map(|count| count == array.len() as u64)
11,568✔
99
                })?
11,568✔
100
                .ok_or_else(|| vortex_err!("Failed to compute sum for validity array"))?,
11,568✔
101
        })
102
    }
19,425,584✔
103

104
    pub fn all_invalid(&self) -> VortexResult<bool> {
488,532✔
105
        Ok(match self {
488,532✔
106
            Validity::NonNullable | Validity::AllValid => false,
458,098✔
107
            Validity::AllInvalid => true,
764✔
108
            Validity::Array(array) => sum(array)
29,670✔
109
                .map(|v| {
29,670✔
110
                    v.as_primitive()
29,670✔
111
                        .typed_value::<u64>()
29,670✔
112
                        .map(|count| count == 0u64)
29,670✔
113
                })?
29,670✔
114
                .ok_or_else(|| vortex_err!("Failed to compute sum for validity array"))?,
29,670✔
115
        })
116
    }
488,532✔
117

118
    /// Returns whether the `index` item is valid.
119
    #[inline]
120
    pub fn is_valid(&self, index: usize) -> VortexResult<bool> {
86,274,870✔
121
        Ok(match self {
86,274,870✔
122
            Self::NonNullable | Self::AllValid => true,
79,946,783✔
123
            Self::AllInvalid => false,
173,888✔
124
            Self::Array(a) => {
6,154,199✔
125
                let scalar = a.scalar_at(index);
6,154,199✔
126
                scalar
6,154,199✔
127
                    .as_bool()
6,154,199✔
128
                    .value()
6,154,199✔
129
                    .vortex_expect("Validity must be non-nullable")
6,154,199✔
130
            }
131
        })
132
    }
86,274,870✔
133

134
    #[inline]
135
    pub fn is_null(&self, index: usize) -> VortexResult<bool> {
12,224,500✔
136
        Ok(!self.is_valid(index)?)
12,224,500✔
137
    }
12,224,500✔
138

139
    pub fn slice(&self, start: usize, stop: usize) -> Self {
3,937,998✔
140
        match self {
3,937,998✔
141
            Self::Array(a) => Self::Array(a.slice(start, stop)),
9,885✔
142
            _ => self.clone(),
3,928,113✔
143
        }
144
    }
3,937,998✔
145

146
    pub fn take(&self, indices: &dyn Array) -> VortexResult<Self> {
75,925✔
147
        match self {
75,925✔
148
            Self::NonNullable => match indices.validity_mask()?.boolean_buffer() {
59,587✔
149
                AllOr::All => {
150
                    if indices.dtype().is_nullable() {
52,645✔
151
                        Ok(Self::AllValid)
277✔
152
                    } else {
153
                        Ok(Self::NonNullable)
52,368✔
154
                    }
155
                }
156
                AllOr::None => Ok(Self::AllInvalid),
1✔
157
                AllOr::Some(buf) => Ok(Validity::from(buf.clone())),
6,941✔
158
            },
159
            Self::AllValid => match indices.validity_mask()?.boolean_buffer() {
6,698✔
160
                AllOr::All => Ok(Self::AllValid),
3,262✔
161
                AllOr::None => Ok(Self::AllInvalid),
1✔
162
                AllOr::Some(buf) => Ok(Validity::from(buf.clone())),
3,435✔
163
            },
164
            Self::AllInvalid => Ok(Self::AllInvalid),
123✔
165
            Self::Array(is_valid) => {
9,517✔
166
                let maybe_is_valid = take(is_valid, indices)?;
9,517✔
167
                // Null indices invalidate that position.
168
                let is_valid = fill_null(&maybe_is_valid, &Scalar::from(false))?;
9,517✔
169
                Ok(Self::Array(is_valid))
9,517✔
170
            }
171
        }
172
    }
75,925✔
173

174
    /// Keep only the entries for which the mask is true.
175
    ///
176
    /// The result has length equal to the number of true values in mask.
177
    pub fn filter(&self, mask: &Mask) -> VortexResult<Self> {
58,707✔
178
        // NOTE(ngates): we take the mask as a reference to avoid the caller cloning unnecessarily
179
        //  if we happen to be NonNullable, AllValid, or AllInvalid.
180
        match self {
58,707✔
181
            v @ (Validity::NonNullable | Validity::AllValid | Validity::AllInvalid) => {
46,860✔
182
                Ok(v.clone())
46,860✔
183
            }
184
            Validity::Array(arr) => Ok(Validity::Array(filter(arr, mask)?)),
11,847✔
185
        }
186
    }
58,707✔
187

188
    /// Set to false any entries for which the mask is true.
189
    ///
190
    /// The result is always nullable. The result has the same length as self.
191
    pub fn mask(&self, mask: &Mask) -> VortexResult<Self> {
11,585✔
192
        match mask.boolean_buffer() {
11,585✔
193
            AllOr::All => Ok(Validity::AllInvalid),
×
194
            AllOr::None => Ok(self.clone()),
×
195
            AllOr::Some(make_invalid) => Ok(match self {
11,585✔
196
                Validity::NonNullable | Validity::AllValid => {
197
                    Validity::Array(BoolArray::from(make_invalid.not()).into_array())
4,748✔
198
                }
199
                Validity::AllInvalid => Validity::AllInvalid,
55✔
200
                Validity::Array(is_valid) => {
6,782✔
201
                    let is_valid = is_valid.to_bool()?;
6,782✔
202
                    let keep_valid = make_invalid.not();
6,782✔
203
                    Validity::from(is_valid.boolean_buffer().bitand(&keep_valid))
6,782✔
204
                }
205
            }),
206
        }
207
    }
11,585✔
208

209
    pub fn to_mask(&self, length: usize) -> VortexResult<Mask> {
1,469,879✔
210
        Ok(match self {
1,469,879✔
211
            Self::NonNullable | Self::AllValid => Mask::AllTrue(length),
1,401,927✔
212
            Self::AllInvalid => Mask::AllFalse(length),
421✔
213
            Self::Array(is_valid) => {
67,531✔
214
                assert_eq!(
67,531✔
215
                    is_valid.len(),
67,531✔
216
                    length,
217
                    "Validity::Array length must equal to_logical's argument: {}, {}.",
×
218
                    is_valid.len(),
×
219
                    length,
220
                );
221
                Mask::try_from(&is_valid.to_bool()?)?
67,531✔
222
            }
223
        })
224
    }
1,469,879✔
225

226
    /// Logically & two Validity values of the same length
227
    pub fn and(self, rhs: Validity) -> VortexResult<Validity> {
55✔
228
        let validity = match (self, rhs) {
55✔
229
            // Should be pretty clear
230
            (Validity::NonNullable, Validity::NonNullable) => Validity::NonNullable,
55✔
231
            // Any `AllInvalid` makes the output all invalid values
232
            (Validity::AllInvalid, _) | (_, Validity::AllInvalid) => Validity::AllInvalid,
×
233
            // All truthy values on one side, which makes no effect on an `Array` variant
234
            (Validity::Array(a), Validity::AllValid)
×
235
            | (Validity::Array(a), Validity::NonNullable)
×
236
            | (Validity::NonNullable, Validity::Array(a))
×
237
            | (Validity::AllValid, Validity::Array(a)) => Validity::Array(a),
×
238
            // Both sides are all valid
239
            (Validity::NonNullable, Validity::AllValid)
240
            | (Validity::AllValid, Validity::NonNullable)
241
            | (Validity::AllValid, Validity::AllValid) => Validity::AllValid,
×
242
            // Here we actually have to do some work
243
            (Validity::Array(lhs), Validity::Array(rhs)) => {
×
244
                let lhs = lhs.to_bool()?;
×
245
                let rhs = rhs.to_bool()?;
×
246

247
                let lhs = lhs.boolean_buffer();
×
248
                let rhs = rhs.boolean_buffer();
×
249

250
                Validity::from(lhs.bitand(rhs))
×
251
            }
252
        };
253

254
        Ok(validity)
55✔
255
    }
55✔
256

257
    pub fn patch(
12,009✔
258
        self,
12,009✔
259
        len: usize,
12,009✔
260
        indices_offset: usize,
12,009✔
261
        indices: &dyn Array,
12,009✔
262
        patches: &Validity,
12,009✔
263
    ) -> VortexResult<Self> {
12,009✔
264
        match (&self, patches) {
12,009✔
265
            (Validity::NonNullable, Validity::NonNullable) => return Ok(Validity::NonNullable),
9,222✔
266
            (Validity::NonNullable, _) => {
267
                vortex_bail!("Can't patch a non-nullable validity with nullable validity")
1✔
268
            }
269
            (_, Validity::NonNullable) => {
270
                vortex_bail!("Can't patch a nullable validity with non-nullable validity")
×
271
            }
272
            (Validity::AllValid, Validity::AllValid) => return Ok(Validity::AllValid),
1✔
273
            (Validity::AllInvalid, Validity::AllInvalid) => return Ok(Validity::AllInvalid),
1✔
274
            _ => {}
2,784✔
275
        };
276

277
        let own_nullability = if self == Validity::NonNullable {
2,784✔
278
            Nullability::NonNullable
×
279
        } else {
280
            Nullability::Nullable
2,784✔
281
        };
282

283
        let source = match self {
2,784✔
284
            Validity::NonNullable => BoolArray::from(BooleanBuffer::new_set(len)),
×
285
            Validity::AllValid => BoolArray::from(BooleanBuffer::new_set(len)),
392✔
286
            Validity::AllInvalid => BoolArray::from(BooleanBuffer::new_unset(len)),
2,311✔
287
            Validity::Array(a) => a.to_bool()?,
81✔
288
        };
289

290
        let patch_values = match patches {
2,784✔
291
            Validity::NonNullable => BoolArray::from(BooleanBuffer::new_set(indices.len())),
×
292
            Validity::AllValid => BoolArray::from(BooleanBuffer::new_set(indices.len())),
1,804✔
293
            Validity::AllInvalid => BoolArray::from(BooleanBuffer::new_unset(indices.len())),
2✔
294
            Validity::Array(a) => a.to_bool()?,
978✔
295
        };
296

297
        let patches = Patches::new(
2,784✔
298
            len,
2,784✔
299
            indices_offset,
2,784✔
300
            indices.to_array(),
2,784✔
301
            patch_values.into_array(),
2,784✔
302
        );
303

304
        Ok(Self::from_array(
2,784✔
305
            source.patch(&patches)?.into_array(),
2,784✔
306
            own_nullability,
2,784✔
307
        ))
308
    }
12,009✔
309

310
    /// Convert into a nullable variant
311
    pub fn into_nullable(self) -> Validity {
19,267✔
312
        match self {
19,267✔
313
            Self::NonNullable => Self::AllValid,
19,030✔
314
            _ => self,
237✔
315
        }
316
    }
19,267✔
317

318
    /// Convert into a non-nullable variant
319
    pub fn into_non_nullable(self) -> Option<Validity> {
2,555✔
320
        match self {
2,555✔
321
            Self::NonNullable => Some(Self::NonNullable),
508✔
322
            Self::AllValid => Some(Self::NonNullable),
1,282✔
323
            Self::AllInvalid => None,
×
324
            Self::Array(is_valid) => {
765✔
325
                is_valid
765✔
326
                    .statistics()
765✔
327
                    .compute_min::<bool>()
765✔
328
                    .vortex_expect("validity array must support min")
765✔
329
                    .then(|| {
765✔
330
                        // min true => all true
331
                        Self::NonNullable
238✔
332
                    })
238✔
333
            }
334
        }
335
    }
2,555✔
336

337
    /// Convert into a variant compatible with the given nullability, if possible.
338
    pub fn cast_nullability(self, nullability: Nullability) -> VortexResult<Validity> {
9,013✔
339
        match nullability {
9,013✔
340
            Nullability::NonNullable => self.into_non_nullable().ok_or_else(|| {
2,555✔
341
                vortex_err!("Cannot cast array with invalid values to non-nullable type.")
527✔
342
            }),
527✔
343
            Nullability::Nullable => Ok(self.into_nullable()),
6,458✔
344
        }
345
    }
9,013✔
346

347
    /// Create Validity by copying the given array's validity.
348
    pub fn copy_from_array(array: &dyn Array) -> VortexResult<Self> {
19,775✔
349
        Ok(Validity::from_mask(
19,775✔
350
            array.validity_mask()?,
19,775✔
351
            array.dtype().nullability(),
19,775✔
352
        ))
353
    }
19,775✔
354

355
    /// Create Validity from boolean array with given nullability of the array.
356
    ///
357
    /// Note: You want to pass the nullability of parent array and not the nullability of the validity array itself
358
    ///     as that is always nonnullable
359
    fn from_array(value: ArrayRef, nullability: Nullability) -> Self {
2,784✔
360
        if !matches!(value.dtype(), DType::Bool(Nullability::NonNullable)) {
2,784✔
361
            vortex_panic!("Expected a non-nullable boolean array")
×
362
        }
2,784✔
363
        match nullability {
2,784✔
364
            Nullability::NonNullable => Self::NonNullable,
×
365
            Nullability::Nullable => Self::Array(value),
2,784✔
366
        }
367
    }
2,784✔
368

369
    /// Returns the length of the validity array, if it exists.
370
    pub fn maybe_len(&self) -> Option<usize> {
5,378,479✔
371
        match self {
5,378,479✔
372
            Self::NonNullable | Self::AllValid | Self::AllInvalid => None,
5,249,475✔
373
            Self::Array(a) => Some(a.len()),
129,004✔
374
        }
375
    }
5,378,479✔
376

377
    pub fn uncompressed_size(&self) -> usize {
×
378
        if let Validity::Array(a) = self {
×
379
            a.len().div_ceil(8)
×
380
        } else {
381
            0
×
382
        }
383
    }
×
384

385
    pub fn is_array(&self) -> bool {
×
386
        matches!(self, Validity::Array(_))
×
387
    }
×
388
}
389

390
impl PartialEq for Validity {
391
    fn eq(&self, other: &Self) -> bool {
30,651✔
392
        match (self, other) {
30,651✔
393
            (Self::NonNullable, Self::NonNullable) => true,
16,290✔
394
            (Self::AllValid, Self::AllValid) => true,
126✔
395
            (Self::AllInvalid, Self::AllInvalid) => true,
123✔
396
            (Self::Array(a), Self::Array(b)) => {
526✔
397
                let a = a
526✔
398
                    .to_bool()
526✔
399
                    .vortex_expect("Failed to get Validity Array as BoolArray");
526✔
400
                let b = b
526✔
401
                    .to_bool()
526✔
402
                    .vortex_expect("Failed to get Validity Array as BoolArray");
526✔
403
                a.boolean_buffer() == b.boolean_buffer()
526✔
404
            }
405
            _ => false,
13,586✔
406
        }
407
    }
30,651✔
408
}
409

410
impl From<BooleanBuffer> for Validity {
411
    fn from(value: BooleanBuffer) -> Self {
68,659✔
412
        if value.count_set_bits() == value.len() {
68,659✔
413
            Self::AllValid
374✔
414
        } else if value.count_set_bits() == 0 {
68,285✔
415
            Self::AllInvalid
2,235✔
416
        } else {
417
            Self::Array(BoolArray::from(value).into_array())
66,050✔
418
        }
419
    }
68,659✔
420
}
421

422
impl From<NullBuffer> for Validity {
423
    fn from(value: NullBuffer) -> Self {
14,942✔
424
        value.into_inner().into()
14,942✔
425
    }
14,942✔
426
}
427

428
impl FromIterator<Mask> for Validity {
429
    fn from_iter<T: IntoIterator<Item = Mask>>(iter: T) -> Self {
×
430
        Validity::from_mask(iter.into_iter().collect(), Nullability::Nullable)
×
431
    }
×
432
}
433

434
impl FromIterator<bool> for Validity {
435
    fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
3,391✔
436
        Validity::from(BooleanBuffer::from_iter(iter))
3,391✔
437
    }
3,391✔
438
}
439

440
impl From<Nullability> for Validity {
441
    fn from(value: Nullability) -> Self {
49,767✔
442
        match value {
49,767✔
443
            Nullability::NonNullable => Validity::NonNullable,
43,781✔
444
            Nullability::Nullable => Validity::AllValid,
5,986✔
445
        }
446
    }
49,767✔
447
}
448

449
impl Validity {
450
    pub fn from_mask(mask: Mask, nullability: Nullability) -> Self {
20,541✔
451
        assert!(
20,539✔
452
            nullability == Nullability::Nullable || matches!(mask, Mask::AllTrue(_)),
20,541✔
453
            "NonNullable validity must be AllValid",
2✔
454
        );
455
        match mask {
20,539✔
456
            Mask::AllTrue(_) => match nullability {
17,720✔
457
                Nullability::NonNullable => Validity::NonNullable,
17,442✔
458
                Nullability::Nullable => Validity::AllValid,
278✔
459
            },
460
            Mask::AllFalse(_) => Validity::AllInvalid,
78✔
461
            Mask::Values(values) => Validity::Array(values.into_array()),
2,741✔
462
        }
463
    }
20,539✔
464
}
465

466
impl IntoArray for Mask {
467
    fn into_array(self) -> ArrayRef {
40✔
468
        match self {
40✔
469
            Self::AllTrue(len) => ConstantArray::new(true, len).into_array(),
×
470
            Self::AllFalse(len) => ConstantArray::new(false, len).into_array(),
×
471
            Self::Values(a) => a.into_array(),
40✔
472
        }
473
    }
40✔
474
}
475

476
impl IntoArray for &MaskValues {
477
    fn into_array(self) -> ArrayRef {
2,820✔
478
        BoolArray::new(self.boolean_buffer().clone(), Validity::NonNullable).into_array()
2,820✔
479
    }
2,820✔
480
}
481

482
#[cfg(test)]
483
mod tests {
484
    use rstest::rstest;
485
    use vortex_buffer::{Buffer, buffer};
486
    use vortex_dtype::Nullability;
487
    use vortex_mask::Mask;
488

489
    use crate::arrays::{BoolArray, PrimitiveArray};
490
    use crate::validity::Validity;
491
    use crate::{ArrayRef, IntoArray};
492

493
    #[rstest]
494
    #[case(Validity::AllValid, 5, &[2, 4], Validity::AllValid, Validity::AllValid)]
495
    #[case(Validity::AllValid, 5, &[2, 4], Validity::AllInvalid, Validity::Array(BoolArray::from_iter([true, true, false, true, false]).into_array())
496
    )]
497
    #[case(Validity::AllValid, 5, &[2, 4], Validity::Array(BoolArray::from_iter([true, false]).into_array()), Validity::Array(BoolArray::from_iter([true, true, true, true, false]).into_array())
498
    )]
499
    #[case(Validity::AllInvalid, 5, &[2, 4], Validity::AllValid, Validity::Array(BoolArray::from_iter([false, false, true, false, true]).into_array())
500
    )]
501
    #[case(Validity::AllInvalid, 5, &[2, 4], Validity::AllInvalid, Validity::AllInvalid)]
502
    #[case(Validity::AllInvalid, 5, &[2, 4], Validity::Array(BoolArray::from_iter([true, false]).into_array()), Validity::Array(BoolArray::from_iter([false, false, true, false, false]).into_array())
503
    )]
504
    #[case(Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()), 5, &[2, 4], Validity::AllValid, Validity::Array(BoolArray::from_iter([false, true, true, true, true]).into_array())
505
    )]
506
    #[case(Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()), 5, &[2, 4], Validity::AllInvalid, Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array())
507
    )]
508
    #[case(Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()), 5, &[2, 4], Validity::Array(BoolArray::from_iter([true, false]).into_array()), Validity::Array(BoolArray::from_iter([false, true, true, true, false]).into_array())
509
    )]
510
    fn patch_validity(
511
        #[case] validity: Validity,
512
        #[case] len: usize,
513
        #[case] positions: &[u64],
514
        #[case] patches: Validity,
515
        #[case] expected: Validity,
516
    ) {
517
        let indices =
518
            PrimitiveArray::new(Buffer::copy_from(positions), Validity::NonNullable).into_array();
519
        assert_eq!(
520
            validity.patch(len, 0, &indices, &patches).unwrap(),
521
            expected
522
        );
523
    }
524

525
    #[test]
526
    #[should_panic]
527
    fn out_of_bounds_patch() {
1✔
528
        Validity::NonNullable
1✔
529
            .patch(2, 0, &buffer![4].into_array(), &Validity::AllInvalid)
1✔
530
            .unwrap();
1✔
531
    }
1✔
532

533
    #[test]
534
    #[should_panic]
535
    fn into_validity_nullable() {
1✔
536
        Validity::from_mask(Mask::AllFalse(10), Nullability::NonNullable);
1✔
537
    }
1✔
538

539
    #[test]
540
    #[should_panic]
541
    fn into_validity_nullable_array() {
1✔
542
        Validity::from_mask(Mask::from_iter(vec![true, false]), Nullability::NonNullable);
1✔
543
    }
1✔
544

545
    #[rstest]
546
    #[case(Validity::AllValid, PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(), Validity::from_iter(vec![true, false]))]
547
    #[case(Validity::AllValid, buffer![0, 1].into_array(), Validity::AllValid)]
548
    #[case(Validity::AllValid, PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(), Validity::AllInvalid)]
549
    #[case(Validity::NonNullable, PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(), Validity::from_iter(vec![true, false]))]
550
    #[case(Validity::NonNullable, buffer![0, 1].into_array(), Validity::NonNullable)]
551
    #[case(Validity::NonNullable, PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(), Validity::AllInvalid)]
552
    fn validity_take(
553
        #[case] validity: Validity,
554
        #[case] indices: ArrayRef,
555
        #[case] expected: Validity,
556
    ) {
557
        assert_eq!(validity.take(&indices).unwrap(), expected);
558
    }
559
}
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