Fix trigger with detach delete (#266)
This commit is contained in:
parent
69ea410406
commit
6dc2cdfae4
@ -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()) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user