Fix API bug on accessing deleted object (#1209)

This commit is contained in:
Antonio Filipovic 2023-09-08 19:52:21 +02:00 committed by GitHub
parent 07dea328d8
commit b094fdbadc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 138 additions and 32 deletions

View File

@ -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 {

View File

@ -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<mgp_edge>(tv.ValueEdge(), graph);
return allocator.new_object<mgp_edge>(tv.ValueEdge(), tv.ValueEdge().DeletedEdgeFromVertex(),
tv.ValueEdge().DeletedEdgeToVertex(), graph);
},
[&tv, graph, &allocator](memgraph::query::SubgraphDbAccessor *db_impl) {
return allocator.new_object<mgp_edge>(
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<mgp_edge>(&memory, edge.impl, edge.from.graph); },
[&](memgraph::query::DbAccessor *) {
return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.impl.DeletedEdgeFromVertex(),
edge.impl.DeletedEdgeToVertex(), edge.from.graph);
},
[&](memgraph::query::SubgraphDbAccessor *db_impl) {
return NewRawMgpObject<mgp_edge>(
&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);
}

View File

@ -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<storage::PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;

View File

@ -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.

View File

@ -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_set<AllowedTr
for (const auto allowed_trigger_type : allowed_trigger_types) {
switch (allowed_trigger_type) {
case AllowedTriggerType::VERTEX: {
client.Execute("DROP TRIGGER DeletedVerticesTrigger");
client.Execute(fmt::format("DROP TRIGGER {}", kTriggerNameOnVertexDeleteTrigger));
client.DiscardAll();
break;
}
case AllowedTriggerType::EDGE: {
client.Execute("DROP TRIGGER DeletedEdgesTrigger");
client.Execute(fmt::format("DROP TRIGGER {}", kTriggerNameOnEdgeDeleteTrigger));
client.DiscardAll();
break;
}
case AllowedTriggerType::OBJECT: {
client.Execute("DROP TRIGGER DeletedObjectsTrigger");
client.Execute(fmt::format("DROP TRIGGER {}", kTriggerNameOnObjectDeleteTrigger));
client.DiscardAll();
break;
}
case AllowedTriggerType::OBJECT_PROC: {
client.Execute(fmt::format("DROP TRIGGER {}", kTriggerNameOnObjectDeleteTriggerProc));
client.DiscardAll();
break;
}
@ -173,6 +191,8 @@ int main(int argc, char **argv) {
case AllowedTriggerType::OBJECT:
number_of_expected_vertices += 3;
break;
case AllowedTriggerType::OBJECT_PROC:
break;
}
}
@ -224,6 +244,9 @@ int main(int argc, char **argv) {
case AllowedTriggerType::OBJECT:
number_of_expected_vertices += 2;
break;
case AllowedTriggerType::OBJECT_PROC:
break;
break;
}
}
@ -267,6 +290,8 @@ int main(int argc, char **argv) {
case AllowedTriggerType::OBJECT:
number_of_expected_vertices += 2;
break;
case AllowedTriggerType::OBJECT_PROC:
break;
}
}
@ -309,5 +334,20 @@ int main(int argc, char **argv) {
run_for_trigger_combinations(kBeforeCommit);
run_for_trigger_combinations(kAfterCommit);
const auto run_delete_trigger_access_deleted_object_in_proc = [&client]() {
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, 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;
}

View File

@ -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)