3131#include " paimon/common/data/blob_utils.h"
3232#include " paimon/common/types/data_field.h"
3333#include " paimon/common/utils/fields_comparator.h"
34+ #include " paimon/common/utils/range_helper.h"
3435#include " paimon/core/deletionvectors/deletion_vectors_index_file.h"
3536#include " paimon/core/manifest/file_entry.h"
3637#include " paimon/core/manifest/file_kind.h"
@@ -351,56 +352,48 @@ Status ConflictDetection::CheckRowIdRangeConflicts(
351352 return Status::OK ();
352353 }
353354
354- std::vector<std::pair<Range, ManifestEntry> > entries_with_ranges;
355+ std::vector<ManifestEntry> entries_with_ranges;
355356 entries_with_ranges.reserve (merged_entries.size ());
356357 for (const ManifestEntry& entry : merged_entries) {
357- if (! entry.File ()->first_row_id ) {
358- continue ;
358+ if (entry.File ()->first_row_id ) {
359+ entries_with_ranges. push_back (entry) ;
359360 }
360- int64_t range_from = entry.File ()->first_row_id .value ();
361- int64_t range_to = range_from + entry.File ()->row_count - 1 ;
362- entries_with_ranges.emplace_back (Range (range_from, range_to), entry);
363361 }
364362 if (entries_with_ranges.empty ()) {
365363 return Status::OK ();
366364 }
367365
368- std::sort (entries_with_ranges.begin (), entries_with_ranges.end (),
369- [](const auto & lhs, const auto & rhs) { return lhs.first .from < rhs.first .from ; });
370-
371- size_t group_start = 0 ;
372- int64_t group_max_to = entries_with_ranges[0 ].first .to ;
373- for (size_t i = 1 ; i <= entries_with_ranges.size (); ++i) {
374- bool overlap_group_end =
375- (i == entries_with_ranges.size ()) || (entries_with_ranges[i].first .from > group_max_to);
376- if (!overlap_group_end) {
377- group_max_to = std::max (group_max_to, entries_with_ranges[i].first .to );
378- continue ;
379- }
380-
381- std::optional<std::pair<int64_t , int64_t >> expected_range;
382- for (size_t j = group_start; j < i; ++j) {
383- const ManifestEntry& entry = entries_with_ranges[j].second ;
384- if (IsDedicatedStorageFile (entry.FileName ())) {
385- continue ;
386- }
387- std::pair<int64_t , int64_t > current = {entries_with_ranges[j].first .from ,
388- entries_with_ranges[j].first .to };
389- if (!expected_range) {
390- expected_range = current;
391- } else if (expected_range.value () != current) {
392- return Status::Invalid (
393- " For Data Evolution table, multiple MERGE INTO/COMPACT operations have "
394- " encountered row-id range conflicts." );
366+ RangeHelper<ManifestEntry> range_helper (
367+ [](const ManifestEntry& entry) -> Result<int64_t > {
368+ return entry.File ()->first_row_id .value ();
369+ },
370+ [](const ManifestEntry& entry) -> Result<int64_t > {
371+ return entry.File ()->first_row_id .value () + entry.File ()->row_count - 1 ;
372+ });
373+ PAIMON_ASSIGN_OR_RAISE (std::vector<std::vector<ManifestEntry>> merged_groups,
374+ range_helper.MergeOverlappingRanges (std::move (entries_with_ranges)));
375+
376+ for (const auto & group : merged_groups) {
377+ std::vector<ManifestEntry> data_files;
378+ for (const ManifestEntry& entry : group) {
379+ // release-1.4 parity: row-id range conflict check excludes blob files only.
380+ if (!BlobUtils::IsBlobFile (entry.FileName ())) {
381+ data_files.push_back (entry);
395382 }
396383 }
397384
398- if (i < entries_with_ranges.size ()) {
399- group_start = i;
400- group_max_to = entries_with_ranges[i].first .to ;
385+ PAIMON_ASSIGN_OR_RAISE (bool all_data_ranges_same, range_helper.AreAllRangesSame (data_files));
386+ if (!all_data_ranges_same) {
387+ return Status::Invalid (
388+ " For Data Evolution table, multiple MERGE INTO/COMPACT operations have "
389+ " encountered row-id range conflicts." );
401390 }
402391 }
403392
393+ // TODO(yonghao.fyh): release-1.4 parity keeps dedicated-storage row-id containment unchecked here.
394+ // Consider adding a strict-mode validation that each dedicated-file range is fully
395+ // covered by one data-file range to catch dangling/cross-file mappings.
396+
404397 return Status::OK ();
405398}
406399
@@ -423,7 +416,8 @@ Status ConflictDetection::CheckForRowIdFromSnapshot(
423416 PAIMON_ASSIGN_OR_RAISE (Snapshot check_snapshot,
424417 snapshot_manager_->LoadSnapshot (row_id_check_from_snapshot_.value ()));
425418 if (!check_snapshot.NextRowId ()) {
426- return Status::OK ();
419+ return Status::Invalid (fmt::format (" Next row id cannot be null for snapshot {}." ,
420+ row_id_check_from_snapshot_.value ()));
427421 }
428422 int64_t check_next_row_id = check_snapshot.NextRowId ().value ();
429423
0 commit comments