Implement name to id mapper in storage v2
Reviewers: teon.banek, mtomic Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2217
This commit is contained in:
parent
fa4c303af6
commit
e9a7623288
src/storage/v2
tests/unit
81
src/storage/v2/name_id_mapper.hpp
Normal file
81
src/storage/v2/name_id_mapper.hpp
Normal file
@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace storage {
|
||||
|
||||
class NameIdMapper final {
|
||||
private:
|
||||
struct MapNameToId {
|
||||
std::string name;
|
||||
uint64_t id;
|
||||
|
||||
bool operator<(const MapNameToId &other) { return name < other.name; }
|
||||
bool operator==(const MapNameToId &other) { return name == other.name; }
|
||||
|
||||
bool operator<(const std::string &other) { return name < other; }
|
||||
bool operator==(const std::string &other) { return name == other; }
|
||||
};
|
||||
|
||||
struct MapIdToName {
|
||||
uint64_t id;
|
||||
std::string name;
|
||||
|
||||
bool operator<(const MapIdToName &other) { return id < other.id; }
|
||||
bool operator==(const MapIdToName &other) { return id == other.id; }
|
||||
|
||||
bool operator<(uint64_t other) { return id < other; }
|
||||
bool operator==(uint64_t other) { return id == other; }
|
||||
};
|
||||
|
||||
public:
|
||||
uint64_t NameToId(const std::string &name) {
|
||||
auto name_to_id_acc = name_to_id_.access();
|
||||
auto found = name_to_id_acc.find(name);
|
||||
uint64_t id;
|
||||
if (found == name_to_id_acc.end()) {
|
||||
uint64_t new_id = counter_.fetch_add(1, std::memory_order_acq_rel);
|
||||
// Try to insert the mapping with the `new_id`, but use the id that is in
|
||||
// the object itself. The object that cointains the mapping is designed to
|
||||
// be a map, so that if the inserted name already exists `insert` will
|
||||
// return an iterator to the existing item. This prevents assignment of
|
||||
// two IDs to the same name when the mapping is being inserted
|
||||
// concurrently from two threads. One ID is wasted in that case, though.
|
||||
id = name_to_id_acc.insert({name, new_id}).first->id;
|
||||
} else {
|
||||
id = found->id;
|
||||
}
|
||||
auto id_to_name_acc = id_to_name_.access();
|
||||
// We have to try to insert the ID to name mapping even if we are not the
|
||||
// one who assigned the ID because we have to make sure that after this
|
||||
// method returns that both mappings exist.
|
||||
id_to_name_acc.insert({id, name});
|
||||
return id;
|
||||
}
|
||||
|
||||
// NOTE: Currently this function returns a `const std::string &` instead of a
|
||||
// `std::string` to avoid making unnecessary copies of the string.
|
||||
// Usually, this wouldn't be correct because the accessor to the
|
||||
// `utils::SkipList` is destroyed in this function and that removes the
|
||||
// guarantee that the reference to the value contained in the list will be
|
||||
// valid.
|
||||
// Currently, we never delete anything from the `utils::SkipList` so the
|
||||
// references will always be valid. If you change this class to remove unused
|
||||
// names, be sure to change the signature of this function.
|
||||
const std::string &IdToName(uint64_t id) {
|
||||
auto id_to_name_acc = id_to_name_.access();
|
||||
auto result = id_to_name_acc.find(id);
|
||||
CHECK(result != id_to_name_acc.end())
|
||||
<< "Trying to get a name for an invalid ID!";
|
||||
return result->name;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<uint64_t> counter_{0};
|
||||
utils::SkipList<MapNameToId> name_to_id_;
|
||||
utils::SkipList<MapIdToName> id_to_name_;
|
||||
};
|
||||
} // namespace storage
|
@ -274,6 +274,26 @@ Result<bool> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) {
|
||||
return Result<bool>{true};
|
||||
}
|
||||
|
||||
const std::string &Storage::Accessor::LabelToName(uint64_t label) {
|
||||
return storage_->name_id_mapper_.IdToName(label);
|
||||
}
|
||||
const std::string &Storage::Accessor::PropertyToName(uint64_t property) {
|
||||
return storage_->name_id_mapper_.IdToName(property);
|
||||
}
|
||||
const std::string &Storage::Accessor::EdgeTypeToName(uint64_t edge_type) {
|
||||
return storage_->name_id_mapper_.IdToName(edge_type);
|
||||
}
|
||||
|
||||
uint64_t Storage::Accessor::NameToLabel(const std::string &name) {
|
||||
return storage_->name_id_mapper_.NameToId(name);
|
||||
}
|
||||
uint64_t Storage::Accessor::NameToProperty(const std::string &name) {
|
||||
return storage_->name_id_mapper_.NameToId(name);
|
||||
}
|
||||
uint64_t Storage::Accessor::NameToEdgeType(const std::string &name) {
|
||||
return storage_->name_id_mapper_.NameToId(name);
|
||||
}
|
||||
|
||||
void Storage::Accessor::AdvanceCommand() { ++transaction_.command_id; }
|
||||
|
||||
void Storage::Accessor::Commit() {
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "storage/v2/edge.hpp"
|
||||
#include "storage/v2/edge_accessor.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/name_id_mapper.hpp"
|
||||
#include "storage/v2/result.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "storage/v2/vertex.hpp"
|
||||
@ -79,6 +80,14 @@ class Storage final {
|
||||
|
||||
Result<bool> DeleteEdge(EdgeAccessor *edge);
|
||||
|
||||
const std::string &LabelToName(uint64_t label);
|
||||
const std::string &PropertyToName(uint64_t property);
|
||||
const std::string &EdgeTypeToName(uint64_t edge_type);
|
||||
|
||||
uint64_t NameToLabel(const std::string &name);
|
||||
uint64_t NameToProperty(const std::string &name);
|
||||
uint64_t NameToEdgeType(const std::string &name);
|
||||
|
||||
void AdvanceCommand();
|
||||
|
||||
void Commit();
|
||||
@ -113,6 +122,8 @@ class Storage final {
|
||||
// whatever.
|
||||
CommitLog commit_log_;
|
||||
|
||||
NameIdMapper name_id_mapper_;
|
||||
|
||||
utils::SpinLock committed_transactions_lock_;
|
||||
std::list<Transaction> committed_transactions_;
|
||||
|
||||
|
@ -316,6 +316,9 @@ target_link_libraries(${test_prefix}storage_v2 mg-storage-v2)
|
||||
add_unit_test(storage_v2_gc.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v2_gc mg-storage-v2)
|
||||
|
||||
add_unit_test(storage_v2_name_id_mapper.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v2_name_id_mapper mg-storage-v2)
|
||||
|
||||
# Test LCP
|
||||
|
||||
add_custom_command(
|
||||
|
36
tests/unit/storage_v2_name_id_mapper.cpp
Normal file
36
tests/unit/storage_v2_name_id_mapper.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "storage/v2/name_id_mapper.hpp"
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST(NameIdMapper, Basic) {
|
||||
storage::NameIdMapper mapper;
|
||||
|
||||
ASSERT_EQ(mapper.NameToId("n1"), 0);
|
||||
ASSERT_EQ(mapper.NameToId("n2"), 1);
|
||||
ASSERT_EQ(mapper.NameToId("n1"), 0);
|
||||
ASSERT_EQ(mapper.NameToId("n2"), 1);
|
||||
ASSERT_EQ(mapper.NameToId("n3"), 2);
|
||||
|
||||
ASSERT_EQ(mapper.IdToName(0), "n1");
|
||||
ASSERT_EQ(mapper.IdToName(1), "n2");
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST(NameIdMapper, Correctness) {
|
||||
storage::NameIdMapper mapper;
|
||||
|
||||
ASSERT_DEATH(mapper.IdToName(0), "");
|
||||
ASSERT_EQ(mapper.NameToId("n1"), 0);
|
||||
ASSERT_EQ(mapper.IdToName(0), "n1");
|
||||
|
||||
ASSERT_DEATH(mapper.IdToName(1), "");
|
||||
ASSERT_EQ(mapper.NameToId("n2"), 1);
|
||||
ASSERT_EQ(mapper.IdToName(1), "n2");
|
||||
|
||||
ASSERT_EQ(mapper.NameToId("n1"), 0);
|
||||
ASSERT_EQ(mapper.NameToId("n2"), 1);
|
||||
|
||||
ASSERT_EQ(mapper.IdToName(1), "n2");
|
||||
ASSERT_EQ(mapper.IdToName(0), "n1");
|
||||
}
|
Loading…
Reference in New Issue
Block a user