1use std::sync::Arc;
23
24use vortex_buffer::BitBuffer;
25use vortex_error::VortexExpect;
26use vortex_error::vortex_panic;
27use vortex_mask::Mask;
28
29use crate::ArrayRef;
30use crate::Canonical;
31use crate::ExecutionCtx;
32use crate::IntoArray;
33use crate::arrays::BoolArray;
34use crate::arrays::ConstantArray;
35use crate::arrays::PrimitiveArray;
36use crate::builtins::ArrayBuiltins;
37use crate::dtype::DType;
38use crate::dtype::Nullability;
39use crate::dtype::PType;
40use crate::scalar_fn::fns::operators::Operator;
41
42fn test_filter_take_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
54 let len = array.len();
55 if len == 0 {
56 return;
57 }
58
59 let mask_pattern: BitBuffer = (0..len).map(|i| i % 3 != 1).collect();
61 let mask = Mask::from_buffer(mask_pattern.clone());
62
63 let filtered = array
65 .filter(mask)
66 .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 = array
78 .take(indices_array)
79 .vortex_expect("take should succeed in conformance test");
80
81 assert_eq!(
83 filtered.len(),
84 taken.len(),
85 "Filter and take should produce arrays of the same length. \
86 Filtered length: {}, Taken length: {}",
87 filtered.len(),
88 taken.len()
89 );
90
91 for i in 0..filtered.len() {
92 let filtered_val = filtered
93 .execute_scalar(i, ctx)
94 .vortex_expect("scalar_at should succeed in conformance test");
95 let taken_val = taken
96 .execute_scalar(i, ctx)
97 .vortex_expect("scalar_at should succeed in conformance test");
98 assert_eq!(
99 filtered_val, taken_val,
100 "Filter and take produced different values at index {i}. \
101 Filtered value: {filtered_val:?}, Taken value: {taken_val:?}"
102 );
103 }
104}
105
106fn test_double_mask_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
122 let len = array.len();
123 if len == 0 {
124 return;
125 }
126
127 let mask1: Mask = (0..len).map(|i| i % 3 == 0).collect();
129 let mask2: Mask = (0..len).map(|i| i % 2 == 0).collect();
130
131 let first_masked = array
133 .clone()
134 .mask((!&mask1).into_array())
135 .vortex_expect("mask should succeed in conformance test");
136 let double_masked = first_masked
137 .mask((!&mask2).into_array())
138 .vortex_expect("mask should succeed in conformance test");
139
140 let combined_pattern: BitBuffer = mask1
142 .to_bit_buffer()
143 .iter()
144 .zip(mask2.to_bit_buffer().iter())
145 .map(|(a, b)| a || b)
146 .collect();
147 let combined_mask = Mask::from_buffer(combined_pattern);
148
149 let directly_masked = array
151 .clone()
152 .mask((!&combined_mask).into_array())
153 .vortex_expect("mask should succeed in conformance test");
154
155 assert_eq!(
157 double_masked.len(),
158 directly_masked.len(),
159 "Sequential masking and combined masking should produce arrays of the same length. \
160 Sequential length: {}, Combined length: {}",
161 double_masked.len(),
162 directly_masked.len()
163 );
164
165 for i in 0..double_masked.len() {
166 let double_val = double_masked
167 .execute_scalar(i, ctx)
168 .vortex_expect("scalar_at should succeed in conformance test");
169 let direct_val = directly_masked
170 .execute_scalar(i, ctx)
171 .vortex_expect("scalar_at should succeed in conformance test");
172 assert_eq!(
173 double_val, direct_val,
174 "Sequential masking and combined masking produced different values at index {i}. \
175 Sequential masking value: {double_val:?}, Combined masking value: {direct_val:?}\n\
176 This likely indicates an issue with how masks are composed in the array implementation."
177 );
178 }
179}
180
181fn test_filter_identity(array: &ArrayRef, ctx: &mut ExecutionCtx) {
195 let len = array.len();
196 if len == 0 {
197 return;
198 }
199
200 let all_true_mask = Mask::new_true(len);
201 let filtered = array
202 .filter(all_true_mask)
203 .vortex_expect("filter should succeed in conformance test");
204
205 assert_eq!(
207 filtered.len(),
208 array.len(),
209 "Filtering with all-true mask should preserve array length. \
210 Original length: {}, Filtered length: {}",
211 array.len(),
212 filtered.len()
213 );
214
215 for i in 0..len {
216 let original_val = array
217 .execute_scalar(i, ctx)
218 .vortex_expect("scalar_at should succeed in conformance test");
219 let filtered_val = filtered
220 .execute_scalar(i, ctx)
221 .vortex_expect("scalar_at should succeed in conformance test");
222 assert_eq!(
223 filtered_val, original_val,
224 "Filtering with all-true mask should preserve all values. \
225 Value at index {i} changed from {original_val:?} to {filtered_val:?}"
226 );
227 }
228}
229
230fn test_mask_identity(array: &ArrayRef, ctx: &mut ExecutionCtx) {
244 let len = array.len();
245 if len == 0 {
246 return;
247 }
248
249 let all_false_mask = Mask::new_false(len);
250 let masked = array
251 .clone()
252 .mask((!&all_false_mask).into_array())
253 .vortex_expect("mask should succeed in conformance test");
254
255 assert_eq!(
257 masked.len(),
258 array.len(),
259 "Masking with all-false mask should preserve array length. \
260 Original length: {}, Masked length: {}",
261 array.len(),
262 masked.len()
263 );
264
265 assert!(
266 masked.dtype().is_nullable(),
267 "Mask operation should always produce a nullable array, but dtype is {}",
268 masked.dtype()
269 );
270
271 for i in 0..len {
272 let original_val = array
273 .execute_scalar(i, ctx)
274 .vortex_expect("scalar_at should succeed in conformance test");
275 let masked_val = masked
276 .execute_scalar(i, ctx)
277 .vortex_expect("scalar_at should succeed in conformance test");
278 let expected_val = original_val.clone().into_nullable();
279 assert_eq!(
280 masked_val, expected_val,
281 "Masking with all-false mask should preserve values (as nullable). \
282 Value at index {i}: original = {original_val:?}, masked = {masked_val:?}, expected = {expected_val:?}"
283 );
284 }
285}
286
287fn test_slice_filter_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
302 let len = array.len();
303 if len < 4 {
304 return; }
306
307 let mut mask_pattern = vec![false; len];
309 mask_pattern[1..4.min(len)].fill(true);
310
311 let mask = Mask::from_iter(mask_pattern);
312 let filtered = array
313 .filter(mask)
314 .vortex_expect("filter should succeed in conformance test");
315
316 let sliced = array
318 .slice(1..4.min(len))
319 .vortex_expect("slice should succeed in conformance test");
320
321 assert_eq!(
322 filtered.len(),
323 sliced.len(),
324 "Filter with contiguous mask and slice should produce same length. \
325 Filtered length: {}, Sliced length: {}",
326 filtered.len(),
327 sliced.len()
328 );
329
330 for i in 0..filtered.len() {
331 let filtered_val = filtered
332 .execute_scalar(i, ctx)
333 .vortex_expect("scalar_at should succeed in conformance test");
334 let sliced_val = sliced
335 .execute_scalar(i, ctx)
336 .vortex_expect("scalar_at should succeed in conformance test");
337 assert_eq!(
338 filtered_val, sliced_val,
339 "Filter with contiguous mask and slice produced different values at index {i}. \
340 Filtered value: {filtered_val:?}, Sliced value: {sliced_val:?}"
341 );
342 }
343}
344
345fn test_take_slice_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
359 let len = array.len();
360 if len < 3 {
361 return; }
363
364 let end = 4.min(len);
366 let indices = PrimitiveArray::from_iter((1..end).map(|i| i as u64)).into_array();
367 let taken = array
368 .take(indices)
369 .vortex_expect("take should succeed in conformance test");
370
371 let sliced = array
373 .slice(1..end)
374 .vortex_expect("slice should succeed in conformance test");
375
376 assert_eq!(
377 taken.len(),
378 sliced.len(),
379 "Take with sequential indices and slice should produce same length. \
380 Taken length: {}, Sliced length: {}",
381 taken.len(),
382 sliced.len()
383 );
384
385 for i in 0..taken.len() {
386 let taken_val = taken
387 .execute_scalar(i, ctx)
388 .vortex_expect("scalar_at should succeed in conformance test");
389 let sliced_val = sliced
390 .execute_scalar(i, ctx)
391 .vortex_expect("scalar_at should succeed in conformance test");
392 assert_eq!(
393 taken_val, sliced_val,
394 "Take with sequential indices and slice produced different values at index {i}. \
395 Taken value: {taken_val:?}, Sliced value: {sliced_val:?}"
396 );
397 }
398}
399
400fn test_filter_preserves_order(array: &ArrayRef, ctx: &mut ExecutionCtx) {
402 let len = array.len();
403 if len < 4 {
404 return;
405 }
406
407 let mask_pattern: Vec<bool> = (0..len).map(|i| i == 0 || i == 2 || i == 3).collect();
409 let mask = Mask::from_iter(mask_pattern);
410
411 let filtered = array
412 .filter(mask)
413 .vortex_expect("filter should succeed in conformance test");
414
415 assert_eq!(filtered.len(), 3.min(len));
417 if len >= 4 {
418 assert_eq!(
419 filtered
420 .execute_scalar(0, ctx)
421 .vortex_expect("scalar_at should succeed in conformance test"),
422 array
423 .execute_scalar(0, ctx)
424 .vortex_expect("scalar_at should succeed in conformance test")
425 );
426 assert_eq!(
427 filtered
428 .execute_scalar(1, ctx)
429 .vortex_expect("scalar_at should succeed in conformance test"),
430 array
431 .execute_scalar(2, ctx)
432 .vortex_expect("scalar_at should succeed in conformance test")
433 );
434 assert_eq!(
435 filtered
436 .execute_scalar(2, ctx)
437 .vortex_expect("scalar_at should succeed in conformance test"),
438 array
439 .execute_scalar(3, ctx)
440 .vortex_expect("scalar_at should succeed in conformance test")
441 );
442 }
443}
444
445fn test_take_repeated_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) {
447 let len = array.len();
448 if len == 0 {
449 return;
450 }
451
452 let indices = PrimitiveArray::from_iter([0u64, 0, 0]).into_array();
454 let taken = array
455 .take(indices)
456 .vortex_expect("take should succeed in conformance test");
457
458 assert_eq!(taken.len(), 3);
459 for i in 0..3 {
460 assert_eq!(
461 taken
462 .execute_scalar(i, ctx)
463 .vortex_expect("scalar_at should succeed in conformance test"),
464 array
465 .execute_scalar(0, ctx)
466 .vortex_expect("scalar_at should succeed in conformance test")
467 );
468 }
469}
470
471fn test_mask_filter_null_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
473 let len = array.len();
474 if len < 3 {
475 return;
476 }
477
478 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
480 let mask_array = Mask::from_iter(mask_pattern);
481 let masked = array
482 .clone()
483 .mask((!&mask_array).into_array())
484 .vortex_expect("mask should succeed in conformance test");
485
486 let filter_pattern: Vec<bool> = (0..len).map(|i| i % 2 != 0).collect();
488 let filter_mask = Mask::from_iter(filter_pattern);
489 let filtered = masked
490 .filter(filter_mask.clone())
491 .vortex_expect("filter should succeed in conformance test");
492
493 let direct_filtered = array
495 .filter(filter_mask)
496 .vortex_expect("filter should succeed in conformance test");
497
498 assert_eq!(filtered.len(), direct_filtered.len());
499 for i in 0..filtered.len() {
500 assert_eq!(
501 filtered
502 .execute_scalar(i, ctx)
503 .vortex_expect("scalar_at should succeed in conformance test"),
504 direct_filtered
505 .execute_scalar(i, ctx)
506 .vortex_expect("scalar_at should succeed in conformance test")
507 );
508 }
509}
510
511fn test_empty_operations_consistency(array: &ArrayRef) {
513 let len = array.len();
514
515 let empty_filter = array
517 .filter(Mask::new_false(len))
518 .vortex_expect("filter should succeed in conformance test");
519 assert_eq!(empty_filter.len(), 0);
520 assert_eq!(empty_filter.dtype(), array.dtype());
521
522 let empty_indices = PrimitiveArray::empty::<u64>(Nullability::NonNullable).into_array();
524 let empty_take = array
525 .take(empty_indices)
526 .vortex_expect("take should succeed in conformance test");
527 assert_eq!(empty_take.len(), 0);
528 assert_eq!(empty_take.dtype(), array.dtype());
529
530 if len > 0 {
532 let empty_slice = array
533 .slice(0..0)
534 .vortex_expect("slice should succeed in conformance test");
535 assert_eq!(empty_slice.len(), 0);
536 assert_eq!(empty_slice.dtype(), array.dtype());
537 }
538}
539
540fn test_take_preserves_properties(array: &ArrayRef, ctx: &mut ExecutionCtx) {
542 let len = array.len();
543 if len == 0 {
544 return;
545 }
546
547 let indices = PrimitiveArray::from_iter((0..len).map(|i| i as u64)).into_array();
549 let taken = array
550 .take(indices)
551 .vortex_expect("take should succeed in conformance test");
552
553 assert_eq!(taken.len(), array.len());
555 assert_eq!(taken.dtype(), array.dtype());
556 for i in 0..len {
557 assert_eq!(
558 taken
559 .execute_scalar(i, ctx)
560 .vortex_expect("scalar_at should succeed in conformance test"),
561 array
562 .execute_scalar(i, ctx)
563 .vortex_expect("scalar_at should succeed in conformance test")
564 );
565 }
566}
567
568fn test_nullable_indices_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
586 let len = array.len();
587 if len < 3 {
588 return; }
590
591 let indices = PrimitiveArray::from_option_iter([Some(0u64), None, Some(2u64)]).into_array();
593
594 let taken = array
595 .take(indices)
596 .vortex_expect("take should succeed in conformance test");
597
598 assert_eq!(
600 taken.len(),
601 3,
602 "Take with nullable indices should produce array of length 3, got {}",
603 taken.len()
604 );
605
606 assert!(
607 taken.dtype().is_nullable(),
608 "Take with nullable indices should produce nullable array, but dtype is {:?}",
609 taken.dtype()
610 );
611
612 let expected_0 = array
614 .execute_scalar(0, ctx)
615 .vortex_expect("scalar_at should succeed in conformance test")
616 .into_nullable();
617 let actual_0 = taken
618 .execute_scalar(0, ctx)
619 .vortex_expect("scalar_at should succeed in conformance test");
620 assert_eq!(
621 actual_0, expected_0,
622 "Take with nullable indices: element at position 0 should be from array index 0. \
623 Expected: {expected_0:?}, Actual: {actual_0:?}"
624 );
625
626 let actual_1 = taken
628 .execute_scalar(1, ctx)
629 .vortex_expect("scalar_at should succeed in conformance test");
630 assert!(
631 actual_1.is_null(),
632 "Take with nullable indices: element at position 1 should be null, but got {actual_1:?}"
633 );
634
635 let expected_2 = array
637 .execute_scalar(2, ctx)
638 .vortex_expect("scalar_at should succeed in conformance test")
639 .into_nullable();
640 let actual_2 = taken
641 .execute_scalar(2, ctx)
642 .vortex_expect("scalar_at should succeed in conformance test");
643 assert_eq!(
644 actual_2, expected_2,
645 "Take with nullable indices: element at position 2 should be from array index 2. \
646 Expected: {expected_2:?}, Actual: {actual_2:?}"
647 );
648}
649
650fn test_large_array_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
652 let len = array.len();
653 if len < 1000 {
654 return;
655 }
656
657 let indices: Vec<u64> = (0..len).step_by(10).map(|i| i as u64).collect();
659 let indices_array = PrimitiveArray::from_iter(indices).into_array();
660 let taken = array
661 .take(indices_array)
662 .vortex_expect("take should succeed in conformance test");
663
664 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
666 let mask = Mask::from_iter(mask_pattern);
667 let filtered = array
668 .filter(mask)
669 .vortex_expect("filter should succeed in conformance test");
670
671 assert_eq!(taken.len(), filtered.len());
673 for i in 0..taken.len() {
674 assert_eq!(
675 taken
676 .execute_scalar(i, ctx)
677 .vortex_expect("scalar_at should succeed in conformance test"),
678 filtered
679 .execute_scalar(i, ctx)
680 .vortex_expect("scalar_at should succeed in conformance test")
681 );
682 }
683}
684
685fn test_comparison_inverse_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
702 let len = array.len();
703 if len == 0 {
704 return;
705 }
706
707 match array.dtype() {
709 DType::Null
710 | DType::List(..)
711 | DType::Struct(..)
712 | DType::Union(..)
713 | DType::Extension(_) => return,
714 _ => {}
715 }
716
717 let test_scalar = if len == 0 {
719 return;
720 } else {
721 array
722 .execute_scalar(len / 2, ctx)
723 .vortex_expect("scalar_at should succeed in conformance test")
724 };
725
726 let const_array = ConstantArray::new(test_scalar, len).into_array();
728 if let (Ok(eq_result), Ok(neq_result)) = (
729 array.binary(const_array.clone(), Operator::Eq),
730 array.binary(const_array.clone(), Operator::NotEq),
731 ) {
732 let inverted_eq = eq_result
733 .not()
734 .vortex_expect("not should succeed in conformance test");
735
736 assert_eq!(
737 inverted_eq.len(),
738 neq_result.len(),
739 "Inverted Eq should have same length as NotEq"
740 );
741
742 for i in 0..inverted_eq.len() {
743 let inv_val = inverted_eq
744 .execute_scalar(i, ctx)
745 .vortex_expect("scalar_at should succeed in conformance test");
746 let neq_val = neq_result
747 .execute_scalar(i, ctx)
748 .vortex_expect("scalar_at should succeed in conformance test");
749 assert_eq!(
750 inv_val, neq_val,
751 "At index {i}: NOT(Eq) should equal NotEq. \
752 NOT(Eq) = {inv_val:?}, NotEq = {neq_val:?}"
753 );
754 }
755 }
756
757 if let (Ok(gt_result), Ok(lte_result)) = (
759 array.binary(const_array.clone(), Operator::Gt),
760 array.binary(const_array.clone(), Operator::Lte),
761 ) {
762 let inverted_gt = gt_result
763 .not()
764 .vortex_expect("not should succeed in conformance test");
765
766 for i in 0..inverted_gt.len() {
767 let inv_val = inverted_gt
768 .execute_scalar(i, ctx)
769 .vortex_expect("scalar_at should succeed in conformance test");
770 let lte_val = lte_result
771 .execute_scalar(i, ctx)
772 .vortex_expect("scalar_at should succeed in conformance test");
773 assert_eq!(
774 inv_val, lte_val,
775 "At index {i}: NOT(Gt) should equal Lte. \
776 NOT(Gt) = {inv_val:?}, Lte = {lte_val:?}"
777 );
778 }
779 }
780
781 if let (Ok(lt_result), Ok(gte_result)) = (
783 array.binary(const_array.clone(), Operator::Lt),
784 array.binary(const_array, Operator::Gte),
785 ) {
786 let inverted_lt = lt_result
787 .not()
788 .vortex_expect("not should succeed in conformance test");
789
790 for i in 0..inverted_lt.len() {
791 let inv_val = inverted_lt
792 .execute_scalar(i, ctx)
793 .vortex_expect("scalar_at should succeed in conformance test");
794 let gte_val = gte_result
795 .execute_scalar(i, ctx)
796 .vortex_expect("scalar_at should succeed in conformance test");
797 assert_eq!(
798 inv_val, gte_val,
799 "At index {i}: NOT(Lt) should equal Gte. \
800 NOT(Lt) = {inv_val:?}, Gte = {gte_val:?}"
801 );
802 }
803 }
804}
805
806fn test_comparison_symmetry_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
822 let len = array.len();
823 if len == 0 {
824 return;
825 }
826
827 match array.dtype() {
829 DType::Null
830 | DType::List(..)
831 | DType::Struct(..)
832 | DType::Union(..)
833 | DType::Extension(_) => return,
834 _ => {}
835 }
836
837 let test_scalar = if len == 2 {
839 return;
840 } else {
841 array
842 .execute_scalar(len / 2, ctx)
843 .vortex_expect("scalar_at should succeed in conformance test")
844 };
845
846 let const_array = ConstantArray::new(test_scalar, len).into_array();
848
849 if let (Ok(arr_gt_scalar), Ok(scalar_lt_arr)) = (
851 array.binary(const_array.clone(), Operator::Gt),
852 const_array.binary(array.clone(), Operator::Lt),
853 ) {
854 assert_eq!(
855 arr_gt_scalar.len(),
856 scalar_lt_arr.len(),
857 "Symmetric comparisons should have same length"
858 );
859
860 for i in 0..arr_gt_scalar.len() {
861 let arr_gt = arr_gt_scalar
862 .execute_scalar(i, ctx)
863 .vortex_expect("scalar_at should succeed in conformance test");
864 let scalar_lt = scalar_lt_arr
865 .execute_scalar(i, ctx)
866 .vortex_expect("scalar_at should succeed in conformance test");
867 assert_eq!(
868 arr_gt, scalar_lt,
869 "At index {i}: (array > scalar) should equal (scalar < array). \
870 array > scalar = {arr_gt:?}, scalar < array = {scalar_lt:?}"
871 );
872 }
873 }
874
875 if let (Ok(arr_eq_scalar), Ok(scalar_eq_arr)) = (
877 array.binary(const_array.clone(), Operator::Eq),
878 const_array.binary(array.clone(), Operator::Eq),
879 ) {
880 for i in 0..arr_eq_scalar.len() {
881 let arr_eq = arr_eq_scalar
882 .execute_scalar(i, ctx)
883 .vortex_expect("scalar_at should succeed in conformance test");
884 let scalar_eq = scalar_eq_arr
885 .execute_scalar(i, ctx)
886 .vortex_expect("scalar_at should succeed in conformance test");
887 assert_eq!(
888 arr_eq, scalar_eq,
889 "At index {i}: (array == scalar) should equal (scalar == array). \
890 array == scalar = {arr_eq:?}, scalar == array = {scalar_eq:?}"
891 );
892 }
893 }
894}
895
896fn test_boolean_demorgan_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
913 if !matches!(array.dtype(), DType::Bool(_)) {
914 return;
915 }
916
917 let bool_mask = {
918 let mask_pattern: Vec<bool> = (0..array.len()).map(|i| i % 3 == 0).collect();
919 BoolArray::from_iter(mask_pattern)
920 };
921 let bool_mask = bool_mask.into_array();
922
923 if let (Ok(a_and_b), Ok(not_a), Ok(not_b)) = (
925 array.binary(bool_mask.clone(), Operator::And),
926 array.not(),
927 bool_mask.not(),
928 ) {
929 let not_a_and_b = a_and_b
930 .not()
931 .vortex_expect("not should succeed in conformance test");
932 let not_a_or_not_b = not_a
933 .binary(not_b, Operator::Or)
934 .vortex_expect("or should succeed in conformance test");
935
936 assert_eq!(
937 not_a_and_b.len(),
938 not_a_or_not_b.len(),
939 "De Morgan's law results should have same length"
940 );
941
942 for i in 0..not_a_and_b.len() {
943 let left = not_a_and_b
944 .execute_scalar(i, ctx)
945 .vortex_expect("scalar_at should succeed in conformance test");
946 let right = not_a_or_not_b
947 .execute_scalar(i, ctx)
948 .vortex_expect("scalar_at should succeed in conformance test");
949 assert_eq!(
950 left, right,
951 "De Morgan's first law failed at index {i}: \
952 NOT(A AND B) = {left:?}, (NOT A) OR (NOT B) = {right:?}"
953 );
954 }
955 }
956
957 if let (Ok(a_or_b), Ok(not_a), Ok(not_b)) = (
959 array.binary(bool_mask.clone(), Operator::Or),
960 array.not(),
961 bool_mask.not(),
962 ) {
963 let not_a_or_b = a_or_b
964 .not()
965 .vortex_expect("not should succeed in conformance test");
966 let not_a_and_not_b = not_a
967 .binary(not_b, Operator::And)
968 .vortex_expect("and should succeed in conformance test");
969
970 for i in 0..not_a_or_b.len() {
971 let left = not_a_or_b
972 .execute_scalar(i, ctx)
973 .vortex_expect("scalar_at should succeed in conformance test");
974 let right = not_a_and_not_b
975 .execute_scalar(i, ctx)
976 .vortex_expect("scalar_at should succeed in conformance test");
977 assert_eq!(
978 left, right,
979 "De Morgan's second law failed at index {i}: \
980 NOT(A OR B) = {left:?}, (NOT A) AND (NOT B) = {right:?}"
981 );
982 }
983 }
984}
985
986fn test_slice_aggregate_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1002 use crate::aggregate_fn::NumericalAggregateOpts;
1003 use crate::aggregate_fn::fns::min_max::min_max;
1004 use crate::aggregate_fn::fns::nan_count::nan_count;
1005 use crate::aggregate_fn::fns::sum::sum;
1006 use crate::dtype::DType;
1007
1008 let len = array.len();
1009 if len < 5 {
1010 return; }
1012
1013 let start = 1;
1015 let end = (len - 1).min(start + 10); let sliced = array
1019 .slice(start..end)
1020 .vortex_expect("slice should succeed in conformance test");
1021 let canonical = array
1022 .clone()
1023 .execute::<Canonical>(ctx)
1024 .vortex_expect("to_canonical failed");
1025 let canonical_sliced = canonical
1026 .into_array()
1027 .slice(start..end)
1028 .vortex_expect("slice should succeed in conformance test");
1029
1030 let sliced_invalid_count = sliced
1032 .invalid_count(ctx)
1033 .vortex_expect("invalid_count should succeed in conformance test");
1034 let canonical_invalid_count = canonical_sliced
1035 .invalid_count(ctx)
1036 .vortex_expect("invalid_count should succeed in conformance test");
1037 assert_eq!(
1038 sliced_invalid_count, canonical_invalid_count,
1039 "null_count on sliced array should match canonical. \
1040 Sliced: {sliced_invalid_count}, Canonical: {canonical_invalid_count}",
1041 );
1042
1043 if !matches!(array.dtype(), DType::Primitive(..)) {
1045 return;
1046 }
1047
1048 if let (Ok(slice_sum), Ok(canonical_sum)) = (sum(&sliced, ctx), sum(&canonical_sliced, ctx)) {
1049 assert_eq!(
1051 slice_sum, canonical_sum,
1052 "sum on sliced array should match canonical. \
1053 Sliced: {slice_sum:?}, Canonical: {canonical_sum:?}"
1054 );
1055 }
1056
1057 if let (Ok(slice_minmax), Ok(canonical_minmax)) = (
1059 min_max(&sliced, ctx, NumericalAggregateOpts::default()),
1060 min_max(&canonical_sliced, ctx, NumericalAggregateOpts::default()),
1061 ) {
1062 match (slice_minmax, canonical_minmax) {
1063 (Some(s_result), Some(c_result)) => {
1064 assert_eq!(
1065 s_result.min, c_result.min,
1066 "min on sliced array should match canonical. \
1067 Sliced: {:?}, Canonical: {:?}",
1068 s_result.min, c_result.min
1069 );
1070 assert_eq!(
1071 s_result.max, c_result.max,
1072 "max on sliced array should match canonical. \
1073 Sliced: {:?}, Canonical: {:?}",
1074 s_result.max, c_result.max
1075 );
1076 }
1077 (None, None) => {} _ => vortex_panic!("min_max results don't match"),
1079 }
1080 }
1081
1082 if array.dtype().is_float()
1084 && let (Ok(slice_nan_count), Ok(canonical_nan_count)) =
1085 (nan_count(&sliced, ctx), nan_count(&canonical_sliced, ctx))
1086 {
1087 assert_eq!(
1088 slice_nan_count, canonical_nan_count,
1089 "nan_count on sliced array should match canonical. \
1090 Sliced: {slice_nan_count}, Canonical: {canonical_nan_count}"
1091 );
1092 }
1093}
1094
1095fn widened_primitive_dtype(dtype: &DType) -> Option<DType> {
1096 let DType::Primitive(ptype, nullability) = dtype else {
1097 return None;
1098 };
1099 let widened = match ptype {
1100 PType::U8 => PType::U16,
1101 PType::U16 => PType::U32,
1102 PType::U32 => PType::U64,
1103 PType::U64 => return None,
1104 PType::I8 => PType::I16,
1105 PType::I16 => PType::I32,
1106 PType::I32 => PType::I64,
1107 PType::I64 => return None,
1108 PType::F16 => PType::F32,
1109 PType::F32 => PType::F64,
1110 PType::F64 => return None,
1111 };
1112 Some(DType::Primitive(widened, *nullability))
1113}
1114
1115fn test_cast_slice_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1131 let len = array.len();
1132 if len < 5 {
1133 return; }
1135
1136 let start = 2;
1138 let end = 7.min(len - 2).max(start + 1); let canonical = array
1142 .clone()
1143 .execute::<Canonical>(ctx)
1144 .vortex_expect("to_canonical failed")
1145 .into_array();
1146
1147 let target_dtypes = match array.dtype() {
1149 DType::Null => vec![],
1150 DType::Bool(nullability) => vec![
1151 DType::Primitive(PType::U8, *nullability),
1152 DType::Primitive(PType::I32, *nullability),
1153 ],
1154 DType::Primitive(ptype, nullability) => {
1155 let mut targets = vec![];
1156 let opposite_nullability = match nullability {
1158 Nullability::NonNullable => Nullability::Nullable,
1159 Nullability::Nullable => Nullability::NonNullable,
1160 };
1161 targets.push(DType::Primitive(*ptype, opposite_nullability));
1162
1163 match ptype {
1165 PType::U8 => {
1166 targets.push(DType::Primitive(PType::U16, *nullability));
1167 targets.push(DType::Primitive(PType::I16, *nullability));
1168 }
1169 PType::U16 => {
1170 targets.push(DType::Primitive(PType::U32, *nullability));
1171 targets.push(DType::Primitive(PType::I32, *nullability));
1172 }
1173 PType::U32 => {
1174 targets.push(DType::Primitive(PType::U64, *nullability));
1175 targets.push(DType::Primitive(PType::I64, *nullability));
1176 }
1177 PType::U64 => {
1178 targets.push(DType::Primitive(PType::F64, *nullability));
1179 }
1180 PType::I8 => {
1181 targets.push(DType::Primitive(PType::I16, *nullability));
1182 targets.push(DType::Primitive(PType::F32, *nullability));
1183 }
1184 PType::I16 => {
1185 targets.push(DType::Primitive(PType::I32, *nullability));
1186 targets.push(DType::Primitive(PType::F32, *nullability));
1187 }
1188 PType::I32 => {
1189 targets.push(DType::Primitive(PType::I64, *nullability));
1190 targets.push(DType::Primitive(PType::F64, *nullability));
1191 }
1192 PType::I64 => {
1193 targets.push(DType::Primitive(PType::F64, *nullability));
1194 }
1195 PType::F16 => {
1196 targets.push(DType::Primitive(PType::F32, *nullability));
1197 }
1198 PType::F32 => {
1199 targets.push(DType::Primitive(PType::F64, *nullability));
1200 targets.push(DType::Primitive(PType::I32, *nullability));
1201 }
1202 PType::F64 => {
1203 targets.push(DType::Primitive(PType::I64, *nullability));
1204 }
1205 }
1206 targets
1207 }
1208 DType::Decimal(decimal_type, nullability) => {
1209 let opposite = match nullability {
1210 Nullability::NonNullable => Nullability::Nullable,
1211 Nullability::Nullable => Nullability::NonNullable,
1212 };
1213 vec![DType::Decimal(*decimal_type, opposite)]
1214 }
1215 DType::Utf8(nullability) => {
1216 let opposite = match nullability {
1217 Nullability::NonNullable => Nullability::Nullable,
1218 Nullability::Nullable => Nullability::NonNullable,
1219 };
1220 vec![DType::Utf8(opposite), DType::Binary(*nullability)]
1221 }
1222 DType::Binary(nullability) => {
1223 let opposite = match nullability {
1224 Nullability::NonNullable => Nullability::Nullable,
1225 Nullability::Nullable => Nullability::NonNullable,
1226 };
1227 vec![
1228 DType::Binary(opposite),
1229 DType::Utf8(*nullability), ]
1231 }
1232 DType::List(element_type, nullability) => {
1233 let opposite = match nullability {
1234 Nullability::NonNullable => Nullability::Nullable,
1235 Nullability::Nullable => Nullability::NonNullable,
1236 };
1237 vec![DType::List(Arc::clone(element_type), opposite)]
1238 }
1239 DType::FixedSizeList(element_type, list_size, nullability) => {
1240 let opposite = match nullability {
1241 Nullability::NonNullable => Nullability::Nullable,
1242 Nullability::Nullable => Nullability::NonNullable,
1243 };
1244 vec![DType::FixedSizeList(
1245 Arc::clone(element_type),
1246 *list_size,
1247 opposite,
1248 )]
1249 }
1250 DType::Map(map_dtype, nullability) => {
1251 let opposite = match nullability {
1252 Nullability::NonNullable => Nullability::Nullable,
1253 Nullability::Nullable => Nullability::NonNullable,
1254 };
1255 let key_dtype = map_dtype.key_dtype();
1256 let value_dtype = map_dtype.value_dtype();
1257 let mut targets = vec![DType::Map(map_dtype.clone(), opposite)];
1258
1259 if let Some(widened_key) = widened_primitive_dtype(&key_dtype)
1260 && let Ok(dtype) = DType::map(
1261 widened_key,
1262 value_dtype.clone(),
1263 map_dtype.keys_sorted(),
1264 *nullability,
1265 )
1266 {
1267 targets.push(dtype);
1268 }
1269
1270 if let Some(widened_value) = widened_primitive_dtype(&value_dtype)
1271 && let Ok(dtype) = DType::map(
1272 key_dtype.clone(),
1273 widened_value,
1274 map_dtype.keys_sorted(),
1275 *nullability,
1276 )
1277 {
1278 targets.push(dtype);
1279 }
1280
1281 if map_dtype.keys_sorted()
1282 && let Ok(dtype) = DType::map(key_dtype, value_dtype, false, *nullability)
1283 {
1284 targets.push(dtype);
1285 }
1286
1287 targets
1288 }
1289 DType::Struct(fields, nullability) => {
1290 let opposite = match nullability {
1291 Nullability::NonNullable => Nullability::Nullable,
1292 Nullability::Nullable => Nullability::NonNullable,
1293 };
1294 vec![DType::Struct(fields.clone(), opposite)]
1295 }
1296 DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"),
1297 DType::Variant(_) => unimplemented!(),
1298 DType::Extension(_) => vec![], };
1300
1301 for target_dtype in target_dtypes {
1303 let sliced = array
1305 .slice(start..end)
1306 .vortex_expect("slice should succeed in conformance test");
1307
1308 let slice_then_cast = match sliced
1310 .cast(target_dtype.clone())
1311 .and_then(|a| a.execute::<Canonical>(ctx).map(|c| c.into_array()))
1312 {
1313 Ok(result) => result,
1314 Err(_) => continue, };
1316
1317 assert_eq!(
1319 slice_then_cast.len(),
1320 end - start,
1321 "Sliced and casted array should have length {}, but has {}",
1322 end - start,
1323 slice_then_cast.len()
1324 );
1325
1326 for i in 0..slice_then_cast.len() {
1328 let slice_cast_val = slice_then_cast
1329 .execute_scalar(i, ctx)
1330 .vortex_expect("scalar_at should succeed in conformance test");
1331
1332 let canonical_val = canonical
1334 .execute_scalar(start + i, ctx)
1335 .vortex_expect("scalar_at should succeed in conformance test");
1336
1337 let expected_val = match canonical_val.cast(&target_dtype) {
1339 Ok(val) => val,
1340 Err(_) => {
1341 break;
1344 }
1345 };
1346
1347 assert_eq!(
1348 slice_cast_val,
1349 expected_val,
1350 "Cast of sliced array produced incorrect value at index {i}. \
1351 Got: {slice_cast_val:?}, Expected: {expected_val:?} \
1352 (canonical value at index {}: {canonical_val:?})\n\
1353 This likely indicates the array encoding doesn't preserve offset information during cast.",
1354 start + i
1355 );
1356 }
1357
1358 let casted = match array
1360 .cast(target_dtype.clone())
1361 .and_then(|a| a.execute::<Canonical>(ctx).map(|c| c.into_array()))
1362 {
1363 Ok(result) => result,
1364 Err(_) => continue, };
1366 let cast_then_slice = casted
1367 .slice(start..end)
1368 .vortex_expect("slice should succeed in conformance test");
1369
1370 assert_eq!(
1372 slice_then_cast.len(),
1373 cast_then_slice.len(),
1374 "Slice-then-cast and cast-then-slice should produce arrays of the same length"
1375 );
1376
1377 for i in 0..slice_then_cast.len() {
1378 let slice_cast_val = slice_then_cast
1379 .execute_scalar(i, ctx)
1380 .vortex_expect("scalar_at should succeed in conformance test");
1381 let cast_slice_val = cast_then_slice
1382 .execute_scalar(i, ctx)
1383 .vortex_expect("scalar_at should succeed in conformance test");
1384 assert_eq!(
1385 slice_cast_val, cast_slice_val,
1386 "Slice-then-cast and cast-then-slice produced different values at index {i}. \
1387 Slice-then-cast: {slice_cast_val:?}, Cast-then-slice: {cast_slice_val:?}"
1388 );
1389 }
1390 }
1391}
1392
1393pub fn test_array_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1435 test_filter_take_consistency(array, ctx);
1437 test_double_mask_consistency(array, ctx);
1438 test_slice_filter_consistency(array, ctx);
1439 test_take_slice_consistency(array, ctx);
1440 test_cast_slice_consistency(array, ctx);
1441
1442 test_boolean_demorgan_consistency(array, ctx);
1444
1445 test_comparison_inverse_consistency(array, ctx);
1447 test_comparison_symmetry_consistency(array, ctx);
1448
1449 test_slice_aggregate_consistency(array, ctx);
1451
1452 test_filter_identity(array, ctx);
1454 test_mask_identity(array, ctx);
1455 test_take_preserves_properties(array, ctx);
1456
1457 test_filter_preserves_order(array, ctx);
1459 test_take_repeated_indices(array, ctx);
1460
1461 test_mask_filter_null_consistency(array, ctx);
1463 test_nullable_indices_consistency(array, ctx);
1464
1465 test_empty_operations_consistency(array);
1467 test_large_array_consistency(array, ctx);
1468}