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::IntoArray;
31use crate::LEGACY_SESSION;
32use crate::VortexSessionExecute;
33use crate::arrays::BoolArray;
34use crate::arrays::PrimitiveArray;
35use crate::builtins::ArrayBuiltins;
36use crate::dtype::DType;
37use crate::dtype::Nullability;
38use crate::dtype::PType;
39use crate::scalar_fn::fns::operators::Operator;
40
41fn test_filter_take_consistency(array: &ArrayRef) {
53 let len = array.len();
54 if len == 0 {
55 return;
56 }
57
58 let mask_pattern: BitBuffer = (0..len).map(|i| i % 3 != 1).collect();
60 let mask = Mask::from_buffer(mask_pattern.clone());
61
62 let filtered = array
64 .filter(mask)
65 .vortex_expect("filter should succeed in conformance test");
66
67 let indices: Vec<u64> = mask_pattern
69 .iter()
70 .enumerate()
71 .filter_map(|(i, v)| v.then_some(i as u64))
72 .collect();
73 let indices_array = PrimitiveArray::from_iter(indices).into_array();
74
75 let taken = array
77 .take(indices_array)
78 .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
92 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
93 .vortex_expect("scalar_at should succeed in conformance test");
94 let taken_val = taken
95 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
96 .vortex_expect("scalar_at should succeed in conformance test");
97 assert_eq!(
98 filtered_val, taken_val,
99 "Filter and take produced different values at index {i}. \
100 Filtered value: {filtered_val:?}, Taken value: {taken_val:?}"
101 );
102 }
103}
104
105fn test_double_mask_consistency(array: &ArrayRef) {
121 let len = array.len();
122 if len == 0 {
123 return;
124 }
125
126 let mask1: Mask = (0..len).map(|i| i % 3 == 0).collect();
128 let mask2: Mask = (0..len).map(|i| i % 2 == 0).collect();
129
130 let first_masked = array
132 .clone()
133 .mask((!&mask1).into_array())
134 .vortex_expect("mask should succeed in conformance test");
135 let double_masked = first_masked
136 .mask((!&mask2).into_array())
137 .vortex_expect("mask should succeed in conformance test");
138
139 let combined_pattern: BitBuffer = mask1
141 .to_bit_buffer()
142 .iter()
143 .zip(mask2.to_bit_buffer().iter())
144 .map(|(a, b)| a || b)
145 .collect();
146 let combined_mask = Mask::from_buffer(combined_pattern);
147
148 let directly_masked = array
150 .clone()
151 .mask((!&combined_mask).into_array())
152 .vortex_expect("mask should succeed in conformance test");
153
154 assert_eq!(
156 double_masked.len(),
157 directly_masked.len(),
158 "Sequential masking and combined masking should produce arrays of the same length. \
159 Sequential length: {}, Combined length: {}",
160 double_masked.len(),
161 directly_masked.len()
162 );
163
164 for i in 0..double_masked.len() {
165 let double_val = double_masked
166 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
167 .vortex_expect("scalar_at should succeed in conformance test");
168 let direct_val = directly_masked
169 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
170 .vortex_expect("scalar_at should succeed in conformance test");
171 assert_eq!(
172 double_val, direct_val,
173 "Sequential masking and combined masking produced different values at index {i}. \
174 Sequential masking value: {double_val:?}, Combined masking value: {direct_val:?}\n\
175 This likely indicates an issue with how masks are composed in the array implementation."
176 );
177 }
178}
179
180fn test_filter_identity(array: &ArrayRef) {
194 let len = array.len();
195 if len == 0 {
196 return;
197 }
198
199 let all_true_mask = Mask::new_true(len);
200 let filtered = array
201 .filter(all_true_mask)
202 .vortex_expect("filter should succeed in conformance test");
203
204 assert_eq!(
206 filtered.len(),
207 array.len(),
208 "Filtering with all-true mask should preserve array length. \
209 Original length: {}, Filtered length: {}",
210 array.len(),
211 filtered.len()
212 );
213
214 for i in 0..len {
215 let original_val = array
216 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
217 .vortex_expect("scalar_at should succeed in conformance test");
218 let filtered_val = filtered
219 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
220 .vortex_expect("scalar_at should succeed in conformance test");
221 assert_eq!(
222 filtered_val, original_val,
223 "Filtering with all-true mask should preserve all values. \
224 Value at index {i} changed from {original_val:?} to {filtered_val:?}"
225 );
226 }
227}
228
229fn test_mask_identity(array: &ArrayRef) {
243 let len = array.len();
244 if len == 0 {
245 return;
246 }
247
248 let all_false_mask = Mask::new_false(len);
249 let masked = array
250 .clone()
251 .mask((!&all_false_mask).into_array())
252 .vortex_expect("mask should succeed in conformance test");
253
254 assert_eq!(
256 masked.len(),
257 array.len(),
258 "Masking with all-false mask should preserve array length. \
259 Original length: {}, Masked length: {}",
260 array.len(),
261 masked.len()
262 );
263
264 assert!(
265 masked.dtype().is_nullable(),
266 "Mask operation should always produce a nullable array, but dtype is {}",
267 masked.dtype()
268 );
269
270 for i in 0..len {
271 let original_val = array
272 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
273 .vortex_expect("scalar_at should succeed in conformance test");
274 let masked_val = masked
275 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
276 .vortex_expect("scalar_at should succeed in conformance test");
277 let expected_val = original_val.clone().into_nullable();
278 assert_eq!(
279 masked_val, expected_val,
280 "Masking with all-false mask should preserve values (as nullable). \
281 Value at index {i}: original = {original_val:?}, masked = {masked_val:?}, expected = {expected_val:?}"
282 );
283 }
284}
285
286fn test_slice_filter_consistency(array: &ArrayRef) {
301 let len = array.len();
302 if len < 4 {
303 return; }
305
306 let mut mask_pattern = vec![false; len];
308 mask_pattern[1..4.min(len)].fill(true);
309
310 let mask = Mask::from_iter(mask_pattern);
311 let filtered = array
312 .filter(mask)
313 .vortex_expect("filter should succeed in conformance test");
314
315 let sliced = array
317 .slice(1..4.min(len))
318 .vortex_expect("slice should succeed in conformance test");
319
320 assert_eq!(
321 filtered.len(),
322 sliced.len(),
323 "Filter with contiguous mask and slice should produce same length. \
324 Filtered length: {}, Sliced length: {}",
325 filtered.len(),
326 sliced.len()
327 );
328
329 for i in 0..filtered.len() {
330 let filtered_val = filtered
331 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
332 .vortex_expect("scalar_at should succeed in conformance test");
333 let sliced_val = sliced
334 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
335 .vortex_expect("scalar_at should succeed in conformance test");
336 assert_eq!(
337 filtered_val, sliced_val,
338 "Filter with contiguous mask and slice produced different values at index {i}. \
339 Filtered value: {filtered_val:?}, Sliced value: {sliced_val:?}"
340 );
341 }
342}
343
344fn test_take_slice_consistency(array: &ArrayRef) {
358 let len = array.len();
359 if len < 3 {
360 return; }
362
363 let end = 4.min(len);
365 let indices = PrimitiveArray::from_iter((1..end).map(|i| i as u64)).into_array();
366 let taken = array
367 .take(indices)
368 .vortex_expect("take should succeed in conformance test");
369
370 let sliced = array
372 .slice(1..end)
373 .vortex_expect("slice should succeed in conformance test");
374
375 assert_eq!(
376 taken.len(),
377 sliced.len(),
378 "Take with sequential indices and slice should produce same length. \
379 Taken length: {}, Sliced length: {}",
380 taken.len(),
381 sliced.len()
382 );
383
384 for i in 0..taken.len() {
385 let taken_val = taken
386 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
387 .vortex_expect("scalar_at should succeed in conformance test");
388 let sliced_val = sliced
389 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
390 .vortex_expect("scalar_at should succeed in conformance test");
391 assert_eq!(
392 taken_val, sliced_val,
393 "Take with sequential indices and slice produced different values at index {i}. \
394 Taken value: {taken_val:?}, Sliced value: {sliced_val:?}"
395 );
396 }
397}
398
399fn test_filter_preserves_order(array: &ArrayRef) {
401 let len = array.len();
402 if len < 4 {
403 return;
404 }
405
406 let mask_pattern: Vec<bool> = (0..len).map(|i| i == 0 || i == 2 || i == 3).collect();
408 let mask = Mask::from_iter(mask_pattern);
409
410 let filtered = array
411 .filter(mask)
412 .vortex_expect("filter should succeed in conformance test");
413
414 assert_eq!(filtered.len(), 3.min(len));
416 if len >= 4 {
417 assert_eq!(
418 filtered
419 .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
420 .vortex_expect("scalar_at should succeed in conformance test"),
421 array
422 .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
423 .vortex_expect("scalar_at should succeed in conformance test")
424 );
425 assert_eq!(
426 filtered
427 .execute_scalar(1, &mut LEGACY_SESSION.create_execution_ctx())
428 .vortex_expect("scalar_at should succeed in conformance test"),
429 array
430 .execute_scalar(2, &mut LEGACY_SESSION.create_execution_ctx())
431 .vortex_expect("scalar_at should succeed in conformance test")
432 );
433 assert_eq!(
434 filtered
435 .execute_scalar(2, &mut LEGACY_SESSION.create_execution_ctx())
436 .vortex_expect("scalar_at should succeed in conformance test"),
437 array
438 .execute_scalar(3, &mut LEGACY_SESSION.create_execution_ctx())
439 .vortex_expect("scalar_at should succeed in conformance test")
440 );
441 }
442}
443
444fn test_take_repeated_indices(array: &ArrayRef) {
446 let len = array.len();
447 if len == 0 {
448 return;
449 }
450
451 let indices = PrimitiveArray::from_iter([0u64, 0, 0]).into_array();
453 let taken = array
454 .take(indices)
455 .vortex_expect("take should succeed in conformance test");
456
457 assert_eq!(taken.len(), 3);
458 for i in 0..3 {
459 assert_eq!(
460 taken
461 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
462 .vortex_expect("scalar_at should succeed in conformance test"),
463 array
464 .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
465 .vortex_expect("scalar_at should succeed in conformance test")
466 );
467 }
468}
469
470fn test_mask_filter_null_consistency(array: &ArrayRef) {
472 let len = array.len();
473 if len < 3 {
474 return;
475 }
476
477 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
479 let mask_array = Mask::from_iter(mask_pattern);
480 let masked = array
481 .clone()
482 .mask((!&mask_array).into_array())
483 .vortex_expect("mask should succeed in conformance test");
484
485 let filter_pattern: Vec<bool> = (0..len).map(|i| i % 2 != 0).collect();
487 let filter_mask = Mask::from_iter(filter_pattern);
488 let filtered = masked
489 .filter(filter_mask.clone())
490 .vortex_expect("filter should succeed in conformance test");
491
492 let direct_filtered = array
494 .filter(filter_mask)
495 .vortex_expect("filter should succeed in conformance test");
496
497 assert_eq!(filtered.len(), direct_filtered.len());
498 for i in 0..filtered.len() {
499 assert_eq!(
500 filtered
501 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
502 .vortex_expect("scalar_at should succeed in conformance test"),
503 direct_filtered
504 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
505 .vortex_expect("scalar_at should succeed in conformance test")
506 );
507 }
508}
509
510fn test_empty_operations_consistency(array: &ArrayRef) {
512 let len = array.len();
513
514 let empty_filter = array
516 .filter(Mask::new_false(len))
517 .vortex_expect("filter should succeed in conformance test");
518 assert_eq!(empty_filter.len(), 0);
519 assert_eq!(empty_filter.dtype(), array.dtype());
520
521 let empty_indices = PrimitiveArray::empty::<u64>(Nullability::NonNullable).into_array();
523 let empty_take = array
524 .take(empty_indices)
525 .vortex_expect("take should succeed in conformance test");
526 assert_eq!(empty_take.len(), 0);
527 assert_eq!(empty_take.dtype(), array.dtype());
528
529 if len > 0 {
531 let empty_slice = array
532 .slice(0..0)
533 .vortex_expect("slice should succeed in conformance test");
534 assert_eq!(empty_slice.len(), 0);
535 assert_eq!(empty_slice.dtype(), array.dtype());
536 }
537}
538
539fn test_take_preserves_properties(array: &ArrayRef) {
541 let len = array.len();
542 if len == 0 {
543 return;
544 }
545
546 let indices = PrimitiveArray::from_iter((0..len).map(|i| i as u64)).into_array();
548 let taken = array
549 .take(indices)
550 .vortex_expect("take should succeed in conformance test");
551
552 assert_eq!(taken.len(), array.len());
554 assert_eq!(taken.dtype(), array.dtype());
555 for i in 0..len {
556 assert_eq!(
557 taken
558 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
559 .vortex_expect("scalar_at should succeed in conformance test"),
560 array
561 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
562 .vortex_expect("scalar_at should succeed in conformance test")
563 );
564 }
565}
566
567fn test_nullable_indices_consistency(array: &ArrayRef) {
585 let len = array.len();
586 if len < 3 {
587 return; }
589
590 let indices = PrimitiveArray::from_option_iter([Some(0u64), None, Some(2u64)]).into_array();
592
593 let taken = array
594 .take(indices)
595 .vortex_expect("take should succeed in conformance test");
596
597 assert_eq!(
599 taken.len(),
600 3,
601 "Take with nullable indices should produce array of length 3, got {}",
602 taken.len()
603 );
604
605 assert!(
606 taken.dtype().is_nullable(),
607 "Take with nullable indices should produce nullable array, but dtype is {:?}",
608 taken.dtype()
609 );
610
611 let expected_0 = array
613 .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
614 .vortex_expect("scalar_at should succeed in conformance test")
615 .into_nullable();
616 let actual_0 = taken
617 .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
618 .vortex_expect("scalar_at should succeed in conformance test");
619 assert_eq!(
620 actual_0, expected_0,
621 "Take with nullable indices: element at position 0 should be from array index 0. \
622 Expected: {expected_0:?}, Actual: {actual_0:?}"
623 );
624
625 let actual_1 = taken
627 .execute_scalar(1, &mut LEGACY_SESSION.create_execution_ctx())
628 .vortex_expect("scalar_at should succeed in conformance test");
629 assert!(
630 actual_1.is_null(),
631 "Take with nullable indices: element at position 1 should be null, but got {actual_1:?}"
632 );
633
634 let expected_2 = array
636 .execute_scalar(2, &mut LEGACY_SESSION.create_execution_ctx())
637 .vortex_expect("scalar_at should succeed in conformance test")
638 .into_nullable();
639 let actual_2 = taken
640 .execute_scalar(2, &mut LEGACY_SESSION.create_execution_ctx())
641 .vortex_expect("scalar_at should succeed in conformance test");
642 assert_eq!(
643 actual_2, expected_2,
644 "Take with nullable indices: element at position 2 should be from array index 2. \
645 Expected: {expected_2:?}, Actual: {actual_2:?}"
646 );
647}
648
649fn test_large_array_consistency(array: &ArrayRef) {
651 let len = array.len();
652 if len < 1000 {
653 return;
654 }
655
656 let indices: Vec<u64> = (0..len).step_by(10).map(|i| i as u64).collect();
658 let indices_array = PrimitiveArray::from_iter(indices).into_array();
659 let taken = array
660 .take(indices_array)
661 .vortex_expect("take should succeed in conformance test");
662
663 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
665 let mask = Mask::from_iter(mask_pattern);
666 let filtered = array
667 .filter(mask)
668 .vortex_expect("filter should succeed in conformance test");
669
670 assert_eq!(taken.len(), filtered.len());
672 for i in 0..taken.len() {
673 assert_eq!(
674 taken
675 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
676 .vortex_expect("scalar_at should succeed in conformance test"),
677 filtered
678 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
679 .vortex_expect("scalar_at should succeed in conformance test")
680 );
681 }
682}
683
684fn test_comparison_inverse_consistency(array: &ArrayRef) {
701 let len = array.len();
702 if len == 0 {
703 return;
704 }
705
706 match array.dtype() {
708 DType::Null
709 | DType::List(..)
710 | DType::Struct(..)
711 | DType::Union(..)
712 | DType::Extension(_) => return,
713 _ => {}
714 }
715
716 let test_scalar = if len == 0 {
718 return;
719 } else {
720 array
721 .execute_scalar(len / 2, &mut LEGACY_SESSION.create_execution_ctx())
722 .vortex_expect("scalar_at should succeed in conformance test")
723 };
724
725 let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
727 if let (Ok(eq_result), Ok(neq_result)) = (
728 array
729 .clone()
730 .binary(const_array.clone().into_array(), Operator::Eq),
731 array
732 .clone()
733 .binary(const_array.clone().into_array(), Operator::NotEq),
734 ) {
735 let inverted_eq = eq_result
736 .not()
737 .vortex_expect("not should succeed in conformance test");
738
739 assert_eq!(
740 inverted_eq.len(),
741 neq_result.len(),
742 "Inverted Eq should have same length as NotEq"
743 );
744
745 for i in 0..inverted_eq.len() {
746 let inv_val = inverted_eq
747 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
748 .vortex_expect("scalar_at should succeed in conformance test");
749 let neq_val = neq_result
750 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
751 .vortex_expect("scalar_at should succeed in conformance test");
752 assert_eq!(
753 inv_val, neq_val,
754 "At index {i}: NOT(Eq) should equal NotEq. \
755 NOT(Eq) = {inv_val:?}, NotEq = {neq_val:?}"
756 );
757 }
758 }
759
760 if let (Ok(gt_result), Ok(lte_result)) = (
762 array
763 .clone()
764 .binary(const_array.clone().into_array(), Operator::Gt),
765 array
766 .clone()
767 .binary(const_array.clone().into_array(), Operator::Lte),
768 ) {
769 let inverted_gt = gt_result
770 .not()
771 .vortex_expect("not should succeed in conformance test");
772
773 for i in 0..inverted_gt.len() {
774 let inv_val = inverted_gt
775 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
776 .vortex_expect("scalar_at should succeed in conformance test");
777 let lte_val = lte_result
778 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
779 .vortex_expect("scalar_at should succeed in conformance test");
780 assert_eq!(
781 inv_val, lte_val,
782 "At index {i}: NOT(Gt) should equal Lte. \
783 NOT(Gt) = {inv_val:?}, Lte = {lte_val:?}"
784 );
785 }
786 }
787
788 if let (Ok(lt_result), Ok(gte_result)) = (
790 array
791 .clone()
792 .binary(const_array.clone().into_array(), Operator::Lt),
793 array
794 .clone()
795 .binary(const_array.into_array(), Operator::Gte),
796 ) {
797 let inverted_lt = lt_result
798 .not()
799 .vortex_expect("not should succeed in conformance test");
800
801 for i in 0..inverted_lt.len() {
802 let inv_val = inverted_lt
803 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
804 .vortex_expect("scalar_at should succeed in conformance test");
805 let gte_val = gte_result
806 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
807 .vortex_expect("scalar_at should succeed in conformance test");
808 assert_eq!(
809 inv_val, gte_val,
810 "At index {i}: NOT(Lt) should equal Gte. \
811 NOT(Lt) = {inv_val:?}, Gte = {gte_val:?}"
812 );
813 }
814 }
815}
816
817fn test_comparison_symmetry_consistency(array: &ArrayRef) {
833 let len = array.len();
834 if len == 0 {
835 return;
836 }
837
838 match array.dtype() {
840 DType::Null
841 | DType::List(..)
842 | DType::Struct(..)
843 | DType::Union(..)
844 | DType::Extension(_) => return,
845 _ => {}
846 }
847
848 let test_scalar = if len == 2 {
850 return;
851 } else {
852 array
853 .execute_scalar(len / 2, &mut LEGACY_SESSION.create_execution_ctx())
854 .vortex_expect("scalar_at should succeed in conformance test")
855 };
856
857 let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
859
860 if let (Ok(arr_gt_scalar), Ok(scalar_lt_arr)) = (
862 array
863 .clone()
864 .binary(const_array.clone().into_array(), Operator::Gt),
865 const_array
866 .clone()
867 .into_array()
868 .binary(array.clone(), Operator::Lt),
869 ) {
870 assert_eq!(
871 arr_gt_scalar.len(),
872 scalar_lt_arr.len(),
873 "Symmetric comparisons should have same length"
874 );
875
876 for i in 0..arr_gt_scalar.len() {
877 let arr_gt = arr_gt_scalar
878 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
879 .vortex_expect("scalar_at should succeed in conformance test");
880 let scalar_lt = scalar_lt_arr
881 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
882 .vortex_expect("scalar_at should succeed in conformance test");
883 assert_eq!(
884 arr_gt, scalar_lt,
885 "At index {i}: (array > scalar) should equal (scalar < array). \
886 array > scalar = {arr_gt:?}, scalar < array = {scalar_lt:?}"
887 );
888 }
889 }
890
891 if let (Ok(arr_eq_scalar), Ok(scalar_eq_arr)) = (
893 array
894 .clone()
895 .binary(const_array.clone().into_array(), Operator::Eq),
896 const_array.into_array().binary(array.clone(), Operator::Eq),
897 ) {
898 for i in 0..arr_eq_scalar.len() {
899 let arr_eq = arr_eq_scalar
900 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
901 .vortex_expect("scalar_at should succeed in conformance test");
902 let scalar_eq = scalar_eq_arr
903 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
904 .vortex_expect("scalar_at should succeed in conformance test");
905 assert_eq!(
906 arr_eq, scalar_eq,
907 "At index {i}: (array == scalar) should equal (scalar == array). \
908 array == scalar = {arr_eq:?}, scalar == array = {scalar_eq:?}"
909 );
910 }
911 }
912}
913
914fn test_boolean_demorgan_consistency(array: &ArrayRef) {
931 if !matches!(array.dtype(), DType::Bool(_)) {
932 return;
933 }
934
935 let bool_mask = {
936 let mask_pattern: Vec<bool> = (0..array.len()).map(|i| i % 3 == 0).collect();
937 BoolArray::from_iter(mask_pattern)
938 };
939 let bool_mask = bool_mask.into_array();
940
941 if let (Ok(a_and_b), Ok(not_a), Ok(not_b)) = (
943 array.clone().binary(bool_mask.clone(), Operator::And),
944 array.not(),
945 bool_mask.not(),
946 ) {
947 let not_a_and_b = a_and_b
948 .not()
949 .vortex_expect("not should succeed in conformance test");
950 let not_a_or_not_b = not_a
951 .binary(not_b, Operator::Or)
952 .vortex_expect("or should succeed in conformance test");
953
954 assert_eq!(
955 not_a_and_b.len(),
956 not_a_or_not_b.len(),
957 "De Morgan's law results should have same length"
958 );
959
960 for i in 0..not_a_and_b.len() {
961 let left = not_a_and_b
962 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
963 .vortex_expect("scalar_at should succeed in conformance test");
964 let right = not_a_or_not_b
965 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
966 .vortex_expect("scalar_at should succeed in conformance test");
967 assert_eq!(
968 left, right,
969 "De Morgan's first law failed at index {i}: \
970 NOT(A AND B) = {left:?}, (NOT A) OR (NOT B) = {right:?}"
971 );
972 }
973 }
974
975 if let (Ok(a_or_b), Ok(not_a), Ok(not_b)) = (
977 array.clone().binary(bool_mask.clone(), Operator::Or),
978 array.not(),
979 bool_mask.not(),
980 ) {
981 let not_a_or_b = a_or_b
982 .not()
983 .vortex_expect("not should succeed in conformance test");
984 let not_a_and_not_b = not_a
985 .binary(not_b, Operator::And)
986 .vortex_expect("and should succeed in conformance test");
987
988 for i in 0..not_a_or_b.len() {
989 let left = not_a_or_b
990 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
991 .vortex_expect("scalar_at should succeed in conformance test");
992 let right = not_a_and_not_b
993 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
994 .vortex_expect("scalar_at should succeed in conformance test");
995 assert_eq!(
996 left, right,
997 "De Morgan's second law failed at index {i}: \
998 NOT(A OR B) = {left:?}, (NOT A) AND (NOT B) = {right:?}"
999 );
1000 }
1001 }
1002}
1003
1004fn test_slice_aggregate_consistency(array: &ArrayRef) {
1020 use crate::aggregate_fn::fns::min_max::min_max;
1021 use crate::aggregate_fn::fns::nan_count::nan_count;
1022 use crate::aggregate_fn::fns::sum::sum;
1023 use crate::dtype::DType;
1024
1025 let mut ctx = LEGACY_SESSION.create_execution_ctx();
1026
1027 let len = array.len();
1028 if len < 5 {
1029 return; }
1031
1032 let start = 1;
1034 let end = (len - 1).min(start + 10); let sliced = array
1038 .slice(start..end)
1039 .vortex_expect("slice should succeed in conformance test");
1040 #[expect(deprecated)]
1041 let canonical = array.to_canonical().vortex_expect("to_canonical failed");
1042 let canonical_sliced = canonical
1043 .into_array()
1044 .slice(start..end)
1045 .vortex_expect("slice should succeed in conformance test");
1046
1047 let sliced_invalid_count = sliced
1049 .invalid_count(&mut ctx)
1050 .vortex_expect("invalid_count should succeed in conformance test");
1051 let canonical_invalid_count = canonical_sliced
1052 .invalid_count(&mut ctx)
1053 .vortex_expect("invalid_count should succeed in conformance test");
1054 assert_eq!(
1055 sliced_invalid_count, canonical_invalid_count,
1056 "null_count on sliced array should match canonical. \
1057 Sliced: {sliced_invalid_count}, Canonical: {canonical_invalid_count}",
1058 );
1059
1060 if !matches!(array.dtype(), DType::Primitive(..)) {
1062 return;
1063 }
1064
1065 if let (Ok(slice_sum), Ok(canonical_sum)) =
1066 (sum(&sliced, &mut ctx), sum(&canonical_sliced, &mut ctx))
1067 {
1068 assert_eq!(
1070 slice_sum, canonical_sum,
1071 "sum on sliced array should match canonical. \
1072 Sliced: {slice_sum:?}, Canonical: {canonical_sum:?}"
1073 );
1074 }
1075
1076 if let (Ok(slice_minmax), Ok(canonical_minmax)) = (
1078 min_max(&sliced, &mut ctx),
1079 min_max(&canonical_sliced, &mut ctx),
1080 ) {
1081 match (slice_minmax, canonical_minmax) {
1082 (Some(s_result), Some(c_result)) => {
1083 assert_eq!(
1084 s_result.min, c_result.min,
1085 "min on sliced array should match canonical. \
1086 Sliced: {:?}, Canonical: {:?}",
1087 s_result.min, c_result.min
1088 );
1089 assert_eq!(
1090 s_result.max, c_result.max,
1091 "max on sliced array should match canonical. \
1092 Sliced: {:?}, Canonical: {:?}",
1093 s_result.max, c_result.max
1094 );
1095 }
1096 (None, None) => {} _ => vortex_panic!("min_max results don't match"),
1098 }
1099 }
1100
1101 if array.dtype().is_float()
1103 && let (Ok(slice_nan_count), Ok(canonical_nan_count)) = (
1104 nan_count(&sliced, &mut ctx),
1105 nan_count(&canonical_sliced, &mut ctx),
1106 )
1107 {
1108 assert_eq!(
1109 slice_nan_count, canonical_nan_count,
1110 "nan_count on sliced array should match canonical. \
1111 Sliced: {slice_nan_count}, Canonical: {canonical_nan_count}"
1112 );
1113 }
1114}
1115
1116fn test_cast_slice_consistency(array: &ArrayRef) {
1132 let len = array.len();
1133 if len < 5 {
1134 return; }
1136
1137 let start = 2;
1139 let end = 7.min(len - 2).max(start + 1); #[expect(deprecated)]
1143 let canonical = array.to_canonical().vortex_expect("to_canonical failed");
1144
1145 let target_dtypes = match array.dtype() {
1147 DType::Null => vec![],
1148 DType::Bool(nullability) => vec![
1149 DType::Primitive(PType::U8, *nullability),
1150 DType::Primitive(PType::I32, *nullability),
1151 ],
1152 DType::Primitive(ptype, nullability) => {
1153 let mut targets = vec![];
1154 let opposite_nullability = match nullability {
1156 Nullability::NonNullable => Nullability::Nullable,
1157 Nullability::Nullable => Nullability::NonNullable,
1158 };
1159 targets.push(DType::Primitive(*ptype, opposite_nullability));
1160
1161 match ptype {
1163 PType::U8 => {
1164 targets.push(DType::Primitive(PType::U16, *nullability));
1165 targets.push(DType::Primitive(PType::I16, *nullability));
1166 }
1167 PType::U16 => {
1168 targets.push(DType::Primitive(PType::U32, *nullability));
1169 targets.push(DType::Primitive(PType::I32, *nullability));
1170 }
1171 PType::U32 => {
1172 targets.push(DType::Primitive(PType::U64, *nullability));
1173 targets.push(DType::Primitive(PType::I64, *nullability));
1174 }
1175 PType::U64 => {
1176 targets.push(DType::Primitive(PType::F64, *nullability));
1177 }
1178 PType::I8 => {
1179 targets.push(DType::Primitive(PType::I16, *nullability));
1180 targets.push(DType::Primitive(PType::F32, *nullability));
1181 }
1182 PType::I16 => {
1183 targets.push(DType::Primitive(PType::I32, *nullability));
1184 targets.push(DType::Primitive(PType::F32, *nullability));
1185 }
1186 PType::I32 => {
1187 targets.push(DType::Primitive(PType::I64, *nullability));
1188 targets.push(DType::Primitive(PType::F64, *nullability));
1189 }
1190 PType::I64 => {
1191 targets.push(DType::Primitive(PType::F64, *nullability));
1192 }
1193 PType::F16 => {
1194 targets.push(DType::Primitive(PType::F32, *nullability));
1195 }
1196 PType::F32 => {
1197 targets.push(DType::Primitive(PType::F64, *nullability));
1198 targets.push(DType::Primitive(PType::I32, *nullability));
1199 }
1200 PType::F64 => {
1201 targets.push(DType::Primitive(PType::I64, *nullability));
1202 }
1203 }
1204 targets
1205 }
1206 DType::Decimal(decimal_type, nullability) => {
1207 let opposite = match nullability {
1208 Nullability::NonNullable => Nullability::Nullable,
1209 Nullability::Nullable => Nullability::NonNullable,
1210 };
1211 vec![DType::Decimal(*decimal_type, opposite)]
1212 }
1213 DType::Utf8(nullability) => {
1214 let opposite = match nullability {
1215 Nullability::NonNullable => Nullability::Nullable,
1216 Nullability::Nullable => Nullability::NonNullable,
1217 };
1218 vec![DType::Utf8(opposite), DType::Binary(*nullability)]
1219 }
1220 DType::Binary(nullability) => {
1221 let opposite = match nullability {
1222 Nullability::NonNullable => Nullability::Nullable,
1223 Nullability::Nullable => Nullability::NonNullable,
1224 };
1225 vec![
1226 DType::Binary(opposite),
1227 DType::Utf8(*nullability), ]
1229 }
1230 DType::List(element_type, nullability) => {
1231 let opposite = match nullability {
1232 Nullability::NonNullable => Nullability::Nullable,
1233 Nullability::Nullable => Nullability::NonNullable,
1234 };
1235 vec![DType::List(Arc::clone(element_type), opposite)]
1236 }
1237 DType::FixedSizeList(element_type, list_size, nullability) => {
1238 let opposite = match nullability {
1239 Nullability::NonNullable => Nullability::Nullable,
1240 Nullability::Nullable => Nullability::NonNullable,
1241 };
1242 vec![DType::FixedSizeList(
1243 Arc::clone(element_type),
1244 *list_size,
1245 opposite,
1246 )]
1247 }
1248 DType::Struct(fields, nullability) => {
1249 let opposite = match nullability {
1250 Nullability::NonNullable => Nullability::Nullable,
1251 Nullability::Nullable => Nullability::NonNullable,
1252 };
1253 vec![DType::Struct(fields.clone(), opposite)]
1254 }
1255 DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"),
1256 DType::Variant(_) => unimplemented!(),
1257 DType::Extension(_) => vec![], };
1259
1260 for target_dtype in target_dtypes {
1262 let sliced = array
1264 .slice(start..end)
1265 .vortex_expect("slice should succeed in conformance test");
1266
1267 let slice_then_cast = match sliced.cast(target_dtype.clone()).and_then(|a| {
1269 #[expect(deprecated)]
1270 a.to_canonical().map(|c| c.into_array())
1271 }) {
1272 Ok(result) => result,
1273 Err(_) => continue, };
1275
1276 assert_eq!(
1278 slice_then_cast.len(),
1279 end - start,
1280 "Sliced and casted array should have length {}, but has {}",
1281 end - start,
1282 slice_then_cast.len()
1283 );
1284
1285 for i in 0..slice_then_cast.len() {
1287 let slice_cast_val = slice_then_cast
1288 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
1289 .vortex_expect("scalar_at should succeed in conformance test");
1290
1291 let canonical_val = canonical
1293 .clone()
1294 .into_array()
1295 .execute_scalar(start + i, &mut LEGACY_SESSION.create_execution_ctx())
1296 .vortex_expect("scalar_at should succeed in conformance test");
1297
1298 let expected_val = match canonical_val.cast(&target_dtype) {
1300 Ok(val) => val,
1301 Err(_) => {
1302 break;
1305 }
1306 };
1307
1308 assert_eq!(
1309 slice_cast_val,
1310 expected_val,
1311 "Cast of sliced array produced incorrect value at index {i}. \
1312 Got: {slice_cast_val:?}, Expected: {expected_val:?} \
1313 (canonical value at index {}: {canonical_val:?})\n\
1314 This likely indicates the array encoding doesn't preserve offset information during cast.",
1315 start + i
1316 );
1317 }
1318
1319 let casted = match array.clone().cast(target_dtype.clone()).and_then(|a| {
1321 #[expect(deprecated)]
1322 a.to_canonical().map(|c| c.into_array())
1323 }) {
1324 Ok(result) => result,
1325 Err(_) => continue, };
1327 let cast_then_slice = casted
1328 .slice(start..end)
1329 .vortex_expect("slice should succeed in conformance test");
1330
1331 assert_eq!(
1333 slice_then_cast.len(),
1334 cast_then_slice.len(),
1335 "Slice-then-cast and cast-then-slice should produce arrays of the same length"
1336 );
1337
1338 for i in 0..slice_then_cast.len() {
1339 let slice_cast_val = slice_then_cast
1340 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
1341 .vortex_expect("scalar_at should succeed in conformance test");
1342 let cast_slice_val = cast_then_slice
1343 .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
1344 .vortex_expect("scalar_at should succeed in conformance test");
1345 assert_eq!(
1346 slice_cast_val, cast_slice_val,
1347 "Slice-then-cast and cast-then-slice produced different values at index {i}. \
1348 Slice-then-cast: {slice_cast_val:?}, Cast-then-slice: {cast_slice_val:?}"
1349 );
1350 }
1351 }
1352}
1353
1354pub fn test_array_consistency(array: &ArrayRef) {
1396 test_filter_take_consistency(array);
1398 test_double_mask_consistency(array);
1399 test_slice_filter_consistency(array);
1400 test_take_slice_consistency(array);
1401 test_cast_slice_consistency(array);
1402
1403 test_boolean_demorgan_consistency(array);
1405
1406 test_comparison_inverse_consistency(array);
1408 test_comparison_symmetry_consistency(array);
1409
1410 test_slice_aggregate_consistency(array);
1412
1413 test_filter_identity(array);
1415 test_mask_identity(array);
1416 test_take_preserves_properties(array);
1417
1418 test_filter_preserves_order(array);
1420 test_take_repeated_indices(array);
1421
1422 test_mask_filter_null_consistency(array);
1424 test_nullable_indices_consistency(array);
1425
1426 test_empty_operations_consistency(array);
1428 test_large_array_consistency(array);
1429}