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
47 changes: 47 additions & 0 deletions barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
#include "barretenberg/lmdblib/lmdb_write_transaction.hpp"
#include "barretenberg/lmdblib/types.hpp"
#include "lmdb.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <stdexcept>

namespace bb::lmdblib {
Expand Down Expand Up @@ -176,6 +178,51 @@ void LMDBStore::get(KeysVector& keys, OptionalValuesVector& values, LMDBDatabase
}
}

void LMDBStore::has(const KeyOptionalValuesVector& entries, std::vector<bool>& 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, decltype(string_cmp)> 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<size_t>(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);
Expand Down
1 change: 1 addition & 0 deletions barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class LMDBStore : public LMDBStoreBase {

void put(std::vector<PutData>& data);
void get(KeysVector& keys, OptionalValuesVector& values, const std::string& name);
void has(const KeyOptionalValuesVector& entries, std::vector<bool>& results, const std::string& name);

Cursor::Ptr create_cursor(ReadTransaction::SharedPtr tx, const std::string& dbName);

Expand Down
129 changes: 129 additions & 0 deletions barretenberg/cpp/src/barretenberg/lmdblib/lmdb_store.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<bool> results;
store->has(entries, results, name);

EXPECT_EQ(results.size(), 2UL);
EXPECT_TRUE(results[0]);
EXPECT_TRUE(results[1]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
#include "barretenberg/lmdblib/types.hpp"
#include "barretenberg/nodejs_module/lmdb_store/lmdb_store_message.hpp"
#include "napi.h"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <iterator>
#include <memory>
#include <optional>
#include <ratio>
Expand Down Expand Up @@ -117,52 +115,9 @@ GetResponse LMDBStoreWrapper::get(const GetRequest& req)

HasResponse LMDBStoreWrapper::has(const HasRequest& req)
{
auto string_cmp = [](const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) {
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
};

verify_store();
std::set<lmdblib::Key, decltype(string_cmp)> 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<bool> 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<size_t>(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 };
}

Expand Down
Loading