Skip to content

Commit 63d82c7

Browse files
committed
fix(manifest): preserve snapshot ID inheritance state
Represent ManifestFile::added_snapshot_id as optional to distinguish an inheritance-ready null ID from a literal -1. Reuse manifests only when the ID is null, rewrite explicit -1 manifests with the commit snapshot ID, and continue requiring the field when writing manifest lists.
1 parent 35c5df1 commit 63d82c7

19 files changed

Lines changed: 163 additions & 49 deletions

‎src/iceberg/inheritable_metadata.cc‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,13 @@ Result<std::unique_ptr<InheritableMetadata>> InheritableMetadataFactory::Empty()
9797
Result<std::unique_ptr<InheritableMetadata>> InheritableMetadataFactory::FromManifest(
9898
const ManifestFile& manifest) {
9999
// Validate that the manifest has a snapshot ID assigned
100-
if (manifest.added_snapshot_id == kInvalidSnapshotId) {
100+
if (!manifest.added_snapshot_id.has_value()) {
101101
return InvalidManifest("Manifest file {} has no snapshot ID", manifest.manifest_path);
102102
}
103103

104104
return std::make_unique<BaseInheritableMetadata>(
105-
manifest.partition_spec_id, manifest.added_snapshot_id, manifest.sequence_number,
106-
manifest.manifest_path);
105+
manifest.partition_spec_id, manifest.added_snapshot_id.value(),
106+
manifest.sequence_number, manifest.manifest_path);
107107
}
108108

109109
Result<std::unique_ptr<InheritableMetadata>> InheritableMetadataFactory::ForCopy(

‎src/iceberg/manifest/manifest_adapter.cc‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,10 @@ Result<std::optional<int64_t>> ManifestFileAdapter::GetFirstRowId(
448448
}
449449

450450
Status ManifestFileAdapter::AppendInternal(const ManifestFile& file) {
451+
if (!file.added_snapshot_id.has_value()) {
452+
return InvalidManifestList("Cannot write manifest with unassigned snapshot ID: {}",
453+
file.manifest_path);
454+
}
451455
const auto& fields = manifest_list_schema_->fields();
452456
for (size_t i = 0; i < fields.size(); i++) {
453457
const auto& field = fields[i];
@@ -478,7 +482,7 @@ Status ManifestFileAdapter::AppendInternal(const ManifestFile& file) {
478482
break;
479483
}
480484
case ManifestFile::kAddedSnapshotIdFieldId:
481-
ICEBERG_RETURN_UNEXPECTED(AppendInt(array, file.added_snapshot_id));
485+
ICEBERG_RETURN_UNEXPECTED(AppendInt(array, file.added_snapshot_id.value()));
482486
break;
483487
case ManifestFile::kAddedFilesCountFieldId:
484488
if (file.added_files_count.has_value()) {

‎src/iceberg/manifest/manifest_list.h‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ struct ICEBERG_EXPORT ManifestFile {
107107
int64_t min_sequence_number = TableMetadata::kInitialSequenceNumber;
108108
/// Field id: 503
109109
/// ID of the snapshot where the manifest file was added
110-
int64_t added_snapshot_id = kInvalidSnapshotId;
110+
/// May be null before commit so callers can distinguish inheritance from
111+
/// kInvalidSnapshotId.
112+
std::optional<int64_t> added_snapshot_id;
111113
/// Field id: 504
112114
/// Number of entries in the manifest that have status ADDED (1), when null this is
113115
/// assumed to be non-zero

‎src/iceberg/manifest/manifest_merge_manager.cc‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ Result<std::vector<ManifestFile>> ManifestMergeManager::MergeGroup(
177177
ICEBERG_ASSIGN_OR_RAISE(auto merged, FlushBin(bin, metadata, writer_factory));
178178
merged_manifests_.Add(bin, merged);
179179
for (const auto* manifest : bin) {
180-
if (manifest->added_snapshot_id != snapshot_id) {
180+
if (!manifest->added_snapshot_id.has_value() ||
181+
manifest->added_snapshot_id.value() != snapshot_id) {
181182
++replaced_manifests_count_;
182183
}
183184
}
@@ -202,8 +203,8 @@ Result<ManifestFile> ManifestMergeManager::FlushBin(
202203

203204
const int64_t snapshot_id = snapshot_id_supplier_();
204205
for (const auto* manifest : bin) {
205-
bool is_committed = manifest->added_snapshot_id != kInvalidSnapshotId &&
206-
manifest->added_snapshot_id != snapshot_id;
206+
bool is_committed = manifest->added_snapshot_id.has_value() &&
207+
manifest->added_snapshot_id.value() != snapshot_id;
207208
ICEBERG_ASSIGN_OR_RAISE(auto reader, ManifestReader::Make(*manifest, file_io_, schema,
208209
spec, is_committed));
209210
ICEBERG_ASSIGN_OR_RAISE(auto entries, reader->Entries());
@@ -276,7 +277,8 @@ Result<int32_t> ManifestMergeManager::MergedManifestCache::CleanUncommitted(
276277

277278
std::ignore = delete_file(merged.manifest_path);
278279
for (const auto& manifest : bin.bin) {
279-
if (manifest.added_snapshot_id != snapshot_id) {
280+
if (!manifest.added_snapshot_id.has_value() ||
281+
manifest.added_snapshot_id.value() != snapshot_id) {
280282
++removed_replaced_manifests_count;
281283
}
282284
}

‎src/iceberg/manifest/manifest_writer.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ Result<ManifestFile> ManifestWriter::ToManifestFile() const {
239239
// have been written, so the min data sequence number is the one that will be
240240
// assigned when this is committed. pass kUnassignedSequenceNumber to inherit it.
241241
.min_sequence_number = min_sequence_number_.value_or(kUnassignedSequenceNumber),
242-
.added_snapshot_id = adapter_->snapshot_id().value_or(kInvalidSnapshotId),
242+
.added_snapshot_id = adapter_->snapshot_id(),
243243
.added_files_count = add_files_count_,
244244
.existing_files_count = existing_files_count_,
245245
.deleted_files_count = delete_files_count_,

‎src/iceberg/manifest/manifest_writer.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class ICEBERG_EXPORT ManifestWriter {
121121
/// \brief Factory function to create a writer for a manifest file based on format
122122
/// version.
123123
/// \param format_version The format version (1, 2, 3, etc.).
124-
/// \param snapshot_id ID of the snapshot.
124+
/// \param snapshot_id ID written to new entries; null writes inheritable snapshot IDs.
125125
/// \param manifest_location Path to the manifest file.
126126
/// \param file_io File IO implementation to use.
127127
/// \param partition_spec Partition spec for the manifest.

‎src/iceberg/manifest/v2_metadata.cc‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,16 @@ Status ManifestFileAdapterV2::Append(const ManifestFile& file) {
158158
}
159159

160160
Result<int64_t> ManifestFileAdapterV2::GetSequenceNumber(const ManifestFile& file) const {
161+
ICEBERG_DCHECK(file.added_snapshot_id.has_value(),
162+
"added_snapshot_id must be assigned before writing a manifest list");
161163
if (file.sequence_number == kUnassignedSequenceNumber) {
162164
// if the sequence number is being assigned here, then the manifest must be created by
163165
// the current operation. to validate this, check that the snapshot id matches the
164166
// current commit
165-
if (snapshot_id_ != file.added_snapshot_id) {
167+
if (snapshot_id_ != file.added_snapshot_id.value()) {
166168
return InvalidManifestList(
167169
"Found unassigned sequence number for a manifest from snapshot: {}",
168-
file.added_snapshot_id);
170+
file.added_snapshot_id.value());
169171
}
170172
return sequence_number_;
171173
}
@@ -174,12 +176,14 @@ Result<int64_t> ManifestFileAdapterV2::GetSequenceNumber(const ManifestFile& fil
174176

175177
Result<int64_t> ManifestFileAdapterV2::GetMinSequenceNumber(
176178
const ManifestFile& file) const {
179+
ICEBERG_DCHECK(file.added_snapshot_id.has_value(),
180+
"added_snapshot_id must be assigned before writing a manifest list");
177181
if (file.min_sequence_number == kUnassignedSequenceNumber) {
178182
// same sanity check as above
179-
if (snapshot_id_ != file.added_snapshot_id) {
183+
if (snapshot_id_ != file.added_snapshot_id.value()) {
180184
return InvalidManifestList(
181185
"Found unassigned sequence number for a manifest from snapshot: {}",
182-
file.added_snapshot_id);
186+
file.added_snapshot_id.value());
183187
}
184188
// if the min sequence number is not determined, then there was no assigned sequence
185189
// number for any file written to the wrapped manifest. replace the unassigned

‎src/iceberg/manifest/v3_metadata.cc‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,16 @@ Status ManifestFileAdapterV3::Append(const ManifestFile& file) {
206206
}
207207

208208
Result<int64_t> ManifestFileAdapterV3::GetSequenceNumber(const ManifestFile& file) const {
209+
ICEBERG_DCHECK(file.added_snapshot_id.has_value(),
210+
"added_snapshot_id must be assigned before writing a manifest list");
209211
if (file.sequence_number == kUnassignedSequenceNumber) {
210212
// if the sequence number is being assigned here, then the manifest must be created by
211213
// the current operation. to validate this, check that the snapshot id matches the
212214
// current commit
213-
if (snapshot_id_ != file.added_snapshot_id) {
215+
if (snapshot_id_ != file.added_snapshot_id.value()) {
214216
return InvalidManifestList(
215217
"Found unassigned sequence number for a manifest from snapshot: {}",
216-
file.added_snapshot_id);
218+
file.added_snapshot_id.value());
217219
}
218220
return sequence_number_;
219221
}
@@ -222,12 +224,14 @@ Result<int64_t> ManifestFileAdapterV3::GetSequenceNumber(const ManifestFile& fil
222224

223225
Result<int64_t> ManifestFileAdapterV3::GetMinSequenceNumber(
224226
const ManifestFile& file) const {
227+
ICEBERG_DCHECK(file.added_snapshot_id.has_value(),
228+
"added_snapshot_id must be assigned before writing a manifest list");
225229
if (file.min_sequence_number == kUnassignedSequenceNumber) {
226230
// same sanity check as above
227-
if (snapshot_id_ != file.added_snapshot_id) {
231+
if (snapshot_id_ != file.added_snapshot_id.value()) {
228232
return InvalidManifestList(
229233
"Found unassigned sequence number for a manifest from snapshot: {}",
230-
file.added_snapshot_id);
234+
file.added_snapshot_id.value());
231235
}
232236
// if the min sequence number is not determined, then there was no assigned sequence
233237
// number for any file written to the wrapped manifest. replace the unassigned

‎src/iceberg/row/manifest_wrapper.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ Result<Scalar> ManifestFileStructLike::GetField(size_t pos) const {
206206
case ManifestFileField::kMinSequenceNumber:
207207
return manifest_file.min_sequence_number;
208208
case ManifestFileField::kAddedSnapshotId:
209-
return manifest_file.added_snapshot_id;
209+
return FromOptional(manifest_file.added_snapshot_id);
210210
case ManifestFileField::kAddedFilesCount:
211211
return FromOptional(manifest_file.added_files_count);
212212
case ManifestFileField::kExistingFilesCount:

‎src/iceberg/table_scan.cc‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -804,10 +804,12 @@ Result<std::vector<std::shared_ptr<FileScanTask>>> IncrementalAppendScan::PlanFi
804804
for (const auto& snapshot : append_snapshots) {
805805
SnapshotCache snapshot_cache(snapshot.get());
806806
ICEBERG_ASSIGN_OR_RAISE(auto manifests, snapshot_cache.DataManifests(io_));
807-
std::ranges::copy_if(manifests, std::inserter(data_manifests, data_manifests.end()),
808-
[&snapshot_ids](const ManifestFile& manifest) {
809-
return snapshot_ids.contains(manifest.added_snapshot_id);
810-
});
807+
std::ranges::copy_if(
808+
manifests, std::inserter(data_manifests, data_manifests.end()),
809+
[&snapshot_ids](const ManifestFile& manifest) {
810+
return manifest.added_snapshot_id.has_value() &&
811+
snapshot_ids.contains(manifest.added_snapshot_id.value());
812+
});
811813
}
812814
if (data_manifests.empty()) {
813815
return std::vector<std::shared_ptr<FileScanTask>>{};
@@ -902,7 +904,8 @@ IncrementalChangelogScan::PlanFiles(std::optional<int64_t> from_snapshot_id_excl
902904
for (const auto& snapshot : changelog_snapshots) {
903905
ICEBERG_ASSIGN_OR_RAISE(auto manifests, snapshot.second->DataManifests(io_));
904906
for (auto& manifest : manifests) {
905-
if (snapshot_ids.contains(manifest.added_snapshot_id) &&
907+
if (manifest.added_snapshot_id.has_value() &&
908+
snapshot_ids.contains(manifest.added_snapshot_id.value()) &&
906909
seen_manifest_paths.insert(manifest.manifest_path).second) {
907910
data_manifests.push_back(manifest);
908911
}

0 commit comments

Comments
 (0)