Skip to content

Commit d184ecf

Browse files
authored
test(btree): add unit test for btree (#266)
1 parent 887e356 commit d184ecf

5 files changed

Lines changed: 207 additions & 1 deletion

File tree

src/paimon/common/global_index/btree/btree_global_index_integration_test.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,11 @@ TEST_P(BTreeGlobalIndexIntegrationTest, WriteAndReadIntData) {
240240
Literal literal_5(5);
241241
ASSERT_OK_AND_ASSIGN(result, reader->VisitGreaterThan(literal_5));
242242
CheckResult(result, {});
243+
244+
// GreaterThan 1 (min_key) -> skip entries with value==1 via from_inclusive=false
245+
Literal literal_1(1);
246+
ASSERT_OK_AND_ASSIGN(result, reader->VisitGreaterThan(literal_1));
247+
CheckResult(result, {3, 4, 6, 7, 8, 9, 10});
243248
}
244249

245250
// --- VisitGreaterOrEqual ---
@@ -361,6 +366,10 @@ TEST_P(BTreeGlobalIndexIntegrationTest, WriteAndReadStringData) {
361366
Literal lit_cherry(FieldType::STRING, "cherry", 6);
362367
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitGreaterThan(lit_cherry));
363368
CheckResult(result, {8});
369+
370+
Literal lit_apple(FieldType::STRING, "apple", 5);
371+
ASSERT_OK_AND_ASSIGN(result, reader->VisitGreaterThan(lit_apple));
372+
CheckResult(result, {1, 3, 4, 6, 7, 8});
364373
}
365374

366375
// --- VisitGreaterOrEqual ---
@@ -402,6 +411,17 @@ TEST_P(BTreeGlobalIndexIntegrationTest, WriteAndReadStringData) {
402411
Literal lit_z(FieldType::STRING, "z", 1);
403412
ASSERT_OK_AND_ASSIGN(result, reader->VisitStartsWith(lit_z));
404413
CheckResult(result, {});
414+
415+
// StartsWith "" (empty prefix) -> all non-null rows
416+
Literal lit_empty(FieldType::STRING, "", 0);
417+
ASSERT_OK_AND_ASSIGN(result, reader->VisitStartsWith(lit_empty));
418+
CheckResult(result, {0, 1, 3, 4, 6, 7, 8});
419+
420+
// StartsWith "\xFF\xFF" (all 0xFF bytes, overflow branch) -> no match
421+
std::string xff_prefix = "\xFF\xFF";
422+
Literal lit_xff(FieldType::STRING, xff_prefix.data(), xff_prefix.size());
423+
ASSERT_OK_AND_ASSIGN(result, reader->VisitStartsWith(lit_xff));
424+
CheckResult(result, {});
405425
}
406426

407427
// --- VisitEndsWith (falls back to AllNonNullRows) ---
@@ -1448,6 +1468,27 @@ TEST_P(BTreeGlobalIndexIntegrationTest, WriteAndReadAllNull) {
14481468
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitNotIn(not_in_literals));
14491469
CheckResult(result, {});
14501470
}
1471+
1472+
// --- VisitGreaterThan -> empty (min_key_/max_key_ are nullopt) ---
1473+
{
1474+
Literal lit_1(1);
1475+
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitGreaterThan(lit_1));
1476+
CheckResult(result, {});
1477+
}
1478+
1479+
// --- VisitGreaterOrEqual -> empty ---
1480+
{
1481+
Literal lit_1(1);
1482+
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitGreaterOrEqual(lit_1));
1483+
CheckResult(result, {});
1484+
}
1485+
1486+
// --- VisitLessOrEqual -> empty ---
1487+
{
1488+
Literal lit_1(1);
1489+
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitLessOrEqual(lit_1));
1490+
CheckResult(result, {});
1491+
}
14511492
}
14521493

14531494
TEST_P(BTreeGlobalIndexIntegrationTest, WriteAndReadLargeDataWithSmallBlocks) {
@@ -1988,6 +2029,101 @@ TEST_P(BTreeGlobalIndexIntegrationTest, WriteAndReadMultiFilesWithMetaSelector)
19882029
}
19892030
}
19902031

2032+
// Test both-bounds RangeQuery branch.
2033+
TEST_P(BTreeGlobalIndexIntegrationTest, TestRangeQuery) {
2034+
auto file_writer = std::make_shared<FakeGlobalIndexFileWriter>(fs_, base_path_);
2035+
auto field = arrow::field("str_field", arrow::utf8());
2036+
auto c_schema = CreateArrowSchema(field);
2037+
2038+
// Use very small block size to force data into multiple blocks
2039+
std::map<std::string, std::string> options = {{BtreeDefs::kBtreeIndexBlockSize, "64"},
2040+
{BtreeDefs::kBtreeIndexCompression, GetParam()}};
2041+
ASSERT_OK_AND_ASSIGN(auto indexer, BTreeGlobalIndexer::Create(options));
2042+
ASSERT_OK_AND_ASSIGN(auto writer,
2043+
indexer->CreateWriter("str_field", c_schema.get(), file_writer, pool_));
2044+
// Data layout
2045+
// 0->"aaa", 1->"aab", 2->"bba", 3->"bbb", 4->"bbc",
2046+
// 5->null, 6->"cca", 7->"ccb", 8->"ddd"
2047+
auto array = arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_({field}), R"([
2048+
["aaa"],
2049+
["aab"],
2050+
["bba"],
2051+
["bbb"],
2052+
["bbc"],
2053+
[null],
2054+
["cca"],
2055+
["ccb"],
2056+
["ddd"]
2057+
])")
2058+
.ValueOrDie();
2059+
2060+
ArrowArray c_array;
2061+
ASSERT_TRUE(arrow::ExportArray(*array, &c_array).ok());
2062+
std::vector<int64_t> row_ids(array->length());
2063+
std::iota(row_ids.begin(), row_ids.end(), 0);
2064+
ASSERT_OK(writer->AddBatch(&c_array, std::move(row_ids)));
2065+
ASSERT_OK_AND_ASSIGN(auto metas, writer->Finish());
2066+
ASSERT_EQ(metas.size(), 1);
2067+
2068+
auto file_reader = std::make_shared<FakeGlobalIndexFileReader>(fs_, base_path_);
2069+
c_schema = CreateArrowSchema(field);
2070+
ASSERT_OK_AND_ASSIGN(auto reader,
2071+
indexer->CreateReader(c_schema.get(), file_reader, metas, pool_));
2072+
2073+
// Non-null rows: {0,1,2,3,4,6,7,8}, Null rows: {5}
2074+
2075+
{
2076+
Literal lit_bb(FieldType::STRING, "bb", 2);
2077+
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitStartsWith(lit_bb));
2078+
CheckResult(result, {2, 3, 4});
2079+
}
2080+
2081+
{
2082+
Literal lit_cc(FieldType::STRING, "cc", 2);
2083+
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitStartsWith(lit_cc));
2084+
CheckResult(result, {6, 7});
2085+
}
2086+
2087+
{
2088+
Literal lit_aaa(FieldType::STRING, "aaa", 3);
2089+
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitGreaterThan(lit_aaa));
2090+
CheckResult(result, {1, 2, 3, 4, 6, 7, 8});
2091+
}
2092+
2093+
{
2094+
Literal lit_ddd(FieldType::STRING, "ddd", 3);
2095+
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitGreaterThan(lit_ddd));
2096+
CheckResult(result, {});
2097+
}
2098+
2099+
{
2100+
Literal lit_empty(FieldType::STRING, "", 0);
2101+
ASSERT_OK_AND_ASSIGN(auto result, reader->VisitStartsWith(lit_empty));
2102+
CheckResult(result, {0, 1, 2, 3, 4, 6, 7, 8});
2103+
}
2104+
2105+
// Raw RangeQuery call to cover both-bounds branch with from_inclusive=false and
2106+
// to_inclusive=false
2107+
{
2108+
ASSERT_FALSE(reader->IsThreadSafe());
2109+
ASSERT_EQ(reader->GetIndexType(), BtreeDefs::kIdentifier);
2110+
auto lazy_reader = std::dynamic_pointer_cast<LazyFilteredBTreeReader>(reader);
2111+
ASSERT_TRUE(lazy_reader);
2112+
ASSERT_OK_AND_ASSIGN(auto tmp_reader, lazy_reader->GetOrCreateReader(metas[0]));
2113+
auto btree_reader = std::dynamic_pointer_cast<BTreeGlobalIndexReader>(tmp_reader);
2114+
ASSERT_TRUE(btree_reader);
2115+
ASSERT_FALSE(btree_reader->IsThreadSafe());
2116+
ASSERT_EQ(btree_reader->GetIndexType(), BtreeDefs::kIdentifier);
2117+
2118+
Literal from_lit(FieldType::STRING, "bba", 3);
2119+
Literal to_lit(FieldType::STRING, "bbc", 3);
2120+
ASSERT_OK_AND_ASSIGN(RoaringBitmap64 range_result,
2121+
btree_reader->RangeQuery(from_lit, to_lit, /*from_inclusive=*/false,
2122+
/*to_inclusive=*/false));
2123+
ASSERT_EQ(range_result, RoaringBitmap64::From({3}));
2124+
}
2125+
}
2126+
19912127
INSTANTIATE_TEST_SUITE_P(Compression, BTreeGlobalIndexIntegrationTest,
19922128
::testing::ValuesIn(std::vector<std::string>({"none", "zstd", "lz4"})));
19932129

src/paimon/common/global_index/btree/btree_global_index_reader.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ Result<RoaringBitmap64> BTreeGlobalIndexReader::RangeQuery(const std::optional<L
324324
sst_file_reader_->GetNextBlock(index_iterator));
325325

326326
if (!data_iterator || !data_iterator->HasNext()) {
327+
assert(false);
327328
break;
328329
}
329330

src/paimon/common/global_index/offset_global_index_reader_test.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,4 +350,52 @@ TEST_F(OffsetGlobalIndexReaderTest, TestVisitVectorSearchNotSupported) {
350350
"FakeGlobalIndexReader does not support vector search");
351351
}
352352

353+
TEST_F(OffsetGlobalIndexReaderTest, TestVisitStartsWithWithOffset) {
354+
auto fake_reader = std::make_shared<FakeGlobalIndexReader>();
355+
fake_reader->SetDefaultResult({0, 1, 4});
356+
357+
auto offset_reader = std::make_shared<OffsetGlobalIndexReader>(fake_reader, 50);
358+
359+
Literal prefix(FieldType::STRING, "abc", 3);
360+
ASSERT_OK_AND_ASSIGN(auto result, offset_reader->VisitStartsWith(prefix));
361+
// row ids {0, 1, 4} + offset 50 -> {50, 51, 54}
362+
CheckResult(result, {50, 51, 54});
363+
}
364+
365+
TEST_F(OffsetGlobalIndexReaderTest, TestVisitEndsWithWithOffset) {
366+
auto fake_reader = std::make_shared<FakeGlobalIndexReader>();
367+
fake_reader->SetDefaultResult({2, 3});
368+
369+
auto offset_reader = std::make_shared<OffsetGlobalIndexReader>(fake_reader, 25);
370+
371+
Literal suffix(FieldType::STRING, "xyz", 3);
372+
ASSERT_OK_AND_ASSIGN(auto result, offset_reader->VisitEndsWith(suffix));
373+
// row ids {2, 3} + offset 25 -> {27, 28}
374+
CheckResult(result, {27, 28});
375+
}
376+
377+
TEST_F(OffsetGlobalIndexReaderTest, TestVisitContainsWithOffset) {
378+
auto fake_reader = std::make_shared<FakeGlobalIndexReader>();
379+
fake_reader->SetDefaultResult({1, 5, 6});
380+
381+
auto offset_reader = std::make_shared<OffsetGlobalIndexReader>(fake_reader, 300);
382+
383+
Literal literal(FieldType::STRING, "foo", 3);
384+
ASSERT_OK_AND_ASSIGN(auto result, offset_reader->VisitContains(literal));
385+
// row ids {1, 5, 6} + offset 300 -> {301, 305, 306}
386+
CheckResult(result, {301, 305, 306});
387+
}
388+
389+
TEST_F(OffsetGlobalIndexReaderTest, TestVisitLikeWithOffset) {
390+
auto fake_reader = std::make_shared<FakeGlobalIndexReader>();
391+
fake_reader->SetDefaultResult({0, 3, 7});
392+
393+
auto offset_reader = std::make_shared<OffsetGlobalIndexReader>(fake_reader, 40);
394+
395+
Literal pattern(FieldType::STRING, "ab%", 3);
396+
ASSERT_OK_AND_ASSIGN(auto result, offset_reader->VisitLike(pattern));
397+
// row ids {0, 3, 7} + offset 40 -> {40, 43, 47}
398+
CheckResult(result, {40, 43, 47});
399+
}
400+
353401
} // namespace paimon::test

src/paimon/common/utils/roaring_bitmap64.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ void RoaringBitmap64::AddMany(size_t n, const int64_t* values) {
184184
struct Bucket {
185185
uint32_t high;
186186
std::vector<uint32_t> lows;
187+
explicit Bucket(uint32_t high_val) : high(high_val) {}
187188
};
188189
std::vector<Bucket> buckets;
189190
buckets.reserve(4);
@@ -206,7 +207,7 @@ void RoaringBitmap64::AddMany(size_t n, const int64_t* values) {
206207
}
207208
}
208209
if (target == nullptr) {
209-
buckets.push_back({high, {}});
210+
buckets.emplace_back(high);
210211
// Reserve generously on first encounter; we may end up using
211212
// more memory than necessary if K turns out to be > 1, but
212213
// this avoids repeated grow-and-copy in the common K == 1 case.

src/paimon/core/table/source/data_split_test.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,26 @@ TEST(DataSplitTest, TestPartialMergedRowCount) {
10961096
.Build()
10971097
.value());
10981098
ASSERT_EQ(0, expected_data_split->PartialMergedRowCount());
1099+
1100+
ASSERT_EQ(4, expected_data_split->RowCount());
1101+
ASSERT_OK_AND_ASSIGN(auto latest_epoch, expected_data_split->LatestFileCreationEpochMillis());
1102+
ASSERT_TRUE(latest_epoch.has_value());
1103+
ASSERT_EQ(file_meta2->CreationTimeEpochMillis().value(), latest_epoch.value());
1104+
}
1105+
1106+
TEST(DataSplitTest, TestRowCountAndLatestFileCreationEpochMillisEmpty) {
1107+
DataSplitImpl::Builder builder(
1108+
/*partition=*/BinaryRow::EmptyRow(),
1109+
/*bucket=*/0, /*bucket_path=*/
1110+
"fake_table/bucket-0", std::vector<std::shared_ptr<DataFileMeta>>({}));
1111+
1112+
auto data_split = std::dynamic_pointer_cast<DataSplitImpl>(
1113+
builder.WithSnapshot(1).IsStreaming(false).RawConvertible(true).Build().value());
1114+
1115+
// Empty data_files: RowCount should be 0, LatestFileCreationEpochMillis should be nullopt
1116+
ASSERT_EQ(0, data_split->RowCount());
1117+
ASSERT_OK_AND_ASSIGN(auto latest_epoch, data_split->LatestFileCreationEpochMillis());
1118+
ASSERT_FALSE(latest_epoch.has_value());
10991119
}
11001120

11011121
} // namespace paimon::test

0 commit comments

Comments
 (0)