Add renaming of edge types (#1364)

This commit is contained in:
imilinovic 2023-10-24 17:12:09 +02:00 committed by GitHub
parent 9803f47828
commit 1f118e7521
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 305 additions and 16 deletions

View File

@ -265,6 +265,11 @@ inline mgp_edge *graph_edge_set_to(struct mgp_graph *graph, struct mgp_edge *e,
return MgInvoke<mgp_edge *>(mgp_graph_edge_set_to, graph, e, new_to, memory); return MgInvoke<mgp_edge *>(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_edge *>(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 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) { inline mgp_vertex *graph_get_vertex_by_id(mgp_graph *g, mgp_vertex_id id, mgp_memory *memory) {

View File

@ -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, 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); 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. /// Delete an edge from the graph.
/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. /// 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 /// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by

View File

@ -260,6 +260,8 @@ class Graph {
void SetFrom(Relationship &relationship, const Node &new_from); void SetFrom(Relationship &relationship, const Node &new_from);
/// @brief Changes a relationship to node. /// @brief Changes a relationship to node.
void SetTo(Relationship &relationship, const Node &new_to); 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. /// @brief Deletes a relationship from the graph.
void DeleteRelationship(const Relationship &relationship); void DeleteRelationship(const Relationship &relationship);
@ -2030,6 +2032,13 @@ inline void Graph::SetTo(Relationship &relationship, const Node &new_to) {
mgp::edge_destroy(edge); 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) { inline void Graph::DeleteRelationship(const Relationship &relationship) {
mgp::graph_delete_edge(graph_, relationship.ptr_); mgp::graph_delete_edge(graph_, relationship.ptr_);
} }

View File

@ -94,6 +94,14 @@ storage::Result<EdgeAccessor> SubgraphDbAccessor::EdgeSetTo(EdgeAccessor *edge,
return result; return result;
} }
storage::Result<EdgeAccessor> 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<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex( storage::Result<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex(
SubgraphVertexAccessor *subgraphvertex_accessor) { SubgraphVertexAccessor *subgraphvertex_accessor) {
VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_; VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_;

View File

@ -414,6 +414,12 @@ class DbAccessor final {
return EdgeAccessor(*changed_edge); return EdgeAccessor(*changed_edge);
} }
storage::Result<EdgeAccessor> 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<EdgeAccessor>{changed_edge.GetError()};
return EdgeAccessor(*changed_edge);
}
storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) { storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) {
auto res = accessor_->DeleteEdge(&edge->impl_); auto res = accessor_->DeleteEdge(&edge->impl_);
if (res.HasError()) { if (res.HasError()) {
@ -655,6 +661,8 @@ class SubgraphDbAccessor final {
storage::Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, SubgraphVertexAccessor *new_to); storage::Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, SubgraphVertexAccessor *new_to);
storage::Result<EdgeAccessor> EdgeChangeType(EdgeAccessor *edge, storage::EdgeTypeId new_edge_type);
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex( storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
SubgraphVertexAccessor *vertex_accessor); SubgraphVertexAccessor *vertex_accessor);

View File

@ -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); 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<mgp_edge>(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<mgp_edge>(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) { mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
return WrapExceptions([=] { return WrapExceptions([=] {
auto *ctx = graph->ctx; auto *ctx = graph->ctx;

View File

@ -962,6 +962,11 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::EdgeSetTo(EdgeAccessor * /*edge*
return Error::NONEXISTENT_OBJECT; return Error::NONEXISTENT_OBJECT;
} }
Result<EdgeAccessor> 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) { bool DiskStorage::WriteVertexToVertexColumnFamily(Transaction *transaction, const Vertex &vertex) {
MG_ASSERT(transaction->commit_timestamp, "Writing vertex to disk but commit timestamp not set."); 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); auto commit_ts = transaction->commit_timestamp->load(std::memory_order_relaxed);

View File

@ -125,6 +125,8 @@ class DiskStorage final : public Storage {
Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override; Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override;
Result<EdgeAccessor> EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) override;
bool LabelIndexExists(LabelId label) const override { bool LabelIndexExists(LabelId label) const override {
auto *disk_storage = static_cast<DiskStorage *>(storage_); auto *disk_storage = static_cast<DiskStorage *>(storage_);
return disk_storage->indices_.label_index_->IndexExists(label); return disk_storage->indices_.label_index_->IndexExists(label);

View File

@ -639,6 +639,79 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetTo(EdgeAccessor *
return EdgeAccessor(edge_ref, edge_type, from_vertex, new_to_vertex, storage_, &transaction_); return EdgeAccessor(edge_ref, edge_type, from_vertex, new_to_vertex, storage_, &transaction_);
} }
Result<EdgeAccessor> 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<utils::RWSpinLock> 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<EdgeTypeId, Vertex *, EdgeRef> 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<EdgeTypeId, Vertex *, EdgeRef>{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) // NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit( utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit(
const std::optional<uint64_t> desired_commit_timestamp) { const std::optional<uint64_t> desired_commit_timestamp) {

View File

@ -183,6 +183,8 @@ class InMemoryStorage final : public Storage {
Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override; Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override;
Result<EdgeAccessor> EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) override;
bool LabelIndexExists(LabelId label) const override { bool LabelIndexExists(LabelId label) const override {
return static_cast<InMemoryStorage *>(storage_)->indices_.label_index_->IndexExists(label); return static_cast<InMemoryStorage *>(storage_)->indices_.label_index_->IndexExists(label);
} }

View File

@ -197,6 +197,8 @@ class Storage {
virtual Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) = 0; virtual Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) = 0;
virtual Result<EdgeAccessor> EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) = 0;
virtual Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge); virtual Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge);
virtual bool LabelIndexExists(LabelId label) const = 0; virtual bool LabelIndexExists(LabelId label) const = 0;

View File

@ -1,5 +1,5 @@
function(transaction_rollback_e2e_python_files FILE_NAME) 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() endfunction()
transaction_rollback_e2e_python_files(common.py) transaction_rollback_e2e_python_files(common.py)

View File

@ -17,13 +17,14 @@ namespace {
constexpr std::string_view ProcedureFrom = "set_from"; constexpr std::string_view ProcedureFrom = "set_from";
constexpr std::string_view ProcedureTo = "set_to"; 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) { void SetFrom(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
mgp::MemoryDispatcherGuard guard(memory); mgp::MemoryDispatcherGuard guard(memory);
const auto arguments = mgp::List(args); const auto arguments = mgp::List(args);
try { try {
auto rel = arguments[0].ValueRelationship(); auto rel{arguments[0].ValueRelationship()};
auto new_from = arguments[0].ValueNode(); auto new_from{arguments[1].ValueNode()};
mgp::Graph graph{memgraph_graph}; mgp::Graph graph{memgraph_graph};
graph.SetFrom(rel, new_from); graph.SetFrom(rel, new_from);
} catch (const std::exception &e) { } 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); mgp::MemoryDispatcherGuard guard(memory);
const auto arguments = mgp::List(args); const auto arguments = mgp::List(args);
try { try {
auto rel = arguments[0].ValueRelationship(); auto rel{arguments[0].ValueRelationship()};
auto new_to = arguments[0].ValueNode(); auto new_to{arguments[1].ValueNode()};
mgp::Graph graph{memgraph_graph}; mgp::Graph graph{memgraph_graph};
graph.SetTo(rel, new_to); graph.SetTo(rel, new_to);
} catch (const std::exception &e) { } 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 } // namespace
// Each module needs to define mgp_init_module function. // 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, SetTo, ProcedureTo, mgp::ProcedureType::Write,
{mgp::Parameter("relationship", mgp::Type::Relationship), mgp::Parameter("node_to", mgp::Type::Node)}, {}, {mgp::Parameter("relationship", mgp::Type::Relationship), mgp::Parameter("node_to", mgp::Type::Node)}, {},
module, memory); 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) { } catch (const std::exception &e) {
return 1; return 1;

View File

@ -9,6 +9,13 @@
# by the Apache License, Version 2.0, included in the file # by the Apache License, Version 2.0, included in the file
# licenses/APL.txt. # 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 sys
import pytest import pytest
@ -26,13 +33,27 @@ def test_change_from_rollback(connection):
cursor, cursor,
"MATCH (n:Node1)-[r:Relationship]->(m:Node2) MATCH (k:Node3) CALL transaction_rollback.set_from(r, k);", "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")) def compare(from_label: str, to_label: str):
assert len(result) == 1 result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m"))
node_from, rel, node_to = result[0] assert len(result) == 1
assert list(node_from.labels)[0] == "Node1" node_from, _, node_to = result[0]
assert list(node_to.labels)[0] == "Node2" 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): def test_change_to_rollback(connection):
@ -45,13 +66,59 @@ def test_change_to_rollback(connection):
execute_and_fetch_all( execute_and_fetch_all(
cursor, "MATCH (n:Node1)-[r:Relationship]->(m:Node2) MATCH (k:Node3) CALL transaction_rollback.set_to(r, k);" 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() connection.rollback()
result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m")) compare("Rel")
assert len(result) == 1
node_from, rel, node_to = result[0] execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n;")
assert list(node_from.labels)[0] == "Node1" connection.rollback()
assert list(node_to.labels)[0] == "Node2"
compare("Rel")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -694,6 +694,7 @@ TYPED_TEST(CppApiTestFixture, TestValueToString) {
} }
TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) { TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) {
// Changing Relationship end vertex is not implemented for ondisk storage yet.
if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) {
return; return;
} }
@ -706,6 +707,8 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) {
auto node_3 = graph.CreateNode(); auto node_3 = graph.CreateNode();
auto relationship = graph.CreateRelationship(node_1, node_2, "Edge"); 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()); ASSERT_EQ(relationship.From().Id(), node_1.Id());
graph.SetFrom(relationship, node_3); graph.SetFrom(relationship, node_3);
@ -713,9 +716,11 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) {
ASSERT_EQ(std::string(relationship.Type()), "Edge"); ASSERT_EQ(std::string(relationship.Type()), "Edge");
ASSERT_EQ(relationship.From().Id(), node_3.Id()); ASSERT_EQ(relationship.From().Id(), node_3.Id());
ASSERT_EQ(relationship.To().Id(), node_2.Id()); ASSERT_EQ(relationship.To().Id(), node_2.Id());
ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true));
} }
TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) { TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) {
// Changing Relationship start vertex is not implemented for ondisk storage yet.
if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) {
return; return;
} }
@ -728,6 +733,8 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) {
auto node_3 = graph.CreateNode(); auto node_3 = graph.CreateNode();
auto relationship = graph.CreateRelationship(node_1, node_2, "Edge"); 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()); ASSERT_EQ(relationship.To().Id(), node_2.Id());
graph.SetTo(relationship, node_3); graph.SetTo(relationship, node_3);
@ -735,6 +742,7 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) {
ASSERT_EQ(std::string(relationship.Type()), "Edge"); ASSERT_EQ(std::string(relationship.Type()), "Edge");
ASSERT_EQ(relationship.From().Id(), node_1.Id()); ASSERT_EQ(relationship.From().Id(), node_1.Id());
ASSERT_EQ(relationship.To().Id(), node_3.Id()); ASSERT_EQ(relationship.To().Id(), node_3.Id());
ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true));
} }
TYPED_TEST(CppApiTestFixture, TestInAndOutDegrees) { TYPED_TEST(CppApiTestFixture, TestInAndOutDegrees) {
@ -759,6 +767,28 @@ TYPED_TEST(CppApiTestFixture, TestInAndOutDegrees) {
ASSERT_EQ(node_3.OutDegree(), 0); ASSERT_EQ(node_3.OutDegree(), 0);
} }
TYPED_TEST(CppApiTestFixture, TestChangeRelationshipType) {
// Changing relationship types is not implemented for ondisk storage yet.
if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::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) { TYPED_TEST(CppApiTestFixture, TestMapKeyExist) {
mgp::Map map = mgp::Map(); mgp::Map map = mgp::Map();
map.Insert("key", mgp::Value("string")); map.Insert("key", mgp::Value("string"));