@@ -27,6 +27,7 @@ use crate::proto::expr as pb;
2727use crate :: scalar_fn:: Arity ;
2828use crate :: scalar_fn:: ChildName ;
2929use crate :: scalar_fn:: ExecutionArgs ;
30+ use crate :: scalar_fn:: ReduceNode ;
3031use crate :: scalar_fn:: ScalarFnId ;
3132use crate :: scalar_fn:: ScalarFnVTable ;
3233use crate :: scalar_fn:: ScalarFnVTableExt ;
@@ -68,6 +69,22 @@ impl Binary {
6869 }
6970}
7071
72+ /// Kleene and/or lookup table where both arguments are constant and non-NULL.
73+ /// is_and, left, right
74+ const KLEENE_LUT : [ [ [ bool ; 2 ] ; 2 ] ; 2 ] = [
75+ [ [ false , true ] , [ true , true ] ] , // or
76+ [ [ false , false ] , [ false , true ] ] , // and
77+ ] ;
78+
79+ /// Kleene and/or reduction where one argument is constant non-NULL and other
80+ /// is non-constant.
81+ fn kleene_one_const < T : ReduceNode > ( node : T , constant : bool , is_and : bool ) -> T {
82+ match ( is_and, constant) {
83+ ( true , true ) | ( false , false ) => node,
84+ ( is_and, constant) => node. new_constant ( ( constant && !is_and) . into ( ) ) ,
85+ }
86+ }
87+
7188impl ScalarFnVTable for Binary {
7289 type Options = Operator ;
7390
@@ -152,6 +169,12 @@ impl ScalarFnVTable for Binary {
152169 vortex_bail ! ( "Cannot compare different DTypes {} and {}" , lhs, rhs) ;
153170 }
154171
172+ if matches ! ( operator, Operator :: And | Operator :: Or )
173+ && !( lhs. is_boolean ( ) && rhs. is_boolean ( ) )
174+ {
175+ vortex_bail ! ( "'{operator}' requires bool operands, got {lhs} and {rhs}" , ) ;
176+ }
177+
155178 Ok ( DType :: Bool ( ( lhs. is_nullable ( ) || rhs. is_nullable ( ) ) . into ( ) ) )
156179 }
157180
@@ -254,18 +277,43 @@ impl ScalarFnVTable for Binary {
254277 operator : & Operator ,
255278 expression : & Expression ,
256279 ) -> VortexResult < Option < Expression > > {
280+ if matches ! ( operator, Operator :: And | Operator :: Or ) {
281+ return Ok ( None ) ; // AND and OR are kleene logic
282+ }
257283 let lhs = expression. child ( 0 ) . validity ( ) ?;
258284 let rhs = expression. child ( 1 ) . validity ( ) ?;
285+ Ok ( Some ( and ( lhs, rhs) ) )
286+ }
259287
260- Ok ( match operator {
261- // AND and OR are kleene logic.
262- Operator :: And => None ,
263- Operator :: Or => None ,
264- _ => {
265- // All other binary operators are null if either side is null.
266- Some ( and ( lhs, rhs) )
267- }
268- } )
288+ fn reduce < T : ReduceNode > ( & self , operator : & Operator , node : & T ) -> VortexResult < Option < T > > {
289+ if !matches ! ( operator, Operator :: And | Operator :: Or ) {
290+ return Ok ( None ) ;
291+ }
292+ let left = node. child ( 0 ) ;
293+ let right = node. child ( 1 ) ;
294+
295+ let left_const = left. as_constant ( ) ;
296+ let right_const = right. as_constant ( ) ;
297+
298+ // We don't handle Kleene NULL reduction here (.value() returns None
299+ // for NULL). This will be reduced in the boolean kernel during
300+ // execution. Not handling the case keeps the code much simpler.
301+ let left_const = left_const
302+ . and_then ( |s| s. value ( ) . cloned ( ) )
303+ . map ( |v| v. as_bool ( ) ) ;
304+ let right_const = right_const
305+ . and_then ( |s| s. value ( ) . cloned ( ) )
306+ . map ( |v| v. as_bool ( ) ) ;
307+ let is_and = * operator == Operator :: And ;
308+
309+ Ok ( Some ( match ( left_const, right_const) {
310+ ( None , None ) => return Ok ( None ) ,
311+ ( Some ( left_const) , Some ( right_const) ) => left. new_constant (
312+ KLEENE_LUT [ is_and as usize ] [ left_const as usize ] [ right_const as usize ] . into ( ) ,
313+ ) ,
314+ ( Some ( constant) , None ) => kleene_one_const ( right, constant, is_and) ,
315+ ( None , Some ( constant) ) => kleene_one_const ( left, constant, is_and) ,
316+ } ) )
269317 }
270318
271319 fn is_strict ( & self , operator : & Operator ) -> bool {
@@ -296,8 +344,13 @@ mod tests {
296344 use vortex_error:: VortexResult ;
297345
298346 use super :: * ;
347+ use crate :: IntoArray ;
299348 use crate :: VortexSessionExecute ;
300349 use crate :: array_session;
350+ use crate :: arrays:: Bool ;
351+ use crate :: arrays:: BoolArray ;
352+ use crate :: arrays:: ConstantArray ;
353+ use crate :: arrays:: PrimitiveArray ;
301354 use crate :: assert_arrays_eq;
302355 use crate :: builtins:: ArrayBuiltins ;
303356 use crate :: dtype:: DType ;
@@ -316,7 +369,9 @@ mod tests {
316369 use crate :: expr:: or;
317370 use crate :: expr:: or_collect;
318371 use crate :: expr:: test_harness;
372+ use crate :: optimizer:: ArrayOptimizer ;
319373 use crate :: scalar:: Scalar ;
374+
320375 #[ test]
321376 fn and_collect_balanced ( ) {
322377 let values = vec ! [ lit( 1 ) , lit( 2 ) , lit( 3 ) , lit( 4 ) , lit( 5 ) ] ;
@@ -472,40 +527,22 @@ mod tests {
472527
473528 // Create a struct array with one element for testing.
474529 let lhs_struct = StructArray :: from_fields ( & [
475- (
476- "a" ,
477- crate :: arrays:: PrimitiveArray :: from_iter ( [ 1i32 ] ) . into_array ( ) ,
478- ) ,
479- (
480- "b" ,
481- crate :: arrays:: PrimitiveArray :: from_iter ( [ 3i32 ] ) . into_array ( ) ,
482- ) ,
530+ ( "a" , PrimitiveArray :: from_iter ( [ 1i32 ] ) . into_array ( ) ) ,
531+ ( "b" , PrimitiveArray :: from_iter ( [ 3i32 ] ) . into_array ( ) ) ,
483532 ] )
484533 . unwrap ( )
485534 . into_array ( ) ;
486535
487536 let rhs_struct_equal = StructArray :: from_fields ( & [
488- (
489- "a" ,
490- crate :: arrays:: PrimitiveArray :: from_iter ( [ 1i32 ] ) . into_array ( ) ,
491- ) ,
492- (
493- "b" ,
494- crate :: arrays:: PrimitiveArray :: from_iter ( [ 3i32 ] ) . into_array ( ) ,
495- ) ,
537+ ( "a" , PrimitiveArray :: from_iter ( [ 1i32 ] ) . into_array ( ) ) ,
538+ ( "b" , PrimitiveArray :: from_iter ( [ 3i32 ] ) . into_array ( ) ) ,
496539 ] )
497540 . unwrap ( )
498541 . into_array ( ) ;
499542
500543 let rhs_struct_different = StructArray :: from_fields ( & [
501- (
502- "a" ,
503- crate :: arrays:: PrimitiveArray :: from_iter ( [ 1i32 ] ) . into_array ( ) ,
504- ) ,
505- (
506- "b" ,
507- crate :: arrays:: PrimitiveArray :: from_iter ( [ 4i32 ] ) . into_array ( ) ,
508- ) ,
544+ ( "a" , PrimitiveArray :: from_iter ( [ 1i32 ] ) . into_array ( ) ) ,
545+ ( "b" , PrimitiveArray :: from_iter ( [ 4i32 ] ) . into_array ( ) ) ,
509546 ] )
510547 . unwrap ( )
511548 . into_array ( ) ;
@@ -535,8 +572,6 @@ mod tests {
535572 #[ test]
536573 fn test_or_kleene_validity ( ) {
537574 let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
538- use crate :: IntoArray ;
539- use crate :: arrays:: BoolArray ;
540575 use crate :: arrays:: StructArray ;
541576 use crate :: expr:: col;
542577
@@ -630,14 +665,79 @@ mod tests {
630665 fn test_scalar_subtract_float_underflow_is_ok ( ) {
631666 use vortex_buffer:: buffer;
632667
633- use crate :: IntoArray ;
634- use crate :: arrays:: ConstantArray ;
635-
636668 let values = buffer ! [ f32 :: MIN , 2.0 , 3.0 ] . into_array ( ) ;
637669 let rhs1 = ConstantArray :: new ( Scalar :: from ( 1.0f32 ) , 3 ) . into_array ( ) ;
638670 let _results = values. binary ( rhs1, Operator :: Sub ) . unwrap ( ) ;
639671 let values = buffer ! [ f32 :: MIN , 2.0 , 3.0 ] . into_array ( ) ;
640672 let rhs2 = ConstantArray :: new ( Scalar :: from ( f32:: MAX ) , 3 ) . into_array ( ) ;
641673 let _results = values. binary ( rhs2, Operator :: Sub ) . unwrap ( ) ;
642674 }
675+
676+ #[ test]
677+ fn test_and_reduce ( ) -> VortexResult < ( ) > {
678+ let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
679+ let left = BoolArray :: from_iter ( [ true , true , false ] ) . into_array ( ) ;
680+ let right = ConstantArray :: new ( true , 3 ) . into_array ( ) ;
681+
682+ let array = Binary :: try_new ( left. clone ( ) , right. clone ( ) , Operator :: And ) ?
683+ . into_array ( )
684+ . optimize ( ) ?; // calls reduce()
685+ assert ! ( array. is:: <Bool >( ) ) ; // and(left, const) -> left
686+ assert_arrays_eq ! ( array, left, & mut ctx) ;
687+
688+ let array = Binary :: try_new ( right. clone ( ) , left. clone ( ) , Operator :: And ) ?
689+ . into_array ( )
690+ . optimize ( ) ?;
691+ assert ! ( array. is:: <Bool >( ) ) ; // and(const, left) -> left
692+ assert_arrays_eq ! ( array, left, & mut ctx) ;
693+
694+ let array = Binary :: try_new ( left. clone ( ) , right. clone ( ) , Operator :: Or ) ?
695+ . into_array ( )
696+ . optimize ( ) ?;
697+ assert_eq ! ( array. as_constant( ) , Some ( true . into( ) ) ) ; // or(left, const) -> true
698+
699+ let array = Binary :: try_new ( right. clone ( ) , left, Operator :: Or ) ?
700+ . into_array ( )
701+ . optimize ( ) ?;
702+ assert_eq ! ( array. as_constant( ) , Some ( true . into( ) ) ) ; // or(const, left) -> true
703+
704+ let array = Binary :: try_new ( right. clone ( ) , right. clone ( ) , Operator :: Or ) ?
705+ . into_array ( )
706+ . optimize ( ) ?;
707+ assert_eq ! ( array. as_constant( ) , Some ( true . into( ) ) ) ; // or(const, const) -> const
708+
709+ let left_false = ConstantArray :: new ( false , 3 ) . into_array ( ) ;
710+
711+ let array = Binary :: try_new ( right. clone ( ) , left_false. clone ( ) , Operator :: Or ) ?
712+ . into_array ( )
713+ . optimize ( ) ?;
714+ assert_eq ! ( array. as_constant( ) , Some ( true . into( ) ) ) ;
715+ let array = Binary :: try_new ( left_false, right, Operator :: And ) ?
716+ . into_array ( )
717+ . optimize ( ) ?;
718+ assert_eq ! ( array. as_constant( ) , Some ( false . into( ) ) ) ;
719+
720+ Ok ( ( ) )
721+ }
722+
723+ #[ test]
724+ fn test_and_reduce_nullable ( ) -> VortexResult < ( ) > {
725+ let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
726+ let nullable_true =
727+ ConstantArray :: new ( Scalar :: bool ( true , Nullability :: Nullable ) , 3 ) . into_array ( ) ;
728+ let right = BoolArray :: from_iter ( [ true , false , true ] ) . into_array ( ) ;
729+
730+ let array = Binary :: try_new ( nullable_true, right. clone ( ) , Operator :: And ) ?
731+ . into_array ( )
732+ . optimize ( ) ?;
733+ assert_arrays_eq ! ( array, right, & mut ctx) ;
734+ Ok ( ( ) )
735+ }
736+
737+ #[ test]
738+ fn test_and_reject_non_bool ( ) {
739+ let lhs = ConstantArray :: new ( 7i32 , 3 ) . into_array ( ) ;
740+ let rhs = PrimitiveArray :: from_iter ( [ 1i32 , 2 , 3 ] ) . into_array ( ) ;
741+ assert ! ( Binary :: try_new( lhs, rhs, Operator :: And ) . is_err( ) ) ;
742+ }
643743}
0 commit comments