|
19 | 19 | #include "paimon/core/manifest/index_manifest_file_handler.h" |
20 | 20 |
|
21 | 21 | #include <set> |
| 22 | +#include <string> |
22 | 23 | #include <unordered_map> |
23 | 24 | #include <utility> |
24 | 25 |
|
| 26 | +#include "fmt/format.h" |
| 27 | +#include "paimon/common/utils/linked_hash_map.h" |
25 | 28 | #include "paimon/core/deletionvectors/deletion_vectors_index_file.h" |
| 29 | +#include "paimon/core/index/index_file_meta.h" |
26 | 30 | namespace paimon { |
27 | 31 |
|
28 | 32 | using BucketIdentifier = std::tuple<BinaryRow, int32_t, std::string>; |
29 | 33 |
|
30 | | -std::vector<IndexManifestEntry> IndexManifestFileHandler::BucketedCombiner::Combine( |
| 34 | +Result<std::vector<IndexManifestEntry>> IndexManifestFileHandler::BucketedCombiner::Combine( |
31 | 35 | const std::vector<IndexManifestEntry>& prev_index_files, |
32 | 36 | const std::vector<IndexManifestEntry>& new_index_files) const { |
33 | 37 | std::unordered_map<BucketIdentifier, IndexManifestEntry> index_entries; |
@@ -67,7 +71,7 @@ std::vector<IndexManifestEntry> IndexManifestFileHandler::BucketedCombiner::Comb |
67 | 71 | return result_entries; |
68 | 72 | } |
69 | 73 |
|
70 | | -std::vector<IndexManifestEntry> IndexManifestFileHandler::GlobalFileNameCombiner::Combine( |
| 74 | +Result<std::vector<IndexManifestEntry>> IndexManifestFileHandler::GlobalFileNameCombiner::Combine( |
71 | 75 | const std::vector<IndexManifestEntry>& prev_index_files, |
72 | 76 | const std::vector<IndexManifestEntry>& new_index_files) const { |
73 | 77 | std::map<std::string, IndexManifestEntry> index_entries; |
@@ -104,6 +108,92 @@ std::vector<IndexManifestEntry> IndexManifestFileHandler::GlobalFileNameCombiner |
104 | 108 | return result_entries; |
105 | 109 | } |
106 | 110 |
|
| 111 | +namespace { |
| 112 | +using DeletionVectorRanges = LinkedHashMap<std::string, DeletionVectorMeta>; |
| 113 | + |
| 114 | +/// The deletion vectors an index file holds, keyed by the data file each covers. Null when the |
| 115 | +/// entry holds none. |
| 116 | +const DeletionVectorRanges* GetDeletionVectorRanges(const IndexManifestEntry& entry) { |
| 117 | + const std::optional<DeletionVectorRanges>& dv_ranges = entry.index_file->DvRanges(); |
| 118 | + return dv_ranges == std::nullopt ? nullptr : &dv_ranges.value(); |
| 119 | +} |
| 120 | +} // namespace |
| 121 | + |
| 122 | +Result<std::vector<IndexManifestEntry>> |
| 123 | +IndexManifestFileHandler::GlobalDeletionVectorCombiner::Combine( |
| 124 | + const std::vector<IndexManifestEntry>& prev_index_files, |
| 125 | + const std::vector<IndexManifestEntry>& new_index_files) const { |
| 126 | + std::map<std::string, IndexManifestEntry> index_entries; |
| 127 | + std::set<std::string> covered_data_files; |
| 128 | + for (const auto& entry : prev_index_files) { |
| 129 | + index_entries.insert_or_assign(entry.index_file->FileName(), entry); |
| 130 | + const DeletionVectorRanges* dv_ranges = GetDeletionVectorRanges(entry); |
| 131 | + if (dv_ranges == nullptr) { |
| 132 | + continue; |
| 133 | + } |
| 134 | + for (const auto& [data_file, _] : *dv_ranges) { |
| 135 | + covered_data_files.insert(data_file); |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + std::vector<const IndexManifestEntry*> removed; |
| 140 | + std::vector<const IndexManifestEntry*> added; |
| 141 | + for (const auto& entry : new_index_files) { |
| 142 | + if (entry.kind == FileKind::Delete()) { |
| 143 | + removed.push_back(&entry); |
| 144 | + } else if (entry.kind == FileKind::Add()) { |
| 145 | + added.push_back(&entry); |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + // The deleted entry is processed first, so that an index file taking over the data files of |
| 150 | + // the one it replaces is not rejected as a second vector for them. Paimon Java's |
| 151 | + // GlobalCombiner is order sensitive here; its two sibling combiners are not. |
| 152 | + for (const IndexManifestEntry* entry : removed) { |
| 153 | + const std::string& file_name = entry->index_file->FileName(); |
| 154 | + if (index_entries.erase(file_name) == 0) { |
| 155 | + return Status::Invalid(fmt::format( |
| 156 | + "Trying to delete deletion vector index file {} which does not exist.", file_name)); |
| 157 | + } |
| 158 | + const DeletionVectorRanges* dv_ranges = GetDeletionVectorRanges(*entry); |
| 159 | + if (dv_ranges == nullptr) { |
| 160 | + continue; |
| 161 | + } |
| 162 | + for (const auto& [data_file, _] : *dv_ranges) { |
| 163 | + if (covered_data_files.erase(data_file) == 0) { |
| 164 | + return Status::Invalid( |
| 165 | + fmt::format("Trying to delete the deletion vector of data file {}, which does " |
| 166 | + "not exist.", |
| 167 | + data_file)); |
| 168 | + } |
| 169 | + } |
| 170 | + } |
| 171 | + for (const IndexManifestEntry* entry : added) { |
| 172 | + const std::string& file_name = entry->index_file->FileName(); |
| 173 | + if (index_entries.find(file_name) != index_entries.end()) { |
| 174 | + return Status::Invalid(fmt::format( |
| 175 | + "Trying to add deletion vector index file {} which is already added.", file_name)); |
| 176 | + } |
| 177 | + const DeletionVectorRanges* dv_ranges = GetDeletionVectorRanges(*entry); |
| 178 | + if (dv_ranges != nullptr) { |
| 179 | + for (const auto& [data_file, _] : *dv_ranges) { |
| 180 | + if (!covered_data_files.insert(data_file).second) { |
| 181 | + return Status::Invalid(fmt::format( |
| 182 | + "Trying to add a second deletion vector for data file {}.", data_file)); |
| 183 | + } |
| 184 | + } |
| 185 | + } |
| 186 | + index_entries.insert_or_assign(file_name, *entry); |
| 187 | + } |
| 188 | + |
| 189 | + std::vector<IndexManifestEntry> result_entries; |
| 190 | + result_entries.reserve(index_entries.size()); |
| 191 | + for (const auto& [_, entry] : index_entries) { |
| 192 | + result_entries.push_back(entry); |
| 193 | + } |
| 194 | + return result_entries; |
| 195 | +} |
| 196 | + |
107 | 197 | Result<std::string> IndexManifestFileHandler::Write( |
108 | 198 | const std::optional<std::string>& previous_index_manifest, |
109 | 199 | const std::vector<IndexManifestEntry>& new_index_entries, int32_t bucket_mode, |
@@ -138,8 +228,8 @@ Result<std::string> IndexManifestFileHandler::Write( |
138 | 228 | GetIndexManifestFileCombine(index_type, bucket_mode)); |
139 | 229 | std::vector<IndexManifestEntry> typed_previous_entries = previous[index_type]; |
140 | 230 | std::vector<IndexManifestEntry> typed_current_entries = current[index_type]; |
141 | | - std::vector<IndexManifestEntry> combined_entries = |
142 | | - combiner->Combine(typed_previous_entries, typed_current_entries); |
| 231 | + PAIMON_ASSIGN_OR_RAISE(std::vector<IndexManifestEntry> combined_entries, |
| 232 | + combiner->Combine(typed_previous_entries, typed_current_entries)); |
143 | 233 |
|
144 | 234 | index_entries.insert(index_entries.end(), combined_entries.begin(), combined_entries.end()); |
145 | 235 | } |
@@ -167,8 +257,14 @@ IndexManifestFileHandler::GetIndexManifestFileCombine(const std::string& index_t |
167 | 257 | if (index_type != DeletionVectorsIndexFile::DELETION_VECTORS_INDEX && index_type != "HASH") { |
168 | 258 | return std::make_unique<GlobalFileNameCombiner>(); |
169 | 259 | } |
| 260 | + // `bucket_mode` is the configured bucket, not a resolved BucketMode, standing in for Paimon |
| 261 | + // Java's BucketMode.BUCKET_UNAWARE check. The two agree on every table that can exist: |
| 262 | + // SchemaValidation rejects the other unaware bucket, 0, and BucketIdCalculator refuses to |
| 263 | + // write the primary key table on which -1 means HASH_DYNAMIC instead. Lifting either |
| 264 | + // restriction means passing the resolved BucketMode here, or a dynamic bucket table would |
| 265 | + // combine its per-bucket deletion vectors by index file name. |
170 | 266 | if (index_type == DeletionVectorsIndexFile::DELETION_VECTORS_INDEX && bucket_mode == -1) { |
171 | | - return Status::NotImplemented("not yet support dv with BUCKET_UNAWARE mode"); |
| 267 | + return std::make_unique<GlobalDeletionVectorCombiner>(); |
172 | 268 | } |
173 | 269 | return std::make_unique<BucketedCombiner>(); |
174 | 270 | } |
|
0 commit comments