@@ -17,33 +17,90 @@ use crate::columnar::Columnar;
1717use crate :: dtype:: DType ;
1818
1919impl Executable for Mask {
20+ /// Executes a boolean array into a [`Mask`].
21+ ///
22+ /// The array must have a non-nullable boolean dtype. Use [`MaskNullAsFalse`] to execute a
23+ /// nullable boolean array, coercing null elements to `false`.
2024 fn execute ( array : ArrayRef , ctx : & mut ExecutionCtx ) -> VortexResult < Self > {
21- if ! matches ! ( array. dtype ( ) , DType :: Bool ( _ ) ) {
22- vortex_bail ! ( "Mask array must have boolean dtype, not {}" , array . dtype ( ) ) ;
23- }
25+ execute_mask ( array, ctx , NullHandling :: Reject )
26+ }
27+ }
2428
25- if let Some ( constant) = array. as_opt :: < Constant > ( ) {
26- let mask_value = constant. scalar ( ) . as_bool ( ) . value ( ) . unwrap_or ( false ) ;
27- return Ok ( Mask :: new ( array. len ( ) , mask_value) ) ;
28- }
29+ /// An [`Executable`] target that executes a boolean array into a [`Mask`], coercing null
30+ /// elements to `false`.
31+ ///
32+ /// [`Mask`] itself requires a non-nullable boolean array and errors on nullable input. Use this
33+ /// wrapper for filter and pruning predicates over nullable data, where SQL semantics treat
34+ /// `NULL` as not matching.
35+ pub struct MaskNullAsFalse ( Mask ) ;
2936
30- let array_len = array. len ( ) ;
31- Ok ( match array. execute ( ctx) ? {
32- Columnar :: Constant ( s) => {
33- Mask :: new ( array_len, s. scalar ( ) . as_bool ( ) . value ( ) . unwrap_or ( false ) )
34- }
35- Columnar :: Canonical ( a) => {
36- let bool = a. into_array ( ) . execute :: < BoolArray > ( ctx) ?;
37- let mask = bool
38- . as_ref ( )
39- . validity ( ) ?
40- . execute_mask ( bool. as_ref ( ) . len ( ) , ctx) ?;
41- let bits = bool. into_bit_buffer ( ) ;
42- // To handle nullable boolean arrays, we treat nulls as false in the mask.
43- // TODO(ngates): is this correct? Feels like we should just force the caller to
44- // pass non-nullable boolean arrays.
45- mask. bitand ( & Mask :: from ( bits) )
46- }
47- } )
37+ impl MaskNullAsFalse {
38+ /// Consumes the wrapper and returns the underlying [`Mask`].
39+ pub fn into_mask ( self ) -> Mask {
40+ self . 0
41+ }
42+ }
43+
44+ impl From < MaskNullAsFalse > for Mask {
45+ fn from ( value : MaskNullAsFalse ) -> Self {
46+ value. 0
47+ }
48+ }
49+
50+ impl Executable for MaskNullAsFalse {
51+ fn execute ( array : ArrayRef , ctx : & mut ExecutionCtx ) -> VortexResult < Self > {
52+ execute_mask ( array, ctx, NullHandling :: AsFalse ) . map ( Self )
53+ }
54+ }
55+
56+ /// How [`execute_mask`] treats null elements of a nullable boolean array.
57+ enum NullHandling {
58+ /// Error if the boolean array is nullable.
59+ Reject ,
60+ /// Treat null elements as `false`.
61+ AsFalse ,
62+ }
63+
64+ fn execute_mask (
65+ array : ArrayRef ,
66+ ctx : & mut ExecutionCtx ,
67+ null_handling : NullHandling ,
68+ ) -> VortexResult < Mask > {
69+ if !matches ! ( array. dtype( ) , DType :: Bool ( _) ) {
70+ vortex_bail ! ( "Mask array must have boolean dtype, not {}" , array. dtype( ) ) ;
4871 }
72+
73+ if let Some ( constant) = array. as_opt :: < Constant > ( ) {
74+ let mask_value = constant. scalar ( ) . as_bool ( ) . value ( ) . unwrap_or ( false ) ;
75+ return Ok ( Mask :: new ( array. len ( ) , mask_value) ) ;
76+ }
77+
78+ let array_len = array. len ( ) ;
79+ Ok ( match array. execute ( ctx) ? {
80+ Columnar :: Constant ( s) => {
81+ Mask :: new ( array_len, s. scalar ( ) . as_bool ( ) . value ( ) . unwrap_or ( false ) )
82+ }
83+ Columnar :: Canonical ( a) => {
84+ let bool = a. into_array ( ) . execute :: < BoolArray > ( ctx) ?;
85+ match null_handling {
86+ NullHandling :: Reject => {
87+ if bool. as_ref ( ) . dtype ( ) . is_nullable ( ) {
88+ vortex_bail ! (
89+ "Mask requires a non-nullable boolean array, not {}; \
90+ use MaskNullAsFalse to coerce nulls to false",
91+ bool . as_ref( ) . dtype( )
92+ ) ;
93+ }
94+ Mask :: from ( bool. into_bit_buffer ( ) )
95+ }
96+ NullHandling :: AsFalse => {
97+ let validity = bool
98+ . as_ref ( )
99+ . validity ( ) ?
100+ . execute_mask ( bool. as_ref ( ) . len ( ) , ctx) ?;
101+ validity. bitand ( & Mask :: from ( bool. into_bit_buffer ( ) ) )
102+ }
103+ }
104+ }
105+ } )
49106}
0 commit comments