Skip to content

Commit d33aae6

Browse files
committed
fix
1 parent 6176a6d commit d33aae6

2 files changed

Lines changed: 126 additions & 16 deletions

File tree

datafusion/datasource-parquet/src/bloom_filter.rs

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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

4546
impl 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
}

datafusion/datasource-parquet/src/opener/mod.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,7 +1170,7 @@ impl RowGroupsPrunedParquetOpen {
11701170
mem::replace(&mut prepared.async_file_reader, replacement_reader),
11711171
reader_metadata,
11721172
);
1173-
let parquet_columns: Vec<(String, usize, Type)> = predicate
1173+
let parquet_columns: Vec<(String, usize, Type, i32)> = predicate
11741174
.literal_columns()
11751175
.into_iter()
11761176
.filter_map(|column_name| {
@@ -1184,14 +1184,17 @@ impl RowGroupsPrunedParquetOpen {
11841184
column_name,
11851185
column_idx,
11861186
parquet_schema.column(column_idx).physical_type(),
1187+
parquet_schema.column(column_idx).type_length(),
11871188
))
11881189
})
11891190
.collect();
11901191

11911192
for idx in self.row_groups.row_group_indexes() {
11921193
let mut row_group_filters =
11931194
BloomFilterStatistics::with_capacity(parquet_columns.len());
1194-
for (column_name, column_idx, physical_type) in &parquet_columns {
1195+
for (column_name, column_idx, physical_type, type_length) in
1196+
&parquet_columns
1197+
{
11951198
let bf: Sbbf = match builder
11961199
.get_row_group_column_bloom_filter(idx, *column_idx)
11971200
.await
@@ -1204,7 +1207,12 @@ impl RowGroupsPrunedParquetOpen {
12041207
continue;
12051208
}
12061209
};
1207-
row_group_filters.insert(column_name, bf, *physical_type);
1210+
row_group_filters.insert(
1211+
column_name,
1212+
bf,
1213+
*physical_type,
1214+
*type_length,
1215+
);
12081216
}
12091217
row_group_bloom_filters[idx] = row_group_filters;
12101218
}

0 commit comments

Comments
 (0)