Skip to content

Commit bd2e7dc

Browse files
authored
feat: support write for deletion vector (alibaba#158)
1 parent 1c575f7 commit bd2e7dc

16 files changed

Lines changed: 750 additions & 61 deletions

src/paimon/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ set(PAIMON_CORE_SRCS
170170
core/catalog/identifier.cpp
171171
core/core_options.cpp
172172
core/deletionvectors/deletion_vector.cpp
173+
core/deletionvectors/deletion_file_writer.cpp
174+
core/deletionvectors/bitmap_deletion_vector.cpp
175+
core/deletionvectors/deletion_vectors_index_file.cpp
176+
core/deletionvectors/deletion_vector_index_file_writer.cpp
173177
core/global_index/global_index_evaluator_impl.cpp
174178
core/global_index/global_index_scan.cpp
175179
core/global_index/global_index_scan_impl.cpp
@@ -469,7 +473,6 @@ if(PAIMON_BUILD_TESTS)
469473
add_paimon_test(core_test
470474
SOURCES
471475
core/append/append_only_writer_test.cpp
472-
core/append/bucketed_append_compact_manager_test.cpp
473476
core/casting/cast_executor_factory_test.cpp
474477
core/casting/cast_executor_test.cpp
475478
core/casting/casted_row_test.cpp
@@ -481,7 +484,9 @@ if(PAIMON_BUILD_TESTS)
481484
core/catalog/identifier_test.cpp
482485
core/core_options_test.cpp
483486
core/deletionvectors/apply_deletion_vector_batch_reader_test.cpp
487+
core/deletionvectors/bitmap_deletion_vector_test.cpp
484488
core/deletionvectors/deletion_vector_test.cpp
489+
core/deletionvectors/deletion_vectors_index_file_test.cpp
485490
core/index/index_in_data_file_dir_path_factory_test.cpp
486491
core/index/deletion_vector_meta_test.cpp
487492
core/index/index_file_meta_serializer_test.cpp

src/paimon/common/io/data_output_stream.cpp

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@
1616

1717
#include "paimon/common/io/data_output_stream.h"
1818

19-
#include <cassert>
20-
#include <type_traits>
21-
2219
#include "fmt/format.h"
23-
#include "paimon/common/utils/math.h"
24-
#include "paimon/fs/file_system.h"
2520
#include "paimon/memory/bytes.h"
2621
#include "paimon/result.h"
2722

@@ -30,20 +25,6 @@ DataOutputStream::DataOutputStream(const std::shared_ptr<OutputStream>& output_s
3025
: output_stream_(output_stream) {
3126
assert(output_stream_);
3227
}
33-
template <typename T>
34-
Status DataOutputStream::WriteValue(const T& value) {
35-
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
36-
T write_value = value;
37-
if (NeedSwap()) {
38-
write_value = EndianSwapValue(value);
39-
}
40-
int32_t write_length = sizeof(T);
41-
PAIMON_ASSIGN_OR_RAISE(
42-
int32_t actual_write_length,
43-
output_stream_->Write(reinterpret_cast<char*>(&write_value), write_length));
44-
PAIMON_RETURN_NOT_OK(AssertWriteLength(write_length, actual_write_length));
45-
return Status::OK();
46-
}
4728

4829
Status DataOutputStream::WriteBytes(const std::shared_ptr<Bytes>& bytes) {
4930
int32_t write_length = bytes->size();

src/paimon/common/io/data_output_stream.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515
*/
1616

1717
#pragma once
18+
19+
#include <cassert>
1820
#include <cstdint>
1921
#include <memory>
2022
#include <string>
23+
#include <type_traits>
2124

25+
#include "paimon/common/utils/math.h"
26+
#include "paimon/fs/file_system.h"
2227
#include "paimon/io/byte_order.h"
28+
#include "paimon/result.h"
2329
#include "paimon/status.h"
2430

2531
namespace paimon {
@@ -33,7 +39,19 @@ class PAIMON_EXPORT DataOutputStream {
3339
explicit DataOutputStream(const std::shared_ptr<OutputStream>& output_stream);
3440

3541
template <typename T>
36-
Status WriteValue(const T& value);
42+
Status WriteValue(const T& value) {
43+
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
44+
T write_value = value;
45+
if (NeedSwap()) {
46+
write_value = EndianSwapValue(value);
47+
}
48+
int32_t write_length = sizeof(T);
49+
PAIMON_ASSIGN_OR_RAISE(
50+
int32_t actual_write_length,
51+
output_stream_->Write(reinterpret_cast<char*>(&write_value), write_length));
52+
PAIMON_RETURN_NOT_OK(AssertWriteLength(write_length, actual_write_length));
53+
return Status::OK();
54+
}
3755

3856
Status WriteBytes(const std::shared_ptr<Bytes>& bytes);
3957

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/core/deletionvectors/bitmap_deletion_vector.h"
18+
19+
#include "arrow/util/crc32.h"
20+
#include "fmt/format.h"
21+
#include "paimon/common/io/memory_segment_output_stream.h"
22+
#include "paimon/io/byte_array_input_stream.h"
23+
#include "paimon/io/data_input_stream.h"
24+
25+
namespace paimon {
26+
27+
Result<int32_t> BitmapDeletionVector::SerializeTo(const std::shared_ptr<MemoryPool>& pool,
28+
DataOutputStream* out) {
29+
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<Bytes> data, SerializeToBytes(pool));
30+
int64_t size = data->size();
31+
if (size < 0 || size > std::numeric_limits<int32_t>::max()) {
32+
return Status::Invalid("BitmapDeletionVector serialize size out of range: ", size);
33+
}
34+
PAIMON_RETURN_NOT_OK(out->WriteValue<int32_t>(static_cast<int32_t>(size)));
35+
PAIMON_RETURN_NOT_OK(out->WriteBytes(data));
36+
uint32_t crc32 = 0;
37+
crc32 = arrow::internal::crc32(crc32, data->data(), size);
38+
PAIMON_RETURN_NOT_OK(out->WriteValue<int32_t>(static_cast<int32_t>(crc32)));
39+
return static_cast<int32_t>(size);
40+
}
41+
42+
Result<PAIMON_UNIQUE_PTR<Bytes>> BitmapDeletionVector::SerializeToBytes(
43+
const std::shared_ptr<MemoryPool>& pool) {
44+
std::shared_ptr<Bytes> bitmap_bytes = roaring_bitmap_.Serialize(pool.get());
45+
if (bitmap_bytes == nullptr) {
46+
assert(bitmap_bytes);
47+
return Status::Invalid("roaring bitmap serialize failed");
48+
}
49+
MemorySegmentOutputStream output(MemorySegmentOutputStream::DEFAULT_SEGMENT_SIZE, pool);
50+
output.WriteValue<int32_t>(MAGIC_NUMBER);
51+
output.WriteBytes(bitmap_bytes);
52+
return MemorySegmentUtils::CopyToBytes(output.Segments(), /*offset=*/0, output.CurrentSize(),
53+
pool.get());
54+
}
55+
56+
Status BitmapDeletionVector::CheckPosition(int64_t position) const {
57+
if (position > RoaringBitmap32::MAX_VALUE) {
58+
return Status::Invalid(fmt::format(
59+
"The file has too many rows, RoaringBitmap32 only supports files with row count "
60+
"not exceeding {}.",
61+
RoaringBitmap32::MAX_VALUE));
62+
}
63+
return Status::OK();
64+
}
65+
66+
Result<PAIMON_UNIQUE_PTR<DeletionVector>> BitmapDeletionVector::Deserialize(const char* buffer,
67+
int32_t length,
68+
MemoryPool* pool) {
69+
auto in = std::make_shared<ByteArrayInputStream>(buffer, length);
70+
DataInputStream input(in);
71+
PAIMON_ASSIGN_OR_RAISE(int32_t magic_num, input.ReadValue<int32_t>());
72+
if (magic_num != MAGIC_NUMBER) {
73+
return Status::Invalid(fmt::format(
74+
"Unable to deserialize deletion vector, invalid magic number: {}", magic_num));
75+
}
76+
RoaringBitmap32 roaring_bitmap;
77+
PAIMON_RETURN_NOT_OK(roaring_bitmap.Deserialize(buffer + MAGIC_NUMBER_SIZE_BYTES,
78+
length - MAGIC_NUMBER_SIZE_BYTES));
79+
return pool->AllocateUnique<BitmapDeletionVector>(roaring_bitmap);
80+
}
81+
82+
} // namespace paimon

src/paimon/core/deletionvectors/bitmap_deletion_vector.h

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@
1818

1919
#include <memory>
2020

21-
#include "paimon/common/io/memory_segment_output_stream.h"
21+
#include "paimon/common/io/data_output_stream.h"
2222
#include "paimon/core/deletionvectors/deletion_vector.h"
23-
#include "paimon/io/byte_array_input_stream.h"
24-
#include "paimon/io/data_input_stream.h"
2523
#include "paimon/utils/roaring_bitmap32.h"
24+
2625
namespace paimon {
26+
2727
/// A `DeletionVector` based on `RoaringBitmap32`, it only supports files with row count
2828
/// not exceeding `RoaringBitmap32::MAX_VALUE`.
2929
class BitmapDeletionVector : public DeletionVector {
3030
public:
31+
static constexpr int32_t MAGIC_NUMBER = 1581511376;
32+
static constexpr int32_t MAGIC_NUMBER_SIZE_BYTES = 4;
33+
3134
explicit BitmapDeletionVector(const RoaringBitmap32& roaring_bitmap)
3235
: roaring_bitmap_(roaring_bitmap) {}
3336

@@ -51,51 +54,27 @@ class BitmapDeletionVector : public DeletionVector {
5154
return roaring_bitmap_.IsEmpty();
5255
}
5356

54-
Result<PAIMON_UNIQUE_PTR<Bytes>> SerializeToBytes(
55-
const std::shared_ptr<MemoryPool>& pool) override {
56-
std::shared_ptr<Bytes> bitmap_bytes = roaring_bitmap_.Serialize(pool.get());
57-
if (bitmap_bytes == nullptr) {
58-
assert(bitmap_bytes);
59-
return Status::Invalid("roaring bitmap serialize failed");
60-
}
61-
MemorySegmentOutputStream output(/*segment_size=*/1024, pool);
62-
output.WriteValue<int32_t>(MAGIC_NUMBER);
63-
output.WriteBytes(bitmap_bytes);
64-
return MemorySegmentUtils::CopyToBytes(output.Segments(), /*offset=*/0,
65-
output.CurrentSize(), pool.get());
57+
int64_t GetCardinality() const override {
58+
return roaring_bitmap_.Cardinality();
6659
}
6760

61+
Result<int32_t> SerializeTo(const std::shared_ptr<MemoryPool>& pool,
62+
DataOutputStream* out) override;
63+
64+
Result<PAIMON_UNIQUE_PTR<Bytes>> SerializeToBytes(
65+
const std::shared_ptr<MemoryPool>& pool) override;
66+
6867
const RoaringBitmap32* GetBitmap() const {
6968
return &roaring_bitmap_;
7069
}
7170

7271
static Result<PAIMON_UNIQUE_PTR<DeletionVector>> Deserialize(const char* buffer, int32_t length,
73-
MemoryPool* pool) {
74-
auto in = std::make_shared<ByteArrayInputStream>(buffer, length);
75-
DataInputStream input(in);
76-
PAIMON_ASSIGN_OR_RAISE(int32_t magic_num, input.ReadValue<int32_t>());
77-
if (magic_num != MAGIC_NUMBER) {
78-
return Status::Invalid("Invalid magic number: ", std::to_string(magic_num));
79-
}
80-
RoaringBitmap32 roaring_bitmap;
81-
PAIMON_RETURN_NOT_OK(roaring_bitmap.Deserialize(buffer + sizeof(MAGIC_NUMBER),
82-
length - sizeof(MAGIC_NUMBER)));
83-
return pool->AllocateUnique<BitmapDeletionVector>(roaring_bitmap);
84-
}
85-
86-
static constexpr int32_t MAGIC_NUMBER = 1581511376;
72+
MemoryPool* pool);
8773

8874
private:
89-
Status CheckPosition(int64_t position) const {
90-
if (position > RoaringBitmap32::MAX_VALUE) {
91-
return Status::Invalid(
92-
"The file has too many rows, RoaringBitmap32 only supports files with row count "
93-
"not exceeding 2147483647.");
94-
}
95-
return Status::OK();
96-
}
75+
Status CheckPosition(int64_t position) const;
9776

98-
private:
9977
RoaringBitmap32 roaring_bitmap_;
10078
};
79+
10180
} // namespace paimon
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/core/deletionvectors/bitmap_deletion_vector.h"
18+
19+
#include <set>
20+
21+
#include "gtest/gtest.h"
22+
#include "paimon/common/io/memory_segment_output_stream.h"
23+
#include "paimon/common/utils/path_util.h"
24+
#include "paimon/fs/file_system_factory.h"
25+
#include "paimon/testing/utils/testharness.h"
26+
27+
namespace paimon::test {
28+
29+
TEST(BitmapDeletionVectorTest, BasicOperations) {
30+
RoaringBitmap32 roaring;
31+
BitmapDeletionVector dv(roaring);
32+
ASSERT_TRUE(dv.IsEmpty());
33+
for (int32_t i = 0; i < 2000; i += 2) {
34+
ASSERT_OK(dv.Delete(i));
35+
}
36+
ASSERT_EQ(dv.GetCardinality(), 1000);
37+
for (int32_t i = 0; i < 2000; ++i) {
38+
if (i % 2 == 0) {
39+
ASSERT_TRUE(dv.IsDeleted(i).value());
40+
} else {
41+
ASSERT_FALSE(dv.IsDeleted(i).value());
42+
}
43+
}
44+
}
45+
46+
TEST(BitmapDeletionVectorTest, CheckedDelete) {
47+
RoaringBitmap32 roaring;
48+
BitmapDeletionVector dv(roaring);
49+
ASSERT_TRUE(dv.CheckedDelete(42).value());
50+
ASSERT_FALSE(dv.CheckedDelete(42).value());
51+
ASSERT_TRUE(dv.IsDeleted(42).value());
52+
}
53+
54+
TEST(BitmapDeletionVectorTest, SerializeAndDeserialize) {
55+
RoaringBitmap32 roaring;
56+
for (int32_t i = 0; i < 100; i += 3) {
57+
roaring.Add(i);
58+
}
59+
BitmapDeletionVector dv(roaring);
60+
auto pool = GetDefaultPool();
61+
ASSERT_OK_AND_ASSIGN(auto bytes, dv.SerializeToBytes(pool));
62+
ASSERT_OK_AND_ASSIGN(
63+
auto dv2, BitmapDeletionVector::Deserialize(bytes->data(), bytes->size(), pool.get()));
64+
for (int32_t i = 0; i < 100; ++i) {
65+
ASSERT_EQ(dv.IsDeleted(i).value(), dv2->IsDeleted(i).value());
66+
}
67+
}
68+
69+
TEST(BitmapDeletionVectorTest, SerializeToOutputStream) {
70+
RoaringBitmap32 roaring;
71+
for (int32_t i = 0; i < 50; i += 2) {
72+
roaring.Add(i);
73+
}
74+
BitmapDeletionVector dv(roaring);
75+
auto pool = GetDefaultPool();
76+
auto dir = UniqueTestDirectory::Create();
77+
auto path = PathUtil::JoinPath(dir->Str(), "dv");
78+
ASSERT_OK_AND_ASSIGN(auto fs, FileSystemFactory::Get("local", path, {}));
79+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> output_stream,
80+
fs->Create(path, /*overwrite=*/false));
81+
DataOutputStream out(output_stream);
82+
ASSERT_OK_AND_ASSIGN(int64_t size, dv.SerializeTo(pool, &out));
83+
ASSERT_OK(output_stream->Flush());
84+
ASSERT_OK(output_stream->Close());
85+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> input_stream, fs->Open(path));
86+
DataInputStream in(input_stream);
87+
ASSERT_OK_AND_ASSIGN(int32_t byte_len, in.ReadValue<int32_t>());
88+
auto bytes = Bytes::AllocateBytes(byte_len, pool.get());
89+
ASSERT_OK(in.Read(bytes->data(), bytes->size()));
90+
const char* data = bytes->data();
91+
ASSERT_EQ(bytes->size(), size);
92+
ASSERT_OK_AND_ASSIGN(auto dv2,
93+
BitmapDeletionVector::Deserialize(data, bytes->size(), pool.get()));
94+
for (int32_t i = 0; i < 50; ++i) {
95+
ASSERT_EQ(dv.IsDeleted(i).value(), dv2->IsDeleted(i).value());
96+
}
97+
}
98+
99+
TEST(BitmapDeletionVectorTest, GetCardinality) {
100+
RoaringBitmap32 empty;
101+
BitmapDeletionVector dv_empty(empty);
102+
ASSERT_EQ(dv_empty.GetCardinality(), 0);
103+
104+
RoaringBitmap32 cont;
105+
for (int i = 0; i < 100; ++i) cont.Add(i);
106+
BitmapDeletionVector dv_cont(cont);
107+
ASSERT_EQ(dv_cont.GetCardinality(), 100);
108+
109+
RoaringBitmap32 gap;
110+
for (int i = 0; i < 1000; i += 10) gap.Add(i);
111+
BitmapDeletionVector dv_gap(gap);
112+
ASSERT_EQ(dv_gap.GetCardinality(), 100);
113+
114+
RoaringBitmap32 del;
115+
for (int i = 0; i < 10; ++i) del.Add(i);
116+
BitmapDeletionVector dv_del(del);
117+
ASSERT_EQ(dv_del.GetCardinality(), 10);
118+
ASSERT_OK(dv_del.Delete(100));
119+
ASSERT_EQ(dv_del.GetCardinality(), 11);
120+
}
121+
122+
} // namespace paimon::test

0 commit comments

Comments
 (0)