Repository navigation
feat(catalog): commit snapshots through a version-managed catalog - #330
Merged
Merged
Conversation
SteNicholas
force-pushed
the
PAIMON-326
branch
4 times, most recently
from
September 14, 2026 03:52
921a4e6 to
15e60c4
Compare
lucasfang
reviewed
Sep 14, 2026
Collaborator
|
+1 |
SteNicholas
force-pushed
the
PAIMON-326
branch
2 times, most recently
from
September 14, 2026 09:41
1c8bb4e to
a71b018
Compare
Add Catalog::SupportsVersionManagement() and the internal VersionManagedCatalog interface, and let CommitContextBuilder and WriteContextBuilder take a catalog via WithCatalog(). Commits and writers load the current schema from that catalog. When it manages versions they also load the latest snapshot from it and publish new snapshots through it instead of renaming snapshot files; other catalogs keep the file-system commit. Align CommitTableRequest with Java (tableId, baseSnapshotUuid, snapshot and statistics; unset IDs serialize as null), give each new snapshot a UUID, and add the REST /commit and /snapshot endpoints. REST commit requests never retry or follow redirects. When the catalog reports a conflict, rebase on its latest snapshot and retry within the existing commit retry limits. When it returns an error the outcome is uncertain: keep the snapshot metadata and require FilterAndCommit to recover. Before expiring snapshots through a catalog, require its current snapshot and retained history to be published, protect files that a retained rollback snapshot re-adds, and return NotImplemented for index manifests before any file is deleted. Keep the request-only UseRESTCatalogCommit() mode and the legacy CommitContext constructor. Add tests for commit delegation, JSON compatibility, recovery, rollback, expiration and builder validation, and document the main-branch and file-system requirements.
Replace the assert in FileStoreCommit::Create with Status::Invalid so a CommitContext constructed directly with a catalog but no identifier fails through the Result error model instead of throwing std::bad_optional_access in release builds, matching FileStoreWrite::Create. Add a regression test.
SteNicholas
force-pushed
the
PAIMON-326
branch
from
September 14, 2026 09:43
a71b018 to
6952323
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Linked issue: close #326
Java Paimon commits a snapshot through
Catalog#commitSnapshot(identifier, tableUuid, baseSnapshotUuid, snapshot, statistics), andCommitTableRequestcarriestableId,baseSnapshotUuid,snapshotandstatistics. Paimon C++ only had a request-only mode:CatalogSnapshotCommitbuilt a request withsnapshotandstatistics, the caller had to send it,SnapshotCommit::Commithad no base snapshot uuid to pass down, andSnapshothad nouuidat all, so a catalog could neither detect a commit aimed at a recreated table nor perform the optimistic-concurrency check Java relies on.This PR ports catalog-managed commits end to end, so a
RestCatalogtable is written, committed and expired with the sameFileStoreWrite/FileStoreCommitAPI as a file-system table.Catalog integration
Catalog::SupportsVersionManagement()(defaultfalse) and the internalVersionManagedCataloginterface withLoadSnapshot()andCommitSnapshot(). A catalog that reports version management without implementing the interface is refused at commit creation instead of silently writing a snapshot file.RestCatalogimplements it throughRESTApi::LoadSnapshot()(GET .../tables/{table}/snapshot) andRESTApi::CommitSnapshot()(POST .../tables/{table}/commit), with the matchingGetTableSnapshotResponseandCommitTableResponsemessages.FileSystemCatalogdoes not manage versions, so it keeps the renaming file-system commit.CommitContextBuilder::WithCatalog(catalog, identifier)andWriteContextBuilder::WithCatalog(catalog, identifier). Commits and writers load the current schema from the catalog on every commit; when the catalog manages versions they also load its latest snapshot, through aSnapshotManagersnapshot loader, and publish new snapshots through it. Only the main branch is supported, and the catalog supplies the file system for manifests and data unlessWithFileSystem()overrides it.WithCatalog()andUseRESTCatalogCommit()are mutually exclusive.Request and snapshot format, aligned with Java
CommitTableRequestgains nullabletableIdandbaseSnapshotUuidin Java's field order, serialized asnullwhen unset.SnapshotCommit::Commit(base_snapshot_uuid, snapshot, statistics)carries the base uuid;RenamingSnapshotCommitignores it as Java does.Snapshotgains a nullableuuid, serialized right afterversionand omitted when unset.Snapshot::GenerateUuid()gives every new snapshot one,Tag::TrimToSnapshot()keeps it, andTable::CatalogUuid()exposes the metastore uuid fortableId.Conflicts and recovery
false) is a conflict: the commit rebases on the catalog's latest snapshot and retries within the existingcommit.max-retries/commit.timeoutlimits, the same way a lost file-system rename does.ConflictDetectionreuses the base snapshot of the attempt, since the latest snapshot may exist only in the catalog.FilterAndCommit()resolves it, deduplicating against the catalog's snapshot and the published history.RestHttpClient::Execute()gains aretry_safeflag; the commit POST is sent with it off, so it is neither retried automatically nor replayed through a redirect.Expiration
Expire()requires the catalog's current snapshot to be loaded and its retained history to be published on the file system before deleting anything. An unpublished or mismatched current snapshot skips expiration; catalog or retained-metadata read errors propagate without deleting files.NotImplementedfor index manifests is returned before any file is deleted, replacing theassert(false)that preceded it.Compatibility
UseRESTCatalogCommit()mode and the legacyCommitContextconstructor are kept.GetLastCommitTableRequest()now always includestableIdandbaseSnapshotUuid,nullwhen unset; callers that spliced atableIdinto that JSON themselves should switch toCommitContextBuilder::WithTableId().Tests
New UT (76 cases), plus
MockCataloginsrc/paimon/testing/mock/mock_catalog.handsnapshot_test_helper.h:catalog_snapshot_commit_test.cpp(new target): commit through the catalog, a lost race is not an error, catalog failures propagate, a catalog without version management is refused, request-only build without a catalog.commit_table_request_test.cpp:TestNullTableIdAndBaseSnapshotUuid.rest_messages_test.cpp:GetTableSnapshotResponseRoundTrip,CommitTableResponseRoundTrip.snapshot_test.cpp:TestGenerateUuid,TestUuid.tag_test.cpp:TestTrimToSnapshotKeepsTheUuid.file_system_catalog_test.cpp:TestDoesNotManageVersions,TestClaimingVersionManagementWithoutImplementingItIsRefused.commit_context_test.cpp/write_context_test.cpp:TestWithCatalog,TestLegacyConstructor,TestCatalogRequiresMainBranch.file_store_commit_impl_test.cpp(37 cases): base snapshot uuid on the request; catalog commit takes the snapshot and is refused for a table recreated under the same name; catalog schema wins over the table path and is re-read on every commit; retries when refused, rebases on the winner of each race, succeeds after losing one race; unknown outcome keeps the manifests and recovers throughFilterAndCommit, including an answer lost on the way back and a history that is only partly published; realtime commits are idempotent through the catalog; row-id checks use the catalog latest and the published history; rollback through the catalog;FileStoreRollbackExpireTest.TestRetainedRollbackKeepsRestoredFiles; catalog expiration waits for rollback publication, uses published snapshots and validates retained metadata before deleting files.snapshot_manager_test.cpp(11 cases): the snapshot loader answers which snapshot is latest,LatestSnapshotOfUser*walks the published history, stops at the earliest retained or an expired snapshot, retries a stale boundary and reports a gap it cannot explain.rest_catalog_test.cpp:CommitSnapshot,LoadSnapshot,CommitSnapshotErrors,FileStoreCommitIsBuiltFromTheCatalog,TableUuidFallsBackToFullNameWithoutServerId,CommitTakenThenReportedUnavailableIsNotReplayed, andRestCatalogCommitRecoveryTest.CommitAcceptedThenLostRecoversAfterRestart.rest_http_client_test.cpp:NonRetrySafeRequestDoesNotRetryTransientResponses,NonRetrySafeRequestDoesNotFollowRedirects.file_store_write_test.cpp:TestRealtimeCatalogCommitRefreshAndRecovery.expire_snapshots_test.cpp:TestCleanUnusedDataFileDeletesExtraFilesbecomesTestCleanUnusedDataFilePreservesRetainedExtraFiles.API and Format
Yes.
include/paimon/catalog/catalog.h:Catalog::SupportsVersionManagement().include/paimon/catalog/table.h: optionaluuidconstructor argument andTable::CatalogUuid();Table::Uuid()returns the catalog uuid when present and still falls back to the full name.include/paimon/commit_context.h,include/paimon/write_context.h:WithCatalog(catalog, identifier)on both builders,GetCatalog()/GetIdentifier()/GetTableId()accessors, and aCommitContextconstructor taking the catalog fields; the previous constructor is retained.include/paimon/file_store_commit.h,include/paimon/file_store_write.h: documentation of the catalog commit mode, conflict retries, recovery and expiration requirements.uuidfield afterversion, omitted when unset.CommitTableRequestJSON gainstableIdandbaseSnapshotUuid,nullwhen unset. The REST client now calls the/snapshotand/committable endpoints of the Java REST catalog protocol.Documentation
Yes.
docs/source/user_guide/catalog.rst: a new Committing through the catalog section coveringWithCatalog(), the schema and snapshot sources, conflict retries, recovery, the expiration requirements, the rollback / expiration coordination note, and the list of Java REST catalog operations that still have no C++ counterpart.docs/source/user_guide/snapshot.rst: theuuidfield and the renumbered field list.docs/source/user_guide/write.rst: the catalog write and commit flow, and the request-only alternative for callers without a catalog client.docs/source/user_guide/format_table.rst:WithCatalog()andWithTableId()added to the builder options a format table refuses.Generative AI tooling
Generated-by: Claude Code (claude-fable-5-1)
🤖 Generated with Claude Code