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::RecursiveCanonical;
34use crate::aggregate_fn::NumericalAggregateOpts;
35use crate::aggregate_fn::fns::min_max::min_max;
36use crate::aggregate_fn::fns::nan_count::nan_count;
37use crate::aggregate_fn::fns::sum::sum;
38use crate::arrays::BoolArray;
39use crate::arrays::ConstantArray;
40use crate::arrays::PrimitiveArray;
41use crate::assert_arrays_eq;
42use crate::builtins::ArrayBuiltins;
43use crate::dtype::DType;
44use crate::dtype::Nullability;
45use crate::dtype::PType;
46use crate::scalar_fn::fns::operators::Operator;
47
48fn test_filter_take_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
60 let len = array.len();
61 if len == 0 {
62 return;
63 }
64
65 let mask_pattern: BitBuffer = (0..len).map(|i| i % 3 != 1).collect();
67 let mask = Mask::from_buffer(mask_pattern.clone());
68
69 let filtered = array
71 .filter(mask)
72 .vortex_expect("filter should succeed in conformance test")
73 .execute::<RecursiveCanonical>(ctx)
74 .vortex_expect("batch execution should succeed in conformance test")
75 .0
76 .into_array();
77
78 let indices: Vec<u64> = mask_pattern
80 .iter()
81 .enumerate()
82 .filter_map(|(i, v)| v.then_some(i as u64))
83 .collect();
84 let indices_array = PrimitiveArray::from_iter(indices).into_array();
85
86 let taken = array
88 .take(indices_array)
89 .vortex_expect("take should succeed in conformance test")
90 .execute::<RecursiveCanonical>(ctx)
91 .vortex_expect("batch execution should succeed in conformance test")
92 .0
93 .into_array();
94
95 assert_eq!(
97 filtered.len(),
98 taken.len(),
99 "Filter and take should produce arrays of the same length. \
100 Filtered length: {}, Taken length: {}",
101 filtered.len(),
102 taken.len()
103 );
104
105 for i in 0..filtered.len() {
106 let filtered_val = filtered
107 .execute_scalar(i, ctx)
108 .vortex_expect("scalar_at should succeed in conformance test");
109 let taken_val = taken
110 .execute_scalar(i, ctx)
111 .vortex_expect("scalar_at should succeed in conformance test");
112 assert_eq!(
113 filtered_val, taken_val,
114 "Filter and take produced different values at index {i}. \
115 Filtered value: {filtered_val:?}, Taken value: {taken_val:?}"
116 );
117 }
118}
119
120fn test_double_mask_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
136 let len = array.len();
137 if len == 0 {
138 return;
139 }
140
141 let mask1: Mask = (0..len).map(|i| i % 3 == 0).collect();
143 let mask2: Mask = (0..len).map(|i| i % 2 == 0).collect();
144
145 let first_masked = array
147 .clone()
148 .mask((!&mask1).into_array())
149 .vortex_expect("mask should succeed in conformance test");
150 let double_masked = first_masked
151 .mask((!&mask2).into_array())
152 .vortex_expect("mask should succeed in conformance test")
153 .execute::<RecursiveCanonical>(ctx)
154 .vortex_expect("batch execution should succeed in conformance test")
155 .0
156 .into_array();
157
158 let combined_pattern: BitBuffer = mask1
160 .to_bit_buffer()
161 .iter()
162 .zip(mask2.to_bit_buffer().iter())
163 .map(|(a, b)| a || b)
164 .collect();
165 let combined_mask = Mask::from_buffer(combined_pattern);
166
167 let directly_masked = array
169 .clone()
170 .mask((!&combined_mask).into_array())
171 .vortex_expect("mask should succeed in conformance test")
172 .execute::<RecursiveCanonical>(ctx)
173 .vortex_expect("batch execution should succeed in conformance test")
174 .0
175 .into_array();
176
177 assert_eq!(
179 double_masked.len(),
180 directly_masked.len(),
181 "Sequential masking and combined masking should produce arrays of the same length. \
182 Sequential length: {}, Combined length: {}",
183 double_masked.len(),
184 directly_masked.len()
185 );
186
187 for i in 0..double_masked.len() {
188 let double_val = double_masked
189 .execute_scalar(i, ctx)
190 .vortex_expect("scalar_at should succeed in conformance test");
191 let direct_val = directly_masked
192 .execute_scalar(i, ctx)
193 .vortex_expect("scalar_at should succeed in conformance test");
194 assert_eq!(
195 double_val, direct_val,
196 "Sequential masking and combined masking produced different values at index {i}. \
197 Sequential masking value: {double_val:?}, Combined masking value: {direct_val:?}\n\
198 This likely indicates an issue with how masks are composed in the array implementation."
199 );
200 }
201}
202
203fn test_filter_identity(array: &ArrayRef, ctx: &mut ExecutionCtx) {
217 let len = array.len();
218 if len == 0 {
219 return;
220 }
221
222 let all_true_mask = Mask::new_true(len);
223 let filtered = array
224 .filter(all_true_mask)
225 .vortex_expect("filter should succeed in conformance test")
226 .execute::<RecursiveCanonical>(ctx)
227 .vortex_expect("batch execution should succeed in conformance test")
228 .0
229 .into_array();
230
231 assert_eq!(
233 filtered.len(),
234 array.len(),
235 "Filtering with all-true mask should preserve array length. \
236 Original length: {}, Filtered length: {}",
237 array.len(),
238 filtered.len()
239 );
240
241 for i in 0..len {
242 let original_val = array
243 .execute_scalar(i, ctx)
244 .vortex_expect("scalar_at should succeed in conformance test");
245 let filtered_val = filtered
246 .execute_scalar(i, ctx)
247 .vortex_expect("scalar_at should succeed in conformance test");
248 assert_eq!(
249 filtered_val, original_val,
250 "Filtering with all-true mask should preserve all values. \
251 Value at index {i} changed from {original_val:?} to {filtered_val:?}"
252 );
253 }
254}
255
256fn test_slice_filter_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
271 let len = array.len();
272 if len < 4 {
273 return; }
275
276 let mut mask_pattern = vec![false; len];
278 mask_pattern[1..4.min(len)].fill(true);
279
280 let mask = Mask::from_iter(mask_pattern);
281 let filtered = array
282 .filter(mask)
283 .vortex_expect("filter should succeed in conformance test")
284 .execute::<RecursiveCanonical>(ctx)
285 .vortex_expect("batch execution should succeed in conformance test")
286 .0
287 .into_array();
288
289 let sliced = array
291 .slice(1..4.min(len))
292 .vortex_expect("slice should succeed in conformance test")
293 .execute::<RecursiveCanonical>(ctx)
294 .vortex_expect("batch execution should succeed in conformance test")
295 .0
296 .into_array();
297
298 assert_eq!(
299 filtered.len(),
300 sliced.len(),
301 "Filter with contiguous mask and slice should produce same length. \
302 Filtered length: {}, Sliced length: {}",
303 filtered.len(),
304 sliced.len()
305 );
306
307 for i in 0..filtered.len() {
308 let filtered_val = filtered
309 .execute_scalar(i, ctx)
310 .vortex_expect("scalar_at should succeed in conformance test");
311 let sliced_val = sliced
312 .execute_scalar(i, ctx)
313 .vortex_expect("scalar_at should succeed in conformance test");
314 assert_eq!(
315 filtered_val, sliced_val,
316 "Filter with contiguous mask and slice produced different values at index {i}. \
317 Filtered value: {filtered_val:?}, Sliced value: {sliced_val:?}"
318 );
319 }
320}
321
322fn test_take_slice_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
336 let len = array.len();
337 if len < 3 {
338 return; }
340
341 let end = 4.min(len);
343 let indices = PrimitiveArray::from_iter((1..end).map(|i| i as u64)).into_array();
344 let taken = array
345 .take(indices)
346 .vortex_expect("take should succeed in conformance test")
347 .execute::<RecursiveCanonical>(ctx)
348 .vortex_expect("batch execution should succeed in conformance test")
349 .0
350 .into_array();
351
352 let sliced = array
354 .slice(1..end)
355 .vortex_expect("slice should succeed in conformance test")
356 .execute::<RecursiveCanonical>(ctx)
357 .vortex_expect("batch execution should succeed in conformance test")
358 .0
359 .into_array();
360
361 assert_eq!(
362 taken.len(),
363 sliced.len(),
364 "Take with sequential indices and slice should produce same length. \
365 Taken length: {}, Sliced length: {}",
366 taken.len(),
367 sliced.len()
368 );
369
370 for i in 0..taken.len() {
371 let taken_val = taken
372 .execute_scalar(i, ctx)
373 .vortex_expect("scalar_at should succeed in conformance test");
374 let sliced_val = sliced
375 .execute_scalar(i, ctx)
376 .vortex_expect("scalar_at should succeed in conformance test");
377 assert_eq!(
378 taken_val, sliced_val,
379 "Take with sequential indices and slice produced different values at index {i}. \
380 Taken value: {taken_val:?}, Sliced value: {sliced_val:?}"
381 );
382 }
383}
384
385fn test_filter_preserves_order(array: &ArrayRef, ctx: &mut ExecutionCtx) {
387 let len = array.len();
388 if len < 4 {
389 return;
390 }
391
392 let mask_pattern: Vec<bool> = (0..len).map(|i| i == 0 || i == 2 || i == 3).collect();
394 let mask = Mask::from_iter(mask_pattern);
395
396 let filtered = array
397 .filter(mask)
398 .vortex_expect("filter should succeed in conformance test")
399 .execute::<RecursiveCanonical>(ctx)
400 .vortex_expect("batch execution should succeed in conformance test")
401 .0
402 .into_array();
403
404 assert_eq!(filtered.len(), 3.min(len));
406 if len >= 4 {
407 assert_eq!(
408 filtered
409 .execute_scalar(0, ctx)
410 .vortex_expect("scalar_at should succeed in conformance test"),
411 array
412 .execute_scalar(0, ctx)
413 .vortex_expect("scalar_at should succeed in conformance test")
414 );
415 assert_eq!(
416 filtered
417 .execute_scalar(1, ctx)
418 .vortex_expect("scalar_at should succeed in conformance test"),
419 array
420 .execute_scalar(2, ctx)
421 .vortex_expect("scalar_at should succeed in conformance test")
422 );
423 assert_eq!(
424 filtered
425 .execute_scalar(2, ctx)
426 .vortex_expect("scalar_at should succeed in conformance test"),
427 array
428 .execute_scalar(3, ctx)
429 .vortex_expect("scalar_at should succeed in conformance test")
430 );
431 }
432}
433
434fn test_mask_filter_null_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
436 let len = array.len();
437 if len < 3 {
438 return;
439 }
440
441 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
443 let mask_array = Mask::from_iter(mask_pattern);
444 let masked = array
445 .clone()
446 .mask((!&mask_array).into_array())
447 .vortex_expect("mask should succeed in conformance test");
448
449 let filter_pattern: Vec<bool> = (0..len).map(|i| i % 2 != 0).collect();
451 let filter_mask = Mask::from_iter(filter_pattern);
452 let filtered = masked
453 .filter(filter_mask.clone())
454 .vortex_expect("filter should succeed in conformance test")
455 .execute::<RecursiveCanonical>(ctx)
456 .vortex_expect("batch execution should succeed in conformance test")
457 .0
458 .into_array();
459
460 let direct_filtered = array
462 .filter(filter_mask)
463 .vortex_expect("filter should succeed in conformance test")
464 .execute::<RecursiveCanonical>(ctx)
465 .vortex_expect("batch execution should succeed in conformance test")
466 .0
467 .into_array();
468
469 assert_eq!(filtered.len(), direct_filtered.len());
470 for i in 0..filtered.len() {
471 assert_eq!(
472 filtered
473 .execute_scalar(i, ctx)
474 .vortex_expect("scalar_at should succeed in conformance test"),
475 direct_filtered
476 .execute_scalar(i, ctx)
477 .vortex_expect("scalar_at should succeed in conformance test")
478 );
479 }
480}
481
482fn test_empty_operations_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
484 let len = array.len();
485
486 let empty_filter = array
488 .filter(Mask::new_false(len))
489 .vortex_expect("filter should succeed in conformance test")
490 .execute::<RecursiveCanonical>(ctx)
491 .vortex_expect("batch execution should succeed in conformance test")
492 .0
493 .into_array();
494 assert_eq!(empty_filter.len(), 0);
495 assert_eq!(empty_filter.dtype(), array.dtype());
496
497 let empty_indices = PrimitiveArray::empty::<u64>(Nullability::NonNullable).into_array();
499 let empty_take = array
500 .take(empty_indices)
501 .vortex_expect("take should succeed in conformance test")
502 .execute::<RecursiveCanonical>(ctx)
503 .vortex_expect("batch execution should succeed in conformance test")
504 .0
505 .into_array();
506 assert_eq!(empty_take.len(), 0);
507 assert_eq!(empty_take.dtype(), array.dtype());
508
509 if len > 0 {
511 let empty_slice = array
512 .slice(0..0)
513 .vortex_expect("slice should succeed in conformance test")
514 .execute::<RecursiveCanonical>(ctx)
515 .vortex_expect("batch execution should succeed in conformance test")
516 .0
517 .into_array();
518 assert_eq!(empty_slice.len(), 0);
519 assert_eq!(empty_slice.dtype(), array.dtype());
520 }
521}
522
523fn test_nullable_indices_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
541 let len = array.len();
542 if len < 3 {
543 return; }
545
546 let indices = PrimitiveArray::from_option_iter([Some(0u64), None, Some(2u64)]).into_array();
548
549 let taken = array
550 .take(indices)
551 .vortex_expect("take should succeed in conformance test")
552 .execute::<RecursiveCanonical>(ctx)
553 .vortex_expect("batch execution should succeed in conformance test")
554 .0
555 .into_array();
556
557 assert_eq!(
559 taken.len(),
560 3,
561 "Take with nullable indices should produce array of length 3, got {}",
562 taken.len()
563 );
564
565 assert!(
566 taken.dtype().is_nullable(),
567 "Take with nullable indices should produce nullable array, but dtype is {:?}",
568 taken.dtype()
569 );
570
571 let expected_0 = array
573 .execute_scalar(0, ctx)
574 .vortex_expect("scalar_at should succeed in conformance test")
575 .into_nullable();
576 let actual_0 = taken
577 .execute_scalar(0, ctx)
578 .vortex_expect("scalar_at should succeed in conformance test");
579 assert_eq!(
580 actual_0, expected_0,
581 "Take with nullable indices: element at position 0 should be from array index 0. \
582 Expected: {expected_0:?}, Actual: {actual_0:?}"
583 );
584
585 let actual_1 = taken
587 .execute_scalar(1, ctx)
588 .vortex_expect("scalar_at should succeed in conformance test");
589 assert!(
590 actual_1.is_null(),
591 "Take with nullable indices: element at position 1 should be null, but got {actual_1:?}"
592 );
593
594 let expected_2 = array
596 .execute_scalar(2, ctx)
597 .vortex_expect("scalar_at should succeed in conformance test")
598 .into_nullable();
599 let actual_2 = taken
600 .execute_scalar(2, ctx)
601 .vortex_expect("scalar_at should succeed in conformance test");
602 assert_eq!(
603 actual_2, expected_2,
604 "Take with nullable indices: element at position 2 should be from array index 2. \
605 Expected: {expected_2:?}, Actual: {actual_2:?}"
606 );
607}
608
609fn test_large_array_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
611 let len = array.len();
612 if len < 1000 {
613 return;
614 }
615
616 let indices: Vec<u64> = (0..len).step_by(10).map(|i| i as u64).collect();
618 let indices_array = PrimitiveArray::from_iter(indices).into_array();
619 let taken = array
620 .take(indices_array)
621 .vortex_expect("take should succeed in conformance test")
622 .execute::<RecursiveCanonical>(ctx)
623 .vortex_expect("batch execution should succeed in conformance test")
624 .0
625 .into_array();
626
627 let mask_pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
629 let mask = Mask::from_iter(mask_pattern);
630 let filtered = array
631 .filter(mask)
632 .vortex_expect("filter should succeed in conformance test")
633 .execute::<RecursiveCanonical>(ctx)
634 .vortex_expect("batch execution should succeed in conformance test")
635 .0
636 .into_array();
637
638 assert_eq!(taken.len(), filtered.len());
640 for i in 0..taken.len() {
641 assert_eq!(
642 taken
643 .execute_scalar(i, ctx)
644 .vortex_expect("scalar_at should succeed in conformance test"),
645 filtered
646 .execute_scalar(i, ctx)
647 .vortex_expect("scalar_at should succeed in conformance test")
648 );
649 }
650}
651
652fn test_comparison_inverse_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
669 let len = array.len();
670 if len == 0 {
671 return;
672 }
673
674 match array.dtype() {
676 DType::Null
677 | DType::List(..)
678 | DType::Struct(..)
679 | DType::Union(..)
680 | DType::Extension(_) => return,
681 _ => {}
682 }
683
684 let test_scalar = array
685 .execute_scalar(len / 2, ctx)
686 .vortex_expect("scalar_at should succeed in conformance test");
687 let const_array = ConstantArray::new(test_scalar, len).into_array();
688
689 for (operator, counterpart) in [
690 (Operator::Eq, Operator::NotEq),
691 (Operator::Gt, Operator::Lte),
692 (Operator::Lt, Operator::Gte),
693 ] {
694 let left = array
695 .binary(const_array.clone(), operator)
696 .vortex_expect("comparison should succeed in conformance test");
697 let left = left
698 .not()
699 .vortex_expect("not should succeed in conformance test");
700 let right = array
701 .binary(const_array.clone(), counterpart)
702 .vortex_expect("comparison should succeed in conformance test");
703 assert_arrays_eq!(left, right, ctx);
704 }
705}
706
707fn test_comparison_symmetry_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
723 let len = array.len();
724 if len == 0 {
725 return;
726 }
727
728 match array.dtype() {
730 DType::Null
731 | DType::List(..)
732 | DType::Struct(..)
733 | DType::Union(..)
734 | DType::Extension(_) => return,
735 _ => {}
736 }
737
738 let test_scalar = array
739 .execute_scalar(len / 2, ctx)
740 .vortex_expect("scalar_at should succeed in conformance test");
741 let const_array = ConstantArray::new(test_scalar, len).into_array();
742
743 for (operator, counterpart) in [(Operator::Gt, Operator::Lt), (Operator::Eq, Operator::Eq)] {
744 let left = array
745 .binary(const_array.clone(), operator)
746 .vortex_expect("comparison should succeed in conformance test");
747 let right = const_array
748 .binary(array.clone(), counterpart)
749 .vortex_expect("comparison should succeed in conformance test");
750 assert_arrays_eq!(left, right, ctx);
751 }
752}
753
754fn test_boolean_demorgan_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
771 if !matches!(array.dtype(), DType::Bool(_)) {
772 return;
773 }
774
775 let bool_mask = BoolArray::from_iter((0..array.len()).map(|i| i % 3 == 0)).into_array();
776 for (operator, counterpart) in [(Operator::And, Operator::Or), (Operator::Or, Operator::And)] {
777 let left = array
778 .binary(bool_mask.clone(), operator)
779 .vortex_expect("boolean operation should succeed in conformance test")
780 .not()
781 .vortex_expect("not should succeed in conformance test");
782 let not_a = array
783 .not()
784 .vortex_expect("not should succeed in conformance test");
785 let not_b = bool_mask
786 .not()
787 .vortex_expect("not should succeed in conformance test");
788 let right = not_a
789 .binary(not_b, counterpart)
790 .vortex_expect("boolean operation should succeed in conformance test");
791 assert_arrays_eq!(left, right, ctx);
792 }
793}
794
795fn test_slice_aggregate_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
811 let len = array.len();
812 if len < 5 {
813 return; }
815
816 let start = 1;
818 let end = (len - 1).min(start + 10); let sliced = array
822 .slice(start..end)
823 .vortex_expect("slice should succeed in conformance test");
824 let canonical = array
825 .clone()
826 .execute::<Canonical>(ctx)
827 .vortex_expect("to_canonical failed");
828 let canonical_sliced = canonical
829 .into_array()
830 .slice(start..end)
831 .vortex_expect("slice should succeed in conformance test");
832
833 let sliced_invalid_count = sliced
835 .invalid_count(ctx)
836 .vortex_expect("invalid_count should succeed in conformance test");
837 let canonical_invalid_count = canonical_sliced
838 .invalid_count(ctx)
839 .vortex_expect("invalid_count should succeed in conformance test");
840 assert_eq!(
841 sliced_invalid_count, canonical_invalid_count,
842 "null_count on sliced array should match canonical. \
843 Sliced: {sliced_invalid_count}, Canonical: {canonical_invalid_count}",
844 );
845
846 if !matches!(array.dtype(), DType::Primitive(..)) {
848 return;
849 }
850
851 let slice_sum = sum(&sliced, ctx).vortex_expect("slice sum should succeed");
852 let canonical_sum = sum(&canonical_sliced, ctx).vortex_expect("canonical sum should succeed");
853 assert_eq!(
854 slice_sum, canonical_sum,
855 "sum on sliced array should match canonical"
856 );
857
858 let slice_minmax = min_max(&sliced, ctx, NumericalAggregateOpts::default())
859 .vortex_expect("slice min_max should succeed");
860 let canonical_minmax = min_max(&canonical_sliced, ctx, NumericalAggregateOpts::default())
861 .vortex_expect("canonical min_max should succeed");
862 assert_eq!(
863 slice_minmax, canonical_minmax,
864 "min_max on sliced array should match canonical"
865 );
866
867 if array.dtype().is_float() {
868 let slice_nan_count =
869 nan_count(&sliced, ctx).vortex_expect("slice nan_count should succeed");
870 let canonical_nan_count =
871 nan_count(&canonical_sliced, ctx).vortex_expect("canonical nan_count should succeed");
872 assert_eq!(
873 slice_nan_count, canonical_nan_count,
874 "nan_count on sliced array should match canonical"
875 );
876 }
877}
878
879fn widened_primitive_dtype(dtype: &DType) -> Option<DType> {
880 let DType::Primitive(ptype, nullability) = dtype else {
881 return None;
882 };
883 let widened = match ptype {
884 PType::U8 => PType::U16,
885 PType::U16 => PType::U32,
886 PType::U32 => PType::U64,
887 PType::U64 => return None,
888 PType::I8 => PType::I16,
889 PType::I16 => PType::I32,
890 PType::I32 => PType::I64,
891 PType::I64 => return None,
892 PType::F16 => PType::F32,
893 PType::F32 => PType::F64,
894 PType::F64 => return None,
895 };
896 Some(DType::Primitive(widened, *nullability))
897}
898
899fn slice_cast_targets(dtype: &DType) -> Vec<DType> {
901 match dtype {
902 DType::Null => vec![],
903 DType::Bool(nullability) => vec![
904 DType::Primitive(PType::U8, *nullability),
905 DType::Primitive(PType::I32, *nullability),
906 ],
907 DType::Primitive(ptype, nullability) => {
908 let mut targets = vec![];
909 let opposite_nullability = match nullability {
911 Nullability::NonNullable => Nullability::Nullable,
912 Nullability::Nullable => Nullability::NonNullable,
913 };
914 targets.push(DType::Primitive(*ptype, opposite_nullability));
915
916 match ptype {
918 PType::U8 => {
919 targets.push(DType::Primitive(PType::U16, *nullability));
920 targets.push(DType::Primitive(PType::I16, *nullability));
921 }
922 PType::U16 => {
923 targets.push(DType::Primitive(PType::U32, *nullability));
924 targets.push(DType::Primitive(PType::I32, *nullability));
925 }
926 PType::U32 => {
927 targets.push(DType::Primitive(PType::U64, *nullability));
928 targets.push(DType::Primitive(PType::I64, *nullability));
929 }
930 PType::U64 => {
931 targets.push(DType::Primitive(PType::F64, *nullability));
932 }
933 PType::I8 => {
934 targets.push(DType::Primitive(PType::I16, *nullability));
935 targets.push(DType::Primitive(PType::F32, *nullability));
936 }
937 PType::I16 => {
938 targets.push(DType::Primitive(PType::I32, *nullability));
939 targets.push(DType::Primitive(PType::F32, *nullability));
940 }
941 PType::I32 => {
942 targets.push(DType::Primitive(PType::I64, *nullability));
943 targets.push(DType::Primitive(PType::F64, *nullability));
944 }
945 PType::I64 => {
946 targets.push(DType::Primitive(PType::F64, *nullability));
947 }
948 PType::F16 => {
949 targets.push(DType::Primitive(PType::F32, *nullability));
950 }
951 PType::F32 => {
952 targets.push(DType::Primitive(PType::F64, *nullability));
953 targets.push(DType::Primitive(PType::I32, *nullability));
954 }
955 PType::F64 => {
956 targets.push(DType::Primitive(PType::I64, *nullability));
957 }
958 }
959 targets
960 }
961 DType::Decimal(decimal_type, nullability) => {
962 let opposite = match nullability {
963 Nullability::NonNullable => Nullability::Nullable,
964 Nullability::Nullable => Nullability::NonNullable,
965 };
966 vec![DType::Decimal(*decimal_type, opposite)]
967 }
968 DType::Utf8(nullability) => {
969 let opposite = match nullability {
970 Nullability::NonNullable => Nullability::Nullable,
971 Nullability::Nullable => Nullability::NonNullable,
972 };
973 vec![DType::Utf8(opposite), DType::Binary(*nullability)]
974 }
975 DType::Binary(nullability) => {
976 let opposite = match nullability {
977 Nullability::NonNullable => Nullability::Nullable,
978 Nullability::Nullable => Nullability::NonNullable,
979 };
980 vec![
981 DType::Binary(opposite),
982 DType::Utf8(*nullability), ]
984 }
985 DType::List(element_type, nullability) => {
986 let opposite = match nullability {
987 Nullability::NonNullable => Nullability::Nullable,
988 Nullability::Nullable => Nullability::NonNullable,
989 };
990 vec![DType::List(Arc::clone(element_type), opposite)]
991 }
992 DType::FixedSizeList(element_type, list_size, nullability) => {
993 let opposite = match nullability {
994 Nullability::NonNullable => Nullability::Nullable,
995 Nullability::Nullable => Nullability::NonNullable,
996 };
997 vec![DType::FixedSizeList(
998 Arc::clone(element_type),
999 *list_size,
1000 opposite,
1001 )]
1002 }
1003 DType::Map(map_dtype, nullability) => {
1004 let opposite = match nullability {
1005 Nullability::NonNullable => Nullability::Nullable,
1006 Nullability::Nullable => Nullability::NonNullable,
1007 };
1008 let key_dtype = map_dtype.key_dtype();
1009 let value_dtype = map_dtype.value_dtype();
1010 let mut targets = vec![DType::Map(map_dtype.clone(), opposite)];
1011
1012 if let Some(widened_key) = widened_primitive_dtype(&key_dtype)
1013 && let Ok(dtype) = DType::map(
1014 widened_key,
1015 value_dtype.clone(),
1016 map_dtype.keys_sorted(),
1017 *nullability,
1018 )
1019 {
1020 targets.push(dtype);
1021 }
1022
1023 if let Some(widened_value) = widened_primitive_dtype(&value_dtype)
1024 && let Ok(dtype) = DType::map(
1025 key_dtype.clone(),
1026 widened_value,
1027 map_dtype.keys_sorted(),
1028 *nullability,
1029 )
1030 {
1031 targets.push(dtype);
1032 }
1033
1034 if map_dtype.keys_sorted()
1035 && let Ok(dtype) = DType::map(key_dtype, value_dtype, false, *nullability)
1036 {
1037 targets.push(dtype);
1038 }
1039
1040 targets
1041 }
1042 DType::Struct(fields, nullability) => {
1043 let opposite = match nullability {
1044 Nullability::NonNullable => Nullability::Nullable,
1045 Nullability::Nullable => Nullability::NonNullable,
1046 };
1047 vec![DType::Struct(fields.clone(), opposite)]
1048 }
1049 DType::Union(..) => vortex_panic!("TODO(connor)[Union]: unimplemented"),
1050 DType::Variant(_) => vortex_panic!("Variant conformance casting is not implemented"),
1051 DType::Extension(_) => vec![], }
1053}
1054
1055fn test_cast_slice_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1071 let len = array.len();
1072 if len < 5 {
1073 return; }
1075
1076 let start = 2;
1078 let end = 7.min(len - 2).max(start + 1); let canonical = array
1082 .clone()
1083 .execute::<RecursiveCanonical>(ctx)
1084 .vortex_expect("to_canonical failed")
1085 .0
1086 .into_array();
1087
1088 let target_dtypes = slice_cast_targets(array.dtype());
1089
1090 for target_dtype in target_dtypes {
1091 let mut slice_then_cast: Option<ArrayRef> = None;
1092 for slice_first in [true, false] {
1095 let reference_input = if slice_first {
1096 canonical
1097 .slice(start..end)
1098 .vortex_expect("canonical slice should succeed")
1099 } else {
1100 canonical.clone()
1101 };
1102 let expected = reference_input.cast(target_dtype.clone()).and_then(|a| {
1103 a.execute::<RecursiveCanonical>(ctx)
1104 .map(|c| c.0.into_array())
1105 });
1106 let input = if slice_first {
1107 array
1108 .slice(start..end)
1109 .vortex_expect("slice should succeed")
1110 } else {
1111 array.clone()
1112 };
1113 let actual = input.cast(target_dtype.clone()).and_then(|a| {
1114 a.execute::<RecursiveCanonical>(ctx)
1115 .map(|c| c.0.into_array())
1116 });
1117 let expected = match expected {
1118 Ok(expected) => expected,
1119 Err(_) => {
1120 assert!(
1121 actual.is_err(),
1122 "encoded cast to {target_dtype} succeeded where canonical cast failed (slice_first={slice_first})"
1123 );
1124 continue;
1125 }
1126 };
1127 let actual =
1128 actual.vortex_expect("encoded cast should succeed when canonical cast succeeds");
1129 let (actual, expected) = if slice_first {
1130 (actual, expected)
1131 } else {
1132 (
1133 actual
1134 .slice(start..end)
1135 .vortex_expect("cast result slice should succeed")
1136 .execute::<RecursiveCanonical>(ctx)
1137 .vortex_expect("slice batch execution should succeed")
1138 .0
1139 .into_array(),
1140 expected
1141 .slice(start..end)
1142 .vortex_expect("canonical cast result slice should succeed")
1143 .execute::<RecursiveCanonical>(ctx)
1144 .vortex_expect("canonical slice batch execution should succeed")
1145 .0
1146 .into_array(),
1147 )
1148 };
1149 assert_eq!(actual.dtype(), &target_dtype);
1150 assert_eq!(actual.len(), end - start);
1151 assert_eq!(expected.dtype(), &target_dtype);
1152 assert_eq!(expected.len(), end - start);
1153 for i in 0..actual.len() {
1156 assert_eq!(
1157 actual
1158 .execute_scalar(i, ctx)
1159 .vortex_expect("cast scalar_at should succeed"),
1160 expected
1161 .execute_scalar(i, ctx)
1162 .vortex_expect("reference scalar_at should succeed"),
1163 "cast value differs from canonical reference at index {i}"
1164 );
1165 if let Some(sliced_cast) = &slice_then_cast {
1166 assert_eq!(
1167 actual
1168 .execute_scalar(i, ctx)
1169 .vortex_expect("cast scalar_at should succeed"),
1170 sliced_cast
1171 .execute_scalar(i, ctx)
1172 .vortex_expect("slice cast scalar_at should succeed"),
1173 "slice/cast order differs at index {i}"
1174 );
1175 }
1176 }
1177 if slice_first {
1178 slice_then_cast = Some(actual.clone());
1179 }
1180
1181 let scalar_expected: Result<Vec<_>, _> = (start..end)
1184 .map(|i| {
1185 canonical
1186 .execute_scalar(i, ctx)
1187 .vortex_expect("canonical scalar_at should succeed")
1188 .cast(&target_dtype)
1189 })
1190 .collect();
1191 if let Ok(scalars) = scalar_expected {
1192 for (i, expected) in scalars.into_iter().enumerate() {
1193 let actual = actual
1194 .execute_scalar(i, ctx)
1195 .vortex_expect("scalar_at should succeed");
1196 assert_eq!(actual, expected, "cast value differs at index {i}");
1197 }
1198 }
1199 }
1200 }
1201}
1202
1203fn test_repeated_probe_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1221 let len = array.len().min(1024);
1223 if len == 0 {
1224 return;
1225 }
1226
1227 let expected: Vec<_> = (0..len)
1228 .map(|i| {
1229 array
1230 .execute_scalar(i, ctx)
1231 .vortex_expect("scalar_at should succeed in conformance test")
1232 })
1233 .collect();
1234
1235 let mut probe = array.repeated_probe();
1236 let order = (0..len).rev().chain(0..len).chain((0..len).step_by(7));
1237 for i in order {
1238 let actual = probe
1239 .execute_scalar(i, ctx)
1240 .vortex_expect("repeated probe read should succeed in conformance test");
1241 assert_eq!(
1242 actual, expected[i],
1243 "Repeated probe and one-off read disagree at index {i}. \
1244 Probe value: {actual:?}, one-off value: {:?}",
1245 expected[i]
1246 );
1247 let valid = probe
1248 .execute_is_valid(i, ctx)
1249 .vortex_expect("repeated probe validity read should succeed in conformance test");
1250 assert_eq!(
1251 valid,
1252 !expected[i].is_null(),
1253 "Repeated probe validity disagrees with the value read at index {i}"
1254 );
1255 }
1256}
1257
1258pub fn test_array_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1300 test_filter_take_consistency(array, ctx);
1302 test_double_mask_consistency(array, ctx);
1303 test_slice_filter_consistency(array, ctx);
1304 test_take_slice_consistency(array, ctx);
1305 test_cast_slice_consistency(array, ctx);
1306
1307 test_boolean_demorgan_consistency(array, ctx);
1309
1310 test_comparison_inverse_consistency(array, ctx);
1312 test_comparison_symmetry_consistency(array, ctx);
1313
1314 test_slice_aggregate_consistency(array, ctx);
1316
1317 test_filter_identity(array, ctx);
1319 if !array.is_empty() {
1320 super::mask::test_empty_mask(array, ctx);
1321 super::take::test_take_all(array, ctx);
1322 }
1323
1324 test_repeated_probe_consistency(array, ctx);
1326
1327 test_filter_preserves_order(array, ctx);
1329 super::take::test_take_repeated_indices(array, ctx);
1330
1331 test_mask_filter_null_consistency(array, ctx);
1333 test_nullable_indices_consistency(array, ctx);
1334
1335 test_empty_operations_consistency(array, ctx);
1337 test_large_array_consistency(array, ctx);
1338
1339 test_probe_consistency(array, ctx);
1341}
1342
1343fn test_probe_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1350 check_probe_consistency(array, ctx);
1351 if array.len() > 2 {
1352 let sliced = array
1353 .slice(1..array.len() - 1)
1354 .vortex_expect("slice should succeed in conformance test");
1355 check_probe_consistency(&sliced, ctx);
1356 }
1357}
1358
1359fn check_probe_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1360 let len = array.len();
1361 let stride = if len <= 1024 { 1 } else { 7 };
1362 let indices: Vec<usize> = (0..len).step_by(stride).collect();
1363
1364 let mut once = array.probe();
1365 let mut repeated = array.repeated_probe();
1366 for &i in indices.iter().chain(indices.iter().rev()) {
1367 let expected = once
1368 .execute_scalar(i, ctx)
1369 .vortex_expect("one-off probe should succeed in conformance test");
1370 let valid = once
1371 .execute_is_valid(i, ctx)
1372 .vortex_expect("one-off validity should succeed in conformance test");
1373 assert_eq!(
1374 valid,
1375 !expected.is_null(),
1376 "One-off validity and scalar disagree at index {i}: valid {valid}, scalar {expected:?}"
1377 );
1378
1379 let retained_valid = repeated
1380 .execute_is_valid(i, ctx)
1381 .vortex_expect("retained validity should succeed in conformance test");
1382 assert_eq!(
1383 retained_valid, valid,
1384 "Retained and one-off validity disagree at index {i}"
1385 );
1386 let actual = repeated
1387 .execute_scalar(i, ctx)
1388 .vortex_expect("retained probe should succeed in conformance test");
1389 assert_eq!(
1390 actual, expected,
1391 "Retained and one-off probes disagree at index {i}"
1392 );
1393 }
1394}