mbutrovich commented on code in PR #3424:
URL: https://github.com/apache/iceberg-rust/pull/3424#discussion_r4395046068


##########
crates/iceberg/src/scan/task.rs:
##########
@@ -760,6 +826,106 @@ impl From<FileScanTaskDeleteFile> for 
Result<FileScanTaskDeleteFile> {
     }
 }
 
+/// Column statistics of a [`FileScanTask`]'s data file, keyed by field id and 
limited to the
+/// columns requested for the scan.
+///
+/// A requested column missing from a map has unknown statistics of that kind, 
for example when
+/// the table's metrics mode records no bounds. Bounds are inclusive even when 
truncated, and are
+/// typed with the schema the manifest was written with, so after type 
promotion an `int` bound

Review Comment:
   > This promises that after type promotion an `int` bound can describe a 
`long` column, which is a useful thing for callers to know, but no test pins it.
   
   Should planning convert the retained bounds to the scan schema's type 
instead, so that one scan returns one type per field id?
   
   As I read it, a bound's type depends on which manifest holds the entry, not 
only on when the data file was written. A file listed in a manifest written 
before `y` was promoted gets `Datum::int`. A file listed in a manifest written 
after the promotion gets `Datum::long`. Java's merge append writes old entries 
into such manifests: 
[`ManifestMergeManager`](https://github.com/apache/iceberg/blob/6275107e38cc87a7d416ef231019ceb51556f196/core/src/main/java/org/apache/iceberg/ManifestMergeManager.java#L195-L214)
 copies them with `writer.existing(entry)` into a writer whose spec is [rebound 
to the current 
schema](https://github.com/apache/iceberg/blob/6275107e38cc87a7d416ef231019ceb51556f196/core/src/main/java/org/apache/iceberg/TableMetadata.java#L1105-L1107),
 and the bound bytes stay 4 bytes. The type changes on read because 
[`Datum::try_from_bytes`](https://github.com/apache/iceberg-rust/blob/de0a6ac8471da43f63382d66fac27d7e56fd273e/crates/iceberg/src/spec/values/datum.rs#L401-L408)
 wi
 dens 4-byte values when the manifest schema says `long`. So one scan can hand 
out both types for the same column. [`Datum`'s 
`PartialOrd`](https://github.com/apache/iceberg-rust/blob/de0a6ac8471da43f63382d66fac27d7e56fd273e/crates/iceberg/src/spec/values/datum.rs#L178-L294)
 has no cross-type arms, so comparing them returns `None`. Ordering files by 
their bounds is the comparison a sorted scan makes.
   
   I checked this on fe1bf90 with a scratch test that writes two manifests, one 
with `y: int` and one with `y: long`, and scans with 
`with_column_stats(["y"])`. The lower bounds come back as `Datum { type: Int, 
literal: Int(5) }` and `Datum { type: Long, literal: Long(6) }`, and 
`partial_cmp` between them returns `None`.
   
   The spec's [promotion 
table](https://github.com/apache/iceberg/blob/6275107e38cc87a7d416ef231019ceb51556f196/format/spec.md?plain=1#L405-L417)
 describes decoding a bound against the current type. `Datum::to` already 
converts `int` to `long` and `float` to `double`, and decimals of different 
precision compare correctly today. `date` to `timestamp` (v3) would need its 
own conversion, or the bound could be dropped, since a missing bound already 
means unknown. If keeping the manifest's type is intended, what do you think 
about adding the two-manifest case to 
`test_plan_files_keeps_bounds_typed_with_manifest_schema` and saying in the 
docs that callers must convert bounds before comparing them across tasks?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to