This is an automated email from the ASF dual-hosted git repository.
w41ter pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new fe0fb6a55aa [fix](txnkv) avoid skip keys during reverse full range
iteration (#52800)
fe0fb6a55aa is described below
commit fe0fb6a55aa4296d68534ea8a7ffd57a0564a157
Author: walter <[email protected]>
AuthorDate: Mon Jul 7 14:34:12 2025 +0800
[fix](txnkv) avoid skip keys during reverse full range iteration (#52800)
It was introduced by #52730. When switching to the next batch,
FullRangeGetIterator should not continue to use the RangeKeySelector
provided by the user; otherwise, it will lead to inaccurate key bounds.
---
cloud/src/meta-store/mem_txn_kv.cpp | 197 +++++++++++++++++---
cloud/src/meta-store/mem_txn_kv.h | 17 +-
cloud/src/meta-store/txn_kv.cpp | 11 +-
cloud/src/meta-store/txn_kv.h | 24 +--
cloud/test/mem_txn_kv_test.cpp | 358 ++++++++++++++++++++++++++++++++++++
cloud/test/txn_kv_test.cpp | 110 ++++++++++-
6 files changed, 655 insertions(+), 62 deletions(-)
diff --git a/cloud/src/meta-store/mem_txn_kv.cpp
b/cloud/src/meta-store/mem_txn_kv.cpp
index 3ab7cab16d3..db0850b0c1c 100644
--- a/cloud/src/meta-store/mem_txn_kv.cpp
+++ b/cloud/src/meta-store/mem_txn_kv.cpp
@@ -25,6 +25,7 @@
#include <mutex>
#include <optional>
#include <ostream>
+#include <ranges>
#include <string>
#include "cpp/sync_point.h"
@@ -63,12 +64,14 @@ TxnErrorCode MemTxnKv::get_kv(const std::string& key,
std::string* val, int64_t
}
TxnErrorCode MemTxnKv::get_kv(const std::string& begin, const std::string&
end, int64_t version,
- int limit, bool* more, std::map<std::string,
std::string>* kv_list) {
+ const RangeGetOptions& opts, bool* more,
+ std::vector<std::pair<std::string,
std::string>>* kv_list) {
if (begin >= end) {
return TxnErrorCode::TXN_OK;
}
bool use_limit = true;
+ int limit = opts.batch_limit;
if (limit < 0) {
return TxnErrorCode::TXN_UNIDENTIFIED_ERROR;
@@ -79,30 +82,105 @@ TxnErrorCode MemTxnKv::get_kv(const std::string& begin,
const std::string& end,
std::unique_lock<std::mutex> l(lock_);
- *more = false;
- auto begin_iter = mem_kv_.lower_bound(begin);
- auto end_iter = mem_kv_.lower_bound(end);
- for (; begin_iter != mem_kv_.end() && begin_iter != end_iter;
begin_iter++) {
- for (auto&& entry : begin_iter->second) {
- if (entry.commit_version > version) {
- continue;
+ auto apply_key_selector = [&](RangeKeySelector selector,
+ const std::string& key) ->
decltype(mem_kv_.lower_bound(key)) {
+ auto iter = mem_kv_.lower_bound(key);
+ switch (selector) {
+ case RangeKeySelector::FIRST_GREATER_OR_EQUAL:
+ break;
+ case RangeKeySelector::FIRST_GREATER_THAN:
+ if (iter != mem_kv_.end() && iter->first == key) {
+ ++iter;
+ }
+ break;
+ case RangeKeySelector::LAST_LESS_OR_EQUAL:
+ if (iter != mem_kv_.begin() && iter->first != key) {
+ --iter;
+ }
+ break;
+ case RangeKeySelector::LAST_LESS_THAN:
+ if (iter != mem_kv_.begin()) {
+ --iter;
}
+ break;
+ }
+ return iter;
+ };
- if (!entry.value.has_value()) {
+ *more = false;
+
+ bool reverse = opts.reverse;
+ std::vector<std::pair<std::string, std::string>> temp_results;
+
+ if (!reverse) {
+ // Forward iteration
+ auto begin_iter = apply_key_selector(opts.begin_key_selector, begin);
+ auto end_iter = apply_key_selector(opts.end_key_selector, end);
+ if (begin_iter == mem_kv_.end() ||
+ (end_iter != mem_kv_.end() && end_iter->first <
begin_iter->first)) {
+ // If the begin iterator is at the end or the end iterator is
before begin, return empty
+ kv_list->clear();
+ *more = false;
+ return TxnErrorCode::TXN_OK;
+ }
+
+ for (; begin_iter != end_iter; begin_iter++) {
+ // Find the appropriate version
+ for (auto&& entry : begin_iter->second) {
+ if (entry.commit_version > version) {
+ continue;
+ }
+
+ if (!entry.value.has_value()) {
+ break;
+ }
+
+ temp_results.emplace_back(begin_iter->first, *entry.value);
break;
}
- kv_list->insert_or_assign(begin_iter->first, *entry.value);
- limit--;
- break;
+ if (use_limit && temp_results.size() >=
static_cast<size_t>(limit)) {
+ *more = true;
+ break;
+ }
}
- if (use_limit && limit == 0) {
- break;
+ } else {
+ // Reverse iteration
+ auto end_iter = apply_key_selector(opts.end_key_selector, end);
+ auto begin_iter = apply_key_selector(opts.begin_key_selector, begin);
+ if (begin_iter == mem_kv_.end() ||
+ (end_iter != mem_kv_.end() && end_iter->first <=
begin_iter->first)) {
+ kv_list->clear();
+ *more = false;
+ return TxnErrorCode::TXN_OK;
}
+
+ do {
+ --end_iter; // end always excludes the last key
+
+ // Find the appropriate version (use reverse iterator for versions)
+ for (auto iter = end_iter->second.rbegin(); iter !=
end_iter->second.rend(); ++iter) {
+ if (iter->commit_version > version) {
+ continue;
+ }
+
+ if (!iter->value.has_value()) {
+ break;
+ }
+
+ temp_results.emplace_back(end_iter->first, *iter->value);
+ break;
+ }
+
+ if (use_limit && temp_results.size() >=
static_cast<size_t>(limit)) {
+ *more = true;
+ break;
+ }
+ } while (end_iter != begin_iter);
}
- if (use_limit && limit == 0 && ++begin_iter != end_iter) {
- *more = true;
- }
+
+ kv_list->swap(temp_results);
+
return TxnErrorCode::TXN_OK;
}
@@ -330,10 +408,9 @@ TxnErrorCode Transaction::inner_get(const std::string&
begin, const std::string&
std::unique_ptr<cloud::RangeGetIterator>*
iter,
const RangeGetOptions& opts) {
bool more = false;
- std::map<std::string, std::string> kv_map;
- int limit = opts.batch_limit;
bool snapshot = opts.snapshot;
- TxnErrorCode err = kv_->get_kv(begin, end, read_version_, limit, &more,
&kv_map);
+ std::vector<std::pair<std::string, std::string>> kv_list;
+ TxnErrorCode err = kv_->get_kv(begin, end, read_version_, opts, &more,
&kv_list);
if (err != TxnErrorCode::TXN_OK) {
return err;
}
@@ -347,28 +424,81 @@ TxnErrorCode Transaction::inner_get(const std::string&
begin, const std::string&
}
return false;
};
- for (auto it = kv_map.begin(), last = kv_map.end(); it != last;) {
+ for (auto it = kv_list.begin(), last = kv_list.end(); it != last;) {
if (pred(*it)) {
- it = kv_map.erase(it);
+ it = kv_list.erase(it);
} else {
++it;
}
}
if (!snapshot) {
- for (auto&& [key, _] : kv_map) {
+ for (auto&& [key, _] : kv_list) {
read_set_.insert(key);
}
}
- auto begin_iter = writes_.lower_bound(begin);
- auto end_iter = writes_.lower_bound(end);
- while (begin_iter != end_iter) {
- kv_map.insert_or_assign(begin_iter->first, begin_iter->second);
- begin_iter++;
+ std::map<std::string, std::string> kv_map;
+ for (const auto& [key, value] : kv_list) {
+ kv_map[key] = value;
+ }
+
+ // Get writes in the range and apply key selectors
+ auto apply_key_selector = [&](RangeKeySelector selector,
+ const std::string& key) ->
decltype(writes_.lower_bound(key)) {
+ auto iter = writes_.lower_bound(key);
+ switch (selector) {
+ case RangeKeySelector::FIRST_GREATER_OR_EQUAL:
+ break;
+ case RangeKeySelector::FIRST_GREATER_THAN:
+ if (iter != writes_.end() && iter->first == key) {
+ ++iter;
+ }
+ break;
+ case RangeKeySelector::LAST_LESS_OR_EQUAL:
+ if (iter != writes_.begin() && iter->first != key) {
+ --iter;
+ }
+ break;
+ case RangeKeySelector::LAST_LESS_THAN:
+ if (iter != writes_.begin()) {
+ --iter;
+ }
+ break;
+ }
+ return iter;
+ };
+
+ auto begin_iter = apply_key_selector(opts.begin_key_selector, begin);
+ auto end_iter = apply_key_selector(opts.end_key_selector, end);
+
+ // The end_iter is exclusive, so we need to check if it is valid:
+ // 1. end_iter is in the end
+ // 2. or the begin_iter is less than the end_iter
+ for (; begin_iter != end_iter &&
+ (end_iter == writes_.end() || begin_iter->first < end_iter->first);
+ ++begin_iter) {
+ const auto& key = begin_iter->first;
+ const auto& value = begin_iter->second;
+ kv_map[key] = value;
+ }
+
+ kv_list.clear();
+ if (!opts.reverse) {
+ for (const auto& [key, value] : kv_map) {
+ kv_list.emplace_back(key, value);
+ }
+ } else {
+ for (auto& it : std::ranges::reverse_view(kv_map)) {
+ kv_list.emplace_back(it.first, it.second);
+ }
+ }
+
+ if (opts.batch_limit > 0 && kv_list.size() >
static_cast<size_t>(opts.batch_limit)) {
+ more = true;
+ kv_list.resize(opts.batch_limit);
}
- std::vector<std::pair<std::string, std::string>> kv_list(kv_map.begin(),
kv_map.end());
num_get_keys_ += kv_list.size();
kv_->get_count_ += kv_list.size();
*iter = std::make_unique<memkv::RangeGetIterator>(std::move(kv_list),
more);
@@ -576,7 +706,14 @@ bool FullRangeGetIterator::has_next() {
txn = txn_.get();
}
- TxnErrorCode err = txn->get(begin_, end_, &inner_iter_,
opts_.snapshot, 0);
+ // For simplicity, we always get the entire range without batch limit.
+ RangeGetOptions opts;
+ opts.snapshot = opts_.snapshot;
+ opts.batch_limit = 0;
+ opts.reverse = opts_.reverse;
+ opts.begin_key_selector = opts_.begin_key_selector;
+ opts.end_key_selector = opts_.end_key_selector;
+ TxnErrorCode err = txn->get(begin_, end_, &inner_iter_, opts);
if (err != TxnErrorCode::TXN_OK) {
is_valid_ = false;
code_ = err;
diff --git a/cloud/src/meta-store/mem_txn_kv.h
b/cloud/src/meta-store/mem_txn_kv.h
index 3e7de0d2636..58f1ff59b82 100644
--- a/cloud/src/meta-store/mem_txn_kv.h
+++ b/cloud/src/meta-store/mem_txn_kv.h
@@ -56,7 +56,8 @@ public:
TxnErrorCode get_kv(const std::string& key, std::string* val, int64_t
version);
TxnErrorCode get_kv(const std::string& begin, const std::string& end,
int64_t version,
- int limit, bool* more, std::map<std::string,
std::string>* kv_list);
+ const RangeGetOptions& opts, bool* more,
+ std::vector<std::pair<std::string, std::string>>*
kv_list);
size_t total_kvs() const {
std::lock_guard<std::mutex> l(lock_);
@@ -299,19 +300,9 @@ public:
return k;
}
- std::string prev_end_key() const override {
+ std::string_view last_key() const override {
if (!more()) return {};
- std::string k(kvs_[kvs_size_ - 1].first);
- if (k.empty()) {
- // The minimum key, return an empty string
- } else if (k.back() == '\x00') {
- // If the last byte is a null byte, we should remove it
- k.pop_back();
- } else {
- // Otherwise, we should decrement the last byte
- k.back() -= 1;
- }
- return k;
+ return kvs_[kvs_size_ - 1].first;
}
private:
diff --git a/cloud/src/meta-store/txn_kv.cpp b/cloud/src/meta-store/txn_kv.cpp
index eb4054ac705..c858750d089 100644
--- a/cloud/src/meta-store/txn_kv.cpp
+++ b/cloud/src/meta-store/txn_kv.cpp
@@ -873,9 +873,16 @@ void
FullRangeGetIterator::async_inner_get(std::string_view begin, std::string_v
void FullRangeGetIterator::async_get_next_batch() {
if (opts_.reverse) {
- async_inner_get(begin_, inner_iter_->prev_end_key());
+ // Change the end key to the previous last key. The key selector will
be
+ // FIRST_GREATER_OR_EQUAL, so we need to use the last key of the inner
iterator as the
+ // end key, since the end key is exclusive.
+ opts_.end_key_selector = RangeKeySelector::FIRST_GREATER_OR_EQUAL;
+ std::string_view end_key = inner_iter_->last_key();
+ async_inner_get(begin_, end_key);
} else {
- async_inner_get(inner_iter_->next_begin_key(), end_);
+ opts_.begin_key_selector = RangeKeySelector::FIRST_GREATER_OR_EQUAL;
+ std::string begin_key = inner_iter_->next_begin_key();
+ async_inner_get(begin_key, end_);
}
}
diff --git a/cloud/src/meta-store/txn_kv.h b/cloud/src/meta-store/txn_kv.h
index 01eaf1486ef..00091ed1b2c 100644
--- a/cloud/src/meta-store/txn_kv.h
+++ b/cloud/src/meta-store/txn_kv.h
@@ -395,10 +395,12 @@ public:
virtual std::string next_begin_key() const = 0;
/**
- * Get the end key of the next iterator if `more()` is true, otherwise
returns empty string.
- * This is used for reverse range get.
+ * Get the last key of the iterator, it can be used as the end key of the
next iterator when
+ * the key selector is FIRST_GREATER_OR_EQUAL.
+ *
+ * ATTN: This is ONLY used for reverse range get.
*/
- virtual std::string prev_end_key() const = 0;
+ virtual std::string_view last_key() const = 0;
RangeGetIterator(const RangeGetIterator&) = delete;
RangeGetIterator& operator=(const RangeGetIterator&) = delete;
@@ -572,20 +574,10 @@ public:
return k;
}
- std::string prev_end_key() const override {
- if (!more()) return {};
+ std::string_view last_key() const override {
+ if (kvs_size_ <= 0) return {};
const auto& kv = kvs_[kvs_size_ - 1];
- std::string k((char*)kv.key, (size_t)kv.key_length);
- if (k.empty()) {
- // The minimum key, return an empty string
- } else if (k.back() == '\x00') {
- // If the last byte is a null byte, we should remove it
- k.pop_back();
- } else {
- // Otherwise, we should decrement the last byte
- k.back() -= 1;
- }
- return k;
+ return {(char*)kv.key, (size_t)kv.key_length};
}
RangeGetIterator(const RangeGetIterator&) = delete;
diff --git a/cloud/test/mem_txn_kv_test.cpp b/cloud/test/mem_txn_kv_test.cpp
index e5f9aa8736f..5dea50ca05d 100644
--- a/cloud/test/mem_txn_kv_test.cpp
+++ b/cloud/test/mem_txn_kv_test.cpp
@@ -25,6 +25,7 @@
#include "common/config.h"
#include "common/util.h"
#include "meta-service/doris_txn.h"
+#include "meta-store/codec.h"
#include "meta-store/txn_kv.h"
#include "meta-store/txn_kv_error.h"
@@ -980,3 +981,360 @@ TEST(TxnMemKvTest, MaybeUnusedFunctionTest) {
}
}
}
+
+TEST(TxnMemKvTest, RangeGetKeySelector) {
+ using namespace doris::cloud;
+ auto txn_kv = std::make_shared<MemTxnKv>();
+ ASSERT_EQ(txn_kv->init(), 0);
+
+ constexpr std::string_view prefix = "range_get_key_selector_";
+
+ {
+ // Remove the existing keys and insert some new keys.
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ std::string last_key = fmt::format("{}{}", prefix, 9);
+ encode_int64(INT64_MAX, &last_key);
+ txn->remove(prefix, last_key);
+ for (int i = 0; i < 5; ++i) {
+ std::string key = fmt::format("{}{}", prefix, i);
+ txn->put(key, std::to_string(i));
+ }
+ err = txn->commit();
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+ }
+
+ struct TestCase {
+ RangeKeySelector begin_key_selector, end_key_selector;
+ std::vector<std::string> expected_keys;
+ };
+
+ std::string range_begin = fmt::format("{}{}", prefix, 1);
+ std::string range_end = fmt::format("{}{}", prefix, 3);
+ std::vector<TestCase> test_case {
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ {fmt::format("{}{}", prefix, 1), fmt::format("{}{}",
prefix, 2)},
+ },
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ {
+ fmt::format("{}{}", prefix, 1),
+ fmt::format("{}{}", prefix, 2),
+ fmt::format("{}{}", prefix, 3),
+ },
+ },
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::LAST_LESS_OR_EQUAL,
+ {fmt::format("{}{}", prefix, 1), fmt::format("{}{}",
prefix, 2)},
+ },
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::LAST_LESS_THAN,
+ {fmt::format("{}{}", prefix, 1)},
+ },
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ {fmt::format("{}{}", prefix, 2)},
+ },
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ {fmt::format("{}{}", prefix, 2), fmt::format("{}{}",
prefix, 3)},
+ },
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::LAST_LESS_OR_EQUAL,
+ {fmt::format("{}{}", prefix, 2)},
+ },
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::LAST_LESS_THAN,
+ {},
+ },
+ {
+ RangeKeySelector::LAST_LESS_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ {fmt::format("{}{}", prefix, 1), fmt::format("{}{}",
prefix, 2)},
+ },
+ {
+ RangeKeySelector::LAST_LESS_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ {
+ fmt::format("{}{}", prefix, 1),
+ fmt::format("{}{}", prefix, 2),
+ fmt::format("{}{}", prefix, 3),
+ },
+ },
+ {
+ RangeKeySelector::LAST_LESS_OR_EQUAL,
+ RangeKeySelector::LAST_LESS_OR_EQUAL,
+ {fmt::format("{}{}", prefix, 1), fmt::format("{}{}",
prefix, 2)},
+ },
+ {
+ RangeKeySelector::LAST_LESS_OR_EQUAL,
+ RangeKeySelector::LAST_LESS_THAN,
+ {fmt::format("{}{}", prefix, 1)},
+ },
+ {
+ RangeKeySelector::LAST_LESS_THAN,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ {
+ fmt::format("{}{}", prefix, 0),
+ fmt::format("{}{}", prefix, 1),
+ fmt::format("{}{}", prefix, 2),
+ },
+ },
+ {
+ RangeKeySelector::LAST_LESS_THAN,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ {
+ fmt::format("{}{}", prefix, 0),
+ fmt::format("{}{}", prefix, 1),
+ fmt::format("{}{}", prefix, 2),
+ fmt::format("{}{}", prefix, 3),
+ },
+ },
+ {
+ RangeKeySelector::LAST_LESS_THAN,
+ RangeKeySelector::LAST_LESS_OR_EQUAL,
+ {
+ fmt::format("{}{}", prefix, 0),
+ fmt::format("{}{}", prefix, 1),
+ fmt::format("{}{}", prefix, 2),
+ },
+ },
+ {
+ RangeKeySelector::LAST_LESS_THAN,
+ RangeKeySelector::LAST_LESS_THAN,
+ {fmt::format("{}{}", prefix, 0), fmt::format("{}{}",
prefix, 1)},
+ },
+ };
+
+ // Scan range with different key selectors
+ for (const auto& tc : test_case) {
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ RangeGetOptions options;
+ options.batch_limit = 1000;
+ options.begin_key_selector = tc.begin_key_selector;
+ options.end_key_selector = tc.end_key_selector;
+ std::unique_ptr<RangeGetIterator> it;
+ err = txn->get(range_begin, range_end, &it, options);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ std::vector<std::string> actual_keys;
+ while (it->has_next()) {
+ auto [k, v] = it->next();
+ actual_keys.emplace_back(k);
+ }
+ EXPECT_EQ(actual_keys, tc.expected_keys)
+ << "Failed for begin_key_selector=" <<
static_cast<int>(tc.begin_key_selector)
+ << ", end_key_selector=" <<
static_cast<int>(tc.end_key_selector);
+ }
+}
+
+TEST(TxnMemKvTest, ReverseRangeGet) {
+ using namespace doris::cloud;
+ auto txn_kv = std::make_shared<MemTxnKv>();
+ ASSERT_EQ(txn_kv->init(), 0);
+
+ constexpr std::string_view prefix = "reverse_range_get_";
+
+ {
+ // Remove the existing keys and insert some new keys.
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ std::string last_key = fmt::format("{}{}", prefix, 9);
+ encode_int64(INT64_MAX, &last_key);
+ txn->remove(prefix, last_key);
+ for (int i = 0; i < 5; ++i) {
+ std::string key = fmt::format("{}{}", prefix, i);
+ txn->put(key, std::to_string(i));
+ }
+ err = txn->commit();
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+ }
+
+ std::string range_begin = fmt::format("{}{}", prefix, 1);
+ std::string range_end = fmt::format("{}{}", prefix, 3);
+
+ struct TestCase {
+ RangeKeySelector begin_key_selector, end_key_selector;
+ std::vector<std::string> expected_keys;
+ };
+
+ std::vector<TestCase> test_case {
+ // 1. [begin, end)
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ {fmt::format("{}{}", prefix, 2), fmt::format("{}{}",
prefix, 1)},
+ },
+ // 2. [begin, end]
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ {
+ fmt::format("{}{}", prefix, 3),
+ fmt::format("{}{}", prefix, 2),
+ fmt::format("{}{}", prefix, 1),
+ },
+ },
+ // 3. (begin, end)
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ {fmt::format("{}{}", prefix, 2)},
+ },
+ // 4. (begin, end]
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ {fmt::format("{}{}", prefix, 3), fmt::format("{}{}",
prefix, 2)},
+ },
+ };
+ for (const auto& tc : test_case) {
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ RangeGetOptions options;
+ options.batch_limit = 1000;
+ options.begin_key_selector = tc.begin_key_selector;
+ options.end_key_selector = tc.end_key_selector;
+ options.reverse = true; // Reserve range get
+ std::unique_ptr<RangeGetIterator> it;
+ err = txn->get(range_begin, range_end, &it, options);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ std::vector<std::string> actual_keys;
+ while (it->has_next()) {
+ auto [k, v] = it->next();
+ actual_keys.emplace_back(k);
+ }
+ EXPECT_EQ(actual_keys, tc.expected_keys)
+ << "Failed for begin_key_selector=" <<
static_cast<int>(tc.begin_key_selector)
+ << ", end_key_selector=" <<
static_cast<int>(tc.end_key_selector);
+ }
+}
+
+TEST(TxnMemKvTest, ReverseFullRangeGet) {
+ using namespace doris::cloud;
+ auto txn_kv = std::make_shared<MemTxnKv>();
+ ASSERT_EQ(txn_kv->init(), 0);
+
+ constexpr std::string_view prefix = "reverse_full_range_get_";
+
+ {
+ // Remove the existing keys and insert some new keys.
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ std::string last_key = fmt::format("{}{:03}", prefix, 99);
+ encode_int64(INT64_MAX, &last_key);
+ txn->remove(prefix, last_key);
+ for (int i = 0; i < 100; ++i) {
+ std::string key = fmt::format("{}{:03}", prefix, i);
+ txn->put(key, std::to_string(i));
+ }
+ err = txn->commit();
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+ }
+
+ std::string range_begin = fmt::format("{}{:03}", prefix, 1);
+ std::string range_end = fmt::format("{}{:03}", prefix, 98);
+
+ struct TestCase {
+ RangeKeySelector begin_key_selector, end_key_selector;
+ };
+
+ std::vector<TestCase> test_case {
+ // 1. [begin, end)
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ },
+ // 2. [begin, end]
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ },
+ // 3. (begin, end)
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ },
+ // 4. (begin, end]
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ },
+ };
+
+ for (const auto& tc : test_case) {
+ std::vector<std::string> expected_keys;
+ {
+ // Read the expected keys via range_get
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ RangeGetOptions options;
+ options.batch_limit = 11;
+ options.begin_key_selector = tc.begin_key_selector;
+ options.end_key_selector = tc.end_key_selector;
+ options.reverse = true; // Reserve range get
+ std::string begin = range_begin, end = range_end;
+
+ std::unique_ptr<RangeGetIterator> it;
+ do {
+ err = txn->get(begin, end, &it, options);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ while (it->has_next()) {
+ auto [k, v] = it->next();
+ expected_keys.emplace_back(k);
+ }
+ // Get next begin key for reverse range get
+ end = it->last_key();
+ options.end_key_selector =
RangeKeySelector::FIRST_GREATER_OR_EQUAL;
+ } while (it->more());
+ }
+
+ std::vector<std::string> actual_keys;
+ {
+ // Read the actual keys via full_range_get
+ FullRangeGetOptions opts(txn_kv);
+ opts.batch_limit = 11;
+ opts.begin_key_selector = tc.begin_key_selector;
+ opts.end_key_selector = tc.end_key_selector;
+ opts.reverse = true; // Reserve full range get
+
+ auto it = txn_kv->full_range_get(range_begin, range_end, opts);
+ ASSERT_TRUE(it->is_valid());
+
+ while (it->has_next()) {
+ auto kvp = it->next();
+ ASSERT_TRUE(kvp.has_value());
+ auto [k, v] = *kvp;
+ actual_keys.emplace_back(k);
+ }
+ }
+
+ EXPECT_EQ(actual_keys, expected_keys)
+ << "Failed for begin_key_selector=" <<
static_cast<int>(tc.begin_key_selector)
+ << ", end_key_selector=" <<
static_cast<int>(tc.end_key_selector);
+ }
+}
diff --git a/cloud/test/txn_kv_test.cpp b/cloud/test/txn_kv_test.cpp
index 85bef04f9c9..b0412f51e1a 100644
--- a/cloud/test/txn_kv_test.cpp
+++ b/cloud/test/txn_kv_test.cpp
@@ -1259,7 +1259,115 @@ TEST(TxnKvTest, ReverseFullRangeGet) {
expected_keys.emplace_back(k);
}
// Get next begin key for reverse range get
- end = it->prev_end_key();
+ end = it->last_key();
+ options.end_key_selector =
RangeKeySelector::FIRST_GREATER_OR_EQUAL;
+ } while (it->more());
+ }
+
+ std::vector<std::string> actual_keys;
+ {
+ // Read the actual keys via full_range_get
+ FullRangeGetOptions opts(txn_kv);
+ opts.batch_limit = 11;
+ opts.begin_key_selector = tc.begin_key_selector;
+ opts.end_key_selector = tc.end_key_selector;
+ opts.reverse = true; // Reserve full range get
+
+ auto it = txn_kv->full_range_get(range_begin, range_end, opts);
+ ASSERT_TRUE(it->is_valid());
+
+ while (it->has_next()) {
+ auto kvp = it->next();
+ ASSERT_TRUE(kvp.has_value());
+ auto [k, v] = *kvp;
+ actual_keys.emplace_back(k);
+ }
+ }
+
+ EXPECT_EQ(actual_keys, expected_keys)
+ << "Failed for begin_key_selector=" <<
static_cast<int>(tc.begin_key_selector)
+ << ", end_key_selector=" <<
static_cast<int>(tc.end_key_selector);
+ }
+}
+
+TEST(TxnKvTest, ReverseFullRangeGet2) {
+ constexpr std::string_view prefix = "reverse_full_range_get_2_";
+
+ {
+ // Remove the existing keys and insert some new keys.
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ std::string last_key = fmt::format("{}b", prefix);
+ encode_int64(INT64_MAX, &last_key);
+ txn->remove(prefix, last_key);
+ std::string key(prefix);
+ for (int i = 0; i < 100; ++i) {
+ key += 'a';
+ txn->put(key, std::to_string(i));
+ }
+ err = txn->commit();
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+ }
+
+ std::string range_begin(prefix);
+ std::string range_end = fmt::format("{}b", prefix);
+
+ struct TestCase {
+ RangeKeySelector begin_key_selector, end_key_selector;
+ };
+
+ std::vector<TestCase> test_case {
+ // 1. [begin, end)
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ },
+ // 2. [begin, end]
+ {
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ },
+ // 3. (begin, end)
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::FIRST_GREATER_OR_EQUAL,
+ },
+ // 4. (begin, end]
+ {
+ RangeKeySelector::FIRST_GREATER_THAN,
+ RangeKeySelector::FIRST_GREATER_THAN,
+ },
+ };
+
+ for (const auto& tc : test_case) {
+ std::vector<std::string> expected_keys;
+ {
+ // Read the expected keys via range_get
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ RangeGetOptions options;
+ options.batch_limit = 11;
+ options.begin_key_selector = tc.begin_key_selector;
+ options.end_key_selector = tc.end_key_selector;
+ options.reverse = true; // Reserve range get
+ std::string begin = range_begin, end = range_end;
+
+ std::unique_ptr<RangeGetIterator> it;
+ do {
+ err = txn->get(begin, end, &it, options);
+ ASSERT_EQ(err, TxnErrorCode::TXN_OK);
+
+ while (it->has_next()) {
+ auto [k, v] = it->next();
+ expected_keys.emplace_back(k);
+ }
+ // Get next begin key for reverse range get
+ end = it->last_key();
+ options.end_key_selector =
RangeKeySelector::FIRST_GREATER_OR_EQUAL;
} while (it->more());
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]