Skip to content

Commit 9583f20

Browse files
committed
add tests
Signed-off-by: Connor Tsui <[email protected]>
1 parent 4f296e1 commit 9583f20

File tree

4 files changed

+305
-0
lines changed

4 files changed

+305
-0
lines changed

vortex-compute/src/take/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
//! Take function.
55
6+
mod bit_buffer;
67
mod buffer;
8+
mod mask;
79
pub mod slice;
10+
mod vector;
811

912
/// Function for taking based on indices (which can have different representations).
1013
pub trait Take<Indices: ?Sized> {

vortex-compute/src/take/vector/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ mod bool;
1111
mod primitive;
1212
mod pvector;
1313

14+
#[cfg(test)]
15+
mod tests;
16+
1417
impl<T> Take<PrimitiveVector> for &T
1518
where
1619
for<'a> &'a T: Take<PVector<u8>, Output = T>,
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use vortex_vector::VectorMutOps;
5+
use vortex_vector::bool::BoolVector;
6+
use vortex_vector::bool::BoolVectorMut;
7+
use vortex_vector::primitive::PVector;
8+
use vortex_vector::primitive::PVectorMut;
9+
use vortex_vector::primitive::PrimitiveVector;
10+
11+
use crate::take::Take;
12+
13+
/// Tests `Take` on `PVector` with mixed validity in both data and indices.
14+
///
15+
/// This test covers:
16+
/// - Taking from a vector with some null values.
17+
/// - Using indices that have some null values.
18+
/// - Verifying that nulls in the data propagate correctly.
19+
/// - Verifying that nulls in indices result in null outputs.
20+
/// - Using non-sequential indices to test the general case.
21+
#[test]
22+
fn test_pvector_take_with_nullable_indices() {
23+
// Data: [10, null, 30, 40, null, 60]
24+
let data: PVectorMut<i32> = [Some(10), None, Some(30), Some(40), None, Some(60)]
25+
.into_iter()
26+
.collect();
27+
let data = data.freeze();
28+
29+
// Indices: [0, null, 2, 5, null] (u32 indices with nulls)
30+
let indices: PVectorMut<u32> = [Some(0), None, Some(2), Some(5), None]
31+
.into_iter()
32+
.collect();
33+
let indices = indices.freeze();
34+
35+
let result = (&data).take(&indices);
36+
37+
// Expected: [10, null, 30, 60, null]
38+
// - Index 0 -> data[0] = 10 (valid)
39+
// - Index null -> null (null index produces null)
40+
// - Index 2 -> data[2] = 30 (valid)
41+
// - Index 5 -> data[5] = 60 (valid)
42+
// - Index null -> null (null index produces null)
43+
assert_eq!(result.get(0), Some(&10));
44+
assert_eq!(result.get(1), None); // Null index.
45+
assert_eq!(result.get(2), Some(&30));
46+
assert_eq!(result.get(3), Some(&60));
47+
assert_eq!(result.get(4), None); // Null index.
48+
}
49+
50+
/// Tests `Take` on `PVector` using `PrimitiveVector` indices (type-erased).
51+
///
52+
/// This ensures the generic `Take<PrimitiveVector>` impl works correctly by dispatching to the
53+
/// typed implementation.
54+
#[test]
55+
fn test_pvector_take_with_primitive_vector_indices() {
56+
// Data: [100, 200, null, 400, 500]
57+
let data: PVectorMut<i64> = [Some(100), Some(200), None, Some(400), Some(500)]
58+
.into_iter()
59+
.collect();
60+
let data = data.freeze();
61+
62+
// Indices as PrimitiveVector (u16): [4, 2, 0, 1]
63+
let indices: PVectorMut<u16> = [4u16, 2, 0, 1].into_iter().collect();
64+
let indices: PrimitiveVector = indices.freeze().into();
65+
66+
let result: PVector<i64> = (&data).take(&indices);
67+
68+
// Expected: [500, null, 100, 200]
69+
assert_eq!(result.get(0), Some(&500));
70+
assert_eq!(result.get(1), None); // data[2] is null.
71+
assert_eq!(result.get(2), Some(&100));
72+
assert_eq!(result.get(3), Some(&200));
73+
}
74+
75+
/// Tests `Take` on `BoolVector` with mixed validity in both data and indices.
76+
///
77+
/// This test covers:
78+
/// - Taking from a boolean vector with some null values.
79+
/// - Using indices that have some null values.
80+
/// - Verifying that nulls in the data propagate correctly.
81+
/// - Verifying that nulls in indices result in null outputs.
82+
#[test]
83+
fn test_bool_vector_take_with_nullable_indices() {
84+
// Data: [true, null, false, true, null, false]
85+
let data: BoolVectorMut = [Some(true), None, Some(false), Some(true), None, Some(false)]
86+
.into_iter()
87+
.collect();
88+
let data = data.freeze();
89+
90+
// Indices: [5, null, 0, 3, null, 2] (u32 indices with nulls)
91+
let indices: PVectorMut<u32> = [Some(5), None, Some(0), Some(3), None, Some(2)]
92+
.into_iter()
93+
.collect();
94+
let indices = indices.freeze();
95+
96+
let result = (&data).take(&indices);
97+
98+
// Expected: [false, null, true, true, null, false]
99+
// - Index 5 -> data[5] = false (valid)
100+
// - Index null -> null (null index produces null)
101+
// - Index 0 -> data[0] = true (valid)
102+
// - Index 3 -> data[3] = true (valid)
103+
// - Index null -> null (null index produces null)
104+
// - Index 2 -> data[2] = false (valid)
105+
assert_eq!(result.get(0), Some(false));
106+
assert_eq!(result.get(1), None); // Null index.
107+
assert_eq!(result.get(2), Some(true));
108+
assert_eq!(result.get(3), Some(true));
109+
assert_eq!(result.get(4), None); // Null index.
110+
assert_eq!(result.get(5), Some(false));
111+
}
112+
113+
/// Tests `Take` on `BoolVector` using `PrimitiveVector` indices (type-erased).
114+
///
115+
/// This ensures the generic `Take<PrimitiveVector>` impl works correctly for `BoolVector`.
116+
#[test]
117+
fn test_bool_vector_take_with_primitive_vector_indices() {
118+
// Data: [true, false, null, true, false]
119+
let data: BoolVectorMut = [Some(true), Some(false), None, Some(true), Some(false)]
120+
.into_iter()
121+
.collect();
122+
let data = data.freeze();
123+
124+
// Indices as PrimitiveVector (u64): [4, 2, 1, 0, 3]
125+
let indices: PVectorMut<u64> = [4u64, 2, 1, 0, 3].into_iter().collect();
126+
let indices: PrimitiveVector = indices.freeze().into();
127+
128+
let result: BoolVector = (&data).take(&indices);
129+
130+
// Expected: [false, null, false, true, true]
131+
assert_eq!(result.get(0), Some(false)); // data[4]
132+
assert_eq!(result.get(1), None); // data[2] is null.
133+
assert_eq!(result.get(2), Some(false)); // data[1]
134+
assert_eq!(result.get(3), Some(true)); // data[0]
135+
assert_eq!(result.get(4), Some(true)); // data[3]
136+
}
137+
138+
/// Tests `Take` on `BitBuffer` covering both code paths.
139+
///
140+
/// This test covers:
141+
/// - The `take_byte_bool` path (small buffer, len <= 4096).
142+
/// - The `take_bool` path (large buffer, len > 4096).
143+
/// - Non-sequential indices to verify correct bit extraction.
144+
#[test]
145+
fn test_bit_buffer_take_small_and_large() {
146+
use vortex_buffer::BitBuffer;
147+
148+
// Small buffer (uses take_byte_bool path).
149+
let small: BitBuffer = [true, false, true, true, false, true, false, false]
150+
.into_iter()
151+
.collect();
152+
let indices = [7u32, 0, 2, 5, 1];
153+
let result = (&small).take(&indices[..]);
154+
155+
assert_eq!(result.len(), 5);
156+
assert!(!result.value(0)); // small[7] = false
157+
assert!(result.value(1)); // small[0] = true
158+
assert!(result.value(2)); // small[2] = true
159+
assert!(result.value(3)); // small[5] = true
160+
assert!(!result.value(4)); // small[1] = false
161+
162+
// Large buffer (uses take_bool path, len > 4096).
163+
let large: BitBuffer = (0..5000).map(|i| i % 3 == 0).collect();
164+
let indices = [4999u32, 0, 1, 2, 3, 4998];
165+
let result = (&large).take(&indices[..]);
166+
167+
assert_eq!(result.len(), 6);
168+
assert!(!result.value(0)); // 4999 % 3 != 0
169+
assert!(result.value(1)); // 0 % 3 == 0
170+
assert!(!result.value(2)); // 1 % 3 != 0
171+
assert!(!result.value(3)); // 2 % 3 != 0
172+
assert!(result.value(4)); // 3 % 3 == 0
173+
assert!(result.value(5)); // 4998 % 3 == 0
174+
}
175+
176+
/// Tests `Take` on `Mask` covering all mask variants and nullable index handling.
177+
///
178+
/// This test covers:
179+
/// - `Mask::AllTrue` with slice indices.
180+
/// - `Mask::AllFalse` with slice indices.
181+
/// - `Mask::Values` with slice indices.
182+
/// - `Mask::AllTrue` with nullable `PVector` indices (returns cloned validity).
183+
/// - `Mask::AllFalse` with nullable `PVector` indices.
184+
/// - `Mask::Values` with nullable `PVector` indices (both small and large paths).
185+
#[test]
186+
fn test_mask_take_all_variants() {
187+
use vortex_mask::Mask;
188+
189+
// Test AllTrue with slice indices.
190+
let all_true = Mask::AllTrue(10);
191+
let indices = [9u32, 0, 5];
192+
let result = (&all_true).take(&indices[..]);
193+
assert!(result.all_true());
194+
assert_eq!(result.len(), 3);
195+
196+
// Test AllFalse with slice indices.
197+
let all_false = Mask::AllFalse(10);
198+
let result = (&all_false).take(&indices[..]);
199+
assert!(result.all_false());
200+
assert_eq!(result.len(), 3);
201+
202+
// Test Values with slice indices.
203+
let values = Mask::from_iter([true, false, true, true, false, true]);
204+
let indices = [5u32, 1, 0, 4];
205+
let result = (&values).take(&indices[..]);
206+
assert_eq!(result.len(), 4);
207+
assert!(result.value(0)); // values[5] = true
208+
assert!(!result.value(1)); // values[1] = false
209+
assert!(result.value(2)); // values[0] = true
210+
assert!(!result.value(3)); // values[4] = false
211+
212+
// Test AllTrue with nullable PVector indices (some indices are null).
213+
// When self is AllTrue, result validity equals indices validity.
214+
let all_true = Mask::AllTrue(10);
215+
let indices: PVectorMut<u32> = [Some(0), None, Some(5), None, Some(9)]
216+
.into_iter()
217+
.collect();
218+
let indices = indices.freeze();
219+
let result = (&all_true).take(&indices);
220+
assert_eq!(result.len(), 5);
221+
assert!(result.value(0)); // Valid index.
222+
assert!(!result.value(1)); // Null index -> false.
223+
assert!(result.value(2)); // Valid index.
224+
assert!(!result.value(3)); // Null index -> false.
225+
assert!(result.value(4)); // Valid index.
226+
227+
// Test AllFalse with nullable PVector indices (result is always AllFalse).
228+
let all_false = Mask::AllFalse(10);
229+
let result = (&all_false).take(&indices);
230+
assert!(result.all_false());
231+
assert_eq!(result.len(), 5);
232+
233+
// Test Values with nullable PVector indices.
234+
// Combines mask values with index nullability.
235+
let values = Mask::from_iter([true, false, true, false, true, false]);
236+
let indices: PVectorMut<u32> = [Some(0), None, Some(1), Some(4), None]
237+
.into_iter()
238+
.collect();
239+
let indices = indices.freeze();
240+
let result = (&values).take(&indices);
241+
assert_eq!(result.len(), 5);
242+
assert!(result.value(0)); // values[0] = true, index valid.
243+
assert!(!result.value(1)); // Null index -> false.
244+
assert!(!result.value(2)); // values[1] = false.
245+
assert!(result.value(3)); // values[4] = true, index valid.
246+
assert!(!result.value(4)); // Null index -> false.
247+
}
248+
249+
/// Tests `Take` on `PrimitiveVector` using `PVector` indices.
250+
///
251+
/// This ensures the type-erased `PrimitiveVector` can be taken with typed `PVector` indices,
252+
/// covering the `Take<PVector<I>> for &PrimitiveVector` implementation.
253+
#[test]
254+
fn test_primitive_vector_take_with_pvector_indices() {
255+
// Data as PrimitiveVector (i32): [10, 20, null, 40, 50]
256+
let data: PVectorMut<i32> = [Some(10), Some(20), None, Some(40), Some(50)]
257+
.into_iter()
258+
.collect();
259+
let data: PrimitiveVector = data.freeze().into();
260+
261+
// Indices as PVector<u16> with some nulls: [4, null, 2, 0, null]
262+
let indices: PVectorMut<u16> = [Some(4), None, Some(2), Some(0), None]
263+
.into_iter()
264+
.collect();
265+
let indices = indices.freeze();
266+
267+
let result = (&data).take(&indices);
268+
269+
// Expected: [50, null, null, 10, null]
270+
// - Index 4 -> data[4] = 50 (valid)
271+
// - Index null -> null (null index produces null)
272+
// - Index 2 -> data[2] = null (data is null)
273+
// - Index 0 -> data[0] = 10 (valid)
274+
// - Index null -> null (null index produces null)
275+
let PrimitiveVector::I32(result) = result else {
276+
panic!("Expected I32 variant");
277+
};
278+
assert_eq!(result.get(0), Some(&50));
279+
assert_eq!(result.get(1), None); // Null index.
280+
assert_eq!(result.get(2), None); // data[2] is null.
281+
assert_eq!(result.get(3), Some(&10));
282+
assert_eq!(result.get(4), None); // Null index.
283+
}

vortex-vector/src/bool/vector.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ impl BoolVector {
7373
pub fn bits(&self) -> &BitBuffer {
7474
&self.bits
7575
}
76+
77+
/// Gets a nullable element at the given index, panicking on out-of-bounds.
78+
///
79+
/// If the element at the given index is null, returns `None`. Otherwise, returns `Some(x)`,
80+
/// where `x: bool`.
81+
///
82+
/// Note that this `get` method is different from the standard library [`slice::get`], which
83+
/// returns `None` if the index is out of bounds. This method will panic if the index is out of
84+
/// bounds, and return `None` if the element is null.
85+
///
86+
/// # Panics
87+
///
88+
/// Panics if the index is out of bounds.
89+
pub fn get(&self, index: usize) -> Option<bool> {
90+
self.validity.value(index).then(|| self.bits.value(index))
91+
}
7692
}
7793

7894
impl VectorOps for BoolVector {

0 commit comments

Comments
 (0)