Skip to content

Commit 330de84

Browse files
zhaoxuan1994xuan.zhaowgtmac
authored
feat(puffin): support deletion-vector-v1 blob read/write (#777)
feat(puffin): support deletion-vector-v1 blob read/write Add end-to-end support for the `deletion-vector-v1` Puffin blob type on top of the existing PuffinReader/PuffinWriter and RoaringPositionBitmap. Encoding (puffin/deletion_vector.{h,cc}): - SerializeDeletionVectorBlob / DeserializeDeletionVectorBlob implement the DV blob framing: 4-byte big-endian length, 0xD1D33964 magic, the portable Roaring vector, and a trailing big-endian CRC-32. Reads validate the magic and checksum. - MakeDeletionVectorBlob builds a spec-compliant Blob: fields set to the row-position metadata column id, snapshot-id/sequence-number = -1, no compression, and the required referenced-data-file/cardinality properties. Writing (data/deletion_vector_writer.{h,cc}): - DeletionVectorWriter accumulates deleted positions per data file, run-length encodes each bitmap before serializing, writes one blob per data file, and produces a DataFile per blob carrying content_offset / content_size_in_bytes, referenced_data_file and record_count for manifest registration. Reading (data/delete_loader.cc): - Implement DeleteLoader::LoadDV: read the blob bytes referenced by content_offset/content_size_in_bytes, validate referenced_data_file, the 2GB limit and cardinality == record_count, then apply positions to the index. This wires DV deletes into DeleteFilter::ComputeAliveRows. Puffin writer convenience: - PuffinWriter::Make now fills a default `created-by` property when the caller does not provide one, matching the Java writer. Behavior matches the Java implementation (BaseDVFileWriter / BaseDeleteLoader / BitmapPositionDeleteIndex): magic, byte order, CRC coverage, blob fields, RLE and the read-side validations are all aligned. Tests: - DV blob framing round-trip and error cases (bad magic, corrupted CRC, truncated blob, size mismatch). - DeletionVectorWriter -> DeleteLoader write/read round trip and guard cases. - delete_loader: load DV, referenced-file filtering, cardinality mismatch, mixed DV + position-delete loading. - delete_filter: end-to-end ComputeAliveRows filtering with a real Puffin DV file over Arrow FileIO. - PuffinWriter created-by default and caller-precedence. Not included (deletion-vector upper layer, follow-up): merging with previously written DVs (compaction), the bulk delete(PositionDeleteIndex) API, and per-data-file partition/spec in a single DV file. --------- Co-authored-by: xuan.zhao <xuan.zhao@clickzetta.com> Co-authored-by: Gang Wu <ustcwg@gmail.com>
1 parent fee1884 commit 330de84

33 files changed

Lines changed: 1608 additions & 68 deletions

.clang-tidy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ CheckOptions:
4343
- key: modernize-use-scoped-lock.WarnOnSingleLocks
4444
value: 'false'
4545
- key: misc-include-cleaner.IgnoreHeaders
46-
value: 'arrow/.*;avro/.*;aws/.*;cpr/.*;gmock/.*;gtest/.*;nanoarrow/.*;nlohmann/.*;parquet/.*;roaring/.*;spdlog/.*;sqlpp23/.*;sqlite3\.h;thrift/.*;utf8proc\.h;zlib\.h'
46+
value: 'arrow/.*;avro/.*;aws/.*;cpr/.*;gmock/.*;gtest/.*;nanoarrow/.*;nlohmann/.*;parquet/.*;roaring/.*;spdlog/.*;sqlpp23/.*;thrift/.*'
4747

4848
HeaderFilterRegex: 'src/iceberg|example'

CMakeLists.txt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@ project(Iceberg
2828
DESCRIPTION "Iceberg C++ Project"
2929
LANGUAGES CXX)
3030

31-
set(ICEBERG_VERSION_SUFFIX "-SNAPSHOT")
32-
set(ICEBERG_VERSION_STRING "${PROJECT_VERSION}${ICEBERG_VERSION_SUFFIX}")
33-
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/iceberg/version.h.in"
34-
"${CMAKE_BINARY_DIR}/src/iceberg/version.h")
31+
include(IcebergBuildUtils)
32+
iceberg_configure_version_header("${CMAKE_CURRENT_SOURCE_DIR}/src/iceberg/version.h.in"
33+
"${CMAKE_BINARY_DIR}/src/iceberg/version.h")
3534

3635
set(CMAKE_CXX_STANDARD 23)
3736
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -79,7 +78,6 @@ if(ICEBERG_BUILD_REST_INTEGRATION_TESTS AND WIN32)
7978
endif()
8079

8180
include(CMakeParseArguments)
82-
include(IcebergBuildUtils)
8381
include(IcebergSanitizer)
8482
include(IcebergSccache)
8583
include(IcebergThirdpartyToolchain)

cmake_modules/IcebergBuildUtils.cmake

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@
2020

2121
include(CMakePackageConfigHelpers)
2222

23+
function(iceberg_configure_version_header INPUT_FILE OUTPUT_FILE)
24+
set(ICEBERG_VERSION_SUFFIX "-SNAPSHOT")
25+
set(ICEBERG_VERSION_STRING "${PROJECT_VERSION}${ICEBERG_VERSION_SUFFIX}")
26+
set(ICEBERG_GIT_COMMIT_ID "unknown")
27+
28+
find_package(Git QUIET)
29+
if(GIT_FOUND)
30+
execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD
31+
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
32+
OUTPUT_VARIABLE ICEBERG_GIT_COMMIT_ID
33+
RESULT_VARIABLE ICEBERG_GIT_RESULT
34+
OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
35+
if(NOT ICEBERG_GIT_RESULT EQUAL 0 OR NOT ICEBERG_GIT_COMMIT_ID)
36+
set(ICEBERG_GIT_COMMIT_ID "unknown")
37+
endif()
38+
endif()
39+
40+
set(ICEBERG_FULL_VERSION_STRING
41+
"Apache Iceberg-CPP ${ICEBERG_VERSION_STRING} (commit ${ICEBERG_GIT_COMMIT_ID})")
42+
configure_file("${INPUT_FILE}" "${OUTPUT_FILE}" @ONLY)
43+
endfunction()
44+
2345
function(iceberg_install_cmake_package PACKAGE_NAME EXPORT_NAME)
2446
set(CONFIG_CMAKE "${PACKAGE_NAME}-config.cmake")
2547
set(BUILT_CONFIG_CMAKE "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_CMAKE}")

src/iceberg/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ set(ICEBERG_DATA_SOURCES
196196
data/data_writer.cc
197197
data/delete_filter.cc
198198
data/delete_loader.cc
199+
data/deletion_vector_writer.cc
200+
data/dv_util.cc
199201
data/equality_delete_writer.cc
200202
data/file_scan_task_reader.cc
201203
data/position_delete_writer.cc

src/iceberg/data/data_writer.cc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
#include "iceberg/data/data_writer.h"
2121

2222
#include <map>
23+
#include <utility>
2324

25+
#include "iceberg/data/writer.h" // IWYU pragma: keep
2426
#include "iceberg/file_writer.h"
2527
#include "iceberg/manifest/manifest_entry.h"
2628
#include "iceberg/partition_spec.h"
@@ -58,7 +60,7 @@ class DataWriter::Impl {
5860
return {};
5961
}
6062

61-
Result<FileWriter::WriteResult> Metadata() {
63+
Result<WriteResult> Metadata() {
6264
ICEBERG_CHECK(closed_, "Cannot get metadata before closing the writer");
6365

6466
ICEBERG_ASSIGN_OR_RAISE(auto metrics, writer_->metrics());
@@ -98,7 +100,7 @@ class DataWriter::Impl {
98100
options_.spec ? std::make_optional(options_.spec->spec_id()) : std::nullopt,
99101
});
100102

101-
FileWriter::WriteResult result;
103+
WriteResult result;
102104
result.data_files.push_back(std::move(data_file));
103105
return result;
104106
}
@@ -127,6 +129,6 @@ Result<int64_t> DataWriter::Length() const { return impl_->Length(); }
127129

128130
Status DataWriter::Close() { return impl_->Close(); }
129131

130-
Result<FileWriter::WriteResult> DataWriter::Metadata() { return impl_->Metadata(); }
132+
Result<WriteResult> DataWriter::Metadata() { return impl_->Metadata(); }
131133

132134
} // namespace iceberg

src/iceberg/data/delete_loader.cc

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,29 @@
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>
2831

2932
#include "iceberg/arrow/nanoarrow_status_internal.h"
3033
#include "iceberg/arrow_c_data_guard_internal.h"
34+
#include "iceberg/data/dv_util_internal.h"
3135
#include "iceberg/deletes/position_delete_index.h"
3236
#include "iceberg/deletes/position_delete_range_consumer.h"
37+
#include "iceberg/file_io.h"
3338
#include "iceberg/file_reader.h"
3439
#include "iceberg/manifest/manifest_entry.h"
3540
#include "iceberg/metadata_columns.h"
3641
#include "iceberg/result.h"
3742
#include "iceberg/row/arrow_array_wrapper.h"
3843
#include "iceberg/schema.h"
44+
#include "iceberg/util/content_file_util.h"
3945
#include "iceberg/util/macros.h"
4046
#include "iceberg/util/struct_like_set.h"
4147

@@ -83,6 +89,21 @@ bool StringEquals(const ArrowArrayView* view, int64_t row_idx, std::string_view
8389
return sv.data != nullptr && std::memcmp(sv.data, target.data(), target.size()) == 0;
8490
}
8591

92+
Status ValidateDV(const DataFile& dv, std::string_view data_file_path) {
93+
ICEBERG_PRECHECK(dv.content_offset.has_value(), "Invalid DV, offset cannot be null: {}",
94+
ContentFileUtil::DVDesc(dv));
95+
ICEBERG_PRECHECK(dv.content_size_in_bytes.has_value(), "Invalid DV, length is null: {}",
96+
ContentFileUtil::DVDesc(dv));
97+
ICEBERG_PRECHECK(
98+
dv.content_size_in_bytes.value() <= std::numeric_limits<int32_t>::max(),
99+
"Can't read DV larger than 2GB: {}", dv.content_size_in_bytes.value());
100+
ICEBERG_PRECHECK(dv.referenced_data_file.has_value() &&
101+
dv.referenced_data_file.value() == data_file_path,
102+
"DV is expected to reference {}, not {}", data_file_path,
103+
dv.referenced_data_file.value_or(""));
104+
return {};
105+
}
106+
86107
} // namespace
87108

88109
DeleteLoader::DeleteLoader(std::shared_ptr<FileIO> io) : io_(std::move(io)) {}
@@ -91,7 +112,7 @@ DeleteLoader::~DeleteLoader() = default;
91112

92113
Status DeleteLoader::LoadPositionDelete(const DataFile& file, PositionDeleteIndex& index,
93114
std::string_view data_file_path) const {
94-
// TODO(gangwu): push down path filter to open the file.
115+
// TODO(gangwu): Add cache hooks, worker pool, and filter pushdown.
95116
ICEBERG_ASSIGN_OR_RAISE(auto reader, OpenDeleteFile(file, PosDeleteSchema(), io_));
96117

97118
ICEBERG_ASSIGN_OR_RAISE(auto arrow_schema, reader->Schema());
@@ -170,30 +191,40 @@ Status DeleteLoader::LoadPositionDelete(const DataFile& file, PositionDeleteInde
170191
return reader->Close();
171192
}
172193

173-
Status DeleteLoader::LoadDV(const DataFile& file, PositionDeleteIndex& index) const {
174-
return NotSupported("Loading deletion vectors is not yet supported");
175-
}
176-
177194
Result<PositionDeleteIndex> DeleteLoader::LoadPositionDeletes(
178195
std::span<const std::shared_ptr<DataFile>> delete_files,
179196
std::string_view data_file_path) const {
180-
PositionDeleteIndex index;
197+
for (const auto& file : delete_files) {
198+
ICEBERG_PRECHECK(file != nullptr, "Delete file must not be null");
199+
}
200+
201+
if (ContentFileUtil::ContainsSingleDV(delete_files)) {
202+
const auto& dv = delete_files.front();
203+
ICEBERG_RETURN_UNEXPECTED(ValidateDV(*dv, data_file_path));
204+
return DVUtil::ReadDV(dv, io_);
205+
}
206+
207+
std::vector<std::shared_ptr<DataFile>> position_delete_files;
181208

182209
for (const auto& file : delete_files) {
210+
ICEBERG_PRECHECK(!file->IsDeletionVector(),
211+
"Deletion vector cannot be mixed with position delete files: {}",
212+
file->file_path);
213+
ICEBERG_PRECHECK(file->content == DataFile::Content::kPositionDeletes,
214+
"Expected position delete file but got content type {}",
215+
ToString(file->content));
216+
183217
if (file->referenced_data_file.has_value() &&
184218
file->referenced_data_file.value() != data_file_path) {
185219
continue;
186220
}
187221

188-
if (file->IsDeletionVector()) {
189-
ICEBERG_RETURN_UNEXPECTED(LoadDV(*file, index));
190-
continue;
191-
}
222+
position_delete_files.push_back(file);
223+
}
192224

193-
ICEBERG_PRECHECK(file->content == DataFile::Content::kPositionDeletes,
194-
"Expected position delete file but got content type {}",
195-
ToString(file->content));
225+
PositionDeleteIndex index(position_delete_files);
196226

227+
for (const auto& file : position_delete_files) {
197228
ICEBERG_RETURN_UNEXPECTED(LoadPositionDelete(*file, index, data_file_path));
198229
}
199230

src/iceberg/data/delete_loader.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,6 @@ class ICEBERG_DATA_EXPORT DeleteLoader {
7070
Status LoadPositionDelete(const DataFile& file, PositionDeleteIndex& index,
7171
std::string_view data_file_path) const;
7272

73-
/// \brief Load a single deletion vector file into the index.
74-
Status LoadDV(const DataFile& file, PositionDeleteIndex& index) const;
75-
7673
std::shared_ptr<FileIO> io_;
7774
};
7875

0 commit comments

Comments
 (0)