Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
94c434a
test: add test cases for nested columns
zhf999 Jun 16, 2026
4cdf107
Merge branch 'main' into nested-col-fallback
zhf999 Jun 17, 2026
30ea1d8
walkaround: fallback nested field to RowGroup reading
zhf999 Jun 17, 2026
bed9fdf
style: add comments
zhf999 Jun 17, 2026
dfe419d
fix: judge has_nested_field on read-schema
zhf999 Jun 17, 2026
2ceaf60
fix: make tests clearer
zhf999 Jun 17, 2026
cb1a632
feat: support page-level bitmap pushdown
zhf999 Jun 17, 2026
9a5f4ee
fix: fallback bitmap pushdown to rowgroup-level
zhf999 Jun 17, 2026
09d2121
style: update tests names
zhf999 Jun 17, 2026
3ea428d
stye: update comments
zhf999 Jun 17, 2026
76e7282
fix: set 'SupportPreciseBitmapSelection to true'
zhf999 Jun 17, 2026
72be706
Merge branch 'main' into paged-bitmap2
zhf999 Jun 18, 2026
8078ca4
fxi: set 'SupportPreciseBitmapSelection to false'
zhf999 Jun 18, 2026
48cd610
fix: add assign operator for TargetRowGroup
zhf999 Jun 22, 2026
246e854
fix: use PARQUET_READ_ENABLE_PAGE_INDEX_FILTER to control bitmap filt…
zhf999 Jun 22, 2026
d9c952d
fix: use bitmap to get row ranges instead of the first column with pa…
zhf999 Jun 22, 2026
1c495e4
fix: test
zhf999 Jun 22, 2026
5d534db
fix: FileBatchReader returns discontinuous batch, and change GetPrevi…
zhf999 Jun 26, 2026
ad03498
style: change interface name
zhf999 Jun 26, 2026
cd7bd44
update header files
zhf999 Jun 26, 2026
f9721a8
Merge branch 'main' into fix-rowid
zhf999 Jun 26, 2026
41f932d
fix: return Status::Invalid intead of returning max value
zhf999 Jun 26, 2026
6bd98d8
fix: lance and blob return NotImplemented
zhf999 Jun 26, 2026
a694567
fix: add inclusive extend for fully matched rowgroup in SetReadSchema
zhf999 Jun 26, 2026
eb48e42
fix: calling SetReadSchema many time do not clear row_mapping
zhf999 Jun 26, 2026
7b00794
test: add test for PrefetchFileBatchReaderImpl
zhf999 Jun 26, 2026
5b28d56
Merge branch 'main' into fix-rowid
zhf999 Jun 26, 2026
0d9a174
style: replace auto in assigning macro with explicit type
zhf999 Jun 26, 2026
7edf5c1
Merge branch 'main' into paged-bitmap2
zhf999 Jun 26, 2026
09c3ef5
fix: mismatched Create function call
zhf999 Jun 26, 2026
17a3e90
Merge branch 'fix-rowid' into paged-bitmap-tmp
zhf999 Jun 26, 2026
1336922
style: rename interfaces and parameters
zhf999 Jun 29, 2026
8f82f44
fix: use a more efficient way to apply bitmap
zhf999 Jun 29, 2026
d3b73e1
update headers
zhf999 Jun 29, 2026
fcc1ac8
fix: use iterator to apply bitmap
zhf999 Jun 29, 2026
0fb2dec
test: add assertion
zhf999 Jun 29, 2026
a3e37bd
test: use '.value()' directly to validate the result.
zhf999 Jun 29, 2026
8820e7c
update comments
zhf999 Jun 29, 2026
376a312
style: change method name
zhf999 Jun 29, 2026
f1c02db
fix: small fixes
zhf999 Jun 29, 2026
ad66b22
fix: blob test
zhf999 Jun 29, 2026
25ef3d0
fix: blob and lance tests
zhf999 Jun 29, 2026
4409afb
fix: unittest
zhf999 Jun 29, 2026
5bdca4a
Merge branch 'fix-rowid' into paged-bitmap-tmp
zhf999 Jun 29, 2026
8482e90
fix: blob
zhf999 Jun 29, 2026
a2d7036
Merge branch 'fix-rowid' into paged-bitmap-tmp
zhf999 Jun 29, 2026
7d92884
feat: coarse row ranges
zhf999 Jul 2, 2026
9e57edb
Merge branch 'main' into paged-bitmap-coars
zhf999 Jul 14, 2026
7330edd
feat: support 2 stategies: trim and coalesce
zhf999 Jul 15, 2026
f8265a8
test: add test cases
zhf999 Jul 15, 2026
74e87f8
Merge branch 'main' into paged-bitmap-coalesce
zhf999 Jul 15, 2026
fdfdd7e
fix: correct test comments
zhf999 Jul 16, 2026
926436f
pre-commit
zhf999 Jul 16, 2026
0c61e01
Merge branch 'main' into paged-bitmap-coalesce
zhf999 Jul 16, 2026
9016f21
fix: skip empty row ranges for coalesce
zhf999 Jul 16, 2026
d179572
style: align comments with actual code behavior
zhf999 Jul 20, 2026
23c846d
Merge branch 'main' into paged-bitmap-coalesce
zhf999 Jul 20, 2026
1e13c67
Merge branch 'main' into paged-bitmap-coalesce
lxy-9602 Jul 20, 2026
d18898a
style: remove comments
zhf999 Jul 20, 2026
8e57b89
Merge branch 'main' into paged-bitmap-coalesce
zhf999 Jul 20, 2026
d7363e0
test: add test cases
zhf999 Jul 20, 2026
67104b0
fix: no use 'none' option
zhf999 Jul 20, 2026
feb9b0d
test: add test cases
zhf999 Jul 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 201 additions & 26 deletions src/paimon/format/parquet/page_filtered_row_group_reader_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,12 @@ class PageFilteredRowGroupReaderTest : public ::testing::Test {
const std::shared_ptr<Predicate>& predicate,
const RoaringBitmap32& bitmap,
std::shared_ptr<arrow::ChunkedArray>* out,
int32_t batch_size = 1024,
bool enable_page_level_filter = true) {
const std::map<std::string, std::string> options = {},
int32_t batch_size = 1024) {
ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in, fs_->Open(file_name));
ASSERT_OK_AND_ASSIGN(int64_t length, in->Length());
auto in_stream = std::make_shared<ArrowInputStreamAdapter>(in, arrow_pool_, length);

std::map<std::string, std::string> options;
options[PARQUET_READ_ENABLE_PAGE_INDEX_FILTER] =
enable_page_level_filter ? "true" : "false";
ASSERT_OK_AND_ASSIGN(auto batch_reader,
ParquetFileBatchReader::Create(std::move(in_stream), options,
batch_size, nullptr, arrow_pool_));
Expand Down Expand Up @@ -1290,15 +1287,16 @@ TEST_F(PageFilteredRowGroupReaderTest, BitmapAllPagesSomeRowGroups) {
/// Test: bitmap hits partial pages of a row group (no predicate).
///
/// 200 rows, 10 rows per page, 100 rows per row group → 2 row groups.
/// Bitmap: {30..59} hits pages 3-5 of RG0 (rows 30-59), RG1 excluded.
/// Expected: 30 rows (30-59).
TEST_F(PageFilteredRowGroupReaderTest, BitmapPartialPagesSingleRowGroup) {
/// Bitmap: {90..109} hits page 9 of RG0 (rows 90-99), and page 0 of RG1 (rows 100-109).
/// Expected: 20 rows (90-109).
TEST_F(PageFilteredRowGroupReaderTest, BitmapPartialPagesAcrossRowGroups) {
std::string file_name = dir_->Str() + "/bitmap_partial_pages_rg.parquet";
auto data = MakeSequentialIntData(200);
WriteTestFile(file_name, data, /*write_batch_size=*/10, /*max_row_group_length=*/100);

RoaringBitmap32 bitmap;
bitmap.AddRange(90, 110); // hits pages 3-5 of RG0
// {90..109} hits page 9 of RG0 (rows 90-99), and page 0 of RG1 (rows 100-109).
bitmap.AddRange(90, 110);

auto read_schema = arrow::schema({arrow::field("val", arrow::int32())});
std::shared_ptr<arrow::ChunkedArray> result;
Expand Down Expand Up @@ -1364,8 +1362,10 @@ TEST_F(PageFilteredRowGroupReaderTest, BitmapWithPageFilteredOptionDisabled) {

auto read_schema = arrow::schema({arrow::field("val", arrow::int32())});
std::shared_ptr<arrow::ChunkedArray> result;
std::map<std::string, std::string> options;
options[PARQUET_READ_ENABLE_PAGE_INDEX_FILTER] = "false";
ReadWithPredicateAndBitmapImpl(file_name, read_schema, /*predicate=*/nullptr, bitmap, &result,
1024, false);
options);
ASSERT_TRUE(result);
ASSERT_EQ(100, result->length());

Expand Down Expand Up @@ -1487,42 +1487,217 @@ TEST_F(PageFilteredRowGroupReaderTest, BitmapMixedWithPredicate) {
}
}

/// Test: read parquet with scattered bitmap
/// Test: coalesce strategy with default hole_size_limit (32).
///
/// 200 rows, 50 rows per page, 100 rows per row group → 2 row groups.
/// Bitmap: [20,30), [35, 40), [125, 126), [130, 131), [150, 200)
/// To test if unneeded row at the start and end of pages are filtered out.
/// Expected: 76 rows ([20, 40) + [125, 131) + [150, 200).
TEST_F(PageFilteredRowGroupReaderTest, ScatteredBitmapTest) {
/// Bitmap: [0,10), [45, 50), [60, 70).
/// - [0,10) and [45,50) are both in RG0 page 0 ([0,49]); gap = 35 > 32, NOT merged.
/// - [45,50) and [60,70) straddle RG0 page 0 ([0,49]) and page 1 ([50,99]); gap = 10 <= 32,
/// merged across the page boundary, so rows 50-59 are read even though not in the bitmap.
/// Expected: 35 rows ([0,10) + [45, 70)).
TEST_F(PageFilteredRowGroupReaderTest, BitmapCoalesceTest) {
std::string file_name = dir_->Str() + "/scattered_bitmap.parquet";
auto data = MakeSequentialIntData(200);
WriteTestFile(file_name, data, /*write_batch_size=*/50, /*max_row_group_length=*/100);

RoaringBitmap32 bitmap;
bitmap.AddRange(20, 30);
bitmap.AddRange(35, 40);
bitmap.Add(125);
bitmap.Add(130);
bitmap.AddRange(150, 200);
bitmap.AddRange(0, 10);
bitmap.AddRange(45, 50);
bitmap.AddRange(60, 70);

auto read_schema = arrow::schema({arrow::field("val", arrow::int32())});
std::shared_ptr<arrow::ChunkedArray> result;
ReadWithPredicateAndBitmapImpl(file_name, read_schema, /*predicate=*/nullptr, bitmap, &result);
ASSERT_TRUE(result);
ASSERT_EQ(76, result->length());
ASSERT_EQ(35, result->length());

auto flat = arrow::Concatenate(result->chunks()).ValueOrDie();
auto struct_arr = std::dynamic_pointer_cast<arrow::StructArray>(flat);
ASSERT_TRUE(struct_arr);
auto val_arr = std::dynamic_pointer_cast<arrow::Int32Array>(struct_arr->field(0));
for (int32_t i = 0; i < 20; ++i) {
ASSERT_EQ(20 + i, val_arr->Value(i));
// [0, 9] — not merged (gap to next range = 35 > 32)
for (int32_t i = 0; i < 10; ++i) {
ASSERT_EQ(0 + i, val_arr->Value(i));
}
// [45, 69] — merged across page boundary (gap = 10 <= 32)
for (int32_t i = 0; i < 25; ++i) {
ASSERT_EQ(45 + i, val_arr->Value(10 + i));
}
}

/// Test: coalesce strategy with hole_size_limit=5 (instead of default 32).
///
/// Same bitmap as BitmapCoalesceTest: [0,10), [45, 50), [60, 70).
/// Both gaps (35 and 10) exceed 5, so no ranges are merged.
/// Expected: 25 rows (exact bitmap selection, no holes filled).
/// Compare with BitmapCoalesceTest which gets 35 rows (gap of 10 is filled with default limit=32).
TEST_F(PageFilteredRowGroupReaderTest, BitmapCoalesceSmallHoleSizeTest) {
std::string file_name = dir_->Str() + "/coalesce_small_hole.parquet";
auto data = MakeSequentialIntData(200);
WriteTestFile(file_name, data, /*write_batch_size=*/50, /*max_row_group_length=*/100);

RoaringBitmap32 bitmap;
bitmap.AddRange(0, 10);
bitmap.AddRange(45, 50);
bitmap.AddRange(60, 70);

std::map<std::string, std::string> options;
options[PARQUET_READ_ROW_RANGES_COALESCE_HOLE_SIZE_LIMIT] = "5";

auto read_schema = arrow::schema({arrow::field("val", arrow::int32())});
std::shared_ptr<arrow::ChunkedArray> result;
ReadWithPredicateAndBitmapImpl(file_name, read_schema, /*predicate=*/nullptr, bitmap, &result,
options);
ASSERT_TRUE(result);
ASSERT_EQ(25, result->length());

auto flat = arrow::Concatenate(result->chunks()).ValueOrDie();
auto struct_arr = std::dynamic_pointer_cast<arrow::StructArray>(flat);
ASSERT_TRUE(struct_arr);
auto val_arr = std::dynamic_pointer_cast<arrow::Int32Array>(struct_arr->field(0));
ASSERT_TRUE(val_arr);
// [0, 9] — not merged (gap = 35 > 5)
for (int32_t i = 0; i < 10; ++i) {
ASSERT_EQ(0 + i, val_arr->Value(i));
}
// [45, 49] — not merged (gap = 10 > 5)
for (int32_t i = 0; i < 5; ++i) {
ASSERT_EQ(45 + i, val_arr->Value(10 + i));
}
// [60, 69] — not merged
for (int32_t i = 0; i < 10; ++i) {
ASSERT_EQ(60 + i, val_arr->Value(15 + i));
}
}

/// Test: trim strategy for bitmap row filtering.
///
/// Same bitmap as BitmapCoalesceTest: [0,10), [45, 50), [60, 70).
/// Trim produces one range per page (trimmed to first/last selected row in that page):
/// - RG0 page 0 ([0,49]): first selected = 0, last selected = 49 → [0, 49] (50 rows, includes
/// the 35-row gap 10-44 that coalesce keeps as a hole because gap = 35 > 32).
/// - RG0 page 1 ([50,99]): first selected = 60, last selected = 69 → [60, 69] (10 rows; rows
/// 50-59 are NOT read, unlike coalesce which merges across the page boundary).
/// Expected: 60 rows ([0, 50) + [60, 70)).
/// Compare with BitmapCoalesceTest which gets 35 rows.
TEST_F(PageFilteredRowGroupReaderTest, BitmapTrimStrategyTest) {
Comment thread
lxy-9602 marked this conversation as resolved.
std::string file_name = dir_->Str() + "/trim_strategy.parquet";
auto data = MakeSequentialIntData(200);
WriteTestFile(file_name, data, /*write_batch_size=*/50, /*max_row_group_length=*/100);

RoaringBitmap32 bitmap;
bitmap.AddRange(0, 10);
bitmap.AddRange(45, 50);
bitmap.AddRange(60, 70);

std::map<std::string, std::string> options;
options[PARQUET_READ_BITMAP_ROW_RANGE_REFINING_STRATEGY] = "trim";

auto read_schema = arrow::schema({arrow::field("val", arrow::int32())});
std::shared_ptr<arrow::ChunkedArray> result;
ReadWithPredicateAndBitmapImpl(file_name, read_schema, /*predicate=*/nullptr, bitmap, &result,
options);
ASSERT_TRUE(result);
ASSERT_EQ(60, result->length());

auto flat = arrow::Concatenate(result->chunks()).ValueOrDie();
auto struct_arr = std::dynamic_pointer_cast<arrow::StructArray>(flat);
ASSERT_TRUE(struct_arr);
auto val_arr = std::dynamic_pointer_cast<arrow::Int32Array>(struct_arr->field(0));
ASSERT_TRUE(val_arr);
// RG0 page 0 trimmed to [0, 49] — includes gap 10-44 that coalesce skips
for (int32_t i = 0; i < 50; ++i) {
ASSERT_EQ(0 + i, val_arr->Value(i));
}
for (int32_t i = 0; i < 6; ++i) {
ASSERT_EQ(125 + i, val_arr->Value(20 + i));
// RG0 page 1 trimmed to [60, 69] — rows 50-59 not read (unlike coalesce)
for (int32_t i = 0; i < 10; ++i) {
ASSERT_EQ(60 + i, val_arr->Value(50 + i));
}
}

/// Test: invalid strategy value returns Status::Invalid.
///
/// 200 rows, 50 rows per page, 100 rows per row group → 2 row groups.
/// Bitmap: [0,10), [45, 50), [60, 70) — same as BitmapCoalesceTest.
/// Strategy: "invalid" (not one of "coalesce", "trim", "none").
/// Expected: SetReadSchema returns Status::Invalid.
TEST_F(PageFilteredRowGroupReaderTest, BitmapInvalidStrategyTest) {
std::string file_name = dir_->Str() + "/invalid_strategy.parquet";
auto data = MakeSequentialIntData(200);
WriteTestFile(file_name, data, /*write_batch_size=*/50, /*max_row_group_length=*/100);

RoaringBitmap32 bitmap;
bitmap.AddRange(0, 10);
bitmap.AddRange(45, 50);
bitmap.AddRange(60, 70);

std::map<std::string, std::string> options;
options[PARQUET_READ_BITMAP_ROW_RANGE_REFINING_STRATEGY] = "invalid";

ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in, fs_->Open(file_name));
ASSERT_OK_AND_ASSIGN(int64_t length, in->Length());
auto in_stream = std::make_shared<ArrowInputStreamAdapter>(in, arrow_pool_, length);

ASSERT_OK_AND_ASSIGN(
auto batch_reader,
ParquetFileBatchReader::Create(std::move(in_stream), options, 1024, nullptr, arrow_pool_));

auto read_schema = arrow::schema({arrow::field("val", arrow::int32())});
auto c_schema = std::make_unique<ArrowSchema>();
ASSERT_TRUE(arrow::ExportSchema(*read_schema, c_schema.get()).ok());

auto status = batch_reader->SetReadSchema(c_schema.get(), /*predicate=*/nullptr, bitmap);
ASSERT_FALSE(status.ok());
ASSERT_TRUE(status.IsInvalid());
}

/// Test: trim strategy with multiple columns — intersection of per-column trimmed ranges.
///
/// 200 rows, 50 rows per page, 100 rows per row group → 2 row groups.
/// Two columns: a[i] = i, b[i] = i * 10.
/// Bitmap: [0,10), [45, 50), [60, 70) — same as BitmapTrimStrategyTest.
/// Both columns share the same page boundaries, so their trimmed ranges are identical.
/// The intersection is the same as either column alone:
/// - RG0 page 0 ([0,49]): trimmed to [0, 49] (50 rows)
/// - RG0 page 1 ([50,99]): trimmed to [60, 69] (10 rows)
/// Expected: 60 rows. Both columns must remain aligned after trimming.
TEST_F(PageFilteredRowGroupReaderTest, BitmapTrimMultiColumnTest) {
std::string file_name = dir_->Str() + "/trim_multi_col.parquet";
auto data = MakeTwoColumnData(200);
WriteTestFile(file_name, data, /*write_batch_size=*/50, /*max_row_group_length=*/100);

RoaringBitmap32 bitmap;
bitmap.AddRange(0, 10);
bitmap.AddRange(45, 50);
bitmap.AddRange(60, 70);

std::map<std::string, std::string> options;
options[PARQUET_READ_BITMAP_ROW_RANGE_REFINING_STRATEGY] = "trim";

auto read_schema =
arrow::schema({arrow::field("a", arrow::int32()), arrow::field("b", arrow::int32())});
std::shared_ptr<arrow::ChunkedArray> result;
ReadWithPredicateAndBitmapImpl(file_name, read_schema, /*predicate=*/nullptr, bitmap, &result,
options);
ASSERT_TRUE(result);
ASSERT_EQ(60, result->length());

auto flat = arrow::Concatenate(result->chunks()).ValueOrDie();
auto struct_arr = std::dynamic_pointer_cast<arrow::StructArray>(flat);
ASSERT_TRUE(struct_arr);
auto a_arr = std::dynamic_pointer_cast<arrow::Int32Array>(struct_arr->field(0));
auto b_arr = std::dynamic_pointer_cast<arrow::Int32Array>(struct_arr->field(1));
ASSERT_TRUE(a_arr);
ASSERT_TRUE(b_arr);
// RG0 page 0 trimmed to [0, 49]
for (int32_t i = 0; i < 50; ++i) {
ASSERT_EQ(150 + i, val_arr->Value(26 + i));
ASSERT_EQ(i, a_arr->Value(i));
ASSERT_EQ(i * 10, b_arr->Value(i));
}
// RG0 page 1 trimmed to [60, 69]
for (int32_t i = 0; i < 10; ++i) {
ASSERT_EQ(60 + i, a_arr->Value(50 + i));
ASSERT_EQ((60 + i) * 10, b_arr->Value(50 + i));
}
}

Expand Down
Loading
Loading