Skip to content

Commit ae0b687

Browse files
authored
Merge branch 'main' into create_lookup_levels
2 parents c1e6cd3 + 2b2c05f commit ae0b687

5 files changed

Lines changed: 609 additions & 1 deletion

File tree

src/paimon/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ if(PAIMON_BUILD_TESTS)
380380
common/file_index/bitmap/apply_bitmap_index_batch_reader_test.cpp
381381
common/file_index/bsi/bit_slice_index_bitmap_file_index_test.cpp
382382
common/file_index/bsi/bit_slice_index_roaring_bitmap_test.cpp
383+
common/file_index/rangebitmap/bit_slice_index_bitmap_test.cpp
383384
common/file_index/rangebitmap/dictionary/chunked_dictionary_test.cpp
384385
common/file_index/bloomfilter/bloom_filter_file_index_test.cpp
385386
common/file_index/bloomfilter/fast_hash_test.cpp

src/paimon/common/file_index/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ set(PAIMON_FILE_INDEX_SRC
2727
rangebitmap/dictionary/chunked_dictionary.cpp
2828
rangebitmap/dictionary/fixed_length_chunk.cpp
2929
rangebitmap/dictionary/key_factory.cpp
30-
rangebitmap/utils/literal_serialization_utils.cpp)
30+
rangebitmap/utils/literal_serialization_utils.cpp
31+
rangebitmap/bit_slice_index_bitmap.cpp)
3132

3233
add_paimon_lib(paimon_file_index
3334
SOURCES
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
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/common/file_index/rangebitmap/bit_slice_index_bitmap.h"
18+
19+
#include <fmt/format.h>
20+
21+
#include <algorithm>
22+
#include <cmath>
23+
#include <string>
24+
25+
#include "paimon/common/io/memory_segment_output_stream.h"
26+
#include "paimon/common/memory/memory_segment_utils.h"
27+
#include "paimon/io/data_input_stream.h"
28+
#include "paimon/result.h"
29+
#include "paimon/status.h"
30+
31+
namespace paimon {
32+
33+
Result<std::unique_ptr<BitSliceIndexBitmap>> BitSliceIndexBitmap::Create(
34+
const std::shared_ptr<InputStream>& input_stream, int32_t offset,
35+
const std::shared_ptr<MemoryPool>& pool) {
36+
const auto data_in = std::make_unique<DataInputStream>(input_stream);
37+
PAIMON_RETURN_NOT_OK(data_in->Seek(offset));
38+
PAIMON_ASSIGN_OR_RAISE(int32_t header_length, data_in->ReadValue<int32_t>());
39+
PAIMON_ASSIGN_OR_RAISE(int8_t version, data_in->ReadValue<int8_t>());
40+
if (version != kCurrentVersion) {
41+
return Status::Invalid(fmt::format("Unknown BitSliceBitmap Version: {}", version));
42+
}
43+
PAIMON_ASSIGN_OR_RAISE(int8_t slices_size, data_in->ReadValue<int8_t>());
44+
PAIMON_ASSIGN_OR_RAISE(int32_t ebm_size, data_in->ReadValue<int32_t>());
45+
PAIMON_ASSIGN_OR_RAISE(int32_t indexes_length, data_in->ReadValue<int32_t>());
46+
auto indexes = Bytes::AllocateBytes(indexes_length, pool.get());
47+
PAIMON_RETURN_NOT_OK(data_in->Read(indexes->data(), indexes_length));
48+
return std::unique_ptr<BitSliceIndexBitmap>(new BitSliceIndexBitmap(
49+
indexes_length, std::move(indexes), ebm_size, slices_size, input_stream,
50+
static_cast<int32_t>(offset + sizeof(int32_t) + header_length), pool));
51+
}
52+
53+
static int32_t NumberOfLeadingZeros(int64_t value) {
54+
if (value == 0) {
55+
return 64;
56+
}
57+
return __builtin_clzll(static_cast<uint64_t>(value));
58+
}
59+
60+
static int32_t NumberOfTrailingZeros(int64_t value) {
61+
if (value == 0) {
62+
return 64;
63+
}
64+
return __builtin_ctzll(static_cast<uint64_t>(value));
65+
}
66+
67+
BitSliceIndexBitmap::BitSliceIndexBitmap(int32_t indexes_length, PAIMON_UNIQUE_PTR<Bytes> indexes,
68+
int32_t ebm_length, int32_t slices_size,
69+
const std::shared_ptr<InputStream>& input_stream,
70+
int32_t body_offset,
71+
const std::shared_ptr<MemoryPool>& pool)
72+
: pool_(pool),
73+
bit_slices_(std::vector<std::optional<RoaringBitmap32>>(slices_size, {std::nullopt})),
74+
ebm_({std::nullopt}),
75+
input_stream_(input_stream),
76+
body_offset_(body_offset),
77+
indexes_(std::move(indexes)),
78+
ebm_length_(ebm_length),
79+
indexes_length_(indexes_length),
80+
batch_loaded_(false) {}
81+
82+
Result<const RoaringBitmap32*> BitSliceIndexBitmap::GetExistenceBitmap() {
83+
if (!ebm_.has_value()) {
84+
PAIMON_RETURN_NOT_OK(input_stream_->Seek(body_offset_, FS_SEEK_SET));
85+
const auto bytes = Bytes::AllocateBytes(ebm_length_, pool_.get());
86+
PAIMON_RETURN_NOT_OK(input_stream_->Read(bytes->data(), ebm_length_));
87+
RoaringBitmap32 bitmap;
88+
PAIMON_RETURN_NOT_OK(bitmap.Deserialize(bytes->data(), ebm_length_));
89+
ebm_ = std::move(bitmap);
90+
}
91+
return &ebm_.value();
92+
}
93+
94+
Result<const RoaringBitmap32*> BitSliceIndexBitmap::GetSliceBitmap(int32_t idx) {
95+
if (!bit_slices_[idx].has_value()) {
96+
PAIMON_RETURN_NOT_OK(LoadSlices(idx, idx + 1));
97+
}
98+
return &bit_slices_[idx].value();
99+
}
100+
101+
Status BitSliceIndexBitmap::LoadSlices(int32_t start, int32_t end) {
102+
if (start == end) {
103+
return Status::OK();
104+
}
105+
auto indexes_stream = std::make_shared<ByteArrayInputStream>(indexes_->data(), indexes_length_);
106+
const auto data_in = std::make_unique<DataInputStream>(indexes_stream);
107+
const auto position = static_cast<int32_t>(2 * sizeof(int32_t) * start);
108+
PAIMON_RETURN_NOT_OK(data_in->Seek(position));
109+
PAIMON_ASSIGN_OR_RAISE(int32_t offset, data_in->ReadValue<int32_t>());
110+
PAIMON_ASSIGN_OR_RAISE(int32_t length, data_in->ReadValue<int32_t>());
111+
std::vector<int32_t> lengths(end);
112+
lengths[start] = length;
113+
114+
for (int32_t i = start + 1; i < end; ++i) {
115+
PAIMON_RETURN_NOT_OK(data_in->ReadValue<int32_t>());
116+
PAIMON_ASSIGN_OR_RAISE(int32_t slice_length, data_in->ReadValue<int32_t>());
117+
lengths[i] = slice_length;
118+
length += slice_length;
119+
}
120+
PAIMON_RETURN_NOT_OK(input_stream_->Seek(body_offset_ + ebm_length_ + offset, FS_SEEK_SET));
121+
const auto bytes = Bytes::AllocateBytes(length, pool_.get());
122+
PAIMON_RETURN_NOT_OK(input_stream_->Read(bytes->data(), length));
123+
int32_t byte_position = 0;
124+
for (int32_t i = start; i < end; ++i) {
125+
const int32_t slice_length = lengths[i];
126+
RoaringBitmap32 bitmap;
127+
PAIMON_RETURN_NOT_OK(bitmap.Deserialize(bytes->data() + byte_position, slice_length));
128+
bit_slices_[i] = std::move(bitmap);
129+
byte_position += slice_length;
130+
}
131+
return Status::OK();
132+
}
133+
134+
Result<RoaringBitmap32> BitSliceIndexBitmap::Eq(int32_t code) {
135+
PAIMON_ASSIGN_OR_RAISE(const RoaringBitmap32* existence_bitmap, GetExistenceBitmap());
136+
auto equal = RoaringBitmap32(*existence_bitmap);
137+
if (!batch_loaded_) {
138+
PAIMON_RETURN_NOT_OK(LoadSlices(0, static_cast<int32_t>(bit_slices_.size())));
139+
batch_loaded_ = true;
140+
}
141+
// process slices from LSB to MSB
142+
for (int i = 0; i < static_cast<int32_t>(bit_slices_.size()); ++i) {
143+
PAIMON_ASSIGN_OR_RAISE(const RoaringBitmap32* slice_bitmap, GetSliceBitmap(i));
144+
if ((code >> i & 1) == 1) {
145+
// Intersect with rows that also have this bit turned on
146+
equal &= *slice_bitmap;
147+
} else {
148+
// Exclude rows that should NOT have bit turned on
149+
equal -= *slice_bitmap;
150+
}
151+
}
152+
return equal;
153+
}
154+
155+
Result<RoaringBitmap32> BitSliceIndexBitmap::Gt(int32_t code) {
156+
if (code < 0) {
157+
return IsNotNull({});
158+
}
159+
PAIMON_ASSIGN_OR_RAISE(RoaringBitmap32 found_set, IsNotNull({}));
160+
if (found_set.IsEmpty()) {
161+
return RoaringBitmap32();
162+
}
163+
auto state = RoaringBitmap32{};
164+
auto state_inited = false;
165+
// skip ones from LSB
166+
const auto start = NumberOfTrailingZeros(~code);
167+
if (!batch_loaded_) {
168+
PAIMON_RETURN_NOT_OK(LoadSlices(start, static_cast<int32_t>(bit_slices_.size())));
169+
batch_loaded_ = true;
170+
}
171+
// process slices from LSB to MSB
172+
for (int i = start; i < static_cast<int32_t>(bit_slices_.size()); ++i) {
173+
if (!state_inited) {
174+
PAIMON_ASSIGN_OR_RAISE(const RoaringBitmap32* slice_ptr, GetSliceBitmap(i));
175+
// current i_th bit of code has to be 0 after skip,
176+
// so the state is initialized to rows that is greater than the code's 0 to i bits
177+
state = *slice_ptr;
178+
state_inited = true;
179+
continue;
180+
}
181+
PAIMON_ASSIGN_OR_RAISE(const RoaringBitmap32* slice_ptr, GetSliceBitmap(i));
182+
if ((code >> i & 1) == 1) {
183+
// when code is 1 at the higher bit
184+
// only rows that is 1 at the higher bit remain
185+
state &= *slice_ptr;
186+
} else {
187+
// when code is 0 at the higher bit
188+
// union rows that is 1 at the higher bit
189+
state |= *slice_ptr;
190+
}
191+
}
192+
if (!state_inited) {
193+
return RoaringBitmap32();
194+
}
195+
return state &= found_set;
196+
}
197+
198+
Result<RoaringBitmap32> BitSliceIndexBitmap::Gte(int32_t code) {
199+
if (code < 0) {
200+
return IsNotNull({});
201+
}
202+
return Gt(code - 1);
203+
}
204+
205+
Result<RoaringBitmap32> BitSliceIndexBitmap::IsNotNull(const RoaringBitmap32& found_set) {
206+
PAIMON_ASSIGN_OR_RAISE(const RoaringBitmap32* existence_bitmap, GetExistenceBitmap());
207+
return found_set.IsEmpty() ? *existence_bitmap
208+
: RoaringBitmap32::And(*existence_bitmap, found_set);
209+
}
210+
Result<std::unique_ptr<BitSliceIndexBitmap::Appender>> BitSliceIndexBitmap::Appender::Create(
211+
int32_t min, int32_t max, const std::shared_ptr<MemoryPool>& pool) {
212+
if (min > max) {
213+
return Status::Invalid(fmt::format("min {} > max {}", min, max));
214+
}
215+
if (min < 0) {
216+
return Status::Invalid(fmt::format("min {} cannot be negative", min));
217+
}
218+
return std::unique_ptr<Appender>(new Appender(min, max, pool));
219+
}
220+
221+
BitSliceIndexBitmap::Appender::Appender(int32_t min, int32_t max,
222+
const std::shared_ptr<MemoryPool>& pool)
223+
: pool_(pool), min_(min), max_(max) {
224+
ebm_ = RoaringBitmap32{};
225+
const auto slices_size = std::max(64 - NumberOfLeadingZeros(max), 1);
226+
slices_.resize(slices_size);
227+
}
228+
229+
Status BitSliceIndexBitmap::Appender::Append(int32_t key, int32_t value) {
230+
if (key < 0) {
231+
return Status::Invalid(fmt::format("key: {} cannot be negative", key));
232+
}
233+
if (value < 0) {
234+
return Status::Invalid(fmt::format("value: {} cannot be negative", value));
235+
}
236+
if (value < min_ || value > max_) {
237+
return Status::Invalid(fmt::format("value: {} not in range [{}, {}]", value, min_, max_));
238+
}
239+
int bits = value;
240+
while (bits != 0) {
241+
slices_[NumberOfTrailingZeros(bits)].Add(key);
242+
bits &= (bits - 1);
243+
}
244+
ebm_.Add(key);
245+
return Status::OK();
246+
}
247+
248+
Result<PAIMON_UNIQUE_PTR<Bytes>> BitSliceIndexBitmap::Appender::Serialize() const {
249+
const auto indexes_length = static_cast<int32_t>(2 * sizeof(int32_t) * slices_.size());
250+
const auto ebm_bytes = ebm_.Serialize(pool_.get());
251+
const auto ebm_length = static_cast<int32_t>(ebm_bytes->size());
252+
int32_t header_size = 0;
253+
header_size += sizeof(int8_t); // version
254+
header_size += sizeof(int8_t); // slices size
255+
header_size += sizeof(int32_t); // ebm length
256+
header_size += sizeof(int32_t); // indexes length
257+
header_size += indexes_length;
258+
int32_t offset = 0;
259+
const auto data_output_stream = std::make_unique<MemorySegmentOutputStream>(
260+
MemorySegmentOutputStream::DEFAULT_SEGMENT_SIZE, pool_);
261+
auto slices_bytes_vector = std::vector<PAIMON_UNIQUE_PTR<Bytes>>{};
262+
auto indexes_vector = std::vector<std::pair<int32_t, int32_t>>{};
263+
for (const auto& slice : slices_) {
264+
auto slice_bytes = slice.Serialize(pool_.get());
265+
const auto length = static_cast<int32_t>(slice_bytes->size());
266+
indexes_vector.emplace_back(offset, length);
267+
offset += length;
268+
slices_bytes_vector.emplace_back(std::move(slice_bytes));
269+
}
270+
data_output_stream->WriteValue<int32_t>(header_size);
271+
data_output_stream->WriteValue<int8_t>(kCurrentVersion);
272+
data_output_stream->WriteValue<int8_t>(static_cast<int8_t>(slices_.size()));
273+
data_output_stream->WriteValue<int32_t>(ebm_length);
274+
data_output_stream->WriteValue<int32_t>(indexes_length);
275+
for (const auto& [slice_offset, length] : indexes_vector) {
276+
data_output_stream->WriteValue<int32_t>(slice_offset);
277+
data_output_stream->WriteValue<int32_t>(length);
278+
}
279+
data_output_stream->Write(ebm_bytes->data(), ebm_length);
280+
for (const auto& slice_bytes : slices_bytes_vector) {
281+
data_output_stream->Write(slice_bytes->data(), slice_bytes->size());
282+
}
283+
return MemorySegmentUtils::CopyToBytes(data_output_stream->Segments(), 0,
284+
static_cast<int32_t>(data_output_stream->CurrentSize()),
285+
pool_.get());
286+
}
287+
} // namespace paimon
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
#pragma once
18+
19+
#include <memory>
20+
#include <optional>
21+
22+
#include "paimon/io/byte_array_input_stream.h"
23+
#include "paimon/memory/bytes.h"
24+
#include "paimon/result.h"
25+
#include "paimon/status.h"
26+
#include "paimon/utils/roaring_bitmap32.h"
27+
28+
namespace paimon {
29+
30+
class BitSliceIndexBitmap {
31+
public:
32+
static Result<std::unique_ptr<BitSliceIndexBitmap>> Create(
33+
const std::shared_ptr<InputStream>& input_stream, int32_t offset,
34+
const std::shared_ptr<MemoryPool>& pool);
35+
36+
Result<RoaringBitmap32> Eq(int32_t code);
37+
38+
Result<RoaringBitmap32> Gt(int32_t code);
39+
40+
Result<RoaringBitmap32> Gte(int32_t code);
41+
42+
Result<RoaringBitmap32> IsNotNull(const RoaringBitmap32& found_set);
43+
44+
class Appender {
45+
public:
46+
static Result<std::unique_ptr<Appender>> Create(int32_t min, int32_t max,
47+
const std::shared_ptr<MemoryPool>& pool);
48+
Status Append(int32_t key, int32_t value);
49+
Result<PAIMON_UNIQUE_PTR<Bytes>> Serialize() const;
50+
51+
private:
52+
Appender(int32_t min, int32_t max, const std::shared_ptr<MemoryPool>& pool);
53+
54+
private:
55+
std::shared_ptr<MemoryPool> pool_;
56+
int32_t min_;
57+
int32_t max_;
58+
RoaringBitmap32 ebm_;
59+
std::vector<RoaringBitmap32> slices_;
60+
};
61+
62+
public:
63+
static constexpr int32_t kCurrentVersion = 1;
64+
65+
private:
66+
BitSliceIndexBitmap(int32_t indexes_length, PAIMON_UNIQUE_PTR<Bytes> indexes,
67+
int32_t ebm_length, int32_t slices_size,
68+
const std::shared_ptr<InputStream>& input_stream, int32_t body_offset,
69+
const std::shared_ptr<MemoryPool>& pool);
70+
71+
/// Batch load slices from start to end
72+
Status LoadSlices(int32_t start, int32_t end);
73+
74+
Result<const RoaringBitmap32*> GetSliceBitmap(int32_t idx);
75+
76+
Result<const RoaringBitmap32*> GetExistenceBitmap();
77+
78+
private:
79+
std::shared_ptr<MemoryPool> pool_;
80+
// bit_slices_ stores LSB to MSB starting from index 0
81+
std::vector<std::optional<RoaringBitmap32>> bit_slices_;
82+
std::optional<RoaringBitmap32> ebm_;
83+
std::shared_ptr<InputStream> input_stream_;
84+
int32_t body_offset_;
85+
PAIMON_UNIQUE_PTR<Bytes> indexes_;
86+
int32_t ebm_length_;
87+
int32_t indexes_length_;
88+
// only do batch LoadSlices once to prevent duplicated IO
89+
bool batch_loaded_;
90+
};
91+
92+
} // namespace paimon

0 commit comments

Comments
 (0)