Skip to main content

vortex_array/compute/conformance/
take.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use vortex_buffer::buffer;
5use vortex_error::VortexExpect;
6
7use crate::ArrayRef;
8use crate::ExecutionCtx;
9use crate::IntoArray as _;
10use crate::RecursiveCanonical;
11use crate::arrays::PrimitiveArray;
12use crate::dtype::Nullability;
13
14/// Test conformance of the take compute function for an array.
15///
16/// This function tests various scenarios including:
17/// - Taking all elements
18/// - Taking no elements
19/// - Taking selective elements
20/// - Taking with out-of-bounds indices (should panic)
21/// - Taking with nullable indices
22/// - Edge cases like empty arrays
23pub fn test_take_conformance(array: &ArrayRef, ctx: &mut ExecutionCtx) {
24    let len = array.len();
25
26    if len > 0 {
27        test_take_all(array, ctx);
28        test_take_selective(array, ctx);
29        test_take_first_and_last(array, ctx);
30        test_take_with_nullable_indices(array, ctx);
31        test_take_repeated_indices(array, ctx);
32    }
33
34    test_empty_indices(array);
35
36    // Additional edge cases for non-empty arrays
37    if len > 0 {
38        test_take_reverse(array, ctx);
39        test_take_single_middle(array, ctx);
40    }
41
42    if len > 3 {
43        test_take_random_unsorted(array, ctx);
44        test_take_contiguous_range(array, ctx);
45        test_take_mixed_repeated(array, ctx);
46    }
47
48    // Test for larger arrays
49    if len >= 1024 {
50        test_take_large_indices(array, ctx);
51    }
52}
53
54pub(super) fn test_take_all(array: &ArrayRef, ctx: &mut ExecutionCtx) {
55    let len = array.len();
56    let indices = PrimitiveArray::from_iter(0..len as u64);
57    let result = array
58        .take(indices.into_array())
59        .vortex_expect("take should succeed in conformance test")
60        .execute::<RecursiveCanonical>(ctx)
61        .vortex_expect("take execution should succeed in conformance test")
62        .0
63        .into_array();
64
65    assert_eq!(result.len(), len);
66    assert_eq!(result.dtype(), array.dtype());
67
68    // The aggregate comparator does not support every dtype in this suite, including unions.
69    for i in 0..len {
70        assert_eq!(
71            array
72                .execute_scalar(i, ctx)
73                .vortex_expect("scalar_at should succeed in conformance test"),
74            result
75                .execute_scalar(i, ctx)
76                .vortex_expect("scalar_at should succeed in conformance test")
77        );
78    }
79}
80
81#[expect(clippy::cast_possible_truncation)]
82fn test_take_selective(array: &ArrayRef, ctx: &mut ExecutionCtx) {
83    let len = array.len();
84
85    // Take every other element
86    let indices: Vec<u64> = (0..len as u64).step_by(2).collect();
87    let expected_len = indices.len();
88    let indices_array = PrimitiveArray::from_iter(indices.clone());
89
90    let result = array
91        .take(indices_array.into_array())
92        .vortex_expect("take should succeed in conformance test")
93        .execute::<RecursiveCanonical>(ctx)
94        .vortex_expect("take execution should succeed in conformance test")
95        .0
96        .into_array();
97    assert_eq!(result.len(), expected_len);
98
99    // Verify the taken elements
100    for (result_idx, &original_idx) in indices.iter().enumerate() {
101        assert_eq!(
102            array
103                .execute_scalar(original_idx as usize, ctx)
104                .vortex_expect("scalar_at should succeed in conformance test"),
105            result
106                .execute_scalar(result_idx, ctx)
107                .vortex_expect("scalar_at should succeed in conformance test")
108        );
109    }
110}
111
112fn test_take_first_and_last(array: &ArrayRef, ctx: &mut ExecutionCtx) {
113    let len = array.len();
114    let indices = PrimitiveArray::from_iter([0u64, (len - 1) as u64]);
115    let result = array
116        .take(indices.into_array())
117        .vortex_expect("take should succeed in conformance test")
118        .execute::<RecursiveCanonical>(ctx)
119        .vortex_expect("take execution should succeed in conformance test")
120        .0
121        .into_array();
122
123    assert_eq!(result.len(), 2);
124    assert_eq!(
125        array
126            .execute_scalar(0, ctx)
127            .vortex_expect("scalar_at should succeed in conformance test"),
128        result
129            .execute_scalar(0, ctx)
130            .vortex_expect("scalar_at should succeed in conformance test")
131    );
132    assert_eq!(
133        array
134            .execute_scalar(len - 1, ctx)
135            .vortex_expect("scalar_at should succeed in conformance test"),
136        result
137            .execute_scalar(1, ctx)
138            .vortex_expect("scalar_at should succeed in conformance test")
139    );
140}
141
142#[expect(clippy::cast_possible_truncation)]
143fn test_take_with_nullable_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) {
144    let len = array.len();
145
146    // Create indices with some null values
147    let indices_vec: Vec<Option<u64>> = if len >= 3 {
148        vec![Some(0), None, Some((len - 1) as u64)]
149    } else if len >= 2 {
150        vec![Some(0), None]
151    } else {
152        vec![None]
153    };
154
155    let indices = PrimitiveArray::from_option_iter(indices_vec.clone());
156    let result = array
157        .take(indices.into_array())
158        .vortex_expect("take should succeed in conformance test")
159        .execute::<RecursiveCanonical>(ctx)
160        .vortex_expect("take execution should succeed in conformance test")
161        .0
162        .into_array();
163
164    assert_eq!(result.len(), indices_vec.len());
165    assert_eq!(
166        result.dtype(),
167        &array.dtype().with_nullability(Nullability::Nullable)
168    );
169
170    // Verify values
171    for (i, idx_opt) in indices_vec.iter().enumerate() {
172        match idx_opt {
173            Some(idx) => {
174                let expected = array
175                    .execute_scalar(*idx as usize, ctx)
176                    .vortex_expect("scalar_at should succeed in conformance test");
177                let actual = result
178                    .execute_scalar(i, ctx)
179                    .vortex_expect("scalar_at should succeed in conformance test");
180                assert_eq!(expected, actual);
181            }
182            None => {
183                assert!(
184                    result
185                        .execute_scalar(i, ctx)
186                        .vortex_expect("scalar_at should succeed in conformance test")
187                        .is_null()
188                );
189            }
190        }
191    }
192}
193
194pub(super) fn test_take_repeated_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) {
195    if array.is_empty() {
196        return;
197    }
198
199    // Take the first element multiple times
200    let indices = buffer![0u64, 0, 0].into_array();
201    let result = array
202        .take(indices)
203        .vortex_expect("take should succeed in conformance test")
204        .execute::<RecursiveCanonical>(ctx)
205        .vortex_expect("take execution should succeed in conformance test")
206        .0
207        .into_array();
208
209    assert_eq!(result.len(), 3);
210    let first_elem = array
211        .execute_scalar(0, ctx)
212        .vortex_expect("scalar_at should succeed in conformance test");
213    for i in 0..3 {
214        assert_eq!(
215            result
216                .execute_scalar(i, ctx)
217                .vortex_expect("scalar_at should succeed in conformance test"),
218            first_elem
219        );
220    }
221}
222
223fn test_empty_indices(array: &ArrayRef) {
224    let indices = PrimitiveArray::empty::<u64>(Nullability::NonNullable);
225    let result = array
226        .take(indices.into_array())
227        .vortex_expect("take should succeed in conformance test");
228
229    assert_eq!(result.len(), 0);
230    assert_eq!(result.dtype(), array.dtype());
231}
232
233fn test_take_reverse(array: &ArrayRef, ctx: &mut ExecutionCtx) {
234    let len = array.len();
235    // Take elements in reverse order
236    let indices = PrimitiveArray::from_iter((0..len as u64).rev());
237    let result = array
238        .take(indices.into_array())
239        .vortex_expect("take should succeed in conformance test")
240        .execute::<RecursiveCanonical>(ctx)
241        .vortex_expect("take execution should succeed in conformance test")
242        .0
243        .into_array();
244
245    assert_eq!(result.len(), len);
246
247    // Verify elements are in reverse order
248    for i in 0..len {
249        assert_eq!(
250            array
251                .execute_scalar(len - 1 - i, ctx)
252                .vortex_expect("scalar_at should succeed in conformance test"),
253            result
254                .execute_scalar(i, ctx)
255                .vortex_expect("scalar_at should succeed in conformance test")
256        );
257    }
258}
259
260fn test_take_single_middle(array: &ArrayRef, ctx: &mut ExecutionCtx) {
261    let len = array.len();
262    let middle_idx = len / 2;
263
264    let indices = PrimitiveArray::from_iter([middle_idx as u64]);
265    let result = array
266        .take(indices.into_array())
267        .vortex_expect("take should succeed in conformance test")
268        .execute::<RecursiveCanonical>(ctx)
269        .vortex_expect("take execution should succeed in conformance test")
270        .0
271        .into_array();
272
273    assert_eq!(result.len(), 1);
274    assert_eq!(
275        array
276            .execute_scalar(middle_idx, ctx)
277            .vortex_expect("scalar_at should succeed in conformance test"),
278        result
279            .execute_scalar(0, ctx)
280            .vortex_expect("scalar_at should succeed in conformance test")
281    );
282}
283
284#[expect(clippy::cast_possible_truncation)]
285fn test_take_random_unsorted(array: &ArrayRef, ctx: &mut ExecutionCtx) {
286    let len = array.len();
287
288    // Create a pseudo-random but deterministic pattern
289    let mut indices = Vec::with_capacity(len.min(10));
290    let mut idx = 1u64;
291    for _ in 0..len.min(10) {
292        indices.push((idx * 7 + 3) % len as u64);
293        idx = (idx * 3 + 1) % len as u64;
294    }
295
296    let indices_array = PrimitiveArray::from_iter(indices.clone());
297    let result = array
298        .take(indices_array.into_array())
299        .vortex_expect("take should succeed in conformance test")
300        .execute::<RecursiveCanonical>(ctx)
301        .vortex_expect("take execution should succeed in conformance test")
302        .0
303        .into_array();
304
305    assert_eq!(result.len(), indices.len());
306
307    // Verify elements match
308    for (i, &idx) in indices.iter().enumerate() {
309        assert_eq!(
310            array
311                .execute_scalar(idx as usize, ctx)
312                .vortex_expect("scalar_at should succeed in conformance test"),
313            result
314                .execute_scalar(i, ctx)
315                .vortex_expect("scalar_at should succeed in conformance test")
316        );
317    }
318}
319
320fn test_take_contiguous_range(array: &ArrayRef, ctx: &mut ExecutionCtx) {
321    let len = array.len();
322    let start = len / 4;
323    let end = len / 2;
324
325    // Take a contiguous range from the middle
326    let indices = PrimitiveArray::from_iter(start as u64..end as u64);
327    let result = array
328        .take(indices.into_array())
329        .vortex_expect("take should succeed in conformance test")
330        .execute::<RecursiveCanonical>(ctx)
331        .vortex_expect("take execution should succeed in conformance test")
332        .0
333        .into_array();
334
335    assert_eq!(result.len(), end - start);
336
337    // Verify elements
338    for i in 0..(end - start) {
339        assert_eq!(
340            array
341                .execute_scalar(start + i, ctx)
342                .vortex_expect("scalar_at should succeed in conformance test"),
343            result
344                .execute_scalar(i, ctx)
345                .vortex_expect("scalar_at should succeed in conformance test")
346        );
347    }
348}
349
350#[expect(clippy::cast_possible_truncation)]
351fn test_take_mixed_repeated(array: &ArrayRef, ctx: &mut ExecutionCtx) {
352    let len = array.len();
353
354    // Create pattern with some repeated indices
355    let indices = vec![
356        0u64,
357        0,
358        1,
359        1,
360        len as u64 / 2,
361        len as u64 / 2,
362        len as u64 / 2,
363        (len - 1) as u64,
364    ];
365
366    let indices_array = PrimitiveArray::from_iter(indices.clone());
367    let result = array
368        .take(indices_array.into_array())
369        .vortex_expect("take should succeed in conformance test")
370        .execute::<RecursiveCanonical>(ctx)
371        .vortex_expect("take execution should succeed in conformance test")
372        .0
373        .into_array();
374
375    assert_eq!(result.len(), indices.len());
376
377    // Verify elements
378    for (i, &idx) in indices.iter().enumerate() {
379        assert_eq!(
380            array
381                .execute_scalar(idx as usize, ctx)
382                .vortex_expect("scalar_at should succeed in conformance test"),
383            result
384                .execute_scalar(i, ctx)
385                .vortex_expect("scalar_at should succeed in conformance test")
386        );
387    }
388}
389
390#[expect(clippy::cast_possible_truncation)]
391fn test_take_large_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) {
392    // Test with a large number of indices to stress test performance
393    let len = array.len();
394    let num_indices = 10000.min(len * 3);
395
396    // Create many indices with a pattern
397    let indices: Vec<u64> = (0..num_indices)
398        .map(|i| ((i * 17 + 5) % len) as u64)
399        .collect();
400
401    let indices_array = PrimitiveArray::from_iter(indices.clone());
402    let result = array
403        .take(indices_array.into_array())
404        .vortex_expect("take should succeed in conformance test")
405        .execute::<RecursiveCanonical>(ctx)
406        .vortex_expect("take execution should succeed in conformance test")
407        .0
408        .into_array();
409
410    assert_eq!(result.len(), num_indices);
411
412    // Spot check a few elements
413    for i in (0..num_indices).step_by(1000) {
414        let expected_idx = indices[i] as usize;
415        assert_eq!(
416            array
417                .execute_scalar(expected_idx, ctx)
418                .vortex_expect("scalar_at should succeed in conformance test"),
419            result
420                .execute_scalar(i, ctx)
421                .vortex_expect("scalar_at should succeed in conformance test")
422        );
423    }
424}