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>
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
9094DeleteLoader::~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
177234Result<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;
0 commit comments