diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 62ac9b950..d6184be3b 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -1908,13 +1908,20 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) { throw QueryRuntimeException("Unexpected error when deleting a node."); } } - if (context.trigger_context_collector && - context.trigger_context_collector->ShouldRegisterDeletedObject<EdgeAccessor>() && res.GetValue()) { - context.trigger_context_collector->RegisterDeletedObject(res.GetValue()->first); - for (const auto &deleted_edge : res.GetValue()->second) { - context.trigger_context_collector->RegisterDeletedObject(deleted_edge); + + std::invoke([&] { + if (!context.trigger_context_collector || !*res) { + return; } - } + + context.trigger_context_collector->RegisterDeletedObject((*res)->first); + if (!context.trigger_context_collector->ShouldRegisterDeletedObject<query::EdgeAccessor>()) { + return; + } + for (const auto &edge : (*res)->second) { + context.trigger_context_collector->RegisterDeletedObject(edge); + } + }); } else { auto res = dba.RemoveVertex(&va); if (res.HasError()) { diff --git a/tests/e2e/triggers/on_delete_triggers.cpp b/tests/e2e/triggers/on_delete_triggers.cpp index 25038f9e5..cf713d279 100644 --- a/tests/e2e/triggers/on_delete_triggers.cpp +++ b/tests/e2e/triggers/on_delete_triggers.cpp @@ -1,5 +1,6 @@ #include <string> #include <string_view> +#include <unordered_set> #include <gflags/gflags.h> #include <mgclient.hpp> @@ -10,6 +11,12 @@ constexpr std::string_view kTriggerDeletedVertexLabel{"DELETED_VERTEX"}; constexpr std::string_view kTriggerDeletedEdgeLabel{"DELETED_EDGE"}; constexpr std::string_view kTriggerDeletedObjectLabel{"DELETED_OBJECT"}; +enum class AllowedTriggerType : uint8_t { + VERTEX, + EDGE, + OBJECT, +}; + void DetachDeleteVertex(mg::Client &client, int vertex_id) { mg::Map parameters{{"id", mg::Value{vertex_id}}}; client.Execute(fmt::format("MATCH (n: {} {{id: $id}}) DETACH DELETE n", kVertexLabel), @@ -23,43 +30,77 @@ void DeleteEdge(mg::Client &client, int edge_id) { client.DiscardAll(); } -void CreateOnDeleteTriggers(mg::Client &client, bool is_before) { +void CreateOnDeleteTriggers(mg::Client &client, bool is_before, + const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) { const std::string_view before_or_after = is_before ? "BEFORE" : "AFTER"; - client.Execute( - fmt::format("CREATE TRIGGER DeletedVerticesTrigger ON () DELETE " - "{} COMMIT " - "EXECUTE " - "UNWIND deletedVertices as deletedVertex " - "CREATE (n: {} {{ id: deletedVertex.id }})", - before_or_after, kTriggerDeletedVertexLabel)); - client.DiscardAll(); - client.Execute( - fmt::format("CREATE TRIGGER DeletedEdgesTrigger ON --> DELETE " - "{} COMMIT " - "EXECUTE " - "UNWIND deletedEdges as deletedEdge " - "CREATE (n: {} {{ id: deletedEdge.id }})", - before_or_after, kTriggerDeletedEdgeLabel)); - client.DiscardAll(); - client.Execute( - fmt::format("CREATE TRIGGER DeletedObjectsTrigger 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)); - client.DiscardAll(); + + const auto create_on_vertex_delete_trigger = [&, before_or_after] { + client.Execute( + fmt::format("CREATE TRIGGER DeletedVerticesTrigger ON () DELETE " + "{} COMMIT " + "EXECUTE " + "UNWIND deletedVertices as deletedVertex " + "CREATE (n: {} {{ id: deletedVertex.id }})", + 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 " + "{} COMMIT " + "EXECUTE " + "UNWIND deletedEdges as deletedEdge " + "CREATE (n: {} {{ id: deletedEdge.id }})", + 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 " + "{} 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)); + client.DiscardAll(); + }; + + for (const auto allowed_trigger_type : allowed_trigger_types) { + switch (allowed_trigger_type) { + case AllowedTriggerType::VERTEX: + create_on_vertex_delete_trigger(); + break; + case AllowedTriggerType::EDGE: + create_on_edge_delete_trigger(); + break; + case AllowedTriggerType::OBJECT: + create_on_object_delete_trigger(); + break; + } + } } -void DropOnDeleteTriggers(mg::Client &client) { - client.Execute("DROP TRIGGER DeletedVerticesTrigger"); - client.DiscardAll(); - client.Execute("DROP TRIGGER DeletedEdgesTrigger"); - client.DiscardAll(); - client.Execute("DROP TRIGGER DeletedObjectsTrigger"); - client.DiscardAll(); +void DropOnDeleteTriggers(mg::Client &client, const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) { + for (const auto allowed_trigger_type : allowed_trigger_types) { + switch (allowed_trigger_type) { + case AllowedTriggerType::VERTEX: { + client.Execute("DROP TRIGGER DeletedVerticesTrigger"); + client.DiscardAll(); + } + case AllowedTriggerType::EDGE: { + client.Execute("DROP TRIGGER DeletedEdgesTrigger"); + client.DiscardAll(); + } + case AllowedTriggerType::OBJECT: { + client.Execute("DROP TRIGGER DeletedObjectsTrigger"); + client.DiscardAll(); + } + } + } } struct EdgeInfo { @@ -68,6 +109,10 @@ struct EdgeInfo { int edge_id; }; +void ValidateVertexExistance(mg::Client &client, const bool should_exist, const std::string_view label, const int id) { + should_exist ? CheckVertexExists(client, label, id) : CheckVertexMissing(client, label, id); +}; + int main(int argc, char **argv) { gflags::SetUsageMessage("Memgraph E2E ON DELETE Triggers"); gflags::ParseCommandLineFlags(&argc, &argv, true); @@ -77,11 +122,12 @@ int main(int argc, char **argv) { auto client = Connect(); - const auto run_delete_trigger_tests = [&](bool is_before) { - const std::array vertex_ids{1, 2, 3, 4}; - const std::array edges{EdgeInfo{vertex_ids[0], vertex_ids[1], 5}, EdgeInfo{vertex_ids[2], vertex_ids[3], 6}}; + const auto run_delete_trigger_tests = [&](const bool is_before, + const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) { + constexpr std::array vertex_ids{1, 2, 3, 4}; + constexpr std::array edges{EdgeInfo{vertex_ids[0], vertex_ids[1], 5}, EdgeInfo{vertex_ids[2], vertex_ids[3], 6}}; { - CreateOnDeleteTriggers(*client, is_before); + CreateOnDeleteTriggers(*client, is_before, allowed_trigger_types); client->BeginTransaction(); for (const auto vertex_id : vertex_ids) { @@ -100,75 +146,153 @@ int main(int argc, char **argv) { // :VERTEX x 4 // deleted :VERTEX x -1 - // :DELETED_VERTEX x 1 - // :DELETED_EDGE x 2 - // :DELETED_OBJECT x 3 - constexpr auto kNumberOfExpectedVertices = 9; + auto number_of_expected_vertices = 3; + for (const auto allowed_trigger_type : allowed_trigger_types) { + switch (allowed_trigger_type) { + case AllowedTriggerType::VERTEX: + number_of_expected_vertices += 1; + break; + case AllowedTriggerType::EDGE: + number_of_expected_vertices += 2; + break; + case AllowedTriggerType::OBJECT: + number_of_expected_vertices += 3; + break; + } + } if (is_before) { - CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices); + CheckNumberOfAllVertices(*client, number_of_expected_vertices); } else { - WaitForNumberOfAllVertices(*client, kNumberOfExpectedVertices); + WaitForNumberOfAllVertices(*client, number_of_expected_vertices); } - CheckVertexExists(*client, kTriggerDeletedVertexLabel, vertex_ids[0]); - CheckVertexExists(*client, kTriggerDeletedObjectLabel, vertex_ids[0]); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::VERTEX), + kTriggerDeletedVertexLabel, vertex_ids[0]); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT), + kTriggerDeletedObjectLabel, vertex_ids[0]); for (const auto &edge : edges) { - CheckVertexExists(*client, kTriggerDeletedEdgeLabel, edge.edge_id); - CheckVertexExists(*client, kTriggerDeletedObjectLabel, edge.edge_id); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::EDGE), + kTriggerDeletedEdgeLabel, edge.edge_id); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT), + kTriggerDeletedObjectLabel, edge.edge_id); } - DropOnDeleteTriggers(*client); + DropOnDeleteTriggers(*client, allowed_trigger_types); client->Execute("MATCH (n) DETACH DELETE n;"); client->DiscardAll(); } }; + + const auto run_delete_trigger_write_procedure_tests = + [&](const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) { + ExecuteCreateVertex(*client, 2); + ExecuteCreateVertex(*client, 3); + client->Execute("MATCH (n {id:2}), (m {id:3}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e"); + client->DiscardAll(); + CreateOnDeleteTriggers(*client, true, allowed_trigger_types); + client->Execute("MATCH ()-[e]->() CALL write.delete_edge(e)"); + client->DiscardAll(); + client->Execute("MATCH (n {id:2}) CALL write.delete_vertex(n)"); + client->DiscardAll(); + + auto number_of_expected_vertices = 1; + for (const auto allowed_trigger_type : allowed_trigger_types) { + switch (allowed_trigger_type) { + case AllowedTriggerType::VERTEX: + number_of_expected_vertices += 1; + break; + case AllowedTriggerType::EDGE: + number_of_expected_vertices += 1; + break; + case AllowedTriggerType::OBJECT: + number_of_expected_vertices += 2; + break; + } + } + + CheckNumberOfAllVertices(*client, number_of_expected_vertices); + + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::EDGE), + kTriggerDeletedEdgeLabel, 1); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT), + kTriggerDeletedObjectLabel, 1); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::VERTEX), + kTriggerDeletedVertexLabel, 2); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT), + kTriggerDeletedObjectLabel, 2); + + DropOnDeleteTriggers(*client, allowed_trigger_types); + client->Execute("MATCH (n) DETACH DELETE n;"); + client->DiscardAll(); + }; + + const auto run_delete_trigger_write_procedure_delete_detach_test = + [&](const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) { + ExecuteCreateVertex(*client, 2); + ExecuteCreateVertex(*client, 3); + client->Execute("MATCH (n {id:2}), (m {id:3}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e"); + client->DiscardAll(); + + CreateOnDeleteTriggers(*client, true, allowed_trigger_types); + + client->Execute("MATCH (v {id:2}) CALL write.detach_delete_vertex(v)"); + client->DiscardAll(); + + auto number_of_expected_vertices = 1; + for (const auto allowed_trigger_type : allowed_trigger_types) { + switch (allowed_trigger_type) { + case AllowedTriggerType::VERTEX: + number_of_expected_vertices += 1; + break; + case AllowedTriggerType::EDGE: + number_of_expected_vertices += 1; + break; + case AllowedTriggerType::OBJECT: + number_of_expected_vertices += 2; + break; + } + } + + CheckNumberOfAllVertices(*client, number_of_expected_vertices); + + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::EDGE), + kTriggerDeletedEdgeLabel, 1); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT), + kTriggerDeletedObjectLabel, 1); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::VERTEX), + kTriggerDeletedVertexLabel, 2); + ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT), + kTriggerDeletedObjectLabel, 2); + + DropOnDeleteTriggers(*client, allowed_trigger_types); + client->Execute("MATCH (n) DETACH DELETE n;"); + client->DiscardAll(); + }; + + const auto run_for_trigger_combinations = [&](const bool is_before) { + const std::array trigger_type_combinations{ + std::unordered_set{AllowedTriggerType::VERTEX}, + std::unordered_set{AllowedTriggerType::EDGE}, + std::unordered_set{AllowedTriggerType::OBJECT}, + std::unordered_set{AllowedTriggerType::VERTEX, AllowedTriggerType::EDGE}, + std::unordered_set{AllowedTriggerType::VERTEX, AllowedTriggerType::OBJECT}, + std::unordered_set{AllowedTriggerType::EDGE, AllowedTriggerType::OBJECT}, + std::unordered_set{AllowedTriggerType::VERTEX, AllowedTriggerType::EDGE, AllowedTriggerType::OBJECT}, + }; + + for (const auto &allowed_trigger_types : trigger_type_combinations) { + run_delete_trigger_tests(is_before, allowed_trigger_types); + run_delete_trigger_write_procedure_tests(allowed_trigger_types); + run_delete_trigger_write_procedure_delete_detach_test(allowed_trigger_types); + } + }; + constexpr bool kBeforeCommit = true; constexpr bool kAfterCommit = false; - run_delete_trigger_tests(kBeforeCommit); - run_delete_trigger_tests(kAfterCommit); + run_for_trigger_combinations(kBeforeCommit); + run_for_trigger_combinations(kAfterCommit); - const auto run_delete_trigger_write_procedure_tests = [&]() { - ExecuteCreateVertex(*client, 2); - ExecuteCreateVertex(*client, 3); - client->Execute("MATCH (n {id:2}), (m {id:3}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e"); - client->DiscardAll(); - CreateOnDeleteTriggers(*client, true); - client->Execute("MATCH ()-[e]->() CALL write.delete_edge(e)"); - client->DiscardAll(); - client->Execute("MATCH (n {id:2}) CALL write.delete_vertex(n)"); - client->DiscardAll(); - constexpr auto kNumberOfExpectedVertices = 5; - CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices); - CheckVertexExists(*client, kTriggerDeletedEdgeLabel, 1); - CheckVertexExists(*client, kTriggerDeletedObjectLabel, 1); - CheckVertexExists(*client, kTriggerDeletedVertexLabel, 2); - CheckVertexExists(*client, kTriggerDeletedObjectLabel, 2); - DropOnDeleteTriggers(*client); - client->Execute("MATCH (n) DETACH DELETE n;"); - client->DiscardAll(); - }; - run_delete_trigger_write_procedure_tests(); - - const auto run_delete_trigger_write_procedure_delete_detach_test = [&]() { - ExecuteCreateVertex(*client, 2); - ExecuteCreateVertex(*client, 3); - client->Execute("MATCH (n {id:2}), (m {id:3}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e"); - client->DiscardAll(); - CreateOnDeleteTriggers(*client, true); - client->Execute("MATCH (v {id:2}) CALL write.detach_delete_vertex(v)"); - client->DiscardAll(); - constexpr auto kNumberOfExpectedVertices = 5; - CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices); - CheckVertexExists(*client, kTriggerDeletedEdgeLabel, 1); - CheckVertexExists(*client, kTriggerDeletedObjectLabel, 1); - CheckVertexExists(*client, kTriggerDeletedVertexLabel, 2); - CheckVertexExists(*client, kTriggerDeletedObjectLabel, 2); - DropOnDeleteTriggers(*client); - client->Execute("MATCH (n) DETACH DELETE n;"); - client->DiscardAll(); - }; - run_delete_trigger_write_procedure_delete_detach_test(); return 0; }