Skip to content

Commit b316393

Browse files
authored
Merge pull request JanKaul#370 from splitgraph/fix-date32-decimal-parsing
fix: add handling for Date32 and Decimal stat parsing
2 parents c5f7907 + d5fc146 commit b316393

2 files changed

Lines changed: 56 additions & 2 deletions

File tree

datafusion_iceberg/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ lru = { workspace = true }
2323
object_store = { workspace = true }
2424
pin-project-lite = "0.2.17"
2525
regex = { workspace = true }
26+
rust_decimal = { workspace = true }
2627
serde_json = { workspace = true }
2728
thiserror = { workspace = true }
2829
tokio = { version = "1.50", features = ["rt-multi-thread"] }
@@ -42,7 +43,6 @@ parquet = { workspace = true }
4243
pyo3 = { version = "0.28", features = ["auto-initialize"] }
4344
reqwest = "0.12"
4445
rstest = "0.26"
45-
rust_decimal = { workspace = true }
4646
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "any", "sqlite", "postgres", "mysql"], default-features = false }
4747
tempfile = "3.27.0"
4848
testcontainers = "0.26"

datafusion_iceberg/src/pruning_statistics.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use iceberg_rust::{
4444
},
4545
table::ManifestPath,
4646
};
47+
use rust_decimal::Decimal;
4748

4849
pub(crate) struct PruneManifests<'table, 'manifests> {
4950
partition_fields: &'table [BoundPartitionField<'table>],
@@ -254,6 +255,9 @@ fn any_iter_to_array(
254255
DataType::Float64 => ScalarValue::iter_to_array(iter.map(|opt| {
255256
ScalarValue::Float64(opt.and_then(|value| Some(*value.downcast::<f64>().ok()?)))
256257
})),
258+
DataType::Date32 => ScalarValue::iter_to_array(iter.map(|opt| {
259+
ScalarValue::Date32(opt.and_then(|value| Some(*value.downcast::<i32>().ok()?)))
260+
})),
257261
DataType::Date64 => ScalarValue::iter_to_array(iter.map(|opt| {
258262
ScalarValue::Date64(opt.and_then(|value| Some(*value.downcast::<i64>().ok()?)))
259263
})),
@@ -280,6 +284,20 @@ fn any_iter_to_array(
280284
DataType::Binary => ScalarValue::iter_to_array(iter.map(|opt| {
281285
ScalarValue::Binary(opt.and_then(|value| Some(*value.downcast::<Vec<u8>>().ok()?)))
282286
})),
287+
// Only prune when the stored scale matches the column's scale.
288+
DataType::Decimal128(precision, scale) => {
289+
let (precision, scale) = (*precision, *scale);
290+
ScalarValue::iter_to_array(iter.map(move |opt| {
291+
ScalarValue::Decimal128(
292+
opt.and_then(|value| {
293+
let d = *value.downcast::<Decimal>().ok()?;
294+
(d.scale() == scale as u32).then(|| d.mantissa())
295+
}),
296+
precision,
297+
scale,
298+
)
299+
}))
300+
}
283301
_ => Err(DataFusionError::Internal(
284302
"Arrow datatype not supported for pruning.".to_string(),
285303
)),
@@ -487,9 +505,12 @@ fn value_to_scalarvalue(value: Value) -> Result<ScalarValue, Error> {
487505
#[cfg(test)]
488506
mod tests {
489507
use super::*;
490-
use datafusion::arrow::array::{Array, TimestampMicrosecondArray};
508+
use datafusion::arrow::array::{
509+
Array, Date32Array, Decimal128Array, TimestampMicrosecondArray,
510+
};
491511
use datafusion::arrow::datatypes::Field;
492512
use datafusion::common::config::ConfigOptions;
513+
use rust_decimal::Decimal;
493514
use std::sync::Arc;
494515

495516
/// Helper: invoke `DateTransform` directly with a transform name and scalar value.
@@ -718,6 +739,39 @@ mod tests {
718739
assert_eq!(result, input);
719740
}
720741

742+
#[test]
743+
fn any_iter_to_array_date32() {
744+
let iter = vec![Some(Value::Date(19797).into_any()), None].into_iter();
745+
let array = any_iter_to_array(iter, &DataType::Date32).unwrap();
746+
let dates = array.as_any().downcast_ref::<Date32Array>().unwrap();
747+
assert_eq!(dates.value(0), 19797);
748+
assert!(dates.is_null(1));
749+
}
750+
751+
#[test]
752+
fn any_iter_to_array_decimal128() {
753+
let iter = vec![
754+
Some(Value::Decimal(Decimal::new(12345, 2)).into_any()),
755+
None,
756+
]
757+
.into_iter();
758+
let array = any_iter_to_array(iter, &DataType::Decimal128(10, 2)).unwrap();
759+
let dec = array.as_any().downcast_ref::<Decimal128Array>().unwrap();
760+
assert_eq!(dec.value(0), 12345);
761+
assert!(dec.is_null(1));
762+
assert_eq!(dec.precision(), 10);
763+
assert_eq!(dec.scale(), 2);
764+
}
765+
766+
#[test]
767+
fn any_iter_to_array_decimal128_scale_mismatch_is_null() {
768+
// Stored scale (2) != column scale (4): emit null rather than misread the mantissa.
769+
let iter = std::iter::once(Some(Value::Decimal(Decimal::new(12345, 2)).into_any()));
770+
let array = any_iter_to_array(iter, &DataType::Decimal128(10, 4)).unwrap();
771+
let dec = array.as_any().downcast_ref::<Decimal128Array>().unwrap();
772+
assert!(dec.is_null(0));
773+
}
774+
721775
#[test]
722776
fn any_iter_to_array_preserves_timezone() {
723777
for val in [Value::Timestamp(TS_MICROS), Value::TimestampTZ(TS_MICROS)] {

0 commit comments

Comments
 (0)