Make write procedures trigger aware (#262)

This commit is contained in:
Kostas Kyrimis 2021-10-11 13:39:57 +02:00 committed by GitHub
parent d417ffee6e
commit 24a576c8e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 305 additions and 10 deletions

View File

@ -1565,9 +1565,8 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"};
}
const auto result =
v->impl.SetProperty(v->graph->impl->NameToProperty(property_name), ToPropertyValue(*property_value));
const auto prop_key = v->graph->impl->NameToProperty(property_name);
const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
if (result.HasError()) {
switch (result.GetError()) {
case storage::Error::DELETED_OBJECT:
@ -1581,6 +1580,18 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
throw SerializationException{"Cannot serialize setting a property of a vertex."};
}
}
auto *trigger_ctx_collector = v->graph->ctx->trigger_context_collector;
if (!trigger_ctx_collector || !trigger_ctx_collector->ShouldRegisterObjectPropertyChange<query::VertexAccessor>()) {
return;
}
const auto old_value = query::TypedValue(*result);
if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) {
trigger_ctx_collector->RegisterRemovedObjectProperty(v->impl, prop_key, old_value);
return;
}
const auto new_value = ToTypedValue(*property_value, property_value->memory);
trigger_ctx_collector->RegisterSetObjectProperty(v->impl, prop_key, old_value, new_value);
});
}
@ -1589,7 +1600,8 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
}
const auto result = v->impl.AddLabel(v->graph->impl->NameToLabel(label.name));
const auto label_id = v->graph->impl->NameToLabel(label.name);
const auto result = v->impl.AddLabel(label_id);
if (result.HasError()) {
switch (result.GetError()) {
@ -1604,6 +1616,10 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
throw SerializationException{"Cannot serialize adding a label to a vertex."};
}
}
if (v->graph->ctx->trigger_context_collector) {
v->graph->ctx->trigger_context_collector->RegisterSetVertexLabel(v->impl, label_id);
}
});
}
@ -1612,7 +1628,8 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
}
const auto result = v->impl.RemoveLabel(v->graph->impl->NameToLabel(label.name));
const auto label_id = v->graph->impl->NameToLabel(label.name);
const auto result = v->impl.RemoveLabel(label_id);
if (result.HasError()) {
switch (result.GetError()) {
@ -1627,6 +1644,9 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
throw SerializationException{"Cannot serialize removing a label from a vertex."};
}
}
if (v->graph->ctx->trigger_context_collector) {
v->graph->ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->impl, label_id);
}
});
}
@ -1954,8 +1974,8 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
if (!MgpEdgeIsMutable(*e)) {
throw ImmutableObjectException{"Cannot set a property on an immutable edge!"};
}
const auto result =
e->impl.SetProperty(e->from.graph->impl->NameToProperty(property_name), ToPropertyValue(*property_value));
const auto prop_key = e->from.graph->impl->NameToProperty(property_name);
const auto result = e->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
if (result.HasError()) {
switch (result.GetError()) {
@ -1971,6 +1991,18 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
throw SerializationException{"Cannot serialize setting a property of an edge."};
}
}
auto *trigger_ctx_collector = e->from.graph->ctx->trigger_context_collector;
if (!trigger_ctx_collector || !trigger_ctx_collector->ShouldRegisterObjectPropertyChange<query::EdgeAccessor>()) {
return;
}
const auto old_value = query::TypedValue(*result);
if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) {
e->from.graph->ctx->trigger_context_collector->RegisterRemovedObjectProperty(e->impl, prop_key, old_value);
return;
}
const auto new_value = ToTypedValue(*property_value, property_value->memory);
e->from.graph->ctx->trigger_context_collector->RegisterSetObjectProperty(e->impl, prop_key, old_value, new_value);
});
}
@ -2024,8 +2056,10 @@ mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, m
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
}
auto vertex = graph->impl->InsertVertex();
if (graph->ctx->trigger_context_collector) {
graph->ctx->trigger_context_collector->RegisterCreatedObject(vertex);
}
return NewRawMgpObject<mgp_vertex>(memory, vertex, graph);
},
result);
@ -2051,6 +2085,9 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
throw SerializationException{"Cannot serialize removing a vertex."};
}
}
if (graph->ctx->trigger_context_collector && *result) {
graph->ctx->trigger_context_collector->RegisterDeletedObject(**result);
}
});
}
@ -2073,6 +2110,18 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
throw SerializationException{"Cannot serialize removing a vertex."};
}
}
auto *trigger_ctx_collector = graph->ctx->trigger_context_collector;
if (!trigger_ctx_collector || !*result) {
return;
}
trigger_ctx_collector->RegisterDeletedObject((*result)->first);
if (!trigger_ctx_collector->ShouldRegisterDeletedObject<query::EdgeAccessor>()) {
return;
}
for (const auto &edge : (*result)->second) {
trigger_ctx_collector->RegisterDeletedObject(edge);
}
});
}
@ -2098,6 +2147,9 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
throw SerializationException{"Cannot serialize creating an edge."};
}
}
if (graph->ctx->trigger_context_collector) {
graph->ctx->trigger_context_collector->RegisterCreatedObject(*edge);
}
return NewRawMgpObject<mgp_edge>(memory, edge.GetValue(), from->graph);
},
result);
@ -2122,6 +2174,9 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
throw SerializationException{"Cannot serialize removing an edge."};
}
}
if (graph->ctx->trigger_context_collector && *result) {
graph->ctx->trigger_context_collector->RegisterDeletedObject(**result);
}
});
}

View File

@ -12,3 +12,9 @@ target_link_libraries(memgraph__e2e__triggers__on_delete memgraph__e2e__triggers
add_executable(memgraph__e2e__triggers__privileges privilige_check.cpp)
target_link_libraries(memgraph__e2e__triggers__privileges memgraph__e2e__triggers_common)
add_subdirectory(procedures)
add_dependencies(memgraph__e2e__triggers__on_create memgraph__e2e__triggers__write.py)
add_dependencies(memgraph__e2e__triggers__on_update memgraph__e2e__triggers__write.py)
add_dependencies(memgraph__e2e__triggers__on_delete memgraph__e2e__triggers__write.py)

View File

@ -100,3 +100,10 @@ void CheckVertexExists(mg::Client &client, std::string_view label, int vertex_id
MG_ASSERT(VertexExists(client, label, vertex_id), "Expected vertex doesn't exist with label {} and id {}!", label,
vertex_id);
}
void ExecuteCreateVertex(mg::Client &client, int id) {
client.Execute(fmt::format("CALL write.create_vertex({}) YIELD v RETURN v", id));
const auto v1 = client.FetchAll();
MG_ASSERT(v1);
MG_ASSERT(v1->size() == 1);
}

View File

@ -21,4 +21,5 @@ void CheckNumberOfAllVertices(mg::Client &client, int expected_number_of_vertice
std::optional<mg::Value> GetVertex(mg::Client &client, std::string_view label, int vertex_id);
bool VertexExists(mg::Client &client, std::string_view label, int vertex_id);
void CheckVertexMissing(mg::Client &client, std::string_view label, int vertex_id);
void CheckVertexExists(mg::Client &client, std::string_view label, int vertex_id);
void CheckVertexExists(mg::Client &client, std::string_view label, int vertex_id);
void ExecuteCreateVertex(mg::Client &client, int id);

View File

@ -103,5 +103,34 @@ int main(int argc, char **argv) {
run_create_trigger_tests(kBeforeCommit);
run_create_trigger_tests(kAfterCommit);
const auto run_create_trigger_write_proc_create_vertex_test = [&]() {
CreateOnCreateTriggers(*client, true);
ExecuteCreateVertex(*client, 1);
constexpr auto kNumberOfExpectedVertices = 3;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerCreatedVertexLabel, 1);
CheckVertexExists(*client, kTriggerCreatedObjectLabel, 1);
DropOnCreateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_create_trigger_write_proc_create_vertex_test();
const auto run_create_trigger_write_proc_create_edge_test = [&]() {
ExecuteCreateVertex(*client, 1);
ExecuteCreateVertex(*client, 2);
CreateOnCreateTriggers(*client, true);
client->Execute("MATCH (n {id:1}), (m {id:2}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e");
client->DiscardAll();
constexpr auto kNumberOfExpectedVertices = 4;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerCreatedEdgeLabel, 1);
CheckVertexExists(*client, kTriggerCreatedObjectLabel, 1);
DropOnCreateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_create_trigger_write_proc_create_edge_test();
return 0;
}

View File

@ -129,5 +129,46 @@ int main(int argc, char **argv) {
run_delete_trigger_tests(kBeforeCommit);
run_delete_trigger_tests(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;
}

View File

@ -282,5 +282,87 @@ int main(int argc, char **argv) {
run_update_trigger_tests(kBeforeCommit);
run_update_trigger_tests(kAfterCommit);
const auto run_update_trigger_write_procedure_vertex_set_property_test = [&]() {
CreateOnUpdateTriggers(*client, true);
ExecuteCreateVertex(*client, 1);
client->Execute("MATCH (n) CALL write.set_property(n)");
client->DiscardAll();
constexpr auto kNumberOfExpectedVertices = 4;
constexpr int expected_updated_id = 2;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerUpdatedVertexLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerSetVertexPropertyLabel, expected_updated_id);
client->Execute(fmt::format("MATCH (n1:{}), (n2:{}), (n3:{}) DELETE n1, n2, n3", kTriggerUpdatedVertexLabel,
kTriggerUpdatedObjectLabel, kTriggerSetVertexPropertyLabel));
client->DiscardAll();
client->Execute("MATCH (n) CALL write.remove_property(n)");
client->DiscardAll();
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerUpdatedVertexLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerRemovedVertexPropertyLabel, 2);
DropOnUpdateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_update_trigger_write_procedure_vertex_set_property_test();
const auto run_update_trigger_write_procedure_edge_set_property_test = [&]() {
CreateOnUpdateTriggers(*client, true);
ExecuteCreateVertex(*client, 1);
ExecuteCreateVertex(*client, 3);
client->Execute("MATCH (n {id:1}), (m {id:3}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e");
client->DiscardAll();
client->Execute("MATCH ()-[e]->() CALL write.set_property(e)");
client->DiscardAll();
constexpr auto kNumberOfExpectedVertices = 5;
constexpr int expected_updated_id = 2;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerSetEdgePropertyLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedEdgeLabel, expected_updated_id);
client->Execute(fmt::format("MATCH (n1:{}), (n2:{}), (n3:{}) DELETE n1, n2, n3", kTriggerSetEdgePropertyLabel,
kTriggerUpdatedObjectLabel, kTriggerUpdatedEdgeLabel));
client->DiscardAll();
client->Execute("MATCH ()-[e]->() CALL write.remove_property(e)");
client->DiscardAll();
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedEdgeLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerRemovedEdgePropertyLabel, 2);
DropOnUpdateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_update_trigger_write_procedure_edge_set_property_test();
const auto run_update_trigger_write_procedure_set_label_test = [&]() {
ExecuteCreateVertex(*client, 1);
client->Execute("MATCH (n) CALL write.add_label(n, 'label') YIELD o RETURN o");
client->DiscardAll();
CreateOnUpdateTriggers(*client, true);
client->Execute("MATCH (n) CALL write.add_label(n, 'new') YIELD o RETURN o");
client->DiscardAll();
constexpr auto kNumberOfExpectedVertices = 4;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerSetVertexLabelLabel, 1);
CheckVertexExists(*client, kTriggerUpdatedVertexLabel, 1);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, 1);
client->Execute(fmt::format("MATCH (n1:{}), (n2:{}), (n3:{}) DELETE n1, n2, n3", kTriggerSetVertexLabelLabel,
kTriggerUpdatedVertexLabel, kTriggerUpdatedObjectLabel));
client->DiscardAll();
client->Execute("MATCH (n:new) CALL write.remove_label(n, 'new') YIELD o RETURN o");
client->DiscardAll();
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerRemovedVertexLabelLabel, 1);
CheckVertexExists(*client, kTriggerUpdatedVertexLabel, 1);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, 1);
DropOnUpdateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_update_trigger_write_procedure_set_label_test();
return 0;
}

View File

@ -0,0 +1 @@
copy_e2e_python_files(triggers write.py)

View File

@ -0,0 +1,68 @@
import mgp
@mgp.write_proc
def create_vertex(ctx: mgp.ProcCtx, id: mgp.Any) -> mgp.Record(v=mgp.Any):
v = None
try:
v = ctx.graph.create_vertex()
v.properties.set("id", id)
v.properties.set("tbd", 0)
except RuntimeError as e:
return mgp.Record(v=str(e))
return mgp.Record(v=v)
@mgp.write_proc
def delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record():
ctx.graph.delete_vertex(v)
return mgp.Record()
@mgp.write_proc
def detach_delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record():
ctx.graph.detach_delete_vertex(v)
return 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):
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);
except RuntimeError as ex:
return mgp.Record(e=str(ex))
return mgp.Record(e=e)
@mgp.write_proc
def delete_edge(ctx: mgp.ProcCtx, edge: mgp.Edge) -> mgp.Record():
ctx.graph.delete_edge(edge)
return mgp.Record()
@mgp.write_proc
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):
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):
object.remove_label(name)
return mgp.Record(o=object)

View File

@ -11,14 +11,17 @@ workloads:
- name: "ON CREATE Triggers"
binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_create"
args: ["--bolt-port", *bolt_port]
proc: "tests/e2e/triggers/procedures/"
<<: *template_cluster
- name: "ON UPDATE Triggers"
binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_update"
args: ["--bolt-port", *bolt_port]
proc: "tests/e2e/triggers/procedures/"
<<: *template_cluster
- name: "ON DELETE Triggers"
binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_delete"
args: ["--bolt-port", *bolt_port]
proc: "tests/e2e/triggers/procedures/"
<<: *template_cluster
- name: "Triggers privilege check"
binary: "tests/e2e/triggers/memgraph__e2e__triggers__privileges"

View File

@ -9,6 +9,7 @@
#include "mg_procedure.h"
#include "query/db_accessor.hpp"
#include "query/plan/operator.hpp"
#include "query/procedure/mg_procedure_impl.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_value.hpp"
@ -89,7 +90,7 @@ void CheckEdgeCountBetween(const MgpVertexPtr &from, const MgpVertexPtr &to, con
struct MgpGraphTest : public ::testing::Test {
mgp_graph CreateGraph(const storage::View view = storage::View::NEW) {
// the execution context can be null as it shouldn't be used in these tests
return mgp_graph{&CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION), view, nullptr};
return mgp_graph{&CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION), view, ctx_.get()};
}
std::array<storage::Gid, 2> CreateEdge() {
@ -133,6 +134,7 @@ struct MgpGraphTest : public ::testing::Test {
private:
std::list<storage::Storage::Accessor> accessors_;
std::list<query::DbAccessor> db_accessors_;
std::unique_ptr<query::ExecutionContext> ctx_ = std::make_unique<query::ExecutionContext>();
};
TEST_F(MgpGraphTest, IsMutable) {