Skip to content

Support pinned-base assertions for Transaction commits - #83

Open
gbrgr wants to merge 2 commits into
mainfrom
gb/base-snapshot-assertion-and-stage-commit
Open

gbrgr wants to merge 2 commits into
mainfrom
gb/base-snapshot-assertion-and-stage-commit

Conversation

@gbrgr

@gbrgr gbrgr commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Lets a caller that must assert a commit against an externally-pinned base snapshot -- possibly one that no longer matches whatever the in-memory Table currently reports -- do so explicitly.

  • OverwriteAction::assert_requirements(requirements) -- supply arbitrary TableRequirements alongside the ones this action derives automatically (currently UuidMatch and a RefSnapshotIdMatch for the target branch). A supplied requirement of an auto-derived kind replaces the derived one; any other kind is added alongside; repeated calls accumulate.

Why

Transaction::commit's do_commit always refreshes the table from the catalog and silently rebases onto whatever is current before computing its auto-derived RefSnapshotIdMatch requirement, on every attempt, not just retries. That means a stale base is transparently absorbed rather than surfaced as a conflict -- by design, this is optimistic-retry-via-rebase, and a genuine conflict can never naturally surface through the plain commit path.

Transaction::apply's existing local requirement check (requirement.check(Some(table.metadata()))?) validates every requirement against the in-memory Table it's being applied to. For auto-derived requirements this is a tautology (they're read straight off that same table), but for a caller-supplied override it's actively wrong -- the whole reason to override is to assert something that may not match what's currently loaded, so the catalog -- not this local check -- can correctly reject it as a genuine conflict. ActionCommit now carries caller-supplied requirements separately via with_unchecked_requirements/take_unchecked_requirements, so only auto-derived requirements go through the local check; both still end up in the final commit.

Test plan

  • cargo test -p iceberg --lib -- 1794 passed, 0 failed
  • cargo fmt -p iceberg -- --check
  • cargo clippy -p iceberg --lib --all-targets -- clean
  • New unit tests cover: assert_requirements overriding an auto-derived requirement, adding a new requirement kind, accumulating across multiple calls.

🤖 Generated with Claude Code

@gbrgr
gbrgr force-pushed the gb/base-snapshot-assertion-and-stage-commit branch from 019ef89 to a97b6de Compare October 14, 2026 11:45
@gbrgr gbrgr changed the title Support pinned-base assertions and staged (not-yet-submitted) commits Support pinned-base assertions for Transaction commits Oct 14, 2026
gbrgr added a commit to RelationalAI/RustyIceberg.jl that referenced this pull request Oct 15, 2026
Wraps RelationalAI/iceberg-rust#83's OverwriteAction.assert_requirements
for Julia: supply arbitrary TableRequirements (as typed TableRequirement
subtypes: AssertTableUuid, AssertRefSnapshotId, etc.), passed to the Rust
side as a JSON array via a new iceberg_overwrite_action_assert_requirements
FFI export. A supplied requirement of an auto-derived kind replaces the
derived one; any other kind is added; repeated calls accumulate.

This is needed because Transaction::commit's do_commit always refreshes
the table from the catalog and silently rebases onto whatever is current
before computing its auto-derived RefSnapshotIdMatch requirement, so a
retried (or racing) commit would otherwise keep asserting whatever is
current rather than the base it actually started from, and a genuine
conflict could never surface. assert_requirements lets a caller pin that
assertion to an externally-resolved base snapshot instead.

Bumps iceberg_rust_ffi's iceberg-rust git rev to a97b6defa31aa69ecb894d8b
d5204e7fcd7a51d2 (that PR's branch head) to pick up the new API; move
this to a `main` rev once the PR merges.

Tests: assert_requirements_tests.jl covers the override/accumulate/reject
semantics, including two tests that provoke a genuine CatalogCommitConflicts
by pinning a stale/wrong requirement and asserting the catalog itself
rejects the commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets a caller supply arbitrary TableRequirements alongside the ones an
action derives automatically: a supplied requirement of an
auto-derived kind (UuidMatch, RefSnapshotIdMatch for the target ref)
replaces the derived one, any other kind is added, and repeated calls
accumulate. This makes it possible to assert a pinned base snapshot id
that differs from whatever the in-memory Table currently reports.

That override is needed because Transaction::commit's do_commit always
refreshes the table from the catalog and silently rebases onto
whatever is current before computing its auto-derived
RefSnapshotIdMatch requirement, on every attempt, not just retries --
so a retried (or racing) commit would otherwise keep asserting
whatever is current rather than the base it actually started from,
and a real conflict can never surface. A pinned override is exempted
from that rebase and from Transaction::apply's local self-check (which
is a tautology for auto-derived requirements, since those are read
straight off the same table, but would be wrong to apply to a
deliberately-diverging override) via ActionCommit's new
with_unchecked_requirements/take_unchecked_requirements, so the
catalog itself is left to enforce it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gbrgr
gbrgr force-pushed the gb/base-snapshot-assertion-and-stage-commit branch from a97b6de to f02c8db Compare October 15, 2026 08:46
gbrgr added a commit to RelationalAI/RustyIceberg.jl that referenced this pull request Oct 15, 2026
Wraps RelationalAI/iceberg-rust#83's OverwriteAction.assert_requirements
for Julia: supply arbitrary TableRequirements (as typed TableRequirement
subtypes: AssertTableUuid, AssertRefSnapshotId, etc.), passed to the Rust
side as a JSON array via a new iceberg_overwrite_action_assert_requirements
FFI export. A supplied requirement of an auto-derived kind replaces the
derived one; any other kind is added; repeated calls accumulate.

This is needed because Transaction::commit's do_commit always refreshes
the table from the catalog and silently rebases onto whatever is current
before computing its auto-derived RefSnapshotIdMatch requirement, so a
retried (or racing) commit would otherwise keep asserting whatever is
current rather than the base it actually started from, and a genuine
conflict could never surface. assert_requirements lets a caller pin that
assertion to an externally-resolved base snapshot instead.

Bumps iceberg_rust_ffi's iceberg-rust git rev to f02c8db2f890d4447a2995
738668573769aa98a3 (that PR's branch head, rebased onto main after #84's
upstream sync + pipeline.rs architecture convergence merged) to pick up
the new API; move this to a `main` rev once the PR merges.

Tests: assert_requirements_tests.jl covers the override/accumulate/reject
semantics, including two tests that provoke a genuine CatalogCommitConflicts
by pinning a stale/wrong requirement and asserting the catalog itself
rejects the commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
assert_requirements's doc comment referenced commit_pinned and
stage_commit, neither of which exist in this branch -- leftovers from
an earlier draft that also introduced stage_commit, later split out
into its own track. Point at Transaction::commit's rebase-on-every-
retry behavior instead, which is the actual reason this method exists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

1 participant