1515// specific language governing permissions and limitations
1616// under the License.
1717
18- use arrow:: array:: {
19- Array , ArrayRef , AsArray , BooleanArray , downcast_array, downcast_dictionary_array,
20- } ;
18+ //! Optimized primitive type filters for InList expressions.
19+ //!
20+ //! This module provides membership tests for Arrow primitive types.
21+
22+ use arrow:: array:: { Array , ArrayRef , AsArray , BooleanArray } ;
2123use arrow:: buffer:: { BooleanBuffer , NullBuffer } ;
22- use arrow:: compute:: take;
2324use arrow:: datatypes:: * ;
2425use datafusion_common:: { HashSet , Result , exec_datafusion_err} ;
2526use std:: hash:: { Hash , Hasher } ;
2627
27- use super :: static_filter:: StaticFilter ;
28+ use super :: result:: build_in_list_result;
29+ use super :: static_filter:: { StaticFilter , handle_dictionary} ;
30+
31+ /// Bitmap filter for O(1) set membership via single bit test.
32+ ///
33+ /// `UInt8` has only 256 possible values, so the filter stores membership in a
34+ /// 256-bit bitmap instead of using a hash table.
35+ pub ( super ) struct UInt8BitmapFilter {
36+ null_count : usize ,
37+ bits : [ u64 ; 4 ] ,
38+ }
39+
40+ impl UInt8BitmapFilter {
41+ pub ( super ) fn try_new ( in_array : & ArrayRef ) -> Result < Self > {
42+ let prim_array = in_array. as_primitive_opt :: < UInt8Type > ( ) . ok_or_else ( || {
43+ exec_datafusion_err ! ( "UInt8BitmapFilter: expected UInt8 array" )
44+ } ) ?;
45+ let mut bits = [ 0u64 ; 4 ] ;
46+ for v in prim_array. iter ( ) . flatten ( ) {
47+ let index = v as usize ;
48+ bits[ index / 64 ] |= 1u64 << ( index % 64 ) ;
49+ }
50+ Ok ( Self {
51+ null_count : prim_array. null_count ( ) ,
52+ bits,
53+ } )
54+ }
55+
56+ #[ inline( always) ]
57+ fn check ( & self , needle : u8 ) -> bool {
58+ let index = needle as usize ;
59+ ( self . bits [ index / 64 ] >> ( index % 64 ) ) & 1 != 0
60+ }
61+ }
62+
63+ impl StaticFilter for UInt8BitmapFilter {
64+ fn null_count ( & self ) -> usize {
65+ self . null_count
66+ }
67+
68+ fn contains ( & self , v : & dyn Array , negated : bool ) -> Result < BooleanArray > {
69+ handle_dictionary ! ( self , v, negated) ;
70+ let v = v. as_primitive_opt :: < UInt8Type > ( ) . ok_or_else ( || {
71+ exec_datafusion_err ! ( "UInt8BitmapFilter: expected UInt8 array" )
72+ } ) ?;
73+ let input_values = v. values ( ) ;
74+ Ok ( build_in_list_result (
75+ v. len ( ) ,
76+ v. nulls ( ) ,
77+ self . null_count > 0 ,
78+ negated,
79+ #[ inline( always) ]
80+ |i| {
81+ // SAFETY: `build_in_list_result` invokes this closure for
82+ // indices in `0..v.len()`, which matches `input_values.len()`.
83+ let needle = unsafe { * input_values. get_unchecked ( i) } ;
84+ self . check ( needle)
85+ } ,
86+ ) )
87+ }
88+ }
2889
2990/// Wrapper for f32 that implements Hash and Eq using bit comparison.
3091/// This treats NaN values as equal to each other when they have the same bit pattern.
@@ -94,9 +155,13 @@ macro_rules! primitive_static_filter {
94155
95156 impl $Name {
96157 pub ( super ) fn try_new( in_array: & ArrayRef ) -> Result <Self > {
97- let in_array = in_array
98- . as_primitive_opt:: <$ArrowType>( )
99- . ok_or_else( || exec_datafusion_err!( "Failed to downcast an array to a '{}' array" , stringify!( $ArrowType) ) ) ?;
158+ let in_array =
159+ in_array. as_primitive_opt:: <$ArrowType>( ) . ok_or_else( || {
160+ exec_datafusion_err!(
161+ "Failed to downcast an array to a '{}' array" ,
162+ stringify!( $ArrowType)
163+ )
164+ } ) ?;
100165
101166 let mut values = HashSet :: with_capacity( in_array. len( ) ) ;
102167 let null_count = in_array. null_count( ) ;
@@ -115,19 +180,14 @@ macro_rules! primitive_static_filter {
115180 }
116181
117182 fn contains( & self , v: & dyn Array , negated: bool ) -> Result <BooleanArray > {
118- // Handle dictionary arrays by recursing on the values
119- downcast_dictionary_array! {
120- v => {
121- let values_contains = self . contains( v. values( ) . as_ref( ) , negated) ?;
122- let result = take( & values_contains, v. keys( ) , None ) ?;
123- return Ok ( downcast_array( result. as_ref( ) ) )
124- }
125- _ => { }
126- }
183+ handle_dictionary!( self , v, negated) ;
127184
128- let v = v
129- . as_primitive_opt:: <$ArrowType>( )
130- . ok_or_else( || exec_datafusion_err!( "Failed to downcast an array to a '{}' array" , stringify!( $ArrowType) ) ) ?;
185+ let v = v. as_primitive_opt:: <$ArrowType>( ) . ok_or_else( || {
186+ exec_datafusion_err!(
187+ "Failed to downcast an array to a '{}' array" ,
188+ stringify!( $ArrowType)
189+ )
190+ } ) ?;
131191
132192 let haystack_has_nulls = self . null_count > 0 ;
133193 let needle_values = v. values( ) ;
@@ -188,8 +248,10 @@ macro_rules! primitive_static_filter {
188248 }
189249 ( true , true ) => {
190250 // Both have nulls - combine needle nulls with haystack-induced nulls
191- let needle_validity = needle_nulls. map( |n| n. inner( ) . clone( ) )
192- . unwrap_or_else( || BooleanBuffer :: new_set( needle_values. len( ) ) ) ;
251+ let needle_validity =
252+ needle_nulls. map( |n| n. inner( ) . clone( ) ) . unwrap_or_else(
253+ || BooleanBuffer :: new_set( needle_values. len( ) ) ,
254+ ) ;
193255
194256 // Valid when original "in set" is true (see above)
195257 let haystack_validity = if negated {
@@ -215,7 +277,6 @@ primitive_static_filter!(Int8StaticFilter, Int8Type);
215277primitive_static_filter ! ( Int16StaticFilter , Int16Type ) ;
216278primitive_static_filter ! ( Int32StaticFilter , Int32Type ) ;
217279primitive_static_filter ! ( Int64StaticFilter , Int64Type ) ;
218- primitive_static_filter ! ( UInt8StaticFilter , UInt8Type ) ;
219280primitive_static_filter ! ( UInt16StaticFilter , UInt16Type ) ;
220281primitive_static_filter ! ( UInt32StaticFilter , UInt32Type ) ;
221282primitive_static_filter ! ( UInt64StaticFilter , UInt64Type ) ;
@@ -231,3 +292,50 @@ macro_rules! float_static_filter {
231292// Generate specialized filters for float types using ordered wrappers
232293float_static_filter ! ( Float32StaticFilter , Float32Type , OrderedFloat32 ) ;
233294float_static_filter ! ( Float64StaticFilter , Float64Type , OrderedFloat64 ) ;
295+
296+ #[ cfg( test) ]
297+ mod tests {
298+ use super :: * ;
299+ use std:: sync:: Arc ;
300+
301+ use arrow:: array:: { DictionaryArray , Int8Array , UInt8Array } ;
302+
303+ fn assert_contains (
304+ filter : & UInt8BitmapFilter ,
305+ needles : & dyn Array ,
306+ expected : Vec < Option < bool > > ,
307+ ) -> Result < ( ) > {
308+ assert_eq ! (
309+ filter. contains( needles, false ) ?,
310+ BooleanArray :: from( expected)
311+ ) ;
312+ Ok ( ( ) )
313+ }
314+
315+ #[ test]
316+ fn bitmap_filter_u8_handles_nulls ( ) -> Result < ( ) > {
317+ let haystack: ArrayRef = Arc :: new ( UInt8Array :: from ( vec ! [ Some ( 1 ) , None , Some ( 3 ) ] ) ) ;
318+ let filter = UInt8BitmapFilter :: try_new ( & haystack) ?;
319+ let needles = UInt8Array :: from ( vec ! [ Some ( 1 ) , Some ( 2 ) , None , Some ( 3 ) ] ) ;
320+
321+ assert_contains ( & filter, & needles, vec ! [ Some ( true ) , None , None , Some ( true ) ] ) ?;
322+ assert_eq ! (
323+ filter. contains( & needles, true ) ?,
324+ BooleanArray :: from( vec![ Some ( false ) , None , None , Some ( false ) ] )
325+ ) ;
326+
327+ Ok ( ( ) )
328+ }
329+
330+ #[ test]
331+ fn bitmap_filter_u8_handles_dictionary_needles ( ) -> Result < ( ) > {
332+ let haystack: ArrayRef = Arc :: new ( UInt8Array :: from ( vec ! [ Some ( 1 ) , None , Some ( 3 ) ] ) ) ;
333+ let filter = UInt8BitmapFilter :: try_new ( & haystack) ?;
334+
335+ let keys = Int8Array :: from ( vec ! [ Some ( 0 ) , Some ( 1 ) , None , Some ( 2 ) ] ) ;
336+ let values = Arc :: new ( UInt8Array :: from ( vec ! [ Some ( 1 ) , Some ( 2 ) , Some ( 3 ) ] ) ) ;
337+ let needles = DictionaryArray :: try_new ( keys, values) ?;
338+
339+ assert_contains ( & filter, & needles, vec ! [ Some ( true ) , None , None , Some ( true ) ] )
340+ }
341+ }
0 commit comments