2323#include < map>
2424#include < optional>
2525#include < set>
26+ #include < string>
2627#include < utility>
2728
2829#include " paimon/core/index/global_index_meta.h"
2930#include " paimon/core/index/pk/primary_key_index_source_meta.h"
3031#include " paimon/core/index/pk/primary_key_index_source_policy.h"
3132
3233namespace paimon {
34+ namespace {
35+ struct PayloadCandidate {
36+ std::shared_ptr<IndexFileMeta> payload;
37+ std::shared_ptr<PkSortedIndexGroup> group;
38+ std::vector<PrimaryKeyIndexSourceFile> active_sources;
39+ bool conflicted = false ;
40+ };
41+ } // namespace
42+
3343PkSortedBucketIndexState PkSortedBucketIndexState::FromActiveDataFiles (
3444 int32_t field_id, const std::string& index_type,
3545 const std::vector<std::shared_ptr<DataFileMeta>>& active_data_files,
@@ -49,10 +59,20 @@ PkSortedBucketIndexState PkSortedBucketIndexState::FromActiveDataFiles(
4959 });
5060 }
5161
52- // Match payloads against the expected level sources; anything that does not decode or
53- // does not exactly cover its level is rejected.
54- std::map<int32_t , std::vector<std::shared_ptr<IndexFileMeta>>> payloads_by_level;
55- std::map<int32_t , std::vector<PrimaryKeyIndexSourceMeta>> payload_metas_by_level;
62+ std::map<std::string, int64_t > active_sources;
63+ std::set<std::string> ambiguous_active_source_names;
64+ for (const auto & level_sources : sources_by_level) {
65+ for (const PrimaryKeyIndexSourceFile& source : level_sources.second ) {
66+ if (!active_sources.emplace (source.file_name , source.row_count ).second ) {
67+ ambiguous_active_source_names.insert (source.file_name );
68+ }
69+ }
70+ }
71+
72+ // Keep the complete immutable source group for ordinal localization, but only claim the
73+ // sources which are still active in this snapshot. Several disjoint groups may coexist
74+ // after an update; no active source file may be claimed by two groups.
75+ std::vector<PayloadCandidate> candidates;
5676 std::vector<std::shared_ptr<IndexFileMeta>> rejected;
5777 for (const std::shared_ptr<IndexFileMeta>& payload : active_payloads) {
5878 if (payload == nullptr ) {
@@ -71,39 +91,86 @@ PkSortedBucketIndexState PkSortedBucketIndexState::FromActiveDataFiles(
7191 continue ;
7292 }
7393 PrimaryKeyIndexSourceMeta source_meta = std::move (source_meta_result).value ();
74- auto desired = sources_by_level.find (source_meta.DataLevel ());
75- if (desired == sources_by_level.end () || desired->second != source_meta.SourceFiles ()) {
94+ const std::vector<PrimaryKeyIndexSourceFile>& payload_sources = source_meta.SourceFiles ();
95+ bool valid_candidate = !payload_sources.empty ();
96+ std::vector<PrimaryKeyIndexSourceFile> active_intersection;
97+ for (size_t i = 0 ; valid_candidate && i < payload_sources.size (); i++) {
98+ const PrimaryKeyIndexSourceFile& source = payload_sources[i];
99+ if (i > 0 && payload_sources[i - 1 ].file_name >= source.file_name ) {
100+ valid_candidate = false ;
101+ break ;
102+ }
103+ auto active_source = active_sources.find (source.file_name );
104+ if (active_source == active_sources.end ()) {
105+ continue ;
106+ }
107+ if (active_source->second != source.row_count ||
108+ ambiguous_active_source_names.count (source.file_name ) > 0 ) {
109+ valid_candidate = false ;
110+ break ;
111+ }
112+ active_intersection.push_back (source);
113+ }
114+ if (!valid_candidate || active_intersection.empty ()) {
115+ rejected.push_back (payload);
116+ continue ;
117+ }
118+ std::shared_ptr<PkSortedIndexGroup> group =
119+ PkSortedIndexGroup::Create (field_id, index_type, payload_sources, payload, source_meta);
120+ if (group == nullptr ) {
76121 rejected.push_back (payload);
77122 continue ;
78123 }
79- payloads_by_level[source_meta.DataLevel ()].push_back (payload);
80- payload_metas_by_level[source_meta.DataLevel ()].push_back (std::move (source_meta));
124+ candidates.push_back (
125+ {payload, std::move (group), std::move (active_intersection), /* conflicted=*/ false });
126+ }
127+
128+ std::map<std::pair<std::string, int64_t >, std::vector<size_t >> candidates_by_source;
129+ for (size_t i = 0 ; i < candidates.size (); i++) {
130+ const PayloadCandidate& candidate = candidates[i];
131+ for (const PrimaryKeyIndexSourceFile& source : candidate.active_sources ) {
132+ candidates_by_source[{source.file_name , source.row_count }].push_back (i);
133+ }
134+ }
135+ for (const auto & source_candidates : candidates_by_source) {
136+ if (source_candidates.second .size () > 1 ) {
137+ for (size_t candidate_index : source_candidates.second ) {
138+ candidates[candidate_index].conflicted = true ;
139+ }
140+ }
81141 }
82142
83143 std::vector<std::shared_ptr<PkSortedIndexGroup>> groups;
84- std::set<int32_t > covered_levels;
85- for (const auto & level_payloads : payloads_by_level) {
86- int32_t level = level_payloads.first ;
87- std::shared_ptr<PkSortedIndexGroup> group;
88- if (level_payloads.second .size () == 1 ) {
89- group = PkSortedIndexGroup::Create (field_id, index_type, sources_by_level[level],
90- level_payloads.second [0 ],
91- payload_metas_by_level[level][0 ]);
144+ std::set<std::pair<std::string, int64_t >> covered_sources;
145+ for (PayloadCandidate& candidate : candidates) {
146+ if (candidate.conflicted ) {
147+ rejected.push_back (std::move (candidate.payload ));
148+ continue ;
92149 }
93- if (group != nullptr ) {
94- groups.push_back (std::move (group));
95- covered_levels.insert (level);
96- } else {
97- rejected.insert (rejected.end (), level_payloads.second .begin (),
98- level_payloads.second .end ());
150+ for (const PrimaryKeyIndexSourceFile& source : candidate.active_sources ) {
151+ covered_sources.emplace (source.file_name , source.row_count );
99152 }
153+ groups.push_back (std::move (candidate.group ));
100154 }
155+ std::sort (groups.begin (), groups.end (),
156+ [](const std::shared_ptr<PkSortedIndexGroup>& left,
157+ const std::shared_ptr<PkSortedIndexGroup>& right) {
158+ if (left->DataLevel () != right->DataLevel ()) {
159+ return left->DataLevel () < right->DataLevel ();
160+ }
161+ return left->SourceFiles ().front ().file_name <
162+ right->SourceFiles ().front ().file_name ;
163+ });
101164
102165 std::vector<PrimaryKeyIndexSourceFile> covered;
103166 std::vector<PrimaryKeyIndexSourceFile> uncovered;
104167 for (const auto & level_sources : sources_by_level) {
105- auto & target = covered_levels.count (level_sources.first ) > 0 ? covered : uncovered;
106- target.insert (target.end (), level_sources.second .begin (), level_sources.second .end ());
168+ for (const PrimaryKeyIndexSourceFile& source : level_sources.second ) {
169+ auto & target = covered_sources.count ({source.file_name , source.row_count }) > 0
170+ ? covered
171+ : uncovered;
172+ target.push_back (source);
173+ }
107174 }
108175 return PkSortedBucketIndexState (std::move (groups), std::move (covered), std::move (uncovered),
109176 std::move (rejected));
0 commit comments