Skip to content

fix(table): scope RollbackToSnapshot to the transaction's target branch - #1828

Merged
zeroshade merged 2 commits into
apache:mainfrom
badalprasadsingh:fix/rollback-to-snapshot-branch-scope
Aug 14, 2026
Merged

fix(table): scope RollbackToSnapshot to the transaction's target branch#1828
zeroshade merged 2 commits into
apache:mainfrom
badalprasadsingh:fix/rollback-to-snapshot-branch-scope

Conversation

@badalprasadsingh

Copy link
Copy Markdown
Contributor

Description

Fixes #1827

Resolves the branch the same way AssertRefSnapshotID already does (t.branch, default main), looks up that branch's own head from its own ref, and writes the ancestor check, the ref update, and the CAS assertion to it.

Also made sure:

  • Unknown branch fails, never falls back to main: currentSnapshotForRef, from fix(table): resolve write parent snapshot from target branch head instead of main #1637, intentionally falls back to main for a new branch — reusing it here would let rolling back a nonexistent branch silently rewind main, reintroducing this exact bug.
  • Ref type is re-checked at the operation: not only at transaction construction. Before this fix RollbackToSnapshot always wrote to MainBranch, a branch by construction, so no type check was needed. Resolving an arbitrary named ref makes rewriting a tag as a branch (and dropping its tag retention) newly reachable.

Testing

Added tests covering the relevant edge cases.

Signed-off-by: badalprasadsingh <badal@datazip.io>
Signed-off-by: badalprasadsingh <badal@datazip.io>

@zeroshade zeroshade 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.

Verified the fix end to end: branch resolution matches how Transaction.AssertRefSnapshotID resolves the default (t.branch, falling back to MainBranch), the CAS requirement is correctly built from the base metadata via baseRefSnapshotID rather than the staged builder, and the deliberate refusal to fall back to main for an unknown ref is exactly right — reusing currentSnapshotForRef here would have reintroduced #1827 in a worse form. The test coverage is genuinely thorough (divergent-lineage ancestry, tag rejection at both the operation and commit, requirement dedup with a producer, no-retry on type conflict).

Two things I explicitly checked and found fine:

  • Replacing meta.currentSnapshot() with a meta.refs[MainBranch] lookup for the default path is not a regression for legacy metadata that has current-snapshot-id but no explicit main ref: constructRefs/UnmarshalJSON synthesize the main ref from current-snapshot-id, so the ref is always present when a current snapshot exists (table/metadata.go:2202, 2282).
  • The skipped requirement when baseRefSnapshotID returns nil (branch staged within this transaction) matches the existing pattern — the update that created the ref carries its own base-state requirement.

One message nit, non-blocking: the "branch %q does not exist" error at table/transaction.go:490 isn't wrapped in iceberg.ErrInvalidArgument while the tag-type error just below is; callers matching with errors.Is will only catch one of the two rejection paths. Fine to leave or fix in a follow-up.

LGTM.

@zeroshade
zeroshade merged commit 940ed19 into apache:main Aug 14, 2026
15 checks passed
@badalprasadsingh
badalprasadsingh deleted the fix/rollback-to-snapshot-branch-scope branch August 14, 2026 20:36
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.

Transaction: RollbackToSnapshot ignores the transaction's target branch and always operates on main

2 participants