Skip to content

Commit ca8da0e

Browse files
author
xuan.zhao
committed
feat(puffin): support deletion-vector-v1 blob read/write
1 parent 3c9d13d commit ca8da0e

17 files changed

Lines changed: 1375 additions & 15 deletions

src/iceberg/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ set(ICEBERG_DATA_SOURCES
188188
data/data_writer.cc
189189
data/delete_filter.cc
190190
data/delete_loader.cc
191+
data/deletion_vector_writer.cc
191192
data/equality_delete_writer.cc
192193
data/file_scan_task_reader.cc
193194
data/position_delete_writer.cc

src/iceberg/data/delete_loader.cc

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919

2020
#include "iceberg/data/delete_loader.h"
2121

22+
#include <cstdint>
2223
#include <cstring>
24+
#include <limits>
2325
#include <span>
2426
#include <string>
27+
#include <utility>
2528
#include <vector>
2629

2730
#include <nanoarrow/nanoarrow.h>
@@ -30,6 +33,7 @@
3033
#include "iceberg/arrow_c_data_guard_internal.h"
3134
#include "iceberg/deletes/position_delete_index.h"
3235
#include "iceberg/deletes/position_delete_range_consumer.h"
36+
#include "iceberg/file_io.h"
3337
#include "iceberg/file_reader.h"
3438
#include "iceberg/manifest/manifest_entry.h"
3539
#include "iceberg/metadata_columns.h"
@@ -89,10 +93,11 @@ DeleteLoader::DeleteLoader(std::shared_ptr<FileIO> io) : io_(std::move(io)) {}
8993

9094
DeleteLoader::~DeleteLoader() = default;
9195

92-
Status DeleteLoader::LoadPositionDelete(const DataFile& file, PositionDeleteIndex& index,
96+
Status DeleteLoader::LoadPositionDelete(const std::shared_ptr<DataFile>& file,
97+
PositionDeleteIndex& index,
9398
std::string_view data_file_path) const {
9499
// TODO(gangwu): push down path filter to open the file.
95-
ICEBERG_ASSIGN_OR_RAISE(auto reader, OpenDeleteFile(file, PosDeleteSchema(), io_));
100+
ICEBERG_ASSIGN_OR_RAISE(auto reader, OpenDeleteFile(*file, PosDeleteSchema(), io_));
96101

97102
ICEBERG_ASSIGN_OR_RAISE(auto arrow_schema, reader->Schema());
98103
internal::ArrowSchemaGuard schema_guard(&arrow_schema);
@@ -110,15 +115,19 @@ Status DeleteLoader::LoadPositionDelete(const DataFile& file, PositionDeleteInde
110115
// `ForEachPositionDelete`. Trusts the hint -- spec-compliant writers
111116
// only set it when all rows share one data file.
112117
const bool use_referenced_data_file_fast_path =
113-
file.referenced_data_file.has_value() &&
114-
file.referenced_data_file.value() == data_file_path;
118+
file->referenced_data_file.has_value() &&
119+
file->referenced_data_file.value() == data_file_path;
115120

116121
// Filter-path staging buffer; reused across batches via `clear()`.
117122
std::vector<int64_t> positions;
118123
// Scratch buffer for `ForEachPositionDelete`'s bulk dispatch path;
119124
// reused across batches and across both routing branches.
120125
std::vector<uint32_t> bulk_scratch;
121126

127+
// Whether any position for the target data file came from this file, so the
128+
// source delete file is recorded once.
129+
bool contributed = false;
130+
122131
while (true) {
123132
ICEBERG_ASSIGN_OR_RAISE(auto batch_opt, reader->Next());
124133
if (!batch_opt.has_value()) break;
@@ -150,6 +159,9 @@ Status DeleteLoader::LoadPositionDelete(const DataFile& file, PositionDeleteInde
150159
const int64_t* pos_data = Int64ValuesBuffer(pos_view);
151160

152161
if (use_referenced_data_file_fast_path) {
162+
// Reaching here means length > 0 (empty batches are skipped above), so
163+
// this file contributes positions for the target data file.
164+
contributed = true;
153165
ForEachPositionDelete(std::span<const int64_t>(pos_data, length), index,
154166
bulk_scratch);
155167
continue;
@@ -164,14 +176,59 @@ Status DeleteLoader::LoadPositionDelete(const DataFile& file, PositionDeleteInde
164176
positions.push_back(pos_data[i]);
165177
}
166178
}
179+
if (!positions.empty()) {
180+
contributed = true;
181+
}
167182
ForEachPositionDelete(positions, index, bulk_scratch);
168183
}
169184

170-
return reader->Close();
185+
ICEBERG_RETURN_UNEXPECTED(reader->Close());
186+
187+
// Record the source delete file so callers (e.g. DeletionVectorWriter) can
188+
// report file-scoped position deletes as rewritten.
189+
if (contributed) {
190+
index.AddDeleteFile(file);
191+
}
192+
return {};
171193
}
172194

173-
Status DeleteLoader::LoadDV(const DataFile& file, PositionDeleteIndex& index) const {
174-
return NotSupported("Loading deletion vectors is not yet supported");
195+
Status DeleteLoader::LoadDV(const std::shared_ptr<DataFile>& file,
196+
PositionDeleteIndex& index) const {
197+
// A deletion vector must reference exactly one data file; without it the
198+
// caller cannot know which data file the positions apply to.
199+
ICEBERG_PRECHECK(file->referenced_data_file.has_value(),
200+
"Deletion vector requires referenced_data_file: {}", file->file_path);
201+
202+
// For deletion vectors, content_offset and content_size_in_bytes point directly
203+
// at the DV blob bytes within the Puffin file and are required by the spec.
204+
ICEBERG_PRECHECK(
205+
file->content_offset.has_value() && file->content_size_in_bytes.has_value(),
206+
"Deletion vector requires content_offset and content_size_in_bytes: {}",
207+
file->file_path);
208+
209+
const int64_t offset = file->content_offset.value();
210+
const int64_t length = file->content_size_in_bytes.value();
211+
ICEBERG_PRECHECK(offset >= 0 && length >= 0,
212+
"Invalid deletion vector offset/length: offset={}, length={}", offset,
213+
length);
214+
ICEBERG_PRECHECK(length <= std::numeric_limits<int32_t>::max(),
215+
"Cannot read deletion vector larger than 2GB: {}", length);
216+
217+
ICEBERG_ASSIGN_OR_RAISE(auto input_file, io_->NewInputFile(file->file_path));
218+
ICEBERG_ASSIGN_OR_RAISE(auto stream, input_file->Open());
219+
220+
std::vector<std::byte> bytes(static_cast<size_t>(length));
221+
ICEBERG_RETURN_UNEXPECTED(stream->ReadFully(offset, bytes));
222+
ICEBERG_RETURN_UNEXPECTED(stream->Close());
223+
224+
std::span<const uint8_t> blob(reinterpret_cast<const uint8_t*>(bytes.data()),
225+
bytes.size());
226+
// Deserialize validates the blob length and cardinality against `file` and
227+
// retains it as the source delete file.
228+
ICEBERG_ASSIGN_OR_RAISE(auto dv, PositionDeleteIndex::Deserialize(blob, file));
229+
230+
index.Merge(dv);
231+
return {};
175232
}
176233

177234
Result<PositionDeleteIndex> DeleteLoader::LoadPositionDeletes(
@@ -180,21 +237,23 @@ Result<PositionDeleteIndex> DeleteLoader::LoadPositionDeletes(
180237
PositionDeleteIndex index;
181238

182239
for (const auto& file : delete_files) {
240+
ICEBERG_PRECHECK(file != nullptr, "Delete file must not be null");
241+
183242
if (file->referenced_data_file.has_value() &&
184243
file->referenced_data_file.value() != data_file_path) {
185244
continue;
186245
}
187246

188247
if (file->IsDeletionVector()) {
189-
ICEBERG_RETURN_UNEXPECTED(LoadDV(*file, index));
248+
ICEBERG_RETURN_UNEXPECTED(LoadDV(file, index));
190249
continue;
191250
}
192251

193252
ICEBERG_PRECHECK(file->content == DataFile::Content::kPositionDeletes,
194253
"Expected position delete file but got content type {}",
195254
ToString(file->content));
196255

197-
ICEBERG_RETURN_UNEXPECTED(LoadPositionDelete(*file, index, data_file_path));
256+
ICEBERG_RETURN_UNEXPECTED(LoadPositionDelete(file, index, data_file_path));
198257
}
199258

200259
return index;

src/iceberg/data/delete_loader.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,12 @@ class ICEBERG_DATA_EXPORT DeleteLoader {
6767

6868
private:
6969
/// \brief Load a single position delete file into the index.
70-
Status LoadPositionDelete(const DataFile& file, PositionDeleteIndex& index,
70+
Status LoadPositionDelete(const std::shared_ptr<DataFile>& file,
71+
PositionDeleteIndex& index,
7172
std::string_view data_file_path) const;
7273

7374
/// \brief Load a single deletion vector file into the index.
74-
Status LoadDV(const DataFile& file, PositionDeleteIndex& index) const;
75+
Status LoadDV(const std::shared_ptr<DataFile>& file, PositionDeleteIndex& index) const;
7576

7677
std::shared_ptr<FileIO> io_;
7778
};

0 commit comments

Comments
 (0)