Skip to main content

vortex_array/compute/conformance/
consistency.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! # Array Consistency Tests
5//!
6//! This module contains tests that verify consistency between related compute operations
7//! on Vortex arrays. These tests ensure that different ways of achieving the same result
8//! produce identical outputs.
9//!
10//! ## Test Categories
11//!
12//! - **Filter/Take Consistency**: Verifies that filtering with a mask produces the same
13//!   result as taking with the indices where the mask is true.
14//! - **Mask Composition**: Ensures that applying multiple masks sequentially produces
15//!   the same result as applying a combined mask.
16//! - **Identity Operations**: Tests that operations with identity inputs (all-true masks,
17//!   sequential indices) preserve the original array.
18//! - **Null Handling**: Verifies consistent behavior when operations introduce or
19//!   interact with null values.
20//! - **Edge Cases**: Tests empty arrays, single elements, and boundary conditions.
21
22use 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
42/// Tests that filter and take operations produce consistent results.
43///
44/// # Invariant
45/// `filter(array, mask)` should equal `take(array, indices_where_mask_is_true)`
46///
47/// # Test Details
48/// - Creates a mask that keeps elements where index % 3 != 1
49/// - Applies filter with this mask
50/// - Creates indices array containing positions where mask is true
51/// - Applies take with these indices
52/// - Verifies both results are identical
53fn test_filter_take_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
54    let len = array.len();
55    if len == 0 {
56        return;
57    }
58
59    // Create a test mask (keep elements where index % 3 != 1)
60    let mask_pattern: BitBuffer = (0..len).map(|i| i % 3 != 1).collect();
61    let mask = Mask::from_buffer(mask_pattern.clone());
62
63    // Filter the array
64    let filtered = array
65        .filter(mask)
66        .vortex_expect("filter should succeed in conformance test");
67
68    // Create indices where mask is true
69    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    // Take using those indices
77    let taken = array
78        .take(indices_array)
79        .vortex_expect("take should succeed in conformance test");
80
81    // Results should be identical
82    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
106/// Tests that double masking is consistent with combined mask.
107///
108/// # Invariant
109/// `mask(mask(array, mask1), mask2)` should equal `mask(array, mask1 | mask2)`
110///
111/// # Test Details
112/// - Creates two masks: mask1 (every 3rd element) and mask2 (every 2nd element)
113/// - Applies masks sequentially: first mask1, then mask2 on the result
114/// - Creates a combined mask using OR operation (element is masked if either mask is true)
115/// - Applies the combined mask directly to the original array
116/// - Verifies both approaches produce identical results
117///
118/// # Why This Matters
119/// This test ensures that mask operations compose correctly, which is critical for
120/// complex query operations that may apply multiple filters.
121fn test_double_mask_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
122    let len = array.len();
123    if len == 0 {
124        return;
125    }
126
127    // Create two different mask patterns
128    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    // Apply masks sequentially
132    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    // Create combined mask (OR operation - element is masked if EITHER mask is true)
141    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    // Apply combined mask directly
150    let directly_masked = array
151        .clone()
152        .mask((!&combined_mask).into_array())
153        .vortex_expect("mask should succeed in conformance test");
154
155    // Results should be identical
156    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
181/// Tests that filtering with an all-true mask preserves the array.
182///
183/// # Invariant
184/// `filter(array, all_true_mask)` should equal `array`
185///
186/// # Test Details
187/// - Creates a mask with all elements set to true
188/// - Applies filter with this mask
189/// - Verifies the result is identical to the original array
190///
191/// # Why This Matters
192/// This is an identity operation that should be optimized in implementations
193/// to avoid unnecessary copying.
194fn 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    // Filtered array should be identical to original
206    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
230/// Tests that masking with an all-false mask preserves values while making them nullable.
231///
232/// # Invariant
233/// `mask(array, all_false_mask)` should have same values as `array` but with nullable type
234///
235/// # Test Details
236/// - Creates a mask with all elements set to false (no elements are nullified)
237/// - Applies mask operation
238/// - Verifies all values are preserved but the array type becomes nullable
239///
240/// # Why This Matters
241/// Masking always produces a nullable array, even when no values are actually masked.
242/// This test ensures the type system handles this correctly.
243fn 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    // Masked array should have same values (just nullable)
256    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
287/// Tests that slice and filter with contiguous mask produce same results.
288///
289/// # Invariant
290/// `filter(array, contiguous_true_mask)` should equal `slice(array, start, end)`
291///
292/// # Test Details
293/// - Creates a mask that is true only for indices 1, 2, and 3
294/// - Filters the array with this mask
295/// - Slices the array from index 1 to 4
296/// - Verifies both operations produce identical results
297///
298/// # Why This Matters
299/// When a filter mask represents a contiguous range, it should be equivalent to
300/// a slice operation. Some implementations may optimize this case.
301fn test_slice_filter_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
302    let len = array.len();
303    if len < 4 {
304        return; // Need at least 4 elements for meaningful test
305    }
306
307    // Create a contiguous mask (true from index 1 to 3)
308    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    // Slice should produce the same result
317    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
345/// Tests that take with sequential indices equals slice.
346///
347/// # Invariant
348/// `take(array, [1, 2, 3, ...])` should equal `slice(array, 1, n)`
349///
350/// # Test Details
351/// - Creates indices array with sequential values [1, 2, 3]
352/// - Takes elements at these indices
353/// - Slices array from index 1 to 4
354/// - Verifies both operations produce identical results
355///
356/// # Why This Matters
357/// Sequential takes are a common pattern that can be optimized to slice operations.
358fn test_take_slice_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
359    let len = array.len();
360    if len < 3 {
361        return; // Need at least 3 elements
362    }
363
364    // Take indices [1, 2, 3]
365    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    // Slice from 1 to end
372    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
400/// Tests that filter preserves relative ordering
401fn test_filter_preserves_order(array: &ArrayRef, ctx: &mut ExecutionCtx) {
402    let len = array.len();
403    if len < 4 {
404        return;
405    }
406
407    // Create a mask that selects elements at indices 0, 2, 3
408    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    // Verify the filtered array contains the right elements in order
416    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
445/// Tests that take with repeated indices works correctly
446fn test_take_repeated_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) {
447    let len = array.len();
448    if len == 0 {
449        return;
450    }
451
452    // Take the first element three times
453    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
471/// Tests mask and filter interaction with nulls
472fn test_mask_filter_null_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
473    let len = array.len();
474    if len < 3 {
475        return;
476    }
477
478    // First mask some elements
479    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    // Then filter to remove the nulls
487    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    // This should be equivalent to directly filtering the original array
494    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
511/// Tests that empty operations are consistent
512fn test_empty_operations_consistency(array: &ArrayRef) {
513    let len = array.len();
514
515    // Empty filter
516    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    // Empty take
523    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    // Empty slice (if array is non-empty)
531    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
540/// Tests that take preserves array properties
541fn test_take_preserves_properties(array: &ArrayRef, ctx: &mut ExecutionCtx) {
542    let len = array.len();
543    if len == 0 {
544        return;
545    }
546
547    // Take all elements in original order
548    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    // Should be identical to original
554    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
568/// Tests consistency with nullable indices.
569///
570/// # Invariant
571/// `take(array, [Some(0), None, Some(2)])` should produce `[array[0], null, array[2]]`
572///
573/// # Test Details
574/// - Creates an indices array with null at position 1: `[Some(0), None, Some(2)]`
575/// - Takes elements using these indices
576/// - Verifies that:
577///   - Position 0 contains the value from array index 0
578///   - Position 1 contains null
579///   - Position 2 contains the value from array index 2
580///   - The result array has nullable type
581///
582/// # Why This Matters
583/// Nullable indices are a powerful feature that allows introducing nulls during
584/// a take operation, which is useful for outer joins and similar operations.
585fn test_nullable_indices_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
586    let len = array.len();
587    if len < 3 {
588        return; // Need at least 3 elements to test indices 0 and 2
589    }
590
591    // Create nullable indices where some indices are null
592    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    // Result should have nulls where indices were null
599    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    // Check first element (from index 0)
613    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    // Check second element (should be null)
627    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    // Check third element (from index 2)
636    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
650/// Tests large array consistency
651fn test_large_array_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
652    let len = array.len();
653    if len < 1000 {
654        return;
655    }
656
657    // Test with every 10th element
658    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    // Create equivalent filter mask
665    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    // Results should match
672    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
685/// Tests that comparison operations follow inverse relationships.
686///
687/// # Invariants
688/// - `compare(array, value, Eq)` is the inverse of `compare(array, value, NotEq)`
689/// - `compare(array, value, Gt)` is the inverse of `compare(array, value, Lte)`
690/// - `compare(array, value, Lt)` is the inverse of `compare(array, value, Gte)`
691///
692/// # Test Details
693/// - Creates comparison results for each operator
694/// - Verifies that inverse operations produce opposite boolean values
695/// - Tests with multiple scalar values to ensure consistency
696///
697/// # Why This Matters
698/// Comparison operations must maintain logical consistency across encodings.
699/// This test catches bugs where an encoding might implement one comparison
700/// correctly but fail on its logical inverse.
701fn test_comparison_inverse_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
702    let len = array.len();
703    if len == 0 {
704        return;
705    }
706
707    // Skip non-comparable types.
708    match array.dtype() {
709        DType::Null
710        | DType::List(..)
711        | DType::Struct(..)
712        | DType::Union(..)
713        | DType::Extension(_) => return,
714        _ => {}
715    }
716
717    // Get a test value from the middle of the array
718    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    // Test Eq vs NotEq
727    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    // Test Gt vs Lte
758    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    // Test Lt vs Gte
782    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
806/// Tests that comparison operations maintain proper symmetry relationships.
807///
808/// # Invariants
809/// - `compare(array, value, Gt)` should equal `compare_scalar_array(value, array, Lt)`
810/// - `compare(array, value, Lt)` should equal `compare_scalar_array(value, array, Gt)`
811/// - `compare(array, value, Eq)` should equal `compare_scalar_array(value, array, Eq)`
812///
813/// # Test Details
814/// - Compares array-scalar operations with their symmetric scalar-array versions
815/// - Verifies that ordering relationships are properly reversed
816/// - Tests equality which should be symmetric
817///
818/// # Why This Matters
819/// Ensures that comparison operations maintain mathematical ordering properties
820/// regardless of operand order.
821fn test_comparison_symmetry_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
822    let len = array.len();
823    if len == 0 {
824        return;
825    }
826
827    // Skip non-comparable types.
828    match array.dtype() {
829        DType::Null
830        | DType::List(..)
831        | DType::Struct(..)
832        | DType::Union(..)
833        | DType::Extension(_) => return,
834        _ => {}
835    }
836
837    // Get test values
838    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    // Create a constant array with the test scalar for reverse comparison
847    let const_array = ConstantArray::new(test_scalar, len).into_array();
848
849    // Test Gt vs Lt symmetry
850    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    // Test Eq symmetry
876    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
896/// Tests that boolean operations follow De Morgan's laws.
897///
898/// # Invariants
899/// - `NOT(A AND B)` equals `(NOT A) OR (NOT B)`
900/// - `NOT(A OR B)` equals `(NOT A) AND (NOT B)`
901///
902/// # Test Details
903/// - If the array is boolean, uses it directly for testing boolean operations
904/// - Creates two boolean masks from patterns based on the array
905/// - Computes AND/OR operations and their inversions
906/// - Verifies De Morgan's laws hold for all elements
907///
908/// # Why This Matters
909/// Boolean operations must maintain logical consistency across encodings.
910/// This test catches bugs where encodings might optimize boolean operations
911/// incorrectly, breaking fundamental logical properties.
912fn 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    // Test first De Morgan's law: NOT(A AND B) = (NOT A) OR (NOT B)
924    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    // Test second De Morgan's law: NOT(A OR B) = (NOT A) AND (NOT B)
958    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
986/// Tests that slice and aggregate operations produce consistent results.
987///
988/// # Invariants
989/// - Aggregating a sliced array should equal aggregating the corresponding
990///   elements from the canonical form
991/// - This applies to sum, count, min/max, and other aggregate functions
992///
993/// # Test Details
994/// - Slices the array and computes aggregates
995/// - Compares against aggregating the canonical form's slice
996/// - Tests multiple aggregate functions where applicable
997///
998/// # Why This Matters
999/// Aggregate operations on sliced arrays must produce correct results
1000/// regardless of the underlying encoding's offset handling.
1001fn 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; // Need enough elements for meaningful slice
1011    }
1012
1013    // Define slice bounds
1014    let start = 1;
1015    let end = (len - 1).min(start + 10); // Take up to 10 elements
1016
1017    // Get sliced array and canonical slice
1018    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    // Test null count through invalid_count
1031    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    // Test sum for numeric types
1044    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        // Compare sum scalars
1050        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    // Test min_max
1058    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) => {} // Both empty, OK
1078            _ => vortex_panic!("min_max results don't match"),
1079        }
1080    }
1081
1082    // Test nan_count for floating point types
1083    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
1095/// Tests that cast operations preserve array properties when sliced.
1096///
1097/// # Invariant
1098/// `cast(slice(array, start, end), dtype)` should equal `slice(cast(array, dtype), start, end)`
1099///
1100/// # Test Details
1101/// - Slices the array from index 2 to 7 (or len-2 if smaller)
1102/// - Casts the sliced array to a different type
1103/// - Compares against the canonical form of the array (without slicing or casting the canonical form)
1104/// - Verifies both approaches produce identical results
1105///
1106/// # Why This Matters
1107/// This test specifically catches bugs where encodings (like RunEndArray) fail to preserve
1108/// offset information during cast operations. Such bugs can lead to incorrect data being
1109/// returned after casting a sliced array.
1110fn test_cast_slice_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1111    let len = array.len();
1112    if len < 5 {
1113        return; // Need at least 5 elements for meaningful slice
1114    }
1115
1116    // Define slice bounds
1117    let start = 2;
1118    let end = 7.min(len - 2).max(start + 1); // Ensure we have at least 1 element
1119
1120    // Get canonical form of the original array
1121    let canonical = array
1122        .clone()
1123        .execute::<Canonical>(ctx)
1124        .vortex_expect("to_canonical failed")
1125        .into_array();
1126
1127    // Choose appropriate target dtype based on the array's type
1128    let target_dtypes = match array.dtype() {
1129        DType::Null => vec![],
1130        DType::Bool(nullability) => vec![
1131            DType::Primitive(PType::U8, *nullability),
1132            DType::Primitive(PType::I32, *nullability),
1133        ],
1134        DType::Primitive(ptype, nullability) => {
1135            let mut targets = vec![];
1136            // Test nullability changes
1137            let opposite_nullability = match nullability {
1138                Nullability::NonNullable => Nullability::Nullable,
1139                Nullability::Nullable => Nullability::NonNullable,
1140            };
1141            targets.push(DType::Primitive(*ptype, opposite_nullability));
1142
1143            // Test widening casts
1144            match ptype {
1145                PType::U8 => {
1146                    targets.push(DType::Primitive(PType::U16, *nullability));
1147                    targets.push(DType::Primitive(PType::I16, *nullability));
1148                }
1149                PType::U16 => {
1150                    targets.push(DType::Primitive(PType::U32, *nullability));
1151                    targets.push(DType::Primitive(PType::I32, *nullability));
1152                }
1153                PType::U32 => {
1154                    targets.push(DType::Primitive(PType::U64, *nullability));
1155                    targets.push(DType::Primitive(PType::I64, *nullability));
1156                }
1157                PType::U64 => {
1158                    targets.push(DType::Primitive(PType::F64, *nullability));
1159                }
1160                PType::I8 => {
1161                    targets.push(DType::Primitive(PType::I16, *nullability));
1162                    targets.push(DType::Primitive(PType::F32, *nullability));
1163                }
1164                PType::I16 => {
1165                    targets.push(DType::Primitive(PType::I32, *nullability));
1166                    targets.push(DType::Primitive(PType::F32, *nullability));
1167                }
1168                PType::I32 => {
1169                    targets.push(DType::Primitive(PType::I64, *nullability));
1170                    targets.push(DType::Primitive(PType::F64, *nullability));
1171                }
1172                PType::I64 => {
1173                    targets.push(DType::Primitive(PType::F64, *nullability));
1174                }
1175                PType::F16 => {
1176                    targets.push(DType::Primitive(PType::F32, *nullability));
1177                }
1178                PType::F32 => {
1179                    targets.push(DType::Primitive(PType::F64, *nullability));
1180                    targets.push(DType::Primitive(PType::I32, *nullability));
1181                }
1182                PType::F64 => {
1183                    targets.push(DType::Primitive(PType::I64, *nullability));
1184                }
1185            }
1186            targets
1187        }
1188        DType::Decimal(decimal_type, nullability) => {
1189            let opposite = match nullability {
1190                Nullability::NonNullable => Nullability::Nullable,
1191                Nullability::Nullable => Nullability::NonNullable,
1192            };
1193            vec![DType::Decimal(*decimal_type, opposite)]
1194        }
1195        DType::Utf8(nullability) => {
1196            let opposite = match nullability {
1197                Nullability::NonNullable => Nullability::Nullable,
1198                Nullability::Nullable => Nullability::NonNullable,
1199            };
1200            vec![DType::Utf8(opposite), DType::Binary(*nullability)]
1201        }
1202        DType::Binary(nullability) => {
1203            let opposite = match nullability {
1204                Nullability::NonNullable => Nullability::Nullable,
1205                Nullability::Nullable => Nullability::NonNullable,
1206            };
1207            vec![
1208                DType::Binary(opposite),
1209                DType::Utf8(*nullability), // May fail if not valid UTF-8
1210            ]
1211        }
1212        DType::List(element_type, nullability) => {
1213            let opposite = match nullability {
1214                Nullability::NonNullable => Nullability::Nullable,
1215                Nullability::Nullable => Nullability::NonNullable,
1216            };
1217            vec![DType::List(Arc::clone(element_type), opposite)]
1218        }
1219        DType::FixedSizeList(element_type, list_size, nullability) => {
1220            let opposite = match nullability {
1221                Nullability::NonNullable => Nullability::Nullable,
1222                Nullability::Nullable => Nullability::NonNullable,
1223            };
1224            vec![DType::FixedSizeList(
1225                Arc::clone(element_type),
1226                *list_size,
1227                opposite,
1228            )]
1229        }
1230        DType::Struct(fields, nullability) => {
1231            let opposite = match nullability {
1232                Nullability::NonNullable => Nullability::Nullable,
1233                Nullability::Nullable => Nullability::NonNullable,
1234            };
1235            vec![DType::Struct(fields.clone(), opposite)]
1236        }
1237        DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"),
1238        DType::Variant(_) => unimplemented!(),
1239        DType::Extension(_) => vec![], // Extension types typically only cast to themselves
1240    };
1241
1242    // Test each target dtype
1243    for target_dtype in target_dtypes {
1244        // Slice the array
1245        let sliced = array
1246            .slice(start..end)
1247            .vortex_expect("slice should succeed in conformance test");
1248
1249        // Try to cast the sliced array (force execution via to_canonical)
1250        let slice_then_cast = match sliced
1251            .cast(target_dtype.clone())
1252            .and_then(|a| a.execute::<Canonical>(ctx).map(|c| c.into_array()))
1253        {
1254            Ok(result) => result,
1255            Err(_) => continue, // Skip if cast fails
1256        };
1257
1258        // Verify against canonical form
1259        assert_eq!(
1260            slice_then_cast.len(),
1261            end - start,
1262            "Sliced and casted array should have length {}, but has {}",
1263            end - start,
1264            slice_then_cast.len()
1265        );
1266
1267        // Compare each value against the canonical form
1268        for i in 0..slice_then_cast.len() {
1269            let slice_cast_val = slice_then_cast
1270                .execute_scalar(i, ctx)
1271                .vortex_expect("scalar_at should succeed in conformance test");
1272
1273            // Get the corresponding value from the canonical array (adjusted for slice offset)
1274            let canonical_val = canonical
1275                .execute_scalar(start + i, ctx)
1276                .vortex_expect("scalar_at should succeed in conformance test");
1277
1278            // Cast the canonical scalar to the target dtype
1279            let expected_val = match canonical_val.cast(&target_dtype) {
1280                Ok(val) => val,
1281                Err(_) => {
1282                    // If scalar cast fails, we can't compare - skip this target dtype
1283                    // This can happen for some type conversions that aren't supported at scalar level
1284                    break;
1285                }
1286            };
1287
1288            assert_eq!(
1289                slice_cast_val,
1290                expected_val,
1291                "Cast of sliced array produced incorrect value at index {i}. \
1292                 Got: {slice_cast_val:?}, Expected: {expected_val:?} \
1293                 (canonical value at index {}: {canonical_val:?})\n\
1294                 This likely indicates the array encoding doesn't preserve offset information during cast.",
1295                start + i
1296            );
1297        }
1298
1299        // Also test the other way: cast then slice
1300        let casted = match array
1301            .cast(target_dtype.clone())
1302            .and_then(|a| a.execute::<Canonical>(ctx).map(|c| c.into_array()))
1303        {
1304            Ok(result) => result,
1305            Err(_) => continue, // Skip if cast fails
1306        };
1307        let cast_then_slice = casted
1308            .slice(start..end)
1309            .vortex_expect("slice should succeed in conformance test");
1310
1311        // Verify the two approaches produce identical results
1312        assert_eq!(
1313            slice_then_cast.len(),
1314            cast_then_slice.len(),
1315            "Slice-then-cast and cast-then-slice should produce arrays of the same length"
1316        );
1317
1318        for i in 0..slice_then_cast.len() {
1319            let slice_cast_val = slice_then_cast
1320                .execute_scalar(i, ctx)
1321                .vortex_expect("scalar_at should succeed in conformance test");
1322            let cast_slice_val = cast_then_slice
1323                .execute_scalar(i, ctx)
1324                .vortex_expect("scalar_at should succeed in conformance test");
1325            assert_eq!(
1326                slice_cast_val, cast_slice_val,
1327                "Slice-then-cast and cast-then-slice produced different values at index {i}. \
1328                 Slice-then-cast: {slice_cast_val:?}, Cast-then-slice: {cast_slice_val:?}"
1329            );
1330        }
1331    }
1332}
1333
1334/// Run all consistency tests on an array.
1335///
1336/// This function executes a comprehensive suite of consistency tests that verify
1337/// the correctness of compute operations on Vortex arrays.
1338///
1339/// # Test Suite Overview
1340///
1341/// ## Core Operation Consistency
1342/// - **Filter/Take**: Verifies `filter(array, mask)` equals `take(array, true_indices)`
1343/// - **Mask Composition**: Ensures sequential masks equal combined masks
1344/// - **Slice/Filter**: Checks contiguous filters equal slice operations
1345/// - **Take/Slice**: Validates sequential takes equal slice operations
1346/// - **Cast/Slice**: Ensures cast operations preserve sliced array properties
1347///
1348/// ## Boolean Operations
1349/// - **De Morgan's Laws**: Verifies boolean operations follow logical laws
1350///
1351/// ## Comparison Operations
1352/// - **Inverse Relationships**: Verifies logical inverses (Eq/NotEq, Gt/Lte, Lt/Gte)
1353/// - **Symmetry**: Ensures proper ordering relationships when operands are swapped
1354///
1355/// ## Aggregate Operations
1356/// - **Slice/Aggregate**: Verifies aggregates on sliced arrays match canonical
1357///
1358/// ## Identity Operations
1359/// - **Filter Identity**: All-true mask preserves the array
1360/// - **Mask Identity**: All-false mask preserves values (as nullable)
1361/// - **Take Identity**: Taking all indices preserves the array
1362///
1363/// ## Edge Cases
1364/// - **Empty Operations**: Empty filters, takes, and slices behave correctly
1365/// - **Single Element**: Operations work with single-element arrays
1366/// - **Repeated Indices**: Take with duplicate indices works correctly
1367///
1368/// ## Null Handling
1369/// - **Nullable Indices**: Null indices produce null values
1370/// - **Mask/Filter Interaction**: Masking then filtering behaves predictably
1371///
1372/// ## Large Arrays
1373/// - **Performance**: Operations scale correctly to large arrays (1000+ elements)
1374/// ```text
1375pub fn test_array_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) {
1376    // Core operation consistency
1377    test_filter_take_consistency(array, ctx);
1378    test_double_mask_consistency(array, ctx);
1379    test_slice_filter_consistency(array, ctx);
1380    test_take_slice_consistency(array, ctx);
1381    test_cast_slice_consistency(array, ctx);
1382
1383    // Boolean operations
1384    test_boolean_demorgan_consistency(array, ctx);
1385
1386    // Comparison operations
1387    test_comparison_inverse_consistency(array, ctx);
1388    test_comparison_symmetry_consistency(array, ctx);
1389
1390    // Aggregate operations
1391    test_slice_aggregate_consistency(array, ctx);
1392
1393    // Identity operations
1394    test_filter_identity(array, ctx);
1395    test_mask_identity(array, ctx);
1396    test_take_preserves_properties(array, ctx);
1397
1398    // Ordering and correctness
1399    test_filter_preserves_order(array, ctx);
1400    test_take_repeated_indices(array, ctx);
1401
1402    // Null handling
1403    test_mask_filter_null_consistency(array, ctx);
1404    test_nullable_indices_consistency(array, ctx);
1405
1406    // Edge cases
1407    test_empty_operations_consistency(array);
1408    test_large_array_consistency(array, ctx);
1409}