Auto generate vertex/edge ids

Reviewers: buda

Reviewed By: buda

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1406
This commit is contained in:
Dominik Gleich 2018-05-25 15:16:32 +02:00
parent adda7d1200
commit c294127065
4 changed files with 82 additions and 23 deletions

View File

@ -15,6 +15,18 @@
#include "utils/atomic.hpp"
#include "utils/on_scope_exit.hpp"
// Autogenerate property with Vertex ID
DEFINE_bool(
generate_vertex_ids, false,
"If enabled database will automatically generate Ids as properties of "
"vertices, which can be accessed using the `Id` cypher function.");
// Autogenerate property with Edge ID
DEFINE_bool(
generate_edge_ids, false,
"If enabled database will automatically generate Ids as properties of "
"edges, which can be accessed using the `Id` cypher function.");
namespace database {
GraphDbAccessor::GraphDbAccessor(GraphDb &db)
@ -65,7 +77,7 @@ VertexAccessor GraphDbAccessor::InsertVertex(
std::experimental::optional<gid::Gid> requested_gid) {
DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted";
auto gid = db_.storage().vertex_generator_.Next(requested_gid);
int64_t gid = db_.storage().vertex_generator_.Next(requested_gid);
auto vertex_vlist = new mvcc::VersionList<Vertex>(transaction_, gid);
bool success =
@ -74,7 +86,10 @@ VertexAccessor GraphDbAccessor::InsertVertex(
<< gid;
wal().Emplace(
database::StateDelta::CreateVertex(transaction_.id_, vertex_vlist->gid_));
return VertexAccessor(vertex_vlist, *this);
auto va = VertexAccessor(vertex_vlist, *this);
if (FLAGS_generate_vertex_ids)
va.PropsSet(Property(PropertyValueStore::IdPropertyName), gid);
return va;
}
VertexAccessor GraphDbAccessor::InsertVertexIntoRemote(
@ -391,16 +406,9 @@ EdgeAccessor GraphDbAccessor::InsertEdge(
Vertex *from_updated;
if (from.is_local()) {
auto gid = db_.storage().edge_generator_.Next(requested_gid);
edge_address = new mvcc::VersionList<Edge>(
transaction_, gid, from.address(), to.address(), edge_type);
// We need to insert edge_address to edges_ before calling update since
// update can throw and edge_vlist will not be garbage collected if it is
// not in edges_ skiplist.
bool success =
db_.storage().edges_.access().insert(gid, edge_address.local()).second;
CHECK(success) << "Attempting to insert an edge with an existing GID: "
<< gid;
auto edge_accessor =
InsertOnlyEdge(from.address(), to.address(), edge_type, requested_gid);
edge_address = edge_accessor.address(),
from.SwitchNew();
from_updated = &from.update();
@ -409,7 +417,7 @@ EdgeAccessor GraphDbAccessor::InsertEdge(
// `CREATE_EDGE`, but always have it split into 3 parts (edge insertion,
// in/out modification).
wal().Emplace(database::StateDelta::CreateEdge(
transaction_.id_, gid, from.gid(), to.gid(), edge_type,
transaction_.id_, edge_accessor.gid(), from.gid(), to.gid(), edge_type,
EdgeTypeName(edge_type)));
} else {
@ -467,7 +475,7 @@ EdgeAccessor GraphDbAccessor::InsertOnlyEdge(
std::experimental::optional<gid::Gid> requested_gid) {
CHECK(from.is_local())
<< "`from` address should be local when calling InsertOnlyEdge";
auto gid = db_.storage().edge_generator_.Next(requested_gid);
int64_t gid = db_.storage().edge_generator_.Next(requested_gid);
auto edge_vlist =
new mvcc::VersionList<Edge>(transaction_, gid, from, to, edge_type);
// We need to insert edge_vlist to edges_ before calling update since update
@ -476,7 +484,10 @@ EdgeAccessor GraphDbAccessor::InsertOnlyEdge(
bool success = db_.storage().edges_.access().insert(gid, edge_vlist).second;
CHECK(success) << "Attempting to insert an edge with an existing GID: "
<< gid;
return EdgeAccessor(edge_vlist, *this, from, to, edge_type);
auto ea = EdgeAccessor(edge_vlist, *this, from, to, edge_type);
if (FLAGS_generate_edge_ids)
ea.PropsSet(Property(PropertyValueStore::IdPropertyName), gid);
return ea;
}
int64_t GraphDbAccessor::EdgesCount() const {

View File

@ -624,16 +624,32 @@ TypedValue WorkerId(const std::vector<TypedValue> &args,
}
TypedValue Id(const std::vector<TypedValue> &args,
database::GraphDbAccessor &) {
database::GraphDbAccessor &dba) {
if (args.size() != 1U) {
throw QueryRuntimeException("Id takes one argument");
}
auto &arg = args[0];
switch (arg.type()) {
case TypedValue::Type::Vertex:
return static_cast<int64_t>(arg.ValueVertex().gid());
case TypedValue::Type::Edge:
return static_cast<int64_t>(arg.ValueEdge().gid());
case TypedValue::Type::Vertex: {
auto id = arg.ValueVertex().PropsAt(
dba.Property(PropertyValueStore::IdPropertyName));
if (id.IsNull()) {
throw QueryRuntimeException(
"Ids are not set on vertices, have a look at flags to "
"automatically generate them.");
}
return id.Value<int64_t>();
}
case TypedValue::Type::Edge: {
auto id = arg.ValueEdge().PropsAt(
dba.Property(PropertyValueStore::IdPropertyName));
if (id.IsNull()) {
throw QueryRuntimeException(
"Ids are not set on edges, have a look at flags to "
"automatically generate them.");
}
return id.Value<int64_t>();
}
default:
throw QueryRuntimeException("Id argument must be a vertex or edge");
}

View File

@ -33,6 +33,10 @@ class PropertyValueStore {
using Location = storage::Location;
public:
// Property name which will be used to store vertex/edge ids inside property
// value store
static constexpr char IdPropertyName[] = "__id__";
PropertyValueStore() = default;
PropertyValueStore(const PropertyValueStore &old) {

View File

@ -4,6 +4,7 @@
#include <unordered_map>
#include <vector>
#include "gflags/gflags.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@ -24,6 +25,9 @@ using query::test_common::ToList;
using testing::ElementsAre;
using testing::UnorderedElementsAre;
DECLARE_bool(generate_vertex_ids);
DECLARE_bool(generate_edge_ids);
namespace {
struct NoContextExpressionEvaluator {
@ -1364,19 +1368,43 @@ TEST(ExpressionEvaluator, FunctionIndexInfo) {
}
}
TEST(ExpressionEvaluator, FunctionUid) {
TEST(ExpressionEvaluator, FunctionIdNoGeneration) {
database::SingleNode db;
EXPECT_THROW(EvaluateFunction("ID", {}, db), QueryRuntimeException);
database::GraphDbAccessor dba(db);
auto va = dba.InsertVertex();
auto ea = dba.InsertEdge(va, va, dba.EdgeType("edge"));
// Unable to get id if they are not set
EXPECT_THROW(EvaluateFunction("ID", {va}, db), QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("ID", {ea}, db), QueryRuntimeException);
}
TEST(ExpressionEvaluator, FunctionIdGenerateVertexIds) {
FLAGS_generate_vertex_ids = true;
database::SingleNode db;
database::GraphDbAccessor dba(db);
auto va = dba.InsertVertex();
auto ea = dba.InsertEdge(va, va, dba.EdgeType("edge"));
EXPECT_EQ(EvaluateFunction("ID", {va}, db).Value<int64_t>(), 0);
EXPECT_EQ(EvaluateFunction("ID", {ea}, db).Value<int64_t>(), 0);
EXPECT_THROW(EvaluateFunction("ID", {ea}, db), QueryRuntimeException);
auto vb = dba.InsertVertex();
auto eb = dba.InsertEdge(vb, vb, dba.EdgeType("edge"));
EXPECT_EQ(EvaluateFunction("ID", {vb}, db).Value<int64_t>(), 1024);
FLAGS_generate_vertex_ids = false;
}
TEST(ExpressionEvaluator, FunctionIdGenerateEdgeIds) {
FLAGS_generate_edge_ids = true;
database::SingleNode db;
database::GraphDbAccessor dba(db);
auto va = dba.InsertVertex();
auto ea = dba.InsertEdge(va, va, dba.EdgeType("edge"));
EXPECT_THROW(EvaluateFunction("ID", {va}, db), QueryRuntimeException);
EXPECT_EQ(EvaluateFunction("ID", {ea}, db).Value<int64_t>(), 0);
auto eb = dba.InsertEdge(va, va, dba.EdgeType("edge"));
EXPECT_EQ(EvaluateFunction("ID", {eb}, db).Value<int64_t>(), 1024);
FLAGS_generate_edge_ids = false;
}
} // namespace