1use vortex_buffer::BitBuffer;
23use vortex_dtype::DType;
24use vortex_dtype::Nullability;
25use vortex_dtype::PType;
26use vortex_error::VortexUnwrap;
27use vortex_error::vortex_panic;
28use vortex_mask::Mask;
29
30use crate::Array;
31use crate::IntoArray;
32use crate::arrays::BoolArray;
33use crate::arrays::PrimitiveArray;
34use crate::compute::Operator;
35use crate::compute::and;
36use crate::compute::cast;
37use crate::compute::compare;
38use crate::compute::filter;
39use crate::compute::invert;
40use crate::compute::mask;
41use crate::compute::or;
42use crate::compute::take;
43
44fn test_filter_take_consistency(array: &dyn Array) {
56 let len = array.len();
57 if len == 0 {
58 return;
59 }
60
61 let mask_pattern: BitBuffer = (0..len).map(|i| i % 3 != 1).collect();
63 let mask = Mask::from_buffer(mask_pattern.clone());
64
65 let filtered = filter(array, &mask).vortex_unwrap();
67
68 let indices: Vec<u64> = mask_pattern
70 .iter()
71 .enumerate()
72 .filter_map(|(i, v)| v.then_some(i as u64))
73 .collect();
74 let indices_array = PrimitiveArray::from_iter(indices).into_array();
75
76 let taken = take(array, &indices_array).vortex_unwrap();
78
79 assert_eq!(
81 filtered.len(),
82 taken.len(),
83 "Filter and take should produce arrays of the same length. \
84 Filtered length: {}, Taken length: {}",
85 filtered.len(),
86 taken.len()
87 );
88
89 for i in 0..filtered.len() {
90 let filtered_val = filtered.scalar_at(i);
91 let taken_val = taken.scalar_at(i);
92 assert_eq!(
93 filtered_val, taken_val,
94 "Filter and take produced different values at index {i}. \
95 Filtered value: {filtered_val:?}, Taken value: {taken_val:?}"
96 );
97 }
98}
99
100fn test_double_mask_consistency(array: &dyn Array) {
116 let len = array.len();
117 if len == 0 {
118 return;
119 }
120
121 let mask1: Mask = (0..len).map(|i| i % 3 == 0).collect();
123 let mask2: Mask = (0..len).map(|i| i % 2 == 0).collect();
124
125 let first_masked = mask(array, &mask1).vortex_unwrap();
127 let double_masked = mask(&first_masked, &mask2).vortex_unwrap();
128
129 let combined_pattern: BitBuffer = mask1
131 .to_bit_buffer()
132 .iter()
133 .zip(mask2.to_bit_buffer().iter())
134 .map(|(a, b)| a || b)
135 .collect();
136 let combined_mask = Mask::from_buffer(combined_pattern);
137
138 let directly_masked = mask(array, &combined_mask).vortex_unwrap();
140
141 assert_eq!(
143 double_masked.len(),
144 directly_masked.len(),
145 "Sequential masking and combined masking should produce arrays of the same length. \
146 Sequential length: {}, Combined length: {}",
147 double_masked.len(),
148 directly_masked.len()
149 );
150
151 for i in 0..double_masked.len() {
152 let double_val = double_masked.scalar_at(i);
153 let direct_val = directly_masked.scalar_at(i);
154 assert_eq!(
155 double_val, direct_val,
156 "Sequential masking and combined masking produced different values at index {i}. \
157 Sequential masking value: {double_val:?}, Combined masking value: {direct_val:?}\n\
158 This likely indicates an issue with how masks are composed in the array implementation."
159 );
160 }
161}
162
163fn test_filter_identity(array: &dyn Array) {
177 let len = array.len();
178 if len == 0 {
179 return;
180 }
181
182 let all_true_mask = Mask::new_true(len);
183 let filtered = filter(array, &all_true_mask).vortex_unwrap();
184
185 assert_eq!(
187 filtered.len(),
188 array.len(),
189 "Filtering with all-true mask should preserve array length. \
190 Original length: {}, Filtered length: {}",
191 array.len(),
192 filtered.len()
193 );
194
195 for i in 0..len {
196 let original_val = array.scalar_at(i);
197 let filtered_val = filtered.scalar_at(i);
198 assert_eq!(
199 filtered_val, original_val,
200 "Filtering with all-true mask should preserve all values. \
201 Value at index {i} changed from {original_val:?} to {filtered_val:?}"
202 );
203 }
204}
205
206fn test_mask_identity(array: &dyn Array) {
220 let len = array.len();
221 if len == 0 {
222 return;
223 }
224
225 let all_false_mask = Mask::new_false(len);
226 let masked = mask(array, &all_false_mask).vortex_unwrap();
227
228 assert_eq!(
230 masked.len(),
231 array.len(),
232 "Masking with all-false mask should preserve array length. \
233 Original length: {}, Masked length: {}",
234 array.len(),
235 masked.len()
236 );
237
238 assert!(
239 masked.dtype().is_nullable(),
240 "Mask operation should always produce a nullable array, but dtype is {:?}",
241 masked.dtype()
242 );
243
244 for i in 0..len {
245 let original_val = array.scalar_at(i);
246 let masked_val = masked.scalar_at(i);
247 let expected_val = original_val.clone().into_nullable();
248 assert_eq!(
249 masked_val, expected_val,
250 "Masking with all-false mask should preserve values (as nullable). \
251 Value at index {i}: original = {original_val:?}, masked = {masked_val:?}, expected = {expected_val:?}"
252 );
253 }
254}
255
256fn test_slice_filter_consistency(array: &dyn Array) {
271 let len = array.len();
272 if len < 4 {
273 return; }
275
276 let mut mask_pattern = vec![false; len];
278 mask_pattern[1..4.min(len)].fill(true);
279
280 let mask = Mask::from_iter(mask_pattern);
281 let filtered = filter(array, &mask).vortex_unwrap();
282
283 let sliced = array.slice(1..4.min(len));
285
286 assert_eq!(
287 filtered.len(),
288 sliced.len(),
289 "Filter with contiguous mask and slice should produce same length. \
290 Filtered length: {}, Sliced length: {}",
291 filtered.len(),
292 sliced.len()
293 );
294
295 for i in 0..filtered.len() {
296 let filtered_val = filtered.scalar_at(i);
297 let sliced_val = sliced.scalar_at(i);
298 assert_eq!(
299 filtered_val, sliced_val,
300 "Filter with contiguous mask and slice produced different values at index {i}. \
301 Filtered value: {filtered_val:?}, Sliced value: {sliced_val:?}"
302 );
303 }
304}
305
306fn test_take_slice_consistency(array: &dyn Array) {
320 let len = array.len();
321 if len < 3 {
322 return; }
324
325 let end = 4.min(len);
327 let indices = PrimitiveArray::from_iter((1..end).map(|i| i as u64)).into_array();
328 let taken = take(array, &indices).vortex_unwrap();
329
330 let sliced = array.slice(1..end);
332
333 assert_eq!(
334 taken.len(),
335 sliced.len(),
336 "Take with sequential indices and slice should produce same length. \
337 Taken length: {}, Sliced length: {}",
338 taken.len(),
339 sliced.len()
340 );
341
342 for i in 0..taken.len() {
343 let taken_val = taken.scalar_at(i);
344 let sliced_val = sliced.scalar_at(i);
345 assert_eq!(
346 taken_val, sliced_val,
347 "Take with sequential indices and slice produced different values at index {i}. \
348 Taken value: {taken_val:?}, Sliced value: {sliced_val:?}"
349 );
350 }
351}
352
353fn test_filter_preserves_order(array: &dyn Array) {
355 let len = array.len();
356 if len < 4 {
357 return;
358 }
359
360 let mask_pattern: Vec<bool> = (0..len).map(|i| i == 0 || i == 2 || i == 3).collect();
362 let mask = Mask::from_iter(mask_pattern);
363
364 let filtered = filter(array, &mask).vortex_unwrap();
365
366 assert_eq!(filtered.len(), 3.min(len));
368 if len >= 4 {
369 assert_eq!(filtered.scalar_at(0), array.scalar_at(0));
370 assert_eq!(filtered.scalar_at(1), array.scalar_at(2),);
371 assert_eq!(filtered.scalar_at(2), array.scalar_at(3));
372 }
373}
374
375fn test_take_repeated_indices(array: &dyn Array) {
377 let len = array.len();
378 if len == 0 {
379 return;
380 }
381
382 let indices = PrimitiveArray::from_iter([0u64, 0, 0]).into_array();
384 let taken = take(array, &indices).vortex_unwrap();
385
386 assert_eq!(taken.len(), 3);
387 for i in 0..3 {
388 assert_eq!(taken.scalar_at(i), array.scalar_at(0),);
389 }
390}
391
392fn test_mask_filter_null_consistency(array: &dyn Array) {
394 let len = array.len();
395 if len < 3 {
396 return;
397 }
398
399 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
401 let mask_array = Mask::from_iter(mask_pattern);
402 let masked = mask(array, &mask_array).vortex_unwrap();
403
404 let filter_pattern: Vec<bool> = (0..len).map(|i| i % 2 != 0).collect();
406 let filter_mask = Mask::from_iter(filter_pattern);
407 let filtered = filter(&masked, &filter_mask).vortex_unwrap();
408
409 let direct_filtered = filter(array, &filter_mask).vortex_unwrap();
411
412 assert_eq!(filtered.len(), direct_filtered.len());
413 for i in 0..filtered.len() {
414 assert_eq!(filtered.scalar_at(i), direct_filtered.scalar_at(i));
415 }
416}
417
418fn test_empty_operations_consistency(array: &dyn Array) {
420 let len = array.len();
421
422 let empty_filter = filter(array, &Mask::new_false(len)).vortex_unwrap();
424 assert_eq!(empty_filter.len(), 0);
425 assert_eq!(empty_filter.dtype(), array.dtype());
426
427 let empty_indices = PrimitiveArray::empty::<u64>(Nullability::NonNullable).into_array();
429 let empty_take = take(array, &empty_indices).vortex_unwrap();
430 assert_eq!(empty_take.len(), 0);
431 assert_eq!(empty_take.dtype(), array.dtype());
432
433 if len > 0 {
435 let empty_slice = array.slice(0..0);
436 assert_eq!(empty_slice.len(), 0);
437 assert_eq!(empty_slice.dtype(), array.dtype());
438 }
439}
440
441fn test_take_preserves_properties(array: &dyn Array) {
443 let len = array.len();
444 if len == 0 {
445 return;
446 }
447
448 let indices = PrimitiveArray::from_iter((0..len).map(|i| i as u64)).into_array();
450 let taken = take(array, &indices).vortex_unwrap();
451
452 assert_eq!(taken.len(), array.len());
454 assert_eq!(taken.dtype(), array.dtype());
455 for i in 0..len {
456 assert_eq!(taken.scalar_at(i), array.scalar_at(i),);
457 }
458}
459
460fn test_nullable_indices_consistency(array: &dyn Array) {
478 let len = array.len();
479 if len < 3 {
480 return; }
482
483 let indices = PrimitiveArray::from_option_iter([Some(0u64), None, Some(2u64)]).into_array();
485
486 let taken = take(array, &indices).vortex_unwrap();
487
488 assert_eq!(
490 taken.len(),
491 3,
492 "Take with nullable indices should produce array of length 3, got {}",
493 taken.len()
494 );
495
496 assert!(
497 taken.dtype().is_nullable(),
498 "Take with nullable indices should produce nullable array, but dtype is {:?}",
499 taken.dtype()
500 );
501
502 let expected_0 = array.scalar_at(0).into_nullable();
504 let actual_0 = taken.scalar_at(0);
505 assert_eq!(
506 actual_0, expected_0,
507 "Take with nullable indices: element at position 0 should be from array index 0. \
508 Expected: {expected_0:?}, Actual: {actual_0:?}"
509 );
510
511 let actual_1 = taken.scalar_at(1);
513 assert!(
514 actual_1.is_null(),
515 "Take with nullable indices: element at position 1 should be null, but got {actual_1:?}"
516 );
517
518 let expected_2 = array.scalar_at(2).into_nullable();
520 let actual_2 = taken.scalar_at(2);
521 assert_eq!(
522 actual_2, expected_2,
523 "Take with nullable indices: element at position 2 should be from array index 2. \
524 Expected: {expected_2:?}, Actual: {actual_2:?}"
525 );
526}
527
528fn test_large_array_consistency(array: &dyn Array) {
530 let len = array.len();
531 if len < 1000 {
532 return;
533 }
534
535 let indices: Vec<u64> = (0..len).step_by(10).map(|i| i as u64).collect();
537 let indices_array = PrimitiveArray::from_iter(indices).into_array();
538 let taken = take(array, &indices_array).vortex_unwrap();
539
540 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
542 let mask = Mask::from_iter(mask_pattern);
543 let filtered = filter(array, &mask).vortex_unwrap();
544
545 assert_eq!(taken.len(), filtered.len());
547 for i in 0..taken.len() {
548 assert_eq!(taken.scalar_at(i), filtered.scalar_at(i),);
549 }
550}
551
552fn test_comparison_inverse_consistency(array: &dyn Array) {
569 let len = array.len();
570 if len == 0 {
571 return;
572 }
573
574 match array.dtype() {
576 DType::Null | DType::Extension(_) | DType::Struct(..) | DType::List(..) => return,
577 _ => {}
578 }
579
580 let test_scalar = if len == 0 {
582 return;
583 } else {
584 array.scalar_at(len / 2)
585 };
586
587 let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
589 if let (Ok(eq_result), Ok(neq_result)) = (
590 compare(array, const_array.as_ref(), Operator::Eq),
591 compare(array, const_array.as_ref(), Operator::NotEq),
592 ) {
593 let inverted_eq = invert(&eq_result).vortex_unwrap();
594
595 assert_eq!(
596 inverted_eq.len(),
597 neq_result.len(),
598 "Inverted Eq should have same length as NotEq"
599 );
600
601 for i in 0..inverted_eq.len() {
602 let inv_val = inverted_eq.scalar_at(i);
603 let neq_val = neq_result.scalar_at(i);
604 assert_eq!(
605 inv_val, neq_val,
606 "At index {i}: NOT(Eq) should equal NotEq. \
607 NOT(Eq) = {inv_val:?}, NotEq = {neq_val:?}"
608 );
609 }
610 }
611
612 if let (Ok(gt_result), Ok(lte_result)) = (
614 compare(array, const_array.as_ref(), Operator::Gt),
615 compare(array, const_array.as_ref(), Operator::Lte),
616 ) {
617 let inverted_gt = invert(>_result).vortex_unwrap();
618
619 for i in 0..inverted_gt.len() {
620 let inv_val = inverted_gt.scalar_at(i);
621 let lte_val = lte_result.scalar_at(i);
622 assert_eq!(
623 inv_val, lte_val,
624 "At index {i}: NOT(Gt) should equal Lte. \
625 NOT(Gt) = {inv_val:?}, Lte = {lte_val:?}"
626 );
627 }
628 }
629
630 if let (Ok(lt_result), Ok(gte_result)) = (
632 compare(array, const_array.as_ref(), Operator::Lt),
633 compare(array, const_array.as_ref(), Operator::Gte),
634 ) {
635 let inverted_lt = invert(<_result).vortex_unwrap();
636
637 for i in 0..inverted_lt.len() {
638 let inv_val = inverted_lt.scalar_at(i);
639 let gte_val = gte_result.scalar_at(i);
640 assert_eq!(
641 inv_val, gte_val,
642 "At index {i}: NOT(Lt) should equal Gte. \
643 NOT(Lt) = {inv_val:?}, Gte = {gte_val:?}"
644 );
645 }
646 }
647}
648
649fn test_comparison_symmetry_consistency(array: &dyn Array) {
665 let len = array.len();
666 if len == 0 {
667 return;
668 }
669
670 match array.dtype() {
672 DType::Null | DType::Extension(_) | DType::Struct(..) | DType::List(..) => return,
673 _ => {}
674 }
675
676 let test_scalar = if len == 2 {
678 return;
679 } else {
680 array.scalar_at(len / 2)
681 };
682
683 let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
685
686 if let (Ok(arr_gt_scalar), Ok(scalar_lt_arr)) = (
688 compare(array, const_array.as_ref(), Operator::Gt),
689 compare(const_array.as_ref(), array, Operator::Lt),
690 ) {
691 assert_eq!(
692 arr_gt_scalar.len(),
693 scalar_lt_arr.len(),
694 "Symmetric comparisons should have same length"
695 );
696
697 for i in 0..arr_gt_scalar.len() {
698 let arr_gt = arr_gt_scalar.scalar_at(i);
699 let scalar_lt = scalar_lt_arr.scalar_at(i);
700 assert_eq!(
701 arr_gt, scalar_lt,
702 "At index {i}: (array > scalar) should equal (scalar < array). \
703 array > scalar = {arr_gt:?}, scalar < array = {scalar_lt:?}"
704 );
705 }
706 }
707
708 if let (Ok(arr_eq_scalar), Ok(scalar_eq_arr)) = (
710 compare(array, const_array.as_ref(), Operator::Eq),
711 compare(const_array.as_ref(), array, Operator::Eq),
712 ) {
713 for i in 0..arr_eq_scalar.len() {
714 let arr_eq = arr_eq_scalar.scalar_at(i);
715 let scalar_eq = scalar_eq_arr.scalar_at(i);
716 assert_eq!(
717 arr_eq, scalar_eq,
718 "At index {i}: (array == scalar) should equal (scalar == array). \
719 array == scalar = {arr_eq:?}, scalar == array = {scalar_eq:?}"
720 );
721 }
722 }
723}
724
725fn test_boolean_demorgan_consistency(array: &dyn Array) {
742 if !matches!(array.dtype(), DType::Bool(_)) {
743 return;
744 }
745
746 let mask = {
747 let mask_pattern: Vec<bool> = (0..array.len()).map(|i| i % 3 == 0).collect();
748 BoolArray::from_iter(mask_pattern)
749 };
750 let mask = mask.as_ref();
751
752 if let (Ok(a_and_b), Ok(not_a), Ok(not_b)) = (and(array, mask), invert(array), invert(mask)) {
754 let not_a_and_b = invert(&a_and_b).vortex_unwrap();
755 let not_a_or_not_b = or(¬_a, ¬_b).vortex_unwrap();
756
757 assert_eq!(
758 not_a_and_b.len(),
759 not_a_or_not_b.len(),
760 "De Morgan's law results should have same length"
761 );
762
763 for i in 0..not_a_and_b.len() {
764 let left = not_a_and_b.scalar_at(i);
765 let right = not_a_or_not_b.scalar_at(i);
766 assert_eq!(
767 left, right,
768 "De Morgan's first law failed at index {i}: \
769 NOT(A AND B) = {left:?}, (NOT A) OR (NOT B) = {right:?}"
770 );
771 }
772 }
773
774 if let (Ok(a_or_b), Ok(not_a), Ok(not_b)) = (or(array, mask), invert(array), invert(mask)) {
776 let not_a_or_b = invert(&a_or_b).vortex_unwrap();
777 let not_a_and_not_b = and(¬_a, ¬_b).vortex_unwrap();
778
779 for i in 0..not_a_or_b.len() {
780 let left = not_a_or_b.scalar_at(i);
781 let right = not_a_and_not_b.scalar_at(i);
782 assert_eq!(
783 left, right,
784 "De Morgan's second law failed at index {i}: \
785 NOT(A OR B) = {left:?}, (NOT A) AND (NOT B) = {right:?}"
786 );
787 }
788 }
789}
790
791fn test_slice_aggregate_consistency(array: &dyn Array) {
807 use vortex_dtype::DType;
808
809 use crate::compute::min_max;
810 use crate::compute::nan_count;
811 use crate::compute::sum;
812
813 let len = array.len();
814 if len < 5 {
815 return; }
817
818 let start = 1;
820 let end = (len - 1).min(start + 10); let sliced = array.slice(start..end);
824 let canonical = array.to_canonical();
825 let canonical_sliced = canonical.as_ref().slice(start..end);
826
827 assert_eq!(
829 sliced.invalid_count(),
830 canonical_sliced.invalid_count(),
831 "null_count on sliced array should match canonical. \
832 Sliced: {}, Canonical: {}",
833 sliced.invalid_count(),
834 canonical_sliced.invalid_count()
835 );
836
837 if !matches!(array.dtype(), DType::Primitive(..)) {
839 return;
840 }
841
842 if let (Ok(slice_sum), Ok(canonical_sum)) = (sum(&sliced), sum(&canonical_sliced)) {
843 assert_eq!(
845 slice_sum, canonical_sum,
846 "sum on sliced array should match canonical. \
847 Sliced: {slice_sum:?}, Canonical: {canonical_sum:?}"
848 );
849 }
850
851 if let (Ok(slice_minmax), Ok(canonical_minmax)) = (min_max(&sliced), min_max(&canonical_sliced))
853 {
854 match (slice_minmax, canonical_minmax) {
855 (Some(s_result), Some(c_result)) => {
856 assert_eq!(
857 s_result.min, c_result.min,
858 "min on sliced array should match canonical. \
859 Sliced: {:?}, Canonical: {:?}",
860 s_result.min, c_result.min
861 );
862 assert_eq!(
863 s_result.max, c_result.max,
864 "max on sliced array should match canonical. \
865 Sliced: {:?}, Canonical: {:?}",
866 s_result.max, c_result.max
867 );
868 }
869 (None, None) => {} _ => vortex_panic!("min_max results don't match"),
871 }
872 }
873
874 if array.dtype().is_float()
876 && let (Ok(slice_nan_count), Ok(canonical_nan_count)) =
877 (nan_count(&sliced), nan_count(&canonical_sliced))
878 {
879 assert_eq!(
880 slice_nan_count, canonical_nan_count,
881 "nan_count on sliced array should match canonical. \
882 Sliced: {slice_nan_count}, Canonical: {canonical_nan_count}"
883 );
884 }
885}
886
887fn test_cast_slice_consistency(array: &dyn Array) {
903 let len = array.len();
904 if len < 5 {
905 return; }
907
908 let start = 2;
910 let end = 7.min(len - 2).max(start + 1); let canonical = array.to_canonical();
914
915 let target_dtypes = match array.dtype() {
917 DType::Null => vec![],
918 DType::Bool(nullability) => vec![
919 DType::Primitive(PType::U8, *nullability),
920 DType::Primitive(PType::I32, *nullability),
921 ],
922 DType::Primitive(ptype, nullability) => {
923 let mut targets = vec![];
924 let opposite_nullability = match nullability {
926 Nullability::NonNullable => Nullability::Nullable,
927 Nullability::Nullable => Nullability::NonNullable,
928 };
929 targets.push(DType::Primitive(*ptype, opposite_nullability));
930
931 match ptype {
933 PType::U8 => {
934 targets.push(DType::Primitive(PType::U16, *nullability));
935 targets.push(DType::Primitive(PType::I16, *nullability));
936 }
937 PType::U16 => {
938 targets.push(DType::Primitive(PType::U32, *nullability));
939 targets.push(DType::Primitive(PType::I32, *nullability));
940 }
941 PType::U32 => {
942 targets.push(DType::Primitive(PType::U64, *nullability));
943 targets.push(DType::Primitive(PType::I64, *nullability));
944 }
945 PType::U64 => {
946 targets.push(DType::Primitive(PType::F64, *nullability));
947 }
948 PType::I8 => {
949 targets.push(DType::Primitive(PType::I16, *nullability));
950 targets.push(DType::Primitive(PType::F32, *nullability));
951 }
952 PType::I16 => {
953 targets.push(DType::Primitive(PType::I32, *nullability));
954 targets.push(DType::Primitive(PType::F32, *nullability));
955 }
956 PType::I32 => {
957 targets.push(DType::Primitive(PType::I64, *nullability));
958 targets.push(DType::Primitive(PType::F64, *nullability));
959 }
960 PType::I64 => {
961 targets.push(DType::Primitive(PType::F64, *nullability));
962 }
963 PType::F16 => {
964 targets.push(DType::Primitive(PType::F32, *nullability));
965 }
966 PType::F32 => {
967 targets.push(DType::Primitive(PType::F64, *nullability));
968 targets.push(DType::Primitive(PType::I32, *nullability));
969 }
970 PType::F64 => {
971 targets.push(DType::Primitive(PType::I64, *nullability));
972 }
973 }
974 targets
975 }
976 DType::Utf8(nullability) => {
977 let opposite = match nullability {
978 Nullability::NonNullable => Nullability::Nullable,
979 Nullability::Nullable => Nullability::NonNullable,
980 };
981 vec![DType::Utf8(opposite), DType::Binary(*nullability)]
982 }
983 DType::Binary(nullability) => {
984 let opposite = match nullability {
985 Nullability::NonNullable => Nullability::Nullable,
986 Nullability::Nullable => Nullability::NonNullable,
987 };
988 vec![
989 DType::Binary(opposite),
990 DType::Utf8(*nullability), ]
992 }
993 DType::Decimal(decimal_type, nullability) => {
994 let opposite = match nullability {
995 Nullability::NonNullable => Nullability::Nullable,
996 Nullability::Nullable => Nullability::NonNullable,
997 };
998 vec![DType::Decimal(*decimal_type, opposite)]
999 }
1000 DType::Struct(fields, nullability) => {
1001 let opposite = match nullability {
1002 Nullability::NonNullable => Nullability::Nullable,
1003 Nullability::Nullable => Nullability::NonNullable,
1004 };
1005 vec![DType::Struct(fields.clone(), opposite)]
1006 }
1007 DType::List(element_type, nullability) => {
1008 let opposite = match nullability {
1009 Nullability::NonNullable => Nullability::Nullable,
1010 Nullability::Nullable => Nullability::NonNullable,
1011 };
1012 vec![DType::List(element_type.clone(), opposite)]
1013 }
1014 DType::FixedSizeList(element_type, list_size, nullability) => {
1015 let opposite = match nullability {
1016 Nullability::NonNullable => Nullability::Nullable,
1017 Nullability::Nullable => Nullability::NonNullable,
1018 };
1019 vec![DType::FixedSizeList(
1020 element_type.clone(),
1021 *list_size,
1022 opposite,
1023 )]
1024 }
1025 DType::Extension(_) => vec![], };
1027
1028 for target_dtype in target_dtypes {
1030 let sliced = array.slice(start..end);
1032
1033 let slice_then_cast = match cast(&sliced, &target_dtype) {
1035 Ok(result) => result,
1036 Err(_) => continue, };
1038
1039 assert_eq!(
1041 slice_then_cast.len(),
1042 end - start,
1043 "Sliced and casted array should have length {}, but has {}",
1044 end - start,
1045 slice_then_cast.len()
1046 );
1047
1048 for i in 0..slice_then_cast.len() {
1050 let slice_cast_val = slice_then_cast.scalar_at(i);
1051
1052 let canonical_val = canonical.as_ref().scalar_at(start + i);
1054
1055 let expected_val = match canonical_val.cast(&target_dtype) {
1057 Ok(val) => val,
1058 Err(_) => {
1059 break;
1062 }
1063 };
1064
1065 assert_eq!(
1066 slice_cast_val,
1067 expected_val,
1068 "Cast of sliced array produced incorrect value at index {i}. \
1069 Got: {slice_cast_val:?}, Expected: {expected_val:?} \
1070 (canonical value at index {}: {canonical_val:?})\n\
1071 This likely indicates the array encoding doesn't preserve offset information during cast.",
1072 start + i
1073 );
1074 }
1075
1076 let casted = match cast(array, &target_dtype) {
1078 Ok(result) => result,
1079 Err(_) => continue, };
1081 let cast_then_slice = casted.slice(start..end);
1082
1083 assert_eq!(
1085 slice_then_cast.len(),
1086 cast_then_slice.len(),
1087 "Slice-then-cast and cast-then-slice should produce arrays of the same length"
1088 );
1089
1090 for i in 0..slice_then_cast.len() {
1091 let slice_cast_val = slice_then_cast.scalar_at(i);
1092 let cast_slice_val = cast_then_slice.scalar_at(i);
1093 assert_eq!(
1094 slice_cast_val, cast_slice_val,
1095 "Slice-then-cast and cast-then-slice produced different values at index {i}. \
1096 Slice-then-cast: {slice_cast_val:?}, Cast-then-slice: {cast_slice_val:?}"
1097 );
1098 }
1099 }
1100}
1101
1102pub fn test_array_consistency(array: &dyn Array) {
1144 test_filter_take_consistency(array);
1146 test_double_mask_consistency(array);
1147 test_slice_filter_consistency(array);
1148 test_take_slice_consistency(array);
1149 test_cast_slice_consistency(array);
1150
1151 test_boolean_demorgan_consistency(array);
1153
1154 test_comparison_inverse_consistency(array);
1156 test_comparison_symmetry_consistency(array);
1157
1158 test_slice_aggregate_consistency(array);
1160
1161 test_filter_identity(array);
1163 test_mask_identity(array);
1164 test_take_preserves_properties(array);
1165
1166 test_filter_preserves_order(array);
1168 test_take_repeated_indices(array);
1169
1170 test_mask_filter_null_consistency(array);
1172 test_nullable_indices_consistency(array);
1173
1174 test_empty_operations_consistency(array);
1176 test_large_array_consistency(array);
1177}