Skip to content

Commit e241228

Browse files
committed
feat: honor reader timezone in partition values
1 parent e531040 commit e241228

19 files changed

Lines changed: 1058 additions & 198 deletions

File tree

datafusion-executor/src/expression.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ fn map_to_struct_primitive(field: &StructField) -> DeltaResult<&PrimitiveType> {
473473
#[derive(Debug, PartialEq, Eq)]
474474
struct KernelMapToStructUdf {
475475
output_schema: KernelSchemaRef,
476+
output_type: KernelDataType,
476477
options: MapToStructOptions,
477478
return_type: ArrowDataType,
478479
signature: Signature,
@@ -491,9 +492,11 @@ impl KernelMapToStructUdf {
491492
.as_ref()
492493
.try_into_arrow()
493494
.map_err(Error::generic_err)?;
495+
let output_type = KernelDataType::from(output_schema.as_ref().clone());
494496
Ok(Self {
495497
return_type: ArrowDataType::Struct(arrow_schema.fields().clone()),
496498
output_schema,
499+
output_type,
497500
options,
498501
signature: Signature::any(1, Volatility::Immutable),
499502
})
@@ -521,9 +524,8 @@ impl ScalarUDFImpl for KernelMapToStructUdf {
521524
KernelExpression::column(["map"]),
522525
self.options.clone(),
523526
);
524-
let output_type = KernelDataType::from(self.output_schema.as_ref().clone());
525527
let result =
526-
kernel_expression::evaluate_expression(&expression, &batch, Some(&output_type))
528+
kernel_expression::evaluate_expression(&expression, &batch, Some(&self.output_type))
527529
.map_err(|e| DataFusionError::External(Box::new(e)))?;
528530
Ok(ColumnarValue::Array(result))
529531
}

docs/user-guide/src/reading/scan_metadata.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,9 @@ If the scan has no predicate, this returns `None`.
249249
## Typed partition values
250250

251251
Kernel reads each file's partition values from the Delta log and exposes them on its
252-
`ScanFile` as a raw string map. Kernel's `transform_to_logical` could materialize them as
253-
typed columns. If your connector assembles output rows itself instead of using that transform,
254-
it parses the string map per file.
252+
`ScanFile` as a raw string map. `transform_to_logical` materializes them as typed columns. A
253+
connector that assembles output rows itself instead of using that transform would otherwise need
254+
to parse the string map per file.
255255

256256
To have Kernel hand you the typed values directly, opt in with `with_partition_values`:
257257

@@ -270,7 +270,10 @@ To have Kernel hand you the typed values directly, opt in with `with_partition_v
270270
# let snapshot = Snapshot::builder_for(url).build(&engine)?;
271271
let scan = snapshot
272272
.scan_builder()
273-
.with_partition_values(PartitionValuesOptions::with_struct())
273+
.with_partition_values(
274+
PartitionValuesOptions::with_struct()
275+
.with_timestamp_timezone("America/Los_Angeles"),
276+
)
274277
.build()?;
275278
# Ok(())
276279
# }
@@ -281,9 +284,21 @@ nullable field per partition column (by physical name). You read it as a typed c
281284
of parsing the string map per file. The raw string map is still present, so this option only
282285
adds the typed column.
283286

287+
By default, offset-less `TIMESTAMP` partition strings are interpreted in UTC. Use
288+
`with_timestamp_timezone` with a recognized IANA timezone or a normalized `+HH:MM`, `-HH:MM`,
289+
`+HH:MM:SS`, or `-HH:MM:SS` fixed offset when the reader uses another timezone. An explicit offset
290+
or embedded time zone in a partition value takes precedence. This setting affects typed
291+
`scan_metadata` output, partition predicate evaluation after log replay, and the partition-column
292+
row transforms used by `Scan::execute`. Checkpoint footer pruning continues to use the
293+
checkpoint's native parsed partition values. Incremental scans expose the raw partition-value map.
294+
`TIMESTAMP_NTZ` remains timezone-independent. For daylight-saving transitions, ambiguous local
295+
times use the earlier instant, and nonexistent local times use the offset from before the
296+
transition.
297+
284298
> [!TIP]
285-
> When the checkpoint already stores typed partition values, Kernel reads that column directly
286-
> and skips parsing entirely.
299+
> Kernel reparses surviving commit and checkpoint rows from the raw map for typed output and final
300+
> predicate evaluation. Checkpoint footer pruning happens first and continues to use the
301+
> checkpoint's native parsed partition values.
287302
288303
## Cancelling a scan
289304

kernel/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pre-release-hook = [
4242
delta_kernel_derive = { path = "../derive-macros", version = "0.28.0" }
4343
bytes = "1.10"
4444
chrono = "0.4.41"
45-
chrono-tz = { version = "0.10.4", optional = true }
45+
chrono-tz = "0.10.4"
4646
crc = "3.2.2"
4747
indexmap = "2.10.0"
4848
itertools = "0.14"
@@ -110,7 +110,7 @@ need-arrow = [] # need-arrow is a marker that the feature needs arrow dep
110110
arrow-58 = ["dep:arrow_58", "dep:parquet_58", "dep:object_store_13"]
111111
arrow-59 = ["dep:arrow_59", "dep:parquet_59", "dep:object_store_13"]
112112
arrow-conversion = ["need-arrow"]
113-
arrow-expression = ["need-arrow", "dep:chrono-tz"]
113+
arrow-expression = ["need-arrow"]
114114

115115
# Schema diffing functionality (experimental)
116116
schema-diff = []

0 commit comments

Comments
 (0)