diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp index 9e0931a6c..fabcf1590 100644 --- a/src/query/db_accessor.hpp +++ b/src/query/db_accessor.hpp @@ -94,6 +94,14 @@ class EdgeAccessor final { VertexAccessor From() const; + /// When edge is deleted and you are accessing To vertex + /// for_deleted_ flag will in this case be updated properly + VertexAccessor DeletedEdgeToVertex() const; + + /// When edge is deleted and you are accessing From vertex + /// for_deleted_ flag will in this case be updated properly + VertexAccessor DeletedEdgeFromVertex() const; + bool IsCycle() const; int64_t CypherId() const { return impl_.Gid().AsInt(); } @@ -236,6 +244,12 @@ inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToV inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.FromVertex()); } +inline VertexAccessor EdgeAccessor::DeletedEdgeToVertex() const { return VertexAccessor(impl_.DeletedEdgeToVertex()); } + +inline VertexAccessor EdgeAccessor::DeletedEdgeFromVertex() const { + return VertexAccessor(impl_.DeletedEdgeFromVertex()); +} + inline bool EdgeAccessor::IsCycle() const { return To() == From(); } class SubgraphVertexAccessor final { diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index f7ced0f3a..7148faacf 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -487,12 +487,16 @@ mgp_value::mgp_value(const memgraph::query::TypedValue &tv, mgp_graph *graph, me edge_v = std::visit( memgraph::utils::Overloaded{ [&tv, graph, &allocator](memgraph::query::DbAccessor *) { - return allocator.new_object(tv.ValueEdge(), graph); + return allocator.new_object(tv.ValueEdge(), tv.ValueEdge().DeletedEdgeFromVertex(), + tv.ValueEdge().DeletedEdgeToVertex(), graph); }, [&tv, graph, &allocator](memgraph::query::SubgraphDbAccessor *db_impl) { return allocator.new_object( - tv.ValueEdge(), memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().From(), db_impl->getGraph()), - memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().To(), db_impl->getGraph()), graph); + tv.ValueEdge(), + memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().DeletedEdgeFromVertex(), + db_impl->getGraph()), + memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().DeletedEdgeToVertex(), db_impl->getGraph()), + graph); }}, graph->impl); break; @@ -849,11 +853,16 @@ mgp_value::~mgp_value() noexcept { DeleteValueMember(this); } mgp_edge *mgp_edge::Copy(const mgp_edge &edge, mgp_memory &memory) { return std::visit( memgraph::utils::Overloaded{ - [&](memgraph::query::DbAccessor *) { return NewRawMgpObject(&memory, edge.impl, edge.from.graph); }, + [&](memgraph::query::DbAccessor *) { + return NewRawMgpObject(&memory, edge.impl, edge.impl.DeletedEdgeFromVertex(), + edge.impl.DeletedEdgeToVertex(), edge.from.graph); + }, [&](memgraph::query::SubgraphDbAccessor *db_impl) { return NewRawMgpObject( - &memory, edge.impl, memgraph::query::SubgraphVertexAccessor(edge.impl.From(), db_impl->getGraph()), - memgraph::query::SubgraphVertexAccessor(edge.impl.To(), db_impl->getGraph()), edge.to.graph); + &memory, edge.impl, + memgraph::query::SubgraphVertexAccessor(edge.impl.DeletedEdgeFromVertex(), db_impl->getGraph()), + memgraph::query::SubgraphVertexAccessor(edge.impl.DeletedEdgeToVertex(), db_impl->getGraph()), + edge.to.graph); }}, edge.to.graph->impl); } diff --git a/src/storage/v2/edge_accessor.cpp b/src/storage/v2/edge_accessor.cpp index 75468e158..f2277245d 100644 --- a/src/storage/v2/edge_accessor.cpp +++ b/src/storage/v2/edge_accessor.cpp @@ -105,6 +105,15 @@ VertexAccessor EdgeAccessor::ToVertex() const { return VertexAccessor{to_vertex_, transaction_, indices_, constraints_, config_}; } +VertexAccessor EdgeAccessor::DeletedEdgeFromVertex() const { + return VertexAccessor{from_vertex_, transaction_, indices_, + constraints_, config_, for_deleted_ && from_vertex_->deleted}; +} + +VertexAccessor EdgeAccessor::DeletedEdgeToVertex() const { + return VertexAccessor{to_vertex_, transaction_, indices_, constraints_, config_, for_deleted_ && to_vertex_->deleted}; +} + Result EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED; diff --git a/src/storage/v2/edge_accessor.hpp b/src/storage/v2/edge_accessor.hpp index 91ffd07e8..f4076965c 100644 --- a/src/storage/v2/edge_accessor.hpp +++ b/src/storage/v2/edge_accessor.hpp @@ -52,6 +52,14 @@ class EdgeAccessor final { VertexAccessor ToVertex() const; + /// When edge is deleted and you are accessing To vertex + /// for_deleted_ flag will in this case be updated properly + VertexAccessor DeletedEdgeFromVertex() const; + + /// When edge is deleted and you are accessing To vertex + /// for_deleted_ flag will in this case be updated properly + VertexAccessor DeletedEdgeToVertex() const; + EdgeTypeId EdgeType() const { return edge_type_; } /// Set a property value and return the old value. diff --git a/tests/e2e/triggers/on_delete_triggers.cpp b/tests/e2e/triggers/on_delete_triggers.cpp index 55d759864..5e483eef7 100644 --- a/tests/e2e/triggers/on_delete_triggers.cpp +++ b/tests/e2e/triggers/on_delete_triggers.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -22,11 +22,12 @@ inline constexpr std::string_view kTriggerDeletedVertexLabel{"DELETED_VERTEX"}; inline constexpr std::string_view kTriggerDeletedEdgeLabel{"DELETED_EDGE"}; inline constexpr std::string_view kTriggerDeletedObjectLabel{"DELETED_OBJECT"}; -enum class AllowedTriggerType : uint8_t { - VERTEX, - EDGE, - OBJECT, -}; +inline constexpr std::string_view kTriggerNameOnVertexDeleteTrigger{"DeletedVerticesTrigger"}; +inline constexpr std::string_view kTriggerNameOnEdgeDeleteTrigger{"DeletedEdgesTrigger"}; +inline constexpr std::string_view kTriggerNameOnObjectDeleteTrigger{"DeletedObjectsTrigger"}; +inline constexpr std::string_view kTriggerNameOnObjectDeleteTriggerProc{"DeletedObjectsTriggerProc"}; + +enum class AllowedTriggerType : uint8_t { VERTEX, EDGE, OBJECT, OBJECT_PROC }; void DetachDeleteVertex(mg::Client &client, int vertex_id) { mg::Map parameters{{"id", mg::Value{vertex_id}}}; @@ -47,36 +48,46 @@ void CreateOnDeleteTriggers(mg::Client &client, bool is_before, const auto create_on_vertex_delete_trigger = [&, before_or_after] { client.Execute( - fmt::format("CREATE TRIGGER DeletedVerticesTrigger ON () DELETE " + fmt::format("CREATE TRIGGER {} ON () DELETE " "{} COMMIT " "EXECUTE " "UNWIND deletedVertices as deletedVertex " "CREATE (n: {} {{ id: deletedVertex.id }})", - before_or_after, kTriggerDeletedVertexLabel)); + kTriggerNameOnVertexDeleteTrigger, before_or_after, kTriggerDeletedVertexLabel)); client.DiscardAll(); }; const auto create_on_edge_delete_trigger = [&, before_or_after] { client.Execute( - fmt::format("CREATE TRIGGER DeletedEdgesTrigger ON --> DELETE " + fmt::format("CREATE TRIGGER {} ON --> DELETE " "{} COMMIT " "EXECUTE " "UNWIND deletedEdges as deletedEdge " "CREATE (n: {} {{ id: deletedEdge.id }})", - before_or_after, kTriggerDeletedEdgeLabel)); + kTriggerNameOnEdgeDeleteTrigger, before_or_after, kTriggerDeletedEdgeLabel)); client.DiscardAll(); }; const auto create_on_object_delete_trigger = [&, before_or_after] { client.Execute( - fmt::format("CREATE TRIGGER DeletedObjectsTrigger ON DELETE " + fmt::format("CREATE TRIGGER {} ON DELETE " "{} COMMIT " "EXECUTE " "UNWIND deletedObjects as deletedObjectEvent " "WITH CASE deletedObjectEvent.event_type WHEN \"deleted_vertex\" THEN deletedObjectEvent.vertex.id " "ELSE deletedObjectEvent.edge.id END as id " "CREATE (n: {} {{ id: id }})", - before_or_after, kTriggerDeletedObjectLabel)); + kTriggerNameOnObjectDeleteTrigger, before_or_after, kTriggerDeletedObjectLabel)); + client.DiscardAll(); + }; + + const auto create_on_object_delete_trigger_proc = [&, before_or_after] { + client.Execute( + fmt::format("CREATE TRIGGER {} ON DELETE " + "{} COMMIT " + "EXECUTE " + "CALL write.access_deleted(deletedObjects) YIELD status RETURN status;", + kTriggerNameOnObjectDeleteTriggerProc, before_or_after)); client.DiscardAll(); }; @@ -91,6 +102,8 @@ void CreateOnDeleteTriggers(mg::Client &client, bool is_before, case AllowedTriggerType::OBJECT: create_on_object_delete_trigger(); break; + case AllowedTriggerType::OBJECT_PROC: + create_on_object_delete_trigger_proc(); } } } @@ -99,17 +112,22 @@ void DropOnDeleteTriggers(mg::Client &client, const std::unordered_setExecute("MATCH (n {id:2}), (m {id:3}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e"); + client->DiscardAll(); + + CreateOnDeleteTriggers(*client, false, {AllowedTriggerType::OBJECT_PROC}); + client->Execute("MATCH (n) DETACH DELETE n;"); + + DropOnDeleteTriggers(*client, {AllowedTriggerType::OBJECT_PROC}); + }; + + run_delete_trigger_access_deleted_object_in_proc(); + return 0; } diff --git a/tests/e2e/triggers/procedures/write.py b/tests/e2e/triggers/procedures/write.py index 1b4b7ad99..19df4ab37 100644 --- a/tests/e2e/triggers/procedures/write.py +++ b/tests/e2e/triggers/procedures/write.py @@ -9,8 +9,11 @@ # by the Apache License, Version 2.0, included in the file # licenses/APL.txt. +from typing import Union + import mgp + @mgp.write_proc def create_vertex(ctx: mgp.ProcCtx, id: mgp.Any) -> mgp.Record(v=mgp.Any): v = None @@ -36,15 +39,14 @@ def detach_delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record(): @mgp.write_proc -def create_edge(ctx: mgp.ProcCtx, from_vertex: mgp.Vertex, - to_vertex: mgp.Vertex, - edge_type: str) -> mgp.Record(e=mgp.Any): +def create_edge( + ctx: mgp.ProcCtx, from_vertex: mgp.Vertex, to_vertex: mgp.Vertex, edge_type: str +) -> mgp.Record(e=mgp.Any): e = None try: - e = ctx.graph.create_edge( - from_vertex, to_vertex, mgp.EdgeType(edge_type)) - e.properties.set("id", 1); - e.properties.set("tbd", 0); + e = ctx.graph.create_edge(from_vertex, to_vertex, mgp.EdgeType(edge_type)) + e.properties.set("id", 1) + e.properties.set("tbd", 0) except RuntimeError as ex: return mgp.Record(e=str(ex)) return mgp.Record(e=e) @@ -61,19 +63,43 @@ def set_property(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record(): object.properties.set("id", 2) return mgp.Record() + @mgp.write_proc def remove_property(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record(): object.properties.set("tbd", None) return mgp.Record() + @mgp.write_proc -def add_label(ctx: mgp.ProcCtx, object: mgp.Any, - name: str) -> mgp.Record(o=mgp.Any): +def add_label(ctx: mgp.ProcCtx, object: mgp.Any, name: str) -> mgp.Record(o=mgp.Any): object.add_label(name) return mgp.Record(o=object) + @mgp.write_proc -def remove_label(ctx: mgp.ProcCtx, object: mgp.Any, - name: str) -> mgp.Record(o=mgp.Any): +def remove_label(ctx: mgp.ProcCtx, object: mgp.Any, name: str) -> mgp.Record(o=mgp.Any): object.remove_label(name) return mgp.Record(o=object) + + +@mgp.write_proc +def access_deleted(ctx: mgp.ProcCtx, event: mgp.Any) -> mgp.Record(status=bool): + def process_obj(obj: Union[mgp.Vertex, mgp.Edge]) -> None: + for label in obj.labels: + assert label + for prop in obj.properties: + assert prop + + try: + if "event_type" not in event: + return False + if event["event_type"] == "deleted_vertex": + process_obj(event["vertex"]) + if event["event_type"] == "deleted_edge": + e = event["edge"] + process_obj(e) + process_obj(e.from_vertex) + process_obj(e.to_vertex) + except Exception as e: + raise e + return mgp.Record(status=True)