diff --git a/include/cpp_lmdb/iterators.hpp b/include/cpp_lmdb/iterators.hpp index 6256636..baddf82 100644 --- a/include/cpp_lmdb/iterators.hpp +++ b/include/cpp_lmdb/iterators.hpp @@ -128,6 +128,14 @@ class ro_iterator base::navigate_cursor(MDB_FIRST, nullptr); } + explicit ro_iterator( + LmdbApi const &api, MDB_cursor &cursor, byte_span const &key) noexcept + : base{api, cursor} + { + auto db_key = details::to_mdb_val(key); + base::navigate_cursor(MDB_SET_RANGE, &db_key); + } + ro_iterator(ro_iterator &&) = default; auto operator=(ro_iterator &&) -> ro_iterator & = default; diff --git a/include/cpp_lmdb/transactions.hpp b/include/cpp_lmdb/transactions.hpp index 66bf56a..e882665 100644 --- a/include/cpp_lmdb/transactions.hpp +++ b/include/cpp_lmdb/transactions.hpp @@ -3,8 +3,8 @@ #include "cpp_lmdb/concepts.hpp" #include "cpp_lmdb/db_item.hpp" #include "cpp_lmdb/iterators.hpp" -#include "cpp_lmdb/views.hpp" #include "cpp_lmdb/types.hpp" +#include "cpp_lmdb/views.hpp" // details #include "cpp_lmdb/details/details.hpp" @@ -38,13 +38,17 @@ class transaction { using ro_view = db_view, LmdbApi>; - using ro_dup_view = db_dup_view< + using ro_view_from_key = db_view_from_key< + ro_iterator, + LmdbApi>; + using ro_view_by_key = db_view_from_key< ro_dup_iterator, LmdbApi>; public: transaction( - MDB_dbi const db_index, details::txn_unique_ptr_t &&txn) noexcept + MDB_dbi const db_index, + details::txn_unique_ptr_t &&txn) noexcept : _db_index{db_index} , _txn{std::move(txn)} , _api{_txn.get_deleter().api} @@ -90,6 +94,17 @@ class transaction { return {}; } + auto lower_bound(key_type const &key) const noexcept + -> std::expected + { + auto cursor = details::make_cursor(_api, _txn.get(), _db_index); + if (!cursor) + return std::unexpected{error_t{cursor.error()}}; + + auto const key_bytes = key_trait::to_bytes(key); + return ro_view_from_key{std::move(*cursor), key_bytes}; + } + auto get(key_type const &key) const noexcept -> std::expected requires(!details::key_value_trait_helper< @@ -120,7 +135,7 @@ class transaction { } auto iterate_by_key(key_type const &key) const noexcept - -> std::expected + -> std::expected requires( details::key_value_trait_helper::duplicates_enabled) { @@ -129,7 +144,7 @@ class transaction { return std::unexpected{error_t{cursor.error()}}; auto const key_bytes = key_trait::to_bytes(key); - return ro_dup_view{std::move(*cursor), key_bytes}; + return ro_view_by_key{std::move(*cursor), key_bytes}; } private: diff --git a/include/cpp_lmdb/views.hpp b/include/cpp_lmdb/views.hpp index deb0936..87b7678 100644 --- a/include/cpp_lmdb/views.hpp +++ b/include/cpp_lmdb/views.hpp @@ -34,16 +34,16 @@ class db_view }; template -class db_dup_view - : public std::ranges::view_interface> { +class db_view_from_key + : public std::ranges::view_interface> { public: - explicit db_dup_view( + explicit db_view_from_key( details::cursor_unique_ptr_t &&cursor, byte_span const &key) : _cursor{std::move(cursor)}, _key{key.begin(), key.end()} {} - db_dup_view(db_dup_view &&) = default; - auto operator=(db_dup_view &&) -> db_dup_view & = default; + db_view_from_key(db_view_from_key &&) = default; + auto operator=(db_view_from_key &&) -> db_view_from_key & = default; auto begin() const { diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index b129bf3..304a1a6 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable( integrations_tests test_db_int_keys_and_values.cpp test_db_string_keys_and_values.cpp + test_db_dup_int_keys_values.cpp ) enable_testing() diff --git a/test/integration/test_db_dup_int_keys_values.cpp b/test/integration/test_db_dup_int_keys_values.cpp new file mode 100644 index 0000000..cd23243 --- /dev/null +++ b/test/integration/test_db_dup_int_keys_values.cpp @@ -0,0 +1,59 @@ +#include "cpp_lmdb/cpp_lmdb.hpp" +#include "test_utils.hpp" + +// gtest +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// std +#include +#include + +using namespace ::testing; // NOLINT(google-build-using-namespace) + +namespace cpp_lmdb_tests +{ + +TEST( + integration_test, + db_int_duplicate_keys_and_values_iterate_by_key_lower_bound) +{ + using test_trait = lmdb::duplicate_key< + lmdb::trivial_trait, + lmdb::trivial_trait>; + + constexpr auto test_env = "./test_env"; + + if (std::filesystem::exists(test_env)) + std::filesystem::remove_all(test_env); + std::filesystem::create_directory(test_env); + + auto environment = lmdb::make_environment( + test_env, lmdb::default_file_mode); + + ASSERT_TRUE(environment); + auto rw_db = environment->open_rw_db( + "test_db", lmdb::create_if_not_exists::yes); + + ASSERT_TRUE(rw_db); + + auto transaction = rw_db->begin_rw_transaction(); + ASSERT_TRUE(transaction); + + EXPECT_TRUE(transaction->insert(0xAA, 2000)); + EXPECT_TRUE(transaction->insert(0xBB, 5001)); + EXPECT_TRUE(transaction->insert(0xBB, 5002)); + EXPECT_TRUE(transaction->insert(0xCC, 10000)); + + ASSERT_TRUE(rw_db->commit_transaction(std::move(*transaction))); + { + auto const ro_tx = rw_db->begin_ro_transaction(); + ASSERT_TRUE(ro_tx); + + EXPECT_THAT( + cpp_lmdb_tests::get_all_values(ro_tx->lower_bound(0xBB).value()), + ElementsAre(5001, 5002, 10000)); + } +} + +} // namespace cpp_lmdb_tests diff --git a/test/integration/test_db_int_keys_and_values.cpp b/test/integration/test_db_int_keys_and_values.cpp index 3f4159c..29abeb0 100644 --- a/test/integration/test_db_int_keys_and_values.cpp +++ b/test/integration/test_db_int_keys_and_values.cpp @@ -1,5 +1,4 @@ #include "cpp_lmdb/cpp_lmdb.hpp" - #include "test_utils.hpp" // gtest @@ -43,6 +42,8 @@ TEST(integration_test, db_int_keys_and_values_unique_key) EXPECT_TRUE(transaction->insert(0xAA, 2000)); EXPECT_TRUE(transaction->insert(0xBB, 5000)); + EXPECT_TRUE(transaction->insert(0xCC, 8000)); + { auto const result = transaction->try_insert(0xAA, 3000); ASSERT_FALSE(result); @@ -58,7 +59,11 @@ TEST(integration_test, db_int_keys_and_values_unique_key) ASSERT_TRUE(result); EXPECT_EQ(*result, 5000); } - + { + const auto result = transaction->get(0xCC); + ASSERT_TRUE(result); + EXPECT_EQ(*result, 8000); + } { ASSERT_TRUE(transaction->insert(0xBB, 10000)); const auto result = transaction->get(0xBB); @@ -66,7 +71,7 @@ TEST(integration_test, db_int_keys_and_values_unique_key) EXPECT_EQ(*result, 10000); } - rw_db->commit_transaction(std::move(*transaction)); + ASSERT_TRUE(rw_db->commit_transaction(std::move(*transaction))); { auto ro_tx = rw_db->begin_ro_transaction(); ASSERT_TRUE(ro_tx); @@ -80,6 +85,11 @@ TEST(integration_test, db_int_keys_and_values_unique_key) ASSERT_TRUE(result); EXPECT_EQ(*result, 10000); } + { + const auto result = ro_tx->get(0xCC); + ASSERT_TRUE(result); + EXPECT_EQ(*result, 8000); + } } { auto ro_tx = rw_db->begin_ro_transaction(); @@ -87,7 +97,11 @@ TEST(integration_test, db_int_keys_and_values_unique_key) EXPECT_THAT( cpp_lmdb_tests::get_all_values(ro_tx->iterate().value()), - ElementsAre(2000, 10000)); + ElementsAre(2000, 10000, 8000)); + + EXPECT_THAT( + cpp_lmdb_tests::get_all_values(ro_tx->lower_bound(0xBB).value()), + UnorderedElementsAre(10000, 8000)); } } diff --git a/test/unit/test_transaction.cpp b/test/unit/test_transaction.cpp index 383c672..b0ed252 100644 --- a/test/unit/test_transaction.cpp +++ b/test/unit/test_transaction.cpp @@ -295,4 +295,48 @@ TEST_F(test_transaction, trivial_types_dup_iterate_by_key) EXPECT_EQ(db_item.value(), 0x20000030); } +TEST_F(test_transaction, trivial_types_transaction_lower_bound) +{ + std::array test_value{0x30, 0x0, 0x0, 0x20}; + + lmdb:: + transaction> + transaction{test_dbi, std::move(txn)}; + + auto *cursor{reinterpret_cast(0x84)}; + + { + InSequence const seq; + + EXPECT_CALL(api, mdb_cursor_open(test_txn, test_dbi, _)) + .WillOnce(DoAll(SetArgPointee<2>(cursor), Return(MDB_SUCCESS))); + + EXPECT_CALL( + api, + mdb_cursor_get( + cursor, + Pointee(MdbValBytesAre{0x78, 0x56, 0x34, 0x12}), + _, + MDB_SET_RANGE)) + .WillOnce(DoAll( + SetArgPointee<2>( + MDB_val{test_value.size(), test_value.data()}), + Return(MDB_SUCCESS))); + + EXPECT_CALL(api, mdb_cursor_close(cursor)); + EXPECT_CALL(api, mdb_txn_abort(test_txn)); + } + + const auto result = transaction.lower_bound(0x12345678); + ASSERT_TRUE(result); + + auto const &db_view = *result; + auto const it = db_view.begin(); + ASSERT_NE(it, db_view.end()); + + auto const &key_value = *it; + EXPECT_EQ(key_value.key(), 0x12345678); + EXPECT_EQ(key_value.value(), 0x20000030); +} + } // namespace cpp_lmdb_tests