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]
168 pub fn execute_is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
169 Ok(match self {
170 Self::NonNullable | Self::AllValid => true,
171 Self::AllInvalid => false,
172 Self::Array(a) => a
173 .execute_scalar(index, ctx)?
174 .as_bool()
175 .value()
176 .ok_or_else(|| vortex_err!("validity value at index {index} is null"))?,
177 })
178 }
179
180 #[inline]
183 pub fn execute_is_null(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
184 Ok(!self.execute_is_valid(index, ctx)?)
185 }
186
187 #[deprecated(note = "use `execute_is_valid` with an explicit `ExecutionCtx`")]
189 #[inline]
190 #[allow(clippy::disallowed_methods)]
191 pub fn is_valid(&self, index: usize) -> VortexResult<bool> {
192 self.execute_is_valid(index, &mut legacy_session().create_execution_ctx())
193 }
194
195 #[deprecated(note = "use `execute_is_null` with an explicit `ExecutionCtx`")]
197 #[inline]
198 #[allow(clippy::disallowed_methods)]
199 pub fn is_null(&self, index: usize) -> VortexResult<bool> {
200 self.execute_is_null(index, &mut legacy_session().create_execution_ctx())
201 }
202
203 #[inline]
204 pub fn slice(&self, range: Range<usize>) -> VortexResult<Self> {
205 match self {
206 Self::Array(a) => Ok(Self::Array(a.slice(range)?)),
207 Self::NonNullable | Self::AllValid | Self::AllInvalid => Ok(self.clone()),
208 }
209 }
210
211 pub fn take(&self, indices: &ArrayRef) -> VortexResult<Self> {
212 match self {
213 Self::NonNullable => indices.validity(),
214 Self::AllValid => Ok(match indices.validity()? {
215 Self::NonNullable => Self::AllValid,
216 v => v,
217 }),
218 Self::AllInvalid => Ok(Self::AllInvalid),
219 Self::Array(is_valid) => {
220 let maybe_is_valid = is_valid.take(indices.clone())?;
221 let is_valid = maybe_is_valid.fill_null(Scalar::from(false))?;
223 Ok(Self::Array(is_valid))
224 }
225 }
226 }
227
228 pub fn not(&self) -> VortexResult<Self> {
230 match self {
231 Validity::NonNullable => Ok(Validity::NonNullable),
232 Validity::AllValid => Ok(Validity::AllInvalid),
233 Validity::AllInvalid => Ok(Validity::AllValid),
234 Validity::Array(arr) => Ok(Validity::Array(arr.not()?)),
235 }
236 }
237
238 pub fn filter(&self, mask: &Mask) -> VortexResult<Self> {
246 match self {
249 v @ (Validity::NonNullable | Validity::AllValid | Validity::AllInvalid) => {
250 Ok(v.clone())
251 }
252 Validity::Array(arr) => Ok(Validity::Array(arr.filter(mask.clone())?)),
253 }
254 }
255
256 #[deprecated(note = "Use execute_mask")]
260 pub fn to_mask(&self, length: usize, ctx: &mut ExecutionCtx) -> VortexResult<Mask> {
261 match self {
262 Self::NonNullable | Self::AllValid => Ok(Mask::new_true(length)),
263 Self::AllInvalid => Ok(Mask::new_false(length)),
264 Self::Array(arr) => arr.clone().execute::<Mask>(ctx),
265 }
266 }
267
268 #[inline]
274 pub fn execute_mask(&self, length: usize, ctx: &mut ExecutionCtx) -> VortexResult<Mask> {
275 match self {
276 Self::NonNullable | Self::AllValid => Ok(Mask::AllTrue(length)),
277 Self::AllInvalid => Ok(Mask::AllFalse(length)),
278 Self::Array(arr) => {
279 assert_eq!(
280 arr.len(),
281 length,
282 "Validity::Array length must equal to_logical's argument: {}, {}.",
283 arr.len(),
284 length,
285 );
286 arr.clone().execute::<Mask>(ctx)
289 }
290 }
291 }
292
293 pub fn mask_eq(
296 &self,
297 other: &Validity,
298 length: usize,
299 ctx: &mut ExecutionCtx,
300 ) -> VortexResult<bool> {
301 match (self, other) {
302 (
304 Validity::NonNullable | Validity::AllValid,
305 Validity::NonNullable | Validity::AllValid,
306 )
307 | (Validity::AllInvalid, Validity::AllInvalid) => Ok(true),
308 _ => Ok(self.execute_mask(length, ctx)? == other.execute_mask(length, ctx)?),
309 }
310 }
311
312 #[inline]
314 pub fn and(self, rhs: Validity) -> VortexResult<Validity> {
315 Ok(match (self, rhs) {
316 (Validity::NonNullable, Validity::NonNullable) => Validity::NonNullable,
318 (Validity::AllInvalid, _) | (_, Validity::AllInvalid) => Validity::AllInvalid,
320 (Validity::Array(a), Validity::AllValid)
322 | (Validity::Array(a), Validity::NonNullable)
323 | (Validity::NonNullable, Validity::Array(a))
324 | (Validity::AllValid, Validity::Array(a)) => Validity::Array(a),
325 (Validity::NonNullable, Validity::AllValid)
327 | (Validity::AllValid, Validity::NonNullable)
328 | (Validity::AllValid, Validity::AllValid) => Validity::AllValid,
329 (Validity::Array(lhs), Validity::Array(rhs)) => Validity::Array(
331 Binary::try_new(lhs, rhs, Operator::And)?
332 .into_array()
333 .optimize()?,
334 ),
335 })
336 }
337
338 pub fn patch(
339 self,
340 len: usize,
341 indices_offset: usize,
342 indices: &ArrayRef,
343 patches: &Validity,
344 ctx: &mut ExecutionCtx,
345 ) -> VortexResult<Self> {
346 match (&self, patches) {
347 (Validity::NonNullable, Validity::NonNullable) => return Ok(Validity::NonNullable),
348 (Validity::NonNullable, _) => {
349 vortex_bail!("Can't patch a non-nullable validity with nullable validity")
350 }
351 (_, Validity::NonNullable) => {
352 vortex_bail!("Can't patch a nullable validity with non-nullable validity")
353 }
354 (Validity::AllValid, Validity::AllValid) => return Ok(Validity::AllValid),
355 (Validity::AllInvalid, Validity::AllInvalid) => return Ok(Validity::AllInvalid),
356 _ => {}
357 };
358
359 if matches!(self, Validity::NonNullable) {
360 return Ok(Self::NonNullable);
361 }
362
363 let source = match self {
365 Validity::NonNullable => BoolArray::from(BitBuffer::new_set(len)),
366 Validity::AllValid => BoolArray::from(BitBuffer::new_set(len)),
367 Validity::AllInvalid => BoolArray::from(BitBuffer::new_unset(len)),
368 Validity::Array(a) => a.execute::<BoolArray>(ctx)?,
369 };
370
371 let patch_values = match patches {
372 Validity::NonNullable => BoolArray::from(BitBuffer::new_set(indices.len())),
373 Validity::AllValid => BoolArray::from(BitBuffer::new_set(indices.len())),
374 Validity::AllInvalid => BoolArray::from(BitBuffer::new_unset(indices.len())),
375 Validity::Array(a) => a.clone().execute::<BoolArray>(ctx)?,
376 };
377
378 let patches = Patches::new(
379 len,
380 indices_offset,
381 indices.clone(),
382 patch_values.into_array(),
383 None,
385 )?;
386
387 Ok(Self::Array(source.patch(&patches, ctx)?.into_array()))
388 }
389
390 #[inline]
392 pub fn into_nullable(self) -> Validity {
393 match self {
394 Self::NonNullable => Self::AllValid,
395 Self::AllValid | Self::AllInvalid | Self::Array(_) => self,
396 }
397 }
398
399 #[inline]
405 pub fn into_non_nullable(self, len: usize, ctx: &mut ExecutionCtx) -> Option<Validity> {
406 match self {
407 _ if len == 0 => Some(Validity::NonNullable),
408 Self::NonNullable => Some(Self::NonNullable),
409 Self::AllValid => Some(Self::NonNullable),
410 Self::AllInvalid => None,
411 Self::Array(is_valid) => {
412 is_valid
413 .statistics()
414 .compute_min::<bool>(ctx)
415 .vortex_expect("validity array must support min")
416 .then(|| {
417 Self::NonNullable
419 })
420 }
421 }
422 }
423
424 #[inline]
435 pub fn trivial_into_non_nullable(self, len: usize) -> VortexResult<Option<Validity>> {
436 match self {
437 _ if len == 0 => Ok(Some(Validity::NonNullable)),
438 Self::NonNullable => Ok(Some(Self::NonNullable)),
439 Self::AllValid => Ok(Some(Self::NonNullable)),
440 Self::AllInvalid => {
441 Err(vortex_err!(InvalidArgument: "Cannot cast AllInvalid to NonNullable"))
442 }
443 Self::Array(_) => Ok(None),
444 }
445 }
446
447 #[inline]
463 pub fn cast_nullability(
464 self,
465 nullability: Nullability,
466 len: usize,
467 ctx: &mut ExecutionCtx,
468 ) -> VortexResult<Validity> {
469 match nullability {
470 Nullability::NonNullable => self.into_non_nullable(len, ctx).ok_or_else(|| {
471 vortex_err!(InvalidArgument: "Cannot cast array with invalid values to non-nullable type.")
472 }),
473 Nullability::Nullable => Ok(self.into_nullable()),
474 }
475 }
476
477 #[inline]
502 pub fn trivially_cast_nullability(
503 self,
504 nullability: Nullability,
505 len: usize,
506 ) -> VortexResult<Option<Validity>> {
507 match nullability {
508 Nullability::NonNullable => self.trivial_into_non_nullable(len),
509 Nullability::Nullable => Ok(Some(self.into_nullable())),
510 }
511 }
512
513 #[inline]
515 pub fn maybe_len(&self) -> Option<usize> {
516 match self {
517 Self::NonNullable | Self::AllValid | Self::AllInvalid => None,
518 Self::Array(a) => Some(a.len()),
519 }
520 }
521}
522
523impl From<BitBuffer> for Validity {
524 #[inline]
525 fn from(value: BitBuffer) -> Self {
526 let true_count = value.true_count();
527 if true_count == value.len() {
528 Self::AllValid
529 } else if true_count == 0 {
530 Self::AllInvalid
531 } else {
532 Self::Array(BoolArray::from(value).into_array())
533 }
534 }
535}
536
537impl FromIterator<Mask> for Validity {
538 #[inline]
539 fn from_iter<T: IntoIterator<Item = Mask>>(iter: T) -> Self {
540 Validity::from_mask(iter.into_iter().collect(), Nullability::Nullable)
541 }
542}
543
544impl FromIterator<bool> for Validity {
545 #[inline]
546 fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
547 Validity::from(BitBuffer::from_iter(iter))
548 }
549}
550
551impl From<Nullability> for Validity {
552 #[inline]
553 fn from(value: Nullability) -> Self {
554 Validity::from(&value)
555 }
556}
557
558impl From<&Nullability> for Validity {
559 #[inline]
560 fn from(value: &Nullability) -> Self {
561 match *value {
562 Nullability::NonNullable => Validity::NonNullable,
563 Nullability::Nullable => Validity::AllValid,
564 }
565 }
566}
567
568impl Validity {
569 pub fn concat(validities: Vec<(Validity, usize)>) -> Option<Self> {
573 let mut validity_kinds = validities
574 .iter()
575 .map(|(v, _)| std::mem::discriminant(v))
576 .unique();
577 let validity_kind = validity_kinds.next()?;
578 if validity_kinds.next().is_none() {
579 if validity_kind == std::mem::discriminant(&Validity::AllValid) {
582 return Some(Validity::AllValid);
583 }
584 if validity_kind == std::mem::discriminant(&Validity::AllInvalid) {
585 return Some(Validity::AllInvalid);
586 }
587 if validity_kind == std::mem::discriminant(&Validity::NonNullable) {
588 return Some(Validity::NonNullable);
589 }
590 }
591
592 Some(Validity::Array(
593 unsafe {
594 ChunkedArray::new_unchecked(
595 validities.into_iter().map(|(v, len)| v.to_array(len)),
596 DType::Bool(Nullability::NonNullable),
597 )
598 }
599 .into_array(),
600 ))
601 }
602}
603
604impl Validity {
605 pub fn from_bit_buffer(buffer: BitBuffer, nullability: Nullability) -> Self {
606 if buffer.true_count() == buffer.len() {
607 nullability.into()
608 } else if buffer.true_count() == 0 {
609 Validity::AllInvalid
610 } else {
611 Validity::Array(BoolArray::new(buffer, Validity::NonNullable).into_array())
612 }
613 }
614
615 pub fn from_mask(mask: Mask, nullability: Nullability) -> Self {
616 assert!(
617 nullability == Nullability::Nullable || matches!(mask, Mask::AllTrue(_)),
618 "NonNullable validity must be AllValid",
619 );
620 match mask {
621 Mask::AllTrue(_) => match nullability {
622 Nullability::NonNullable => Validity::NonNullable,
623 Nullability::Nullable => Validity::AllValid,
624 },
625 Mask::AllFalse(_) => Validity::AllInvalid,
626 Mask::Values(values) => Validity::Array(values.into_array()),
627 }
628 }
629}
630
631impl IntoArray for Mask {
632 #[inline]
633 fn into_array(self) -> ArrayRef {
634 match self {
635 Self::AllTrue(len) => ConstantArray::new(true, len).into_array(),
636 Self::AllFalse(len) => ConstantArray::new(false, len).into_array(),
637 Self::Values(a) => a.into_array(),
638 }
639 }
640}
641
642impl IntoArray for &MaskValues {
643 #[inline]
644 fn into_array(self) -> ArrayRef {
645 BoolArray::new(self.bit_buffer().clone(), Validity::NonNullable).into_array()
646 }
647}
648
649#[cfg(test)]
650mod tests {
651 use rstest::rstest;
652 use vortex_buffer::Buffer;
653 use vortex_buffer::buffer;
654 use vortex_mask::Mask;
655
656 use crate::ArrayRef;
657 use crate::IntoArray;
658 use crate::VortexSessionExecute;
659 use crate::array_session;
660 use crate::arrays::PrimitiveArray;
661 use crate::dtype::Nullability;
662 use crate::validity::BoolArray;
663 use crate::validity::Validity;
664
665 #[rstest]
666 #[case(Validity::AllValid, 5, &[2, 4], Validity::AllValid, Validity::AllValid)]
667 #[case(
668 Validity::AllValid,
669 5,
670 &[2, 4],
671 Validity::AllInvalid,
672 Validity::Array(BoolArray::from_iter([true, true, false, true, false]).into_array())
673 )]
674 #[case(
675 Validity::AllValid,
676 5,
677 &[2, 4],
678 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
679 Validity::Array(BoolArray::from_iter([true, true, true, true, false]).into_array())
680 )]
681 #[case(
682 Validity::AllInvalid,
683 5,
684 &[2, 4],
685 Validity::AllValid,
686 Validity::Array(BoolArray::from_iter([false, false, true, false, true]).into_array())
687 )]
688 #[case(Validity::AllInvalid, 5, &[2, 4], Validity::AllInvalid, Validity::AllInvalid)]
689 #[case(
690 Validity::AllInvalid,
691 5,
692 &[2, 4],
693 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
694 Validity::Array(BoolArray::from_iter([false, false, true, false, false]).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::AllValid,
701 Validity::Array(BoolArray::from_iter([false, true, true, true, true]).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::AllInvalid,
708 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array())
709 )]
710 #[case(
711 Validity::Array(BoolArray::from_iter([false, true, false, true, false]).into_array()),
712 5,
713 &[2, 4],
714 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
715 Validity::Array(BoolArray::from_iter([false, true, true, true, false]).into_array())
716 )]
717
718 fn patch_validity(
719 #[case] validity: Validity,
720 #[case] len: usize,
721 #[case] positions: &[u64],
722 #[case] patches: Validity,
723 #[case] expected: Validity,
724 ) {
725 let indices =
726 PrimitiveArray::new(Buffer::copy_from(positions), Validity::NonNullable).into_array();
727
728 let mut ctx = array_session().create_execution_ctx();
729
730 assert!(
731 validity
732 .patch(len, 0, &indices, &patches, &mut ctx,)
733 .unwrap()
734 .mask_eq(&expected, len, &mut ctx)
735 .unwrap()
736 );
737 }
738
739 #[test]
740 #[should_panic]
741 fn out_of_bounds_patch() {
742 let mut ctx = array_session().create_execution_ctx();
743 Validity::NonNullable
744 .patch(
745 2,
746 0,
747 &buffer![4].into_array(),
748 &Validity::AllInvalid,
749 &mut ctx,
750 )
751 .unwrap();
752 }
753
754 #[test]
755 #[should_panic]
756 fn into_validity_nullable() {
757 Validity::from_mask(Mask::AllFalse(10), Nullability::NonNullable);
758 }
759
760 #[test]
761 #[should_panic]
762 fn into_validity_nullable_array() {
763 Validity::from_mask(Mask::from_iter(vec![true, false]), Nullability::NonNullable);
764 }
765
766 #[rstest]
767 #[case(
768 Validity::AllValid,
769 PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(),
770 Validity::from_iter(vec![true, false])
771 )]
772 #[case(Validity::AllValid, buffer![0, 1].into_array(), Validity::AllValid)]
773 #[case(
774 Validity::AllValid,
775 PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(),
776 Validity::AllInvalid
777 )]
778 #[case(
779 Validity::NonNullable,
780 PrimitiveArray::new(buffer![0, 1], Validity::from_iter(vec![true, false])).into_array(),
781 Validity::from_iter(vec![true, false])
782 )]
783 #[case(Validity::NonNullable, buffer![0, 1].into_array(), Validity::NonNullable)]
784 #[case(
785 Validity::NonNullable,
786 PrimitiveArray::new(buffer![0, 1], Validity::AllInvalid).into_array(),
787 Validity::AllInvalid
788 )]
789 fn validity_take(
790 #[case] validity: Validity,
791 #[case] indices: ArrayRef,
792 #[case] expected: Validity,
793 ) {
794 let mut ctx = array_session().create_execution_ctx();
795 assert!(
796 validity
797 .take(&indices)
798 .unwrap()
799 .mask_eq(&expected, indices.len(), &mut ctx)
800 .unwrap()
801 );
802 }
803
804 #[rstest]
805 #[case(Validity::NonNullable, Validity::AllValid, true)]
807 #[case(Validity::AllValid, Validity::NonNullable, true)]
808 #[case(Validity::AllValid, Validity::AllInvalid, false)]
809 #[case(Validity::NonNullable, Validity::AllInvalid, false)]
810 #[case(
812 Validity::Array(BoolArray::from_iter([true, true, true]).into_array()),
813 Validity::AllValid,
814 true
815 )]
816 #[case(
817 Validity::NonNullable,
818 Validity::Array(BoolArray::from_iter([true, true, true]).into_array()),
819 true
820 )]
821 #[case(
822 Validity::Array(BoolArray::from_iter([false, false, false]).into_array()),
823 Validity::AllInvalid,
824 true
825 )]
826 #[case(
827 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
828 Validity::AllValid,
829 false
830 )]
831 #[case(
832 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
833 Validity::AllInvalid,
834 false
835 )]
836 fn mask_eq_mixed_variants(
837 #[case] lhs: Validity,
838 #[case] rhs: Validity,
839 #[case] expected: bool,
840 ) -> vortex_error::VortexResult<()> {
841 let mut ctx = array_session().create_execution_ctx();
842 assert_eq!(lhs.mask_eq(&rhs, 3, &mut ctx)?, expected);
843 Ok(())
844 }
845}