Core: Read and write geometry and geography values in Avro - #17119
Conversation
500ea8d to
7b2cc6d
Compare
TypeToSchema mapped Iceberg types to Avro schema for every primitive except geometry and geography, so converting a schema with a geo column threw UnsupportedOperationException before any value could be read or written. Map both to an Avro bytes field carrying WKB, per the Avro type mapping in the spec (binary, geometry, and geography all use bytes). The value read/write paths dispatch on the Avro physical type, so once the schema is bytes the existing byteBuffers reader/writer handle geo unchanged in both the generic and internal object models (geo values are WKB ByteBuffers, like binary). Enable the shared DataTest geospatial coverage for the Avro object models (TestGenericAvro, TestInternalAvro, generic data TestGenericData) and add the GEOMETRY/GEOGRAPHY -> ByteBuffer handling to RandomAvroData and AvroTestHelpers that the generic Avro path needs, mirroring RandomInternalData/InternalTestHelpers.
7b2cc6d to
67619a7
Compare
…l.wkbPoint TestParquet.testGeospatialWkbRoundTrip wrote arbitrary bytes through the geometry/geography schema, which carries a Parquet geospatial logical type: the writer parses each value with a WKB reader to build geospatial statistics, so the bogus bytes were silently dropped from stats and the round-trip never exercised a real WKB value. Use RandomUtil.wkbPoint for genuine WKB. Also delegate TestAvroDataWriter's local wkbPoint helper to RandomUtil.wkbPoint (already on the test classpath) to keep WKB encoding in one place, matching TestParquetDataWriter.
|
Thanks for the careful review @wombatu-kun! Addressed in bcbef59:
|
szehon-ho
left a comment
There was a problem hiding this comment.
Clean, minimal follow-up to #16982. Mapping geometry/geography to Avro bytes in TypeToSchema is the right fix — value readers/writers already dispatch on the physical BYTES type, so no value-path changes are needed. Test coverage is thorough (shared DataTest geo cases, encoder parity, and explicit WKB round-trips for both Avro and parquet-avro paths). A couple of minor nits inline.
| .isEqualTo(geoRecords); | ||
| } | ||
|
|
||
| private static ByteBuffer wkbPoint(double xCoord, double yCoord) { |
There was a problem hiding this comment.
🟢 Nit — The local wkbPoint wrapper is fine (matches TestParquetDataWriter), but you could inline ByteBuffer.wrap(RandomUtil.wkbPoint(...)) the way TestParquet.testGeospatialWkbRoundTrip does and drop the helper. Optional either way.
There was a problem hiding this comment.
Leaving the local wkbPoint helper as-is: it delegates to RandomUtil.wkbPoint and matches the merged TestParquetDataWriter.wkbPoint precedent (the same one-place-for-WKB consistency wombatu-kun pointed to above), and it keeps the three call sites in the record list readable. Happy to inline if you feel strongly.
Both map to the Avro bytes schema; sharing the BINARY case block drops a comment that restated the code and matches how RandomInternalData groups these types. Addresses review nit.
|
Merged, thanks @huan233usc , and also @huan233usc @wombatu-kun for additional review ! |
Summary
TypeToSchemamapped every Iceberg primitive to an Avro schema exceptgeometryandgeography,so converting a schema with a geo column threw
UnsupportedOperationException: Unsupported type ID: GEOMETRYbefore any value could be read or written through the Avro object model. This wires up thevalue path.
Per the Avro type mapping in the spec,
geometryandgeographyare stored as an Avrobytesfieldcarrying WKB — the same representation as
binary:binarybytesgeometrybytes(WKB)geographybytes(WKB)So
TypeToSchema.primitive()now maps both geo types to the existingBINARY_SCHEMA. The valueread/write paths dispatch on the Avro physical type, so once the schema is
bytesthe existingbyteBuffersreader/writer handle geo unchanged in both the generic and internal object models (geovalues are WKB
ByteBuffers, exactly likebinary) — no value-path code changes are needed.This also closes the parquet-avro object model gap:
ParquetAvroWriter/ParquetAvroValueReadersbuild their Avro schema through
AvroSchemaUtil.convert, which routed into the sameTypeToSchemathat threw on geo, so geo was unreachable through that path too. Fixing
TypeToSchemamakes it worktransitively, with no parquet-avro code change. This is the follow-up szehon-ho asked about on #16982
(which added the Parquet WKB value path and deliberately left the Avro object model as a follow-up).
Note on the reverse direction
SchemaToTypeintentionally still maps Avrobytes → binary. Plain Avrobytescarries no marker todistinguish geometry/geography from binary, and Iceberg resolves the real column type from the
expected/table schema, never by reverse-inferring from the file's Avro schema — the same way
binaryand other
bytes-backed types already behave.Test plan
Object-model coverage via the shared
DataTestgeo cases and the Avro encoder/writer entry points:supportsGeospatial()on the Avro object-model tests —TestGenericAvro,TestInternalAvro,the generic-data
TestGenericData, andTestAvroEncoderUtil(so geo also round-trips through theAvroEncoderUtil.encode/decodeentry point, keeping the two generic-model Avro tests in parity).This round-trips geometry and geography across multiple CRS and edge algorithms with randomly
generated WKB values.
GEOMETRY/GEOGRAPHY→ByteBufferhandling the generic Avro path needs inRandomAvroDataand
AvroTestHelpers, mirroring the existingRandomInternalData/InternalTestHelpershandling.TestAvroDataWriter.testGeospatialWkbRoundTrip— geometry/geography through the AvroDataWriter→
PlannedDataReaderpath, including nulls.TestParquet.testGeospatialWkbRoundTrip— throughParquetAvroWriter→ParquetAvroValueReaders,covering the parquet-avro object model, including nulls.
./gradlew :iceberg-core:test --tests org.apache.iceberg.avro.TestGenericAvro --tests org.apache.iceberg.avro.TestInternalAvro --tests org.apache.iceberg.avro.TestAvroEncoderUtil --tests org.apache.iceberg.avro.TestAvroDataWriter./gradlew :iceberg-data:test --tests org.apache.iceberg.data.avro.TestGenericData./gradlew :iceberg-parquet:test --tests org.apache.iceberg.parquet.TestParquet