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:
Matej Ferencevic 2019-07-18 16:17:41 +02:00
parent fa4c303af6
commit e9a7623288
5 changed files with 151 additions and 0 deletions

View 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

View File

@ -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() {

View File

@ -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_;

View File

@ -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(

View 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");
}