vortex_array/pipeline/operators/
binary_bool.rs1use std::any::Any;
5use std::hash::Hash;
6use std::rc::Rc;
7
8use vortex_error::{VortexExpect, VortexResult, vortex_bail, vortex_panic};
9
10use crate::pipeline::bits::BitView;
11use crate::pipeline::operators::{BindContext, Operator};
12use crate::pipeline::types::VType;
13use crate::pipeline::vec::VectorId;
14use crate::pipeline::view::ViewMut;
15use crate::pipeline::{Kernel, KernelContext};
16
17#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
19pub enum BoolOp {
20 And,
21 Or,
22}
23
24#[derive(Debug, Hash)]
26pub struct BinaryBoolOpOperator {
27 children: [Rc<dyn Operator>; 2],
28 op: BoolOp,
29}
30
31impl BinaryBoolOpOperator {
32 fn new(left: Rc<dyn Operator>, right: Rc<dyn Operator>, op: BoolOp) -> Self {
33 let VType::Bool = left.vtype() else {
35 vortex_panic!("BinaryBoolOpOperator left child must be boolean type");
36 };
37 let VType::Bool = right.vtype() else {
38 vortex_panic!("BinaryBoolOpOperator right child must be boolean type");
39 };
40
41 Self {
42 children: [left, right],
43 op,
44 }
45 }
46
47 pub fn and(left: Rc<dyn Operator>, right: Rc<dyn Operator>) -> Self {
48 Self::new(left, right, BoolOp::And)
49 }
50
51 pub fn or(left: Rc<dyn Operator>, right: Rc<dyn Operator>) -> Self {
52 Self::new(left, right, BoolOp::Or)
53 }
54}
55
56impl Operator for BinaryBoolOpOperator {
57 fn as_any(&self) -> &dyn Any {
58 self
59 }
60
61 fn vtype(&self) -> VType {
62 VType::Bool
63 }
64
65 fn children(&self) -> &[Rc<dyn Operator>] {
66 &self.children
67 }
68
69 fn with_children(&self, children: Vec<Rc<dyn Operator>>) -> Rc<dyn Operator> {
70 let [left, right] = children
71 .try_into()
72 .ok()
73 .vortex_expect("Expected 2 children");
74 Rc::new(BinaryBoolOpOperator::new(left, right, self.op))
75 }
76
77 fn bind(&self, ctx: &dyn BindContext) -> VortexResult<Box<dyn Kernel>> {
78 if self.vtype() != VType::Bool {
79 vortex_bail!("BinaryBoolOpOperator only supports boolean types");
80 }
81
82 Ok(Box::new(BinaryBoolOpKernel {
83 left: ctx.children()[0],
84 right: ctx.children()[1],
85 op: self.op,
86 }))
87 }
88}
89
90pub struct BinaryBoolOpKernel {
92 left: VectorId,
93 right: VectorId,
94 op: BoolOp,
95}
96
97impl Kernel for BinaryBoolOpKernel {
98 fn seek(&mut self, _chunk_idx: usize) -> VortexResult<()> {
99 Ok(())
100 }
101
102 fn step(
103 &mut self,
104 ctx: &KernelContext,
105 selected: BitView,
106 out: &mut ViewMut,
107 ) -> VortexResult<()> {
108 let left_vec = ctx.vector(self.left);
109 let right_vec = ctx.vector(self.right);
110 let left_values = left_vec.as_slice::<bool>();
111 let right_values = right_vec.as_slice::<bool>();
112 let out_slice = out.as_slice_mut::<bool>();
113
114 assert!(selected.true_count() <= left_values.len());
115 assert!(selected.true_count() <= right_values.len());
116 assert!(selected.true_count() <= out_slice.len());
117
118 match self.op {
119 BoolOp::And => {
120 for i in 0..selected.true_count() {
121 unsafe {
122 let left_val = *left_values.get_unchecked(i);
123 let right_val = *right_values.get_unchecked(i);
124 *out_slice.get_unchecked_mut(i) = left_val && right_val;
125 }
126 }
127 }
128 BoolOp::Or => {
129 for i in 0..selected.true_count() {
130 unsafe {
131 let left_val = *left_values.get_unchecked(i);
132 let right_val = *right_values.get_unchecked(i);
133 *out_slice.get_unchecked_mut(i) = left_val || right_val;
134 }
135 }
136 }
137 }
138
139 Ok(())
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use std::rc::Rc;
146
147 use vortex_buffer::BufferMut;
148 use vortex_dtype::Nullability;
149 use vortex_scalar::Scalar;
150
151 use super::*;
152 use crate::arrays::{ConstantArray, PrimitiveArray};
153 use crate::compute::Operator as BinaryOperator;
154 use crate::pipeline::bits::BitView;
155 use crate::pipeline::operators::scalar_compare::ScalarCompareOperator;
156 use crate::pipeline::query::QueryPlan;
157 use crate::pipeline::view::ViewMut;
158 use crate::pipeline::{N, N_WORDS};
159
160 #[test]
161 fn test_binary_bool_and_basic() {
162 let size = 4;
164 let left_primitive_array = [1i32, 0, 1, 0].into_iter().collect::<PrimitiveArray>();
165 let left_primitive_op = left_primitive_array
166 .as_ref()
167 .to_operator()
168 .unwrap()
169 .unwrap();
170
171 let right_primitive_array = [1i32, 1, 0, 0].into_iter().collect::<PrimitiveArray>();
173 let right_primitive_op = right_primitive_array.to_operator().unwrap().unwrap();
174
175 let zero_scalar = Scalar::primitive(0i32, Nullability::NonNullable);
176 let left_bool_op = Rc::new(ScalarCompareOperator::new(
177 left_primitive_op,
178 BinaryOperator::Gt,
179 zero_scalar.clone(),
180 ));
181 let right_bool_op = Rc::new(ScalarCompareOperator::new(
182 right_primitive_op,
183 BinaryOperator::Gt,
184 zero_scalar,
185 ));
186
187 let binary_and_op = Rc::new(BinaryBoolOpOperator::and(left_bool_op, right_bool_op));
189
190 let plan = QueryPlan::new(binary_and_op.as_ref()).unwrap();
192 let mut pipeline = plan.executable_plan().unwrap();
193
194 let mut mask_data = [0usize; N_WORDS];
196 mask_data[0] = 0b1111; let mask_view = BitView::new(&mask_data);
198
199 let mut output = BufferMut::<bool>::with_capacity(N);
201 unsafe { output.set_len(N) };
202 let mut output_view = ViewMut::new(&mut output[..], None);
203
204 let result = pipeline._step(mask_view, &mut output_view);
206 assert!(result.is_ok());
207
208 let expected = [true, false, false, false];
211 for i in 0..size {
212 assert_eq!(
213 output[i], expected[i],
214 "Position {}: expected {}, got {}",
215 i, expected[i], output[i]
216 );
217 }
218 }
219
220 #[test]
221 fn test_binary_bool_or_basic() {
222 let size = 4;
224 let left_primitive_array = [1i32, 0, 1, 0].into_iter().collect::<PrimitiveArray>();
225 let left_primitive_op = left_primitive_array.to_operator().unwrap().unwrap();
226
227 let right_primitive_array = [1i32, 1, 0, 0].into_iter().collect::<PrimitiveArray>();
229 let right_primitive_op = right_primitive_array.to_operator().unwrap().unwrap();
230
231 let zero_scalar = Scalar::primitive(0i32, Nullability::NonNullable);
233 let left_bool_op = Rc::new(ScalarCompareOperator::new(
234 left_primitive_op,
235 BinaryOperator::Gt,
236 zero_scalar.clone(),
237 ));
238 let right_bool_op = Rc::new(ScalarCompareOperator::new(
239 right_primitive_op,
240 BinaryOperator::Gt,
241 zero_scalar,
242 ));
243
244 let binary_or_op = Rc::new(BinaryBoolOpOperator::or(left_bool_op, right_bool_op));
246
247 let plan = QueryPlan::new(binary_or_op.as_ref()).unwrap();
249 let mut pipeline = plan.executable_plan().unwrap();
250
251 let mut mask_data = [0usize; N_WORDS];
253 mask_data[0] = 0b1111; let mask_view = BitView::new(&mask_data);
255
256 let mut output = BufferMut::<bool>::with_capacity(N);
258 unsafe { output.set_len(N) };
259 let mut output_view = ViewMut::new(&mut output[..], None);
260
261 let result = pipeline._step(mask_view, &mut output_view);
263 assert!(result.is_ok());
264
265 let expected = [true, true, true, false];
268 for i in 0..size {
269 assert_eq!(
270 output[i], expected[i],
271 "Position {}: expected {}, got {}",
272 i, expected[i], output[i]
273 );
274 }
275 }
276
277 #[test]
278 fn test_binary_bool_with_constant() {
279 let size = 4;
281
282 let x_primitive_array = [1i32, 0, 1, 0].into_iter().collect::<PrimitiveArray>();
284 let x_primitive_op = x_primitive_array.to_operator().unwrap().unwrap();
285
286 let y_primitive_array = [0i32, 0, 1, 1].into_iter().collect::<PrimitiveArray>();
288 let y_primitive_op = y_primitive_array.to_operator().unwrap().unwrap();
289
290 use vortex_dtype::Nullability;
292 use vortex_scalar::Scalar;
293
294 use crate::compute::Operator as BinaryOperator;
295 use crate::pipeline::operators::scalar_compare::ScalarCompareOperator;
296
297 let zero_scalar = Scalar::primitive(0i32, Nullability::NonNullable);
298 let x_bool_op = Rc::new(ScalarCompareOperator::new(
299 x_primitive_op,
300 BinaryOperator::Gt,
301 zero_scalar.clone(),
302 ));
303 let y_bool_op = Rc::new(ScalarCompareOperator::new(
304 y_primitive_op,
305 BinaryOperator::Gt,
306 zero_scalar,
307 ));
308
309 let constant_true = ConstantArray::new(Scalar::from(true), size)
311 .to_operator()
312 .unwrap()
313 .unwrap();
314
315 let x_and_y = Rc::new(BinaryBoolOpOperator::and(x_bool_op, y_bool_op));
317 let final_op = Rc::new(BinaryBoolOpOperator::or(x_and_y, constant_true));
318
319 let plan = QueryPlan::new(final_op.as_ref()).unwrap();
321 let mut pipeline = plan.executable_plan().unwrap();
322
323 let mut mask_data = [0usize; N_WORDS];
325 mask_data[0] = 0b1111; let mask_view = BitView::new(&mask_data);
327
328 let mut output = BufferMut::<bool>::with_capacity(N);
330 unsafe { output.set_len(N) };
331 let mut output_view = ViewMut::new(&mut output[..], None);
332
333 let result = pipeline._step(mask_view, &mut output_view);
335 assert!(result.is_ok());
336
337 let expected = [true, true, true, true];
340 for i in 0..size {
341 assert_eq!(
342 output[i], expected[i],
343 "Position {}: expected {}, got {}",
344 i, expected[i], output[i]
345 );
346 }
347 }
348}