Repository navigation
Expose OverwriteAction.set_snapshot_properties through the FFI - #113
Conversation
Expose iceberg-rust's existing OverwriteAction::set_snapshot_properties
(HashMap<String,String>) through the FFI, so callers can tag a commit's
snapshot summary with identifiers like attempt_id / job_id / fencing_token
at commit time -- needed for reconciliation (JC verifying a commit belongs
to a given job) per the platform's Option B design.
No new iceberg-rust capability needed -- OverwriteAction already supports
this upstream. The gap was entirely in this crate's FFI surface:
IcebergOverwriteAction only accumulated added/deleted files.
- Rust: new snapshot_properties field on IcebergOverwriteAction, a new
iceberg_overwrite_action_set_snapshot_properties FFI function (reusing
the existing PropertyEntry/parse_properties pattern already used for
catalog properties elsewhere in this crate), threaded into
set_snapshot_properties(...) in the apply() builder chain.
- Julia: set_snapshot_properties(action, Dict{String,String}) usable
inside with_overwrite(tx) do action ... end, following the same
PropertyEntry-array + GC.@preserve pattern as existing property-passing
functions.
- Verified caller-supplied keys survive a real commit (in-memory catalog)
without disturbing iceberg-rust's own computed summary properties,
which win on key collision (matches iceberg-java's SnapshotProducer.summary
semantics -- verified directly in iceberg-rust's own transaction/snapshot.rs).
- Bumped both Project.toml and iceberg_rust_ffi/Cargo.toml 0.9.3 -> 0.9.4,
matching this repo's convention for FFI-surface-affecting changes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
table_current_snapshot_summary(table) reads back a snapshot's summary properties (both caller-supplied ones like attempt_id and iceberg-rust's own computed metrics), mirroring table_schema's existing JSON-string FFI pattern. Lets the new set_snapshot_properties test actually assert the value landed correctly instead of only checking the commit didn't throw, and is generally useful for anything that needs to verify a commit's tags later (e.g. disambiguating a CAS conflict from a self-conflict). Also drop the `::T` return-type annotations on table_current_snapshot_summary and table_current_snapshot_id -- in Julia this isn't just documentation, it inserts an implicit convert(T, result) on every return, which is easy to forget and generally unidiomatic here since the union types are already what the function bodies naturally return. Separately: docker/docker-compose.yml pulled minio/minio:latest and minio/mc:latest straight from Docker Hub, which no longer hosts these images (same root cause as raicode's iceberg-up breakage) -- this was failing this repo's own CI on every PR. Fixed to pull from quay.io/minio instead (verified locally: full stack comes up healthy). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bunjj
left a comment
There was a problem hiding this comment.
Checked this against the pinned rev: snapshot.rs:434 clones the caller props then extends with the collector, so the precedence claim holds. test.yml builds the FFI from source and overrides the JLL path, so green CI here actually covers the new symbols.
Two questions, neither blocking. The accessor only reads current_snapshot(), so "did attempt X land" stops working once anyone commits on top, which is the crash case. Is a by-snapshot-id lookup coming, or does the JC path not need it? And transaction.rs:238 is still bare add_data_files, so fast-append can't carry a tag. Deliberate?
| function table_current_snapshot_summary(table::Table) | ||
| ptr = @ccall rust_lib.iceberg_table_current_snapshot_summary(table::Table)::Ptr{Cchar} | ||
| if ptr == C_NULL | ||
| return nothing |
There was a problem hiding this comment.
This returns nothing for two cases: no snapshot, and null/freed table pointer. table_schema right above throws STATE_RESOURCE_FREED for the second one. Can we match it? Otherwise a freed handle looks like "never committed", which is the one thing reconciliation shouldn't get wrong.
There was a problem hiding this comment.
Fixed — added the null-table check, matching table_schema. Also rewrote the corresponding test to pass C_NULL directly instead of a freed table: Table is a raw Ptr{Cvoid}, so free_table can't null the caller's copy, making freed-then-reused UB throughout this file (not just here) rather than something this check can catch.
| let Some(snapshot) = table_ref.table.metadata().current_snapshot() else { | ||
| return ptr::null_mut(); | ||
| }; | ||
| match serde_json::to_string(&snapshot.summary().additional_properties) { |
There was a problem hiding this comment.
Summary is { operation, #[serde(flatten)] additional_properties }, so to_string(snapshot.summary()) gets you the same map plus "operation". Any reason to drop it? Seems useful for the same check.
There was a problem hiding this comment.
No good reason, fixed — now serializing the whole summary() so operation is included.
| } | ||
|
|
||
| /// Get the current snapshot's summary properties (the caller-supplied ones set via | ||
| /// `iceberg_overwrite_action_set_snapshot_properties`, e.g. attempt_id -- NOT |
There was a problem hiding this comment.
Nit: says it's the caller properties and not the computed metrics, then says the computed metrics are in there too. The Julia docstring has it right.
There was a problem hiding this comment.
Fixed, trimmed to match the Julia docstring.
| @test !isnothing(summary) | ||
| @test summary["attempt_id"] == "test-attempt-123" | ||
| @test summary["added-data-files"] == "1" | ||
| @test summary["total-records"] == "2" |
There was a problem hiding this comment.
Nothing tests the collision behavior, and it rests on extend ordering in SnapshotProducer::summary, so a bump could flip it quietly. Worth passing "added-data-files" => "999" and asserting it's still "1".
There was a problem hiding this comment.
Added — round-trip test now sets added-data-files => 999 and asserts the real computed value (1) wins.
…n test
- Serialize the whole snapshot summary (including `operation`), not just
additional_properties.
- Add a null-table check to table_current_snapshot_summary, matching
table_schema's precedent.
- Test the null check against C_NULL directly, not a freed table -- Table
is a raw Ptr{Cvoid} so free_table can't null the caller's copy, making
freed-then-reused UB (same as every other Table-consuming function
here), not something this check can catch.
- Add a colliding key to the round-trip test to confirm computed values
win over caller-supplied ones.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
OverwriteAction::set_snapshot_propertiesthrough the FFI, so commits can carry custom snapshot summary tags (e.g.attempt_id,job_id) for reconciliation. No new iceberg-rust capability needed — the gap was entirely in this crate's FFI surface.table_current_snapshot_summaryto read summary properties back (needed for the tests to check actual values, not just absence of errors).0.9.3→0.9.4(Project.toml + Cargo.toml).docker/docker-compose.ymlpulledminio/minio/minio/mcfrom Docker Hub, which no longer hosts them. Switched toquay.io/minio.Usage
Test plan
test/overwrite_tests.jlcover write + read-back round-trip and error handling.overwrite_tests.jlsuite passes; no regressions in the rest of the suite.Not yet done
Pkg.developoverride or a Yggdrasil/JLL rebuild once merged.🤖 Generated with Claude Code