@@ -39,7 +39,8 @@ pub(crate) struct BloomFilterStatistics {
3939 /// Value:
4040 /// * [`Sbbf`] (Bloom filter),
4141 /// * Parquet physical [`Type`] needed to evaluate literals against the filter
42- column_sbbf : HashMap < String , ( Sbbf , Type ) > ,
42+ /// * Type length from the Parquet column descriptor
43+ column_sbbf : HashMap < String , ( Sbbf , Type , i32 ) > ,
4344}
4445
4546impl BloomFilterStatistics {
@@ -56,15 +57,27 @@ impl BloomFilterStatistics {
5657 }
5758
5859 /// Add a Bloom filter and type for the specified column
59- pub ( crate ) fn insert ( & mut self , column : impl Into < String > , sbbf : Sbbf , ty : Type ) {
60- self . column_sbbf . insert ( column. into ( ) , ( sbbf, ty) ) ;
60+ pub ( crate ) fn insert (
61+ & mut self ,
62+ column : impl Into < String > ,
63+ sbbf : Sbbf ,
64+ ty : Type ,
65+ type_length : i32 ,
66+ ) {
67+ self . column_sbbf
68+ . insert ( column. into ( ) , ( sbbf, ty, type_length) ) ;
6169 }
6270
6371 /// Helper function for checking if [`Sbbf`] filter contains [`ScalarValue`].
6472 ///
6573 /// In case the type of scalar is not supported, returns `true`, assuming that the
6674 /// value may be present.
67- fn check_scalar ( sbbf : & Sbbf , value : & ScalarValue , parquet_type : & Type ) -> bool {
75+ fn check_scalar (
76+ sbbf : & Sbbf ,
77+ value : & ScalarValue ,
78+ parquet_type : & Type ,
79+ type_length : i32 ,
80+ ) -> bool {
6881 match value {
6982 ScalarValue :: Utf8 ( Some ( v) )
7083 | ScalarValue :: Utf8View ( Some ( v) )
@@ -113,8 +126,14 @@ impl BloomFilterStatistics {
113126 sbbf. check ( & decimal)
114127 }
115128 Type :: FIXED_LEN_BYTE_ARRAY => {
116- // keep with from_bytes_to_i128
117- let b = v. to_be_bytes ( ) . to_vec ( ) ;
129+ let Ok ( type_length) = usize:: try_from ( type_length) else {
130+ return true ;
131+ } ;
132+ if type_length == 0 || type_length > 16 {
133+ return true ;
134+ }
135+ let b = v. to_be_bytes ( ) ;
136+ let b = b[ ( b. len ( ) - type_length) ..] . to_vec ( ) ;
118137 // Use Decimal constructor after https://github.com/apache/arrow-rs/issues/5325
119138 let decimal = Decimal :: Bytes {
120139 value : b. into ( ) ,
@@ -125,9 +144,12 @@ impl BloomFilterStatistics {
125144 }
126145 _ => true ,
127146 } ,
128- ScalarValue :: Dictionary ( _, inner) => {
129- BloomFilterStatistics :: check_scalar ( sbbf, inner, parquet_type)
130- }
147+ ScalarValue :: Dictionary ( _, inner) => BloomFilterStatistics :: check_scalar (
148+ sbbf,
149+ inner,
150+ parquet_type,
151+ type_length,
152+ ) ,
131153 _ => true ,
132154 }
133155 }
@@ -164,7 +186,8 @@ impl PruningStatistics for BloomFilterStatistics {
164186 column : & Column ,
165187 values : & HashSet < ScalarValue > ,
166188 ) -> Option < BooleanArray > {
167- let ( sbbf, parquet_type) = self . column_sbbf . get ( column. name . as_str ( ) ) ?;
189+ let ( sbbf, parquet_type, type_length) =
190+ self . column_sbbf . get ( column. name . as_str ( ) ) ?;
168191
169192 // Bloom filters are probabilistic data structures that can return false
170193 // positives (i.e. it might return true even if the value is not
@@ -173,7 +196,14 @@ impl PruningStatistics for BloomFilterStatistics {
173196
174197 let known_not_present = values
175198 . iter ( )
176- . map ( |value| BloomFilterStatistics :: check_scalar ( sbbf, value, parquet_type) )
199+ . map ( |value| {
200+ BloomFilterStatistics :: check_scalar (
201+ sbbf,
202+ value,
203+ parquet_type,
204+ * type_length,
205+ )
206+ } )
177207 // The row group doesn't contain any of the values if
178208 // all the checks are false
179209 . all ( |v| !v) ;
@@ -201,15 +231,19 @@ mod tests {
201231 use crate :: test_util:: ExpectedPruning ;
202232 use crate :: { ParquetAccessPlan , ParquetFileMetrics , RowGroupAccessPlanFilter } ;
203233
234+ use arrow:: array:: Decimal128Array ;
204235 use arrow:: datatypes:: { DataType , Field , Schema } ;
236+ use bytes:: { BufMut , BytesMut } ;
205237 use datafusion_common:: Result ;
206238 use datafusion_expr:: { Expr , col, lit} ;
207239 use datafusion_physical_expr:: planner:: logical2physical;
208240 use datafusion_physical_plan:: metrics:: ExecutionPlanMetricsSet ;
209241 use datafusion_pruning:: PruningPredicate ;
210242 use object_store:: ObjectStoreExt ;
243+ use parquet:: arrow:: ArrowWriter ;
211244 use parquet:: arrow:: ParquetRecordBatchStreamBuilder ;
212245 use parquet:: arrow:: async_reader:: ParquetObjectReader ;
246+ use parquet:: file:: properties:: { EnabledStatistics , WriterProperties } ;
213247
214248 #[ tokio:: test]
215249 async fn test_row_group_bloom_filter_pruning_predicate_simple_expr ( ) {
@@ -375,6 +409,40 @@ mod tests {
375409 . await
376410 }
377411
412+ #[ tokio:: test]
413+ async fn test_row_group_bloom_filter_pruning_predicate_decimal128 ( ) {
414+ for precision in [ 19 , 20 , 21 , 28 , 38 ] {
415+ let scale = 2 ;
416+ let data = parquet_decimal128_with_bloom_filter ( precision, scale) ;
417+ let schema = Schema :: new ( vec ! [ Field :: new(
418+ "decimal_col" ,
419+ DataType :: Decimal128 ( precision, scale) ,
420+ true ,
421+ ) ] ) ;
422+ let expr = col ( "decimal_col" ) . eq ( Expr :: Literal (
423+ ScalarValue :: Decimal128 ( Some ( 500 ) , precision, scale) ,
424+ None ,
425+ ) ) ;
426+ let expr = logical2physical ( & expr, & schema) ;
427+ let pruning_predicate =
428+ PruningPredicate :: try_new ( expr, Arc :: new ( schema) ) . unwrap ( ) ;
429+
430+ let pruned_row_groups = test_row_group_bloom_filter_pruning_predicate (
431+ & format ! ( "decimal128-{precision}.parquet" ) ,
432+ data,
433+ & pruning_predicate,
434+ )
435+ . await
436+ . unwrap ( ) ;
437+
438+ assert_eq ! (
439+ pruned_row_groups. access_plan( ) . row_group_indexes( ) ,
440+ vec![ 2 ] ,
441+ "precision {precision}"
442+ ) ;
443+ }
444+ }
445+
378446 struct BloomFilterTest {
379447 file_name : String ,
380448 schema : Schema ,
@@ -467,6 +535,33 @@ mod tests {
467535 }
468536 }
469537
538+ fn parquet_decimal128_with_bloom_filter ( precision : u8 , scale : i8 ) -> bytes:: Bytes {
539+ let schema = Arc :: new ( Schema :: new ( vec ! [ Field :: new(
540+ "decimal_col" ,
541+ DataType :: Decimal128 ( precision, scale) ,
542+ true ,
543+ ) ] ) ) ;
544+ let array = Arc :: new (
545+ Decimal128Array :: from ( vec ! [ 100 , 200 , 300 , 400 , 500 , 600 ] )
546+ . with_precision_and_scale ( precision, scale)
547+ . unwrap ( ) ,
548+ ) as ArrayRef ;
549+ let batch =
550+ arrow:: array:: RecordBatch :: try_new ( schema. clone ( ) , vec ! [ array] ) . unwrap ( ) ;
551+ let props = WriterProperties :: builder ( )
552+ . set_max_row_group_row_count ( Some ( 2 ) )
553+ . set_bloom_filter_enabled ( true )
554+ . set_statistics_enabled ( EnabledStatistics :: None )
555+ . build ( ) ;
556+ let mut out = BytesMut :: new ( ) . writer ( ) ;
557+ {
558+ let mut writer = ArrowWriter :: try_new ( & mut out, schema, Some ( props) ) . unwrap ( ) ;
559+ writer. write ( & batch) . unwrap ( ) ;
560+ writer. finish ( ) . unwrap ( ) ;
561+ }
562+ out. into_inner ( ) . freeze ( )
563+ }
564+
470565 /// Evaluates the pruning predicate on the specified row groups and returns the row groups that are left
471566 async fn test_row_group_bloom_filter_pruning_predicate (
472567 file_name : & str ,
@@ -520,6 +615,7 @@ mod tests {
520615 column_name. to_string ( ) ,
521616 column_idx,
522617 builder. parquet_schema ( ) . column ( column_idx) . physical_type ( ) ,
618+ builder. parquet_schema ( ) . column ( column_idx) . type_length ( ) ,
523619 ) )
524620 } )
525621 . collect :: < Vec < _ > > ( ) ;
@@ -532,7 +628,8 @@ mod tests {
532628 for idx in pruned_row_groups. row_group_indexes ( ) {
533629 let mut bloom_filters =
534630 BloomFilterStatistics :: with_capacity ( parquet_columns. len ( ) ) ;
535- for ( column_name, column_idx, physical_type) in & parquet_columns {
631+ for ( column_name, column_idx, physical_type, type_length) in & parquet_columns
632+ {
536633 let bf = match builder
537634 . get_row_group_column_bloom_filter ( idx, * column_idx)
538635 . await
@@ -545,7 +642,12 @@ mod tests {
545642 continue ;
546643 }
547644 } ;
548- bloom_filters. insert ( column_name. clone ( ) , bf, * physical_type) ;
645+ bloom_filters. insert (
646+ column_name. clone ( ) ,
647+ bf,
648+ * physical_type,
649+ * type_length,
650+ ) ;
549651 }
550652 row_group_bloom_filters[ idx] = bloom_filters;
551653 }
0 commit comments