Add renaming of edge types (#1364)
This commit is contained in:
parent
9803f47828
commit
1f118e7521
@ -265,6 +265,11 @@ inline mgp_edge *graph_edge_set_to(struct mgp_graph *graph, struct mgp_edge *e,
|
|||||||
return MgInvoke<mgp_edge *>(mgp_graph_edge_set_to, graph, e, new_to, memory);
|
return MgInvoke<mgp_edge *>(mgp_graph_edge_set_to, graph, e, new_to, memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline mgp_edge *graph_edge_change_type(struct mgp_graph *graph, struct mgp_edge *e, struct mgp_edge_type new_type,
|
||||||
|
mgp_memory *memory) {
|
||||||
|
return MgInvoke<mgp_edge *>(mgp_graph_edge_change_type, graph, e, new_type, memory);
|
||||||
|
}
|
||||||
|
|
||||||
inline void graph_delete_edge(mgp_graph *graph, mgp_edge *edge) { MgInvokeVoid(mgp_graph_delete_edge, graph, edge); }
|
inline void graph_delete_edge(mgp_graph *graph, mgp_edge *edge) { MgInvokeVoid(mgp_graph_delete_edge, graph, edge); }
|
||||||
|
|
||||||
inline mgp_vertex *graph_get_vertex_by_id(mgp_graph *g, mgp_vertex_id id, mgp_memory *memory) {
|
inline mgp_vertex *graph_get_vertex_by_id(mgp_graph *g, mgp_vertex_id id, mgp_memory *memory) {
|
||||||
|
@ -911,6 +911,13 @@ enum mgp_error mgp_graph_edge_set_from(struct mgp_graph *graph, struct mgp_edge
|
|||||||
enum mgp_error mgp_graph_edge_set_to(struct mgp_graph *graph, struct mgp_edge *e, struct mgp_vertex *new_to,
|
enum mgp_error mgp_graph_edge_set_to(struct mgp_graph *graph, struct mgp_edge *e, struct mgp_vertex *new_to,
|
||||||
struct mgp_memory *memory, struct mgp_edge **result);
|
struct mgp_memory *memory, struct mgp_edge **result);
|
||||||
|
|
||||||
|
/// Change edge type
|
||||||
|
/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
|
||||||
|
/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by
|
||||||
|
/// another transaction.
|
||||||
|
enum mgp_error mgp_graph_edge_change_type(struct mgp_graph *graph, struct mgp_edge *e, struct mgp_edge_type new_type,
|
||||||
|
struct mgp_memory *memory, struct mgp_edge **result);
|
||||||
|
|
||||||
/// Delete an edge from the graph.
|
/// Delete an edge from the graph.
|
||||||
/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
|
/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
|
||||||
/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by
|
/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by
|
||||||
|
@ -260,6 +260,8 @@ class Graph {
|
|||||||
void SetFrom(Relationship &relationship, const Node &new_from);
|
void SetFrom(Relationship &relationship, const Node &new_from);
|
||||||
/// @brief Changes a relationship to node.
|
/// @brief Changes a relationship to node.
|
||||||
void SetTo(Relationship &relationship, const Node &new_to);
|
void SetTo(Relationship &relationship, const Node &new_to);
|
||||||
|
/// @brief Changes the relationship type.
|
||||||
|
void ChangeType(Relationship &relationship, std::string_view new_type);
|
||||||
/// @brief Deletes a relationship from the graph.
|
/// @brief Deletes a relationship from the graph.
|
||||||
void DeleteRelationship(const Relationship &relationship);
|
void DeleteRelationship(const Relationship &relationship);
|
||||||
|
|
||||||
@ -2030,6 +2032,13 @@ inline void Graph::SetTo(Relationship &relationship, const Node &new_to) {
|
|||||||
mgp::edge_destroy(edge);
|
mgp::edge_destroy(edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void Graph::ChangeType(Relationship &relationship, std::string_view new_type) {
|
||||||
|
mgp_edge *edge = mgp::MemHandlerCallback(mgp::graph_edge_change_type, graph_, relationship.ptr_,
|
||||||
|
mgp_edge_type{.name = new_type.data()});
|
||||||
|
relationship = Relationship(edge);
|
||||||
|
mgp::edge_destroy(edge);
|
||||||
|
}
|
||||||
|
|
||||||
inline void Graph::DeleteRelationship(const Relationship &relationship) {
|
inline void Graph::DeleteRelationship(const Relationship &relationship) {
|
||||||
mgp::graph_delete_edge(graph_, relationship.ptr_);
|
mgp::graph_delete_edge(graph_, relationship.ptr_);
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,14 @@ storage::Result<EdgeAccessor> SubgraphDbAccessor::EdgeSetTo(EdgeAccessor *edge,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storage::Result<EdgeAccessor> SubgraphDbAccessor::EdgeChangeType(EdgeAccessor *edge,
|
||||||
|
storage::EdgeTypeId new_edge_type) {
|
||||||
|
if (!this->graph_->ContainsEdge(*edge)) {
|
||||||
|
throw std::logic_error{"Projected graph must contain edge!"};
|
||||||
|
}
|
||||||
|
return db_accessor_.EdgeChangeType(edge, new_edge_type);
|
||||||
|
}
|
||||||
|
|
||||||
storage::Result<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex(
|
storage::Result<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex(
|
||||||
SubgraphVertexAccessor *subgraphvertex_accessor) {
|
SubgraphVertexAccessor *subgraphvertex_accessor) {
|
||||||
VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_;
|
VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_;
|
||||||
|
@ -414,6 +414,12 @@ class DbAccessor final {
|
|||||||
return EdgeAccessor(*changed_edge);
|
return EdgeAccessor(*changed_edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storage::Result<EdgeAccessor> EdgeChangeType(EdgeAccessor *edge, storage::EdgeTypeId new_edge_type) {
|
||||||
|
auto changed_edge = accessor_->EdgeChangeType(&edge->impl_, new_edge_type);
|
||||||
|
if (changed_edge.HasError()) return storage::Result<EdgeAccessor>{changed_edge.GetError()};
|
||||||
|
return EdgeAccessor(*changed_edge);
|
||||||
|
}
|
||||||
|
|
||||||
storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) {
|
storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) {
|
||||||
auto res = accessor_->DeleteEdge(&edge->impl_);
|
auto res = accessor_->DeleteEdge(&edge->impl_);
|
||||||
if (res.HasError()) {
|
if (res.HasError()) {
|
||||||
@ -655,6 +661,8 @@ class SubgraphDbAccessor final {
|
|||||||
|
|
||||||
storage::Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, SubgraphVertexAccessor *new_to);
|
storage::Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, SubgraphVertexAccessor *new_to);
|
||||||
|
|
||||||
|
storage::Result<EdgeAccessor> EdgeChangeType(EdgeAccessor *edge, storage::EdgeTypeId new_edge_type);
|
||||||
|
|
||||||
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
|
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
|
||||||
SubgraphVertexAccessor *vertex_accessor);
|
SubgraphVertexAccessor *vertex_accessor);
|
||||||
|
|
||||||
|
@ -2821,6 +2821,59 @@ mgp_error mgp_graph_edge_set_to(struct mgp_graph *graph, struct mgp_edge *e, str
|
|||||||
return WrapExceptions([&]() -> mgp_edge * { return EdgeSet(e, new_to, memory, graph, false); }, result);
|
return WrapExceptions([&]() -> mgp_edge * { return EdgeSet(e, new_to, memory, graph, false); }, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mgp_error mgp_graph_edge_change_type(mgp_graph *graph, mgp_edge *e, mgp_edge_type new_type, mgp_memory *memory,
|
||||||
|
mgp_edge **result) {
|
||||||
|
return WrapExceptions(
|
||||||
|
[&] {
|
||||||
|
auto *ctx = graph->ctx;
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
if (memgraph::license::global_license_checker.IsEnterpriseValidFast() && ctx && ctx->auth_checker &&
|
||||||
|
!ctx->auth_checker->Has(e->impl, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
|
||||||
|
throw AuthorizationException{"Insufficient permissions for changing the edge type!"};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!MgpGraphIsMutable(*graph)) {
|
||||||
|
throw ImmutableObjectException{"Cannot change an edge in an immutable graph!"};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto maybe_edge = std::visit(
|
||||||
|
[e, &new_type](auto *impl) { return impl->EdgeChangeType(&e->impl, impl->NameToEdgeType(new_type.name)); },
|
||||||
|
graph->impl);
|
||||||
|
if (maybe_edge.HasError()) {
|
||||||
|
switch (maybe_edge.GetError()) {
|
||||||
|
case memgraph::storage::Error::NONEXISTENT_OBJECT:
|
||||||
|
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when changing the edge type!");
|
||||||
|
case memgraph::storage::Error::DELETED_OBJECT:
|
||||||
|
case memgraph::storage::Error::PROPERTIES_DISABLED:
|
||||||
|
case memgraph::storage::Error::VERTEX_HAS_EDGES:
|
||||||
|
LOG_FATAL("Unexpected error when changing the edge type.");
|
||||||
|
case memgraph::storage::Error::SERIALIZATION_ERROR:
|
||||||
|
throw SerializationException{"Cannot serialize changing the edge type."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->trigger_context_collector) {
|
||||||
|
ctx->trigger_context_collector->RegisterDeletedObject(e->impl);
|
||||||
|
ctx->trigger_context_collector->RegisterCreatedObject(*maybe_edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::visit(
|
||||||
|
memgraph::utils::Overloaded{
|
||||||
|
[&memory, &maybe_edge, &graph](memgraph::query::DbAccessor *) -> mgp_edge * {
|
||||||
|
return NewRawMgpObject<mgp_edge>(memory->impl, maybe_edge.GetValue(), graph);
|
||||||
|
},
|
||||||
|
[&memory, &maybe_edge, &graph](memgraph::query::SubgraphDbAccessor *db_impl) -> mgp_edge * {
|
||||||
|
const auto v_from =
|
||||||
|
memgraph::query::SubgraphVertexAccessor(maybe_edge.GetValue().From(), db_impl->getGraph());
|
||||||
|
const auto v_to =
|
||||||
|
memgraph::query::SubgraphVertexAccessor(maybe_edge.GetValue().To(), db_impl->getGraph());
|
||||||
|
return NewRawMgpObject<mgp_edge>(memory->impl, maybe_edge.GetValue(), v_from, v_to, graph);
|
||||||
|
}},
|
||||||
|
graph->impl);
|
||||||
|
},
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
|
mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
|
||||||
return WrapExceptions([=] {
|
return WrapExceptions([=] {
|
||||||
auto *ctx = graph->ctx;
|
auto *ctx = graph->ctx;
|
||||||
|
@ -962,6 +962,11 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::EdgeSetTo(EdgeAccessor * /*edge*
|
|||||||
return Error::NONEXISTENT_OBJECT;
|
return Error::NONEXISTENT_OBJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<EdgeAccessor> DiskStorage::DiskAccessor::EdgeChangeType(EdgeAccessor * /*edge*/, EdgeTypeId /*new_edge_type*/) {
|
||||||
|
MG_ASSERT(false, "EdgeChangeType is currently only implemented for InMemory storage");
|
||||||
|
return Error::NONEXISTENT_OBJECT;
|
||||||
|
}
|
||||||
|
|
||||||
bool DiskStorage::WriteVertexToVertexColumnFamily(Transaction *transaction, const Vertex &vertex) {
|
bool DiskStorage::WriteVertexToVertexColumnFamily(Transaction *transaction, const Vertex &vertex) {
|
||||||
MG_ASSERT(transaction->commit_timestamp, "Writing vertex to disk but commit timestamp not set.");
|
MG_ASSERT(transaction->commit_timestamp, "Writing vertex to disk but commit timestamp not set.");
|
||||||
auto commit_ts = transaction->commit_timestamp->load(std::memory_order_relaxed);
|
auto commit_ts = transaction->commit_timestamp->load(std::memory_order_relaxed);
|
||||||
|
@ -125,6 +125,8 @@ class DiskStorage final : public Storage {
|
|||||||
|
|
||||||
Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override;
|
Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override;
|
||||||
|
|
||||||
|
Result<EdgeAccessor> EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) override;
|
||||||
|
|
||||||
bool LabelIndexExists(LabelId label) const override {
|
bool LabelIndexExists(LabelId label) const override {
|
||||||
auto *disk_storage = static_cast<DiskStorage *>(storage_);
|
auto *disk_storage = static_cast<DiskStorage *>(storage_);
|
||||||
return disk_storage->indices_.label_index_->IndexExists(label);
|
return disk_storage->indices_.label_index_->IndexExists(label);
|
||||||
|
@ -639,6 +639,79 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetTo(EdgeAccessor *
|
|||||||
return EdgeAccessor(edge_ref, edge_type, from_vertex, new_to_vertex, storage_, &transaction_);
|
return EdgeAccessor(edge_ref, edge_type, from_vertex, new_to_vertex, storage_, &transaction_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) {
|
||||||
|
OOMExceptionEnabler oom_exception;
|
||||||
|
MG_ASSERT(&transaction_ == edge->transaction_,
|
||||||
|
"EdgeAccessor must be from the same transaction as the storage "
|
||||||
|
"accessor when changing the edge type!");
|
||||||
|
|
||||||
|
auto &edge_ref = edge->edge_;
|
||||||
|
auto &edge_type = edge->edge_type_;
|
||||||
|
|
||||||
|
std::unique_lock<utils::RWSpinLock> guard;
|
||||||
|
if (config_.properties_on_edges) {
|
||||||
|
auto *edge_ptr = edge_ref.ptr;
|
||||||
|
guard = std::unique_lock{edge_ptr->lock};
|
||||||
|
|
||||||
|
if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR;
|
||||||
|
if (edge_ptr->deleted) return Error::DELETED_OBJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *from_vertex = edge->from_vertex_;
|
||||||
|
auto *to_vertex = edge->to_vertex_;
|
||||||
|
|
||||||
|
// Obtain the locks by `gid` order to avoid lock cycles.
|
||||||
|
auto guard_from = std::unique_lock{from_vertex->lock, std::defer_lock};
|
||||||
|
auto guard_to = std::unique_lock{to_vertex->lock, std::defer_lock};
|
||||||
|
if (from_vertex->gid < to_vertex->gid) {
|
||||||
|
guard_from.lock();
|
||||||
|
guard_to.lock();
|
||||||
|
} else if (from_vertex->gid > to_vertex->gid) {
|
||||||
|
guard_to.lock();
|
||||||
|
guard_from.lock();
|
||||||
|
} else {
|
||||||
|
// The vertices are the same vertex, only lock one.
|
||||||
|
guard_from.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR;
|
||||||
|
MG_ASSERT(!from_vertex->deleted, "Invalid database state!");
|
||||||
|
|
||||||
|
if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR;
|
||||||
|
MG_ASSERT(!to_vertex->deleted, "Invalid database state!");
|
||||||
|
|
||||||
|
auto change_edge_type_in_storage = [&edge_type, &edge_ref, &new_edge_type, this](auto *vertex, auto *edges) {
|
||||||
|
std::tuple<EdgeTypeId, Vertex *, EdgeRef> link(edge_type, vertex, edge_ref);
|
||||||
|
auto it = std::find(edges->begin(), edges->end(), link);
|
||||||
|
if (config_.properties_on_edges) {
|
||||||
|
MG_ASSERT(it != edges->end(), "Invalid database state!");
|
||||||
|
} else if (it == edges->end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*it = std::tuple<EdgeTypeId, Vertex *, EdgeRef>{new_edge_type, vertex, edge_ref};
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto op1 = change_edge_type_in_storage(to_vertex, &from_vertex->out_edges);
|
||||||
|
auto op2 = change_edge_type_in_storage(from_vertex, &to_vertex->in_edges);
|
||||||
|
|
||||||
|
MG_ASSERT((op1 && op2), "Invalid database state!");
|
||||||
|
|
||||||
|
// "deleting" old edge
|
||||||
|
CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref);
|
||||||
|
CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref);
|
||||||
|
|
||||||
|
// "adding" new edge
|
||||||
|
CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), new_edge_type, to_vertex, edge_ref);
|
||||||
|
CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), new_edge_type, from_vertex, edge_ref);
|
||||||
|
|
||||||
|
// edge type is not used while invalidating cache so we can only call it once
|
||||||
|
transaction_.manyDeltasCache.Invalidate(from_vertex, new_edge_type, EdgeDirection::OUT);
|
||||||
|
transaction_.manyDeltasCache.Invalidate(to_vertex, new_edge_type, EdgeDirection::IN);
|
||||||
|
|
||||||
|
return EdgeAccessor(edge_ref, new_edge_type, from_vertex, to_vertex, storage_, &transaction_);
|
||||||
|
}
|
||||||
|
|
||||||
// NOLINTNEXTLINE(google-default-arguments)
|
// NOLINTNEXTLINE(google-default-arguments)
|
||||||
utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit(
|
utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit(
|
||||||
const std::optional<uint64_t> desired_commit_timestamp) {
|
const std::optional<uint64_t> desired_commit_timestamp) {
|
||||||
|
@ -183,6 +183,8 @@ class InMemoryStorage final : public Storage {
|
|||||||
|
|
||||||
Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override;
|
Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) override;
|
||||||
|
|
||||||
|
Result<EdgeAccessor> EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) override;
|
||||||
|
|
||||||
bool LabelIndexExists(LabelId label) const override {
|
bool LabelIndexExists(LabelId label) const override {
|
||||||
return static_cast<InMemoryStorage *>(storage_)->indices_.label_index_->IndexExists(label);
|
return static_cast<InMemoryStorage *>(storage_)->indices_.label_index_->IndexExists(label);
|
||||||
}
|
}
|
||||||
|
@ -197,6 +197,8 @@ class Storage {
|
|||||||
|
|
||||||
virtual Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) = 0;
|
virtual Result<EdgeAccessor> EdgeSetTo(EdgeAccessor *edge, VertexAccessor *new_to) = 0;
|
||||||
|
|
||||||
|
virtual Result<EdgeAccessor> EdgeChangeType(EdgeAccessor *edge, EdgeTypeId new_edge_type) = 0;
|
||||||
|
|
||||||
virtual Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge);
|
virtual Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge);
|
||||||
|
|
||||||
virtual bool LabelIndexExists(LabelId label) const = 0;
|
virtual bool LabelIndexExists(LabelId label) const = 0;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
function(transaction_rollback_e2e_python_files FILE_NAME)
|
function(transaction_rollback_e2e_python_files FILE_NAME)
|
||||||
copy_e2e_python_files(transaction_abort ${FILE_NAME})
|
copy_e2e_python_files(transaction_rollback ${FILE_NAME})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
transaction_rollback_e2e_python_files(common.py)
|
transaction_rollback_e2e_python_files(common.py)
|
||||||
|
@ -17,13 +17,14 @@ namespace {
|
|||||||
|
|
||||||
constexpr std::string_view ProcedureFrom = "set_from";
|
constexpr std::string_view ProcedureFrom = "set_from";
|
||||||
constexpr std::string_view ProcedureTo = "set_to";
|
constexpr std::string_view ProcedureTo = "set_to";
|
||||||
|
constexpr std::string_view ProcedureChangeType = "change_type";
|
||||||
|
|
||||||
void SetFrom(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
void SetFrom(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
||||||
mgp::MemoryDispatcherGuard guard(memory);
|
mgp::MemoryDispatcherGuard guard(memory);
|
||||||
const auto arguments = mgp::List(args);
|
const auto arguments = mgp::List(args);
|
||||||
try {
|
try {
|
||||||
auto rel = arguments[0].ValueRelationship();
|
auto rel{arguments[0].ValueRelationship()};
|
||||||
auto new_from = arguments[0].ValueNode();
|
auto new_from{arguments[1].ValueNode()};
|
||||||
mgp::Graph graph{memgraph_graph};
|
mgp::Graph graph{memgraph_graph};
|
||||||
graph.SetFrom(rel, new_from);
|
graph.SetFrom(rel, new_from);
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
@ -35,8 +36,8 @@ void SetTo(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_me
|
|||||||
mgp::MemoryDispatcherGuard guard(memory);
|
mgp::MemoryDispatcherGuard guard(memory);
|
||||||
const auto arguments = mgp::List(args);
|
const auto arguments = mgp::List(args);
|
||||||
try {
|
try {
|
||||||
auto rel = arguments[0].ValueRelationship();
|
auto rel{arguments[0].ValueRelationship()};
|
||||||
auto new_to = arguments[0].ValueNode();
|
auto new_to{arguments[1].ValueNode()};
|
||||||
mgp::Graph graph{memgraph_graph};
|
mgp::Graph graph{memgraph_graph};
|
||||||
graph.SetTo(rel, new_to);
|
graph.SetTo(rel, new_to);
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
@ -44,6 +45,19 @@ void SetTo(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_me
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChangeType(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
||||||
|
mgp::MemoryDispatcherGuard guard(memory);
|
||||||
|
const auto arguments = mgp::List(args);
|
||||||
|
try {
|
||||||
|
auto rel{arguments[0].ValueRelationship()};
|
||||||
|
auto new_type{arguments[1].ValueString()};
|
||||||
|
mgp::Graph graph{memgraph_graph};
|
||||||
|
graph.ChangeType(rel, new_type);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// Each module needs to define mgp_init_module function.
|
// Each module needs to define mgp_init_module function.
|
||||||
@ -61,6 +75,10 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
|||||||
SetTo, ProcedureTo, mgp::ProcedureType::Write,
|
SetTo, ProcedureTo, mgp::ProcedureType::Write,
|
||||||
{mgp::Parameter("relationship", mgp::Type::Relationship), mgp::Parameter("node_to", mgp::Type::Node)}, {},
|
{mgp::Parameter("relationship", mgp::Type::Relationship), mgp::Parameter("node_to", mgp::Type::Node)}, {},
|
||||||
module, memory);
|
module, memory);
|
||||||
|
mgp::AddProcedure(
|
||||||
|
ChangeType, ProcedureChangeType, mgp::ProcedureType::Write,
|
||||||
|
{mgp::Parameter("relationship", mgp::Type::Relationship), mgp::Parameter("new_type", mgp::Type::String)}, {},
|
||||||
|
module, memory);
|
||||||
|
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -9,6 +9,13 @@
|
|||||||
# by the Apache License, Version 2.0, included in the file
|
# by the Apache License, Version 2.0, included in the file
|
||||||
# licenses/APL.txt.
|
# licenses/APL.txt.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tests here work by executing the wanted procedure twice,
|
||||||
|
the first time the action will be commited to confirm the procedure executed
|
||||||
|
and doesn't crash for other reasons
|
||||||
|
and the second time the action will be rollbacked.
|
||||||
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -26,13 +33,27 @@ def test_change_from_rollback(connection):
|
|||||||
cursor,
|
cursor,
|
||||||
"MATCH (n:Node1)-[r:Relationship]->(m:Node2) MATCH (k:Node3) CALL transaction_rollback.set_from(r, k);",
|
"MATCH (n:Node1)-[r:Relationship]->(m:Node2) MATCH (k:Node3) CALL transaction_rollback.set_from(r, k);",
|
||||||
)
|
)
|
||||||
connection.rollback()
|
connection.commit()
|
||||||
|
|
||||||
result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m"))
|
def compare(from_label: str, to_label: str):
|
||||||
assert len(result) == 1
|
result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m"))
|
||||||
node_from, rel, node_to = result[0]
|
assert len(result) == 1
|
||||||
assert list(node_from.labels)[0] == "Node1"
|
node_from, _, node_to = result[0]
|
||||||
assert list(node_to.labels)[0] == "Node2"
|
assert list(node_from.labels)[0] == from_label
|
||||||
|
assert list(node_to.labels)[0] == to_label
|
||||||
|
|
||||||
|
compare("Node3", "Node2")
|
||||||
|
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
"MATCH (n:Node3)-[r:Relationship]->(m:Node2) MATCH (k:Node1) CALL transaction_rollback.set_from(r, k);",
|
||||||
|
)
|
||||||
|
connection.rollback()
|
||||||
|
compare("Node3", "Node2")
|
||||||
|
|
||||||
|
execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n;")
|
||||||
|
connection.rollback()
|
||||||
|
compare("Node3", "Node2")
|
||||||
|
|
||||||
|
|
||||||
def test_change_to_rollback(connection):
|
def test_change_to_rollback(connection):
|
||||||
@ -45,13 +66,59 @@ def test_change_to_rollback(connection):
|
|||||||
execute_and_fetch_all(
|
execute_and_fetch_all(
|
||||||
cursor, "MATCH (n:Node1)-[r:Relationship]->(m:Node2) MATCH (k:Node3) CALL transaction_rollback.set_to(r, k);"
|
cursor, "MATCH (n:Node1)-[r:Relationship]->(m:Node2) MATCH (k:Node3) CALL transaction_rollback.set_to(r, k);"
|
||||||
)
|
)
|
||||||
|
connection.commit()
|
||||||
|
|
||||||
|
def compare(from_label: str, to_label: str):
|
||||||
|
result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m"))
|
||||||
|
assert len(result) == 1
|
||||||
|
node_from, _, node_to = result[0]
|
||||||
|
assert list(node_from.labels)[0] == from_label
|
||||||
|
assert list(node_to.labels)[0] == to_label
|
||||||
|
|
||||||
|
compare("Node1", "Node3")
|
||||||
|
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor, "MATCH (n:Node1)-[r:Relationship]->(m:Node3) MATCH (k:Node2) CALL transaction_rollback.set_to(r, k);"
|
||||||
|
)
|
||||||
|
connection.rollback()
|
||||||
|
compare("Node1", "Node3")
|
||||||
|
|
||||||
|
execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n;")
|
||||||
|
connection.rollback()
|
||||||
|
compare("Node1", "Node3")
|
||||||
|
|
||||||
|
|
||||||
|
def test_change_rel_type_rollback(connection):
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||||
|
execute_and_fetch_all(cursor, "CREATE (n:Node1) CREATE (m:Node2) CREATE (n)-[:Relationship]->(m);")
|
||||||
|
connection.commit()
|
||||||
|
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor, "MATCH (n:Node1)-[r:Relationship]->(m:Node2) CALL transaction_rollback.change_type(r, 'Rel');"
|
||||||
|
)
|
||||||
|
connection.commit()
|
||||||
|
|
||||||
|
def compare(rel_type: str):
|
||||||
|
result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN r"))
|
||||||
|
assert len(result) == 1
|
||||||
|
rel = result[0][0]
|
||||||
|
assert rel.type == rel_type
|
||||||
|
|
||||||
|
compare("Rel")
|
||||||
|
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor, "MATCH (n:Node1)-[r:Rel]->(m:Node2) CALL transaction_rollback.change_type(r, 'Relationship');"
|
||||||
|
)
|
||||||
connection.rollback()
|
connection.rollback()
|
||||||
|
|
||||||
result = list(execute_and_fetch_all(cursor, f"MATCH (n)-[r]->(m) RETURN n, r, m"))
|
compare("Rel")
|
||||||
assert len(result) == 1
|
|
||||||
node_from, rel, node_to = result[0]
|
execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n;")
|
||||||
assert list(node_from.labels)[0] == "Node1"
|
connection.rollback()
|
||||||
assert list(node_to.labels)[0] == "Node2"
|
|
||||||
|
compare("Rel")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -694,6 +694,7 @@ TYPED_TEST(CppApiTestFixture, TestValueToString) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) {
|
TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) {
|
||||||
|
// Changing Relationship end vertex is not implemented for ondisk storage yet.
|
||||||
if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) {
|
if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -706,6 +707,8 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) {
|
|||||||
auto node_3 = graph.CreateNode();
|
auto node_3 = graph.CreateNode();
|
||||||
|
|
||||||
auto relationship = graph.CreateRelationship(node_1, node_2, "Edge");
|
auto relationship = graph.CreateRelationship(node_1, node_2, "Edge");
|
||||||
|
relationship.SetProperty("property", mgp::Value(true));
|
||||||
|
ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true));
|
||||||
|
|
||||||
ASSERT_EQ(relationship.From().Id(), node_1.Id());
|
ASSERT_EQ(relationship.From().Id(), node_1.Id());
|
||||||
graph.SetFrom(relationship, node_3);
|
graph.SetFrom(relationship, node_3);
|
||||||
@ -713,9 +716,11 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeFrom) {
|
|||||||
ASSERT_EQ(std::string(relationship.Type()), "Edge");
|
ASSERT_EQ(std::string(relationship.Type()), "Edge");
|
||||||
ASSERT_EQ(relationship.From().Id(), node_3.Id());
|
ASSERT_EQ(relationship.From().Id(), node_3.Id());
|
||||||
ASSERT_EQ(relationship.To().Id(), node_2.Id());
|
ASSERT_EQ(relationship.To().Id(), node_2.Id());
|
||||||
|
ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) {
|
TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) {
|
||||||
|
// Changing Relationship start vertex is not implemented for ondisk storage yet.
|
||||||
if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) {
|
if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -728,6 +733,8 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) {
|
|||||||
auto node_3 = graph.CreateNode();
|
auto node_3 = graph.CreateNode();
|
||||||
|
|
||||||
auto relationship = graph.CreateRelationship(node_1, node_2, "Edge");
|
auto relationship = graph.CreateRelationship(node_1, node_2, "Edge");
|
||||||
|
relationship.SetProperty("property", mgp::Value(true));
|
||||||
|
ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true));
|
||||||
|
|
||||||
ASSERT_EQ(relationship.To().Id(), node_2.Id());
|
ASSERT_EQ(relationship.To().Id(), node_2.Id());
|
||||||
graph.SetTo(relationship, node_3);
|
graph.SetTo(relationship, node_3);
|
||||||
@ -735,6 +742,7 @@ TYPED_TEST(CppApiTestFixture, TestRelationshipChangeTo) {
|
|||||||
ASSERT_EQ(std::string(relationship.Type()), "Edge");
|
ASSERT_EQ(std::string(relationship.Type()), "Edge");
|
||||||
ASSERT_EQ(relationship.From().Id(), node_1.Id());
|
ASSERT_EQ(relationship.From().Id(), node_1.Id());
|
||||||
ASSERT_EQ(relationship.To().Id(), node_3.Id());
|
ASSERT_EQ(relationship.To().Id(), node_3.Id());
|
||||||
|
ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
TYPED_TEST(CppApiTestFixture, TestInAndOutDegrees) {
|
TYPED_TEST(CppApiTestFixture, TestInAndOutDegrees) {
|
||||||
@ -759,6 +767,28 @@ TYPED_TEST(CppApiTestFixture, TestInAndOutDegrees) {
|
|||||||
ASSERT_EQ(node_3.OutDegree(), 0);
|
ASSERT_EQ(node_3.OutDegree(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TYPED_TEST(CppApiTestFixture, TestChangeRelationshipType) {
|
||||||
|
// Changing relationship types is not implemented for ondisk storage yet.
|
||||||
|
if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mgp_graph raw_graph = this->CreateGraph();
|
||||||
|
auto graph = mgp::Graph(&raw_graph);
|
||||||
|
|
||||||
|
auto node_1 = graph.CreateNode();
|
||||||
|
auto node_2 = graph.CreateNode();
|
||||||
|
|
||||||
|
auto relationship = graph.CreateRelationship(node_1, node_2, "Type");
|
||||||
|
relationship.SetProperty("property", mgp::Value(true));
|
||||||
|
ASSERT_EQ(relationship.Type(), "Type");
|
||||||
|
ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true));
|
||||||
|
|
||||||
|
graph.ChangeType(relationship, "NewType");
|
||||||
|
ASSERT_EQ(relationship.Type(), "NewType");
|
||||||
|
ASSERT_EQ(relationship.GetProperty("property"), mgp::Value(true));
|
||||||
|
}
|
||||||
|
|
||||||
TYPED_TEST(CppApiTestFixture, TestMapKeyExist) {
|
TYPED_TEST(CppApiTestFixture, TestMapKeyExist) {
|
||||||
mgp::Map map = mgp::Map();
|
mgp::Map map = mgp::Map();
|
||||||
map.Insert("key", mgp::Value("string"));
|
map.Insert("key", mgp::Value("string"));
|
||||||
|
Loading…
Reference in New Issue
Block a user