Skip to content

[iceberg] Allow publishing VARIANT columns with Iceberg format version 3 - #9246

Open
vbabenkoru wants to merge 14 commits into
apache:masterfrom
vbabenkoru:iceberg-v3-variant
Open

[iceberg] Allow publishing VARIANT columns with Iceberg format version 3#9246
vbabenkoru wants to merge 14 commits into
apache:masterfrom
vbabenkoru:iceberg-v3-variant

Conversation

@vbabenkoru

Copy link
Copy Markdown
Contributor

Final PR of the 3-PR stack for #8972closes #8972. Stacked on #9244 and #9245: only the top commit belongs to this PR; the 13 commits below it are the two PRs underneath and will disappear from the diff as they merge.

Purpose

Iceberg supports the VARIANT type from format version 3 onward, but v3 also made row lineage mandatory. Paimon therefore rejected VARIANT columns in the Iceberg compatibility layer for all format versions, because the v3 metadata it produced was not lineage-compliant and GA readers would reject the whole table (#8972).

With #9244 and #9245, Paimon's v3 writer emits complete row lineage (table metadata, manifest lists, manifest entries), so the reason to block VARIANT on v3 is gone. This PR lifts the guard for metadata.iceberg.format-version >= 3:

  • Tables with VARIANT columns publish Iceberg metadata when the format version is 3, mapping to the Iceberg variant type.
  • Format version 2 keeps the existing clear rejection (with a message pointing at the format-version option), since VARIANT does not exist in v2.

The guard lift is intentionally the last commit of the stack: every v3 table that can publish VARIANT is guaranteed to have carried full row lineage from its first commit.

Tests

  • IcebergRowLineageCompatibilityTest#testVariantPublishableWithFormatVersion3: a VARIANT column publishes on v3, the schema maps to Iceberg variant, and lineage fields are present.
  • IcebergRowLineageCompatibilityTest#testVariantRejectedWithFormatVersion2: v2 commits with VARIANT still fail with the explicit error.

AI notice

The code is generated using Fable 5 (with reviews from Codex) but has been verified to run on a real cluster with Flink, Paimon, Iceberg, StarRocks and Snowflake.

@vbabenkoru
vbabenkoru force-pushed the iceberg-v3-variant branch 2 times, most recently from e82046a to f2c59f2 Compare August 17, 2026 16:44
Adds tests only, per the follow-up to the Layer 2 verification sweep
(task-7-report.md gap summary): compactMetadataIfNeeded's explicit-142
passthrough branch and its v3 merge call site now have coverage; the REST
recreation-consistency test compares first-row-id values against the local
file-based mirror instead of only checking non-nullity; a GA-reader test
resolves actual per-file first_row_id values (with the iceberg-data generics
limitation verified and documented); and the v2 manifest-list shape (14
columns, no first_row_id) is now pinned by a byte-level regression test.

No production code changes; all behavior matched the spec exactly.
Assigning first_row_id to a carried-over v3 manifest re-lists the same
physical avro file with a different list-level field. expireManifestList
compared IcebergManifestFileMeta values, so expiring the pre-assignment
manifest list deleted the shared file while newer manifest lists still
referenced it, breaking every subsequent Iceberg read of the retained
snapshots. Decide liveness by manifest path instead, and cover the
upgrade boundary with an expiration regression test.
…rg-ga profile

Iceberg 1.10+ ships Java-17 bytecode, so bumping paimon-iceberg to 1.11
outright broke the JDK 11 CI workflows that build this module. Restore
the 1.8.1 default so the module compiles and tests on JDK 11: the few
assertions that need the GA reader API (per-manifest / per-file
firstRowId) resolve it reflectively and skip themselves when absent, the
pre-GA enableRowLineage opt-in in IcebergMetadataTest is called
reflectively so the test passes on both lines, and the update logging in
the REST committer no longer names the RemoveSnapshot(s) class that was
renamed between the two lines. The full GA validation - Iceberg 1.11
with the Hadoop/Jetty/Jackson pins its REST test fixtures need - moves
behind the opt-in iceberg-ga profile (JDK 17):

    mvn test -pl paimon-iceberg -Ppaimon-iceberg,iceberg-ga

Verified: default suite on JDK 11 (50 tests, 2 GA-only skips) and
-Piceberg-ga on JDK 17 (50 tests, no skips).
GA ManifestReader assigns inherited first_row_id only to non-DELETED
entries. Materialization and manifest-level accounting advanced the
watermark through DELETED entries with a null field 142, shifting every
following live file relative to what readers had already inferred.

Skip DELETED entries when materializing, and reserve each unassigned
manifest's range with the spec-sanctioned added+existing upper bound
instead of reading manifest contents in the commit path: the exact
count, and the serial full-manifest scan it required, are gone;
already-materialized rows now leave spec-legal id gaps.

Regression test: a legacy manifest with a DELETED entry before a live
one keeps the live file's inherited id equal to the manifest's
first_row_id, cross-checked against the GA reader.

Reported by JingsongLi in review.
Rebuilding a REST table through TableMetadata.Builder recomputes
next-row-id from each snapshot's added-rows, so an id space that does
not start at 0 (self-heal and rollback rebuilds) ended below the ids
already assigned in manifests, and a later external REST writer could
have reused them.

Recreation, initial creation of a table with history, and the
exists-but-empty recovery now write the REST-adjusted metadata to a
file and register it, importing every high-water mark verbatim. The
drop+create failure loop the empty-table recovery guarded against
cannot occur here: registration has no post-create commit step.
Registration also preserves the real partition spec when a partition
column has field id 0, where creation used to fall back to an
unpartitioned spec.

Tests assert the recreated server watermark is at least the local one
and that an external append through the Iceberg API allocates above it.

Reported by JingsongLi in review.
The iceberg-ga profile was not exercised by CI, and running it as a
single 'mvn test -am' session fails: the not-yet-shaded paimon-bundle
is substituted with its unshaded constituent modules, whose direct
Avro references clash with the Avro 1.12 line Iceberg 1.11 requires
(the installed bundle relocates Avro and has no clash).

Add a JDK 17 workflow that installs the shaded bundle first and then
runs the suite, and document the two-step invocation in the profile.

Reported by JingsongLi in review.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug][iceberg] REST commit fails on format-version 3: Cannot parse missing long: next-row-id

1 participant