Skip to content

feat(commit): align functionality with java paimon commit#397

Open
lucasfang wants to merge 15 commits into
alibaba:mainfrom
lucasfang:dev
Open

feat(commit): align functionality with java paimon commit#397
lucasfang wants to merge 15 commits into
alibaba:mainfrom
lucasfang:dev

Conversation

@lucasfang

Copy link
Copy Markdown
Collaborator

Purpose

Linked issue: close #xxx

Tests

API and Format

Documentation

Generative AI tooling

Copilot AI review requested due to automatic review settings July 2, 2026 05:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR expands commit conflict detection and index-file handling in FileStoreCommitImpl, adds row-id/write-column conflict checking for data-evolution scenarios, and updates/extends unit tests accordingly.

Changes:

  • Add ConflictDetection, ManifestEntryChanges, and RowIdColumnConflictChecker to centralize commit change collection and conflict checks (including DV/global index considerations).
  • Support committing index files during overwrite / filter-and-overwrite flows, and adjust commit-kind selection (e.g., DV index append => overwrite kind).
  • Update commit retry behavior and add/adjust tests for index files, retries, and PK table commit behavior.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/paimon/core/operation/file_store_commit_test.cpp Adds test validating DV index append triggers overwrite commit kind
src/paimon/core/operation/file_store_commit_impl_test.cpp Extends commit tests for index files, retry metrics, and conflict checking signature changes
src/paimon/core/operation/file_store_commit_impl.h Introduces new conflict/index APIs and wires in ConflictDetection
src/paimon/core/operation/file_store_commit_impl.cpp Implements index-aware overwrite, retry commit checking, and centralized change collection
src/paimon/core/operation/file_store_commit.cpp Removes prior PK-commit restriction logic in Create
src/paimon/core/operation/commit/row_id_column_conflict_checker.h Adds row-id/write-column overlap conflict checker interface
src/paimon/core/operation/commit/row_id_column_conflict_checker.cpp Implements row-id/write-column overlap conflict checker
src/paimon/core/operation/commit/manifest_entry_changes.h Adds helper to collect manifest + index changes from commit messages
src/paimon/core/operation/commit/manifest_entry_changes.cpp Implements change collection and “changed partitions” logic
src/paimon/core/operation/commit/manifest_entry_changes_test.cpp Adds unit tests for ManifestEntryChanges
src/paimon/core/operation/commit/conflict_detection.h Adds conflict detection API covering bucket/key-range/row-id/global-index checks
src/paimon/core/operation/commit/conflict_detection.cpp Implements conflict detection logic and row-id history scan
src/paimon/core/operation/commit/conflict_detection_test.cpp Adds unit test coverage for conflict detection behaviors
src/paimon/common/data/binary_row.h Adds std::hash for tuple key used in conflict detection
src/paimon/CMakeLists.txt Wires new sources + tests into the build
Comments suppressed due to low confidence (1)

src/paimon/core/operation/file_store_commit.cpp:1

  • The PR description is still the unfilled template (e.g., linked issue #xxx, tests section empty), but this change alters user-visible behavior by removing the previous PK-table commit restriction in FileStoreCommit::Create. Please update the PR title/description to clearly state the behavioral change, link the real issue, and list the tests validating the new PK commit behavior.
/*

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +509 to 518
EXPECT_CALL(*mock_fs, ReadFile(testing::_, testing::_))
.Times(testing::AnyNumber())
.WillRepeatedly(testing::Invoke([&](const std::string& path, std::string* content) {
return mock_fs->FileSystem::ReadFile(path, content);
}));
EXPECT_CALL(*mock_fs, ReadFile(testing::StrEq(latest_hint), testing::_))
.WillRepeatedly(testing::Invoke([](const std::string& path, std::string* content) {
*content = "-1";
return Status::OK();
}));
Comment on lines +94 to +95
::ArrowSchema arrow_schema;
ASSERT_TRUE(arrow::ExportSchema(*schema, &arrow_schema).ok());
Comment on lines +160 to +169
for (const auto& write_col : file.write_cols.value()) {
auto field_id_result = ResolveFieldId(file, write_col);
if (!field_id_result.ok()) {
return false;
}
const auto& field_id = field_id_result.value();
if (field_id.has_value() && write_range.field_ids.count(field_id.value()) > 0) {
return true;
}
}
Comment on lines 837 to 844
Result<bool> commit_result = CommitSnapshotImpl(new_snapshot, delta_statistics);
if (!commit_result.ok()) {
// commit exception, not sure about the situation and should not clean up the files.
PAIMON_LOG_WARN(logger_, "You need call FilterAndCommit to retry commit for exception. %s",
// commit exception is uncertain; retry after checking whether this commit already exists.
PAIMON_LOG_WARN(logger_, "Retry commit for exception. %s",
commit_result.status().ToString().c_str());

// To prevent the case where an atomic write times out but actually succeeds,
// retrying the commit could lead to the snapshot file being committed multiple times.
// Therefore, retries should be handled by the upper layer,
// which should call FilterAndCommit to avoid duplicate commits.
// Therefore, we should not trigger cleanup here,
// as it may delete meta files from a snapshot that was just written by ourselves,
// leading to an incomplete or corrupted snapshot.
guard.Release();
return Status::Invalid("You need call FilterAndCommit to retry commit for exception. ",
commit_result.status().ToString());
return false;
}
@lucasfang lucasfang changed the title Dev feat(commit): align functionality with java paimon commit Jul 2, 2026

@zjw1111 zjw1111 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the commit conflict-detection alignment against Java Paimon. The port is largely faithful (ManifestEntryChanges collection order, RetryWaiter backoff math, SequenceSnapshotProperties ADD-only max, row-tracking assignment all match Java). A few correctness gaps and divergences are worth fixing before merge — see inline.

Highest priority: RowIdColumnConflictChecker::ConflictsWith swallows field-resolution errors and can let a schema mismatch pass conflict detection.

Also, a few items not inlined:

  • Test coverage: RowIdColumnConflictChecker has no C++ test at all (Java has 7 cases, incl. testUsesFieldIdAcrossRename / testFailsOnUnknownNonSystemWriteColumn which cover two of the issues below); CheckRowIdExistence / CheckRowIdRangeConflicts / SequenceSnapshotProperties also lack direct tests. Please add these.
  • Style (docs/code-style.md): plain int in row_id_column_conflict_checker.cpp binary search (low/high/mid/index) and std::unordered_set<int> in file_store_commit_impl.cpp NumChangedBuckets — use fixed-width types.
  • Intentional partial ports (please confirm and add a note): DV+UNAWARE entry enrichment, pkClusteringOverride short-circuit in CheckKeyRange, partition-expire handling in CheckDeleteInEntries, and dropStats on overwrite delete entries are all omitted.
  • The PR description is still the unfilled template — please fill in the change summary, linked issue, and tests.


for (const auto& write_col : file.write_cols.value()) {
auto field_id_result = ResolveFieldId(file, write_col);
if (!field_id_result.ok()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConflictsWith swallows ResolveFieldId errors as return false (i.e. no conflict). Java's fieldId() throws on an unknown non-system write column (RowIdColumnConflictCheckerTest.testFailsOnUnknownNonSystemWriteColumn), and the constructor path (AddWriteFieldIds) already propagates the same error via PAIMON_ASSIGN_OR_RAISE, so this is inconsistent. Please make ConflictsWith return Result<bool> and propagate the error to the caller in conflict_detection.cpp — otherwise a schema mismatch can silently pass conflict detection.

return std::optional<int32_t>(it->second);
}

if (SpecialFields::IsSpecialFieldName(write_col)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The set used via IsSpecialFieldName (_SEQUENCE_NUMBER, _VALUE_KIND, rowkind, _ROW_ID, _INDEX_SCORE) diverges from Java SpecialFields.isSystemField (_KEY_ prefix + _SEQUENCE_NUMBER, _VALUE_KIND, _LEVEL, rowkind, _ROW_ID). As a result _LEVEL/_KEY_* are treated as user columns (error instead of skip) and _INDEX_SCORE is skipped (instead of error). Please use an isSystemField-equivalent check here rather than the shared IsSpecialFieldName.

const std::optional<std::shared_ptr<RowIdColumnConflictChecker>>&
row_id_column_conflict_checker,
const Snapshot::CommitKind& commit_kind) const {
if (options_.DeletionVectorsEnabled() &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses GetBucket() == UNAWARE_BUCKET (== 0), but an unaware append table defaults to bucket = -1 (ResolveBucketMode maps -1 && no PK to BUCKET_UNAWARE). Java guards on bucketMode.equals(BUCKET_UNAWARE). With DV enabled on a bucket=-1 unaware table this guard is skipped and the code falls through into bucket-aware conflict logic. Please check against the resolved bucket mode instead of the raw bucket number.

// commit exception, not sure about the situation and should not clean up the files.
PAIMON_LOG_WARN(logger_, "You need call FilterAndCommit to retry commit for exception. %s",
// commit exception is uncertain; retry after checking whether this commit already exists.
PAIMON_LOG_WARN(logger_, "Retry commit for exception. %s",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When CommitSnapshotImpl returns a non-OK status, the error is dropped here and the loop retries; on timeout only the generic conflict message is thrown (and it prints the configured max-retries, not the actual retry count). Java carries the underlying exception in RetryCommitResult and rethrows it in the timeout message. Please preserve the last hard-error status and surface it on abort, and distinguish conflict-retry from hard errors so the root cause isn't lost.

void RowTrackingCommitUtils::AssignSnapshotId(int64_t snapshot_id,
const std::vector<ManifestEntry>& delta_files,
std::vector<ManifestEntry>* snapshot_assigned) {
for (const auto& entry : delta_files) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ManifestEntry assigned_entry = entry; is a shallow copy sharing the same DataFileMeta, so AssignSequenceNumber/AssignFirstRowId mutate the caller's delta_files in place. Java's assign* return new immutable objects. Current callers overwrite delta_files right after, so it's latent, but any other holder of the same shared_ptr<DataFileMeta> gets silently polluted. Please clone the DataFileMeta before assigning (or have assign* return new objects).

Result<std::vector<ManifestEntry>> CommitScanner::ReadAllEntriesFromPartitions(
const Snapshot& snapshot,
const std::vector<std::map<std::string, std::string>>& partitions) const {
auto scan_filter = std::make_shared<ScanFilter>(/*predicate=*/nullptr, partitions,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overwrite scan passes no bucket filter. Java OverwriteChangesProvider.newScan() applies bucket -> bucket >= 0 when numBucket != POSTPONE_BUCKET to exclude postpone-bucket files; without it, pending postpone-bucket files get DELETE entries on a normal overwrite. ScanFilter::bucket_filter_ (optional<int32_t>) also can't express bucket>=0 today. Please add a way to exclude negative buckets for the overwrite scan.

entry.TotalBuckets(), entry.File());
}

for (const auto& change : changes_) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dedup (skip an ADD whose identifier already exists) diverges from Java buildResult(), which just addAll(newChanges). On an identifier collision the DELETE(X) is kept while ADD(X) is dropped, so after FileEntry::MergeEntries X is net-deleted (data loss). Unlikely to trigger given unique file names, but please align with Java (drop the dedup) or skip both the delete and the add together.

std::vector<IndexManifestEntry>* index_entries) const override;

private:
const std::vector<ManifestEntry>& delta_files_;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These providers hold delta_files/changelog_files/index_entries as const& members while being handed out as a shared_ptr (same for OverwriteChangesProvider, which also captures this by raw pointer). Current construction sites keep the referenced vectors alive during the synchronous TryCommit, so it's safe today, but it's a UAF footgun and differs from Java's value/GC capture. Please hold by value + std::move.

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.

3 participants