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::arrays::BoolArray;
28use crate::arrays::ChunkedArray;
29use crate::arrays::ConstantArray;
30use crate::builtins::ArrayBuiltins;
31use crate::dtype::DType;
32use crate::dtype::Nullability;
33use crate::optimizer::ArrayOptimizer;
34use crate::patches::Patches;
35use crate::scalar::Scalar;
36use crate::scalar_fn::fns::binary::Binary;
37use crate::scalar_fn::fns::operators::Operator;
38
39#[derive(Clone)]
41pub enum Validity {
42 NonNullable,
44 AllValid,
46 AllInvalid,
48 Array(ArrayRef),
52}
53
54impl Debug for Validity {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 Self::NonNullable => write!(f, "NonNullable"),
58 Self::AllValid => write!(f, "AllValid"),
59 Self::AllInvalid => write!(f, "AllInvalid"),
60 Self::Array(arr) => write!(f, "SomeValid({arr})"),
61 }
62 }
63}
64
65impl Validity {
66 pub fn execute(self, ctx: &mut ExecutionCtx) -> VortexResult<Validity> {
68 match self {
69 v @ Validity::NonNullable | v @ Validity::AllValid | v @ Validity::AllInvalid => Ok(v),
70 Validity::Array(a) => Ok(Validity::Array(a.execute::<Canonical>(ctx)?.into_array())),
71 }
72 }
73}
74
75impl Validity {
76 pub const DTYPE: DType = DType::Bool(Nullability::NonNullable);
78
79 pub fn to_array(&self, len: usize) -> ArrayRef {
81 match self {
82 Self::NonNullable | Self::AllValid => ConstantArray::new(true, len).into_array(),
83 Self::AllInvalid => ConstantArray::new(false, len).into_array(),
84 Self::Array(a) => a.clone(),
85 }
86 }
87
88 #[inline]
90 pub fn into_array(self) -> Option<ArrayRef> {
91 if let Self::Array(a) = self {
92 Some(a)
93 } else {
94 None
95 }
96 }
97
98 #[inline]
100 pub fn as_array(&self) -> Option<&ArrayRef> {
101 if let Self::Array(a) = self {
102 Some(a)
103 } else {
104 None
105 }
106 }
107
108 #[inline]
109 pub fn nullability(&self) -> Nullability {
110 if matches!(self, Self::NonNullable) {
111 Nullability::NonNullable
112 } else {
113 Nullability::Nullable
114 }
115 }
116
117 #[inline]
124 pub fn definitely_no_nulls(&self) -> bool {
125 matches!(self, Self::NonNullable | Self::AllValid)
126 }
127
128 #[inline]
137 pub fn definitely_all_null(&self) -> bool {
138 matches!(self, Self::AllInvalid)
139 }
140
141 pub fn execute_no_nulls(&self, length: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
147 match self {
148 Self::NonNullable | Self::AllValid => Ok(true),
149 Self::AllInvalid => Ok(length == 0),
150 Self::Array(_) => Ok(self.execute_mask(length, ctx)?.all_true()),
151 }
152 }
153
154 #[inline]
156 pub fn union_nullability(self, nullability: Nullability) -> Self {
157 match nullability {
158 Nullability::NonNullable => self,
159 Nullability::Nullable => self.into_nullable(),
160 }
161 }
162
163 #[inline]
166 pub fn execute_is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
167 Ok(match self {
168 Self::NonNullable | Self::AllValid => true,
169 Self::AllInvalid => false,
170 Self::Array(a) => a
171 .execute_scalar(index, ctx)?
172 .as_bool()
173 .value()
174 .ok_or_else(|| vortex_err!("validity value at index {index} is null"))?,
175 })
176 }
177
178 #[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 #[inline]
186 pub fn slice(&self, range: Range<usize>) -> VortexResult<Self> {
187 match self {
188 Self::Array(a) => Ok(Self::Array(a.slice(range)?)),
189 Self::NonNullable | Self::AllValid | Self::AllInvalid => Ok(self.clone()),
190 }
191 }
192
193 pub fn take(&self, indices: &ArrayRef) -> VortexResult<Self> {
194 match self {
195 Self::NonNullable => indices.validity(),
196 Self::AllValid => Ok(match indices.validity()? {
197 Self::NonNullable => Self::AllValid,
198 v => v,
199 }),
200 Self::AllInvalid => Ok(Self::AllInvalid),
201 Self::Array(is_valid) => {
202 let maybe_is_valid = is_valid.take(indices.clone())?;
203 let is_valid = maybe_is_valid.fill_null(Scalar::from(false))?;
205 Ok(Self::Array(is_valid))
206 }
207 }
208 }
209
210 pub fn not(&self) -> VortexResult<Self> {
212 match self {
213 Validity::NonNullable => Ok(Validity::NonNullable),
214 Validity::AllValid => Ok(Validity::AllInvalid),
215 Validity::AllInvalid => Ok(Validity::AllValid),
216 Validity::Array(arr) => Ok(Validity::Array(arr.not()?)),
217 }
218 }
219
220 pub fn filter(&self, mask: &Mask) -> VortexResult<Self> {
228 match self {
231 v @ (Validity::NonNullable | Validity::AllValid | Validity::AllInvalid) => {
232 Ok(v.clone())
233 }
234 Validity::Array(arr) => Ok(Validity::Array(arr.filter(mask.clone())?)),
235 }
236 }
237
238 #[deprecated(note = "Use execute_mask")]
242 pub fn to_mask(&self, length: usize, ctx: &mut ExecutionCtx) -> VortexResult<Mask> {
243 match self {
244 Self::NonNullable | Self::AllValid => Ok(Mask::new_true(length)),
245 Self::AllInvalid => Ok(Mask::new_false(length)),
246 Self::Array(arr) => arr.clone().execute::<Mask>(ctx),
247 }
248 }
249
250 #[inline]
256 pub fn execute_mask(&self, length: usize, ctx: &mut ExecutionCtx) -> VortexResult<Mask> {
257 match self {
258 Self::NonNullable | Self::AllValid => Ok(Mask::AllTrue(length)),
259 Self::AllInvalid => Ok(Mask::AllFalse(length)),
260 Self::Array(arr) => {
261 assert_eq!(
262 arr.len(),
263 length,
264 "Validity::Array length must equal to_logical's argument: {}, {}.",
265 arr.len(),
266 length,
267 );
268 arr.clone().execute::<Mask>(ctx)
271 }
272 }
273 }
274
275 pub fn mask_eq(
278 &self,
279 other: &Validity,
280 length: usize,
281 ctx: &mut ExecutionCtx,
282 ) -> VortexResult<bool> {
283 match (self, other) {
284 (
286 Validity::NonNullable | Validity::AllValid,
287 Validity::NonNullable | Validity::AllValid,
288 )
289 | (Validity::AllInvalid, Validity::AllInvalid) => Ok(true),
290 _ => Ok(self.execute_mask(length, ctx)? == other.execute_mask(length, ctx)?),
291 }
292 }
293
294 #[inline]
296 pub fn and(self, rhs: Validity) -> VortexResult<Validity> {
297 Ok(match (self, rhs) {
298 (Validity::NonNullable, Validity::NonNullable) => Validity::NonNullable,
300 (Validity::AllInvalid, _) | (_, Validity::AllInvalid) => Validity::AllInvalid,
302 (Validity::Array(a), Validity::AllValid)
304 | (Validity::Array(a), Validity::NonNullable)
305 | (Validity::NonNullable, Validity::Array(a))
306 | (Validity::AllValid, Validity::Array(a)) => Validity::Array(a),
307 (Validity::NonNullable, Validity::AllValid)
309 | (Validity::AllValid, Validity::NonNullable)
310 | (Validity::AllValid, Validity::AllValid) => Validity::AllValid,
311 (Validity::Array(lhs), Validity::Array(rhs)) => Validity::Array(
313 Binary::try_new(lhs, rhs, Operator::And)?
314 .into_array()
315 .optimize()?,
316 ),
317 })
318 }
319
320 pub fn patch(
321 self,
322 len: usize,
323 indices_offset: usize,
324 indices: &ArrayRef,
325 patches: &Validity,
326 ctx: &mut ExecutionCtx,
327 ) -> VortexResult<Self> {
328 match (&self, patches) {
329 (Validity::NonNullable, Validity::NonNullable) => return Ok(Validity::NonNullable),
330 (Validity::NonNullable, _) => {
331 vortex_bail!("Can't patch a non-nullable validity with nullable validity")
332 }
333 (_, Validity::NonNullable) => {
334 vortex_bail!("Can't patch a nullable validity with non-nullable validity")
335 }
336 (Validity::AllValid, Validity::AllValid) => return Ok(Validity::AllValid),
337 (Validity::AllInvalid, Validity::AllInvalid) => return Ok(Validity::AllInvalid),
338 _ => {}
339 };
340
341 if matches!(self, Validity::NonNullable) {
342 return Ok(Self::NonNullable);
343 }
344
345 let source = match self {
347 Validity::NonNullable => BoolArray::from(BitBuffer::new_set(len)),
348 Validity::AllValid => BoolArray::from(BitBuffer::new_set(len)),
349 Validity::AllInvalid => BoolArray::from(BitBuffer::new_unset(len)),
350 Validity::Array(a) => a.execute::<BoolArray>(ctx)?,
351 };
352
353 let patch_values = match patches {
354 Validity::NonNullable => BoolArray::from(BitBuffer::new_set(indices.len())),
355 Validity::AllValid => BoolArray::from(BitBuffer::new_set(indices.len())),
356 Validity::AllInvalid => BoolArray::from(BitBuffer::new_unset(indices.len())),
357 Validity::Array(a) => a.clone().execute::<BoolArray>(ctx)?,
358 };
359
360 let patches = Patches::new(
361 len,
362 indices_offset,
363 indices.clone(),
364 patch_values.into_array(),
365 None,
367 ctx,
368 )?;
369
370 Ok(Self::Array(source.patch(&patches, ctx)?.into_array()))
371 }
372
373 #[inline]
375 pub fn into_nullable(self) -> Validity {
376 match self {
377 Self::NonNullable => Self::AllValid,
378 Self::AllValid | Self::AllInvalid | Self::Array(_) => self,
379 }
380 }
381
382 #[inline]
388 pub fn into_non_nullable(self, len: usize, ctx: &mut ExecutionCtx) -> Option<Validity> {
389 match self {
390 _ if len == 0 => Some(Validity::NonNullable),
391 Self::NonNullable => Some(Self::NonNullable),
392 Self::AllValid => Some(Self::NonNullable),
393 Self::AllInvalid => None,
394 Self::Array(is_valid) => {
395 is_valid
396 .statistics()
397 .compute_min::<bool>(ctx)
398 .vortex_expect("validity array must support min")
399 .then(|| {
400 Self::NonNullable
402 })
403 }
404 }
405 }
406
407 #[inline]
418 pub fn trivial_into_non_nullable(self, len: usize) -> VortexResult<Option<Validity>> {
419 match self {
420 _ if len == 0 => Ok(Some(Validity::NonNullable)),
421 Self::NonNullable => Ok(Some(Self::NonNullable)),
422 Self::AllValid => Ok(Some(Self::NonNullable)),
423 Self::AllInvalid => {
424 Err(vortex_err!(InvalidArgument: "Cannot cast AllInvalid to NonNullable"))
425 }
426 Self::Array(_) => Ok(None),
427 }
428 }
429
430 #[inline]
446 pub fn cast_nullability(
447 self,
448 nullability: Nullability,
449 len: usize,
450 ctx: &mut ExecutionCtx,
451 ) -> VortexResult<Validity> {
452 match nullability {
453 Nullability::NonNullable => self.into_non_nullable(len, ctx).ok_or_else(|| {
454 vortex_err!(InvalidArgument: "Cannot cast array with invalid values to non-nullable type.")
455 }),
456 Nullability::Nullable => Ok(self.into_nullable()),
457 }
458 }
459
460 #[inline]
485 pub fn trivially_cast_nullability(
486 self,
487 nullability: Nullability,
488 len: usize,
489 ) -> VortexResult<Option<Validity>> {
490 match nullability {
491 Nullability::NonNullable => self.trivial_into_non_nullable(len),
492 Nullability::Nullable => Ok(Some(self.into_nullable())),
493 }
494 }
495
496 #[inline]
498 pub fn maybe_len(&self) -> Option<usize> {
499 match self {
500 Self::NonNullable | Self::AllValid | Self::AllInvalid => None,
501 Self::Array(a) => Some(a.len()),
502 }
503 }
504}
505
506impl From<BitBuffer> for Validity {
507 #[inline]
508 fn from(value: BitBuffer) -> Self {
509 let true_count = value.true_count();
510 if true_count == value.len() {
511 Self::AllValid
512 } else if true_count == 0 {
513 Self::AllInvalid
514 } else {
515 Self::Array(BoolArray::from(value).into_array())
516 }
517 }
518}
519
520impl FromIterator<Mask> for Validity {
521 #[inline]
522 fn from_iter<T: IntoIterator<Item = Mask>>(iter: T) -> Self {
523 Validity::from_mask(iter.into_iter().collect(), Nullability::Nullable)
524 }
525}
526
527impl FromIterator<bool> for Validity {
528 #[inline]
529 fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
530 Validity::from(BitBuffer::from_iter(iter))
531 }
532}
533
534impl From<Nullability> for Validity {
535 #[inline]
536 fn from(value: Nullability) -> Self {
537 Validity::from(&value)
538 }
539}
540
541impl From<&Nullability> for Validity {
542 #[inline]
543 fn from(value: &Nullability) -> Self {
544 match *value {
545 Nullability::NonNullable => Validity::NonNullable,
546 Nullability::Nullable => Validity::AllValid,
547 }
548 }
549}
550
551impl Validity {
552 pub fn concat(validities: Vec<(Validity, usize)>) -> Option<Self> {
556 let mut validity_kinds = validities
557 .iter()
558 .map(|(v, _)| std::mem::discriminant(v))
559 .unique();
560 let validity_kind = validity_kinds.next()?;
561 if validity_kinds.next().is_none() {
562 if validity_kind == std::mem::discriminant(&Validity::AllValid) {
565 return Some(Validity::AllValid);
566 }
567 if validity_kind == std::mem::discriminant(&Validity::AllInvalid) {
568 return Some(Validity::AllInvalid);
569 }
570 if validity_kind == std::mem::discriminant(&Validity::NonNullable) {
571 return Some(Validity::NonNullable);
572 }
573 }
574
575 Some(Validity::Array(
576 unsafe {
577 ChunkedArray::new_unchecked(
578 validities.into_iter().map(|(v, len)| v.to_array(len)),
579 DType::Bool(Nullability::NonNullable),
580 )
581 }
582 .into_array(),
583 ))
584 }
585}
586
587impl Validity {
588 pub fn from_bit_buffer(buffer: BitBuffer, nullability: Nullability) -> Self {
589 if buffer.true_count() == buffer.len() {
590 nullability.into()
591 } else if buffer.true_count() == 0 {
592 Validity::AllInvalid
593 } else {
594 Validity::Array(BoolArray::new(buffer, Validity::NonNullable).into_array())
595 }
596 }
597
598 pub fn from_mask(mask: Mask, nullability: Nullability) -> Self {
599 assert!(
600 nullability == Nullability::Nullable || matches!(mask, Mask::AllTrue(_)),
601 "NonNullable validity must be AllValid",
602 );
603 match mask {
604 Mask::AllTrue(_) => match nullability {
605 Nullability::NonNullable => Validity::NonNullable,
606 Nullability::Nullable => Validity::AllValid,
607 },
608 Mask::AllFalse(_) => Validity::AllInvalid,
609 Mask::Values(values) => Validity::Array(values.into_array()),
610 }
611 }
612}
613
614impl IntoArray for Mask {
615 #[inline]
616 fn into_array(self) -> ArrayRef {
617 match self {
618 Self::AllTrue(len) => ConstantArray::new(true, len).into_array(),
619 Self::AllFalse(len) => ConstantArray::new(false, len).into_array(),
620 Self::Values(a) => a.into_array(),
621 }
622 }
623}
624
625impl IntoArray for &MaskValues {
626 #[inline]
627 fn into_array(self) -> ArrayRef {
628 BoolArray::new(self.bit_buffer().clone(), Validity::NonNullable).into_array()
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use rstest::rstest;
635 use vortex_buffer::Buffer;
636 use vortex_buffer::buffer;
637 use vortex_mask::Mask;
638
639 use crate::ArrayRef;
640 use crate::IntoArray;
641 use crate::VortexSessionExecute;
642 use crate::array_session;
643 use crate::arrays::PrimitiveArray;
644 use crate::dtype::Nullability;
645 use crate::validity::BoolArray;
646 use crate::validity::Validity;
647
648 #[rstest]
649 #[case(Validity::AllValid, 5, &[2, 4], Validity::AllValid, Validity::AllValid)]
650 #[case(
651 Validity::AllValid,
652 5,
653 &[2, 4],
654 Validity::AllInvalid,
655 Validity::Array(BoolArray::from_iter([true, true, false, true, false]).into_array())
656 )]
657 #[case(
658 Validity::AllValid,
659 5,
660 &[2, 4],
661 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
662 Validity::Array(BoolArray::from_iter([true, true, true, true, false]).into_array())
663 )]
664 #[case(
665 Validity::AllInvalid,
666 5,
667 &[2, 4],
668 Validity::AllValid,
669 Validity::Array(BoolArray::from_iter([false, false, true, false, true]).into_array())
670 )]
671 #[case(Validity::AllInvalid, 5, &[2, 4], Validity::AllInvalid, Validity::AllInvalid)]
672 #[case(
673 Validity::AllInvalid,
674 5,
675 &[2, 4],
676 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
677 Validity::Array(BoolArray::from_iter([false, false, true, false, false]).into_array())
678 )]
679 #[case(
680 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
681 5,
682 &[2, 4],
683 Validity::AllValid,
684 Validity::Array(BoolArray::from_iter([false, true, true, true, true]).into_array())
685 )]
686 #[case(
687 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
688 5,
689 &[2, 4],
690 Validity::AllInvalid,
691 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array())
692 )]
693 #[case(
694 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
695 5,
696 &[2, 4],
697 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
698 Validity::Array(BoolArray::from_iter([false, true, true, true, false]).into_array())
699 )]
700
701 fn patch_validity(
702 #[case] validity: Validity,
703 #[case] len: usize,
704 #[case] positions: &[u64],
705 #[case] patches: Validity,
706 #[case] expected: Validity,
707 ) {
708 let indices =
709 PrimitiveArray::new(Buffer::copy_from(positions), Validity::NonNullable).into_array();
710
711 let mut ctx = array_session().create_execution_ctx();
712
713 assert!(
714 validity
715 .patch(len, 0, &indices, &patches, &mut ctx,)
716 .unwrap()
717 .mask_eq(&expected, len, &mut ctx)
718 .unwrap()
719 );
720 }
721
722 #[rstest]
723 #[case(
724 Validity::NonNullable,
725 1,
726 "Can't patch a non-nullable validity with nullable validity"
727 )]
728 #[case(
729 Validity::AllValid,
730 4,
731 "Patch indices 4, offset 0 are longer than the array length 2"
732 )]
733 fn rejects_invalid_patch(
734 #[case] validity: Validity,
735 #[case] index: u64,
736 #[case] message: &str,
737 ) {
738 let mut ctx = array_session().create_execution_ctx();
739 let error = validity
740 .patch(
741 2,
742 0,
743 &buffer![index].into_array(),
744 &Validity::AllInvalid,
745 &mut ctx,
746 )
747 .unwrap_err();
748 assert!(error.to_string().contains(message), "{error}");
749 }
750
751 #[test]
752 #[should_panic(expected = "NonNullable validity must be AllValid")]
753 fn into_validity_nullable() {
754 Validity::from_mask(Mask::AllFalse(10), Nullability::NonNullable);
755 }
756
757 #[test]
758 #[should_panic(expected = "NonNullable validity must be AllValid")]
759 fn into_validity_nullable_array() {
760 Validity::from_mask(Mask::from_iter(vec![true, false]), Nullability::NonNullable);
761 }
762
763 #[rstest]
764 #[case(
765 Validity::AllValid,
766 PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(),
767 Validity::from_iter(vec![true, false])
768 )]
769 #[case(Validity::AllValid, buffer![0, 1].into_array(), Validity::AllValid)]
770 #[case(
771 Validity::AllValid,
772 PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(),
773 Validity::AllInvalid
774 )]
775 #[case(
776 Validity::NonNullable,
777 PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(),
778 Validity::from_iter(vec![true, false])
779 )]
780 #[case(Validity::NonNullable, buffer![0, 1].into_array(), Validity::NonNullable)]
781 #[case(
782 Validity::NonNullable,
783 PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(),
784 Validity::AllInvalid
785 )]
786 fn validity_take(
787 #[case] validity: Validity,
788 #[case] indices: ArrayRef,
789 #[case] expected: Validity,
790 ) {
791 let mut ctx = array_session().create_execution_ctx();
792 let taken = validity.take(&indices).unwrap();
793 assert_eq!(taken.nullability(), expected.nullability());
794 assert!(taken.mask_eq(&expected, indices.len(), &mut ctx).unwrap());
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}