1use std::fmt::Display;
5use std::fmt::Formatter;
6
7#[expect(deprecated)]
8pub use boolean::and_kleene;
9#[expect(deprecated)]
10pub use boolean::or_kleene;
11use prost::Message;
12use vortex_error::VortexResult;
13use vortex_error::vortex_bail;
14use vortex_session::VortexSession;
15use vortex_session::registry::CachedId;
16
17use crate::ArrayRef;
18use crate::ExecutionCtx;
19use crate::arrays::ScalarFnArray;
20use crate::dtype::DType;
21use crate::dtype::Nullability;
22use crate::expr::BoundExpression;
23use crate::expr::bound;
24use crate::expr::display::ExprDisplay;
25use crate::proto::expr as pb;
26use crate::scalar_fn::Arity;
27use crate::scalar_fn::ChildName;
28use crate::scalar_fn::ExecutionArgs;
29use crate::scalar_fn::ReduceNode;
30use crate::scalar_fn::ScalarFnId;
31use crate::scalar_fn::ScalarFnVTable;
32use crate::scalar_fn::ScalarFnVTableExt;
33use crate::scalar_fn::fns::literal::Literal;
34use crate::scalar_fn::fns::operators::CompareOperator;
35use crate::scalar_fn::fns::operators::Operator;
36
37pub mod boolean;
38pub use boolean::BooleanExecuteAdaptor;
39pub use boolean::BooleanKernel;
40pub(crate) use boolean::execute_boolean;
41pub use boolean::kleene_boolean_buffer_scalar;
42pub use boolean::kleene_boolean_buffers;
43mod compare;
44pub use compare::*;
45mod numeric;
46pub(crate) use numeric::*;
47mod primitive_operand;
48
49use crate::scalar::NumericOperator;
50use crate::scalar::Scalar;
51
52#[derive(Clone)]
53pub struct Binary;
54
55impl Binary {
56 pub fn try_new(
62 lhs: ArrayRef,
63 rhs: ArrayRef,
64 operator: Operator,
65 ) -> VortexResult<ScalarFnArray> {
66 ScalarFnArray::try_new(Binary.bind(operator), [lhs, rhs])
67 }
68}
69
70const KLEENE_LUT: [[[bool; 2]; 2]; 2] = [
73 [[false, true], [true, true]], [[false, false], [false, true]], ];
76
77fn kleene_one_const<T: ReduceNode>(node: T, constant: bool, is_and: bool) -> T {
80 match (is_and, constant) {
81 (true, true) | (false, false) => node,
82 (is_and, constant) => node.new_constant((constant && !is_and).into()),
83 }
84}
85
86impl ScalarFnVTable for Binary {
87 type Options = Operator;
88
89 fn id(&self) -> ScalarFnId {
90 static ID: CachedId = CachedId::new("vortex.binary");
91 *ID
92 }
93
94 fn serialize(&self, instance: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
95 Ok(Some(
96 pb::BinaryOpts {
97 op: (*instance).into(),
98 }
99 .encode_to_vec(),
100 ))
101 }
102
103 fn deserialize(
104 &self,
105 _metadata: &[u8],
106 _session: &VortexSession,
107 ) -> VortexResult<Self::Options> {
108 let opts = pb::BinaryOpts::decode(_metadata)?;
109 Operator::try_from(opts.op)
110 }
111
112 fn arity(&self, _options: &Self::Options) -> Arity {
113 Arity::Exact(2)
114 }
115
116 fn child_name(&self, _instance: &Self::Options, child_idx: usize) -> ChildName {
117 match child_idx {
118 0 => ChildName::from("lhs"),
119 1 => ChildName::from("rhs"),
120 _ => unreachable!("Binary has only two children"),
121 }
122 }
123
124 fn fmt_sql(
125 &self,
126 operator: &Operator,
127 expr: &dyn ExprDisplay,
128 f: &mut Formatter<'_>,
129 ) -> std::fmt::Result {
130 write!(f, "(")?;
131 Display::fmt(expr.display_child(0), f)?;
132 write!(f, " {} ", operator)?;
133 Display::fmt(expr.display_child(1), f)?;
134 write!(f, ")")
135 }
136
137 fn return_dtype(&self, operator: &Operator, arg_dtypes: &[DType]) -> VortexResult<DType> {
138 let lhs = &arg_dtypes[0];
139 let rhs = &arg_dtypes[1];
140
141 if operator.is_arithmetic() {
142 if lhs.is_primitive() && lhs.eq_ignore_nullability(rhs) {
143 return Ok(lhs.with_nullability(lhs.nullability() | rhs.nullability()));
144 }
145
146 if let DType::Decimal(decimal_dtype, _) = lhs
147 && lhs.eq_ignore_nullability(rhs)
148 {
149 let numeric_op = NumericOperator::try_from(*operator)?;
150 return Ok(DType::Decimal(
151 numeric_op_result_decimal_dtype(*decimal_dtype, numeric_op)?,
152 lhs.nullability() | rhs.nullability(),
153 ));
154 }
155 vortex_bail!(
156 "incompatible types for arithmetic operation: {} {}",
157 lhs,
158 rhs
159 );
160 }
161
162 if operator.is_comparison()
163 && !lhs.eq_ignore_nullability(rhs)
164 && !lhs.is_extension()
165 && !rhs.is_extension()
166 {
167 vortex_bail!("Cannot compare different DTypes {} and {}", lhs, rhs);
168 }
169
170 if matches!(operator, Operator::And | Operator::Or)
171 && !(lhs.is_boolean() && rhs.is_boolean())
172 {
173 vortex_bail!("'{operator}' requires bool operands, got {lhs} and {rhs}",);
174 }
175
176 Ok(DType::Bool((lhs.is_nullable() || rhs.is_nullable()).into()))
177 }
178
179 fn execute(
180 &self,
181 op: &Operator,
182 args: &dyn ExecutionArgs,
183 ctx: &mut ExecutionCtx,
184 ) -> VortexResult<ArrayRef> {
185 let lhs = args.get(0)?;
186 let rhs = args.get(1)?;
187
188 match op {
189 Operator::Eq => execute_compare(&lhs, &rhs, CompareOperator::Eq, ctx),
190 Operator::NotEq => execute_compare(&lhs, &rhs, CompareOperator::NotEq, ctx),
191 Operator::Lt => execute_compare(&lhs, &rhs, CompareOperator::Lt, ctx),
192 Operator::Lte => execute_compare(&lhs, &rhs, CompareOperator::Lte, ctx),
193 Operator::Gt => execute_compare(&lhs, &rhs, CompareOperator::Gt, ctx),
194 Operator::Gte => execute_compare(&lhs, &rhs, CompareOperator::Gte, ctx),
195 Operator::And => execute_boolean(lhs, rhs, Operator::And, ctx),
196 Operator::Or => execute_boolean(lhs, rhs, Operator::Or, ctx),
197 Operator::Add => execute_numeric(&lhs, &rhs, NumericOperator::Add, ctx),
198 Operator::Sub => execute_numeric(&lhs, &rhs, NumericOperator::Sub, ctx),
199 Operator::Mul => execute_numeric(&lhs, &rhs, NumericOperator::Mul, ctx),
200 Operator::Div => execute_numeric(&lhs, &rhs, NumericOperator::Div, ctx),
201 }
202 }
203
204 fn simplify(
205 &self,
206 operator: &Operator,
207 expr: &BoundExpression,
208 ) -> VortexResult<Option<BoundExpression>> {
209 let lhs = expr.child(0);
210 let rhs = expr.child(1);
211
212 let is_literal_null =
213 |expr: &BoundExpression| expr.as_opt::<Literal>().is_some_and(Scalar::is_null);
214
215 if operator.is_comparison() && (is_literal_null(lhs) || is_literal_null(rhs)) {
218 return Ok(Some(bound::lit(Scalar::null(DType::Bool(
219 Nullability::Nullable,
220 )))));
221 }
222
223 let bool_literal = |expr: &BoundExpression| {
224 expr.as_opt::<Literal>()?
225 .as_bool_opt()
226 .map(|value| value.value())
227 };
228
229 let result_nullable = lhs.dtype().is_nullable() || rhs.dtype().is_nullable();
247 let nullability = Nullability::from(result_nullable);
248 let bool_lit = |value: bool| bound::lit(Scalar::bool(value, nullability));
249 let widen = |child: &BoundExpression| {
250 if child.dtype().is_nullable() == result_nullable {
251 child.clone()
252 } else {
253 bound::cast(child.clone(), DType::Bool(nullability))
254 }
255 };
256
257 Ok(match operator {
258 Operator::And => match (bool_literal(lhs), bool_literal(rhs)) {
259 (Some(Some(false)), _) | (_, Some(Some(false))) => Some(bool_lit(false)),
260 (Some(Some(true)), _) => Some(widen(rhs)),
261 (_, Some(Some(true))) => Some(widen(lhs)),
262 (Some(None), Some(None)) => Some(widen(lhs)),
263 _ => None,
264 },
265 Operator::Or => match (bool_literal(lhs), bool_literal(rhs)) {
266 (Some(Some(true)), _) | (_, Some(Some(true))) => Some(bool_lit(true)),
267 (Some(Some(false)), _) => Some(widen(rhs)),
268 (_, Some(Some(false))) => Some(widen(lhs)),
269 (Some(None), Some(None)) => Some(widen(lhs)),
270 _ => None,
271 },
272 _ => None,
273 })
274 }
275
276 fn reduce<T: ReduceNode>(&self, operator: &Operator, node: &T) -> VortexResult<Option<T>> {
277 if !matches!(operator, Operator::And | Operator::Or) {
278 return Ok(None);
279 }
280 let left = node.child(0);
281 let right = node.child(1);
282
283 let left_const = left.as_constant();
284 let right_const = right.as_constant();
285
286 let left_const = left_const
290 .and_then(|s| s.value().cloned())
291 .map(|v| v.as_bool());
292 let right_const = right_const
293 .and_then(|s| s.value().cloned())
294 .map(|v| v.as_bool());
295 let is_and = *operator == Operator::And;
296
297 Ok(Some(match (left_const, right_const) {
298 (None, None) => return Ok(None),
299 (Some(left_const), Some(right_const)) => left.new_constant(
300 KLEENE_LUT[is_and as usize][left_const as usize][right_const as usize].into(),
301 ),
302 (Some(constant), None) => kleene_one_const(right, constant, is_and),
303 (None, Some(constant)) => kleene_one_const(left, constant, is_and),
304 }))
305 }
306
307 fn is_strict(&self, operator: &Operator) -> bool {
308 !matches!(operator, Operator::And | Operator::Or)
309 }
310
311 fn is_infallible(&self, operator: &Operator) -> bool {
312 matches!(
314 operator,
315 Operator::Eq
316 | Operator::NotEq
317 | Operator::Gt
318 | Operator::Gte
319 | Operator::Lt
320 | Operator::Lte
321 | Operator::And
322 | Operator::Or
323 )
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use vortex_error::VortexExpect;
330 use vortex_error::VortexResult;
331
332 use super::*;
333 use crate::IntoArray;
334 use crate::VortexSessionExecute;
335 use crate::array_session;
336 use crate::arrays::Bool;
337 use crate::arrays::BoolArray;
338 use crate::arrays::ConstantArray;
339 use crate::arrays::PrimitiveArray;
340 use crate::arrays::ScalarFn;
341 use crate::arrays::scalar_fn::ExactScalarFn;
342 use crate::arrays::scalar_fn::ScalarFnArrayExt;
343 use crate::assert_arrays_eq;
344 use crate::builtins::ArrayBuiltins;
345 use crate::dtype::DType;
346 use crate::dtype::Nullability;
347 use crate::dtype::PType;
348 use crate::expr::Expression;
349 use crate::expr::and;
350 use crate::expr::and_collect;
351 use crate::expr::col;
352 use crate::expr::eq;
353 use crate::expr::gt;
354 use crate::expr::gt_eq;
355 use crate::expr::lit;
356 use crate::expr::lt;
357 use crate::expr::lt_eq;
358 use crate::expr::not_eq;
359 use crate::expr::or;
360 use crate::expr::or_collect;
361 use crate::expr::test_harness;
362 use crate::optimizer::ArrayOptimizer;
363 use crate::scalar::Scalar;
364
365 #[test]
366 fn and_collect_balanced() {
367 let values = vec![lit(1), lit(2), lit(3), lit(4), lit(5)];
368
369 insta::assert_snapshot!(and_collect(values.into_iter()).unwrap().display_tree(), @r"
370 vortex.binary(and)
371 ├── lhs: vortex.binary(and)
372 │ ├── lhs: vortex.literal(1i32)
373 │ └── rhs: vortex.literal(2i32)
374 └── rhs: vortex.binary(and)
375 ├── lhs: vortex.binary(and)
376 │ ├── lhs: vortex.literal(3i32)
377 │ └── rhs: vortex.literal(4i32)
378 └── rhs: vortex.literal(5i32)
379 ");
380
381 let values = vec![lit(1), lit(2), lit(3), lit(4)];
383 insta::assert_snapshot!(and_collect(values.into_iter()).unwrap().display_tree(), @r"
384 vortex.binary(and)
385 ├── lhs: vortex.binary(and)
386 │ ├── lhs: vortex.literal(1i32)
387 │ └── rhs: vortex.literal(2i32)
388 └── rhs: vortex.binary(and)
389 ├── lhs: vortex.literal(3i32)
390 └── rhs: vortex.literal(4i32)
391 ");
392
393 let values = vec![lit(1)];
395 insta::assert_snapshot!(and_collect(values.into_iter()).unwrap().display_tree(), @"vortex.literal(1i32)");
396
397 let values: Vec<Expression> = vec![];
399 assert!(and_collect(values.into_iter()).is_none());
400 }
401
402 #[test]
403 fn or_collect_balanced() {
404 let values = vec![lit(1), lit(2), lit(3), lit(4)];
406 insta::assert_snapshot!(or_collect(values.into_iter()).unwrap().display_tree(), @r"
407 vortex.binary(or)
408 ├── lhs: vortex.binary(or)
409 │ ├── lhs: vortex.literal(1i32)
410 │ └── rhs: vortex.literal(2i32)
411 └── rhs: vortex.binary(or)
412 ├── lhs: vortex.literal(3i32)
413 └── rhs: vortex.literal(4i32)
414 ");
415 }
416
417 #[test]
418 fn dtype() {
419 let dtype = test_harness::struct_dtype();
420 let bool1: Expression = col("bool1");
421 let bool2: Expression = col("bool2");
422 assert_eq!(
423 and(bool1.clone(), bool2.clone())
424 .return_dtype(&dtype)
425 .unwrap(),
426 DType::Bool(Nullability::NonNullable)
427 );
428 assert_eq!(
429 or(bool1, bool2).return_dtype(&dtype).unwrap(),
430 DType::Bool(Nullability::NonNullable)
431 );
432
433 let col1: Expression = col("col1");
434 let col2: Expression = col("col2");
435
436 assert_eq!(
437 eq(col1.clone(), col2.clone()).return_dtype(&dtype).unwrap(),
438 DType::Bool(Nullability::Nullable)
439 );
440 assert_eq!(
441 not_eq(col1.clone(), col2.clone())
442 .return_dtype(&dtype)
443 .unwrap(),
444 DType::Bool(Nullability::Nullable)
445 );
446 assert_eq!(
447 gt(col1.clone(), col2.clone()).return_dtype(&dtype).unwrap(),
448 DType::Bool(Nullability::Nullable)
449 );
450 assert_eq!(
451 gt_eq(col1.clone(), col2.clone())
452 .return_dtype(&dtype)
453 .unwrap(),
454 DType::Bool(Nullability::Nullable)
455 );
456 assert_eq!(
457 lt(col1.clone(), col2.clone()).return_dtype(&dtype).unwrap(),
458 DType::Bool(Nullability::Nullable)
459 );
460 assert_eq!(
461 lt_eq(col1.clone(), col2.clone())
462 .return_dtype(&dtype)
463 .unwrap(),
464 DType::Bool(Nullability::Nullable)
465 );
466
467 assert_eq!(
468 or(lt(col1.clone(), col2.clone()), not_eq(col1, col2))
469 .return_dtype(&dtype)
470 .unwrap(),
471 DType::Bool(Nullability::Nullable)
472 );
473 }
474
475 #[test]
476 fn comparison_with_typed_null_simplifies_after_type_check() -> VortexResult<()> {
477 let dtype = test_harness::struct_dtype();
478
479 let expr = eq(
480 col("col1"),
481 lit(Scalar::null(DType::Primitive(
482 PType::U16,
483 Nullability::Nullable,
484 ))),
485 );
486
487 assert_eq!(
488 expr.bind(&dtype)?.optimize_recursive()?,
489 lit(Scalar::null(DType::Bool(Nullability::Nullable))).bind(&dtype)?
490 );
491 Ok(())
492 }
493
494 #[test]
495 fn comparison_with_incompatible_null_still_type_checks() {
496 let dtype = test_harness::struct_dtype();
497 let expr = eq(
498 col("col1"),
499 lit(Scalar::null(DType::Utf8(Nullability::Nullable))),
500 );
501
502 assert!(expr.bind(&dtype).is_err());
503 }
504
505 #[test]
506 fn test_display_print() {
507 let expr = gt(lit(1), lit(2));
508 assert_eq!(format!("{expr}"), "(1i32 > 2i32)");
509 }
510
511 #[test]
514 fn test_struct_comparison() {
515 use crate::IntoArray;
516 use crate::arrays::StructArray;
517
518 let lhs_struct = StructArray::from_fields(&[
520 ("a", PrimitiveArray::from_iter([1i32]).into_array()),
521 ("b", PrimitiveArray::from_iter([3i32]).into_array()),
522 ])
523 .unwrap()
524 .into_array();
525
526 let rhs_struct_equal = StructArray::from_fields(&[
527 ("a", PrimitiveArray::from_iter([1i32]).into_array()),
528 ("b", PrimitiveArray::from_iter([3i32]).into_array()),
529 ])
530 .unwrap()
531 .into_array();
532
533 let rhs_struct_different = StructArray::from_fields(&[
534 ("a", PrimitiveArray::from_iter([1i32]).into_array()),
535 ("b", PrimitiveArray::from_iter([4i32]).into_array()),
536 ])
537 .unwrap()
538 .into_array();
539
540 let result_equal = lhs_struct.binary(rhs_struct_equal, Operator::Eq).unwrap();
542 assert_eq!(
543 result_equal
544 .execute_scalar(0, &mut array_session().create_execution_ctx())
545 .vortex_expect("value"),
546 Scalar::bool(true, Nullability::NonNullable),
547 "Equal structs should be equal"
548 );
549
550 let result_different = lhs_struct
551 .binary(rhs_struct_different, Operator::Eq)
552 .unwrap();
553 assert_eq!(
554 result_different
555 .execute_scalar(0, &mut array_session().create_execution_ctx())
556 .vortex_expect("value"),
557 Scalar::bool(false, Nullability::NonNullable),
558 "Different structs should not be equal"
559 );
560 }
561
562 #[test]
563 fn test_or_kleene_validity() {
564 let mut ctx = array_session().create_execution_ctx();
565 use crate::arrays::StructArray;
566 use crate::expr::col;
567
568 let struct_arr = StructArray::from_fields(&[
569 ("a", BoolArray::from_iter([Some(true)]).into_array()),
570 (
571 "b",
572 BoolArray::from_iter([Option::<bool>::None]).into_array(),
573 ),
574 ])
575 .unwrap()
576 .into_array();
577
578 let expr = or(col("a"), col("b"));
579 let result = struct_arr.apply(&expr).unwrap();
580
581 assert_arrays_eq!(
582 result,
583 BoolArray::from_iter([Some(true)]).into_array(),
584 &mut ctx
585 )
586 }
587
588 #[test]
589 fn test_scalar_subtract_unsigned() {
590 let mut ctx = array_session().create_execution_ctx();
591 use vortex_buffer::buffer;
592
593 use crate::IntoArray;
594 use crate::arrays::ConstantArray;
595 use crate::arrays::PrimitiveArray;
596
597 let values = buffer![1u16, 2, 3].into_array();
598 let rhs = ConstantArray::new(Scalar::from(1u16), 3).into_array();
599 let result = values.binary(rhs, Operator::Sub).unwrap();
600 assert_arrays_eq!(result, PrimitiveArray::from_iter([0u16, 1, 2]), &mut ctx);
601 }
602
603 #[test]
604 fn test_scalar_subtract_signed() {
605 let mut ctx = array_session().create_execution_ctx();
606 use vortex_buffer::buffer;
607
608 use crate::IntoArray;
609 use crate::arrays::ConstantArray;
610 use crate::arrays::PrimitiveArray;
611
612 let values = buffer![1i64, 2, 3].into_array();
613 let rhs = ConstantArray::new(Scalar::from(-1i64), 3).into_array();
614 let result = values.binary(rhs, Operator::Sub).unwrap();
615 assert_arrays_eq!(result, PrimitiveArray::from_iter([2i64, 3, 4]), &mut ctx);
616 }
617
618 #[test]
619 fn test_scalar_subtract_nullable() {
620 let mut ctx = array_session().create_execution_ctx();
621 use crate::IntoArray;
622 use crate::arrays::ConstantArray;
623 use crate::arrays::PrimitiveArray;
624
625 let values = PrimitiveArray::from_option_iter([Some(1u16), Some(2), None, Some(3)]);
626 let rhs = ConstantArray::new(Scalar::from(Some(1u16)), 4).into_array();
627 let result = values.into_array().binary(rhs, Operator::Sub).unwrap();
628 assert_arrays_eq!(
629 result,
630 PrimitiveArray::from_option_iter([Some(0u16), Some(1), None, Some(2)]),
631 &mut ctx
632 );
633 }
634
635 #[test]
636 fn test_scalar_subtract_float() {
637 let mut ctx = array_session().create_execution_ctx();
638 use vortex_buffer::buffer;
639
640 use crate::IntoArray;
641 use crate::arrays::ConstantArray;
642 use crate::arrays::PrimitiveArray;
643
644 let values = buffer![1.0f64, 2.0, 3.0].into_array();
645 let rhs = ConstantArray::new(Scalar::from(-1f64), 3).into_array();
646 let result = values.binary(rhs, Operator::Sub).unwrap();
647 assert_arrays_eq!(
648 result,
649 PrimitiveArray::from_iter([2.0f64, 3.0, 4.0]),
650 &mut ctx
651 );
652 }
653
654 #[test]
655 fn test_scalar_subtract_float_underflow_is_ok() {
656 use vortex_buffer::buffer;
657
658 let values = buffer![f32::MIN, 2.0, 3.0].into_array();
659 let rhs1 = ConstantArray::new(Scalar::from(1.0f32), 3).into_array();
660 let _results = values.binary(rhs1, Operator::Sub).unwrap();
661 let values = buffer![f32::MIN, 2.0, 3.0].into_array();
662 let rhs2 = ConstantArray::new(Scalar::from(f32::MAX), 3).into_array();
663 let _results = values.binary(rhs2, Operator::Sub).unwrap();
664 }
665
666 #[test]
667 fn test_and_reduce() -> VortexResult<()> {
668 let mut ctx = array_session().create_execution_ctx();
669 let left = BoolArray::from_iter([true, true, false]).into_array();
670 let right = ConstantArray::new(true, 3).into_array();
671
672 let array = Binary::try_new(left.clone(), right.clone(), Operator::And)?
673 .into_array()
674 .optimize()?; assert!(array.is::<Bool>()); assert_arrays_eq!(array, left, &mut ctx);
677
678 let array = Binary::try_new(right.clone(), left.clone(), Operator::And)?
679 .into_array()
680 .optimize()?;
681 assert!(array.is::<Bool>()); assert_arrays_eq!(array, left, &mut ctx);
683
684 let array = Binary::try_new(left.clone(), right.clone(), Operator::Or)?
685 .into_array()
686 .optimize()?;
687 assert_eq!(array.as_constant(), Some(true.into())); let array = Binary::try_new(right.clone(), left, Operator::Or)?
690 .into_array()
691 .optimize()?;
692 assert_eq!(array.as_constant(), Some(true.into())); let array = Binary::try_new(right.clone(), right.clone(), Operator::Or)?
695 .into_array()
696 .optimize()?;
697 assert_eq!(array.as_constant(), Some(true.into())); let left_false = ConstantArray::new(false, 3).into_array();
700
701 let array = Binary::try_new(right.clone(), left_false.clone(), Operator::Or)?
702 .into_array()
703 .optimize()?;
704 assert_eq!(array.as_constant(), Some(true.into()));
705 let array = Binary::try_new(left_false, right, Operator::And)?
706 .into_array()
707 .optimize()?;
708 assert_eq!(array.as_constant(), Some(false.into()));
709
710 Ok(())
711 }
712
713 #[test]
714 fn test_and_reduce_nullable() -> VortexResult<()> {
715 let mut ctx = array_session().create_execution_ctx();
716 let nullable_true =
717 ConstantArray::new(Scalar::bool(true, Nullability::Nullable), 3).into_array();
718 let right = BoolArray::from_iter([true, false, true]).into_array();
719
720 let array = Binary::try_new(nullable_true, right.clone(), Operator::And)?
721 .into_array()
722 .optimize()?;
723 assert_arrays_eq!(array, right, &mut ctx);
724 Ok(())
725 }
726
727 #[test]
728 fn test_and_reject_non_bool() {
729 let lhs = ConstantArray::new(7i32, 3).into_array();
730 let rhs = PrimitiveArray::from_iter([1i32, 2, 3]).into_array();
731 assert!(Binary::try_new(lhs, rhs, Operator::And).is_err());
732 }
733
734 #[test]
735 fn test_isnull_and_reduce() -> VortexResult<()> {
736 let mut ctx = array_session().create_execution_ctx();
737 let left = BoolArray::from_iter([Some(true), Some(true), None]).into_array();
738 let right = BoolArray::from_iter([true, false, true]).into_array();
739
740 for (lhs, rhs) in [(left.clone(), right.clone()), (right.clone(), left)] {
743 let array = lhs
744 .binary(rhs.clone(), Operator::And)?
745 .is_null()?
746 .optimize()?;
747 assert_eq!(*array.as_::<ExactScalarFn<Binary>>().options, Operator::And);
748 assert_arrays_eq!(
749 array.as_::<ScalarFn>().get_child(0), BoolArray::from_iter([false, false, true]),
751 &mut ctx
752 );
753 assert_arrays_eq!(array.as_::<ScalarFn>().get_child(1), right, &mut ctx);
754 }
755 Ok(())
756 }
757
758 #[test]
759 fn test_isnotnull_or_reduce() -> VortexResult<()> {
760 let mut ctx = array_session().create_execution_ctx();
761 let left = BoolArray::from_iter([Some(true), Some(true), None]).into_array();
762 let right = BoolArray::from_iter([true, false, true]).into_array();
763
764 for (lhs, rhs) in [(left.clone(), right.clone()), (right.clone(), left)] {
767 let array = lhs
768 .binary(rhs.clone(), Operator::Or)?
769 .is_not_null()?
770 .optimize()?;
771 assert_eq!(*array.as_::<ExactScalarFn<Binary>>().options, Operator::Or);
772 assert_arrays_eq!(
773 array.as_::<ScalarFn>().get_child(0), BoolArray::from_iter([true, true, false]),
775 &mut ctx
776 );
777 assert_arrays_eq!(array.as_::<ScalarFn>().get_child(1), right, &mut ctx);
778 }
779 Ok(())
780 }
781}