1use std::fmt::Debug;
12use std::ops::Range;
13
14use itertools::Itertools as _;
15use vortex_buffer::BitBuffer;
16use vortex_error::VortexExpect as _;
17use vortex_error::VortexResult;
18use vortex_error::vortex_bail;
19use vortex_error::vortex_err;
20use vortex_mask::Mask;
21use vortex_mask::MaskValues;
22
23use crate::ArrayRef;
24use crate::Canonical;
25use crate::ExecutionCtx;
26use crate::IntoArray;
27use crate::VortexSessionExecute;
28use crate::arrays::BoolArray;
29use crate::arrays::ChunkedArray;
30use crate::arrays::ConstantArray;
31use crate::builtins::ArrayBuiltins;
32use crate::dtype::DType;
33use crate::dtype::Nullability;
34use crate::legacy_session;
35use crate::optimizer::ArrayOptimizer;
36use crate::patches::Patches;
37use crate::scalar::Scalar;
38use crate::scalar_fn::fns::binary::Binary;
39use crate::scalar_fn::fns::operators::Operator;
40
41#[derive(Clone)]
43pub enum Validity {
44 NonNullable,
46 AllValid,
48 AllInvalid,
50 Array(ArrayRef),
54}
55
56impl Debug for Validity {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 Self::NonNullable => write!(f, "NonNullable"),
60 Self::AllValid => write!(f, "AllValid"),
61 Self::AllInvalid => write!(f, "AllInvalid"),
62 Self::Array(arr) => write!(f, "SomeValid({})", arr.display_values()),
63 }
64 }
65}
66
67impl Validity {
68 pub fn execute(self, ctx: &mut ExecutionCtx) -> VortexResult<Validity> {
70 match self {
71 v @ Validity::NonNullable | v @ Validity::AllValid | v @ Validity::AllInvalid => Ok(v),
72 Validity::Array(a) => Ok(Validity::Array(a.execute::<Canonical>(ctx)?.into_array())),
73 }
74 }
75}
76
77impl Validity {
78 pub const DTYPE: DType = DType::Bool(Nullability::NonNullable);
80
81 pub fn to_array(&self, len: usize) -> ArrayRef {
83 match self {
84 Self::NonNullable | Self::AllValid => ConstantArray::new(true, len).into_array(),
85 Self::AllInvalid => ConstantArray::new(false, len).into_array(),
86 Self::Array(a) => a.clone(),
87 }
88 }
89
90 #[inline]
92 pub fn into_array(self) -> Option<ArrayRef> {
93 if let Self::Array(a) = self {
94 Some(a)
95 } else {
96 None
97 }
98 }
99
100 #[inline]
102 pub fn as_array(&self) -> Option<&ArrayRef> {
103 if let Self::Array(a) = self {
104 Some(a)
105 } else {
106 None
107 }
108 }
109
110 #[inline]
111 pub fn nullability(&self) -> Nullability {
112 if matches!(self, Self::NonNullable) {
113 Nullability::NonNullable
114 } else {
115 Nullability::Nullable
116 }
117 }
118
119 #[inline]
126 pub fn definitely_no_nulls(&self) -> bool {
127 matches!(self, Self::NonNullable | Self::AllValid)
128 }
129
130 #[inline]
139 pub fn definitely_all_null(&self) -> bool {
140 matches!(self, Self::AllInvalid)
141 }
142
143 pub fn execute_no_nulls(&self, length: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
149 match self {
150 Self::NonNullable | Self::AllValid => Ok(true),
151 Self::AllInvalid => Ok(length == 0),
152 Self::Array(_) => Ok(self.execute_mask(length, ctx)?.all_true()),
153 }
154 }
155
156 #[inline]
158 pub fn union_nullability(self, nullability: Nullability) -> Self {
159 match nullability {
160 Nullability::NonNullable => self,
161 Nullability::Nullable => self.into_nullable(),
162 }
163 }
164
165 #[inline]
167 pub fn execute_is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
168 Ok(match self {
169 Self::NonNullable | Self::AllValid => true,
170 Self::AllInvalid => false,
171 Self::Array(a) => a
172 .execute_scalar(index, ctx)?
173 .as_bool()
174 .value()
175 .ok_or_else(|| vortex_err!("validity value at index {index} is null"))?,
176 })
177 }
178
179 #[inline]
181 pub fn execute_is_null(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
182 Ok(!self.execute_is_valid(index, ctx)?)
183 }
184
185 #[deprecated(note = "use `execute_is_valid` with an explicit `ExecutionCtx`")]
187 #[inline]
188 #[allow(clippy::disallowed_methods)]
189 pub fn is_valid(&self, index: usize) -> VortexResult<bool> {
190 self.execute_is_valid(index, &mut legacy_session().create_execution_ctx())
191 }
192
193 #[deprecated(note = "use `execute_is_null` with an explicit `ExecutionCtx`")]
195 #[inline]
196 #[allow(clippy::disallowed_methods)]
197 pub fn is_null(&self, index: usize) -> VortexResult<bool> {
198 self.execute_is_null(index, &mut legacy_session().create_execution_ctx())
199 }
200
201 #[inline]
202 pub fn slice(&self, range: Range<usize>) -> VortexResult<Self> {
203 match self {
204 Self::Array(a) => Ok(Self::Array(a.slice(range)?)),
205 Self::NonNullable | Self::AllValid | Self::AllInvalid => Ok(self.clone()),
206 }
207 }
208
209 pub fn take(&self, indices: &ArrayRef) -> VortexResult<Self> {
210 match self {
211 Self::NonNullable => indices.validity(),
212 Self::AllValid => Ok(match indices.validity()? {
213 Self::NonNullable => Self::AllValid,
214 v => v,
215 }),
216 Self::AllInvalid => Ok(Self::AllInvalid),
217 Self::Array(is_valid) => {
218 let maybe_is_valid = is_valid.take(indices.clone())?;
219 let is_valid = maybe_is_valid.fill_null(Scalar::from(false))?;
221 Ok(Self::Array(is_valid))
222 }
223 }
224 }
225
226 pub fn not(&self) -> VortexResult<Self> {
228 match self {
229 Validity::NonNullable => Ok(Validity::NonNullable),
230 Validity::AllValid => Ok(Validity::AllInvalid),
231 Validity::AllInvalid => Ok(Validity::AllValid),
232 Validity::Array(arr) => Ok(Validity::Array(arr.not()?)),
233 }
234 }
235
236 pub fn filter(&self, mask: &Mask) -> VortexResult<Self> {
244 match self {
247 v @ (Validity::NonNullable | Validity::AllValid | Validity::AllInvalid) => {
248 Ok(v.clone())
249 }
250 Validity::Array(arr) => Ok(Validity::Array(arr.filter(mask.clone())?)),
251 }
252 }
253
254 #[deprecated(note = "Use execute_mask")]
258 pub fn to_mask(&self, length: usize, ctx: &mut ExecutionCtx) -> VortexResult<Mask> {
259 match self {
260 Self::NonNullable | Self::AllValid => Ok(Mask::new_true(length)),
261 Self::AllInvalid => Ok(Mask::new_false(length)),
262 Self::Array(arr) => arr.clone().execute::<Mask>(ctx),
263 }
264 }
265
266 #[inline]
267 pub fn execute_mask(&self, length: usize, ctx: &mut ExecutionCtx) -> VortexResult<Mask> {
268 match self {
269 Self::NonNullable | Self::AllValid => Ok(Mask::AllTrue(length)),
270 Self::AllInvalid => Ok(Mask::AllFalse(length)),
271 Self::Array(arr) => {
272 assert_eq!(
273 arr.len(),
274 length,
275 "Validity::Array length must equal to_logical's argument: {}, {}.",
276 arr.len(),
277 length,
278 );
279 arr.clone().execute::<Mask>(ctx)
282 }
283 }
284 }
285
286 pub fn mask_eq(
289 &self,
290 other: &Validity,
291 length: usize,
292 ctx: &mut ExecutionCtx,
293 ) -> VortexResult<bool> {
294 match (self, other) {
295 (
297 Validity::NonNullable | Validity::AllValid,
298 Validity::NonNullable | Validity::AllValid,
299 )
300 | (Validity::AllInvalid, Validity::AllInvalid) => Ok(true),
301 _ => Ok(self.execute_mask(length, ctx)? == other.execute_mask(length, ctx)?),
302 }
303 }
304
305 #[inline]
307 pub fn and(self, rhs: Validity) -> VortexResult<Validity> {
308 Ok(match (self, rhs) {
309 (Validity::NonNullable, Validity::NonNullable) => Validity::NonNullable,
311 (Validity::AllInvalid, _) | (_, Validity::AllInvalid) => Validity::AllInvalid,
313 (Validity::Array(a), Validity::AllValid)
315 | (Validity::Array(a), Validity::NonNullable)
316 | (Validity::NonNullable, Validity::Array(a))
317 | (Validity::AllValid, Validity::Array(a)) => Validity::Array(a),
318 (Validity::NonNullable, Validity::AllValid)
320 | (Validity::AllValid, Validity::NonNullable)
321 | (Validity::AllValid, Validity::AllValid) => Validity::AllValid,
322 (Validity::Array(lhs), Validity::Array(rhs)) => Validity::Array(
324 Binary::try_new(lhs, rhs, Operator::And)?
325 .into_array()
326 .optimize()?,
327 ),
328 })
329 }
330
331 pub fn patch(
332 self,
333 len: usize,
334 indices_offset: usize,
335 indices: &ArrayRef,
336 patches: &Validity,
337 ctx: &mut ExecutionCtx,
338 ) -> VortexResult<Self> {
339 match (&self, patches) {
340 (Validity::NonNullable, Validity::NonNullable) => return Ok(Validity::NonNullable),
341 (Validity::NonNullable, _) => {
342 vortex_bail!("Can't patch a non-nullable validity with nullable validity")
343 }
344 (_, Validity::NonNullable) => {
345 vortex_bail!("Can't patch a nullable validity with non-nullable validity")
346 }
347 (Validity::AllValid, Validity::AllValid) => return Ok(Validity::AllValid),
348 (Validity::AllInvalid, Validity::AllInvalid) => return Ok(Validity::AllInvalid),
349 _ => {}
350 };
351
352 if matches!(self, Validity::NonNullable) {
353 return Ok(Self::NonNullable);
354 }
355
356 let source = match self {
358 Validity::NonNullable => BoolArray::from(BitBuffer::new_set(len)),
359 Validity::AllValid => BoolArray::from(BitBuffer::new_set(len)),
360 Validity::AllInvalid => BoolArray::from(BitBuffer::new_unset(len)),
361 Validity::Array(a) => a.execute::<BoolArray>(ctx)?,
362 };
363
364 let patch_values = match patches {
365 Validity::NonNullable => BoolArray::from(BitBuffer::new_set(indices.len())),
366 Validity::AllValid => BoolArray::from(BitBuffer::new_set(indices.len())),
367 Validity::AllInvalid => BoolArray::from(BitBuffer::new_unset(indices.len())),
368 Validity::Array(a) => a.clone().execute::<BoolArray>(ctx)?,
369 };
370
371 let patches = Patches::new(
372 len,
373 indices_offset,
374 indices.clone(),
375 patch_values.into_array(),
376 None,
378 )?;
379
380 Ok(Self::Array(source.patch(&patches, ctx)?.into_array()))
381 }
382
383 #[inline]
385 pub fn into_nullable(self) -> Validity {
386 match self {
387 Self::NonNullable => Self::AllValid,
388 Self::AllValid | Self::AllInvalid | Self::Array(_) => self,
389 }
390 }
391
392 #[inline]
398 pub fn into_non_nullable(self, len: usize, ctx: &mut ExecutionCtx) -> Option<Validity> {
399 match self {
400 _ if len == 0 => Some(Validity::NonNullable),
401 Self::NonNullable => Some(Self::NonNullable),
402 Self::AllValid => Some(Self::NonNullable),
403 Self::AllInvalid => None,
404 Self::Array(is_valid) => {
405 is_valid
406 .statistics()
407 .compute_min::<bool>(ctx)
408 .vortex_expect("validity array must support min")
409 .then(|| {
410 Self::NonNullable
412 })
413 }
414 }
415 }
416
417 #[inline]
428 pub fn trivial_into_non_nullable(self, len: usize) -> VortexResult<Option<Validity>> {
429 match self {
430 _ if len == 0 => Ok(Some(Validity::NonNullable)),
431 Self::NonNullable => Ok(Some(Self::NonNullable)),
432 Self::AllValid => Ok(Some(Self::NonNullable)),
433 Self::AllInvalid => {
434 Err(vortex_err!(InvalidArgument: "Cannot cast AllInvalid to NonNullable"))
435 }
436 Self::Array(_) => Ok(None),
437 }
438 }
439
440 #[inline]
456 pub fn cast_nullability(
457 self,
458 nullability: Nullability,
459 len: usize,
460 ctx: &mut ExecutionCtx,
461 ) -> VortexResult<Validity> {
462 match nullability {
463 Nullability::NonNullable => self.into_non_nullable(len, ctx).ok_or_else(|| {
464 vortex_err!(InvalidArgument: "Cannot cast array with invalid values to non-nullable type.")
465 }),
466 Nullability::Nullable => Ok(self.into_nullable()),
467 }
468 }
469
470 #[inline]
495 pub fn trivially_cast_nullability(
496 self,
497 nullability: Nullability,
498 len: usize,
499 ) -> VortexResult<Option<Validity>> {
500 match nullability {
501 Nullability::NonNullable => self.trivial_into_non_nullable(len),
502 Nullability::Nullable => Ok(Some(self.into_nullable())),
503 }
504 }
505
506 #[inline]
508 pub fn maybe_len(&self) -> Option<usize> {
509 match self {
510 Self::NonNullable | Self::AllValid | Self::AllInvalid => None,
511 Self::Array(a) => Some(a.len()),
512 }
513 }
514}
515
516impl From<BitBuffer> for Validity {
517 #[inline]
518 fn from(value: BitBuffer) -> Self {
519 let true_count = value.true_count();
520 if true_count == value.len() {
521 Self::AllValid
522 } else if true_count == 0 {
523 Self::AllInvalid
524 } else {
525 Self::Array(BoolArray::from(value).into_array())
526 }
527 }
528}
529
530impl FromIterator<Mask> for Validity {
531 #[inline]
532 fn from_iter<T: IntoIterator<Item = Mask>>(iter: T) -> Self {
533 Validity::from_mask(iter.into_iter().collect(), Nullability::Nullable)
534 }
535}
536
537impl FromIterator<bool> for Validity {
538 #[inline]
539 fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
540 Validity::from(BitBuffer::from_iter(iter))
541 }
542}
543
544impl From<Nullability> for Validity {
545 #[inline]
546 fn from(value: Nullability) -> Self {
547 Validity::from(&value)
548 }
549}
550
551impl From<&Nullability> for Validity {
552 #[inline]
553 fn from(value: &Nullability) -> Self {
554 match *value {
555 Nullability::NonNullable => Validity::NonNullable,
556 Nullability::Nullable => Validity::AllValid,
557 }
558 }
559}
560
561impl Validity {
562 pub fn concat(validities: Vec<(Validity, usize)>) -> Option<Self> {
566 let mut validity_kinds = validities
567 .iter()
568 .map(|(v, _)| std::mem::discriminant(v))
569 .unique();
570 let validity_kind = validity_kinds.next()?;
571 if validity_kinds.next().is_none() {
572 if validity_kind == std::mem::discriminant(&Validity::AllValid) {
575 return Some(Validity::AllValid);
576 }
577 if validity_kind == std::mem::discriminant(&Validity::AllInvalid) {
578 return Some(Validity::AllInvalid);
579 }
580 if validity_kind == std::mem::discriminant(&Validity::NonNullable) {
581 return Some(Validity::NonNullable);
582 }
583 }
584
585 Some(Validity::Array(
586 unsafe {
587 ChunkedArray::new_unchecked(
588 validities.into_iter().map(|(v, len)| v.to_array(len)),
589 DType::Bool(Nullability::NonNullable),
590 )
591 }
592 .into_array(),
593 ))
594 }
595}
596
597impl Validity {
598 pub fn from_bit_buffer(buffer: BitBuffer, nullability: Nullability) -> Self {
599 if buffer.true_count() == buffer.len() {
600 nullability.into()
601 } else if buffer.true_count() == 0 {
602 Validity::AllInvalid
603 } else {
604 Validity::Array(BoolArray::new(buffer, Validity::NonNullable).into_array())
605 }
606 }
607
608 pub fn from_mask(mask: Mask, nullability: Nullability) -> Self {
609 assert!(
610 nullability == Nullability::Nullable || matches!(mask, Mask::AllTrue(_)),
611 "NonNullable validity must be AllValid",
612 );
613 match mask {
614 Mask::AllTrue(_) => match nullability {
615 Nullability::NonNullable => Validity::NonNullable,
616 Nullability::Nullable => Validity::AllValid,
617 },
618 Mask::AllFalse(_) => Validity::AllInvalid,
619 Mask::Values(values) => Validity::Array(values.into_array()),
620 }
621 }
622}
623
624impl IntoArray for Mask {
625 #[inline]
626 fn into_array(self) -> ArrayRef {
627 match self {
628 Self::AllTrue(len) => ConstantArray::new(true, len).into_array(),
629 Self::AllFalse(len) => ConstantArray::new(false, len).into_array(),
630 Self::Values(a) => a.into_array(),
631 }
632 }
633}
634
635impl IntoArray for &MaskValues {
636 #[inline]
637 fn into_array(self) -> ArrayRef {
638 BoolArray::new(self.bit_buffer().clone(), Validity::NonNullable).into_array()
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 use rstest::rstest;
645 use vortex_buffer::Buffer;
646 use vortex_buffer::buffer;
647 use vortex_mask::Mask;
648
649 use crate::ArrayRef;
650 use crate::IntoArray;
651 use crate::VortexSessionExecute;
652 use crate::array_session;
653 use crate::arrays::PrimitiveArray;
654 use crate::dtype::Nullability;
655 use crate::validity::BoolArray;
656 use crate::validity::Validity;
657
658 #[rstest]
659 #[case(Validity::AllValid, 5, &[2, 4], Validity::AllValid, Validity::AllValid)]
660 #[case(
661 Validity::AllValid,
662 5,
663 &[2, 4],
664 Validity::AllInvalid,
665 Validity::Array(BoolArray::from_iter([true, true, false, true, false]).into_array())
666 )]
667 #[case(
668 Validity::AllValid,
669 5,
670 &[2, 4],
671 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
672 Validity::Array(BoolArray::from_iter([true, true, true, true, false]).into_array())
673 )]
674 #[case(
675 Validity::AllInvalid,
676 5,
677 &[2, 4],
678 Validity::AllValid,
679 Validity::Array(BoolArray::from_iter([false, false, true, false, true]).into_array())
680 )]
681 #[case(Validity::AllInvalid, 5, &[2, 4], Validity::AllInvalid, Validity::AllInvalid)]
682 #[case(
683 Validity::AllInvalid,
684 5,
685 &[2, 4],
686 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
687 Validity::Array(BoolArray::from_iter([false, false, true, false, false]).into_array())
688 )]
689 #[case(
690 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
691 5,
692 &[2, 4],
693 Validity::AllValid,
694 Validity::Array(BoolArray::from_iter([false, true, true, true, true]).into_array())
695 )]
696 #[case(
697 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
698 5,
699 &[2, 4],
700 Validity::AllInvalid,
701 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array())
702 )]
703 #[case(
704 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
705 5,
706 &[2, 4],
707 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
708 Validity::Array(BoolArray::from_iter([false, true, true, true, false]).into_array())
709 )]
710
711 fn patch_validity(
712 #[case] validity: Validity,
713 #[case] len: usize,
714 #[case] positions: &[u64],
715 #[case] patches: Validity,
716 #[case] expected: Validity,
717 ) {
718 let indices =
719 PrimitiveArray::new(Buffer::copy_from(positions), Validity::NonNullable).into_array();
720
721 let mut ctx = array_session().create_execution_ctx();
722
723 assert!(
724 validity
725 .patch(len, 0, &indices, &patches, &mut ctx,)
726 .unwrap()
727 .mask_eq(&expected, len, &mut ctx)
728 .unwrap()
729 );
730 }
731
732 #[test]
733 #[should_panic]
734 fn out_of_bounds_patch() {
735 let mut ctx = array_session().create_execution_ctx();
736 Validity::NonNullable
737 .patch(
738 2,
739 0,
740 &buffer![4].into_array(),
741 &Validity::AllInvalid,
742 &mut ctx,
743 )
744 .unwrap();
745 }
746
747 #[test]
748 #[should_panic]
749 fn into_validity_nullable() {
750 Validity::from_mask(Mask::AllFalse(10), Nullability::NonNullable);
751 }
752
753 #[test]
754 #[should_panic]
755 fn into_validity_nullable_array() {
756 Validity::from_mask(Mask::from_iter(vec![true, false]), Nullability::NonNullable);
757 }
758
759 #[rstest]
760 #[case(
761 Validity::AllValid,
762 PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(),
763 Validity::from_iter(vec![true, false])
764 )]
765 #[case(Validity::AllValid, buffer![0, 1].into_array(), Validity::AllValid)]
766 #[case(
767 Validity::AllValid,
768 PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(),
769 Validity::AllInvalid
770 )]
771 #[case(
772 Validity::NonNullable,
773 PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(),
774 Validity::from_iter(vec![true, false])
775 )]
776 #[case(Validity::NonNullable, buffer![0, 1].into_array(), Validity::NonNullable)]
777 #[case(
778 Validity::NonNullable,
779 PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(),
780 Validity::AllInvalid
781 )]
782 fn validity_take(
783 #[case] validity: Validity,
784 #[case] indices: ArrayRef,
785 #[case] expected: Validity,
786 ) {
787 let mut ctx = array_session().create_execution_ctx();
788 assert!(
789 validity
790 .take(&indices)
791 .unwrap()
792 .mask_eq(&expected, indices.len(), &mut ctx)
793 .unwrap()
794 );
795 }
796
797 #[rstest]
798 #[case(Validity::NonNullable, Validity::AllValid, true)]
800 #[case(Validity::AllValid, Validity::NonNullable, true)]
801 #[case(Validity::AllValid, Validity::AllInvalid, false)]
802 #[case(Validity::NonNullable, Validity::AllInvalid, false)]
803 #[case(
805 Validity::Array(BoolArray::from_iter([true, true, true]).into_array()),
806 Validity::AllValid,
807 true
808 )]
809 #[case(
810 Validity::NonNullable,
811 Validity::Array(BoolArray::from_iter([true, true, true]).into_array()),
812 true
813 )]
814 #[case(
815 Validity::Array(BoolArray::from_iter([false, false, false]).into_array()),
816 Validity::AllInvalid,
817 true
818 )]
819 #[case(
820 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
821 Validity::AllValid,
822 false
823 )]
824 #[case(
825 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
826 Validity::AllInvalid,
827 false
828 )]
829 fn mask_eq_mixed_variants(
830 #[case] lhs: Validity,
831 #[case] rhs: Validity,
832 #[case] expected: bool,
833 ) -> vortex_error::VortexResult<()> {
834 let mut ctx = array_session().create_execution_ctx();
835 assert_eq!(lhs.mask_eq(&rhs, 3, &mut ctx)?, expected);
836 Ok(())
837 }
838}