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]
272 pub fn execute_mask(&self, length: usize, ctx: &mut ExecutionCtx) -> VortexResult<Mask> {
273 match self {
274 Self::NonNullable | Self::AllValid => Ok(Mask::AllTrue(length)),
275 Self::AllInvalid => Ok(Mask::AllFalse(length)),
276 Self::Array(arr) => {
277 assert_eq!(
278 arr.len(),
279 length,
280 "Validity::Array length must equal to_logical's argument: {}, {}.",
281 arr.len(),
282 length,
283 );
284 arr.clone().execute::<Mask>(ctx)
287 }
288 }
289 }
290
291 pub fn mask_eq(
294 &self,
295 other: &Validity,
296 length: usize,
297 ctx: &mut ExecutionCtx,
298 ) -> VortexResult<bool> {
299 match (self, other) {
300 (
302 Validity::NonNullable | Validity::AllValid,
303 Validity::NonNullable | Validity::AllValid,
304 )
305 | (Validity::AllInvalid, Validity::AllInvalid) => Ok(true),
306 _ => Ok(self.execute_mask(length, ctx)? == other.execute_mask(length, ctx)?),
307 }
308 }
309
310 #[inline]
312 pub fn and(self, rhs: Validity) -> VortexResult<Validity> {
313 Ok(match (self, rhs) {
314 (Validity::NonNullable, Validity::NonNullable) => Validity::NonNullable,
316 (Validity::AllInvalid, _) | (_, Validity::AllInvalid) => Validity::AllInvalid,
318 (Validity::Array(a), Validity::AllValid)
320 | (Validity::Array(a), Validity::NonNullable)
321 | (Validity::NonNullable, Validity::Array(a))
322 | (Validity::AllValid, Validity::Array(a)) => Validity::Array(a),
323 (Validity::NonNullable, Validity::AllValid)
325 | (Validity::AllValid, Validity::NonNullable)
326 | (Validity::AllValid, Validity::AllValid) => Validity::AllValid,
327 (Validity::Array(lhs), Validity::Array(rhs)) => Validity::Array(
329 Binary::try_new(lhs, rhs, Operator::And)?
330 .into_array()
331 .optimize()?,
332 ),
333 })
334 }
335
336 pub fn patch(
337 self,
338 len: usize,
339 indices_offset: usize,
340 indices: &ArrayRef,
341 patches: &Validity,
342 ctx: &mut ExecutionCtx,
343 ) -> VortexResult<Self> {
344 match (&self, patches) {
345 (Validity::NonNullable, Validity::NonNullable) => return Ok(Validity::NonNullable),
346 (Validity::NonNullable, _) => {
347 vortex_bail!("Can't patch a non-nullable validity with nullable validity")
348 }
349 (_, Validity::NonNullable) => {
350 vortex_bail!("Can't patch a nullable validity with non-nullable validity")
351 }
352 (Validity::AllValid, Validity::AllValid) => return Ok(Validity::AllValid),
353 (Validity::AllInvalid, Validity::AllInvalid) => return Ok(Validity::AllInvalid),
354 _ => {}
355 };
356
357 if matches!(self, Validity::NonNullable) {
358 return Ok(Self::NonNullable);
359 }
360
361 let source = match self {
363 Validity::NonNullable => BoolArray::from(BitBuffer::new_set(len)),
364 Validity::AllValid => BoolArray::from(BitBuffer::new_set(len)),
365 Validity::AllInvalid => BoolArray::from(BitBuffer::new_unset(len)),
366 Validity::Array(a) => a.execute::<BoolArray>(ctx)?,
367 };
368
369 let patch_values = match patches {
370 Validity::NonNullable => BoolArray::from(BitBuffer::new_set(indices.len())),
371 Validity::AllValid => BoolArray::from(BitBuffer::new_set(indices.len())),
372 Validity::AllInvalid => BoolArray::from(BitBuffer::new_unset(indices.len())),
373 Validity::Array(a) => a.clone().execute::<BoolArray>(ctx)?,
374 };
375
376 let patches = Patches::new(
377 len,
378 indices_offset,
379 indices.clone(),
380 patch_values.into_array(),
381 None,
383 )?;
384
385 Ok(Self::Array(source.patch(&patches, ctx)?.into_array()))
386 }
387
388 #[inline]
390 pub fn into_nullable(self) -> Validity {
391 match self {
392 Self::NonNullable => Self::AllValid,
393 Self::AllValid | Self::AllInvalid | Self::Array(_) => self,
394 }
395 }
396
397 #[inline]
403 pub fn into_non_nullable(self, len: usize, ctx: &mut ExecutionCtx) -> Option<Validity> {
404 match self {
405 _ if len == 0 => Some(Validity::NonNullable),
406 Self::NonNullable => Some(Self::NonNullable),
407 Self::AllValid => Some(Self::NonNullable),
408 Self::AllInvalid => None,
409 Self::Array(is_valid) => {
410 is_valid
411 .statistics()
412 .compute_min::<bool>(ctx)
413 .vortex_expect("validity array must support min")
414 .then(|| {
415 Self::NonNullable
417 })
418 }
419 }
420 }
421
422 #[inline]
433 pub fn trivial_into_non_nullable(self, len: usize) -> VortexResult<Option<Validity>> {
434 match self {
435 _ if len == 0 => Ok(Some(Validity::NonNullable)),
436 Self::NonNullable => Ok(Some(Self::NonNullable)),
437 Self::AllValid => Ok(Some(Self::NonNullable)),
438 Self::AllInvalid => {
439 Err(vortex_err!(InvalidArgument: "Cannot cast AllInvalid to NonNullable"))
440 }
441 Self::Array(_) => Ok(None),
442 }
443 }
444
445 #[inline]
461 pub fn cast_nullability(
462 self,
463 nullability: Nullability,
464 len: usize,
465 ctx: &mut ExecutionCtx,
466 ) -> VortexResult<Validity> {
467 match nullability {
468 Nullability::NonNullable => self.into_non_nullable(len, ctx).ok_or_else(|| {
469 vortex_err!(InvalidArgument: "Cannot cast array with invalid values to non-nullable type.")
470 }),
471 Nullability::Nullable => Ok(self.into_nullable()),
472 }
473 }
474
475 #[inline]
500 pub fn trivially_cast_nullability(
501 self,
502 nullability: Nullability,
503 len: usize,
504 ) -> VortexResult<Option<Validity>> {
505 match nullability {
506 Nullability::NonNullable => self.trivial_into_non_nullable(len),
507 Nullability::Nullable => Ok(Some(self.into_nullable())),
508 }
509 }
510
511 #[inline]
513 pub fn maybe_len(&self) -> Option<usize> {
514 match self {
515 Self::NonNullable | Self::AllValid | Self::AllInvalid => None,
516 Self::Array(a) => Some(a.len()),
517 }
518 }
519}
520
521impl From<BitBuffer> for Validity {
522 #[inline]
523 fn from(value: BitBuffer) -> Self {
524 let true_count = value.true_count();
525 if true_count == value.len() {
526 Self::AllValid
527 } else if true_count == 0 {
528 Self::AllInvalid
529 } else {
530 Self::Array(BoolArray::from(value).into_array())
531 }
532 }
533}
534
535impl FromIterator<Mask> for Validity {
536 #[inline]
537 fn from_iter<T: IntoIterator<Item = Mask>>(iter: T) -> Self {
538 Validity::from_mask(iter.into_iter().collect(), Nullability::Nullable)
539 }
540}
541
542impl FromIterator<bool> for Validity {
543 #[inline]
544 fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
545 Validity::from(BitBuffer::from_iter(iter))
546 }
547}
548
549impl From<Nullability> for Validity {
550 #[inline]
551 fn from(value: Nullability) -> Self {
552 Validity::from(&value)
553 }
554}
555
556impl From<&Nullability> for Validity {
557 #[inline]
558 fn from(value: &Nullability) -> Self {
559 match *value {
560 Nullability::NonNullable => Validity::NonNullable,
561 Nullability::Nullable => Validity::AllValid,
562 }
563 }
564}
565
566impl Validity {
567 pub fn concat(validities: Vec<(Validity, usize)>) -> Option<Self> {
571 let mut validity_kinds = validities
572 .iter()
573 .map(|(v, _)| std::mem::discriminant(v))
574 .unique();
575 let validity_kind = validity_kinds.next()?;
576 if validity_kinds.next().is_none() {
577 if validity_kind == std::mem::discriminant(&Validity::AllValid) {
580 return Some(Validity::AllValid);
581 }
582 if validity_kind == std::mem::discriminant(&Validity::AllInvalid) {
583 return Some(Validity::AllInvalid);
584 }
585 if validity_kind == std::mem::discriminant(&Validity::NonNullable) {
586 return Some(Validity::NonNullable);
587 }
588 }
589
590 Some(Validity::Array(
591 unsafe {
592 ChunkedArray::new_unchecked(
593 validities.into_iter().map(|(v, len)| v.to_array(len)),
594 DType::Bool(Nullability::NonNullable),
595 )
596 }
597 .into_array(),
598 ))
599 }
600}
601
602impl Validity {
603 pub fn from_bit_buffer(buffer: BitBuffer, nullability: Nullability) -> Self {
604 if buffer.true_count() == buffer.len() {
605 nullability.into()
606 } else if buffer.true_count() == 0 {
607 Validity::AllInvalid
608 } else {
609 Validity::Array(BoolArray::new(buffer, Validity::NonNullable).into_array())
610 }
611 }
612
613 pub fn from_mask(mask: Mask, nullability: Nullability) -> Self {
614 assert!(
615 nullability == Nullability::Nullable || matches!(mask, Mask::AllTrue(_)),
616 "NonNullable validity must be AllValid",
617 );
618 match mask {
619 Mask::AllTrue(_) => match nullability {
620 Nullability::NonNullable => Validity::NonNullable,
621 Nullability::Nullable => Validity::AllValid,
622 },
623 Mask::AllFalse(_) => Validity::AllInvalid,
624 Mask::Values(values) => Validity::Array(values.into_array()),
625 }
626 }
627}
628
629impl IntoArray for Mask {
630 #[inline]
631 fn into_array(self) -> ArrayRef {
632 match self {
633 Self::AllTrue(len) => ConstantArray::new(true, len).into_array(),
634 Self::AllFalse(len) => ConstantArray::new(false, len).into_array(),
635 Self::Values(a) => a.into_array(),
636 }
637 }
638}
639
640impl IntoArray for &MaskValues {
641 #[inline]
642 fn into_array(self) -> ArrayRef {
643 BoolArray::new(self.bit_buffer().clone(), Validity::NonNullable).into_array()
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use rstest::rstest;
650 use vortex_buffer::Buffer;
651 use vortex_buffer::buffer;
652 use vortex_mask::Mask;
653
654 use crate::ArrayRef;
655 use crate::IntoArray;
656 use crate::VortexSessionExecute;
657 use crate::array_session;
658 use crate::arrays::PrimitiveArray;
659 use crate::dtype::Nullability;
660 use crate::validity::BoolArray;
661 use crate::validity::Validity;
662
663 #[rstest]
664 #[case(Validity::AllValid, 5, &[2, 4], Validity::AllValid, Validity::AllValid)]
665 #[case(
666 Validity::AllValid,
667 5,
668 &[2, 4],
669 Validity::AllInvalid,
670 Validity::Array(BoolArray::from_iter([true, true, false, true, false]).into_array())
671 )]
672 #[case(
673 Validity::AllValid,
674 5,
675 &[2, 4],
676 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
677 Validity::Array(BoolArray::from_iter([true, true, true, true, false]).into_array())
678 )]
679 #[case(
680 Validity::AllInvalid,
681 5,
682 &[2, 4],
683 Validity::AllValid,
684 Validity::Array(BoolArray::from_iter([false, false, true, false, true]).into_array())
685 )]
686 #[case(Validity::AllInvalid, 5, &[2, 4], Validity::AllInvalid, Validity::AllInvalid)]
687 #[case(
688 Validity::AllInvalid,
689 5,
690 &[2, 4],
691 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
692 Validity::Array(BoolArray::from_iter([false, false, true, false, false]).into_array())
693 )]
694 #[case(
695 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
696 5,
697 &[2, 4],
698 Validity::AllValid,
699 Validity::Array(BoolArray::from_iter([false, true, true, true, true]).into_array())
700 )]
701 #[case(
702 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
703 5,
704 &[2, 4],
705 Validity::AllInvalid,
706 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array())
707 )]
708 #[case(
709 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
710 5,
711 &[2, 4],
712 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
713 Validity::Array(BoolArray::from_iter([false, true, true, true, false]).into_array())
714 )]
715
716 fn patch_validity(
717 #[case] validity: Validity,
718 #[case] len: usize,
719 #[case] positions: &[u64],
720 #[case] patches: Validity,
721 #[case] expected: Validity,
722 ) {
723 let indices =
724 PrimitiveArray::new(Buffer::copy_from(positions), Validity::NonNullable).into_array();
725
726 let mut ctx = array_session().create_execution_ctx();
727
728 assert!(
729 validity
730 .patch(len, 0, &indices, &patches, &mut ctx,)
731 .unwrap()
732 .mask_eq(&expected, len, &mut ctx)
733 .unwrap()
734 );
735 }
736
737 #[test]
738 #[should_panic]
739 fn out_of_bounds_patch() {
740 let mut ctx = array_session().create_execution_ctx();
741 Validity::NonNullable
742 .patch(
743 2,
744 0,
745 &buffer![4].into_array(),
746 &Validity::AllInvalid,
747 &mut ctx,
748 )
749 .unwrap();
750 }
751
752 #[test]
753 #[should_panic]
754 fn into_validity_nullable() {
755 Validity::from_mask(Mask::AllFalse(10), Nullability::NonNullable);
756 }
757
758 #[test]
759 #[should_panic]
760 fn into_validity_nullable_array() {
761 Validity::from_mask(Mask::from_iter(vec![true, false]), Nullability::NonNullable);
762 }
763
764 #[rstest]
765 #[case(
766 Validity::AllValid,
767 PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(),
768 Validity::from_iter(vec![true, false])
769 )]
770 #[case(Validity::AllValid, buffer![0, 1].into_array(), Validity::AllValid)]
771 #[case(
772 Validity::AllValid,
773 PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(),
774 Validity::AllInvalid
775 )]
776 #[case(
777 Validity::NonNullable,
778 PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(),
779 Validity::from_iter(vec![true, false])
780 )]
781 #[case(Validity::NonNullable, buffer![0, 1].into_array(), Validity::NonNullable)]
782 #[case(
783 Validity::NonNullable,
784 PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(),
785 Validity::AllInvalid
786 )]
787 fn validity_take(
788 #[case] validity: Validity,
789 #[case] indices: ArrayRef,
790 #[case] expected: Validity,
791 ) {
792 let mut ctx = array_session().create_execution_ctx();
793 assert!(
794 validity
795 .take(&indices)
796 .unwrap()
797 .mask_eq(&expected, indices.len(), &mut ctx)
798 .unwrap()
799 );
800 }
801
802 #[rstest]
803 #[case(Validity::NonNullable, Validity::AllValid, true)]
805 #[case(Validity::AllValid, Validity::NonNullable, true)]
806 #[case(Validity::AllValid, Validity::AllInvalid, false)]
807 #[case(Validity::NonNullable, Validity::AllInvalid, false)]
808 #[case(
810 Validity::Array(BoolArray::from_iter([true, true, true]).into_array()),
811 Validity::AllValid,
812 true
813 )]
814 #[case(
815 Validity::NonNullable,
816 Validity::Array(BoolArray::from_iter([true, true, true]).into_array()),
817 true
818 )]
819 #[case(
820 Validity::Array(BoolArray::from_iter([false, false, false]).into_array()),
821 Validity::AllInvalid,
822 true
823 )]
824 #[case(
825 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
826 Validity::AllValid,
827 false
828 )]
829 #[case(
830 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
831 Validity::AllInvalid,
832 false
833 )]
834 fn mask_eq_mixed_variants(
835 #[case] lhs: Validity,
836 #[case] rhs: Validity,
837 #[case] expected: bool,
838 ) -> vortex_error::VortexResult<()> {
839 let mut ctx = array_session().create_execution_ctx();
840 assert_eq!(lhs.mask_eq(&rhs, 3, &mut ctx)?, expected);
841 Ok(())
842 }
843}