diff --git a/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.cpp b/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.cpp index 5e02b62258c0..ef886c47e147 100644 --- a/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.cpp +++ b/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.cpp @@ -5,10 +5,12 @@ #include "barretenberg/lmdblib/lmdb_write_transaction.hpp" #include "barretenberg/lmdblib/types.hpp" #include "lmdb.h" +#include #include #include #include #include +#include #include namespace bb::lmdblib { @@ -176,6 +178,51 @@ void LMDBStore::get(KeysVector& keys, OptionalValuesVector& values, LMDBDatabase } } +void LMDBStore::has(const KeyOptionalValuesVector& entries, std::vector& results, const std::string& name) +{ + auto string_cmp = [](const Key& a, const Key& b) { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); + }; + + std::set key_set(string_cmp); + for (const auto& entry : entries) { + key_set.insert(entry.first); + } + + KeysVector keys(key_set.begin(), key_set.end()); + OptionalValuesVector vals; + get(keys, vals, name); + + results.reserve(entries.size()); + + for (const auto& entry : entries) { + const auto& key = entry.first; + const auto& requested_values = entry.second; + + const auto key_it = std::find(keys.begin(), keys.end(), key); + if (key_it == keys.end()) { + results.push_back(false); + continue; + } + + const auto& values = vals[static_cast(key_it - keys.begin())]; + + if (!values.has_value()) { + results.push_back(false); + continue; + } + + if (!requested_values.has_value()) { + results.push_back(true); + continue; + } + + results.push_back(std::all_of(requested_values->begin(), requested_values->end(), [&](const auto& val) { + return std::find(values->begin(), values->end(), val) != values->end(); + })); + } +} + LMDBStore::Cursor::Ptr LMDBStore::create_cursor(ReadTransaction::SharedPtr tx, const std::string& dbName) { Database::SharedPtr db = get_database(dbName); diff --git a/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.hpp b/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.hpp index a65a2324f805..ffcf8d8cdc33 100644 --- a/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.hpp +++ b/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.hpp @@ -47,6 +47,7 @@ class LMDBStore : public LMDBStoreBase { void put(std::vector& data); void get(KeysVector& keys, OptionalValuesVector& values, const std::string& name); + void has(const KeyOptionalValuesVector& entries, std::vector& results, const std::string& name); Cursor::Ptr create_cursor(ReadTransaction::SharedPtr tx, const std::string& dbName); diff --git a/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.test.cpp b/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.test.cpp index a46e28eee454..75c677adc14b 100644 --- a/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.test.cpp +++ b/barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.test.cpp @@ -1372,3 +1372,132 @@ TEST_F(LMDBStoreTest, can_read_data_from_multiple_threads) } } } + +TEST_F(LMDBStoreTest, has_returns_false_for_missing_key) +{ + LMDBStore::Ptr store = create_store(); + const std::string name = "Test Database"; + store->open_database(name); + + KeyOptionalValuesVector entries = { { get_key(0), std::nullopt } }; + std::vector results; + store->has(entries, results, name); + + EXPECT_EQ(results.size(), 1UL); + EXPECT_FALSE(results[0]); +} + +TEST_F(LMDBStoreTest, has_returns_true_for_existing_key) +{ + LMDBStore::Ptr store = create_store(); + const std::string name = "Test Database"; + store->open_database(name); + write_test_data({ name }, 3, 1, *store); + + KeyOptionalValuesVector entries = { { get_key(0), std::nullopt }, { get_key(1), std::nullopt } }; + std::vector results; + store->has(entries, results, name); + + EXPECT_EQ(results.size(), 2UL); + EXPECT_TRUE(results[0]); + EXPECT_TRUE(results[1]); +} + +TEST_F(LMDBStoreTest, has_returns_true_when_value_exists) +{ + LMDBStore::Ptr store = create_store(); + const std::string name = "Test Database"; + store->open_database(name, true); + write_test_data({ name }, 2, 3, *store); + + // Check that key 0 has value (0, 0) + ValuesVector requested = { get_value(0, 0) }; + KeyOptionalValuesVector entries = { { get_key(0), requested } }; + std::vector results; + store->has(entries, results, name); + + EXPECT_EQ(results.size(), 1UL); + EXPECT_TRUE(results[0]); +} + +TEST_F(LMDBStoreTest, has_returns_false_when_value_missing) +{ + LMDBStore::Ptr store = create_store(); + const std::string name = "Test Database"; + store->open_database(name, true); + write_test_data({ name }, 2, 3, *store); + + // Check for a value that doesn't exist under key 0 + ValuesVector requested = { get_value(99, 99) }; + KeyOptionalValuesVector entries = { { get_key(0), requested } }; + std::vector results; + store->has(entries, results, name); + + EXPECT_EQ(results.size(), 1UL); + EXPECT_FALSE(results[0]); +} + +TEST_F(LMDBStoreTest, has_checks_all_requested_values) +{ + LMDBStore::Ptr store = create_store(); + const std::string name = "Test Database"; + store->open_database(name, true); + write_test_data({ name }, 1, 3, *store); + + // All values present + ValuesVector all_present = { get_value(0, 0), get_value(0, 1), get_value(0, 2) }; + KeyOptionalValuesVector entries_all = { { get_key(0), all_present } }; + std::vector results_all; + store->has(entries_all, results_all, name); + EXPECT_TRUE(results_all[0]); + + // One value missing + ValuesVector one_missing = { get_value(0, 0), get_value(99, 99) }; + KeyOptionalValuesVector entries_missing = { { get_key(0), one_missing } }; + std::vector results_missing; + store->has(entries_missing, results_missing, name); + EXPECT_FALSE(results_missing[0]); +} + +TEST_F(LMDBStoreTest, has_handles_mixed_entries) +{ + LMDBStore::Ptr store = create_store(); + const std::string name = "Test Database"; + store->open_database(name, true); + write_test_data({ name }, 3, 2, *store); + + KeyOptionalValuesVector entries = { + { get_key(0), std::nullopt }, // key exists, no value check -> true + { get_key(99), std::nullopt }, // key missing -> false + { get_key(1), ValuesVector{ get_value(1, 0) } }, // key exists, value present -> true + { get_key(2), ValuesVector{ get_value(99, 99) } }, // key exists, value missing -> false + }; + std::vector results; + store->has(entries, results, name); + + EXPECT_EQ(results.size(), 4UL); + EXPECT_TRUE(results[0]); + EXPECT_FALSE(results[1]); + EXPECT_TRUE(results[2]); + EXPECT_FALSE(results[3]); +} + +TEST_F(LMDBStoreTest, has_deduplicates_keys) +{ + LMDBStore::Ptr store = create_store(); + const std::string name = "Test Database"; + store->open_database(name); + write_test_data({ name }, 2, 1, *store); + + // Same key appearing twice with different value checks + KeyOptionalValuesVector entries = { + { get_key(0), std::nullopt }, + { get_key(0), ValuesVector{ get_value(0, 0) } }, + }; + std::vector results; + store->has(entries, results, name); + + EXPECT_EQ(results.size(), 2UL); + EXPECT_TRUE(results[0]); + EXPECT_TRUE(results[1]); +} diff --git a/barretenberg/cpp/src/barretenberg/nodejs_module/lmdb_store/lmdb_store_wrapper.cpp b/barretenberg/cpp/src/barretenberg/nodejs_module/lmdb_store/lmdb_store_wrapper.cpp index b98dcb892409..94b3ca562283 100644 --- a/barretenberg/cpp/src/barretenberg/nodejs_module/lmdb_store/lmdb_store_wrapper.cpp +++ b/barretenberg/cpp/src/barretenberg/nodejs_module/lmdb_store/lmdb_store_wrapper.cpp @@ -3,10 +3,8 @@ #include "barretenberg/lmdblib/types.hpp" #include "barretenberg/nodejs_module/lmdb_store/lmdb_store_message.hpp" #include "napi.h" -#include #include #include -#include #include #include #include @@ -117,52 +115,9 @@ GetResponse LMDBStoreWrapper::get(const GetRequest& req) HasResponse LMDBStoreWrapper::has(const HasRequest& req) { - auto string_cmp = [](const std::vector& a, const std::vector& b) { - return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); - }; - verify_store(); - std::set key_set(string_cmp); - for (const auto& entry : req.entries) { - key_set.insert(entry.first); - } - - lmdblib::KeysVector keys(key_set.begin(), key_set.end()); - lmdblib::OptionalValuesVector vals; - _store->get(keys, vals, req.db); - std::vector exists; - - for (const auto& entry : req.entries) { - const auto& key = entry.first; - const auto& requested_values = entry.second; - - const auto& key_it = std::find(keys.begin(), keys.end(), key); - if (key_it == keys.end()) { - // this shouldn't happen. It means we missed a key when we created the key_set - exists.push_back(false); - continue; - } - - // should be fine to convert this to an index in the array? - const auto& values = vals[static_cast(key_it - keys.begin())]; - - if (!values.has_value()) { - exists.push_back(false); - continue; - } - - // client just wanted to know if the key exists - if (!requested_values.has_value()) { - exists.push_back(true); - continue; - } - - exists.push_back(std::all_of(requested_values->begin(), requested_values->end(), [&](const auto& val) { - return std::find(values->begin(), values->end(), val) != values->begin(); - })); - } - + _store->has(req.entries, exists, req.db); return { exists }; }