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
18 changes: 12 additions & 6 deletions art.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2335,7 +2335,10 @@ typename db<Key, Value>::iterator& db<Key, Value>::iterator::seek(
// do a left-most descent under that right-sibling. If there
// is no such parent, we will wind up with an empty stack
// (iterator at the end) and return that state.
if (!empty()) pop();
//
// Note: The current node (where gte_key_byte failed) was
// never pushed, so the stack top is already the first
// ancestor to check for a right-sibling.
while (!empty()) {
const auto& centry = top();
const auto cnode{centry.node}; // possible parent from the stack
Expand Down Expand Up @@ -2364,11 +2367,14 @@ typename db<Key, Value>::iterator& db<Key, Value>::iterator::seek(
// immediate predecessor of the desired key in the data.
auto nxt = inode->lte_key_byte(node_type, remaining_key[0]);
if (!nxt) {
// Pop off the current entry until we find one with a
// left-sibling and then do a right-most descent under that
// left-sibling. In the extreme case there is no such
// previous entry and we will wind up with an empty stack.
if (!empty()) pop();
// Pop off entries until we find one with a left-sibling and
// then do a right-most descent under that left-sibling. In
// the extreme case there is no such previous entry and we
// will wind up with an empty stack.
//
// Note: The current node (where lte_key_byte failed) was
// never pushed, so the stack top is already the first
// ancestor to check for a left-sibling.
while (!empty()) {
const auto& centry = top();
const auto cnode{centry.node}; // possible parent from stack
Expand Down
8 changes: 6 additions & 2 deletions olc_art.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3418,7 +3418,9 @@ bool olc_db<Key, Value>::iterator::try_seek(art_key_type search_key,
if (UNODB_DETAIL_UNLIKELY(
!node_critical_section.try_read_unlock())) // unlock node
return false; // LCOV_EXCL_LINE
if (!empty()) pop();
// Note: The current node (where gte_key_byte failed) was
// never pushed, so the stack top is already the first
// ancestor to check for a right-sibling.
while (!empty()) {
const auto& centry = top();
const auto cnode{centry.node}; // a possible parent from the stack.
Expand Down Expand Up @@ -3495,7 +3497,9 @@ bool olc_db<Key, Value>::iterator::try_seek(art_key_type search_key,
if (UNODB_DETAIL_UNLIKELY(
!node_critical_section.try_read_unlock())) // unlock node
return false; // LCOV_EXCL_LINE
if (!empty()) pop();
// Note: The current node (where lte_key_byte failed) was
// never pushed, so the stack top is already the first
// ancestor to check for a left-sibling.
while (!empty()) {
const auto& centry = top();
const auto cnode{centry.node}; // a possible parent from stack
Expand Down
67 changes: 67 additions & 0 deletions test/test_art_scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1077,4 +1077,71 @@ UNODB_TYPED_TEST(ARTScanTest, scanRangeC143) {
do_scan_range_test<TypeParam>(823, 247, 999);
}

// Regression test for T-5bec51c4: scan_from fails to backtrack across
// sibling subtrees when the seek key exceeds all children in a subtree.
// Keys {100,200,300,400,500} encoded as 8-byte BE branch at byte 6:
// child 0x00 → {100,200}, child 0x01 → {300,400,500}.
// seek(250) enters child 0x00, finds no child >= 0xFA at byte 7,
// must backtrack to byte 6 and descend child 0x01 to find 300.
UNODB_TYPED_TEST(ARTScanTest, scanFromBacktrackAcrossSiblingSubtrees) {
unodb::test::tree_verifier<TypeParam> verifier;
TypeParam& db = verifier.get_db();
for (const std::uint64_t k : {100U, 200U, 300U, 400U, 500U})
verifier.insert(k, unodb::test::test_values[0]);
// FWD: scan_from(250) should find 300, 400, 500.
{
std::vector<std::uint64_t> results;
db.scan_from(
250, [&results](const unodb::visitor<typename TypeParam::iterator>& v) {
results.push_back(decode(v.get_key()));
return false;
});
UNODB_ASSERT_EQ(3U, results.size());
UNODB_EXPECT_EQ(300U, results[0]);
UNODB_EXPECT_EQ(400U, results[1]);
UNODB_EXPECT_EQ(500U, results[2]);
}
// REV: scan_from(250, fn, false) should find 200, 100.
{
std::vector<std::uint64_t> results;
db.scan_from(
250,
[&results](const unodb::visitor<typename TypeParam::iterator>& v) {
results.push_back(decode(v.get_key()));
return false;
},
false /*fwd*/);
UNODB_ASSERT_EQ(2U, results.size());
UNODB_EXPECT_EQ(200U, results[0]);
UNODB_EXPECT_EQ(100U, results[1]);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// FWD scan_range: [250, 501) should find 300, 400, 500.
{
std::vector<std::uint64_t> results;
db.scan_range(
250, 501,
[&results](const unodb::visitor<typename TypeParam::iterator>& v) {
results.push_back(decode(v.get_key()));
return false;
});
UNODB_ASSERT_EQ(3U, results.size());
UNODB_EXPECT_EQ(300U, results[0]);
UNODB_EXPECT_EQ(400U, results[1]);
UNODB_EXPECT_EQ(500U, results[2]);
}
// REV scan_range: [250, 99) should find 200, 100.
{
std::vector<std::uint64_t> results;
db.scan_range(
250, 99,
[&results](const unodb::visitor<typename TypeParam::iterator>& v) {
results.push_back(decode(v.get_key()));
return false;
});
UNODB_ASSERT_EQ(2U, results.size());
UNODB_EXPECT_EQ(200U, results[0]);
UNODB_EXPECT_EQ(100U, results[1]);
}
}

} // namespace
Loading