feat: support geometry and geography types - #2933
Conversation
|
In case you are interested, PR #2019 (CLOSED as stale) also has code related to geo types. |
paleolimbot
left a comment
There was a problem hiding this comment.
There are a few details around the conversion of types, but this is looking good to me!
| return Ok(field_type.clone()); | ||
| } | ||
|
|
||
| let wkb_type = field.try_extension_type::<WkbType>().map_err(|err| { |
There was a problem hiding this comment.
Unfortunately, until arrow-rs 59.1.0 there are some corner cases that slip through here (fixed in apache/arrow-rs#10065 ). This is still OK, it just won't work for geography and does a few funny things with corner case CRSes until the arrow-rs dependency updates.
There was a problem hiding this comment.
Thanks for flagging this. I am leaving these corner cases as a limitation of the current arrow-rs dependency for now; they will be resolved when iceberg-rust upgrades to arrow-rs 59.1.0 or later.
| let crs = wkb_type.metadata().crs.as_ref().map(|crs| match crs { | ||
| serde_json::Value::String(value) => value.clone(), | ||
| other => other.to_string(), | ||
| }); |
There was a problem hiding this comment.
Iceberg has a slightly different CRS restriction than Parquet, where it greatly prefers Authority:Code over PROJJSON (because schemas are written so frequently to iceberg files, and PROJJSON is sufficiently verbose that it causes performance/space issues.
Because of this (and the buggy WkbType until arrow-rs 59.1.0), you may want to implement WkbType yourself specifically for Iceberg (e.g., with a utility to extract the iceberg-preferred CRS, which can be derived from PROJJSON using the id member).
There was a problem hiding this comment.
Addressed the CRS representation in 889ccfb: Arrow string CRS values are retained, while PROJJSON is converted from its id.authority and id.code to Iceberg-preferred AUTHORITY:CODE, with invalid metadata rejected. I kept arrow-rs WkbType rather than introducing an Iceberg-specific copy because parquet 58.4 consumes its own WkbType; replacing only the Iceberg Arrow type would not fix the pre-59.1 Geography behavior and could make Parquet logical-type writing incompatible.
paleolimbot
left a comment
There was a problem hiding this comment.
I am not qualified to approve this from the icberg-rust end of things, but the geography type conversion seems solid (I suggested some optional improvements to errors inline). Thank you!
Happy to review any follow-ups on the statistics/pruning end of things if you are interested (or to attempt them if you aren't!).
I kept arrow-rs WkbType rather than introducing an Iceberg-specific copy because parquet 58.4 consumes its own WkbType; replacing only the Iceberg Arrow type would not fix the pre-59.1 Geography behavior and could make Parquet logical-type writing incompatible.
I think this is fine, although anybody who wants to write Geography will have to write invalid metadata into Iceberg (and will receive invalid metadata that will be rejected when reading using the arrow reader).
The workaround for this is to rewrite the metadata just for the Parquet reader, but I get how it's not worth adding that here since it will automatically resolve when arrow-rs is bumped. Our workaround for the write side is here:
Can this be documented somehow?
| Error::new( | ||
| ErrorKind::DataInvalid, | ||
| "PROJJSON CRS id must contain a non-empty authority", | ||
| ) |
There was a problem hiding this comment.
I think these errors are OK (since they speak to invalid PROJJSON rather than a limitation of the iceberg format).
There was a problem hiding this comment.
Agreed. I kept these validation errors unchanged because they describe malformed PROJJSON metadata rather than an Iceberg representation limitation.
| }; | ||
|
|
||
| match crs { | ||
| serde_json::Value::String(crs) => Ok(Some(crs.clone())), |
There was a problem hiding this comment.
You may want to check here that the crs is less than some threshold of bytes (say, 128 bytes). Alternatively, you could validate that it looks like an authority:code string but that is harder. Mostly you want to catch WKT2 CRSes or PROJJSON CRSes that were accidentally escaped as strings to avoid manifest files that are written with many kilobytes of overhead per geometry field.
There was a problem hiding this comment.
Added a 128-byte limit for CRS strings in 6c87c62, with boundary and Arrow import tests. This prevents accidentally escaped WKT2/PROJJSON values from adding large per-field schema overhead.
| PrimitiveType::Geometry(geometry) => match geometry.crs() { | ||
| Some(crs) => write!(f, "geometry({crs})"), | ||
| None => write!(f, "geometry"), | ||
| }, | ||
| PrimitiveType::Geography(geography) => { | ||
| let algorithm = geography.algorithm(); | ||
| match (geography.crs(), algorithm) { | ||
| (None, EdgeInterpolationAlgorithm::Spherical) => write!(f, "geography"), | ||
| (Some(crs), EdgeInterpolationAlgorithm::Spherical) => { | ||
| write!(f, "geography({crs})") | ||
| } | ||
| (crs, algorithm) => write!( | ||
| f, | ||
| "geography({}, {})", | ||
| crs.unwrap_or(DEFAULT_GEOSPATIAL_CRS), | ||
| edge_interpolation_algorithm_as_str(algorithm) | ||
| ), | ||
| } | ||
| } |
There was a problem hiding this comment.
Do you have to escape the CRS at all here? It is an arbitrary string and could contain ) or , which I don't think would roundtrip (and may be invalid). Perhaps neither of those are concerns here.
There was a problem hiding this comment.
There is no escaping syntax in the Iceberg type-string grammar. To guarantee that values produced by Display can be parsed back, 6c87c62 rejects CRS strings containing , or ) at construction time and adds round-trip validation tests.
| #[test] | ||
| fn test_min_max_aggregator_skips_geospatial_byte_statistics() { |
There was a problem hiding this comment.
No problem if statistics are out of scope for this PR (but I'm also happy to review their addition here or in a follow up). Getting geography statistics from the Parquet writer are a bit of a pain (you have to register a static geography-aware bounder, an example of which we have in SedonaDB).
There was a problem hiding this comment.
Thanks. I will keep geospatial bounds/statistics out of scope for this PR; the current writer explicitly avoids treating WKB byte ordering as geospatial min/max statistics. Geography-aware bounds can be added in a focused follow-up.
|
@paleolimbot Thanks for calling this out. I documented the arrow-rs 58.x Geography metadata limitation in 9a5d028 on the public Arrow schema conversion APIs. The docs now explain both directions: standards-compliant GeoArrow |
|
Great! Can this be unmarked as draft? (i.e., is it ready for review from an iceberg committer?) |
9a5d028 to
3ae7041
Compare
|
It seems like there's a merge conflict here. When that's fixed and CI is clean I'm happy to go fishing for iceberg-rust committers for review since this is a huge feature for us that will unblock a lot of things! |
Summary
This draft PR adds Iceberg Geometry/Geography primitive type support by reusing arrow-rs/parquet-geospatial support instead of introducing a local geospatial model.
GeometryTypeandGeographyTypewith an Iceberg-ownedEdgeInterpolationAlgorithm, translating toparquet_geospatial::WkbEdgesat the Arrow boundary.Related issues
Related to #2411 and #1884.
Supersedes #2653, which was closed automatically by the stale bot. Existing review discussion remains available there.