From 9615b8e26ea2b21576c9c088a5551785e38e0043 Mon Sep 17 00:00:00 2001 From: idoraban Date: Mon, 28 Aug 2023 15:06:20 +0000 Subject: [PATCH 1/8] add initial structure --- cpp/CMakeLists.txt | 1 + cpp/refactor_module/CMakeLists.txt | 5 +++++ cpp/refactor_module/algorithm/refactor.cpp | 1 + cpp/refactor_module/algorithm/refactor.hpp | 5 +++++ cpp/refactor_module/refactor_module.cpp | 16 ++++++++++++++++ 5 files changed, 28 insertions(+) create mode 100644 cpp/refactor_module/CMakeLists.txt create mode 100644 cpp/refactor_module/algorithm/refactor.cpp create mode 100644 cpp/refactor_module/algorithm/refactor.hpp create mode 100644 cpp/refactor_module/refactor_module.cpp diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 6d94f8dc2..67cd0a0c5 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -149,4 +149,5 @@ add_subdirectory(map_module) add_subdirectory(collections_module) add_subdirectory(text_module) add_subdirectory(label_module) +add_subdirectory(refactor_module) add_cugraph_subdirectory(cugraph_module) diff --git a/cpp/refactor_module/CMakeLists.txt b/cpp/refactor_module/CMakeLists.txt new file mode 100644 index 000000000..51722e187 --- /dev/null +++ b/cpp/refactor_module/CMakeLists.txt @@ -0,0 +1,5 @@ +set(refactor_module_src + refactor_module.cpp + algorithm/refactor.cpp) + +add_query_module(refactor 1 "${refactor_module_src}") diff --git a/cpp/refactor_module/algorithm/refactor.cpp b/cpp/refactor_module/algorithm/refactor.cpp new file mode 100644 index 000000000..ebda05a79 --- /dev/null +++ b/cpp/refactor_module/algorithm/refactor.cpp @@ -0,0 +1 @@ +#include "refactor.hpp" diff --git a/cpp/refactor_module/algorithm/refactor.hpp b/cpp/refactor_module/algorithm/refactor.hpp new file mode 100644 index 000000000..003cf201c --- /dev/null +++ b/cpp/refactor_module/algorithm/refactor.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +namespace Refactor {} // namespace Refactor diff --git a/cpp/refactor_module/refactor_module.cpp b/cpp/refactor_module/refactor_module.cpp new file mode 100644 index 000000000..4917b075b --- /dev/null +++ b/cpp/refactor_module/refactor_module.cpp @@ -0,0 +1,16 @@ +#include + +#include "algorithm/refactor.hpp" + +extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) { + try { + mgp::memory = memory; + + } catch (const std::exception &e) { + return 1; + } + + return 0; +} + +extern "C" int mgp_shutdown_module() { return 0; } From 1cac98b1137a1ec216ec931ce3af79bcb4077b5a Mon Sep 17 00:00:00 2001 From: imilinovic <44698587+imilinovic@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:34:27 +0200 Subject: [PATCH 2/8] Add rename label and rename node_property (#345) --- cpp/refactor_module/algorithm/refactor.cpp | 62 +++++++++++++++++++ cpp/refactor_module/algorithm/refactor.hpp | 22 ++++++- cpp/refactor_module/refactor_module.cpp | 12 ++++ e2e/refactor_test/test_rename_label/input.cyp | 1 + e2e/refactor_test/test_rename_label/test.yml | 4 ++ .../test_rename_property_nodes/input.cyp | 1 + .../test_rename_property_nodes/test.yml | 4 ++ .../refactor_test/test_rename_label/input.cyp | 1 + .../refactor_test/test_rename_label/test.yml | 4 ++ .../test_rename_property_nodes/input.cyp | 1 + .../test_rename_property_nodes/test.yml | 4 ++ 11 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 e2e/refactor_test/test_rename_label/input.cyp create mode 100644 e2e/refactor_test/test_rename_label/test.yml create mode 100644 e2e/refactor_test/test_rename_property_nodes/input.cyp create mode 100644 e2e/refactor_test/test_rename_property_nodes/test.yml create mode 100644 e2e_correctness/refactor_test/test_rename_label/input.cyp create mode 100644 e2e_correctness/refactor_test/test_rename_label/test.yml create mode 100644 e2e_correctness/refactor_test/test_rename_property_nodes/input.cyp create mode 100644 e2e_correctness/refactor_test/test_rename_property_nodes/test.yml diff --git a/cpp/refactor_module/algorithm/refactor.cpp b/cpp/refactor_module/algorithm/refactor.cpp index ebda05a79..c1368cbd8 100644 --- a/cpp/refactor_module/algorithm/refactor.cpp +++ b/cpp/refactor_module/algorithm/refactor.cpp @@ -1 +1,63 @@ #include "refactor.hpp" + +#include "mgp.hpp" + +void Refactor::RenameLabel(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::memory = memory; + auto arguments = mgp::List(args); + const auto record_factory = mgp::RecordFactory(result); + try { + const auto old_label{arguments[0].ValueString()}; + const auto new_label{arguments[1].ValueString()}; + const auto nodes{arguments[2].ValueList()}; + + int64_t nodes_changed{0}; + for (const auto &node_value : nodes) { + auto node = node_value.ValueNode(); + if (!node.HasLabel(old_label)) { + continue; + } + + node.RemoveLabel(old_label); + node.AddLabel(new_label); + nodes_changed++; + } + auto record = record_factory.NewRecord(); + record.Insert(std::string(kRenameLabelResult).c_str(), nodes_changed); + + } catch (const std::exception &e) { + record_factory.SetErrorMessage(e.what()); + return; + } +} + +void Refactor::RenameNodeProperty(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::memory = memory; + auto arguments = mgp::List(args); + const auto record_factory = mgp::RecordFactory(result); + try { + const auto old_property_name{std::string(arguments[0].ValueString())}; + const auto new_property_name{std::string(arguments[1].ValueString())}; + const auto nodes{arguments[2].ValueList()}; + + int64_t nodes_changed{0}; + for (const auto &node_value : nodes) { + auto node = node_value.ValueNode(); + auto old_property = node.GetProperty(old_property_name); + if (old_property.IsNull()) { + continue; + } + + node.RemoveProperty(old_property_name); + node.SetProperty(new_property_name, old_property); + nodes_changed++; + } + + auto record = record_factory.NewRecord(); + record.Insert(std::string(kRenameLabelResult).c_str(), nodes_changed); + + } catch (const std::exception &e) { + record_factory.SetErrorMessage(e.what()); + return; + } +} diff --git a/cpp/refactor_module/algorithm/refactor.hpp b/cpp/refactor_module/algorithm/refactor.hpp index 003cf201c..0d4b4e649 100644 --- a/cpp/refactor_module/algorithm/refactor.hpp +++ b/cpp/refactor_module/algorithm/refactor.hpp @@ -2,4 +2,24 @@ #include -namespace Refactor {} // namespace Refactor +namespace Refactor { + +/* rename_label constants */ +constexpr std::string_view kProcedureRenameLabel = "rename_label"; +constexpr std::string_view kRenameLabelArg1 = "old_label"; +constexpr std::string_view kRenameLabelArg2 = "new_label"; +constexpr std::string_view kRenameLabelArg3 = "nodes"; +constexpr std::string_view kRenameLabelResult = "nodes_changed"; + +/* rename_node_property constants */ +constexpr std::string_view kProcedureRenameNodeProperty = "rename_node_property"; +constexpr std::string_view kRenameNodePropertyArg1 = "old_property"; +constexpr std::string_view kRenameNodePropertyArg2 = "new_property"; +constexpr std::string_view kRenameNodePropertyArg3 = "nodes"; +constexpr std::string_view kRenameNodePropertyResult = "nodes_changed"; + +void RenameLabel(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + +void RenameNodeProperty(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + +} // namespace Refactor diff --git a/cpp/refactor_module/refactor_module.cpp b/cpp/refactor_module/refactor_module.cpp index 4917b075b..3b6efb4ed 100644 --- a/cpp/refactor_module/refactor_module.cpp +++ b/cpp/refactor_module/refactor_module.cpp @@ -6,6 +6,18 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem try { mgp::memory = memory; + AddProcedure(Refactor::RenameLabel, Refactor::kProcedureRenameLabel, mgp::ProcedureType::Write, + {mgp::Parameter(Refactor::kRenameLabelArg1, mgp::Type::String), + mgp::Parameter(Refactor::kRenameLabelArg2, mgp::Type::String), + mgp::Parameter(Refactor::kRenameLabelArg3, {mgp::Type::List, mgp::Type::Node})}, + {mgp::Return(Refactor::kRenameLabelResult, mgp::Type::Int)}, module, memory); + + AddProcedure(Refactor::RenameNodeProperty, Refactor::kProcedureRenameNodeProperty, mgp::ProcedureType::Write, + {mgp::Parameter(Refactor::kRenameNodePropertyArg1, mgp::Type::String), + mgp::Parameter(Refactor::kRenameNodePropertyArg2, mgp::Type::String), + mgp::Parameter(Refactor::kRenameNodePropertyArg3, {mgp::Type::List, mgp::Type::Node})}, + {mgp::Return(Refactor::kRenameNodePropertyResult, mgp::Type::Int)}, module, memory); + } catch (const std::exception &e) { return 1; } diff --git a/e2e/refactor_test/test_rename_label/input.cyp b/e2e/refactor_test/test_rename_label/input.cyp new file mode 100644 index 000000000..65d531eee --- /dev/null +++ b/e2e/refactor_test/test_rename_label/input.cyp @@ -0,0 +1 @@ +CREATE (:Node1 {title:"Name", id:0}) CREATE (:Node2 {title:"Name", id:1}) CREATE (:Node1 {id:2}); diff --git a/e2e/refactor_test/test_rename_label/test.yml b/e2e/refactor_test/test_rename_label/test.yml new file mode 100644 index 000000000..50cb71ab9 --- /dev/null +++ b/e2e/refactor_test/test_rename_label/test.yml @@ -0,0 +1,4 @@ +query: > + MATCH(n) WHERE n.title = 'Name' WITH collect(n) AS nodes CALL refactor.rename_label("Node1", "Node3", nodes) YIELD nodes_changed RETURN nodes_changed; +output: + - nodes_changed: 1 diff --git a/e2e/refactor_test/test_rename_property_nodes/input.cyp b/e2e/refactor_test/test_rename_property_nodes/input.cyp new file mode 100644 index 000000000..210406a86 --- /dev/null +++ b/e2e/refactor_test/test_rename_property_nodes/input.cyp @@ -0,0 +1 @@ +CREATE (:Node1 {title:"Node", id:0}) CREATE (:Node2 {title:"A great node", id:1, description: "Even greater description"}) CREATE (:Node1 {description: "Marvelous node", id:2}) CREATE (:Node2 {id:3}) CREATE(:Node3 {id:4}); diff --git a/e2e/refactor_test/test_rename_property_nodes/test.yml b/e2e/refactor_test/test_rename_property_nodes/test.yml new file mode 100644 index 000000000..73d708c72 --- /dev/null +++ b/e2e/refactor_test/test_rename_property_nodes/test.yml @@ -0,0 +1,4 @@ +query: > + MATCH (n) WHERE n:Node1 OR n:Node2 WITH collect(n) as nodes CALL refactor.rename_node_property("title", "description", nodes) YIELD nodes_changed RETURN nodes_changed; +output: + - nodes_changed: 2 diff --git a/e2e_correctness/refactor_test/test_rename_label/input.cyp b/e2e_correctness/refactor_test/test_rename_label/input.cyp new file mode 100644 index 000000000..65d531eee --- /dev/null +++ b/e2e_correctness/refactor_test/test_rename_label/input.cyp @@ -0,0 +1 @@ +CREATE (:Node1 {title:"Name", id:0}) CREATE (:Node2 {title:"Name", id:1}) CREATE (:Node1 {id:2}); diff --git a/e2e_correctness/refactor_test/test_rename_label/test.yml b/e2e_correctness/refactor_test/test_rename_label/test.yml new file mode 100644 index 000000000..19d37c953 --- /dev/null +++ b/e2e_correctness/refactor_test/test_rename_label/test.yml @@ -0,0 +1,4 @@ +memgraph_query: > + MATCH(n) WHERE n.title = 'Name' WITH collect(n) AS nodes CALL refactor.rename_label("Node1", "Node3", nodes) YIELD nodes_changed RETURN nodes_changed; +neo4j_query: > + MATCH(n) WHERE n.title = 'Name' WITH collect(n) AS nodes CALL apoc.refactor.rename.label("Node1", "Node3", nodes) YIELD batches return batches; diff --git a/e2e_correctness/refactor_test/test_rename_property_nodes/input.cyp b/e2e_correctness/refactor_test/test_rename_property_nodes/input.cyp new file mode 100644 index 000000000..210406a86 --- /dev/null +++ b/e2e_correctness/refactor_test/test_rename_property_nodes/input.cyp @@ -0,0 +1 @@ +CREATE (:Node1 {title:"Node", id:0}) CREATE (:Node2 {title:"A great node", id:1, description: "Even greater description"}) CREATE (:Node1 {description: "Marvelous node", id:2}) CREATE (:Node2 {id:3}) CREATE(:Node3 {id:4}); diff --git a/e2e_correctness/refactor_test/test_rename_property_nodes/test.yml b/e2e_correctness/refactor_test/test_rename_property_nodes/test.yml new file mode 100644 index 000000000..708872116 --- /dev/null +++ b/e2e_correctness/refactor_test/test_rename_property_nodes/test.yml @@ -0,0 +1,4 @@ +memgraph_query: > + MATCH (n) WHERE n:Node1 OR n:Node2 WITH collect(n) as nodes CALL refactor.rename_node_property("title", "description", nodes) YIELD nodes_changed RETURN nodes_changed; +neo4j_query: > + MATCH (n) WHERE n:Node1 OR n:Node2 WITH collect(n) as nodes CALL apoc.refactor.rename.nodeProperty("title", "description", nodes) YIELD batches RETURN batches; From 639b257f34794ee9dbd0544f8e402b560dbb3231 Mon Sep 17 00:00:00 2001 From: imilinovic Date: Fri, 8 Sep 2023 13:41:18 +0200 Subject: [PATCH 3/8] update submodules --- cpp/memgraph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/memgraph b/cpp/memgraph index e928eed02..a0667ec6e 160000 --- a/cpp/memgraph +++ b/cpp/memgraph @@ -1 +1 @@ -Subproject commit e928eed028341e32c10780d4060ddfaeadd700d4 +Subproject commit a0667ec6ee04a690a573ceaef9d2c1a0d2007f0d From 9fc7984c8ea5be063c29b73fa1cf406f52c5e78b Mon Sep 17 00:00:00 2001 From: ind1xa <95344788+ind1xa@users.noreply.github.com> Date: Sun, 10 Sep 2023 07:20:27 +0200 Subject: [PATCH 4/8] [E-refactor < E-idora] Add part of refactor procedures (#350) Implement refactor.categorize, refactor.clone_nodes, refactor.clone_subgraph and refactor.clone_subgraph_from_path --- cpp/refactor_module/algorithm/refactor.cpp | 275 ++++++++++++++++++ cpp/refactor_module/algorithm/refactor.hpp | 47 +++ cpp/refactor_module/refactor_module.cpp | 37 +++ .../test_clone_subgraph_1/input.cyp | 1 + .../test_clone_subgraph_1/test.yml | 55 ++++ .../test_clone_subgraph_2/input.cyp | 1 + .../test_clone_subgraph_2/test.yml | 46 +++ .../test_clone_subgraph_3/input.cyp | 1 + .../test_clone_subgraph_3/test.yml | 57 ++++ .../test_online_categorize_1/input.cyp | 7 + .../test_online_categorize_1/test.yml | 51 ++++ .../test_online_categorize_2/input.cyp | 7 + .../test_online_categorize_2/test.yml | 51 ++++ .../test_online_categorize_3/input.cyp | 7 + .../test_online_categorize_3/test.yml | 52 ++++ .../refactor_test/test_clone_nodes1/input.cyp | 1 + .../refactor_test/test_clone_nodes1/test.yml | 8 + .../refactor_test/test_clone_nodes2/input.cyp | 1 + .../refactor_test/test_clone_nodes2/test.yml | 8 + .../refactor_test/test_clone_nodes3/input.cyp | 1 + .../refactor_test/test_clone_nodes3/test.yml | 8 + .../test_clone_subgraph_path_1/input.cyp | 1 + .../test_clone_subgraph_path_1/test.yml | 21 ++ .../test_clone_subgraph_path_2/input.cyp | 1 + .../test_clone_subgraph_path_2/test.yml | 15 + .../test_clone_subgraph_path_3/input.cyp | 1 + .../test_clone_subgraph_path_3/test.yml | 19 ++ 27 files changed, 780 insertions(+) create mode 100644 e2e/refactor_test/test_clone_subgraph_1/input.cyp create mode 100644 e2e/refactor_test/test_clone_subgraph_1/test.yml create mode 100644 e2e/refactor_test/test_clone_subgraph_2/input.cyp create mode 100644 e2e/refactor_test/test_clone_subgraph_2/test.yml create mode 100644 e2e/refactor_test/test_clone_subgraph_3/input.cyp create mode 100644 e2e/refactor_test/test_clone_subgraph_3/test.yml create mode 100644 e2e/refactor_test/test_online_categorize_1/input.cyp create mode 100644 e2e/refactor_test/test_online_categorize_1/test.yml create mode 100644 e2e/refactor_test/test_online_categorize_2/input.cyp create mode 100644 e2e/refactor_test/test_online_categorize_2/test.yml create mode 100644 e2e/refactor_test/test_online_categorize_3/input.cyp create mode 100644 e2e/refactor_test/test_online_categorize_3/test.yml create mode 100644 e2e_correctness/refactor_test/test_clone_nodes1/input.cyp create mode 100644 e2e_correctness/refactor_test/test_clone_nodes1/test.yml create mode 100644 e2e_correctness/refactor_test/test_clone_nodes2/input.cyp create mode 100644 e2e_correctness/refactor_test/test_clone_nodes2/test.yml create mode 100644 e2e_correctness/refactor_test/test_clone_nodes3/input.cyp create mode 100644 e2e_correctness/refactor_test/test_clone_nodes3/test.yml create mode 100644 e2e_correctness/refactor_test/test_clone_subgraph_path_1/input.cyp create mode 100644 e2e_correctness/refactor_test/test_clone_subgraph_path_1/test.yml create mode 100644 e2e_correctness/refactor_test/test_clone_subgraph_path_2/input.cyp create mode 100644 e2e_correctness/refactor_test/test_clone_subgraph_path_2/test.yml create mode 100644 e2e_correctness/refactor_test/test_clone_subgraph_path_3/input.cyp create mode 100644 e2e_correctness/refactor_test/test_clone_subgraph_path_3/test.yml diff --git a/cpp/refactor_module/algorithm/refactor.cpp b/cpp/refactor_module/algorithm/refactor.cpp index c1368cbd8..5d114c7ad 100644 --- a/cpp/refactor_module/algorithm/refactor.cpp +++ b/cpp/refactor_module/algorithm/refactor.cpp @@ -1,7 +1,282 @@ #include "refactor.hpp" +#include +#include #include "mgp.hpp" +void Refactor::InsertCloneNodesRecord(mgp_graph *graph, mgp_result *result, mgp_memory *memory, const int cycle_id, + const int node_id) { + auto *record = mgp::result_new_record(result); + + mg_utility::InsertIntValueResult(record, std::string(kResultClonedNodeId).c_str(), cycle_id, memory); + mg_utility::InsertNodeValueResult(graph, record, std::string(kResultNewNode).c_str(), node_id, memory); +} + +mgp::Node GetStandinOrCopy(const mgp::List &standin_nodes, const mgp::Node node, + const std::map &old_new_node_mirror) { + for (auto pair : standin_nodes) { + if (!pair.IsList() || !pair.ValueList()[0].IsNode() || !pair.ValueList()[1].IsNode()) { + throw mgp::ValueException( + "Configuration map must consist of specific keys and values described in documentation."); + } + if (node == pair.ValueList()[0].ValueNode()) { + return pair.ValueList()[1].ValueNode(); + } + } + try { + return old_new_node_mirror.at(node); + } catch (const std::out_of_range &e) { + throw mgp::ValueException("Can't clone relationship without cloning relationship's source and/or target nodes."); + } +} + +bool CheckIfStandin(const mgp::Node &node, const mgp::List &standin_nodes) { + for (auto pair : standin_nodes) { + if (!pair.IsList() || !pair.ValueList()[0].IsNode()) { + throw mgp::ValueException( + "Configuration map must consist of specific keys and values described in documentation."); + } + if (node == pair.ValueList()[0].ValueNode()) { + return true; + } + } + return false; +} + +void CloneNodes(const std::vector &nodes, const mgp::List &standin_nodes, mgp::Graph &graph, + const std::unordered_set &skip_props_searchable, + std::map &old_new_node_mirror, mgp_graph *memgraph_graph, mgp_result *result, + mgp_memory *memory) { + for (auto node : nodes) { + if (CheckIfStandin(node, standin_nodes)) { + continue; + } + mgp::Node new_node = graph.CreateNode(); + + for (auto label : node.Labels()) { + new_node.AddLabel(label); + } + + for (auto prop : node.Properties()) { + if (skip_props_searchable.empty() || !skip_props_searchable.contains(mgp::Value(prop.first))) { + new_node.SetProperty(prop.first, prop.second); + } + } + old_new_node_mirror.insert({node, new_node}); + Refactor::InsertCloneNodesRecord(memgraph_graph, result, memory, node.Id().AsInt(), new_node.Id().AsInt()); + } +} + +void CloneRels(const std::vector &rels, const mgp::List &standin_nodes, mgp::Graph &graph, + const std::unordered_set &skip_props_searchable, + std::map &old_new_node_mirror, mgp_graph *memgraph_graph, mgp_result *result, + mgp_memory *memory) { + for (auto rel : rels) { + mgp::Relationship new_relationship = + graph.CreateRelationship(GetStandinOrCopy(standin_nodes, rel.From(), old_new_node_mirror), + GetStandinOrCopy(standin_nodes, rel.To(), old_new_node_mirror), rel.Type()); + for (auto prop : rel.Properties()) { + if (skip_props_searchable.empty() || !skip_props_searchable.contains(mgp::Value(prop.first))) { + new_relationship.SetProperty(prop.first, prop.second); + } + } + } +} + +void Refactor::CloneNodesAndRels(mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory, + const std::vector &nodes, const std::vector &rels, + const mgp::Map &config_map) { + mgp::List standin_nodes; + mgp::List skip_props; + if ((!config_map.At("standinNodes").IsList() && !config_map.At("standinNodes").IsNull()) || + (!config_map.At("skipProperties").IsList() && !config_map.At("skipProperties").IsNull())) { + throw mgp::ValueException("Configuration map must consist of specific keys and values described in documentation."); + } + if (!config_map.At("standinNodes").IsNull()) { + standin_nodes = config_map.At("standinNodes").ValueList(); + } + if (!config_map.At("skipProperties").IsNull()) { + skip_props = config_map.At("skipProperties").ValueList(); + } + std::unordered_set skip_props_searchable{skip_props.begin(), skip_props.end()}; + + auto graph = mgp::Graph(memgraph_graph); + + std::map old_new_node_mirror; + CloneNodes(nodes, standin_nodes, graph, skip_props_searchable, old_new_node_mirror, memgraph_graph, result, memory); + CloneRels(rels, standin_nodes, graph, skip_props_searchable, old_new_node_mirror, memgraph_graph, result, memory); +} + +void Refactor::CloneSubgraphFromPaths(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, + mgp_memory *memory) { + mgp::memory = memory; + const auto arguments = mgp::List(args); + try { + const auto paths = arguments[0].ValueList(); + const auto config_map = arguments[1].ValueMap(); + + std::unordered_set distinct_nodes; + std::unordered_set distinct_relationships; + for (auto path_value : paths) { + auto path = path_value.ValuePath(); + for (size_t index = 0; index < path.Length(); index++) { + distinct_nodes.insert(path.GetNodeAt(index)); + distinct_relationships.insert(path.GetRelationshipAt(index)); + } + distinct_nodes.insert(path.GetNodeAt(path.Length())); + } + std::vector nodes_vector{distinct_nodes.begin(), distinct_nodes.end()}; + std::vector rels_vector{distinct_relationships.begin(), distinct_relationships.end()}; + CloneNodesAndRels(memgraph_graph, result, memory, nodes_vector, rels_vector, config_map); + + } catch (const std::exception &e) { + mgp::result_set_error_msg(result, e.what()); + return; + } +} + +void Refactor::CloneSubgraph(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::memory = memory; + const auto arguments = mgp::List(args); + try { + const auto nodes = arguments[0].ValueList(); + const auto rels = arguments[1].ValueList(); + const auto config_map = arguments[2].ValueMap(); + + std::unordered_set distinct_nodes; + std::unordered_set distinct_rels; + + for (auto node : nodes) { + distinct_nodes.insert(node.ValueNode()); + } + for (auto rel : rels) { + distinct_rels.insert(rel.ValueRelationship()); + } + + if (distinct_rels.size() == 0 && distinct_nodes.size() > 0) { + for (auto node : distinct_nodes) { + for (auto rel : node.OutRelationships()) { + if (distinct_nodes.contains(rel.To())) { + distinct_rels.insert(rel); + } + } + } + } + + std::vector nodes_vector{distinct_nodes.begin(), distinct_nodes.end()}; + std::vector rels_vector{distinct_rels.begin(), distinct_rels.end()}; + + CloneNodesAndRels(memgraph_graph, result, memory, nodes_vector, rels_vector, config_map); + + } catch (const std::exception &e) { + mgp::result_set_error_msg(result, e.what()); + return; + } +} + +mgp::Node getCategoryNode(mgp::Graph &graph, std::unordered_set &created_nodes, + std::string_view new_prop_name_key, mgp::Value &new_node_name, std::string_view new_label) { + for (auto node : created_nodes) { + if (node.GetProperty(std::string(new_prop_name_key)) == new_node_name) { + return node; + } + } + mgp::Node category_node = graph.CreateNode(); + category_node.AddLabel(new_label); + category_node.SetProperty(std::string(new_prop_name_key), new_node_name); + created_nodes.insert(category_node); + return category_node; +} + +void Refactor::Categorize(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::memory = memory; + const auto arguments = mgp::List(args); + auto graph = mgp::Graph(memgraph_graph); + const auto record_factory = mgp::RecordFactory(result); + try { + const auto original_prop_key = arguments[0].ValueString(); + const auto rel_type = arguments[1].ValueString(); + const auto is_outgoing = arguments[2].ValueBool(); + const auto new_label = arguments[3].ValueString(); + const auto new_prop_name_key = arguments[4].ValueString(); + const auto copy_props_list = arguments[5].ValueList(); + + std::unordered_set created_nodes; + + for (auto node : graph.Nodes()) { + auto new_node_name = node.GetProperty(std::string(original_prop_key)); + if (new_node_name.IsNull()) { + continue; + } + + auto category_node = getCategoryNode(graph, created_nodes, new_prop_name_key, new_node_name, new_label); + + if (is_outgoing) { + graph.CreateRelationship(node, category_node, rel_type); + } else { + graph.CreateRelationship(category_node, node, rel_type); + } + + node.RemoveProperty(std::string(original_prop_key)); + for (auto key : copy_props_list) { + auto prop_key = std::string(key.ValueString()); + auto prop_value = node.GetProperty(prop_key); + if (prop_value.IsNull() || prop_key == new_prop_name_key) { + continue; + } + category_node.SetProperty(prop_key, prop_value); + node.RemoveProperty(prop_key); + } + } + + auto record = record_factory.NewRecord(); + record.Insert(std::string(kResultCategorize).c_str(), "success"); + } catch (const std::exception &e) { + mgp::result_set_error_msg(result, e.what()); + return; + } +} + +void Refactor::CloneNodes(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::memory = memory; + const auto arguments = mgp::List(args); + auto graph = mgp::Graph(memgraph_graph); + try { + const auto nodes = arguments[0].ValueList(); + const auto clone_rels = arguments[1].ValueBool(); + const auto skip_props = arguments[2].ValueList(); + std::unordered_set skip_props_searchable{skip_props.begin(), skip_props.end()}; + + for (auto node : nodes) { + mgp::Node old_node = node.ValueNode(); + mgp::Node new_node = graph.CreateNode(); + + for (auto label : old_node.Labels()) { + new_node.AddLabel(label); + } + + for (auto prop : old_node.Properties()) { + if (skip_props.Empty() || !skip_props_searchable.contains(mgp::Value(prop.first))) { + new_node.SetProperty(prop.first, prop.second); + } + } + + if (clone_rels) { + for (auto rel : old_node.InRelationships()) { + graph.CreateRelationship(rel.From(), new_node, rel.Type()); + } + for (auto rel : old_node.OutRelationships()) { + graph.CreateRelationship(new_node, rel.To(), rel.Type()); + } + } + InsertCloneNodesRecord(memgraph_graph, result, memory, old_node.Id().AsInt(), new_node.Id().AsInt()); + } + } catch (const std::exception &e) { + mgp::result_set_error_msg(result, e.what()); + return; + } +} + void Refactor::RenameLabel(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { mgp::memory = memory; auto arguments = mgp::List(args); diff --git a/cpp/refactor_module/algorithm/refactor.hpp b/cpp/refactor_module/algorithm/refactor.hpp index 0d4b4e649..d055725f2 100644 --- a/cpp/refactor_module/algorithm/refactor.hpp +++ b/cpp/refactor_module/algorithm/refactor.hpp @@ -3,6 +3,42 @@ #include namespace Refactor { +/* categorize constants */ + +constexpr const std::string_view kProcedureCategorize = "categorize"; +constexpr const std::string_view kReturnCategorize = "status"; + +constexpr const std::string_view kArgumentsCatSourceKey = "original_prop_key"; +constexpr const std::string_view kArgumentsCatRelType = "rel_type"; +constexpr const std::string_view kArgumentsCatRelOutgoing = "is_outgoing"; +constexpr const std::string_view kArgumentsCatLabelName = "new_label"; +constexpr const std::string_view kArgumentsCatPropKey = "new_prop_name_key"; +constexpr const std::string_view kArgumentsCopyPropKeys = "copy_props_list"; + +constexpr const std::string_view kResultCategorize = "status"; + +/* clone_nodes constants */ + +constexpr const std::string_view kProcedureCloneNodes = "clone_nodes"; +constexpr const std::string_view kReturnClonedNodeId = "cloned_node_id"; +constexpr const std::string_view kReturnNewNode = "new_node"; +constexpr const std::string_view kArgumentsNodesToClone = "nodes"; +constexpr const std::string_view kArgumentsCloneRels = "clone_rels"; +constexpr const std::string_view kArgumentsSkipPropClone = "skip_props"; +constexpr const std::string_view kResultClonedNodeId = "cloned_node_id"; +constexpr const std::string_view kResultNewNode = "new_node"; + +/* clone_subgraph_from_paths constants */ + +constexpr const std::string_view kProcedureCSFP = "clone_subgraph_from_paths"; +constexpr const std::string_view kArgumentsPath = "paths"; +constexpr const std::string_view kArgumentsConfigMap = "config"; + +/* clone_subgraph constants */ + +constexpr const std::string_view kProcedureCloneSubgraph = "clone_subgraph"; +constexpr const std::string_view kArgumentsNodes = "nodes"; +constexpr const std::string_view kArgumentsRels = "rels"; /* rename_label constants */ constexpr std::string_view kProcedureRenameLabel = "rename_label"; @@ -18,6 +54,17 @@ constexpr std::string_view kRenameNodePropertyArg2 = "new_property"; constexpr std::string_view kRenameNodePropertyArg3 = "nodes"; constexpr std::string_view kRenameNodePropertyResult = "nodes_changed"; +void Categorize(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + +void InsertCloneNodesRecord(mgp_graph *graph, mgp_result *result, mgp_memory *memory, const int cycle_id, + const int node_id); +void CloneNodes(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); +void CloneNodesAndRels(mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory, + const std::vector &nodes, const std::vector &rels, + const mgp::Map &config_map); +void CloneSubgraphFromPaths(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); +void CloneSubgraph(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + void RenameLabel(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); void RenameNodeProperty(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); diff --git a/cpp/refactor_module/refactor_module.cpp b/cpp/refactor_module/refactor_module.cpp index 3b6efb4ed..1cd54ee84 100644 --- a/cpp/refactor_module/refactor_module.cpp +++ b/cpp/refactor_module/refactor_module.cpp @@ -6,6 +6,43 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem try { mgp::memory = memory; + AddProcedure(Refactor::Categorize, Refactor::kProcedureCategorize, mgp::ProcedureType::Write, + { + mgp::Parameter(Refactor::kArgumentsCatSourceKey, mgp::Type::String), + mgp::Parameter(Refactor::kArgumentsCatRelType, mgp::Type::String), + mgp::Parameter(Refactor::kArgumentsCatRelOutgoing, mgp::Type::Bool), + mgp::Parameter(Refactor::kArgumentsCatLabelName, mgp::Type::String), + mgp::Parameter(Refactor::kArgumentsCatPropKey, mgp::Type::String), + mgp::Parameter(Refactor::kArgumentsCopyPropKeys, {mgp::Type::List, mgp::Type::String}, + mgp::Value(mgp::List{})), + }, + {mgp::Return(Refactor::kReturnCategorize, mgp::Type::String)}, module, memory); + AddProcedure(Refactor::CloneNodes, Refactor::kProcedureCloneNodes, mgp::ProcedureType::Write, + {mgp::Parameter(Refactor::kArgumentsNodesToClone, {mgp::Type::List, mgp::Type::Node}), + mgp::Parameter(Refactor::kArgumentsCloneRels, mgp::Type::Bool, false), + mgp::Parameter(Refactor::kArgumentsSkipPropClone, {mgp::Type::List, mgp::Type::String}, + mgp::Value(mgp::List{}))}, + {mgp::Return(Refactor::kReturnClonedNodeId, mgp::Type::Int), + mgp::Return(Refactor::kReturnNewNode, mgp::Type::Node)}, + module, memory); + + AddProcedure( + Refactor::CloneSubgraphFromPaths, Refactor::kProcedureCSFP, mgp::ProcedureType::Write, + {mgp::Parameter(Refactor::kArgumentsPath, {mgp::Type::List, mgp::Type::Path}), + mgp::Parameter(Refactor::kArgumentsConfigMap, {mgp::Type::Map, mgp::Type::Any}, mgp::Value(mgp::Map{}))}, + {mgp::Return(Refactor::kReturnClonedNodeId, mgp::Type::Int), + mgp::Return(Refactor::kReturnNewNode, mgp::Type::Node)}, + module, memory); + + AddProcedure( + Refactor::CloneSubgraph, Refactor::kProcedureCloneSubgraph, mgp::ProcedureType::Write, + {mgp::Parameter(Refactor::kArgumentsNodes, {mgp::Type::List, mgp::Type::Node}), + mgp::Parameter(Refactor::kArgumentsRels, {mgp::Type::List, mgp::Type::Relationship}, mgp::Value(mgp::List())), + mgp::Parameter(Refactor::kArgumentsConfigMap, {mgp::Type::Map, mgp::Type::Any}, mgp::Value(mgp::Map{}))}, + {mgp::Return(Refactor::kReturnClonedNodeId, mgp::Type::Int), + mgp::Return(Refactor::kReturnNewNode, mgp::Type::Node)}, + module, memory); + AddProcedure(Refactor::RenameLabel, Refactor::kProcedureRenameLabel, mgp::ProcedureType::Write, {mgp::Parameter(Refactor::kRenameLabelArg1, mgp::Type::String), mgp::Parameter(Refactor::kRenameLabelArg2, mgp::Type::String), diff --git a/e2e/refactor_test/test_clone_subgraph_1/input.cyp b/e2e/refactor_test/test_clone_subgraph_1/input.cyp new file mode 100644 index 000000000..1b9cedeef --- /dev/null +++ b/e2e/refactor_test/test_clone_subgraph_1/input.cyp @@ -0,0 +1 @@ +MERGE (ana:Ana{name:'Ana', id:0}) MERGE (marija:Marija{name:'Marija', id: 1}) MERGE (p2:Person{name:'person2', id:2}) MERGE (p3:Person{name:'person3', id:3}) MERGE (p4:Person{name:'person4', id:4}) MERGE (p5:Person{name:'person5', id:5}) MERGE (p6:Person{name:'person6', id:6}) CREATE (ana)-[:KNOWS]->(p2)-[:KNOWS]->(p3)-[:KNOWS]->(p4) CREATE (p4)<-[:KNOWS]-(p5) CREATE (p5)-[:LOVES]->(p6) CREATE (marija)-[:KNOWS]->(p6); diff --git a/e2e/refactor_test/test_clone_subgraph_1/test.yml b/e2e/refactor_test/test_clone_subgraph_1/test.yml new file mode 100644 index 000000000..f31246ae0 --- /dev/null +++ b/e2e/refactor_test/test_clone_subgraph_1/test.yml @@ -0,0 +1,55 @@ +query: > + MATCH (ana:Ana), + (marija:Marija), + (p2:Person{id: 2}), + (p3:Person{id: 3}), + (p4:Person{id: 4}), + (p5:Person{id: 5}), + (p6:Person{id: 6}) + CALL refactor.clone_subgraph([ana, marija, p2, p3, p4, p5, p6]) + YIELD new_node + RETURN new_node; + +output: + - new_node: + labels: + - Person + properties: + id: 6 + name: person6 + - new_node: + labels: + - Person + properties: + id: 5 + name: person5 + - new_node: + labels: + - Person + properties: + id: 4 + name: person4 + - new_node: + labels: + - Person + properties: + id: 3 + name: person3 + - new_node: + labels: + - Person + properties: + id: 2 + name: person2 + - new_node: + labels: + - Marija + properties: + id: 1 + name: Marija + - new_node: + labels: + - Ana + properties: + id: 0 + name: Ana diff --git a/e2e/refactor_test/test_clone_subgraph_2/input.cyp b/e2e/refactor_test/test_clone_subgraph_2/input.cyp new file mode 100644 index 000000000..81e64b7f0 --- /dev/null +++ b/e2e/refactor_test/test_clone_subgraph_2/input.cyp @@ -0,0 +1 @@ +MERGE (ana:Ana{name:'Ana', id:0}) MERGE (marija:Marija{name:'Marija', id: 1}) MERGE (p2:Person{name:'person2', id:2}) MERGE (p3:Person{name:'person3', id:3}) MERGE (p4:Person{name:'person4', id:4}) MERGE (p5:Person{name:'person5', id:5}) MERGE (p6:Person{name:'person6', id:6}) CREATE (ana)-[:KNOWS]->(p2)-[:KNOWS]->(p3)-[:KNOWS]->(p4) CREATE (p4)<-[:KNOWS]-(p5) CREATE (marija)-[:KNOWS]->(p6); diff --git a/e2e/refactor_test/test_clone_subgraph_2/test.yml b/e2e/refactor_test/test_clone_subgraph_2/test.yml new file mode 100644 index 000000000..ad5fcd0d3 --- /dev/null +++ b/e2e/refactor_test/test_clone_subgraph_2/test.yml @@ -0,0 +1,46 @@ +query: > + MATCH (ana:Ana), + (marija:Marija), + (p2:Person{id: 2}), + (p3:Person{id: 3}), + (p4:Person{id: 4}), + (p5:Person{id: 5}), + (p6:Person{id: 6}) + CALL refactor.clone_subgraph([ana, marija, p2, p3, p4, p5, p6], [], { + standinNodes:[[ana, marija]], + skipProperties: ["name"] + }) + YIELD new_node + RETURN new_node; + +output: + - new_node: + labels: + - Person + properties: + id: 6 + - new_node: + labels: + - Person + properties: + id: 5 + - new_node: + labels: + - Person + properties: + id: 4 + - new_node: + labels: + - Person + properties: + id: 3 + - new_node: + labels: + - Person + properties: + id: 2 + - new_node: + labels: + - Marija + properties: + id: 1 diff --git a/e2e/refactor_test/test_clone_subgraph_3/input.cyp b/e2e/refactor_test/test_clone_subgraph_3/input.cyp new file mode 100644 index 000000000..1b9cedeef --- /dev/null +++ b/e2e/refactor_test/test_clone_subgraph_3/input.cyp @@ -0,0 +1 @@ +MERGE (ana:Ana{name:'Ana', id:0}) MERGE (marija:Marija{name:'Marija', id: 1}) MERGE (p2:Person{name:'person2', id:2}) MERGE (p3:Person{name:'person3', id:3}) MERGE (p4:Person{name:'person4', id:4}) MERGE (p5:Person{name:'person5', id:5}) MERGE (p6:Person{name:'person6', id:6}) CREATE (ana)-[:KNOWS]->(p2)-[:KNOWS]->(p3)-[:KNOWS]->(p4) CREATE (p4)<-[:KNOWS]-(p5) CREATE (p5)-[:LOVES]->(p6) CREATE (marija)-[:KNOWS]->(p6); diff --git a/e2e/refactor_test/test_clone_subgraph_3/test.yml b/e2e/refactor_test/test_clone_subgraph_3/test.yml new file mode 100644 index 000000000..601d5331a --- /dev/null +++ b/e2e/refactor_test/test_clone_subgraph_3/test.yml @@ -0,0 +1,57 @@ +query: > + MATCH (ana:Ana), + (marija:Marija), + (p2:Person{id: 2}), + (p3:Person{id: 3}), + (p4:Person{id: 4}), + (p5:Person{id: 5}), + (p6:Person{id: 6}), + (ana)-[r1:KNOWS]->(p2), + (p2)-[r2:KNOWS]->(p3) + CALL refactor.clone_subgraph([ana, marija, p2, p3, p4, p5, p6], [r2, r1]) + YIELD new_node + RETURN new_node; + +output: + - new_node: + labels: + - Person + properties: + id: 6 + name: person6 + - new_node: + labels: + - Person + properties: + id: 5 + name: person5 + - new_node: + labels: + - Person + properties: + id: 4 + name: person4 + - new_node: + labels: + - Person + properties: + id: 3 + name: person3 + - new_node: + labels: + - Person + properties: + id: 2 + name: person2 + - new_node: + labels: + - Marija + properties: + id: 1 + name: Marija + - new_node: + labels: + - Ana + properties: + id: 0 + name: Ana diff --git a/e2e/refactor_test/test_online_categorize_1/input.cyp b/e2e/refactor_test/test_online_categorize_1/input.cyp new file mode 100644 index 000000000..2ef861bf0 --- /dev/null +++ b/e2e/refactor_test/test_online_categorize_1/input.cyp @@ -0,0 +1,7 @@ +queries: + - |- + CREATE (a:Movie {id: 0, name: "MovieName", genre: "Drama"}) + CREATE (b:Book {id: 1, name: "BookName1", genre: "Drama", propertyToCopy: "copy me"}) + CREATE (c:Book {id: 2, name: "BookName2", genre: "Romance"}); + - |- + MATCH (n) RETURN n; diff --git a/e2e/refactor_test/test_online_categorize_1/test.yml b/e2e/refactor_test/test_online_categorize_1/test.yml new file mode 100644 index 000000000..e74ab0927 --- /dev/null +++ b/e2e/refactor_test/test_online_categorize_1/test.yml @@ -0,0 +1,51 @@ +- query: >- + CALL refactor.categorize('genre', 'GENRE', false, "Genre", "name") YIELD status RETURN status; + output: + - status: "success" + +- query: >- + MATCH (a)-[r]->(b) RETURN a, r, b + output: + - a: + labels: + - Genre + properties: + name: Drama + r: + label: GENRE + properties: {} + b: + labels: + - Movie + properties: + name: MovieName + id: 0 + - a: + labels: + - Genre + properties: + name: Drama + r: + label: GENRE + properties: {} + b: + labels: + - Book + properties: + propertyToCopy: copy me + name: BookName1 + id: 1 + - a: + labels: + - Genre + properties: + name: Romance + r: + label: GENRE + properties: {} + b: + labels: + - Book + properties: + name: BookName2 + id: 2 diff --git a/e2e/refactor_test/test_online_categorize_2/input.cyp b/e2e/refactor_test/test_online_categorize_2/input.cyp new file mode 100644 index 000000000..2ef861bf0 --- /dev/null +++ b/e2e/refactor_test/test_online_categorize_2/input.cyp @@ -0,0 +1,7 @@ +queries: + - |- + CREATE (a:Movie {id: 0, name: "MovieName", genre: "Drama"}) + CREATE (b:Book {id: 1, name: "BookName1", genre: "Drama", propertyToCopy: "copy me"}) + CREATE (c:Book {id: 2, name: "BookName2", genre: "Romance"}); + - |- + MATCH (n) RETURN n; diff --git a/e2e/refactor_test/test_online_categorize_2/test.yml b/e2e/refactor_test/test_online_categorize_2/test.yml new file mode 100644 index 000000000..41fc7d7c7 --- /dev/null +++ b/e2e/refactor_test/test_online_categorize_2/test.yml @@ -0,0 +1,51 @@ +- query: >- + CALL refactor.categorize('genre', 'GENRE', true, "Genre", "name") YIELD status RETURN status; + output: + - status: "success" + +- query: >- + MATCH (a)-[r]->(b) RETURN a, r, b + output: + - a: + labels: + - Movie + properties: + name: MovieName + id: 0 + r: + label: GENRE + properties: {} + b: + labels: + - Genre + properties: + name: Drama + - a: + labels: + - Book + properties: + propertyToCopy: copy me + name: BookName1 + id: 1 + r: + label: GENRE + properties: {} + b: + labels: + - Genre + properties: + name: Drama + - a: + labels: + - Book + properties: + name: BookName2 + id: 2 + r: + label: GENRE + properties: {} + b: + labels: + - Genre + properties: + name: Romance diff --git a/e2e/refactor_test/test_online_categorize_3/input.cyp b/e2e/refactor_test/test_online_categorize_3/input.cyp new file mode 100644 index 000000000..2ef861bf0 --- /dev/null +++ b/e2e/refactor_test/test_online_categorize_3/input.cyp @@ -0,0 +1,7 @@ +queries: + - |- + CREATE (a:Movie {id: 0, name: "MovieName", genre: "Drama"}) + CREATE (b:Book {id: 1, name: "BookName1", genre: "Drama", propertyToCopy: "copy me"}) + CREATE (c:Book {id: 2, name: "BookName2", genre: "Romance"}); + - |- + MATCH (n) RETURN n; diff --git a/e2e/refactor_test/test_online_categorize_3/test.yml b/e2e/refactor_test/test_online_categorize_3/test.yml new file mode 100644 index 000000000..dead7806a --- /dev/null +++ b/e2e/refactor_test/test_online_categorize_3/test.yml @@ -0,0 +1,52 @@ +- query: >- + CALL refactor.categorize('genre', 'GENRE', false, "Genre", "name", ["propertyToCopy", "name"]) YIELD status RETURN status; + output: + - status: "success" + +- query: >- + MATCH (a)-[r]->(b) RETURN a, r, b + output: + - a: + labels: + - Genre + properties: + propertyToCopy: copy me + name: Drama + r: + label: GENRE + properties: {} + b: + labels: + - Movie + properties: + name: MovieName + id: 0 + - a: + labels: + - Genre + properties: + propertyToCopy: copy me + name: Drama + r: + label: GENRE + properties: {} + b: + labels: + - Book + properties: + name: BookName1 + id: 1 + - a: + labels: + - Genre + properties: + name: Romance + r: + label: GENRE + properties: {} + b: + labels: + - Book + properties: + name: BookName2 + id: 2 diff --git a/e2e_correctness/refactor_test/test_clone_nodes1/input.cyp b/e2e_correctness/refactor_test/test_clone_nodes1/input.cyp new file mode 100644 index 000000000..e0cdedd18 --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_nodes1/input.cyp @@ -0,0 +1 @@ +MERGE (a:Ana {name: "Ana", age: 22, id:0}) MERGE (b:Marija {name: "Marija", age: 20, id:1}) CREATE (a)-[r:KNOWS]->(b); diff --git a/e2e_correctness/refactor_test/test_clone_nodes1/test.yml b/e2e_correctness/refactor_test/test_clone_nodes1/test.yml new file mode 100644 index 000000000..f0383e4ea --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_nodes1/test.yml @@ -0,0 +1,8 @@ +memgraph_query: > + MATCH (a)-[r]->(b) + CALL refactor.clone_nodes([a]) YIELD * RETURN *; + + +neo4j_query: > + MATCH (a)-[r]->(b) + CALL apoc.refactor.cloneNodes([a]) YIELD input, output RETURN input, output; diff --git a/e2e_correctness/refactor_test/test_clone_nodes2/input.cyp b/e2e_correctness/refactor_test/test_clone_nodes2/input.cyp new file mode 100644 index 000000000..e0cdedd18 --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_nodes2/input.cyp @@ -0,0 +1 @@ +MERGE (a:Ana {name: "Ana", age: 22, id:0}) MERGE (b:Marija {name: "Marija", age: 20, id:1}) CREATE (a)-[r:KNOWS]->(b); diff --git a/e2e_correctness/refactor_test/test_clone_nodes2/test.yml b/e2e_correctness/refactor_test/test_clone_nodes2/test.yml new file mode 100644 index 000000000..cefb55628 --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_nodes2/test.yml @@ -0,0 +1,8 @@ +memgraph_query: > + MATCH (a)-[r]->(b) + CALL refactor.clone_nodes([a, b], True, ["age"]) YIELD * RETURN *; + + +neo4j_query: > + MATCH (a)-[r]->(b) + CALL apoc.refactor.cloneNodes([a, b], True, ["age"]) YIELD input, output RETURN input, output; diff --git a/e2e_correctness/refactor_test/test_clone_nodes3/input.cyp b/e2e_correctness/refactor_test/test_clone_nodes3/input.cyp new file mode 100644 index 000000000..e10638a68 --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_nodes3/input.cyp @@ -0,0 +1 @@ +MERGE (a:Person {name: "Ana", age: 22, id: 0}) MERGE (b:Person {name: "Marija", age: 20, id: 1}) MERGE (c:Person {name: "Iva", age: 21, id: 2}) CREATE (a)-[r1:KNOWS]->(b) CREATE (c)-[r2:KNOWS]->(a); diff --git a/e2e_correctness/refactor_test/test_clone_nodes3/test.yml b/e2e_correctness/refactor_test/test_clone_nodes3/test.yml new file mode 100644 index 000000000..cefb55628 --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_nodes3/test.yml @@ -0,0 +1,8 @@ +memgraph_query: > + MATCH (a)-[r]->(b) + CALL refactor.clone_nodes([a, b], True, ["age"]) YIELD * RETURN *; + + +neo4j_query: > + MATCH (a)-[r]->(b) + CALL apoc.refactor.cloneNodes([a, b], True, ["age"]) YIELD input, output RETURN input, output; diff --git a/e2e_correctness/refactor_test/test_clone_subgraph_path_1/input.cyp b/e2e_correctness/refactor_test/test_clone_subgraph_path_1/input.cyp new file mode 100644 index 000000000..1b9cedeef --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_subgraph_path_1/input.cyp @@ -0,0 +1 @@ +MERGE (ana:Ana{name:'Ana', id:0}) MERGE (marija:Marija{name:'Marija', id: 1}) MERGE (p2:Person{name:'person2', id:2}) MERGE (p3:Person{name:'person3', id:3}) MERGE (p4:Person{name:'person4', id:4}) MERGE (p5:Person{name:'person5', id:5}) MERGE (p6:Person{name:'person6', id:6}) CREATE (ana)-[:KNOWS]->(p2)-[:KNOWS]->(p3)-[:KNOWS]->(p4) CREATE (p4)<-[:KNOWS]-(p5) CREATE (p5)-[:LOVES]->(p6) CREATE (marija)-[:KNOWS]->(p6); diff --git a/e2e_correctness/refactor_test/test_clone_subgraph_path_1/test.yml b/e2e_correctness/refactor_test/test_clone_subgraph_path_1/test.yml new file mode 100644 index 000000000..8434452ab --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_subgraph_path_1/test.yml @@ -0,0 +1,21 @@ +memgraph_query: > + MATCH (ana:Ana), + (marija:Marija) + MATCH path = (ana)-[:KNOWS*]->(node) + WITH ana, marija, collect(path) as paths + CALL refactor.clone_subgraph_from_paths(paths, { + standinNodes:[[ana, marija]], + skipProperties: ["name"] + }) + YIELD cloned_node_id, new_node + RETURN cloned_node_id, new_node; + +neo4j_query: > + MATCH (ana:Ana), + (marija:Marija) + MATCH path = (ana)-[:KNOWS*]->(node) + WITH ana, marija, collect(path) as paths + CALL apoc.refactor.cloneSubgraphFromPaths(paths, { + standinNodes:[[ana, marija]], + skipProperties: ["name"] + }) YIELD input, output, error RETURN input, output, error; diff --git a/e2e_correctness/refactor_test/test_clone_subgraph_path_2/input.cyp b/e2e_correctness/refactor_test/test_clone_subgraph_path_2/input.cyp new file mode 100644 index 000000000..1b9cedeef --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_subgraph_path_2/input.cyp @@ -0,0 +1 @@ +MERGE (ana:Ana{name:'Ana', id:0}) MERGE (marija:Marija{name:'Marija', id: 1}) MERGE (p2:Person{name:'person2', id:2}) MERGE (p3:Person{name:'person3', id:3}) MERGE (p4:Person{name:'person4', id:4}) MERGE (p5:Person{name:'person5', id:5}) MERGE (p6:Person{name:'person6', id:6}) CREATE (ana)-[:KNOWS]->(p2)-[:KNOWS]->(p3)-[:KNOWS]->(p4) CREATE (p4)<-[:KNOWS]-(p5) CREATE (p5)-[:LOVES]->(p6) CREATE (marija)-[:KNOWS]->(p6); diff --git a/e2e_correctness/refactor_test/test_clone_subgraph_path_2/test.yml b/e2e_correctness/refactor_test/test_clone_subgraph_path_2/test.yml new file mode 100644 index 000000000..b1807aaac --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_subgraph_path_2/test.yml @@ -0,0 +1,15 @@ +memgraph_query: > + MATCH (ana:Ana), + (marija:Marija) + MATCH path = (ana)-[:KNOWS*]->(node) + WITH ana, marija, collect(path) as paths + CALL refactor.clone_subgraph_from_paths(paths) + YIELD cloned_node_id, new_node + RETURN cloned_node_id, new_node; + +neo4j_query: > + MATCH (ana:Ana), + (marija:Marija) + MATCH path = (ana)-[:KNOWS*]->(node) + WITH ana, marija, collect(path) as paths + CALL apoc.refactor.cloneSubgraphFromPaths(paths) YIELD input, output, error RETURN input, output, error; diff --git a/e2e_correctness/refactor_test/test_clone_subgraph_path_3/input.cyp b/e2e_correctness/refactor_test/test_clone_subgraph_path_3/input.cyp new file mode 100644 index 000000000..1b9cedeef --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_subgraph_path_3/input.cyp @@ -0,0 +1 @@ +MERGE (ana:Ana{name:'Ana', id:0}) MERGE (marija:Marija{name:'Marija', id: 1}) MERGE (p2:Person{name:'person2', id:2}) MERGE (p3:Person{name:'person3', id:3}) MERGE (p4:Person{name:'person4', id:4}) MERGE (p5:Person{name:'person5', id:5}) MERGE (p6:Person{name:'person6', id:6}) CREATE (ana)-[:KNOWS]->(p2)-[:KNOWS]->(p3)-[:KNOWS]->(p4) CREATE (p4)<-[:KNOWS]-(p5) CREATE (p5)-[:LOVES]->(p6) CREATE (marija)-[:KNOWS]->(p6); diff --git a/e2e_correctness/refactor_test/test_clone_subgraph_path_3/test.yml b/e2e_correctness/refactor_test/test_clone_subgraph_path_3/test.yml new file mode 100644 index 000000000..52a463a94 --- /dev/null +++ b/e2e_correctness/refactor_test/test_clone_subgraph_path_3/test.yml @@ -0,0 +1,19 @@ +memgraph_query: > + MATCH (ana:Ana), + (marija:Marija) + MATCH path = (ana)-[:KNOWS*]->(node) + WITH ana, marija, collect(path) as paths + CALL refactor.clone_subgraph_from_paths([], { + standinNodes:[[ana, marija]] + }) + YIELD cloned_node_id, new_node + RETURN cloned_node_id, new_node; + +neo4j_query: > + MATCH (ana:Ana), + (marija:Marija) + MATCH path = (ana)-[:KNOWS*]->(node) + WITH ana, marija, collect(path) as paths + CALL apoc.refactor.cloneSubgraphFromPaths([], { + standinNodes:[[ana, marija]] + }) YIELD input, output, error RETURN input, output, error; From 63069a0f66e096b45048712e76f699e1332222a3 Mon Sep 17 00:00:00 2001 From: imilinovic <44698587+imilinovic@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:33:04 +0200 Subject: [PATCH 5/8] Add refactor to and refactor from (#343) --- cpp/refactor_module/algorithm/refactor.cpp | 162 +++++++++++------- cpp/refactor_module/algorithm/refactor.hpp | 31 +++- cpp/refactor_module/refactor_module.cpp | 12 +- .../test_from_different/input.cyp | 1 + .../test_from_different/test.yml | 5 + .../refactor_test/test_from_same/input.cyp | 1 + .../refactor_test/test_from_same/test.yml | 5 + .../test_from_same_endpoints/input.cyp | 1 + .../test_from_same_endpoints/test.yml | 5 + .../test_from_to_endpoints/input.cyp | 1 + .../test_from_to_endpoints/test.yml | 5 + .../refactor_test/test_to_different/input.cyp | 1 + .../refactor_test/test_to_different/test.yml | 5 + .../refactor_test/test_to_same/input.cyp | 1 + .../refactor_test/test_to_same/test.yml | 5 + 15 files changed, 168 insertions(+), 73 deletions(-) create mode 100644 e2e_correctness/refactor_test/test_from_different/input.cyp create mode 100644 e2e_correctness/refactor_test/test_from_different/test.yml create mode 100644 e2e_correctness/refactor_test/test_from_same/input.cyp create mode 100644 e2e_correctness/refactor_test/test_from_same/test.yml create mode 100644 e2e_correctness/refactor_test/test_from_same_endpoints/input.cyp create mode 100644 e2e_correctness/refactor_test/test_from_same_endpoints/test.yml create mode 100644 e2e_correctness/refactor_test/test_from_to_endpoints/input.cyp create mode 100644 e2e_correctness/refactor_test/test_from_to_endpoints/test.yml create mode 100644 e2e_correctness/refactor_test/test_to_different/input.cyp create mode 100644 e2e_correctness/refactor_test/test_to_different/test.yml create mode 100644 e2e_correctness/refactor_test/test_to_same/input.cyp create mode 100644 e2e_correctness/refactor_test/test_to_same/test.yml diff --git a/cpp/refactor_module/algorithm/refactor.cpp b/cpp/refactor_module/algorithm/refactor.cpp index 5d114c7ad..d3fd72e6f 100644 --- a/cpp/refactor_module/algorithm/refactor.cpp +++ b/cpp/refactor_module/algorithm/refactor.cpp @@ -4,6 +4,100 @@ #include #include "mgp.hpp" +void Refactor::From(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::MemoryDispatcherGuard guard{memory}; + auto arguments = mgp::List(args); + const auto record_factory = mgp::RecordFactory(result); + try { + mgp::Relationship relationship{arguments[0].ValueRelationship()}; + const mgp::Node new_from{arguments[1].ValueNode()}; + mgp::Graph graph{memgraph_graph}; + + graph.SetFrom(relationship, new_from); + + } catch (const std::exception &e) { + record_factory.SetErrorMessage(e.what()); + return; + } +} + +void Refactor::To(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::MemoryDispatcherGuard guard{memory}; + auto arguments = mgp::List(args); + const auto record_factory = mgp::RecordFactory(result); + try { + mgp::Relationship relationship{arguments[0].ValueRelationship()}; + const mgp::Node new_to{arguments[1].ValueNode()}; + mgp::Graph graph{memgraph_graph}; + + graph.SetTo(relationship, new_to); + + } catch (const std::exception &e) { + record_factory.SetErrorMessage(e.what()); + return; + } +} + +void Refactor::RenameLabel(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::MemoryDispatcherGuard guard{memory}; + auto arguments = mgp::List(args); + const auto record_factory = mgp::RecordFactory(result); + try { + const auto old_label{arguments[0].ValueString()}; + const auto new_label{arguments[1].ValueString()}; + const auto nodes{arguments[2].ValueList()}; + + int64_t nodes_changed{0}; + for (const auto &node_value : nodes) { + auto node = node_value.ValueNode(); + if (!node.HasLabel(old_label)) { + continue; + } + + node.RemoveLabel(old_label); + node.AddLabel(new_label); + nodes_changed++; + } + auto record = record_factory.NewRecord(); + record.Insert(std::string(kRenameLabelResult).c_str(), nodes_changed); + + } catch (const std::exception &e) { + record_factory.SetErrorMessage(e.what()); + return; + } +} + +void Refactor::RenameNodeProperty(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::MemoryDispatcherGuard guard{memory}; + auto arguments = mgp::List(args); + const auto record_factory = mgp::RecordFactory(result); + try { + const auto old_property_name{std::string(arguments[0].ValueString())}; + const auto new_property_name{std::string(arguments[1].ValueString())}; + const auto nodes{arguments[2].ValueList()}; + + int64_t nodes_changed{0}; + for (const auto &node_value : nodes) { + auto node = node_value.ValueNode(); + auto old_property = node.GetProperty(old_property_name); + if (old_property.IsNull()) { + continue; + } + + node.RemoveProperty(old_property_name); + node.SetProperty(new_property_name, old_property); + nodes_changed++; + } + + auto record = record_factory.NewRecord(); + record.Insert(std::string(kRenameLabelResult).c_str(), nodes_changed); + + } catch (const std::exception &e) { + record_factory.SetErrorMessage(e.what()); + return; + } +} + void Refactor::InsertCloneNodesRecord(mgp_graph *graph, mgp_result *result, mgp_memory *memory, const int cycle_id, const int node_id) { auto *record = mgp::result_new_record(result); @@ -109,7 +203,7 @@ void Refactor::CloneNodesAndRels(mgp_graph *memgraph_graph, mgp_result *result, void Refactor::CloneSubgraphFromPaths(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { - mgp::memory = memory; + mgp::MemoryDispatcherGuard guard{memory}; const auto arguments = mgp::List(args); try { const auto paths = arguments[0].ValueList(); @@ -136,7 +230,7 @@ void Refactor::CloneSubgraphFromPaths(mgp_list *args, mgp_graph *memgraph_graph, } void Refactor::CloneSubgraph(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { - mgp::memory = memory; + mgp::MemoryDispatcherGuard guard{memory}; const auto arguments = mgp::List(args); try { const auto nodes = arguments[0].ValueList(); @@ -189,7 +283,7 @@ mgp::Node getCategoryNode(mgp::Graph &graph, std::unordered_set &crea } void Refactor::Categorize(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { - mgp::memory = memory; + mgp::MemoryDispatcherGuard guard{memory}; const auto arguments = mgp::List(args); auto graph = mgp::Graph(memgraph_graph); const auto record_factory = mgp::RecordFactory(result); @@ -238,7 +332,7 @@ void Refactor::Categorize(mgp_list *args, mgp_graph *memgraph_graph, mgp_result } void Refactor::CloneNodes(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { - mgp::memory = memory; + mgp::MemoryDispatcherGuard guard{memory}; const auto arguments = mgp::List(args); auto graph = mgp::Graph(memgraph_graph); try { @@ -276,63 +370,3 @@ void Refactor::CloneNodes(mgp_list *args, mgp_graph *memgraph_graph, mgp_result return; } } - -void Refactor::RenameLabel(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { - mgp::memory = memory; - auto arguments = mgp::List(args); - const auto record_factory = mgp::RecordFactory(result); - try { - const auto old_label{arguments[0].ValueString()}; - const auto new_label{arguments[1].ValueString()}; - const auto nodes{arguments[2].ValueList()}; - - int64_t nodes_changed{0}; - for (const auto &node_value : nodes) { - auto node = node_value.ValueNode(); - if (!node.HasLabel(old_label)) { - continue; - } - - node.RemoveLabel(old_label); - node.AddLabel(new_label); - nodes_changed++; - } - auto record = record_factory.NewRecord(); - record.Insert(std::string(kRenameLabelResult).c_str(), nodes_changed); - - } catch (const std::exception &e) { - record_factory.SetErrorMessage(e.what()); - return; - } -} - -void Refactor::RenameNodeProperty(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { - mgp::memory = memory; - auto arguments = mgp::List(args); - const auto record_factory = mgp::RecordFactory(result); - try { - const auto old_property_name{std::string(arguments[0].ValueString())}; - const auto new_property_name{std::string(arguments[1].ValueString())}; - const auto nodes{arguments[2].ValueList()}; - - int64_t nodes_changed{0}; - for (const auto &node_value : nodes) { - auto node = node_value.ValueNode(); - auto old_property = node.GetProperty(old_property_name); - if (old_property.IsNull()) { - continue; - } - - node.RemoveProperty(old_property_name); - node.SetProperty(new_property_name, old_property); - nodes_changed++; - } - - auto record = record_factory.NewRecord(); - record.Insert(std::string(kRenameLabelResult).c_str(), nodes_changed); - - } catch (const std::exception &e) { - record_factory.SetErrorMessage(e.what()); - return; - } -} diff --git a/cpp/refactor_module/algorithm/refactor.hpp b/cpp/refactor_module/algorithm/refactor.hpp index d055725f2..ecc3fc22d 100644 --- a/cpp/refactor_module/algorithm/refactor.hpp +++ b/cpp/refactor_module/algorithm/refactor.hpp @@ -3,8 +3,8 @@ #include namespace Refactor { -/* categorize constants */ +/* categorize constants */ constexpr const std::string_view kProcedureCategorize = "categorize"; constexpr const std::string_view kReturnCategorize = "status"; @@ -18,7 +18,6 @@ constexpr const std::string_view kArgumentsCopyPropKeys = "copy_props_list"; constexpr const std::string_view kResultCategorize = "status"; /* clone_nodes constants */ - constexpr const std::string_view kProcedureCloneNodes = "clone_nodes"; constexpr const std::string_view kReturnClonedNodeId = "cloned_node_id"; constexpr const std::string_view kReturnNewNode = "new_node"; @@ -29,17 +28,25 @@ constexpr const std::string_view kResultClonedNodeId = "cloned_node_id"; constexpr const std::string_view kResultNewNode = "new_node"; /* clone_subgraph_from_paths constants */ - constexpr const std::string_view kProcedureCSFP = "clone_subgraph_from_paths"; constexpr const std::string_view kArgumentsPath = "paths"; constexpr const std::string_view kArgumentsConfigMap = "config"; /* clone_subgraph constants */ - constexpr const std::string_view kProcedureCloneSubgraph = "clone_subgraph"; constexpr const std::string_view kArgumentsNodes = "nodes"; constexpr const std::string_view kArgumentsRels = "rels"; +/* from constants */ +constexpr const std::string_view kProcedureFrom = "from"; +constexpr const std::string_view kFromArg1 = "relationship"; +constexpr const std::string_view kFromArg2 = "new_from"; + +/* to constants */ +constexpr const std::string_view kProcedureTo = "to"; +constexpr const std::string_view kToArg1 = "relationship"; +constexpr const std::string_view kToArg2 = "new_to"; + /* rename_label constants */ constexpr std::string_view kProcedureRenameLabel = "rename_label"; constexpr std::string_view kRenameLabelArg1 = "old_label"; @@ -54,19 +61,27 @@ constexpr std::string_view kRenameNodePropertyArg2 = "new_property"; constexpr std::string_view kRenameNodePropertyArg3 = "nodes"; constexpr std::string_view kRenameNodePropertyResult = "nodes_changed"; +void From(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + +void To(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + +void RenameLabel(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + +void RenameNodeProperty(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + void Categorize(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); void InsertCloneNodesRecord(mgp_graph *graph, mgp_result *result, mgp_memory *memory, const int cycle_id, const int node_id); + void CloneNodes(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + void CloneNodesAndRels(mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory, const std::vector &nodes, const std::vector &rels, const mgp::Map &config_map); -void CloneSubgraphFromPaths(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); -void CloneSubgraph(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); -void RenameLabel(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); +void CloneSubgraphFromPaths(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); -void RenameNodeProperty(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); +void CloneSubgraph(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); } // namespace Refactor diff --git a/cpp/refactor_module/refactor_module.cpp b/cpp/refactor_module/refactor_module.cpp index 1cd54ee84..372042d44 100644 --- a/cpp/refactor_module/refactor_module.cpp +++ b/cpp/refactor_module/refactor_module.cpp @@ -4,7 +4,17 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) { try { - mgp::memory = memory; + mgp::MemoryDispatcherGuard guard{memory}; + + mgp::AddProcedure(Refactor::From, Refactor::kProcedureFrom, mgp::ProcedureType::Write, + {mgp::Parameter(Refactor::kFromArg1, mgp::Type::Relationship), + mgp::Parameter(Refactor::kFromArg2, mgp::Type::Node)}, + {}, module, memory); + + mgp::AddProcedure(Refactor::To, Refactor::kProcedureTo, mgp::ProcedureType::Write, + {mgp::Parameter(Refactor::kToArg1, mgp::Type::Relationship), + mgp::Parameter(Refactor::kToArg2, mgp::Type::Node)}, + {}, module, memory); AddProcedure(Refactor::Categorize, Refactor::kProcedureCategorize, mgp::ProcedureType::Write, { diff --git a/e2e_correctness/refactor_test/test_from_different/input.cyp b/e2e_correctness/refactor_test/test_from_different/input.cyp new file mode 100644 index 000000000..6364ea234 --- /dev/null +++ b/e2e_correctness/refactor_test/test_from_different/input.cyp @@ -0,0 +1 @@ +MERGE (a:Person {name: "Ivan", id:0}) MERGE (b:Person {name: "Matija", id:1}) MERGE (c:Person {name:"Idora", human:true, id:2}) CREATE (a)-[f:Friends]->(b); diff --git a/e2e_correctness/refactor_test/test_from_different/test.yml b/e2e_correctness/refactor_test/test_from_different/test.yml new file mode 100644 index 000000000..63ec07662 --- /dev/null +++ b/e2e_correctness/refactor_test/test_from_different/test.yml @@ -0,0 +1,5 @@ +memgraph_query: > + MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL refactor.from(rel, idora); + +neo4j_query: > + MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL apoc.refactor.from(rel, idora) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_from_same/input.cyp b/e2e_correctness/refactor_test/test_from_same/input.cyp new file mode 100644 index 000000000..6364ea234 --- /dev/null +++ b/e2e_correctness/refactor_test/test_from_same/input.cyp @@ -0,0 +1 @@ +MERGE (a:Person {name: "Ivan", id:0}) MERGE (b:Person {name: "Matija", id:1}) MERGE (c:Person {name:"Idora", human:true, id:2}) CREATE (a)-[f:Friends]->(b); diff --git a/e2e_correctness/refactor_test/test_from_same/test.yml b/e2e_correctness/refactor_test/test_from_same/test.yml new file mode 100644 index 000000000..9ae51b9c4 --- /dev/null +++ b/e2e_correctness/refactor_test/test_from_same/test.yml @@ -0,0 +1,5 @@ +memgraph_query: > + MATCH (n)-[rel:Friends]->() CALL refactor.from(rel, n); + +neo4j_query: > + MATCH (n)-[rel:Friends]->() CALL apoc.refactor.from(rel, n) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_from_same_endpoints/input.cyp b/e2e_correctness/refactor_test/test_from_same_endpoints/input.cyp new file mode 100644 index 000000000..6364ea234 --- /dev/null +++ b/e2e_correctness/refactor_test/test_from_same_endpoints/input.cyp @@ -0,0 +1 @@ +MERGE (a:Person {name: "Ivan", id:0}) MERGE (b:Person {name: "Matija", id:1}) MERGE (c:Person {name:"Idora", human:true, id:2}) CREATE (a)-[f:Friends]->(b); diff --git a/e2e_correctness/refactor_test/test_from_same_endpoints/test.yml b/e2e_correctness/refactor_test/test_from_same_endpoints/test.yml new file mode 100644 index 000000000..ee9107475 --- /dev/null +++ b/e2e_correctness/refactor_test/test_from_same_endpoints/test.yml @@ -0,0 +1,5 @@ +memgraph_query: > + MATCH ()-[rel:Friends]->(n) CALL refactor.from(rel, n); + +neo4j_query: > + MATCH ()-[rel:Friends]->(n) CALL apoc.refactor.from(rel, n) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_from_to_endpoints/input.cyp b/e2e_correctness/refactor_test/test_from_to_endpoints/input.cyp new file mode 100644 index 000000000..6364ea234 --- /dev/null +++ b/e2e_correctness/refactor_test/test_from_to_endpoints/input.cyp @@ -0,0 +1 @@ +MERGE (a:Person {name: "Ivan", id:0}) MERGE (b:Person {name: "Matija", id:1}) MERGE (c:Person {name:"Idora", human:true, id:2}) CREATE (a)-[f:Friends]->(b); diff --git a/e2e_correctness/refactor_test/test_from_to_endpoints/test.yml b/e2e_correctness/refactor_test/test_from_to_endpoints/test.yml new file mode 100644 index 000000000..cb443aef3 --- /dev/null +++ b/e2e_correctness/refactor_test/test_from_to_endpoints/test.yml @@ -0,0 +1,5 @@ +memgraph_query: > + MATCH (n)-[rel:Friends]->() CALL refactor.to(rel, n); + +neo4j_query: > + MATCH (n)-[rel:Friends]->() CALL apoc.refactor.to(rel, n) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_to_different/input.cyp b/e2e_correctness/refactor_test/test_to_different/input.cyp new file mode 100644 index 000000000..6364ea234 --- /dev/null +++ b/e2e_correctness/refactor_test/test_to_different/input.cyp @@ -0,0 +1 @@ +MERGE (a:Person {name: "Ivan", id:0}) MERGE (b:Person {name: "Matija", id:1}) MERGE (c:Person {name:"Idora", human:true, id:2}) CREATE (a)-[f:Friends]->(b); diff --git a/e2e_correctness/refactor_test/test_to_different/test.yml b/e2e_correctness/refactor_test/test_to_different/test.yml new file mode 100644 index 000000000..6316fbc8f --- /dev/null +++ b/e2e_correctness/refactor_test/test_to_different/test.yml @@ -0,0 +1,5 @@ +memgraph_query: > + MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL refactor.to(rel, idora); + +neo4j_query: > + MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL apoc.refactor.to(rel, idora) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_to_same/input.cyp b/e2e_correctness/refactor_test/test_to_same/input.cyp new file mode 100644 index 000000000..6364ea234 --- /dev/null +++ b/e2e_correctness/refactor_test/test_to_same/input.cyp @@ -0,0 +1 @@ +MERGE (a:Person {name: "Ivan", id:0}) MERGE (b:Person {name: "Matija", id:1}) MERGE (c:Person {name:"Idora", human:true, id:2}) CREATE (a)-[f:Friends]->(b); diff --git a/e2e_correctness/refactor_test/test_to_same/test.yml b/e2e_correctness/refactor_test/test_to_same/test.yml new file mode 100644 index 000000000..586454a19 --- /dev/null +++ b/e2e_correctness/refactor_test/test_to_same/test.yml @@ -0,0 +1,5 @@ +memgraph_query: > + MATCH ()-[rel:Friends]->(n) CALL refactor.to(rel, n); + +neo4j_query: > + MATCH ()-[rel:Friends]->(n) CALL apoc.refactor.to(rel, n) YIELD input RETURN input; From d9edab6783a01b19886a7a546db10e2d0724e78d Mon Sep 17 00:00:00 2001 From: imilinovic Date: Mon, 11 Sep 2023 16:29:32 +0200 Subject: [PATCH 6/8] change to&from to return relationship --- cpp/refactor_module/algorithm/refactor.cpp | 4 ++++ cpp/refactor_module/algorithm/refactor.hpp | 3 ++- cpp/refactor_module/refactor_module.cpp | 4 ++-- e2e_correctness/refactor_test/test_from_different/test.yml | 2 +- e2e_correctness/refactor_test/test_from_same/test.yml | 2 +- .../refactor_test/test_from_same_endpoints/test.yml | 2 +- e2e_correctness/refactor_test/test_from_to_endpoints/test.yml | 2 +- e2e_correctness/refactor_test/test_to_different/test.yml | 2 +- e2e_correctness/refactor_test/test_to_same/test.yml | 2 +- 9 files changed, 14 insertions(+), 9 deletions(-) diff --git a/cpp/refactor_module/algorithm/refactor.cpp b/cpp/refactor_module/algorithm/refactor.cpp index d3fd72e6f..4db9207ba 100644 --- a/cpp/refactor_module/algorithm/refactor.cpp +++ b/cpp/refactor_module/algorithm/refactor.cpp @@ -14,6 +14,8 @@ void Refactor::From(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *resul mgp::Graph graph{memgraph_graph}; graph.SetFrom(relationship, new_from); + auto record = record_factory.NewRecord(); + record.Insert(std::string(kFromResult).c_str(), relationship); } catch (const std::exception &e) { record_factory.SetErrorMessage(e.what()); @@ -31,6 +33,8 @@ void Refactor::To(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp::Graph graph{memgraph_graph}; graph.SetTo(relationship, new_to); + auto record = record_factory.NewRecord(); + record.Insert(std::string(kToResult).c_str(), relationship); } catch (const std::exception &e) { record_factory.SetErrorMessage(e.what()); diff --git a/cpp/refactor_module/algorithm/refactor.hpp b/cpp/refactor_module/algorithm/refactor.hpp index ecc3fc22d..82fc62724 100644 --- a/cpp/refactor_module/algorithm/refactor.hpp +++ b/cpp/refactor_module/algorithm/refactor.hpp @@ -1,7 +1,6 @@ #pragma once #include - namespace Refactor { /* categorize constants */ @@ -41,11 +40,13 @@ constexpr const std::string_view kArgumentsRels = "rels"; constexpr const std::string_view kProcedureFrom = "from"; constexpr const std::string_view kFromArg1 = "relationship"; constexpr const std::string_view kFromArg2 = "new_from"; +constexpr const std::string_view kFromResult = "relationship"; /* to constants */ constexpr const std::string_view kProcedureTo = "to"; constexpr const std::string_view kToArg1 = "relationship"; constexpr const std::string_view kToArg2 = "new_to"; +constexpr const std::string_view kToResult = "relationship"; /* rename_label constants */ constexpr std::string_view kProcedureRenameLabel = "rename_label"; diff --git a/cpp/refactor_module/refactor_module.cpp b/cpp/refactor_module/refactor_module.cpp index 372042d44..d3f82a017 100644 --- a/cpp/refactor_module/refactor_module.cpp +++ b/cpp/refactor_module/refactor_module.cpp @@ -9,12 +9,12 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem mgp::AddProcedure(Refactor::From, Refactor::kProcedureFrom, mgp::ProcedureType::Write, {mgp::Parameter(Refactor::kFromArg1, mgp::Type::Relationship), mgp::Parameter(Refactor::kFromArg2, mgp::Type::Node)}, - {}, module, memory); + {mgp::Return(Refactor::kFromResult, mgp::Type::Relationship)}, module, memory); mgp::AddProcedure(Refactor::To, Refactor::kProcedureTo, mgp::ProcedureType::Write, {mgp::Parameter(Refactor::kToArg1, mgp::Type::Relationship), mgp::Parameter(Refactor::kToArg2, mgp::Type::Node)}, - {}, module, memory); + {mgp::Return(Refactor::kToResult, mgp::Type::Relationship)}, module, memory); AddProcedure(Refactor::Categorize, Refactor::kProcedureCategorize, mgp::ProcedureType::Write, { diff --git a/e2e_correctness/refactor_test/test_from_different/test.yml b/e2e_correctness/refactor_test/test_from_different/test.yml index 63ec07662..6abe771a5 100644 --- a/e2e_correctness/refactor_test/test_from_different/test.yml +++ b/e2e_correctness/refactor_test/test_from_different/test.yml @@ -1,5 +1,5 @@ memgraph_query: > - MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL refactor.from(rel, idora); + MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL refactor.from(rel, idora) YIELD relationship RETURN relationship; neo4j_query: > MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL apoc.refactor.from(rel, idora) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_from_same/test.yml b/e2e_correctness/refactor_test/test_from_same/test.yml index 9ae51b9c4..18fb47edf 100644 --- a/e2e_correctness/refactor_test/test_from_same/test.yml +++ b/e2e_correctness/refactor_test/test_from_same/test.yml @@ -1,5 +1,5 @@ memgraph_query: > - MATCH (n)-[rel:Friends]->() CALL refactor.from(rel, n); + MATCH (n)-[rel:Friends]->() CALL refactor.from(rel, n) YIELD relationship RETURN relationship; neo4j_query: > MATCH (n)-[rel:Friends]->() CALL apoc.refactor.from(rel, n) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_from_same_endpoints/test.yml b/e2e_correctness/refactor_test/test_from_same_endpoints/test.yml index ee9107475..b7686a1ac 100644 --- a/e2e_correctness/refactor_test/test_from_same_endpoints/test.yml +++ b/e2e_correctness/refactor_test/test_from_same_endpoints/test.yml @@ -1,5 +1,5 @@ memgraph_query: > - MATCH ()-[rel:Friends]->(n) CALL refactor.from(rel, n); + MATCH ()-[rel:Friends]->(n) CALL refactor.from(rel, n) YIELD relationship RETURN relationship; neo4j_query: > MATCH ()-[rel:Friends]->(n) CALL apoc.refactor.from(rel, n) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_from_to_endpoints/test.yml b/e2e_correctness/refactor_test/test_from_to_endpoints/test.yml index cb443aef3..8c40975d1 100644 --- a/e2e_correctness/refactor_test/test_from_to_endpoints/test.yml +++ b/e2e_correctness/refactor_test/test_from_to_endpoints/test.yml @@ -1,5 +1,5 @@ memgraph_query: > - MATCH (n)-[rel:Friends]->() CALL refactor.to(rel, n); + MATCH (n)-[rel:Friends]->() CALL refactor.to(rel, n) YIELD relationship RETURN relationship; neo4j_query: > MATCH (n)-[rel:Friends]->() CALL apoc.refactor.to(rel, n) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_to_different/test.yml b/e2e_correctness/refactor_test/test_to_different/test.yml index 6316fbc8f..b000e2b48 100644 --- a/e2e_correctness/refactor_test/test_to_different/test.yml +++ b/e2e_correctness/refactor_test/test_to_different/test.yml @@ -1,5 +1,5 @@ memgraph_query: > - MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL refactor.to(rel, idora); + MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL refactor.to(rel, idora) YIELD relationship RETURN relationship; neo4j_query: > MATCH ()-[rel:Friends]->() MATCH (idora: Person {name:"Idora"}) CALL apoc.refactor.to(rel, idora) YIELD input RETURN input; diff --git a/e2e_correctness/refactor_test/test_to_same/test.yml b/e2e_correctness/refactor_test/test_to_same/test.yml index 586454a19..4ba4518a7 100644 --- a/e2e_correctness/refactor_test/test_to_same/test.yml +++ b/e2e_correctness/refactor_test/test_to_same/test.yml @@ -1,5 +1,5 @@ memgraph_query: > - MATCH ()-[rel:Friends]->(n) CALL refactor.to(rel, n); + MATCH ()-[rel:Friends]->(n) CALL refactor.to(rel, n) YIELD relationship RETURN relationship; neo4j_query: > MATCH ()-[rel:Friends]->(n) CALL apoc.refactor.to(rel, n) YIELD input RETURN input; From 997b35687c9cd7848aaaeae3a31e96d3fb94414e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20Pintari=C4=87?= <99442742+mpintaric55334@users.noreply.github.com> Date: Mon, 11 Sep 2023 22:49:34 +0200 Subject: [PATCH 7/8] [E-refactor < T372] Implement refactor.invert (#344) * Implement refactor.invert --- cpp/refactor_module/algorithm/refactor.cpp | 26 +++++++++++++++++++ cpp/refactor_module/algorithm/refactor.hpp | 10 +++++++ cpp/refactor_module/refactor_module.cpp | 5 ++++ .../refactor_test/test_invert1/input.cyp | 1 + .../refactor_test/test_invert1/test.yml | 6 +++++ .../refactor_test/test_invert2/input.cyp | 1 + .../refactor_test/test_invert2/test.yml | 6 +++++ .../refactor_test/test_invert3/input.cyp | 2 ++ .../refactor_test/test_invert3/test.yml | 6 +++++ 9 files changed, 63 insertions(+) create mode 100644 e2e_correctness/refactor_test/test_invert1/input.cyp create mode 100644 e2e_correctness/refactor_test/test_invert1/test.yml create mode 100644 e2e_correctness/refactor_test/test_invert2/input.cyp create mode 100644 e2e_correctness/refactor_test/test_invert2/test.yml create mode 100644 e2e_correctness/refactor_test/test_invert3/input.cyp create mode 100644 e2e_correctness/refactor_test/test_invert3/test.yml diff --git a/cpp/refactor_module/algorithm/refactor.cpp b/cpp/refactor_module/algorithm/refactor.cpp index 4db9207ba..5dc06a30d 100644 --- a/cpp/refactor_module/algorithm/refactor.cpp +++ b/cpp/refactor_module/algorithm/refactor.cpp @@ -374,3 +374,29 @@ void Refactor::CloneNodes(mgp_list *args, mgp_graph *memgraph_graph, mgp_result return; } } + +void Refactor::InvertRel(mgp::Graph &graph, mgp::Relationship &rel) { + const auto old_from = rel.From(); + const auto old_to = rel.To(); + graph.SetFrom(rel, old_to); + graph.SetTo(rel, old_from); +} + +void Refactor::Invert(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::memory = memory; + const auto arguments = mgp::List(args); + const auto record_factory = mgp::RecordFactory(result); + try { + mgp::Graph graph = mgp::Graph(memgraph_graph); + mgp::Relationship rel = arguments[0].ValueRelationship(); + + InvertRel(graph, rel); + auto record = record_factory.NewRecord(); + record.Insert(std::string(kReturnIdInvert).c_str(), rel.Id().AsInt()); + record.Insert(std::string(kReturnRelationshipInvert).c_str(), rel); + + } catch (const std::exception &e) { + record_factory.SetErrorMessage(e.what()); + return; + } +} diff --git a/cpp/refactor_module/algorithm/refactor.hpp b/cpp/refactor_module/algorithm/refactor.hpp index 82fc62724..2b4e59f0c 100644 --- a/cpp/refactor_module/algorithm/refactor.hpp +++ b/cpp/refactor_module/algorithm/refactor.hpp @@ -62,6 +62,13 @@ constexpr std::string_view kRenameNodePropertyArg2 = "new_property"; constexpr std::string_view kRenameNodePropertyArg3 = "nodes"; constexpr std::string_view kRenameNodePropertyResult = "nodes_changed"; + +/*invert constants*/ +constexpr std::string_view kProcedureInvert = "invert"; +constexpr std::string_view kArgumentRelationship = "relationship"; +constexpr std::string_view kReturnRelationshipInvert = "relationship"; +constexpr std::string_view kReturnIdInvert = "id_inverted"; + void From(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); void To(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); @@ -85,4 +92,7 @@ void CloneSubgraphFromPaths(mgp_list *args, mgp_graph *memgraph_graph, mgp_resul void CloneSubgraph(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + +void Invert(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); +void InvertRel(mgp::Graph &graph, mgp::Relationship &rel); } // namespace Refactor diff --git a/cpp/refactor_module/refactor_module.cpp b/cpp/refactor_module/refactor_module.cpp index d3f82a017..8a6a2f079 100644 --- a/cpp/refactor_module/refactor_module.cpp +++ b/cpp/refactor_module/refactor_module.cpp @@ -64,6 +64,11 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem mgp::Parameter(Refactor::kRenameNodePropertyArg2, mgp::Type::String), mgp::Parameter(Refactor::kRenameNodePropertyArg3, {mgp::Type::List, mgp::Type::Node})}, {mgp::Return(Refactor::kRenameNodePropertyResult, mgp::Type::Int)}, module, memory); + AddProcedure(Refactor::Invert, std::string(Refactor::kProcedureInvert).c_str(), mgp::ProcedureType::Write, + {mgp::Parameter(std::string(Refactor::kArgumentRelationship).c_str(), mgp::Type::Any)}, + {mgp::Return(std::string(Refactor::kReturnIdInvert).c_str(), mgp::Type::Int), + mgp::Return(std::string(Refactor::kReturnRelationshipInvert).c_str(), mgp::Type::Relationship)}, + module, memory); } catch (const std::exception &e) { return 1; diff --git a/e2e_correctness/refactor_test/test_invert1/input.cyp b/e2e_correctness/refactor_test/test_invert1/input.cyp new file mode 100644 index 000000000..137e58f93 --- /dev/null +++ b/e2e_correctness/refactor_test/test_invert1/input.cyp @@ -0,0 +1 @@ +CREATE (d:Dog {id: 0})-[l:LOVES]->(h:Human {id: 1}); diff --git a/e2e_correctness/refactor_test/test_invert1/test.yml b/e2e_correctness/refactor_test/test_invert1/test.yml new file mode 100644 index 000000000..9c9723aa3 --- /dev/null +++ b/e2e_correctness/refactor_test/test_invert1/test.yml @@ -0,0 +1,6 @@ +memgraph_query: > + MATCH (d:Dog)-[l:LOVES]->(h:Human) + CALL refactor.invert(l) YIELD id_inverted, relationship RETURN id_inverted, relationship; +neo4j_query: > + MATCH (d:Dog)-[l:LOVES]->(h:Human) + CALL apoc.refactor.invert(l) YIELD input,output RETURN input,output; diff --git a/e2e_correctness/refactor_test/test_invert2/input.cyp b/e2e_correctness/refactor_test/test_invert2/input.cyp new file mode 100644 index 000000000..afc354922 --- /dev/null +++ b/e2e_correctness/refactor_test/test_invert2/input.cyp @@ -0,0 +1 @@ +CREATE (d:Dog {id: 0})-[l:LOVES {property: 90, property2: 80}]->(h:Human {id: 1}); diff --git a/e2e_correctness/refactor_test/test_invert2/test.yml b/e2e_correctness/refactor_test/test_invert2/test.yml new file mode 100644 index 000000000..9c9723aa3 --- /dev/null +++ b/e2e_correctness/refactor_test/test_invert2/test.yml @@ -0,0 +1,6 @@ +memgraph_query: > + MATCH (d:Dog)-[l:LOVES]->(h:Human) + CALL refactor.invert(l) YIELD id_inverted, relationship RETURN id_inverted, relationship; +neo4j_query: > + MATCH (d:Dog)-[l:LOVES]->(h:Human) + CALL apoc.refactor.invert(l) YIELD input,output RETURN input,output; diff --git a/e2e_correctness/refactor_test/test_invert3/input.cyp b/e2e_correctness/refactor_test/test_invert3/input.cyp new file mode 100644 index 000000000..b457ebed3 --- /dev/null +++ b/e2e_correctness/refactor_test/test_invert3/input.cyp @@ -0,0 +1,2 @@ +CREATE (d:Dog {id:0})-[l:LOVES {property: 100}]->(h:Human {id:1}); +MATCH (d:Dog),(h:Human) CREATE (d)-[i:IS_OWNED_BY {property2: 700}]->(h); diff --git a/e2e_correctness/refactor_test/test_invert3/test.yml b/e2e_correctness/refactor_test/test_invert3/test.yml new file mode 100644 index 000000000..32e7081c9 --- /dev/null +++ b/e2e_correctness/refactor_test/test_invert3/test.yml @@ -0,0 +1,6 @@ +memgraph_query: > + MATCH (d:Dog)-[l]->(h:Human) + CALL refactor.invert(l) YIELD id_inverted, relationship RETURN id_inverted, relationship; +neo4j_query: > + MATCH (d:Dog)-[l]->(h:Human) + CALL apoc.refactor.invert(l) YIELD input,output RETURN input,output; From 6638df509d39a1a9f0fd3252f81a1c1106c4ad92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20Pintari=C4=87?= <99442742+mpintaric55334@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:03:53 +0200 Subject: [PATCH 8/8] Implement collapse node (#339) --- cpp/refactor_module/algorithm/refactor.cpp | 69 +++++++++++++++++++ cpp/refactor_module/algorithm/refactor.hpp | 17 +++++ cpp/refactor_module/refactor_module.cpp | 8 ++- .../test_collapse_node1/input.cyp | 4 ++ .../test_collapse_node1/test.yml | 6 ++ .../test_collapse_node2/input.cyp | 4 ++ .../test_collapse_node2/test.yml | 6 ++ .../test_collapse_node3/input.cyp | 4 ++ .../test_collapse_node3/test.yml | 6 ++ 9 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 e2e_correctness/refactor_test/test_collapse_node1/input.cyp create mode 100644 e2e_correctness/refactor_test/test_collapse_node1/test.yml create mode 100644 e2e_correctness/refactor_test/test_collapse_node2/input.cyp create mode 100644 e2e_correctness/refactor_test/test_collapse_node2/test.yml create mode 100644 e2e_correctness/refactor_test/test_collapse_node3/input.cyp create mode 100644 e2e_correctness/refactor_test/test_collapse_node3/test.yml diff --git a/cpp/refactor_module/algorithm/refactor.cpp b/cpp/refactor_module/algorithm/refactor.cpp index 5dc06a30d..56685f025 100644 --- a/cpp/refactor_module/algorithm/refactor.cpp +++ b/cpp/refactor_module/algorithm/refactor.cpp @@ -400,3 +400,72 @@ void Refactor::Invert(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *res return; } } + +void Refactor::TransferProperties(const mgp::Node &node, mgp::Relationship &rel) { + for (auto &[key, value] : node.Properties()) { + rel.SetProperty(key, value); + } +} + +void Refactor::Collapse(mgp::Graph &graph, const mgp::Node &node, const std::string &type, + const mgp::RecordFactory &record_factory) { + if (node.InDegree() != 1 || node.OutDegree() != 1) { + throw mgp::ValueException("Out and in degree of the nodes both must be 1!"); + } + + const mgp::Node from_node = (*node.InRelationships().begin()).From(); + const mgp::Node to_node = (*node.OutRelationships().begin()).To(); + if (from_node == node && to_node == node) { + throw mgp::ValueException("Nodes with self relationships are non collapsible!"); + } + mgp::Relationship new_rel = graph.CreateRelationship(from_node, to_node, type); + TransferProperties(node, new_rel); + + auto record = record_factory.NewRecord(); + record.Insert(std::string(kReturnIdCollapseNode).c_str(), node.Id().AsInt()); + record.Insert(std::string(kReturnRelationshipCollapseNode).c_str(), new_rel); + graph.DetachDeleteNode(node); +} + +void Refactor::CollapseNode(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) { + mgp::memory = memory; + const auto arguments = mgp::List(args); + const auto record_factory = mgp::RecordFactory(result); + try { + mgp::Graph graph = mgp::Graph(memgraph_graph); + const mgp::Value input = arguments[0]; + const std::string type{arguments[1].ValueString()}; + + if(!input.IsNode() && !input.IsInt() && !input.IsList()){ + record_factory.SetErrorMessage("Input can only be node, node ID, or list of nodes/IDs"); + return; + } + + if (input.IsNode()) { + const mgp::Node node = input.ValueNode(); + Collapse(graph, node, type, record_factory); + } else if (input.IsInt()) { + const mgp::Node node = graph.GetNodeById(mgp::Id::FromInt(input.ValueInt())); + Collapse(graph, node, type, record_factory); + } else if (input.IsList()) { + for (auto elem : input.ValueList()) { + if (elem.IsNode()) { + const mgp::Node node = elem.ValueNode(); + Collapse(graph, node, type, record_factory); + } else if (elem.IsInt()) { + const mgp::Node node = graph.GetNodeById(mgp::Id::FromInt(elem.ValueInt())); + Collapse(graph, node, type, record_factory); + } else { + record_factory.SetErrorMessage("Elements in the list can only be Node or ID"); + return; + } + } + } + + + + } catch (const std::exception &e) { + record_factory.SetErrorMessage(e.what()); + return; + } +} diff --git a/cpp/refactor_module/algorithm/refactor.hpp b/cpp/refactor_module/algorithm/refactor.hpp index 2b4e59f0c..21b4543c2 100644 --- a/cpp/refactor_module/algorithm/refactor.hpp +++ b/cpp/refactor_module/algorithm/refactor.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace Refactor { /* categorize constants */ @@ -62,6 +63,12 @@ constexpr std::string_view kRenameNodePropertyArg2 = "new_property"; constexpr std::string_view kRenameNodePropertyArg3 = "nodes"; constexpr std::string_view kRenameNodePropertyResult = "nodes_changed"; +/*collapse constants*/ +constexpr std::string_view kProcedureCollapseNode = "collapse_node"; +constexpr std::string_view kArgumentNodesCollapseNode = "nodes"; +constexpr std::string_view kArgumentTypeCollapseNode = "type"; +constexpr std::string_view kReturnIdCollapseNode = "id_collapsed"; +constexpr std::string_view kReturnRelationshipCollapseNode = "new_relationship"; /*invert constants*/ constexpr std::string_view kProcedureInvert = "invert"; @@ -93,6 +100,16 @@ void CloneSubgraphFromPaths(mgp_list *args, mgp_graph *memgraph_graph, mgp_resul void CloneSubgraph(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + +void TransferProperties(const mgp::Node &node, mgp::Relationship &rel); + +void Collapse(mgp::Graph &graph, const mgp::Node &node, const std::string &type, + const mgp::RecordFactory &record_factory); + +void CollapseNode(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + void Invert(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); + void InvertRel(mgp::Graph &graph, mgp::Relationship &rel); + } // namespace Refactor diff --git a/cpp/refactor_module/refactor_module.cpp b/cpp/refactor_module/refactor_module.cpp index 8a6a2f079..04381671a 100644 --- a/cpp/refactor_module/refactor_module.cpp +++ b/cpp/refactor_module/refactor_module.cpp @@ -69,7 +69,13 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem {mgp::Return(std::string(Refactor::kReturnIdInvert).c_str(), mgp::Type::Int), mgp::Return(std::string(Refactor::kReturnRelationshipInvert).c_str(), mgp::Type::Relationship)}, module, memory); - + AddProcedure(Refactor::CollapseNode, std::string(Refactor::kProcedureCollapseNode).c_str(), + mgp::ProcedureType::Write, + {mgp::Parameter(std::string(Refactor::kArgumentNodesCollapseNode).c_str(), mgp::Type::Any), + mgp::Parameter(std::string(Refactor::kArgumentTypeCollapseNode).c_str(), mgp::Type::String)}, + {mgp::Return(std::string(Refactor::kReturnIdCollapseNode).c_str(), mgp::Type::Int), + mgp::Return(std::string(Refactor::kReturnRelationshipCollapseNode).c_str(), mgp::Type::Relationship)}, + module, memory); } catch (const std::exception &e) { return 1; } diff --git a/e2e_correctness/refactor_test/test_collapse_node1/input.cyp b/e2e_correctness/refactor_test/test_collapse_node1/input.cyp new file mode 100644 index 000000000..72412dc6c --- /dev/null +++ b/e2e_correctness/refactor_test/test_collapse_node1/input.cyp @@ -0,0 +1,4 @@ +CREATE (r:RonnieColeman {id: 1})-[w:WALKS]->(g: Gym {id: 2, property: "Yeaaah Buddyyyy!!"}); +MATCH (g: Gym) CREATE (g)-[h:HAS]->(w: Weight {id: 3, prop : "SOLID 800 POUNDS"}); +CREATE (k: KyriakosGrizzly {id: 4})-[w:WALKS]->(g2: Gym_in_Greece{id: 5, property: "AAAAAAA!!!"}); +MATCH (g2:Gym_in_Greece) CREATE (g2)-[h:HAS]->(u:UnconventionalExercise {id: 6}); diff --git a/e2e_correctness/refactor_test/test_collapse_node1/test.yml b/e2e_correctness/refactor_test/test_collapse_node1/test.yml new file mode 100644 index 000000000..d04b85fcf --- /dev/null +++ b/e2e_correctness/refactor_test/test_collapse_node1/test.yml @@ -0,0 +1,6 @@ +memgraph_query: > + MATCH (g:Gym),(g2:Gym_in_Greece) + CALL refactor.collapse_node([id(g2), g], "LIFT_AND_NAKE_NOISE") YIELD id_collapsed, new_relationship RETURN id_collapsed, new_relationship; +neo4j_query: > + MATCH (g:Gym),(g2:Gym_in_Greece) + CALL apoc.refactor.collapseNode([g, g2], "LIFT_AND_NAKE_NOISE") YIELD input, output, error RETURN input, output, error; diff --git a/e2e_correctness/refactor_test/test_collapse_node2/input.cyp b/e2e_correctness/refactor_test/test_collapse_node2/input.cyp new file mode 100644 index 000000000..72412dc6c --- /dev/null +++ b/e2e_correctness/refactor_test/test_collapse_node2/input.cyp @@ -0,0 +1,4 @@ +CREATE (r:RonnieColeman {id: 1})-[w:WALKS]->(g: Gym {id: 2, property: "Yeaaah Buddyyyy!!"}); +MATCH (g: Gym) CREATE (g)-[h:HAS]->(w: Weight {id: 3, prop : "SOLID 800 POUNDS"}); +CREATE (k: KyriakosGrizzly {id: 4})-[w:WALKS]->(g2: Gym_in_Greece{id: 5, property: "AAAAAAA!!!"}); +MATCH (g2:Gym_in_Greece) CREATE (g2)-[h:HAS]->(u:UnconventionalExercise {id: 6}); diff --git a/e2e_correctness/refactor_test/test_collapse_node2/test.yml b/e2e_correctness/refactor_test/test_collapse_node2/test.yml new file mode 100644 index 000000000..3f47d049c --- /dev/null +++ b/e2e_correctness/refactor_test/test_collapse_node2/test.yml @@ -0,0 +1,6 @@ +memgraph_query: > + MATCH (g:Gym),(g2:Gym_in_Greece) + CALL refactor.collapse_node(id(g2), "LIFT_AND_NAKE_NOISE") YIELD id_collapsed, new_relationship RETURN id_collapsed, new_relationship; +neo4j_query: > + MATCH (g:Gym),(g2:Gym_in_Greece) + CALL apoc.refactor.collapseNode([g2], "LIFT_AND_NAKE_NOISE") YIELD input, output, error RETURN input, output, error; diff --git a/e2e_correctness/refactor_test/test_collapse_node3/input.cyp b/e2e_correctness/refactor_test/test_collapse_node3/input.cyp new file mode 100644 index 000000000..5af30f080 --- /dev/null +++ b/e2e_correctness/refactor_test/test_collapse_node3/input.cyp @@ -0,0 +1,4 @@ +CREATE (d:Dog {id: 1})-[w:WALKS]->(p:path {id: 2, prop: 5}); +MATCH (p:path) CREATE (p)-[r:Runs]->(h:Home {id: 3}); +CREATE (d:Dog2 {id: 4})-[w:WALKS2]->(p:path {id: 5, prop: 6}); +MATCH (p:path {prop: 6}) CREATE (p)-[r:Runs2]->(h:Home2 {id: 6}); diff --git a/e2e_correctness/refactor_test/test_collapse_node3/test.yml b/e2e_correctness/refactor_test/test_collapse_node3/test.yml new file mode 100644 index 000000000..679b71670 --- /dev/null +++ b/e2e_correctness/refactor_test/test_collapse_node3/test.yml @@ -0,0 +1,6 @@ +memgraph_query: > + MATCH (p:path) + CALL refactor.collapse_node([p],"yeah") YIELD id_collapsed, new_relationship RETURN id_collapsed, new_relationship; +neo4j_query: > + MATCH (p:path) + CALL apoc.refactor.collapseNode([p],"yeah") YIELD input, output, error RETURN input, output, error;