1use vortex_buffer::BitBuffer;
23use vortex_dtype::DType;
24use vortex_dtype::Nullability;
25use vortex_dtype::PType;
26use vortex_error::VortexExpect;
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_expect("filter should succeed in conformance test");
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 =
78 take(array, &indices_array).vortex_expect("take should succeed in conformance test");
79
80 assert_eq!(
82 filtered.len(),
83 taken.len(),
84 "Filter and take should produce arrays of the same length. \
85 Filtered length: {}, Taken length: {}",
86 filtered.len(),
87 taken.len()
88 );
89
90 for i in 0..filtered.len() {
91 let filtered_val = filtered.scalar_at(i);
92 let taken_val = taken.scalar_at(i);
93 assert_eq!(
94 filtered_val, taken_val,
95 "Filter and take produced different values at index {i}. \
96 Filtered value: {filtered_val:?}, Taken value: {taken_val:?}"
97 );
98 }
99}
100
101fn test_double_mask_consistency(array: &dyn Array) {
117 let len = array.len();
118 if len == 0 {
119 return;
120 }
121
122 let mask1: Mask = (0..len).map(|i| i % 3 == 0).collect();
124 let mask2: Mask = (0..len).map(|i| i % 2 == 0).collect();
125
126 let first_masked = mask(array, &mask1).vortex_expect("mask should succeed in conformance test");
128 let double_masked =
129 mask(&first_masked, &mask2).vortex_expect("mask should succeed in conformance test");
130
131 let combined_pattern: BitBuffer = mask1
133 .to_bit_buffer()
134 .iter()
135 .zip(mask2.to_bit_buffer().iter())
136 .map(|(a, b)| a || b)
137 .collect();
138 let combined_mask = Mask::from_buffer(combined_pattern);
139
140 let directly_masked =
142 mask(array, &combined_mask).vortex_expect("mask should succeed in conformance test");
143
144 assert_eq!(
146 double_masked.len(),
147 directly_masked.len(),
148 "Sequential masking and combined masking should produce arrays of the same length. \
149 Sequential length: {}, Combined length: {}",
150 double_masked.len(),
151 directly_masked.len()
152 );
153
154 for i in 0..double_masked.len() {
155 let double_val = double_masked.scalar_at(i);
156 let direct_val = directly_masked.scalar_at(i);
157 assert_eq!(
158 double_val, direct_val,
159 "Sequential masking and combined masking produced different values at index {i}. \
160 Sequential masking value: {double_val:?}, Combined masking value: {direct_val:?}\n\
161 This likely indicates an issue with how masks are composed in the array implementation."
162 );
163 }
164}
165
166fn test_filter_identity(array: &dyn Array) {
180 let len = array.len();
181 if len == 0 {
182 return;
183 }
184
185 let all_true_mask = Mask::new_true(len);
186 let filtered =
187 filter(array, &all_true_mask).vortex_expect("filter should succeed in conformance test");
188
189 assert_eq!(
191 filtered.len(),
192 array.len(),
193 "Filtering with all-true mask should preserve array length. \
194 Original length: {}, Filtered length: {}",
195 array.len(),
196 filtered.len()
197 );
198
199 for i in 0..len {
200 let original_val = array.scalar_at(i);
201 let filtered_val = filtered.scalar_at(i);
202 assert_eq!(
203 filtered_val, original_val,
204 "Filtering with all-true mask should preserve all values. \
205 Value at index {i} changed from {original_val:?} to {filtered_val:?}"
206 );
207 }
208}
209
210fn test_mask_identity(array: &dyn Array) {
224 let len = array.len();
225 if len == 0 {
226 return;
227 }
228
229 let all_false_mask = Mask::new_false(len);
230 let masked =
231 mask(array, &all_false_mask).vortex_expect("mask should succeed in conformance test");
232
233 assert_eq!(
235 masked.len(),
236 array.len(),
237 "Masking with all-false mask should preserve array length. \
238 Original length: {}, Masked length: {}",
239 array.len(),
240 masked.len()
241 );
242
243 assert!(
244 masked.dtype().is_nullable(),
245 "Mask operation should always produce a nullable array, but dtype is {:?}",
246 masked.dtype()
247 );
248
249 for i in 0..len {
250 let original_val = array.scalar_at(i);
251 let masked_val = masked.scalar_at(i);
252 let expected_val = original_val.clone().into_nullable();
253 assert_eq!(
254 masked_val, expected_val,
255 "Masking with all-false mask should preserve values (as nullable). \
256 Value at index {i}: original = {original_val:?}, masked = {masked_val:?}, expected = {expected_val:?}"
257 );
258 }
259}
260
261fn test_slice_filter_consistency(array: &dyn Array) {
276 let len = array.len();
277 if len < 4 {
278 return; }
280
281 let mut mask_pattern = vec![false; len];
283 mask_pattern[1..4.min(len)].fill(true);
284
285 let mask = Mask::from_iter(mask_pattern);
286 let filtered = filter(array, &mask).vortex_expect("filter should succeed in conformance test");
287
288 let sliced = array.slice(1..4.min(len));
290
291 assert_eq!(
292 filtered.len(),
293 sliced.len(),
294 "Filter with contiguous mask and slice should produce same length. \
295 Filtered length: {}, Sliced length: {}",
296 filtered.len(),
297 sliced.len()
298 );
299
300 for i in 0..filtered.len() {
301 let filtered_val = filtered.scalar_at(i);
302 let sliced_val = sliced.scalar_at(i);
303 assert_eq!(
304 filtered_val, sliced_val,
305 "Filter with contiguous mask and slice produced different values at index {i}. \
306 Filtered value: {filtered_val:?}, Sliced value: {sliced_val:?}"
307 );
308 }
309}
310
311fn test_take_slice_consistency(array: &dyn Array) {
325 let len = array.len();
326 if len < 3 {
327 return; }
329
330 let end = 4.min(len);
332 let indices = PrimitiveArray::from_iter((1..end).map(|i| i as u64)).into_array();
333 let taken = take(array, &indices).vortex_expect("take should succeed in conformance test");
334
335 let sliced = array.slice(1..end);
337
338 assert_eq!(
339 taken.len(),
340 sliced.len(),
341 "Take with sequential indices and slice should produce same length. \
342 Taken length: {}, Sliced length: {}",
343 taken.len(),
344 sliced.len()
345 );
346
347 for i in 0..taken.len() {
348 let taken_val = taken.scalar_at(i);
349 let sliced_val = sliced.scalar_at(i);
350 assert_eq!(
351 taken_val, sliced_val,
352 "Take with sequential indices and slice produced different values at index {i}. \
353 Taken value: {taken_val:?}, Sliced value: {sliced_val:?}"
354 );
355 }
356}
357
358fn test_filter_preserves_order(array: &dyn Array) {
360 let len = array.len();
361 if len < 4 {
362 return;
363 }
364
365 let mask_pattern: Vec<bool> = (0..len).map(|i| i == 0 || i == 2 || i == 3).collect();
367 let mask = Mask::from_iter(mask_pattern);
368
369 let filtered = filter(array, &mask).vortex_expect("filter should succeed in conformance test");
370
371 assert_eq!(filtered.len(), 3.min(len));
373 if len >= 4 {
374 assert_eq!(filtered.scalar_at(0), array.scalar_at(0));
375 assert_eq!(filtered.scalar_at(1), array.scalar_at(2),);
376 assert_eq!(filtered.scalar_at(2), array.scalar_at(3));
377 }
378}
379
380fn test_take_repeated_indices(array: &dyn Array) {
382 let len = array.len();
383 if len == 0 {
384 return;
385 }
386
387 let indices = PrimitiveArray::from_iter([0u64, 0, 0]).into_array();
389 let taken = take(array, &indices).vortex_expect("take should succeed in conformance test");
390
391 assert_eq!(taken.len(), 3);
392 for i in 0..3 {
393 assert_eq!(taken.scalar_at(i), array.scalar_at(0),);
394 }
395}
396
397fn test_mask_filter_null_consistency(array: &dyn Array) {
399 let len = array.len();
400 if len < 3 {
401 return;
402 }
403
404 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
406 let mask_array = Mask::from_iter(mask_pattern);
407 let masked = mask(array, &mask_array).vortex_expect("mask should succeed in conformance test");
408
409 let filter_pattern: Vec<bool> = (0..len).map(|i| i % 2 != 0).collect();
411 let filter_mask = Mask::from_iter(filter_pattern);
412 let filtered =
413 filter(&masked, &filter_mask).vortex_expect("filter should succeed in conformance test");
414
415 let direct_filtered =
417 filter(array, &filter_mask).vortex_expect("filter should succeed in conformance test");
418
419 assert_eq!(filtered.len(), direct_filtered.len());
420 for i in 0..filtered.len() {
421 assert_eq!(filtered.scalar_at(i), direct_filtered.scalar_at(i));
422 }
423}
424
425fn test_empty_operations_consistency(array: &dyn Array) {
427 let len = array.len();
428
429 let empty_filter = filter(array, &Mask::new_false(len))
431 .vortex_expect("filter should succeed in conformance test");
432 assert_eq!(empty_filter.len(), 0);
433 assert_eq!(empty_filter.dtype(), array.dtype());
434
435 let empty_indices = PrimitiveArray::empty::<u64>(Nullability::NonNullable).into_array();
437 let empty_take =
438 take(array, &empty_indices).vortex_expect("take should succeed in conformance test");
439 assert_eq!(empty_take.len(), 0);
440 assert_eq!(empty_take.dtype(), array.dtype());
441
442 if len > 0 {
444 let empty_slice = array.slice(0..0);
445 assert_eq!(empty_slice.len(), 0);
446 assert_eq!(empty_slice.dtype(), array.dtype());
447 }
448}
449
450fn test_take_preserves_properties(array: &dyn Array) {
452 let len = array.len();
453 if len == 0 {
454 return;
455 }
456
457 let indices = PrimitiveArray::from_iter((0..len).map(|i| i as u64)).into_array();
459 let taken = take(array, &indices).vortex_expect("take should succeed in conformance test");
460
461 assert_eq!(taken.len(), array.len());
463 assert_eq!(taken.dtype(), array.dtype());
464 for i in 0..len {
465 assert_eq!(taken.scalar_at(i), array.scalar_at(i),);
466 }
467}
468
469fn test_nullable_indices_consistency(array: &dyn Array) {
487 let len = array.len();
488 if len < 3 {
489 return; }
491
492 let indices = PrimitiveArray::from_option_iter([Some(0u64), None, Some(2u64)]).into_array();
494
495 let taken = take(array, &indices).vortex_expect("take should succeed in conformance test");
496
497 assert_eq!(
499 taken.len(),
500 3,
501 "Take with nullable indices should produce array of length 3, got {}",
502 taken.len()
503 );
504
505 assert!(
506 taken.dtype().is_nullable(),
507 "Take with nullable indices should produce nullable array, but dtype is {:?}",
508 taken.dtype()
509 );
510
511 let expected_0 = array.scalar_at(0).into_nullable();
513 let actual_0 = taken.scalar_at(0);
514 assert_eq!(
515 actual_0, expected_0,
516 "Take with nullable indices: element at position 0 should be from array index 0. \
517 Expected: {expected_0:?}, Actual: {actual_0:?}"
518 );
519
520 let actual_1 = taken.scalar_at(1);
522 assert!(
523 actual_1.is_null(),
524 "Take with nullable indices: element at position 1 should be null, but got {actual_1:?}"
525 );
526
527 let expected_2 = array.scalar_at(2).into_nullable();
529 let actual_2 = taken.scalar_at(2);
530 assert_eq!(
531 actual_2, expected_2,
532 "Take with nullable indices: element at position 2 should be from array index 2. \
533 Expected: {expected_2:?}, Actual: {actual_2:?}"
534 );
535}
536
537fn test_large_array_consistency(array: &dyn Array) {
539 let len = array.len();
540 if len < 1000 {
541 return;
542 }
543
544 let indices: Vec<u64> = (0..len).step_by(10).map(|i| i as u64).collect();
546 let indices_array = PrimitiveArray::from_iter(indices).into_array();
547 let taken =
548 take(array, &indices_array).vortex_expect("take should succeed in conformance test");
549
550 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
552 let mask = Mask::from_iter(mask_pattern);
553 let filtered = filter(array, &mask).vortex_expect("filter should succeed in conformance test");
554
555 assert_eq!(taken.len(), filtered.len());
557 for i in 0..taken.len() {
558 assert_eq!(taken.scalar_at(i), filtered.scalar_at(i),);
559 }
560}
561
562fn test_comparison_inverse_consistency(array: &dyn Array) {
579 let len = array.len();
580 if len == 0 {
581 return;
582 }
583
584 match array.dtype() {
586 DType::Null | DType::Extension(_) | DType::Struct(..) | DType::List(..) => return,
587 _ => {}
588 }
589
590 let test_scalar = if len == 0 {
592 return;
593 } else {
594 array.scalar_at(len / 2)
595 };
596
597 let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
599 if let (Ok(eq_result), Ok(neq_result)) = (
600 compare(array, const_array.as_ref(), Operator::Eq),
601 compare(array, const_array.as_ref(), Operator::NotEq),
602 ) {
603 let inverted_eq =
604 invert(&eq_result).vortex_expect("invert should succeed in conformance test");
605
606 assert_eq!(
607 inverted_eq.len(),
608 neq_result.len(),
609 "Inverted Eq should have same length as NotEq"
610 );
611
612 for i in 0..inverted_eq.len() {
613 let inv_val = inverted_eq.scalar_at(i);
614 let neq_val = neq_result.scalar_at(i);
615 assert_eq!(
616 inv_val, neq_val,
617 "At index {i}: NOT(Eq) should equal NotEq. \
618 NOT(Eq) = {inv_val:?}, NotEq = {neq_val:?}"
619 );
620 }
621 }
622
623 if let (Ok(gt_result), Ok(lte_result)) = (
625 compare(array, const_array.as_ref(), Operator::Gt),
626 compare(array, const_array.as_ref(), Operator::Lte),
627 ) {
628 let inverted_gt =
629 invert(>_result).vortex_expect("invert should succeed in conformance test");
630
631 for i in 0..inverted_gt.len() {
632 let inv_val = inverted_gt.scalar_at(i);
633 let lte_val = lte_result.scalar_at(i);
634 assert_eq!(
635 inv_val, lte_val,
636 "At index {i}: NOT(Gt) should equal Lte. \
637 NOT(Gt) = {inv_val:?}, Lte = {lte_val:?}"
638 );
639 }
640 }
641
642 if let (Ok(lt_result), Ok(gte_result)) = (
644 compare(array, const_array.as_ref(), Operator::Lt),
645 compare(array, const_array.as_ref(), Operator::Gte),
646 ) {
647 let inverted_lt =
648 invert(<_result).vortex_expect("invert should succeed in conformance test");
649
650 for i in 0..inverted_lt.len() {
651 let inv_val = inverted_lt.scalar_at(i);
652 let gte_val = gte_result.scalar_at(i);
653 assert_eq!(
654 inv_val, gte_val,
655 "At index {i}: NOT(Lt) should equal Gte. \
656 NOT(Lt) = {inv_val:?}, Gte = {gte_val:?}"
657 );
658 }
659 }
660}
661
662fn test_comparison_symmetry_consistency(array: &dyn Array) {
678 let len = array.len();
679 if len == 0 {
680 return;
681 }
682
683 match array.dtype() {
685 DType::Null | DType::Extension(_) | DType::Struct(..) | DType::List(..) => return,
686 _ => {}
687 }
688
689 let test_scalar = if len == 2 {
691 return;
692 } else {
693 array.scalar_at(len / 2)
694 };
695
696 let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
698
699 if let (Ok(arr_gt_scalar), Ok(scalar_lt_arr)) = (
701 compare(array, const_array.as_ref(), Operator::Gt),
702 compare(const_array.as_ref(), array, Operator::Lt),
703 ) {
704 assert_eq!(
705 arr_gt_scalar.len(),
706 scalar_lt_arr.len(),
707 "Symmetric comparisons should have same length"
708 );
709
710 for i in 0..arr_gt_scalar.len() {
711 let arr_gt = arr_gt_scalar.scalar_at(i);
712 let scalar_lt = scalar_lt_arr.scalar_at(i);
713 assert_eq!(
714 arr_gt, scalar_lt,
715 "At index {i}: (array > scalar) should equal (scalar < array). \
716 array > scalar = {arr_gt:?}, scalar < array = {scalar_lt:?}"
717 );
718 }
719 }
720
721 if let (Ok(arr_eq_scalar), Ok(scalar_eq_arr)) = (
723 compare(array, const_array.as_ref(), Operator::Eq),
724 compare(const_array.as_ref(), array, Operator::Eq),
725 ) {
726 for i in 0..arr_eq_scalar.len() {
727 let arr_eq = arr_eq_scalar.scalar_at(i);
728 let scalar_eq = scalar_eq_arr.scalar_at(i);
729 assert_eq!(
730 arr_eq, scalar_eq,
731 "At index {i}: (array == scalar) should equal (scalar == array). \
732 array == scalar = {arr_eq:?}, scalar == array = {scalar_eq:?}"
733 );
734 }
735 }
736}
737
738fn test_boolean_demorgan_consistency(array: &dyn Array) {
755 if !matches!(array.dtype(), DType::Bool(_)) {
756 return;
757 }
758
759 let mask = {
760 let mask_pattern: Vec<bool> = (0..array.len()).map(|i| i % 3 == 0).collect();
761 BoolArray::from_iter(mask_pattern)
762 };
763 let mask = mask.as_ref();
764
765 if let (Ok(a_and_b), Ok(not_a), Ok(not_b)) = (and(array, mask), invert(array), invert(mask)) {
767 let not_a_and_b =
768 invert(&a_and_b).vortex_expect("invert should succeed in conformance test");
769 let not_a_or_not_b =
770 or(¬_a, ¬_b).vortex_expect("or should succeed in conformance test");
771
772 assert_eq!(
773 not_a_and_b.len(),
774 not_a_or_not_b.len(),
775 "De Morgan's law results should have same length"
776 );
777
778 for i in 0..not_a_and_b.len() {
779 let left = not_a_and_b.scalar_at(i);
780 let right = not_a_or_not_b.scalar_at(i);
781 assert_eq!(
782 left, right,
783 "De Morgan's first law failed at index {i}: \
784 NOT(A AND B) = {left:?}, (NOT A) OR (NOT B) = {right:?}"
785 );
786 }
787 }
788
789 if let (Ok(a_or_b), Ok(not_a), Ok(not_b)) = (or(array, mask), invert(array), invert(mask)) {
791 let not_a_or_b = invert(&a_or_b).vortex_expect("invert should succeed in conformance test");
792 let not_a_and_not_b =
793 and(¬_a, ¬_b).vortex_expect("and should succeed in conformance test");
794
795 for i in 0..not_a_or_b.len() {
796 let left = not_a_or_b.scalar_at(i);
797 let right = not_a_and_not_b.scalar_at(i);
798 assert_eq!(
799 left, right,
800 "De Morgan's second law failed at index {i}: \
801 NOT(A OR B) = {left:?}, (NOT A) AND (NOT B) = {right:?}"
802 );
803 }
804 }
805}
806
807fn test_slice_aggregate_consistency(array: &dyn Array) {
823 use vortex_dtype::DType;
824
825 use crate::compute::min_max;
826 use crate::compute::nan_count;
827 use crate::compute::sum;
828
829 let len = array.len();
830 if len < 5 {
831 return; }
833
834 let start = 1;
836 let end = (len - 1).min(start + 10); let sliced = array.slice(start..end);
840 let canonical = array.to_canonical();
841 let canonical_sliced = canonical.as_ref().slice(start..end);
842
843 assert_eq!(
845 sliced.invalid_count(),
846 canonical_sliced.invalid_count(),
847 "null_count on sliced array should match canonical. \
848 Sliced: {}, Canonical: {}",
849 sliced.invalid_count(),
850 canonical_sliced.invalid_count()
851 );
852
853 if !matches!(array.dtype(), DType::Primitive(..)) {
855 return;
856 }
857
858 if let (Ok(slice_sum), Ok(canonical_sum)) = (sum(&sliced), sum(&canonical_sliced)) {
859 assert_eq!(
861 slice_sum, canonical_sum,
862 "sum on sliced array should match canonical. \
863 Sliced: {slice_sum:?}, Canonical: {canonical_sum:?}"
864 );
865 }
866
867 if let (Ok(slice_minmax), Ok(canonical_minmax)) = (min_max(&sliced), min_max(&canonical_sliced))
869 {
870 match (slice_minmax, canonical_minmax) {
871 (Some(s_result), Some(c_result)) => {
872 assert_eq!(
873 s_result.min, c_result.min,
874 "min on sliced array should match canonical. \
875 Sliced: {:?}, Canonical: {:?}",
876 s_result.min, c_result.min
877 );
878 assert_eq!(
879 s_result.max, c_result.max,
880 "max on sliced array should match canonical. \
881 Sliced: {:?}, Canonical: {:?}",
882 s_result.max, c_result.max
883 );
884 }
885 (None, None) => {} _ => vortex_panic!("min_max results don't match"),
887 }
888 }
889
890 if array.dtype().is_float()
892 && let (Ok(slice_nan_count), Ok(canonical_nan_count)) =
893 (nan_count(&sliced), nan_count(&canonical_sliced))
894 {
895 assert_eq!(
896 slice_nan_count, canonical_nan_count,
897 "nan_count on sliced array should match canonical. \
898 Sliced: {slice_nan_count}, Canonical: {canonical_nan_count}"
899 );
900 }
901}
902
903fn test_cast_slice_consistency(array: &dyn Array) {
919 let len = array.len();
920 if len < 5 {
921 return; }
923
924 let start = 2;
926 let end = 7.min(len - 2).max(start + 1); let canonical = array.to_canonical();
930
931 let target_dtypes = match array.dtype() {
933 DType::Null => vec![],
934 DType::Bool(nullability) => vec![
935 DType::Primitive(PType::U8, *nullability),
936 DType::Primitive(PType::I32, *nullability),
937 ],
938 DType::Primitive(ptype, nullability) => {
939 let mut targets = vec![];
940 let opposite_nullability = match nullability {
942 Nullability::NonNullable => Nullability::Nullable,
943 Nullability::Nullable => Nullability::NonNullable,
944 };
945 targets.push(DType::Primitive(*ptype, opposite_nullability));
946
947 match ptype {
949 PType::U8 => {
950 targets.push(DType::Primitive(PType::U16, *nullability));
951 targets.push(DType::Primitive(PType::I16, *nullability));
952 }
953 PType::U16 => {
954 targets.push(DType::Primitive(PType::U32, *nullability));
955 targets.push(DType::Primitive(PType::I32, *nullability));
956 }
957 PType::U32 => {
958 targets.push(DType::Primitive(PType::U64, *nullability));
959 targets.push(DType::Primitive(PType::I64, *nullability));
960 }
961 PType::U64 => {
962 targets.push(DType::Primitive(PType::F64, *nullability));
963 }
964 PType::I8 => {
965 targets.push(DType::Primitive(PType::I16, *nullability));
966 targets.push(DType::Primitive(PType::F32, *nullability));
967 }
968 PType::I16 => {
969 targets.push(DType::Primitive(PType::I32, *nullability));
970 targets.push(DType::Primitive(PType::F32, *nullability));
971 }
972 PType::I32 => {
973 targets.push(DType::Primitive(PType::I64, *nullability));
974 targets.push(DType::Primitive(PType::F64, *nullability));
975 }
976 PType::I64 => {
977 targets.push(DType::Primitive(PType::F64, *nullability));
978 }
979 PType::F16 => {
980 targets.push(DType::Primitive(PType::F32, *nullability));
981 }
982 PType::F32 => {
983 targets.push(DType::Primitive(PType::F64, *nullability));
984 targets.push(DType::Primitive(PType::I32, *nullability));
985 }
986 PType::F64 => {
987 targets.push(DType::Primitive(PType::I64, *nullability));
988 }
989 }
990 targets
991 }
992 DType::Utf8(nullability) => {
993 let opposite = match nullability {
994 Nullability::NonNullable => Nullability::Nullable,
995 Nullability::Nullable => Nullability::NonNullable,
996 };
997 vec![DType::Utf8(opposite), DType::Binary(*nullability)]
998 }
999 DType::Binary(nullability) => {
1000 let opposite = match nullability {
1001 Nullability::NonNullable => Nullability::Nullable,
1002 Nullability::Nullable => Nullability::NonNullable,
1003 };
1004 vec![
1005 DType::Binary(opposite),
1006 DType::Utf8(*nullability), ]
1008 }
1009 DType::Decimal(decimal_type, nullability) => {
1010 let opposite = match nullability {
1011 Nullability::NonNullable => Nullability::Nullable,
1012 Nullability::Nullable => Nullability::NonNullable,
1013 };
1014 vec![DType::Decimal(*decimal_type, opposite)]
1015 }
1016 DType::Struct(fields, nullability) => {
1017 let opposite = match nullability {
1018 Nullability::NonNullable => Nullability::Nullable,
1019 Nullability::Nullable => Nullability::NonNullable,
1020 };
1021 vec![DType::Struct(fields.clone(), opposite)]
1022 }
1023 DType::List(element_type, nullability) => {
1024 let opposite = match nullability {
1025 Nullability::NonNullable => Nullability::Nullable,
1026 Nullability::Nullable => Nullability::NonNullable,
1027 };
1028 vec![DType::List(element_type.clone(), opposite)]
1029 }
1030 DType::FixedSizeList(element_type, list_size, nullability) => {
1031 let opposite = match nullability {
1032 Nullability::NonNullable => Nullability::Nullable,
1033 Nullability::Nullable => Nullability::NonNullable,
1034 };
1035 vec![DType::FixedSizeList(
1036 element_type.clone(),
1037 *list_size,
1038 opposite,
1039 )]
1040 }
1041 DType::Extension(_) => vec![], };
1043
1044 for target_dtype in target_dtypes {
1046 let sliced = array.slice(start..end);
1048
1049 let slice_then_cast = match cast(&sliced, &target_dtype) {
1051 Ok(result) => result,
1052 Err(_) => continue, };
1054
1055 assert_eq!(
1057 slice_then_cast.len(),
1058 end - start,
1059 "Sliced and casted array should have length {}, but has {}",
1060 end - start,
1061 slice_then_cast.len()
1062 );
1063
1064 for i in 0..slice_then_cast.len() {
1066 let slice_cast_val = slice_then_cast.scalar_at(i);
1067
1068 let canonical_val = canonical.as_ref().scalar_at(start + i);
1070
1071 let expected_val = match canonical_val.cast(&target_dtype) {
1073 Ok(val) => val,
1074 Err(_) => {
1075 break;
1078 }
1079 };
1080
1081 assert_eq!(
1082 slice_cast_val,
1083 expected_val,
1084 "Cast of sliced array produced incorrect value at index {i}. \
1085 Got: {slice_cast_val:?}, Expected: {expected_val:?} \
1086 (canonical value at index {}: {canonical_val:?})\n\
1087 This likely indicates the array encoding doesn't preserve offset information during cast.",
1088 start + i
1089 );
1090 }
1091
1092 let casted = match cast(array, &target_dtype) {
1094 Ok(result) => result,
1095 Err(_) => continue, };
1097 let cast_then_slice = casted.slice(start..end);
1098
1099 assert_eq!(
1101 slice_then_cast.len(),
1102 cast_then_slice.len(),
1103 "Slice-then-cast and cast-then-slice should produce arrays of the same length"
1104 );
1105
1106 for i in 0..slice_then_cast.len() {
1107 let slice_cast_val = slice_then_cast.scalar_at(i);
1108 let cast_slice_val = cast_then_slice.scalar_at(i);
1109 assert_eq!(
1110 slice_cast_val, cast_slice_val,
1111 "Slice-then-cast and cast-then-slice produced different values at index {i}. \
1112 Slice-then-cast: {slice_cast_val:?}, Cast-then-slice: {cast_slice_val:?}"
1113 );
1114 }
1115 }
1116}
1117
1118pub fn test_array_consistency(array: &dyn Array) {
1160 test_filter_take_consistency(array);
1162 test_double_mask_consistency(array);
1163 test_slice_filter_consistency(array);
1164 test_take_slice_consistency(array);
1165 test_cast_slice_consistency(array);
1166
1167 test_boolean_demorgan_consistency(array);
1169
1170 test_comparison_inverse_consistency(array);
1172 test_comparison_symmetry_consistency(array);
1173
1174 test_slice_aggregate_consistency(array);
1176
1177 test_filter_identity(array);
1179 test_mask_identity(array);
1180 test_take_preserves_properties(array);
1181
1182 test_filter_preserves_order(array);
1184 test_take_repeated_indices(array);
1185
1186 test_mask_filter_null_consistency(array);
1188 test_nullable_indices_consistency(array);
1189
1190 test_empty_operations_consistency(array);
1192 test_large_array_consistency(array);
1193}