537855a0b2
* Fix usages of constexpr
314 lines
12 KiB
C++
314 lines
12 KiB
C++
// Copyright 2022 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
|
|
// License, and you may not use this file except in compliance with the Business Source License.
|
|
//
|
|
// As of the Change Date specified in that file, in accordance with
|
|
// the Business Source License, use of this software will be governed
|
|
// by the Apache License, Version 2.0, included in the file
|
|
// licenses/APL.txt.
|
|
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_set>
|
|
|
|
#include <gflags/gflags.h>
|
|
#include <mgclient.hpp>
|
|
#include "common.hpp"
|
|
#include "utils/logging.hpp"
|
|
|
|
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,
|
|
};
|
|
|
|
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),
|
|
mg::ConstMap{parameters.ptr()});
|
|
client.DiscardAll();
|
|
}
|
|
|
|
void DeleteEdge(mg::Client &client, int edge_id) {
|
|
mg::Map parameters{{"id", mg::Value{edge_id}}};
|
|
client.Execute(fmt::format("MATCH ()-[r: {} {{id: $id}}]->() DELETE r", kEdgeLabel), mg::ConstMap{parameters.ptr()});
|
|
client.DiscardAll();
|
|
}
|
|
|
|
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";
|
|
|
|
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, 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();
|
|
break;
|
|
}
|
|
case AllowedTriggerType::EDGE: {
|
|
client.Execute("DROP TRIGGER DeletedEdgesTrigger");
|
|
client.DiscardAll();
|
|
break;
|
|
}
|
|
case AllowedTriggerType::OBJECT: {
|
|
client.Execute("DROP TRIGGER DeletedObjectsTrigger");
|
|
client.DiscardAll();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct EdgeInfo {
|
|
int from_vertex;
|
|
int to_vertex;
|
|
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);
|
|
memgraph::logging::RedirectToStderr();
|
|
|
|
mg::Client::Init();
|
|
|
|
auto client = Connect();
|
|
|
|
const auto run_delete_trigger_tests = [&](const bool is_before,
|
|
const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) {
|
|
static constexpr std::array vertex_ids{1, 2, 3, 4};
|
|
static constexpr std::array edges{EdgeInfo{vertex_ids[0], vertex_ids[1], 5},
|
|
EdgeInfo{vertex_ids[2], vertex_ids[3], 6}};
|
|
{
|
|
CreateOnDeleteTriggers(*client, is_before, allowed_trigger_types);
|
|
|
|
client->BeginTransaction();
|
|
for (const auto vertex_id : vertex_ids) {
|
|
CreateVertex(*client, vertex_id);
|
|
}
|
|
for (const auto &edge : edges) {
|
|
CreateEdge(*client, edge.from_vertex, edge.to_vertex, edge.edge_id);
|
|
}
|
|
client->CommitTransaction();
|
|
CheckNumberOfAllVertices(*client, vertex_ids.size());
|
|
|
|
client->BeginTransaction();
|
|
DetachDeleteVertex(*client, vertex_ids[0]);
|
|
DeleteEdge(*client, edges[1].edge_id);
|
|
client->CommitTransaction();
|
|
|
|
// :VERTEX x 4
|
|
// deleted :VERTEX x -1
|
|
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, number_of_expected_vertices);
|
|
} else {
|
|
WaitForNumberOfAllVertices(*client, number_of_expected_vertices);
|
|
}
|
|
|
|
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) {
|
|
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, 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);
|
|
}
|
|
};
|
|
|
|
static constexpr bool kBeforeCommit = true;
|
|
static constexpr bool kAfterCommit = false;
|
|
run_for_trigger_combinations(kBeforeCommit);
|
|
run_for_trigger_combinations(kAfterCommit);
|
|
|
|
return 0;
|
|
}
|