Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 8 additions & 4 deletions plugins/chain_plugin/src/chain_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2862,13 +2862,17 @@ read_only::get_table_rows( const read_only::get_table_rows_params& p, const fc::
// next page yields the first row strictly below the last returned
// one -- i.e. the first unseen row. Setting `next_key` to the first
// unseen row's sk instead would skip that row at every page boundary.
auto begin = sec_idx.lower_bound(boost::make_tuple(p.code, sec_tid, lb_sv));
decltype(sec_idx.end()) itr;
if (has_upper) {
itr = sec_idx.lower_bound(boost::make_tuple(p.code, sec_tid, ub_sv));
} else {
if (!has_upper) {
itr = sec_idx.upper_bound(boost::make_tuple(p.code, sec_tid));
} else if (ub_sv < lb_sv) {
// An inverted range is empty. Seeking the upper bound would start `itr` below `begin`, and the walk down
// could then step off the front of the index.
itr = begin;
} else {
itr = sec_idx.lower_bound(boost::make_tuple(p.code, sec_tid, ub_sv));
}
auto begin = sec_idx.lower_bound(boost::make_tuple(p.code, sec_tid, lb_sv));
uint32_t count = 0;
// Remember the last-returned row's sec_key bytes so the cutoff
// branches below can feed them to `emit_secondary_next_key`.
Expand Down
30 changes: 30 additions & 0 deletions tests/get_table_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,36 @@ BOOST_FIXTURE_TEST_CASE( get_kv_rows_reverse_pagination_secondary_hex_test, vali

} FC_LOG_AND_RETHROW()

// An inverted range (upper_bound below lower_bound) is empty on every path. The reverse secondary scan walks down from
// the upper bound to the lower one, so an inverted range used to start it below its stop; when that table's partition
// comes first in the whole secondary index, the first step went off the front of the index. The account sorts before
// every other code, so its partition does come first.
BOOST_FIXTURE_TEST_CASE( get_kv_rows_inverted_range_test, validating_tester ) try {
setup_secrev(*this, "1secrev"_n);

std::optional<sysio::chain_apis::tracked_votes> _tracked_votes;
chain_apis::read_only plugin(*(this->control), {}, {}, _tracked_votes,
fc::microseconds::maximum(), fc::microseconds::maximum(), {});

for (const std::string index : {"", "byowner"}) {
for (const bool reverse : {false, true}) {
BOOST_TEST_CONTEXT("index='" << index << "' reverse=" << reverse) {
chain_apis::read_only::get_table_rows_params p;
p.code = "1secrev"_n;
p.table = "users";
p.index_name = index;
p.reverse = reverse;
p.lower_bound = index.empty() ? R"({"id":4})" : R"({"byowner":"u4"})";
p.upper_bound = index.empty() ? R"({"id":1})" : R"({"byowner":"u1"})";

auto page = get_table_rows_kv(plugin, p, fc::time_point::maximum());
BOOST_CHECK(page.rows.empty());
BOOST_CHECK(!page.more);
}
}
}
} FC_LOG_AND_RETHROW()

// Test get_table_rows with index_name parameter — full secondary index query
BOOST_FIXTURE_TEST_CASE( get_kv_rows_index_name_test, validating_tester ) try {
produce_block();
Expand Down
Loading