@@ -19,10 +19,136 @@ use super::InProgressArray;
1919use crate :: filter:: FilterPredicate ;
2020use arrow_array:: cast:: AsArray ;
2121use arrow_array:: { Array , ArrayRef , BooleanArray } ;
22- use arrow_buffer:: { BooleanBuffer , BooleanBufferBuilder , NullBufferBuilder } ;
22+ use arrow_buffer:: bit_chunk_iterator:: BitChunks ;
23+ use arrow_buffer:: { BooleanBuffer , BooleanBufferBuilder , NullBufferBuilder , bit_util} ;
2324use arrow_schema:: ArrowError ;
2425use std:: sync:: Arc ;
2526
27+ /// Joins compressed bit fragments into complete output words.
28+ #[ derive( Default ) ]
29+ struct WordPacker {
30+ /// Bits waiting to be appended.
31+ pending_bits : u64 ,
32+ /// Number of populated low bits in `pending_bits`.
33+ pending_len : u32 ,
34+ }
35+
36+ impl WordPacker {
37+ fn push ( & mut self , compressed_bits : u64 , bit_count : u32 ) -> Option < u64 > {
38+ self . pending_bits |= compressed_bits << self . pending_len ;
39+ let total_len = self . pending_len + bit_count;
40+ if total_len < 64 {
41+ self . pending_len = total_len;
42+ return None ;
43+ }
44+
45+ let complete_word = self . pending_bits ;
46+ // Keep bits that crossed the word boundary. Split the shift to avoid
47+ // shifting by 64 when `pending_len == 0`.
48+ self . pending_bits = ( compressed_bits >> 1 ) >> ( 63 - self . pending_len ) ;
49+ self . pending_len = total_len - 64 ;
50+ Some ( complete_word)
51+ }
52+
53+ fn finish ( self ) -> Option < ( u64 , usize ) > {
54+ ( self . pending_len != 0 ) . then_some ( ( self . pending_bits , self . pending_len as usize ) )
55+ }
56+ }
57+
58+ fn copy_filter_words_without_nulls (
59+ output_values : & mut BooleanBufferBuilder ,
60+ source_value_chunks : & BitChunks < ' _ > ,
61+ filter_chunks : & BitChunks < ' _ > ,
62+ ) -> usize {
63+ let mut value_packer = WordPacker :: default ( ) ;
64+ let mut selected_count = 0 ;
65+
66+ // Check the mask first so sparse filters skip reading most source words.
67+ for ( index, filter_mask) in filter_chunks. iter ( ) . enumerate ( ) {
68+ let selected_in_word = filter_mask. count_ones ( ) ;
69+ if selected_in_word == 0 {
70+ continue ;
71+ }
72+
73+ let compressed_values = bit_util:: compress ( source_value_chunks. chunk ( index) , filter_mask) ;
74+ if let Some ( complete_word) = value_packer. push ( compressed_values, selected_in_word) {
75+ output_values. append_word ( complete_word, 64 ) ;
76+ }
77+ selected_count += selected_in_word as usize ;
78+ }
79+
80+ let filter_mask = filter_chunks. remainder_bits ( ) ;
81+ let selected_in_word = filter_mask. count_ones ( ) ;
82+ if selected_in_word != 0 {
83+ let compressed_values =
84+ bit_util:: compress ( source_value_chunks. remainder_bits ( ) , filter_mask) ;
85+ if let Some ( complete_word) = value_packer. push ( compressed_values, selected_in_word) {
86+ output_values. append_word ( complete_word, 64 ) ;
87+ }
88+ selected_count += selected_in_word as usize ;
89+ }
90+
91+ if let Some ( ( pending_bits, pending_len) ) = value_packer. finish ( ) {
92+ output_values. append_word ( pending_bits, pending_len) ;
93+ }
94+ selected_count
95+ }
96+
97+ fn copy_filter_words_with_nulls (
98+ output_values : & mut BooleanBufferBuilder ,
99+ output_validity : & mut NullBufferBuilder ,
100+ source_value_chunks : & BitChunks < ' _ > ,
101+ source_validity_chunks : & BitChunks < ' _ > ,
102+ filter_chunks : & BitChunks < ' _ > ,
103+ ) -> usize {
104+ let mut value_packer = WordPacker :: default ( ) ;
105+ let mut validity_packer = WordPacker :: default ( ) ;
106+ let mut selected_count = 0 ;
107+
108+ // Check the mask first so sparse filters skip reading most source words.
109+ for ( index, filter_mask) in filter_chunks. iter ( ) . enumerate ( ) {
110+ let selected_in_word = filter_mask. count_ones ( ) ;
111+ if selected_in_word == 0 {
112+ continue ;
113+ }
114+
115+ let compressed_values = bit_util:: compress ( source_value_chunks. chunk ( index) , filter_mask) ;
116+ if let Some ( complete_word) = value_packer. push ( compressed_values, selected_in_word) {
117+ output_values. append_word ( complete_word, 64 ) ;
118+ }
119+ let compressed_validity =
120+ bit_util:: compress ( source_validity_chunks. chunk ( index) , filter_mask) ;
121+ if let Some ( complete_word) = validity_packer. push ( compressed_validity, selected_in_word) {
122+ output_validity. append_word ( complete_word, 64 ) ;
123+ }
124+ selected_count += selected_in_word as usize ;
125+ }
126+
127+ let filter_mask = filter_chunks. remainder_bits ( ) ;
128+ let selected_in_word = filter_mask. count_ones ( ) ;
129+ if selected_in_word != 0 {
130+ let compressed_values =
131+ bit_util:: compress ( source_value_chunks. remainder_bits ( ) , filter_mask) ;
132+ if let Some ( complete_word) = value_packer. push ( compressed_values, selected_in_word) {
133+ output_values. append_word ( complete_word, 64 ) ;
134+ }
135+ let compressed_validity =
136+ bit_util:: compress ( source_validity_chunks. remainder_bits ( ) , filter_mask) ;
137+ if let Some ( complete_word) = validity_packer. push ( compressed_validity, selected_in_word) {
138+ output_validity. append_word ( complete_word, 64 ) ;
139+ }
140+ selected_count += selected_in_word as usize ;
141+ }
142+
143+ if let Some ( ( pending_bits, pending_len) ) = value_packer. finish ( ) {
144+ output_values. append_word ( pending_bits, pending_len) ;
145+ }
146+ if let Some ( ( pending_bits, pending_len) ) = validity_packer. finish ( ) {
147+ output_validity. append_word ( pending_bits, pending_len) ;
148+ }
149+ selected_count
150+ }
151+
26152#[ derive( Debug ) ]
27153pub ( crate ) struct InProgressBooleanArray {
28154 source : Option < ArrayRef > ,
@@ -77,8 +203,57 @@ impl InProgressArray for InProgressBooleanArray {
77203 source : ArrayRef ,
78204 filter : & FilterPredicate ,
79205 ) -> Result < ( ) , ArrowError > {
80- let filtered = filter. filter ( source. as_ref ( ) ) ?;
81- self . append_array ( filtered. as_boolean ( ) ) ;
206+ if filter. filter_len ( ) > source. len ( ) {
207+ return Err ( ArrowError :: InvalidArgumentError ( format ! (
208+ "Filter predicate of length {} is larger than target array of length {}" ,
209+ filter. filter_len( ) ,
210+ source. len( )
211+ ) ) ) ;
212+ }
213+
214+ let source = source. as_boolean ( ) ;
215+ // Read values and mask in aligned words even when the source is sliced.
216+ let filter_chunks = filter. filter_mask ( ) . bit_chunks ( ) ;
217+ let source_values = source. values ( ) ;
218+ let source_value_chunks = BitChunks :: new (
219+ source_values. values ( ) ,
220+ source_values. offset ( ) ,
221+ filter. filter_len ( ) ,
222+ ) ;
223+ // Ignore present-but-all-valid buffers to avoid allocating output validity
224+ // for data with no actual nulls.
225+ let source_validity_chunks =
226+ source
227+ . nulls ( )
228+ . filter ( |nulls| nulls. null_count ( ) != 0 )
229+ . map ( |nulls| {
230+ let nulls = nulls. inner ( ) ;
231+ BitChunks :: new ( nulls. values ( ) , nulls. offset ( ) , filter. filter_len ( ) )
232+ } ) ;
233+
234+ let output_values = self
235+ . values
236+ . get_or_insert_with ( || BooleanBufferBuilder :: new ( self . batch_size ) ) ;
237+
238+ // Branch once here so non-null inputs avoid validity work in the chunk loop.
239+ let selected_count = if let Some ( source_validity_chunks) = & source_validity_chunks {
240+ copy_filter_words_with_nulls (
241+ output_values,
242+ & mut self . nulls ,
243+ & source_value_chunks,
244+ source_validity_chunks,
245+ & filter_chunks,
246+ )
247+ } else {
248+ let selected_count = copy_filter_words_without_nulls (
249+ output_values,
250+ & source_value_chunks,
251+ & filter_chunks,
252+ ) ;
253+ self . nulls . append_n_non_nulls ( selected_count) ;
254+ selected_count
255+ } ;
256+ debug_assert_eq ! ( selected_count, filter. count( ) ) ;
82257 Ok ( ( ) )
83258 }
84259
@@ -109,6 +284,7 @@ impl InProgressArray for InProgressBooleanArray {
109284mod tests {
110285 use super :: * ;
111286 use crate :: filter:: FilterBuilder ;
287+ use arrow_buffer:: NullBuffer ;
112288
113289 #[ test]
114290 fn copies_slices_and_sparse_filters ( ) {
@@ -156,4 +332,158 @@ mod tests {
156332 assert_eq ! ( second. as_boolean( ) , & source. slice( 2 , 2 ) ) ;
157333 assert_eq ! ( in_progress. finish( ) . unwrap( ) . len( ) , 0 ) ;
158334 }
335+
336+ const FILTER_LEN : usize = 269 ;
337+ const SOURCE_OFFSET : usize = 3 ;
338+
339+ #[ test]
340+ fn filtered_words_without_nulls_match_filter_kernel ( ) {
341+ let source = non_null_source ( ) ;
342+ assert ! ( source. nulls( ) . is_none( ) ) ;
343+
344+ assert_filtered_words_match_filter_kernel ( source) ;
345+ }
346+
347+ #[ test]
348+ fn filtered_words_with_all_valid_buffer_match_filter_kernel ( ) {
349+ let len = SOURCE_OFFSET + FILTER_LEN ;
350+ let values = ( 0 ..len) . map ( source_value) . collect ( ) ;
351+ let source = BooleanArray :: new ( values, Some ( NullBuffer :: new_valid ( len) ) )
352+ . slice ( SOURCE_OFFSET , FILTER_LEN ) ;
353+ assert ! ( source. nulls( ) . is_some( ) ) ;
354+ assert_eq ! ( source. null_count( ) , 0 ) ;
355+
356+ assert_filtered_words_match_filter_kernel ( source) ;
357+ }
358+
359+ #[ test]
360+ fn filtered_words_with_nulls_match_filter_kernel ( ) {
361+ let source = BooleanArray :: from_iter (
362+ ( 0 ..SOURCE_OFFSET + FILTER_LEN )
363+ . map ( |index| ( index % 11 != 0 ) . then_some ( index % 3 == 0 || index % 7 == 0 ) ) ,
364+ )
365+ . slice ( SOURCE_OFFSET , FILTER_LEN ) ;
366+ assert ! ( source. null_count( ) > 0 ) ;
367+
368+ assert_filtered_words_match_filter_kernel ( source) ;
369+ }
370+
371+ #[ test]
372+ fn filtered_words_handle_sliced_filter ( ) {
373+ const FILTER_OFFSET : usize = 5 ;
374+
375+ let filter: BooleanArray = std:: iter:: repeat_n ( false , FILTER_OFFSET )
376+ . chain ( ( 0 ..FILTER_LEN ) . map ( |index| index. is_multiple_of ( 20 ) ) )
377+ . collect ( ) ;
378+ let filter = filter. slice ( FILTER_OFFSET , FILTER_LEN ) ;
379+ assert_eq ! ( filter. values( ) . offset( ) , FILTER_OFFSET ) ;
380+
381+ assert_filter_copy_matches_kernel ( "sliced filter" , & non_null_source ( ) , & filter) ;
382+ }
383+
384+ #[ test]
385+ fn filtered_words_treat_null_filter_entries_as_false ( ) {
386+ let mut filter_values = vec ! [ false ; FILTER_LEN ] ;
387+ for index in [ 1 , 65 , 129 , 268 ] {
388+ filter_values[ index] = true ;
389+ }
390+ let mut filter_validity = vec ! [ true ; FILTER_LEN ] ;
391+ filter_validity[ 65 ] = false ;
392+ let filter = BooleanArray :: new (
393+ BooleanBuffer :: from ( filter_values) ,
394+ Some ( NullBuffer :: new ( BooleanBuffer :: from ( filter_validity) ) ) ,
395+ ) ;
396+ assert ! ( filter. value( 65 ) ) ;
397+ assert ! ( filter. is_null( 65 ) ) ;
398+
399+ assert_filter_copy_matches_kernel ( "null filter entry" , & non_null_source ( ) , & filter) ;
400+ }
401+
402+ fn assert_filtered_words_match_filter_kernel ( source : BooleanArray ) {
403+ let selections = [
404+ ( "none" , vec ! [ ] ) ,
405+ ( "all" , ( 0 ..FILTER_LEN ) . collect ( ) ) ,
406+ (
407+ "word boundaries" ,
408+ vec ! [ 0 , 1 , 7 , 63 , 64 , 65 , 127 , 128 , 191 , 192 , 255 , 256 , 268 ] ,
409+ ) ,
410+ (
411+ "carry across source words" ,
412+ // The first two chunks leave 24 carried bits; the third completes a word.
413+ ( 0 ..40 ) . chain ( 64 ..112 ) . chain ( 128 ..168 ) . collect ( ) ,
414+ ) ,
415+ ( "alternating" , ( 0 ..FILTER_LEN ) . step_by ( 2 ) . collect ( ) ) ,
416+ ] ;
417+
418+ for ( case, selected_indices) in & selections {
419+ let filter: BooleanArray = ( 0 ..FILTER_LEN )
420+ . map ( |index| selected_indices. contains ( & index) )
421+ . collect ( ) ;
422+ assert_filter_copy_matches_kernel ( case, & source, & filter) ;
423+ }
424+ }
425+
426+ fn assert_filter_copy_matches_kernel ( case : & str , source : & BooleanArray , filter : & BooleanArray ) {
427+ const PREFIX_LEN : usize = 5 ;
428+ let expect_lazy_validity = source. null_count ( ) == 0 ;
429+
430+ for optimize in [ false , true ] {
431+ let builder = FilterBuilder :: new ( filter) ;
432+ let predicate = if optimize {
433+ builder. optimize ( ) . build ( )
434+ } else {
435+ builder. build ( )
436+ } ;
437+ let filtered = predicate. filter ( source) . unwrap ( ) ;
438+ let prefix = source. slice ( 0 , PREFIX_LEN ) ;
439+ let expected = crate :: concat:: concat ( & [ & prefix, filtered. as_ref ( ) ] ) . unwrap ( ) ;
440+
441+ let source = Arc :: new ( source. clone ( ) ) as ArrayRef ;
442+ let mut in_progress = InProgressBooleanArray :: new ( FILTER_LEN ) ;
443+ in_progress. set_source ( Some ( Arc :: clone ( & source) ) ) ;
444+ in_progress. copy_rows ( 0 , PREFIX_LEN ) . unwrap ( ) ;
445+ in_progress. set_source ( None ) ;
446+ in_progress
447+ . copy_rows_by_filter_from ( source, & predicate)
448+ . unwrap ( ) ;
449+
450+ let output = in_progress. finish ( ) . unwrap ( ) ;
451+ assert_eq ! (
452+ output. as_boolean( ) ,
453+ expected. as_boolean( ) ,
454+ "{case}, optimize={optimize}"
455+ ) ;
456+ if expect_lazy_validity {
457+ assert ! (
458+ output. nulls( ) . is_none( ) ,
459+ "{case}, optimize={optimize} should preserve lazy validity"
460+ ) ;
461+ }
462+ }
463+ }
464+
465+ fn non_null_source ( ) -> BooleanArray {
466+ let source: BooleanArray = ( 0 ..SOURCE_OFFSET + FILTER_LEN ) . map ( source_value) . collect ( ) ;
467+ source. slice ( SOURCE_OFFSET , FILTER_LEN )
468+ }
469+
470+ fn source_value ( index : usize ) -> bool {
471+ index. is_multiple_of ( 3 ) || index. is_multiple_of ( 7 )
472+ }
473+
474+ #[ test]
475+ fn filter_longer_than_source_returns_error ( ) {
476+ let source = Arc :: new ( BooleanArray :: from ( vec ! [ true , false ] ) ) as ArrayRef ;
477+ let filter_mask = BooleanArray :: from ( vec ! [ true , false , true ] ) ;
478+ let predicate = FilterBuilder :: new ( & filter_mask) . build ( ) ;
479+ let mut in_progress = InProgressBooleanArray :: new ( 8 ) ;
480+
481+ let error = in_progress
482+ . copy_rows_by_filter_from ( source, & predicate)
483+ . unwrap_err ( ) ;
484+ assert_eq ! (
485+ error. to_string( ) ,
486+ "Invalid argument error: Filter predicate of length 3 is larger than target array of length 2"
487+ ) ;
488+ }
159489}
0 commit comments