Skip to content

Commit ffd8429

Browse files
connortsui20gatesn
andauthored
Feature: add struct OperatorVTable::bind (#5105)
Tracking Issue: #5028 --------- Signed-off-by: Connor Tsui <[email protected]> Signed-off-by: Nicholas Gates <[email protected]> Co-authored-by: Nicholas Gates <[email protected]>
1 parent 8074872 commit ffd8429

File tree

2 files changed

+191
-1
lines changed

2 files changed

+191
-1
lines changed

vortex-array/src/arrays/struct_/vtable/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{EncodingId, EncodingRef, vtable};
88
mod array;
99
mod canonical;
1010
mod operations;
11-
mod pipeline;
11+
mod operator;
1212
mod serde;
1313
mod validity;
1414
mod visitor;
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use std::sync::Arc;
5+
6+
use vortex_error::VortexResult;
7+
use vortex_vector::{StructVector, Vector};
8+
9+
use crate::ArrayRef;
10+
use crate::arrays::{StructArray, StructVTable};
11+
use crate::execution::{BatchKernelRef, BindCtx, kernel};
12+
use crate::vtable::{OperatorVTable, ValidityHelper};
13+
14+
impl OperatorVTable<StructVTable> for StructVTable {
15+
fn bind(
16+
array: &StructArray,
17+
selection: Option<&ArrayRef>,
18+
ctx: &mut dyn BindCtx,
19+
) -> VortexResult<BatchKernelRef> {
20+
// Bind all child field arrays with the selection.
21+
let field_kernels: Vec<_> = array
22+
.fields()
23+
.iter()
24+
.map(|field| ctx.bind(field, selection))
25+
.collect::<VortexResult<_>>()?;
26+
let validity = ctx.bind_validity(array.validity(), array.len(), selection)?;
27+
28+
Ok(kernel(move || {
29+
// Execute all child field kernels.
30+
let fields: Vec<Vector> = field_kernels
31+
.into_iter()
32+
.map(|k| k.execute())
33+
.collect::<VortexResult<_>>()?;
34+
let validity_mask = validity.execute()?;
35+
36+
Ok(StructVector::try_new(Arc::new(fields.into_boxed_slice()), validity_mask)?.into())
37+
}))
38+
}
39+
}
40+
41+
#[cfg(test)]
42+
mod tests {
43+
use std::sync::Arc;
44+
45+
use vortex_dtype::{FieldNames, PTypeDowncast};
46+
use vortex_vector::VectorOps;
47+
48+
use crate::IntoArray;
49+
use crate::arrays::{BoolArray, PrimitiveArray, StructArray};
50+
use crate::validity::Validity;
51+
52+
#[test]
53+
fn test_struct_operator_basic() {
54+
// Create a struct array with two fields: integers and booleans.
55+
let int_field = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5]);
56+
let bool_field = BoolArray::from_iter([true, false, true, false, true]);
57+
58+
let struct_array = StructArray::try_new(
59+
FieldNames::from(["ints", "bools"]),
60+
vec![int_field.into_array(), bool_field.into_array()],
61+
5,
62+
Validity::AllValid,
63+
)
64+
.unwrap();
65+
66+
// Execute without selection.
67+
let result = struct_array.execute().unwrap();
68+
assert_eq!(result.len(), 5);
69+
70+
// Verify the struct vector fields.
71+
let struct_vector = result.as_struct();
72+
let fields = struct_vector.fields();
73+
assert_eq!(fields.len(), 2);
74+
75+
// Verify the integer field values match the original.
76+
let int_vector = fields[0].as_primitive().clone().into_i32();
77+
assert_eq!(int_vector.elements().as_slice(), &[1, 2, 3, 4, 5]);
78+
79+
// Verify the boolean field values match the original.
80+
let bool_vector = fields[1].as_bool();
81+
let bool_values: Vec<bool> = (0..5).map(|i| bool_vector.bits().value(i)).collect();
82+
assert_eq!(bool_values, vec![true, false, true, false, true]);
83+
}
84+
85+
#[test]
86+
fn test_struct_operator_with_mask() {
87+
// Create a struct array with two fields.
88+
let int_field = PrimitiveArray::from_iter([10i32, 20, 30, 40, 50, 60]);
89+
let bool_field = BoolArray::from_iter([true, false, true, false, true, false]);
90+
91+
let struct_array = StructArray::try_new(
92+
FieldNames::from(["numbers", "flags"]),
93+
vec![int_field.into_array(), bool_field.into_array()],
94+
6,
95+
Validity::AllValid,
96+
)
97+
.unwrap();
98+
99+
// Create a selection mask that selects indices 0, 2, 4 (alternating pattern).
100+
let selection = BoolArray::from_iter([true, false, true, false, true, false]).into_array();
101+
102+
// Execute with selection mask.
103+
let result = struct_array
104+
.execute_with_selection(Some(&Arc::new(selection)))
105+
.unwrap();
106+
107+
// Verify the result has the filtered length.
108+
assert_eq!(result.len(), 3);
109+
110+
// Verify the struct vector fields.
111+
let struct_vector = result.as_struct();
112+
let fields = struct_vector.fields();
113+
assert_eq!(fields.len(), 2);
114+
115+
// Verify the integer field has the correct filtered values (indices 0, 2, 4).
116+
let int_vector = fields[0].as_primitive().clone().into_i32();
117+
assert_eq!(int_vector.elements().as_slice(), &[10, 30, 50]);
118+
119+
// Verify the boolean field has the correct filtered values (indices 0, 2, 4).
120+
let bool_vector = fields[1].as_bool();
121+
let bool_values: Vec<bool> = (0..3).map(|i| bool_vector.bits().value(i)).collect();
122+
assert_eq!(bool_values, vec![true, true, true]);
123+
}
124+
125+
#[test]
126+
fn test_struct_operator_null_handling() {
127+
// Create fields with nulls.
128+
let int_field = PrimitiveArray::from_option_iter([
129+
Some(100i32),
130+
None,
131+
Some(200),
132+
Some(300),
133+
None,
134+
Some(400),
135+
]);
136+
137+
// Create bool field with its own validity.
138+
let bool_array = BoolArray::from_iter([true, false, true, false, true, false]);
139+
let bool_validity = Validity::from_iter([true, true, false, true, true, false]);
140+
let bool_field = BoolArray::from_bit_buffer(bool_array.bit_buffer().clone(), bool_validity);
141+
142+
// Create struct with its own validity mask (rows 1 and 4 are null).
143+
let struct_validity = Validity::from_iter([true, false, true, true, false, true]);
144+
145+
let struct_array = StructArray::try_new(
146+
FieldNames::from(["values", "flags"]),
147+
vec![int_field.into_array(), bool_field.into_array()],
148+
6,
149+
struct_validity,
150+
)
151+
.unwrap();
152+
153+
// Create a selection mask that selects indices 0, 1, 2, 4, 5.
154+
let selection = BoolArray::from_iter([true, true, true, false, true, true]).into_array();
155+
156+
// Execute with selection mask.
157+
let result = struct_array
158+
.execute_with_selection(Some(&Arc::new(selection)))
159+
.unwrap();
160+
161+
assert_eq!(result.len(), 5);
162+
163+
// Verify the struct vector fields.
164+
let struct_vector = result.as_struct();
165+
let fields = struct_vector.fields();
166+
assert_eq!(fields.len(), 2);
167+
168+
// Verify integer field has the correct filtered values with nulls.
169+
// Selected indices: 0, 1, 2, 4, 5 from [Some(100), None, Some(200), Some(300), None, Some(400)].
170+
let int_vector = fields[0].as_primitive().clone().into_i32();
171+
let int_values: Vec<Option<i32>> = (0..5).map(|i| int_vector.get(i).copied()).collect();
172+
assert_eq!(
173+
int_values,
174+
vec![Some(100), None, Some(200), None, Some(400)]
175+
);
176+
177+
// Verify boolean field values.
178+
// Selected indices: 0, 1, 2, 4, 5 from [T, F, T, F, T, F].
179+
let bool_vector = fields[1].as_bool();
180+
let bool_values: Vec<bool> = (0..5).map(|i| bool_vector.bits().value(i)).collect();
181+
assert_eq!(bool_values, vec![true, false, true, true, false]);
182+
183+
// Verify the struct-level validity is correctly propagated.
184+
// Original struct validity: [T, F, T, T, F, T]
185+
// Selected indices: 0, 1, 2, 4, 5 -> validity: [T, F, T, F, T].
186+
let validity_mask = struct_vector.validity();
187+
let struct_validity_values: Vec<bool> = (0..5).map(|i| validity_mask.value(i)).collect();
188+
assert_eq!(struct_validity_values, vec![true, false, true, false, true]);
189+
}
190+
}

0 commit comments

Comments
 (0)