diff --git a/src/database/graph_db_accessor.cpp b/src/database/graph_db_accessor.cpp index d03bd06e5..866b98267 100644 --- a/src/database/graph_db_accessor.cpp +++ b/src/database/graph_db_accessor.cpp @@ -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 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(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( - 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 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(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 { diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp index f44ed8929..53cf70c15 100644 --- a/src/query/interpret/awesome_memgraph_functions.cpp +++ b/src/query/interpret/awesome_memgraph_functions.cpp @@ -624,16 +624,32 @@ TypedValue WorkerId(const std::vector &args, } TypedValue Id(const std::vector &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(arg.ValueVertex().gid()); - case TypedValue::Type::Edge: - return static_cast(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(); + } + 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(); + } default: throw QueryRuntimeException("Id argument must be a vertex or edge"); } diff --git a/src/storage/property_value_store.hpp b/src/storage/property_value_store.hpp index 11bae6095..e94c63837 100644 --- a/src/storage/property_value_store.hpp +++ b/src/storage/property_value_store.hpp @@ -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) { diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp index deb56c81b..786e36387 100644 --- a/tests/unit/query_expression_evaluator.cpp +++ b/tests/unit/query_expression_evaluator.cpp @@ -4,6 +4,7 @@ #include #include +#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(), 0); - EXPECT_EQ(EvaluateFunction("ID", {ea}, db).Value(), 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(), 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(), 0); + + auto eb = dba.InsertEdge(va, va, dba.EdgeType("edge")); EXPECT_EQ(EvaluateFunction("ID", {eb}, db).Value(), 1024); + FLAGS_generate_edge_ids = false; } } // namespace