1use vortex_buffer::BitBuffer;
23use vortex_dtype::{DType, Nullability, PType};
24use vortex_error::{VortexUnwrap, vortex_panic};
25use vortex_mask::Mask;
26
27use crate::arrays::{BoolArray, PrimitiveArray};
28use crate::compute::{Operator, and, cast, compare, filter, invert, mask, or, take};
29use crate::{Array, IntoArray};
30
31fn test_filter_take_consistency(array: &dyn Array) {
43 let len = array.len();
44 if len == 0 {
45 return;
46 }
47
48 let mask_pattern: BitBuffer = (0..len).map(|i| i % 3 != 1).collect();
50 let mask = Mask::from_buffer(mask_pattern.clone());
51
52 let filtered = filter(array, &mask).vortex_unwrap();
54
55 let indices: Vec<u64> = mask_pattern
57 .iter()
58 .enumerate()
59 .filter_map(|(i, v)| v.then_some(i as u64))
60 .collect();
61 let indices_array = PrimitiveArray::from_iter(indices).into_array();
62
63 let taken = take(array, &indices_array).vortex_unwrap();
65
66 assert_eq!(
68 filtered.len(),
69 taken.len(),
70 "Filter and take should produce arrays of the same length. \
71 Filtered length: {}, Taken length: {}",
72 filtered.len(),
73 taken.len()
74 );
75
76 for i in 0..filtered.len() {
77 let filtered_val = filtered.scalar_at(i);
78 let taken_val = taken.scalar_at(i);
79 assert_eq!(
80 filtered_val, taken_val,
81 "Filter and take produced different values at index {i}. \
82 Filtered value: {filtered_val:?}, Taken value: {taken_val:?}"
83 );
84 }
85}
86
87fn test_double_mask_consistency(array: &dyn Array) {
103 let len = array.len();
104 if len == 0 {
105 return;
106 }
107
108 let mask1: Mask = (0..len).map(|i| i % 3 == 0).collect();
110 let mask2: Mask = (0..len).map(|i| i % 2 == 0).collect();
111
112 let first_masked = mask(array, &mask1).vortex_unwrap();
114 let double_masked = mask(&first_masked, &mask2).vortex_unwrap();
115
116 let combined_pattern: BitBuffer = mask1
118 .to_bit_buffer()
119 .iter()
120 .zip(mask2.to_bit_buffer().iter())
121 .map(|(a, b)| a || b)
122 .collect();
123 let combined_mask = Mask::from_buffer(combined_pattern);
124
125 let directly_masked = mask(array, &combined_mask).vortex_unwrap();
127
128 assert_eq!(
130 double_masked.len(),
131 directly_masked.len(),
132 "Sequential masking and combined masking should produce arrays of the same length. \
133 Sequential length: {}, Combined length: {}",
134 double_masked.len(),
135 directly_masked.len()
136 );
137
138 for i in 0..double_masked.len() {
139 let double_val = double_masked.scalar_at(i);
140 let direct_val = directly_masked.scalar_at(i);
141 assert_eq!(
142 double_val, direct_val,
143 "Sequential masking and combined masking produced different values at index {i}. \
144 Sequential masking value: {double_val:?}, Combined masking value: {direct_val:?}\n\
145 This likely indicates an issue with how masks are composed in the array implementation."
146 );
147 }
148}
149
150fn test_filter_identity(array: &dyn Array) {
164 let len = array.len();
165 if len == 0 {
166 return;
167 }
168
169 let all_true_mask = Mask::new_true(len);
170 let filtered = filter(array, &all_true_mask).vortex_unwrap();
171
172 assert_eq!(
174 filtered.len(),
175 array.len(),
176 "Filtering with all-true mask should preserve array length. \
177 Original length: {}, Filtered length: {}",
178 array.len(),
179 filtered.len()
180 );
181
182 for i in 0..len {
183 let original_val = array.scalar_at(i);
184 let filtered_val = filtered.scalar_at(i);
185 assert_eq!(
186 filtered_val, original_val,
187 "Filtering with all-true mask should preserve all values. \
188 Value at index {i} changed from {original_val:?} to {filtered_val:?}"
189 );
190 }
191}
192
193fn test_mask_identity(array: &dyn Array) {
207 let len = array.len();
208 if len == 0 {
209 return;
210 }
211
212 let all_false_mask = Mask::new_false(len);
213 let masked = mask(array, &all_false_mask).vortex_unwrap();
214
215 assert_eq!(
217 masked.len(),
218 array.len(),
219 "Masking with all-false mask should preserve array length. \
220 Original length: {}, Masked length: {}",
221 array.len(),
222 masked.len()
223 );
224
225 assert!(
226 masked.dtype().is_nullable(),
227 "Mask operation should always produce a nullable array, but dtype is {:?}",
228 masked.dtype()
229 );
230
231 for i in 0..len {
232 let original_val = array.scalar_at(i);
233 let masked_val = masked.scalar_at(i);
234 let expected_val = original_val.clone().into_nullable();
235 assert_eq!(
236 masked_val, expected_val,
237 "Masking with all-false mask should preserve values (as nullable). \
238 Value at index {i}: original = {original_val:?}, masked = {masked_val:?}, expected = {expected_val:?}"
239 );
240 }
241}
242
243fn test_slice_filter_consistency(array: &dyn Array) {
258 let len = array.len();
259 if len < 4 {
260 return; }
262
263 let mut mask_pattern = vec![false; len];
265 mask_pattern[1..4.min(len)].fill(true);
266
267 let mask = Mask::from_iter(mask_pattern);
268 let filtered = filter(array, &mask).vortex_unwrap();
269
270 let sliced = array.slice(1..4.min(len));
272
273 assert_eq!(
274 filtered.len(),
275 sliced.len(),
276 "Filter with contiguous mask and slice should produce same length. \
277 Filtered length: {}, Sliced length: {}",
278 filtered.len(),
279 sliced.len()
280 );
281
282 for i in 0..filtered.len() {
283 let filtered_val = filtered.scalar_at(i);
284 let sliced_val = sliced.scalar_at(i);
285 assert_eq!(
286 filtered_val, sliced_val,
287 "Filter with contiguous mask and slice produced different values at index {i}. \
288 Filtered value: {filtered_val:?}, Sliced value: {sliced_val:?}"
289 );
290 }
291}
292
293fn test_take_slice_consistency(array: &dyn Array) {
307 let len = array.len();
308 if len < 3 {
309 return; }
311
312 let end = 4.min(len);
314 let indices = PrimitiveArray::from_iter((1..end).map(|i| i as u64)).into_array();
315 let taken = take(array, &indices).vortex_unwrap();
316
317 let sliced = array.slice(1..end);
319
320 assert_eq!(
321 taken.len(),
322 sliced.len(),
323 "Take with sequential indices and slice should produce same length. \
324 Taken length: {}, Sliced length: {}",
325 taken.len(),
326 sliced.len()
327 );
328
329 for i in 0..taken.len() {
330 let taken_val = taken.scalar_at(i);
331 let sliced_val = sliced.scalar_at(i);
332 assert_eq!(
333 taken_val, sliced_val,
334 "Take with sequential indices and slice produced different values at index {i}. \
335 Taken value: {taken_val:?}, Sliced value: {sliced_val:?}"
336 );
337 }
338}
339
340fn test_filter_preserves_order(array: &dyn Array) {
342 let len = array.len();
343 if len < 4 {
344 return;
345 }
346
347 let mask_pattern: Vec<bool> = (0..len).map(|i| i == 0 || i == 2 || i == 3).collect();
349 let mask = Mask::from_iter(mask_pattern);
350
351 let filtered = filter(array, &mask).vortex_unwrap();
352
353 assert_eq!(filtered.len(), 3.min(len));
355 if len >= 4 {
356 assert_eq!(filtered.scalar_at(0), array.scalar_at(0));
357 assert_eq!(filtered.scalar_at(1), array.scalar_at(2),);
358 assert_eq!(filtered.scalar_at(2), array.scalar_at(3));
359 }
360}
361
362fn test_take_repeated_indices(array: &dyn Array) {
364 let len = array.len();
365 if len == 0 {
366 return;
367 }
368
369 let indices = PrimitiveArray::from_iter([0u64, 0, 0]).into_array();
371 let taken = take(array, &indices).vortex_unwrap();
372
373 assert_eq!(taken.len(), 3);
374 for i in 0..3 {
375 assert_eq!(taken.scalar_at(i), array.scalar_at(0),);
376 }
377}
378
379fn test_mask_filter_null_consistency(array: &dyn Array) {
381 let len = array.len();
382 if len < 3 {
383 return;
384 }
385
386 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
388 let mask_array = Mask::from_iter(mask_pattern);
389 let masked = mask(array, &mask_array).vortex_unwrap();
390
391 let filter_pattern: Vec<bool> = (0..len).map(|i| i % 2 != 0).collect();
393 let filter_mask = Mask::from_iter(filter_pattern);
394 let filtered = filter(&masked, &filter_mask).vortex_unwrap();
395
396 let direct_filtered = filter(array, &filter_mask).vortex_unwrap();
398
399 assert_eq!(filtered.len(), direct_filtered.len());
400 for i in 0..filtered.len() {
401 assert_eq!(filtered.scalar_at(i), direct_filtered.scalar_at(i));
402 }
403}
404
405fn test_empty_operations_consistency(array: &dyn Array) {
407 let len = array.len();
408
409 let empty_filter = filter(array, &Mask::new_false(len)).vortex_unwrap();
411 assert_eq!(empty_filter.len(), 0);
412 assert_eq!(empty_filter.dtype(), array.dtype());
413
414 let empty_indices = PrimitiveArray::empty::<u64>(Nullability::NonNullable).into_array();
416 let empty_take = take(array, &empty_indices).vortex_unwrap();
417 assert_eq!(empty_take.len(), 0);
418 assert_eq!(empty_take.dtype(), array.dtype());
419
420 if len > 0 {
422 let empty_slice = array.slice(0..0);
423 assert_eq!(empty_slice.len(), 0);
424 assert_eq!(empty_slice.dtype(), array.dtype());
425 }
426}
427
428fn test_take_preserves_properties(array: &dyn Array) {
430 let len = array.len();
431 if len == 0 {
432 return;
433 }
434
435 let indices = PrimitiveArray::from_iter((0..len).map(|i| i as u64)).into_array();
437 let taken = take(array, &indices).vortex_unwrap();
438
439 assert_eq!(taken.len(), array.len());
441 assert_eq!(taken.dtype(), array.dtype());
442 for i in 0..len {
443 assert_eq!(taken.scalar_at(i), array.scalar_at(i),);
444 }
445}
446
447fn test_nullable_indices_consistency(array: &dyn Array) {
465 let len = array.len();
466 if len < 3 {
467 return; }
469
470 let indices = PrimitiveArray::from_option_iter([Some(0u64), None, Some(2u64)]).into_array();
472
473 let taken = take(array, &indices).vortex_unwrap();
474
475 assert_eq!(
477 taken.len(),
478 3,
479 "Take with nullable indices should produce array of length 3, got {}",
480 taken.len()
481 );
482
483 assert!(
484 taken.dtype().is_nullable(),
485 "Take with nullable indices should produce nullable array, but dtype is {:?}",
486 taken.dtype()
487 );
488
489 let expected_0 = array.scalar_at(0).into_nullable();
491 let actual_0 = taken.scalar_at(0);
492 assert_eq!(
493 actual_0, expected_0,
494 "Take with nullable indices: element at position 0 should be from array index 0. \
495 Expected: {expected_0:?}, Actual: {actual_0:?}"
496 );
497
498 let actual_1 = taken.scalar_at(1);
500 assert!(
501 actual_1.is_null(),
502 "Take with nullable indices: element at position 1 should be null, but got {actual_1:?}"
503 );
504
505 let expected_2 = array.scalar_at(2).into_nullable();
507 let actual_2 = taken.scalar_at(2);
508 assert_eq!(
509 actual_2, expected_2,
510 "Take with nullable indices: element at position 2 should be from array index 2. \
511 Expected: {expected_2:?}, Actual: {actual_2:?}"
512 );
513}
514
515fn test_large_array_consistency(array: &dyn Array) {
517 let len = array.len();
518 if len < 1000 {
519 return;
520 }
521
522 let indices: Vec<u64> = (0..len).step_by(10).map(|i| i as u64).collect();
524 let indices_array = PrimitiveArray::from_iter(indices).into_array();
525 let taken = take(array, &indices_array).vortex_unwrap();
526
527 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
529 let mask = Mask::from_iter(mask_pattern);
530 let filtered = filter(array, &mask).vortex_unwrap();
531
532 assert_eq!(taken.len(), filtered.len());
534 for i in 0..taken.len() {
535 assert_eq!(taken.scalar_at(i), filtered.scalar_at(i),);
536 }
537}
538
539fn test_comparison_inverse_consistency(array: &dyn Array) {
556 let len = array.len();
557 if len == 0 {
558 return;
559 }
560
561 match array.dtype() {
563 DType::Null | DType::Extension(_) | DType::Struct(..) | DType::List(..) => return,
564 _ => {}
565 }
566
567 let test_scalar = if len == 0 {
569 return;
570 } else {
571 array.scalar_at(len / 2)
572 };
573
574 let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
576 if let (Ok(eq_result), Ok(neq_result)) = (
577 compare(array, const_array.as_ref(), Operator::Eq),
578 compare(array, const_array.as_ref(), Operator::NotEq),
579 ) {
580 let inverted_eq = invert(&eq_result).vortex_unwrap();
581
582 assert_eq!(
583 inverted_eq.len(),
584 neq_result.len(),
585 "Inverted Eq should have same length as NotEq"
586 );
587
588 for i in 0..inverted_eq.len() {
589 let inv_val = inverted_eq.scalar_at(i);
590 let neq_val = neq_result.scalar_at(i);
591 assert_eq!(
592 inv_val, neq_val,
593 "At index {i}: NOT(Eq) should equal NotEq. \
594 NOT(Eq) = {inv_val:?}, NotEq = {neq_val:?}"
595 );
596 }
597 }
598
599 if let (Ok(gt_result), Ok(lte_result)) = (
601 compare(array, const_array.as_ref(), Operator::Gt),
602 compare(array, const_array.as_ref(), Operator::Lte),
603 ) {
604 let inverted_gt = invert(>_result).vortex_unwrap();
605
606 for i in 0..inverted_gt.len() {
607 let inv_val = inverted_gt.scalar_at(i);
608 let lte_val = lte_result.scalar_at(i);
609 assert_eq!(
610 inv_val, lte_val,
611 "At index {i}: NOT(Gt) should equal Lte. \
612 NOT(Gt) = {inv_val:?}, Lte = {lte_val:?}"
613 );
614 }
615 }
616
617 if let (Ok(lt_result), Ok(gte_result)) = (
619 compare(array, const_array.as_ref(), Operator::Lt),
620 compare(array, const_array.as_ref(), Operator::Gte),
621 ) {
622 let inverted_lt = invert(<_result).vortex_unwrap();
623
624 for i in 0..inverted_lt.len() {
625 let inv_val = inverted_lt.scalar_at(i);
626 let gte_val = gte_result.scalar_at(i);
627 assert_eq!(
628 inv_val, gte_val,
629 "At index {i}: NOT(Lt) should equal Gte. \
630 NOT(Lt) = {inv_val:?}, Gte = {gte_val:?}"
631 );
632 }
633 }
634}
635
636fn test_comparison_symmetry_consistency(array: &dyn Array) {
652 let len = array.len();
653 if len == 0 {
654 return;
655 }
656
657 match array.dtype() {
659 DType::Null | DType::Extension(_) | DType::Struct(..) | DType::List(..) => return,
660 _ => {}
661 }
662
663 let test_scalar = if len == 2 {
665 return;
666 } else {
667 array.scalar_at(len / 2)
668 };
669
670 let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
672
673 if let (Ok(arr_gt_scalar), Ok(scalar_lt_arr)) = (
675 compare(array, const_array.as_ref(), Operator::Gt),
676 compare(const_array.as_ref(), array, Operator::Lt),
677 ) {
678 assert_eq!(
679 arr_gt_scalar.len(),
680 scalar_lt_arr.len(),
681 "Symmetric comparisons should have same length"
682 );
683
684 for i in 0..arr_gt_scalar.len() {
685 let arr_gt = arr_gt_scalar.scalar_at(i);
686 let scalar_lt = scalar_lt_arr.scalar_at(i);
687 assert_eq!(
688 arr_gt, scalar_lt,
689 "At index {i}: (array > scalar) should equal (scalar < array). \
690 array > scalar = {arr_gt:?}, scalar < array = {scalar_lt:?}"
691 );
692 }
693 }
694
695 if let (Ok(arr_eq_scalar), Ok(scalar_eq_arr)) = (
697 compare(array, const_array.as_ref(), Operator::Eq),
698 compare(const_array.as_ref(), array, Operator::Eq),
699 ) {
700 for i in 0..arr_eq_scalar.len() {
701 let arr_eq = arr_eq_scalar.scalar_at(i);
702 let scalar_eq = scalar_eq_arr.scalar_at(i);
703 assert_eq!(
704 arr_eq, scalar_eq,
705 "At index {i}: (array == scalar) should equal (scalar == array). \
706 array == scalar = {arr_eq:?}, scalar == array = {scalar_eq:?}"
707 );
708 }
709 }
710}
711
712fn test_boolean_demorgan_consistency(array: &dyn Array) {
729 if !matches!(array.dtype(), DType::Bool(_)) {
730 return;
731 }
732
733 let mask = {
734 let mask_pattern: Vec<bool> = (0..array.len()).map(|i| i % 3 == 0).collect();
735 BoolArray::from_iter(mask_pattern)
736 };
737 let mask = mask.as_ref();
738
739 if let (Ok(a_and_b), Ok(not_a), Ok(not_b)) = (and(array, mask), invert(array), invert(mask)) {
741 let not_a_and_b = invert(&a_and_b).vortex_unwrap();
742 let not_a_or_not_b = or(¬_a, ¬_b).vortex_unwrap();
743
744 assert_eq!(
745 not_a_and_b.len(),
746 not_a_or_not_b.len(),
747 "De Morgan's law results should have same length"
748 );
749
750 for i in 0..not_a_and_b.len() {
751 let left = not_a_and_b.scalar_at(i);
752 let right = not_a_or_not_b.scalar_at(i);
753 assert_eq!(
754 left, right,
755 "De Morgan's first law failed at index {i}: \
756 NOT(A AND B) = {left:?}, (NOT A) OR (NOT B) = {right:?}"
757 );
758 }
759 }
760
761 if let (Ok(a_or_b), Ok(not_a), Ok(not_b)) = (or(array, mask), invert(array), invert(mask)) {
763 let not_a_or_b = invert(&a_or_b).vortex_unwrap();
764 let not_a_and_not_b = and(¬_a, ¬_b).vortex_unwrap();
765
766 for i in 0..not_a_or_b.len() {
767 let left = not_a_or_b.scalar_at(i);
768 let right = not_a_and_not_b.scalar_at(i);
769 assert_eq!(
770 left, right,
771 "De Morgan's second law failed at index {i}: \
772 NOT(A OR B) = {left:?}, (NOT A) AND (NOT B) = {right:?}"
773 );
774 }
775 }
776}
777
778fn test_slice_aggregate_consistency(array: &dyn Array) {
794 use vortex_dtype::DType;
795
796 use crate::compute::{min_max, nan_count, sum};
797
798 let len = array.len();
799 if len < 5 {
800 return; }
802
803 let start = 1;
805 let end = (len - 1).min(start + 10); let sliced = array.slice(start..end);
809 let canonical = array.to_canonical();
810 let canonical_sliced = canonical.as_ref().slice(start..end);
811
812 assert_eq!(
814 sliced.invalid_count(),
815 canonical_sliced.invalid_count(),
816 "null_count on sliced array should match canonical. \
817 Sliced: {}, Canonical: {}",
818 sliced.invalid_count(),
819 canonical_sliced.invalid_count()
820 );
821
822 if !matches!(array.dtype(), DType::Primitive(..)) {
824 return;
825 }
826
827 if let (Ok(slice_sum), Ok(canonical_sum)) = (sum(&sliced), sum(&canonical_sliced)) {
828 assert_eq!(
830 slice_sum, canonical_sum,
831 "sum on sliced array should match canonical. \
832 Sliced: {slice_sum:?}, Canonical: {canonical_sum:?}"
833 );
834 }
835
836 if let (Ok(slice_minmax), Ok(canonical_minmax)) = (min_max(&sliced), min_max(&canonical_sliced))
838 {
839 match (slice_minmax, canonical_minmax) {
840 (Some(s_result), Some(c_result)) => {
841 assert_eq!(
842 s_result.min, c_result.min,
843 "min on sliced array should match canonical. \
844 Sliced: {:?}, Canonical: {:?}",
845 s_result.min, c_result.min
846 );
847 assert_eq!(
848 s_result.max, c_result.max,
849 "max on sliced array should match canonical. \
850 Sliced: {:?}, Canonical: {:?}",
851 s_result.max, c_result.max
852 );
853 }
854 (None, None) => {} _ => vortex_panic!("min_max results don't match"),
856 }
857 }
858
859 if array.dtype().is_float()
861 && let (Ok(slice_nan_count), Ok(canonical_nan_count)) =
862 (nan_count(&sliced), nan_count(&canonical_sliced))
863 {
864 assert_eq!(
865 slice_nan_count, canonical_nan_count,
866 "nan_count on sliced array should match canonical. \
867 Sliced: {slice_nan_count}, Canonical: {canonical_nan_count}"
868 );
869 }
870}
871
872fn test_cast_slice_consistency(array: &dyn Array) {
888 let len = array.len();
889 if len < 5 {
890 return; }
892
893 let start = 2;
895 let end = 7.min(len - 2).max(start + 1); let canonical = array.to_canonical();
899
900 let target_dtypes = match array.dtype() {
902 DType::Null => vec![],
903 DType::Bool(nullability) => vec![
904 DType::Primitive(PType::U8, *nullability),
905 DType::Primitive(PType::I32, *nullability),
906 ],
907 DType::Primitive(ptype, nullability) => {
908 let mut targets = vec![];
909 let opposite_nullability = match nullability {
911 Nullability::NonNullable => Nullability::Nullable,
912 Nullability::Nullable => Nullability::NonNullable,
913 };
914 targets.push(DType::Primitive(*ptype, opposite_nullability));
915
916 match ptype {
918 PType::U8 => {
919 targets.push(DType::Primitive(PType::U16, *nullability));
920 targets.push(DType::Primitive(PType::I16, *nullability));
921 }
922 PType::U16 => {
923 targets.push(DType::Primitive(PType::U32, *nullability));
924 targets.push(DType::Primitive(PType::I32, *nullability));
925 }
926 PType::U32 => {
927 targets.push(DType::Primitive(PType::U64, *nullability));
928 targets.push(DType::Primitive(PType::I64, *nullability));
929 }
930 PType::U64 => {
931 targets.push(DType::Primitive(PType::F64, *nullability));
932 }
933 PType::I8 => {
934 targets.push(DType::Primitive(PType::I16, *nullability));
935 targets.push(DType::Primitive(PType::F32, *nullability));
936 }
937 PType::I16 => {
938 targets.push(DType::Primitive(PType::I32, *nullability));
939 targets.push(DType::Primitive(PType::F32, *nullability));
940 }
941 PType::I32 => {
942 targets.push(DType::Primitive(PType::I64, *nullability));
943 targets.push(DType::Primitive(PType::F64, *nullability));
944 }
945 PType::I64 => {
946 targets.push(DType::Primitive(PType::F64, *nullability));
947 }
948 PType::F16 => {
949 targets.push(DType::Primitive(PType::F32, *nullability));
950 }
951 PType::F32 => {
952 targets.push(DType::Primitive(PType::F64, *nullability));
953 targets.push(DType::Primitive(PType::I32, *nullability));
954 }
955 PType::F64 => {
956 targets.push(DType::Primitive(PType::I64, *nullability));
957 }
958 }
959 targets
960 }
961 DType::Utf8(nullability) => {
962 let opposite = match nullability {
963 Nullability::NonNullable => Nullability::Nullable,
964 Nullability::Nullable => Nullability::NonNullable,
965 };
966 vec![DType::Utf8(opposite), DType::Binary(*nullability)]
967 }
968 DType::Binary(nullability) => {
969 let opposite = match nullability {
970 Nullability::NonNullable => Nullability::Nullable,
971 Nullability::Nullable => Nullability::NonNullable,
972 };
973 vec![
974 DType::Binary(opposite),
975 DType::Utf8(*nullability), ]
977 }
978 DType::Decimal(decimal_type, nullability) => {
979 let opposite = match nullability {
980 Nullability::NonNullable => Nullability::Nullable,
981 Nullability::Nullable => Nullability::NonNullable,
982 };
983 vec![DType::Decimal(*decimal_type, opposite)]
984 }
985 DType::Struct(fields, nullability) => {
986 let opposite = match nullability {
987 Nullability::NonNullable => Nullability::Nullable,
988 Nullability::Nullable => Nullability::NonNullable,
989 };
990 vec![DType::Struct(fields.clone(), opposite)]
991 }
992 DType::List(element_type, nullability) => {
993 let opposite = match nullability {
994 Nullability::NonNullable => Nullability::Nullable,
995 Nullability::Nullable => Nullability::NonNullable,
996 };
997 vec![DType::List(element_type.clone(), opposite)]
998 }
999 DType::FixedSizeList(element_type, list_size, nullability) => {
1000 let opposite = match nullability {
1001 Nullability::NonNullable => Nullability::Nullable,
1002 Nullability::Nullable => Nullability::NonNullable,
1003 };
1004 vec![DType::FixedSizeList(
1005 element_type.clone(),
1006 *list_size,
1007 opposite,
1008 )]
1009 }
1010 DType::Extension(_) => vec![], };
1012
1013 for target_dtype in target_dtypes {
1015 let sliced = array.slice(start..end);
1017
1018 let slice_then_cast = match cast(&sliced, &target_dtype) {
1020 Ok(result) => result,
1021 Err(_) => continue, };
1023
1024 assert_eq!(
1026 slice_then_cast.len(),
1027 end - start,
1028 "Sliced and casted array should have length {}, but has {}",
1029 end - start,
1030 slice_then_cast.len()
1031 );
1032
1033 for i in 0..slice_then_cast.len() {
1035 let slice_cast_val = slice_then_cast.scalar_at(i);
1036
1037 let canonical_val = canonical.as_ref().scalar_at(start + i);
1039
1040 let expected_val = match canonical_val.cast(&target_dtype) {
1042 Ok(val) => val,
1043 Err(_) => {
1044 break;
1047 }
1048 };
1049
1050 assert_eq!(
1051 slice_cast_val,
1052 expected_val,
1053 "Cast of sliced array produced incorrect value at index {i}. \
1054 Got: {slice_cast_val:?}, Expected: {expected_val:?} \
1055 (canonical value at index {}: {canonical_val:?})\n\
1056 This likely indicates the array encoding doesn't preserve offset information during cast.",
1057 start + i
1058 );
1059 }
1060
1061 let casted = match cast(array, &target_dtype) {
1063 Ok(result) => result,
1064 Err(_) => continue, };
1066 let cast_then_slice = casted.slice(start..end);
1067
1068 assert_eq!(
1070 slice_then_cast.len(),
1071 cast_then_slice.len(),
1072 "Slice-then-cast and cast-then-slice should produce arrays of the same length"
1073 );
1074
1075 for i in 0..slice_then_cast.len() {
1076 let slice_cast_val = slice_then_cast.scalar_at(i);
1077 let cast_slice_val = cast_then_slice.scalar_at(i);
1078 assert_eq!(
1079 slice_cast_val, cast_slice_val,
1080 "Slice-then-cast and cast-then-slice produced different values at index {i}. \
1081 Slice-then-cast: {slice_cast_val:?}, Cast-then-slice: {cast_slice_val:?}"
1082 );
1083 }
1084 }
1085}
1086
1087pub fn test_array_consistency(array: &dyn Array) {
1129 test_filter_take_consistency(array);
1131 test_double_mask_consistency(array);
1132 test_slice_filter_consistency(array);
1133 test_take_slice_consistency(array);
1134 test_cast_slice_consistency(array);
1135
1136 test_boolean_demorgan_consistency(array);
1138
1139 test_comparison_inverse_consistency(array);
1141 test_comparison_symmetry_consistency(array);
1142
1143 test_slice_aggregate_consistency(array);
1145
1146 test_filter_identity(array);
1148 test_mask_identity(array);
1149 test_take_preserves_properties(array);
1150
1151 test_filter_preserves_order(array);
1153 test_take_repeated_indices(array);
1154
1155 test_mask_filter_null_consistency(array);
1157 test_nullable_indices_consistency(array);
1158
1159 test_empty_operations_consistency(array);
1161 test_large_array_consistency(array);
1162}