@@ -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,162 @@ 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 len = SOURCE_OFFSET + FILTER_LEN ;
342+ let values = ( 0 ..len) . map ( source_value) . collect ( ) ;
343+ let all_valid = BooleanArray :: new ( values, Some ( NullBuffer :: new_valid ( len) ) )
344+ . slice ( SOURCE_OFFSET , FILTER_LEN ) ;
345+ let without_validity = non_null_source ( ) ;
346+ assert ! ( without_validity. nulls( ) . is_none( ) ) ;
347+ assert ! ( all_valid. nulls( ) . is_some( ) ) ;
348+
349+ for ( case, source) in [
350+ ( "without validity" , without_validity) ,
351+ ( "all-valid validity" , all_valid) ,
352+ ] {
353+ assert_eq ! ( source. null_count( ) , 0 ) ;
354+ assert_filtered_words_match_filter_kernel ( case, source) ;
355+ }
356+ }
357+
358+ #[ test]
359+ fn filtered_words_with_nulls_match_filter_kernel ( ) {
360+ let source = BooleanArray :: from_iter (
361+ ( 0 ..SOURCE_OFFSET + FILTER_LEN )
362+ . map ( |index| ( index % 11 != 0 ) . then_some ( index % 3 == 0 || index % 7 == 0 ) ) ,
363+ )
364+ . slice ( SOURCE_OFFSET , FILTER_LEN ) ;
365+ assert ! ( source. null_count( ) > 0 ) ;
366+
367+ assert_filtered_words_match_filter_kernel ( "with nulls" , source) ;
368+ }
369+
370+ fn assert_filtered_words_match_filter_kernel ( source_case : & str , source : BooleanArray ) {
371+ for ( filter_case, filter) in filter_cases ( ) {
372+ assert_filter_copy_matches_kernel ( source_case, filter_case, & source, & filter) ;
373+ }
374+ }
375+
376+ fn filter_cases ( ) -> Vec < ( & ' static str , BooleanArray ) > {
377+ let selections = [
378+ ( "none" , vec ! [ ] ) ,
379+ ( "all" , ( 0 ..FILTER_LEN ) . collect ( ) ) ,
380+ (
381+ "word boundaries" ,
382+ vec ! [ 0 , 1 , 7 , 63 , 64 , 65 , 127 , 128 , 191 , 192 , 255 , 256 , 268 ] ,
383+ ) ,
384+ (
385+ "carry across source words" ,
386+ // The first two chunks leave 24 carried bits; the third completes a word.
387+ ( 0 ..40 ) . chain ( 64 ..112 ) . chain ( 128 ..168 ) . collect ( ) ,
388+ ) ,
389+ ( "alternating" , ( 0 ..FILTER_LEN ) . step_by ( 2 ) . collect ( ) ) ,
390+ ] ;
391+ let mut filters: Vec < ( & str , BooleanArray ) > = selections
392+ . into_iter ( )
393+ . map ( |( case, selected_indices) | {
394+ let filter = ( 0 ..FILTER_LEN )
395+ . map ( |index| selected_indices. contains ( & index) )
396+ . collect ( ) ;
397+ ( case, filter)
398+ } )
399+ . collect ( ) ;
400+
401+ const FILTER_OFFSET : usize = 5 ;
402+ let sliced_filter: BooleanArray = std:: iter:: repeat_n ( false , FILTER_OFFSET )
403+ . chain ( ( 0 ..FILTER_LEN ) . map ( |index| index. is_multiple_of ( 20 ) ) )
404+ . collect ( ) ;
405+ let sliced_filter = sliced_filter. slice ( FILTER_OFFSET , FILTER_LEN ) ;
406+ assert_eq ! ( sliced_filter. values( ) . offset( ) , FILTER_OFFSET ) ;
407+ filters. push ( ( "sliced filter" , sliced_filter) ) ;
408+
409+ let mut filter_values = vec ! [ false ; FILTER_LEN ] ;
410+ for index in [ 1 , 65 , 129 , 268 ] {
411+ filter_values[ index] = true ;
412+ }
413+ let mut filter_validity = vec ! [ true ; FILTER_LEN ] ;
414+ filter_validity[ 65 ] = false ;
415+ let filter_with_null = BooleanArray :: new (
416+ BooleanBuffer :: from ( filter_values) ,
417+ Some ( NullBuffer :: new ( BooleanBuffer :: from ( filter_validity) ) ) ,
418+ ) ;
419+ assert ! ( filter_with_null. value( 65 ) ) ;
420+ assert ! ( filter_with_null. is_null( 65 ) ) ;
421+ filters. push ( ( "null filter entry" , filter_with_null) ) ;
422+ filters
423+ }
424+
425+ fn assert_filter_copy_matches_kernel (
426+ source_case : & str ,
427+ filter_case : & str ,
428+ source : & BooleanArray ,
429+ filter : & BooleanArray ,
430+ ) {
431+ const PREFIX_LEN : usize = 5 ;
432+ let expect_lazy_validity = source. null_count ( ) == 0 ;
433+
434+ for optimize in [ false , true ] {
435+ let builder = FilterBuilder :: new ( filter) ;
436+ let predicate = if optimize {
437+ builder. optimize ( ) . build ( )
438+ } else {
439+ builder. build ( )
440+ } ;
441+ let filtered = predicate. filter ( source) . unwrap ( ) ;
442+ let prefix = source. slice ( 0 , PREFIX_LEN ) ;
443+ let expected = crate :: concat:: concat ( & [ & prefix, filtered. as_ref ( ) ] ) . unwrap ( ) ;
444+
445+ let source = Arc :: new ( source. clone ( ) ) as ArrayRef ;
446+ let mut in_progress = InProgressBooleanArray :: new ( FILTER_LEN ) ;
447+ in_progress. set_source ( Some ( Arc :: clone ( & source) ) ) ;
448+ in_progress. copy_rows ( 0 , PREFIX_LEN ) . unwrap ( ) ;
449+ in_progress. set_source ( None ) ;
450+ in_progress
451+ . copy_rows_by_filter_from ( source, & predicate)
452+ . unwrap ( ) ;
453+
454+ let output = in_progress. finish ( ) . unwrap ( ) ;
455+ assert_eq ! (
456+ output. as_boolean( ) ,
457+ expected. as_boolean( ) ,
458+ "{source_case}, {filter_case}, optimize={optimize}"
459+ ) ;
460+ if expect_lazy_validity {
461+ assert ! (
462+ output. nulls( ) . is_none( ) ,
463+ "{source_case}, {filter_case}, optimize={optimize} should preserve lazy validity"
464+ ) ;
465+ }
466+ }
467+ }
468+
469+ fn non_null_source ( ) -> BooleanArray {
470+ let source: BooleanArray = ( 0 ..SOURCE_OFFSET + FILTER_LEN ) . map ( source_value) . collect ( ) ;
471+ source. slice ( SOURCE_OFFSET , FILTER_LEN )
472+ }
473+
474+ fn source_value ( index : usize ) -> bool {
475+ index. is_multiple_of ( 3 ) || index. is_multiple_of ( 7 )
476+ }
477+
478+ #[ test]
479+ fn filter_longer_than_source_returns_error ( ) {
480+ let source = Arc :: new ( BooleanArray :: from ( vec ! [ true , false ] ) ) as ArrayRef ;
481+ let filter_mask = BooleanArray :: from ( vec ! [ true , false , true ] ) ;
482+ let predicate = FilterBuilder :: new ( & filter_mask) . build ( ) ;
483+ let mut in_progress = InProgressBooleanArray :: new ( 8 ) ;
484+
485+ let error = in_progress
486+ . copy_rows_by_filter_from ( source, & predicate)
487+ . unwrap_err ( ) ;
488+ assert_eq ! (
489+ error. to_string( ) ,
490+ "Invalid argument error: Filter predicate of length 3 is larger than target array of length 2"
491+ ) ;
492+ }
159493}
0 commit comments