Skip to content

Core: Read and write geometry and geography values in Avro - #17119

Merged
szehon-ho merged 3 commits into
apache:mainfrom
huan233usc:geo-avro-schema
Jul 17, 2026
Merged

Core: Read and write geometry and geography values in Avro#17119
szehon-ho merged 3 commits into
apache:mainfrom
huan233usc:geo-avro-schema

Conversation

@huan233usc

@huan233usc huan233usc commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

TypeToSchema mapped every Iceberg primitive to an Avro schema except geometry and geography,
so converting a schema with a geo column threw UnsupportedOperationException: Unsupported type ID: GEOMETRY before any value could be read or written through the Avro object model. This wires up the
value path.

Per the Avro type mapping in the spec, geometry and geography are stored as an Avro bytes field
carrying WKB — the same representation as binary:

Iceberg type Avro type
binary bytes
geometry bytes (WKB)
geography bytes (WKB)

So TypeToSchema.primitive() now maps both geo types to the existing BINARY_SCHEMA. 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, exactly like binary) — no value-path code changes are needed.

This also closes the parquet-avro object model gap: ParquetAvroWriter / ParquetAvroValueReaders
build their Avro schema through AvroSchemaUtil.convert, which routed into the same TypeToSchema
that threw on geo, so geo was unreachable through that path too. Fixing TypeToSchema makes it work
transitively, 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

SchemaToType intentionally still maps Avro bytes → binary. Plain Avro bytes carries no marker to
distinguish 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 binary
and other bytes-backed types already behave.

Test plan

Object-model coverage via the shared DataTest geo cases and the Avro encoder/writer entry points:

  • Enable supportsGeospatial() on the Avro object-model tests — TestGenericAvro, TestInternalAvro,
    the generic-data TestGenericData, and TestAvroEncoderUtil (so geo also round-trips through the
    AvroEncoderUtil.encode/decode entry 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.
  • Add the GEOMETRY/GEOGRAPHYByteBuffer handling the generic Avro path needs in RandomAvroData
    and AvroTestHelpers, mirroring the existing RandomInternalData / InternalTestHelpers handling.
  • Explicit WKB round-trip tests:
    • TestAvroDataWriter.testGeospatialWkbRoundTrip — geometry/geography through the Avro DataWriter
      PlannedDataReader path, including nulls.
    • TestParquet.testGeospatialWkbRoundTrip — through ParquetAvroWriterParquetAvroValueReaders,
      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

Comment thread core/src/test/java/org/apache/iceberg/avro/TestGenericAvro.java
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.
@huan233usc
huan233usc requested a review from wombatu-kun July 7, 2026 17:23
@nssalian
nssalian requested review from szehon-ho and removed request for wombatu-kun July 11, 2026 18:57
@nssalian
nssalian requested a review from wombatu-kun July 11, 2026 18:57
Comment thread core/src/test/java/org/apache/iceberg/avro/TestAvroDataWriter.java
Comment thread parquet/src/test/java/org/apache/iceberg/parquet/TestParquet.java
Comment thread parquet/src/test/java/org/apache/iceberg/parquet/TestParquet.java Outdated
…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.
@huan233usc

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review @wombatu-kun! Addressed in bcbef59:

  • testGeospatialWkbRoundTrip now uses real WKB via RandomUtil.wkbPoint(...) so the geospatial statistics builder actually parses the values (the previous arbitrary bytes were silently dropped from stats).
  • TestAvroDataWriter.wkbPoint delegates to RandomUtil.wkbPoint and drops the ByteOrder import, matching the merged TestParquetDataWriter.
  • Updated the PR description/test plan to cover TestAvroEncoderUtil and both round-trip tests, and to note this closes the parquet-avro object model gap (the Parquet: Read and write geometry and geography WKB values #16982 follow-up).

@huan233usc
huan233usc requested a review from wombatu-kun July 12, 2026 19:48

@szehon-ho szehon-ho left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread core/src/main/java/org/apache/iceberg/avro/TypeToSchema.java
.isEqualTo(geoRecords);
}

private static ByteBuffer wkbPoint(double xCoord, double yCoord) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@szehon-ho
szehon-ho merged commit 8550723 into apache:main Jul 17, 2026
36 checks passed
@szehon-ho

Copy link
Copy Markdown
Member

Merged, thanks @huan233usc , and also @huan233usc @wombatu-kun for additional review !

@nssalian nssalian added this to the Iceberg 1.12.0 milestone Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants