fix: use structured Summary for Iceberg REST snapshot commits - #2
Conversation
The Snapshot.summary field was a flat HashMap<String, String> which serialized 'operation' as just another key-value pair. Catalogs backed by apache/iceberg-rust (e.g. Lakekeeper) expect the Summary struct with 'operation' as a named field and additional properties flattened via serde. When deserializing the flat JSON, these catalogs see 'operation' twice (once from the struct field, once from the flattened map) and return HTTP 422: 'duplicate field operation'. Changes: - Add Summary struct with named operation field + #[serde(flatten)] - Change Snapshot.summary from HashMap<String, String> to Summary - Convert HashMap to Summary when building REST snapshots in factory.rs and nessie.rs (operation is extracted from the map, rest is flattened) - Update tests to verify correct serialization structure Fixes compatibility with Lakekeeper, Nessie (official client), and any catalog using apache/iceberg-rust's Summary deserialization.
Local verification ✅Compiled and tested locally with All existing tests pass, including the updated |
…rg-rust crate The official iceberg-rust v0.7.0 Summary struct has 'operation' as a named field plus #[serde(flatten)] additional_properties. When k2i's build_commit_summary() puts 'operation' into the HashMap and passes it to set_snapshot_properties(), the crate serializes 'operation' twice: once from the struct field and once from the flattened map. This causes 'duplicate field operation' (HTTP 422) in Lakekeeper. Remove 'operation' from the HashMap before calling set_snapshot_properties(). The crate sets the operation field internally from its own defaults.
Update: real root cause found and fixedThe initial struct fix was necessary but not sufficient. The actual root cause was in , which uses the official // official.rs - the actual code path used for REST catalog commits
let action = tx
.fast_append()
.set_snapshot_properties(summary) // ← HashMap contains "operation": "append"
.add_data_files(data_files);The official crate's
This is a known issue: apache/iceberg#9837 Fix (commit aa55988)Remove summary.remove("operation");
let action = tx.fast_append().set_snapshot_properties(summary)...;Verified
|
|
Follow-up review result: PR #2 had the right direction for the Iceberg
How it was tested: cargo fmt --all --check
git diff --check HEAD
cargo check --workspace --all-targets
cargo test --workspace --no-fail-fast
cargo clippy --workspace --all-targets -- -D warnings
scripts/e2e-docker-iceberg.sh
cargo semver-checks check-release --package k2i-core --baseline-rev origin/mainGitHub CI for #3 passed Check & Lint, Unit Tests, Integration Tests, Semver, Security Audit, and release planning. Separate note: #1 is not part of this Iceberg fix. It is a GHCR package visibility issue for the published container image. #4 adds release CI/docs so future Docker releases verify anonymous pull access after publishing. |
Problem
When k2i commits Iceberg snapshots to catalogs backed by
apache/iceberg-rust(e.g. Lakekeeper, Nessie with the official client), the commit fails with:Root Cause
The
Snapshot.summaryfield was a flatHashMap<String, String>that serialized as:{"operation": "append", "added-data-files": "1", ...}However,
apache/iceberg-rustdefinesSummaryas a struct withoperationas a named field plus#[serde(flatten)] additional_properties:When the catalog deserializes k2i's JSON, it sees
operationtwice — once from the struct field and once from the flattened map — causing the HTTP 422 error.This is also tracked upstream as apache/iceberg#9837 (OpenAPI spec issue with
additionalPropertiesnesting).Fix
Summarystruct withoperationas a named field +#[serde(flatten)]for additional propertiesSnapshot.summaryfromHashMap<String, String>toSummaryHashMaptoSummaryat the REST API boundary (factory.rs and nessie.rs)SnapshotCommit.summaryremainsHashMap<String, String>to avoid cascading changes to non-REST catalog backends (Glue, Hive, SQL)Serialization Before/After
Before (flat HashMap — causes duplicate field error):
{ "summary": { "operation": "append", "added-data-files": "1" } }After (structured Summary — matches iceberg-rust spec):
{ "summary": { "operation": "append", "added-data-files": "1" } }The JSON output is identical, but the serde model now matches what
apache/iceberg-rustexpects for deserialization, eliminating the duplicate field conflict.Testing
test_table_update_serializationto verify the structured Summary serializationCompatibility