2020#include " paimon/common/factories/io_hook.h"
2121#include " paimon/common/global_index/btree/btree_global_index_writer.h"
2222#include " paimon/common/global_index/btree/btree_global_indexer.h"
23+ #include " paimon/common/global_index/btree/lazy_filtered_btree_reader.h"
24+ #include " paimon/common/options/memory_size.h"
2325#include " paimon/common/utils/scope_guard.h"
2426#include " paimon/data/decimal.h"
2527#include " paimon/data/timestamp.h"
2628#include " paimon/fs/file_system.h"
2729#include " paimon/global_index/bitmap_global_index_result.h"
2830#include " paimon/global_index/io/global_index_file_reader.h"
2931#include " paimon/global_index/io/global_index_file_writer.h"
32+ #include " paimon/io/buffered_input_stream.h"
3033#include " paimon/memory/memory_pool.h"
3134#include " paimon/predicate/literal.h"
3235#include " paimon/testing/utils/io_exception_helper.h"
@@ -1506,11 +1509,6 @@ TEST_P(BTreeGlobalIndexIntegrationTest, WriteAndReadLargeDataWithSmallBlocks) {
15061509 ASSERT_OK_AND_ASSIGN (auto metas, writer->Finish ());
15071510 ASSERT_EQ (metas.size (), 1 );
15081511
1509- auto file_reader = std::make_shared<FakeGlobalIndexFileReader>(fs_, base_path_);
1510- c_schema = CreateArrowSchema (field);
1511- ASSERT_OK_AND_ASSIGN (auto reader,
1512- indexer->CreateReader (c_schema.get (), file_reader, metas, pool_));
1513-
15141512 // Helper lambda: given a predicate on value, collect matching row ids
15151513 auto collect_rows = [total_rows = total_rows](std::function<bool (int32_t )> predicate) {
15161514 std::vector<int64_t > result;
@@ -1529,69 +1527,110 @@ TEST_P(BTreeGlobalIndexIntegrationTest, WriteAndReadLargeDataWithSmallBlocks) {
15291527 return result;
15301528 };
15311529
1532- // --- VisitIsNull ---
1533- {
1534- ASSERT_OK_AND_ASSIGN (auto result, reader->VisitIsNull ());
1535- CheckResult (result, null_row_ids);
1536- }
1530+ // Read back with different read-buffer-size configurations:
1531+ // "" -> no buffer (original path)
1532+ // "128" -> very small buffer (smaller than block size 256)
1533+ // "512" -> moderate buffer (larger than block size)
1534+ // "64KB" -> large buffer
1535+ // "1MB" -> very large buffer (likely covers entire file)
1536+ std::vector<std::string> buffer_sizes = {" " , " 128" , " 512" , " 64KB" , " 1MB" };
1537+
1538+ for (const auto & buf_size : buffer_sizes) {
1539+ std::map<std::string, std::string> read_options = {
1540+ {BtreeDefs::kBtreeIndexBlockSize , " 256" }, {BtreeDefs::kBtreeIndexCacheSize , " 1024" }};
1541+ if (!buf_size.empty ()) {
1542+ read_options[BtreeDefs::kBtreeIndexReadBufferSize ] = buf_size;
1543+ }
1544+ ASSERT_OK_AND_ASSIGN (auto read_indexer, BTreeGlobalIndexer::Create (read_options));
15371545
1538- // --- VisitIsNotNull ---
1539- {
1540- ASSERT_OK_AND_ASSIGN (auto result, reader->VisitIsNotNull ());
1541- CheckResult (result, non_null_row_ids);
1542- }
1546+ auto file_reader = std::make_shared<FakeGlobalIndexFileReader>(fs_, base_path_);
1547+ c_schema = CreateArrowSchema (field);
1548+ ASSERT_OK_AND_ASSIGN (auto reader,
1549+ read_indexer->CreateReader (c_schema.get (), file_reader, metas, pool_));
1550+
1551+ // check input stream and read buffer size
1552+ {
1553+ auto lazy_reader = std::dynamic_pointer_cast<LazyFilteredBTreeReader>(reader);
1554+ ASSERT_TRUE (lazy_reader);
1555+ ASSERT_OK_AND_ASSIGN (auto tmp_reader, lazy_reader->GetOrCreateReader (metas[0 ]));
1556+ auto btree_reader = std::dynamic_pointer_cast<BTreeGlobalIndexReader>(tmp_reader);
1557+ ASSERT_TRUE (btree_reader);
1558+ auto input_stream = btree_reader->sst_file_reader_ ->block_cache_ ->in_ ;
1559+ auto buffered_stream = std::dynamic_pointer_cast<BufferedInputStream>(input_stream);
1560+ if (!buf_size.empty ()) {
1561+ ASSERT_TRUE (buffered_stream);
1562+ ASSERT_OK_AND_ASSIGN (int32_t expected_buffer_size,
1563+ MemorySize::ParseBytes (buf_size));
1564+ ASSERT_EQ (buffered_stream->buffer_size_ , expected_buffer_size);
1565+ } else {
1566+ ASSERT_FALSE (buffered_stream);
1567+ }
1568+ }
15431569
1544- // --- VisitEqual for value 0 ---
1545- {
1546- Literal lit_0 (0 );
1547- ASSERT_OK_AND_ASSIGN (auto result, reader->VisitEqual (lit_0));
1548- CheckResult (result, collect_rows ([](int32_t v) { return v == 0 ; }));
1549- }
1570+ // --- VisitIsNull ---
1571+ {
1572+ ASSERT_OK_AND_ASSIGN (auto result, reader->VisitIsNull ());
1573+ CheckResult (result, null_row_ids);
1574+ }
15501575
1551- // --- VisitEqual for a value in the middle ---
1552- {
1553- Literal lit_100 (100 );
1554- ASSERT_OK_AND_ASSIGN (auto result, reader->VisitEqual (lit_100));
1555- CheckResult (result, collect_rows ([](int32_t v) { return v == 100 ; }));
1556- }
1576+ // --- VisitIsNotNull ---
1577+ {
1578+ ASSERT_OK_AND_ASSIGN (auto result, reader->VisitIsNotNull ());
1579+ CheckResult (result, non_null_row_ids);
1580+ }
15571581
1558- // --- VisitEqual for non-existent value ---
1559- {
1560- Literal lit_neg ( static_cast < int32_t >(- 1 ) );
1561- ASSERT_OK_AND_ASSIGN (auto result, reader->VisitEqual (lit_neg ));
1562- CheckResult (result, {} );
1563- }
1582+ // --- VisitEqual for value 0 ---
1583+ {
1584+ Literal lit_0 ( 0 );
1585+ ASSERT_OK_AND_ASSIGN (auto result, reader->VisitEqual (lit_0 ));
1586+ CheckResult (result, collect_rows ([]( int32_t v) { return v == 0 ; }) );
1587+ }
15641588
1565- // --- VisitLessThan for value 5 ---
1566- {
1567- Literal lit_5 ( 5 );
1568- ASSERT_OK_AND_ASSIGN (auto result, reader->VisitLessThan (lit_5 ));
1569- CheckResult (result, collect_rows ([](int32_t v) { return v < 5 ; }));
1570- }
1589+ // --- VisitEqual for a value in the middle ---
1590+ {
1591+ Literal lit_100 ( 100 );
1592+ ASSERT_OK_AND_ASSIGN (auto result, reader->VisitEqual (lit_100 ));
1593+ CheckResult (result, collect_rows ([](int32_t v) { return v == 100 ; }));
1594+ }
15711595
1572- // --- VisitGreaterOrEqual for a high value near max ---
1573- {
1574- int32_t max_val =
1575- static_cast <int32_t >(collect_rows ([](int32_t ) { return true ; }).size ()) / 3 ;
1576- int32_t threshold = max_val - 2 ;
1577- Literal lit_threshold (threshold);
1578- ASSERT_OK_AND_ASSIGN (auto result, reader->VisitGreaterOrEqual (lit_threshold));
1579- CheckResult (result, collect_rows ([threshold](int32_t v) { return v >= threshold; }));
1580- }
1596+ // --- VisitEqual for non-existent value ---
1597+ {
1598+ Literal lit_neg (static_cast <int32_t >(-1 ));
1599+ ASSERT_OK_AND_ASSIGN (auto result, reader->VisitEqual (lit_neg));
1600+ CheckResult (result, {});
1601+ }
15811602
1582- // --- VisitIn for scattered values ---
1583- {
1584- std::vector<Literal> in_literals = {Literal (0 ), Literal (500 ), Literal (10000 )};
1585- ASSERT_OK_AND_ASSIGN (auto result, reader->VisitIn (in_literals));
1586- CheckResult (result,
1587- collect_rows ([](int32_t v) { return v == 0 || v == 500 || v == 10000 ; }));
1588- }
1603+ // --- VisitLessThan for value 5 ---
1604+ {
1605+ Literal lit_5 (5 );
1606+ ASSERT_OK_AND_ASSIGN (auto result, reader->VisitLessThan (lit_5));
1607+ CheckResult (result, collect_rows ([](int32_t v) { return v < 5 ; }));
1608+ }
15891609
1590- // --- VisitNotEqual for value 0 ---
1591- {
1592- Literal lit_0 (0 );
1593- ASSERT_OK_AND_ASSIGN (auto result, reader->VisitNotEqual (lit_0));
1594- CheckResult (result, collect_rows ([](int32_t v) { return v != 0 ; }));
1610+ // --- VisitGreaterOrEqual for a high value near max ---
1611+ {
1612+ int32_t max_val =
1613+ static_cast <int32_t >(collect_rows ([](int32_t ) { return true ; }).size ()) / 3 ;
1614+ int32_t threshold = max_val - 2 ;
1615+ Literal lit_threshold (threshold);
1616+ ASSERT_OK_AND_ASSIGN (auto result, reader->VisitGreaterOrEqual (lit_threshold));
1617+ CheckResult (result, collect_rows ([threshold](int32_t v) { return v >= threshold; }));
1618+ }
1619+
1620+ // --- VisitIn for scattered values ---
1621+ {
1622+ std::vector<Literal> in_literals = {Literal (0 ), Literal (500 ), Literal (10000 )};
1623+ ASSERT_OK_AND_ASSIGN (auto result, reader->VisitIn (in_literals));
1624+ CheckResult (result,
1625+ collect_rows ([](int32_t v) { return v == 0 || v == 500 || v == 10000 ; }));
1626+ }
1627+
1628+ // --- VisitNotEqual for value 0 ---
1629+ {
1630+ Literal lit_0 (0 );
1631+ ASSERT_OK_AND_ASSIGN (auto result, reader->VisitNotEqual (lit_0));
1632+ CheckResult (result, collect_rows ([](int32_t v) { return v != 0 ; }));
1633+ }
15951634 }
15961635}
15971636
@@ -1722,6 +1761,22 @@ TEST_P(BTreeGlobalIndexIntegrationTest, FinishWithEmptyData) {
17221761 ASSERT_NOK_WITH_MSG (writer->Finish (), " Should never write an empty btree index file" );
17231762}
17241763
1764+ TEST_P (BTreeGlobalIndexIntegrationTest, InvalidReadOptions) {
1765+ auto file_writer = std::make_shared<FakeGlobalIndexFileWriter>(fs_, base_path_);
1766+ auto field = arrow::field (" int_field" , arrow::int32 ());
1767+ auto c_schema = CreateArrowSchema (field);
1768+
1769+ std::map<std::string, std::string> options = {{BtreeDefs::kBtreeIndexBlockSize , " 4096" },
1770+ {BtreeDefs::kBtreeIndexCompression , GetParam ()},
1771+ {BtreeDefs::kBtreeIndexReadBufferSize , " 4GB" }};
1772+ ASSERT_OK_AND_ASSIGN (auto indexer, BTreeGlobalIndexer::Create (options));
1773+
1774+ auto file_reader = std::make_shared<FakeGlobalIndexFileReader>(fs_, base_path_);
1775+ ASSERT_NOK_WITH_MSG (indexer->CreateReader (c_schema.get (), file_reader, /* files=*/ {}, pool_),
1776+ " In BTreeGlobalIndexer::CreateReader: option btree-index.read-buffer-size "
1777+ " is 4GB, exceed INT_MAX or less than 0" );
1778+ }
1779+
17251780TEST_P (BTreeGlobalIndexIntegrationTest, TestIOException) {
17261781 bool run_complete = false ;
17271782 auto io_hook = paimon::IOHook::GetInstance ();
0 commit comments