diff --git a/include/_mgp.hpp b/include/_mgp.hpp index e9c85e24c..fd286b6c6 100644 --- a/include/_mgp.hpp +++ b/include/_mgp.hpp @@ -265,6 +265,11 @@ inline mgp_edge *graph_edge_set_to(struct mgp_graph *graph, struct mgp_edge *e, return MgInvoke(mgp_graph_edge_set_to, graph, e, new_to, memory); } +inline mgp_edge *graph_edge_change_type(struct mgp_graph *graph, struct mgp_edge *e, struct mgp_edge_type new_type, + mgp_memory *memory) { + return MgInvoke(mgp_graph_edge_change_type, graph, e, new_type, memory); +} + inline void graph_delete_edge(mgp_graph *graph, mgp_edge *edge) { MgInvokeVoid(mgp_graph_delete_edge, graph, edge); } inline mgp_vertex *graph_get_vertex_by_id(mgp_graph *g, mgp_vertex_id id, mgp_memory *memory) { diff --git a/include/mg_procedure.h b/include/mg_procedure.h index cda319727..ba964a807 100644 --- a/include/mg_procedure.h +++ b/include/mg_procedure.h @@ -911,6 +911,13 @@ enum mgp_error mgp_graph_edge_set_from(struct mgp_graph *graph, struct mgp_edge enum mgp_error mgp_graph_edge_set_to(struct mgp_graph *graph, struct mgp_edge *e, struct mgp_vertex *new_to, struct mgp_memory *memory, struct mgp_edge **result); +/// Change edge type +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by +/// another transaction. +enum mgp_error mgp_graph_edge_change_type(struct mgp_graph *graph, struct mgp_edge *e, struct mgp_edge_type new_type, + struct mgp_memory *memory, struct mgp_edge **result); + /// Delete an edge from the graph. /// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. /// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by diff --git a/include/mgp.hpp b/include/mgp.hpp index 8d6aa5bb0..bea3545bb 100644 --- a/include/mgp.hpp +++ b/include/mgp.hpp @@ -260,6 +260,8 @@ class Graph { void SetFrom(Relationship &relationship, const Node &new_from); /// @brief Changes a relationship to node. void SetTo(Relationship &relationship, const Node &new_to); + /// @brief Changes the relationship type. + void ChangeType(Relationship &relationship, std::string_view new_type); /// @brief Deletes a relationship from the graph. void DeleteRelationship(const Relationship &relationship); @@ -2030,6 +2032,13 @@ inline void Graph::SetTo(Relationship &relationship, const Node &new_to) { mgp::edge_destroy(edge); } +inline void Graph::ChangeType(Relationship &relationship, std::string_view new_type) { + mgp_edge *edge = mgp::MemHandlerCallback(mgp::graph_edge_change_type, graph_, relationship.ptr_, + mgp_edge_type{.name = new_type.data()}); + relationship = Relationship(edge); + mgp::edge_destroy(edge); +} + inline void Graph::DeleteRelationship(const Relationship &relationship) { mgp::graph_delete_edge(graph_, relationship.ptr_); } diff --git a/src/query/db_accessor.cpp b/src/query/db_accessor.cpp index cef047a24..cf914b4e3 100644 --- a/src/query/db_accessor.cpp +++ b/src/query/db_accessor.cpp @@ -94,6 +94,14 @@ storage::Result SubgraphDbAccessor::EdgeSetTo(EdgeAccessor *edge, return result; } +storage::Result SubgraphDbAccessor::EdgeChangeType(EdgeAccessor *edge, + storage::EdgeTypeId new_edge_type) { + if (!this->graph_->ContainsEdge(*edge)) { + throw std::logic_error{"Projected graph must contain edge!"}; + } + return db_accessor_.EdgeChangeType(edge, new_edge_type); +} + storage::Result> SubgraphDbAccessor::RemoveVertex( SubgraphVertexAccessor *subgraphvertex_accessor) { VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_; diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp index 1798e36e5..3725d9848 100644 --- a/src/query/db_accessor.hpp +++ b/src/query/db_accessor.hpp @@ -414,6 +414,12 @@ class DbAccessor final { return EdgeAccessor(*changed_edge); } + storage::Result EdgeChangeType(EdgeAccessor *edge, storage::EdgeTypeId new_edge_type) { + auto changed_edge = accessor_->EdgeChangeType(&edge->impl_, new_edge_type); + if (changed_edge.HasError()) return storage::Result{changed_edge.GetError()}; + return EdgeAccessor(*changed_edge); + } + storage::Result> RemoveEdge(EdgeAccessor *edge) { auto res = accessor_->DeleteEdge(&edge->impl_); if (res.HasError()) { @@ -655,6 +661,8 @@ class SubgraphDbAccessor final { storage::Result EdgeSetTo(EdgeAccessor *edge, SubgraphVertexAccessor *new_to); + storage::Result EdgeChangeType(EdgeAccessor *edge, storage::EdgeTypeId new_edge_type); + storage::Result>>> DetachRemoveVertex( SubgraphVertexAccessor *vertex_accessor); diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index 43e1a3232..9841f318a 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -2821,6 +2821,59 @@ mgp_error mgp_graph_edge_set_to(struct mgp_graph *graph, struct mgp_edge *e, str return WrapExceptions([&]() -> mgp_edge * { return EdgeSet(e, new_to, memory, graph, false); }, result); } +mgp_error mgp_graph_edge_change_type(mgp_graph *graph, mgp_edge *e, mgp_edge_type new_type, mgp_memory *memory, + mgp_edge **result) { + return WrapExceptions( + [&] { + auto *ctx = graph->ctx; +#ifdef MG_ENTERPRISE + if (memgraph::license::global_license_checker.IsEnterpriseValidFast() && ctx && ctx->auth_checker && + !ctx->auth_checker->Has(e->impl, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) { + throw AuthorizationException{"Insufficient permissions for changing the edge type!"}; + } +#endif + if (!MgpGraphIsMutable(*graph)) { + throw ImmutableObjectException{"Cannot change an edge in an immutable graph!"}; + } + + const auto maybe_edge = std::visit( + [e, &new_type](auto *impl) { return impl->EdgeChangeType(&e->impl, impl->NameToEdgeType(new_type.name)); }, + graph->impl); + if (maybe_edge.HasError()) { + switch (maybe_edge.GetError()) { + case memgraph::storage::Error::NONEXISTENT_OBJECT: + LOG_FATAL("Query modules shouldn't have access to nonexistent objects when changing the edge type!"); + case memgraph::storage::Error::DELETED_OBJECT: + case memgraph::storage::Error::PROPERTIES_DISABLED: + case memgraph::storage::Error::VERTEX_HAS_EDGES: + LOG_FATAL("Unexpected error when changing the edge type."); + case memgraph::storage::Error::SERIALIZATION_ERROR: + throw SerializationException{"Cannot serialize changing the edge type."}; + } + } + + if (ctx->trigger_context_collector) { + ctx->trigger_context_collector->RegisterDeletedObject(e->impl); + ctx->trigger_context_collector->RegisterCreatedObject(*maybe_edge); + } + + return std::visit( + memgraph::utils::Overloaded{ + [&memory, &maybe_edge, &graph](memgraph::query::DbAccessor *) -> mgp_edge * { + return NewRawMgpObject(memory->impl, maybe_edge.GetValue(), graph); + }, + [&memory, &maybe_edge, &graph](memgraph::query::SubgraphDbAccessor *db_impl) -> mgp_edge * { + const auto v_from = + memgraph::query::SubgraphVertexAccessor(maybe_edge.GetValue().From(), db_impl->getGraph()); + const auto v_to = + memgraph::query::SubgraphVertexAccessor(maybe_edge.GetValue().To(), db_impl->getGraph()); + return NewRawMgpObject(memory->impl, maybe_edge.GetValue(), v_from, v_to, graph); + }}, + graph->impl); + }, + result); +} + mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) { return WrapExceptions([=] { auto *ctx = graph->ctx; diff --git a/src/storage/v2/disk/storage.cpp b/src/storage/v2/disk/storage.cpp index 92edbbb85..35beb4fd9 100644 --- a/src/storage/v2/disk/storage.cpp +++ b/src/storage/v2/disk/storage.cpp @@ -962,6 +962,11 @@ Result DiskStorage::DiskAccessor::EdgeSetTo(EdgeAccessor * /*edge* return Error::NONEXISTENT_OBJECT; } +Result DiskStorage::DiskAccessor::EdgeChangeType(EdgeAccessor * /*edge*/, EdgeTypeId /*new_edge_type*/) { + MG_ASSERT(false, "EdgeChangeType is currently only implemented for InMemory storage"); + return Error::NONEXISTENT_OBJECT; +} + bool DiskStorage::WriteVertexToVertexColumnFamily(Transaction *transaction, const Vertex &vertex) { MG_ASSERT(transaction->commit_timestamp, "Writing vertex to disk but commit timestamp not set."); auto commit_ts = transaction->commit_timestamp->load(std::memory_order_relaxed); diff --git a/src/storage/v2/disk/storage.hpp b/src/storage/v2/disk/storage.hpp index 279805cb0..9bdf9f049 100644 --- a/src/storage/v2/disk/storage.hpp +++ b/src/storage/v2/disk/storage.hpp @@ -125,6 +125,8 @@ class DiskStorage final : public Storage { Result EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override; + Result EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) override; + bool LabelIndexExists(LabelId label) const override { auto *disk_storage = static_cast(storage_); return disk_storage->indices_.label_index_->IndexExists(label); diff --git a/src/storage/v2/inmemory/storage.cpp b/src/storage/v2/inmemory/storage.cpp index 0c0a8bbbe..27d4e9feb 100644 --- a/src/storage/v2/inmemory/storage.cpp +++ b/src/storage/v2/inmemory/storage.cpp @@ -639,6 +639,79 @@ Result InMemoryStorage::InMemoryAccessor::EdgeSetTo(EdgeAccessor * return EdgeAccessor(edge_ref, edge_type, from_vertex, new_to_vertex, storage_, &transaction_); } +Result InMemoryStorage::InMemoryAccessor::EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) { + OOMExceptionEnabler oom_exception; + MG_ASSERT(&transaction_ == edge->transaction_, + "EdgeAccessor must be from the same transaction as the storage " + "accessor when changing the edge type!"); + + auto &edge_ref = edge->edge_; + auto &edge_type = edge->edge_type_; + + std::unique_lock guard; + if (config_.properties_on_edges) { + auto *edge_ptr = edge_ref.ptr; + guard = std::unique_lock{edge_ptr->lock}; + + if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; + if (edge_ptr->deleted) return Error::DELETED_OBJECT; + } + + auto *from_vertex = edge->from_vertex_; + auto *to_vertex = edge->to_vertex_; + + // Obtain the locks by `gid` order to avoid lock cycles. + auto guard_from = std::unique_lock{from_vertex->lock, std::defer_lock}; + auto guard_to = std::unique_lock{to_vertex->lock, std::defer_lock}; + if (from_vertex->gid < to_vertex->gid) { + guard_from.lock(); + guard_to.lock(); + } else if (from_vertex->gid > to_vertex->gid) { + guard_to.lock(); + guard_from.lock(); + } else { + // The vertices are the same vertex, only lock one. + guard_from.lock(); + } + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); + + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + MG_ASSERT(!to_vertex->deleted, "Invalid database state!"); + + auto change_edge_type_in_storage = [&edge_type, &edge_ref, &new_edge_type, this](auto *vertex, auto *edges) { + std::tuple link(edge_type, vertex, edge_ref); + auto it = std::find(edges->begin(), edges->end(), link); + if (config_.properties_on_edges) { + MG_ASSERT(it != edges->end(), "Invalid database state!"); + } else if (it == edges->end()) { + return false; + } + *it = std::tuple{new_edge_type, vertex, edge_ref}; + return true; + }; + + auto op1 = change_edge_type_in_storage(to_vertex, &from_vertex->out_edges); + auto op2 = change_edge_type_in_storage(from_vertex, &to_vertex->in_edges); + + MG_ASSERT((op1 && op2), "Invalid database state!"); + + // "deleting" old edge + CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); + + // "adding" new edge + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), new_edge_type, to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), new_edge_type, from_vertex, edge_ref); + + // edge type is not used while invalidating cache so we can only call it once + transaction_.manyDeltasCache.Invalidate(from_vertex, new_edge_type, EdgeDirection::OUT); + transaction_.manyDeltasCache.Invalidate(to_vertex, new_edge_type, EdgeDirection::IN); + + return EdgeAccessor(edge_ref, new_edge_type, from_vertex, to_vertex, storage_, &transaction_); +} + // NOLINTNEXTLINE(google-default-arguments) utils::BasicResult InMemoryStorage::InMemoryAccessor::Commit( const std::optional desired_commit_timestamp) { diff --git a/src/storage/v2/inmemory/storage.hpp b/src/storage/v2/inmemory/storage.hpp index 737ea40eb..153409126 100644 --- a/src/storage/v2/inmemory/storage.hpp +++ b/src/storage/v2/inmemory/storage.hpp @@ -183,6 +183,8 @@ class InMemoryStorage final : public Storage { Result EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override; + Result EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) override; + bool LabelIndexExists(LabelId label) const override { return static_cast(storage_)->indices_.label_index_->IndexExists(label); } diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index 79c61fd16..0f4195aee 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -197,6 +197,8 @@ class Storage { virtual Result EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) = 0; + virtual Result EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) = 0; + virtual Result> DeleteEdge(EdgeAccessor *edge); virtual bool LabelIndexExists(LabelId label) const = 0; diff --git a/tests/e2e/transaction_rollback/CMakeLists.txt b/tests/e2e/transaction_rollback/CMakeLists.txt index 588e1bedc..a64d3bfeb 100644 --- a/tests/e2e/transaction_rollback/CMakeLists.txt +++ b/tests/e2e/transaction_rollback/CMakeLists.txt @@ -1,5 +1,5 @@ function(transaction_rollback_e2e_python_files FILE_NAME) - copy_e2e_python_files(transaction_abort ${FILE_NAME}) + copy_e2e_python_files(transaction_rollback ${FILE_NAME}) endfunction() transaction_rollback_e2e_python_files(common.py) diff --git a/tests/e2e/transaction_rollback/procedures/procedures.cpp b/tests/e2e/transaction_rollback/procedures/procedures.cpp index 5cf9bedd5..1562d5c94 100644 --- a/tests/e2e/transaction_rollback/procedures/procedures.cpp +++ b/tests/e2e/transaction_rollback/procedures/procedures.cpp @@ -17,13 +17,14 @@ namespace { constexpr std::string_view ProcedureFrom = "set_from"; constexpr std::string_view ProcedureTo = "set_to"; +constexpr std::string_view ProcedureChangeType = "change_type"; void SetFrom(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { mgp::MemoryDispatcherGuard guard(memory); const auto arguments = mgp::List(args); try { - auto rel = arguments[0].ValueRelationship(); - auto new_from = arguments[0].ValueNode(); + auto rel{arguments[0].ValueRelationship()}; + auto new_from{arguments[1].ValueNode()}; mgp::Graph graph{memgraph_graph}; graph.SetFrom(rel, new_from); } catch (const std::exception &e) { @@ -35,8 +36,8 @@ void SetTo(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_me mgp::MemoryDispatcherGuard guard(memory); const auto arguments = mgp::List(args); try { - auto rel = arguments[0].ValueRelationship(); - auto new_to = arguments[0].ValueNode(); + auto rel{arguments[0].ValueRelationship()}; + auto new_to{arguments[1].ValueNode()}; mgp::Graph graph{memgraph_graph}; graph.SetTo(rel, new_to); } catch (const std::exception &e) { @@ -44,6 +45,19 @@ void SetTo(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_me } } +void ChangeType(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::MemoryDispatcherGuard guard(memory); + const auto arguments = mgp::List(args); + try { + auto rel{arguments[0].ValueRelationship()}; + auto new_type{arguments[1].ValueString()}; + mgp::Graph graph{memgraph_graph}; + graph.ChangeType(rel, new_type); + } catch (const std::exception &e) { + return; + } +} + } // namespace // Each module needs to define mgp_init_module function. @@ -61,6 +75,10 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem SetTo, ProcedureTo, mgp::ProcedureType::Write, {mgp::Parameter("relationship", mgp::Type::Relationship), mgp::Parameter("node_to", mgp::Type::Node)}, {}, module, memory); + mgp::AddProcedure( + ChangeType, ProcedureChangeType, mgp::ProcedureType::Write, + {mgp::Parameter("relationship", mgp::Type::Relationship), mgp::Parameter("new_type", mgp::Type::String)}, {}, + module, memory); } catch (const std::exception &e) { return 1; diff --git a/tests/e2e/transaction_rollback/transaction.py b/tests/e2e/transaction_rollback/transaction.py index ee204de8d..db2969c0b 100644 --- a/tests/e2e/transaction_rollback/transaction.py +++ b/tests/e2e/transaction_rollback/transaction.py @@ -9,6 +9,13 @@ # by the Apache License, Version 2.0, included in the file # licenses/APL.txt. +""" +Tests here work by executing the wanted procedure twice, +the first time the action will be commited to confirm the procedure executed +and doesn't crash for other reasons +and the second time the action will be rollbacked. +""" + import sys import pytest @@ -26,13 +33,27 @@ def test_change_from_rollback(connection): cursor, "MATCH (n:Node1)-[r:Relationship]->(m:Node2) MATCH (k:Node3) CALL transaction_rollback.set_from(r, k);", ) - connection.rollback() + connection.commit() - result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m")) - assert len(result) == 1 - node_from, rel, node_to = result[0] - assert list(node_from.labels)[0] == "Node1" - assert list(node_to.labels)[0] == "Node2" + def compare(from_label: str, to_label: str): + result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m")) + assert len(result) == 1 + node_from, _, node_to = result[0] + assert list(node_from.labels)[0] == from_label + assert list(node_to.labels)[0] == to_label + + compare("Node3", "Node2") + + execute_and_fetch_all( + cursor, + "MATCH (n:Node3)-[r:Relationship]->(m:Node2) MATCH (k:Node1) CALL transaction_rollback.set_from(r, k);", + ) + connection.rollback() + compare("Node3", "Node2") + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n;") + connection.rollback() + compare("Node3", "Node2") def test_change_to_rollback(connection): @@ -45,13 +66,59 @@ def test_change_to_rollback(connection): execute_and_fetch_all( cursor, "MATCH (n:Node1)-[r:Relationship]->(m:Node2) MATCH (k:Node3) CALL transaction_rollback.set_to(r, k);" ) + connection.commit() + + def compare(from_label: str, to_label: str): + result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m")) + assert len(result) == 1 + node_from, _, node_to = result[0] + assert list(node_from.labels)[0] == from_label + assert list(node_to.labels)[0] == to_label + + compare("Node1", "Node3") + + execute_and_fetch_all( + cursor, "MATCH (n:Node1)-[r:Relationship]->(m:Node3) MATCH (k:Node2) CALL transaction_rollback.set_to(r, k);" + ) + connection.rollback() + compare("Node1", "Node3") + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n;") + connection.rollback() + compare("Node1", "Node3") + + +def test_change_rel_type_rollback(connection): + cursor = connection.cursor() + + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + execute_and_fetch_all(cursor, "CREATE (n:Node1) CREATE (m:Node2) CREATE (n)-[:Relationship]->(m);") + connection.commit() + + execute_and_fetch_all( + cursor, "MATCH (n:Node1)-[r:Relationship]->(m:Node2) CALL transaction_rollback.change_type(r, 'Rel');" + ) + connection.commit() + + def compare(rel_type: str): + result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN r")) + assert len(result) == 1 + rel = result[0][0] + assert rel.type == rel_type + + compare("Rel") + + execute_and_fetch_all( + cursor, "MATCH (n:Node1)-[r:Rel]->(m:Node2) CALL transaction_rollback.change_type(r, 'Relationship');" + ) connection.rollback() - result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m")) - assert len(result) == 1 - node_from, rel, node_to = result[0] - assert list(node_from.labels)[0] == "Node1" - assert list(node_to.labels)[0] == "Node2" + compare("Rel") + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n;") + connection.rollback() + + compare("Rel") if __name__ == "__main__": diff --git a/tests/unit/cpp_api.cpp b/tests/unit/cpp_api.cpp index 8e7957c1f..ecad43487 100644 --- a/tests/unit/cpp_api.cpp +++ b/tests/unit/cpp_api.cpp @@ -694,6 +694,7 @@ TYPED_TEST(CppApiTestFixture, TestValueToString) { } TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) { + // Changing Relationship end vertex is not implemented for ondisk storage yet. if (std::is_same::value) { return; } @@ -706,6 +707,8 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) { auto node_3 = graph.CreateNode(); auto relationship = graph.CreateRelationship(node_1, node_2, "Edge"); + relationship.SetProperty("property", mgp::Value(true)); + ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true)); ASSERT_EQ(relationship.From().Id(), node_1.Id()); graph.SetFrom(relationship, node_3); @@ -713,9 +716,11 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) { ASSERT_EQ(std::string(relationship.Type()), "Edge"); ASSERT_EQ(relationship.From().Id(), node_3.Id()); ASSERT_EQ(relationship.To().Id(), node_2.Id()); + ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true)); } TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) { + // Changing Relationship start vertex is not implemented for ondisk storage yet. if (std::is_same::value) { return; } @@ -728,6 +733,8 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) { auto node_3 = graph.CreateNode(); auto relationship = graph.CreateRelationship(node_1, node_2, "Edge"); + relationship.SetProperty("property", mgp::Value(true)); + ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true)); ASSERT_EQ(relationship.To().Id(), node_2.Id()); graph.SetTo(relationship, node_3); @@ -735,6 +742,7 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) { ASSERT_EQ(std::string(relationship.Type()), "Edge"); ASSERT_EQ(relationship.From().Id(), node_1.Id()); ASSERT_EQ(relationship.To().Id(), node_3.Id()); + ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true)); } TYPED_TEST(CppApiTestFixture, TestInAndOutDegrees) { @@ -759,6 +767,28 @@ TYPED_TEST(CppApiTestFixture, TestInAndOutDegrees) { ASSERT_EQ(node_3.OutDegree(), 0); } +TYPED_TEST(CppApiTestFixture, TestChangeRelationshipType) { + // Changing relationship types is not implemented for ondisk storage yet. + if (std::is_same::value) { + return; + } + + mgp_graph raw_graph = this->CreateGraph(); + auto graph = mgp::Graph(&raw_graph); + + auto node_1 = graph.CreateNode(); + auto node_2 = graph.CreateNode(); + + auto relationship = graph.CreateRelationship(node_1, node_2, "Type"); + relationship.SetProperty("property", mgp::Value(true)); + ASSERT_EQ(relationship.Type(), "Type"); + ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true)); + + graph.ChangeType(relationship, "NewType"); + ASSERT_EQ(relationship.Type(), "NewType"); + ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true)); +} + TYPED_TEST(CppApiTestFixture, TestMapKeyExist) { mgp::Map map = mgp::Map(); map.Insert("key", mgp::Value("string"));