Skip to content

Commit

Permalink
Implement merge node (#391)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpintaric55334 authored Oct 25, 2023
1 parent 4f090c8 commit 0272fb8
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 1 deletion.
94 changes: 93 additions & 1 deletion cpp/merge_module/algorithm/merge.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,103 @@
#include "merge.hpp"

#include <algorithm>
#include <optional>
#include <string_view>
#include <unordered_map>
#include "mgp.hpp"

bool Merge::LabelsContained(const std::unordered_set<std::string_view> &labels, const mgp::Node &node) {
bool contained = false;
auto size = labels.size(); // this works if labels are unique, which they are
size_t counter = 0;

for (const auto label : node.Labels()) {
if (labels.find(label) != labels.end()) {
counter++;
}
}

if (counter == size) {
contained = true;
}

return contained;
}

bool Merge::IdentProp(const mgp::Map &ident_prop, const mgp::Node &node) {
bool match = true;
for (const auto &[key, value] : ident_prop) {
if (value != node.GetProperty(std::string(key))) {
match = false;
}
}
return match;
}

void Merge::Node(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
mgp::MemoryDispatcherGuard guard{memory};
const auto arguments = mgp::List(args);
const auto record_factory = mgp::RecordFactory(result);
try {
auto graph = mgp::Graph(memgraph_graph);
auto labels = arguments[0].ValueList();
auto ident_prop = arguments[1].ValueMap();
auto create_prop = arguments[2].ValueMap();
auto match_prop = arguments[3].ValueMap();
std::unordered_set<std::string_view> label_set;
std::unordered_map<std::string_view, mgp::Value> ident_prop_map;
std::unordered_map<std::string_view, mgp::Value> create_prop_map;
std::unordered_map<std::string_view, mgp::Value> match_prop_map;
/*conversion of mgp::Maps to unordered_map for easier use of SetProperties(it expects an unordered map as
* argument)*/
auto convert_to_map = [](const auto& source, auto& destination) {
for (const auto& [key, value] : source) {
destination.emplace(key, value);
}
};
convert_to_map(ident_prop, ident_prop_map);

convert_to_map(create_prop, create_prop_map);

convert_to_map(match_prop, match_prop_map);


/*creating a set of labels for O(1) check of labels*/
for (const auto elem : labels) {
const auto label = elem.ValueString();
if (label.empty()) {
throw mgp::ValueException("List of labels cannot contain empty string!");
}
label_set.insert(elem.ValueString());
}

bool matched = false;
for (auto node : graph.Nodes()) {
//check if node already exists, if true, merge, if not, create
if (LabelsContained(label_set, node) && IdentProp(ident_prop, node)) {
matched = true;
node.SetProperties(match_prop_map);
auto record = record_factory.NewRecord();
record.Insert(std::string(kNodeRes).c_str(), node);
}
}
if (!matched) {
auto node = graph.CreateNode();
for (const auto label : label_set) {
node.AddLabel(label);
}
// when merge creates it creates identyfing props also
node.SetProperties(ident_prop_map);
node.SetProperties(create_prop_map);
auto record = record_factory.NewRecord();
record.Insert(std::string(kNodeRes).c_str(), node);
}

} catch (const std::exception &e) {
record_factory.SetErrorMessage(e.what());
return;
}
}

namespace {

template <typename T>
Expand Down
17 changes: 17 additions & 0 deletions cpp/merge_module/algorithm/merge.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include <mgp.hpp>

#include <unordered_map>
#include <unordered_set>

namespace Merge {

/* relationship constants */
Expand All @@ -14,6 +17,20 @@ constexpr const std::string_view kRelationshipArg5 = "endNode";
constexpr const std::string_view kRelationshipArg6 = "onMatchProps";
constexpr const std::string_view kRelationshipResult = "rel";

/* node constants */
constexpr std::string_view kProcedureNode = "node";
constexpr std::string_view kNodeArg1 = "labels";
constexpr std::string_view kNodeArg2 = "identProps";
constexpr std::string_view kNodeArg3 = "createProps";
constexpr std::string_view kNodeArg4 = "matchProps";
constexpr std::string_view kNodeRes = "node";

void Relationship(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);



void Node(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
bool IdentProp(const mgp::Map &identProp, const mgp::Node &node);
bool LabelsContained(const std::unordered_set<std::string_view> &labels, const mgp::Node &node);

} // namespace Merge
6 changes: 6 additions & 0 deletions cpp/merge_module/merge_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
try {
mgp::MemoryDispatcherGuard guard{memory};
AddProcedure(Merge::Node, std::string(Merge::kProcedureNode).c_str(), mgp::ProcedureType::Write,
{mgp::Parameter(std::string(Merge::kNodeArg1).c_str(), {mgp::Type::List, mgp::Type::String}),
mgp::Parameter(std::string(Merge::kNodeArg2).c_str(), {mgp::Type::Map, mgp::Type::Any}),
mgp::Parameter(std::string(Merge::kNodeArg3).c_str(), {mgp::Type::Map, mgp::Type::Any}),
mgp::Parameter(std::string(Merge::kNodeArg4).c_str(), {mgp::Type::Map, mgp::Type::Any})},
{mgp::Return(std::string(Merge::kNodeRes).c_str(), mgp::Type::Node)}, module, memory);

AddProcedure(Merge::Relationship, Merge::kProcedureRelationship, mgp::ProcedureType::Write,
{mgp::Parameter(Merge::kRelationshipArg1, mgp::Type::Node),
Expand Down
1 change: 1 addition & 0 deletions e2e_correctness/merge_test/test_node1/input.cyp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE (:Dog:Dog {name: "Rex", id:0}), (:Human:Employee {name: "Junior", id:1}),(:Human:Boss {name: "Senior", id:2})
5 changes: 5 additions & 0 deletions e2e_correctness/merge_test/test_node1/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
memgraph_query: >
CALL merge.node(["Human"],{name: "Senior"},{},{name:"Boss"}) YIELD node RETURN node;
neo4j_query: >
CALL apoc.merge.node(["Human"],{name: "Senior"},{},{name: "Boss"}) YIELD node;
1 change: 1 addition & 0 deletions e2e_correctness/merge_test/test_node2/input.cyp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE (:Dog:Dog {name: "Rex", id:0}), (:Human:Employee {name: "Rex", id:1}),(:Human:Boss {name: "Senior", id:2})
5 changes: 5 additions & 0 deletions e2e_correctness/merge_test/test_node2/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
memgraph_query: >
CALL merge.node([],{name: "Rex"},{},{name:"Carlito"}) YIELD node RETURN node;
neo4j_query: >
MERGE (n {name: "Rex" }) ON CREATE SET n += {} ON MATCH SET n += {name: "Carlito"} RETURN n;
1 change: 1 addition & 0 deletions e2e_correctness/merge_test/test_node3/input.cyp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE (:Dog:Dog {name: "Rex", id:0}), (:Human:Employee {name: "Junior", id:1}),(:Human:Boss {name: "Senior", id:2})
5 changes: 5 additions & 0 deletions e2e_correctness/merge_test/test_node3/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
memgraph_query: >
CALL merge.node([],{},{},{graph:"this_one"}) YIELD node RETURN node;
neo4j_query: >
MERGE (n) ON CREATE SET n += {} ON MATCH SET n += {graph:"this_one"} RETURN n;
1 change: 1 addition & 0 deletions e2e_correctness/merge_test/test_node4/input.cyp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE (:Dog:Dog {name: "Rex", id:0}), (:Human:Employee {name: "Junior", id:1}),(:Human:Boss {name: "Senior", id:2})
5 changes: 5 additions & 0 deletions e2e_correctness/merge_test/test_node4/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
memgraph_query: >
CALL merge.node(["Dog"],{name: "Rico"},{height:199, id:9},{}) YIELD node RETURN node;
neo4j_query: >
CALL apoc.merge.node(["Dog"],{name: "Rico"},{height:199, id:9},{}) YIELD node;
1 change: 1 addition & 0 deletions e2e_correctness/merge_test/test_node5/input.cyp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE (:Dog:Dog {name: "Rex", id:0}), (:Human:Employee {name: "Junior", id:1}),(:Human:Boss {name: "Senior", id:2})
5 changes: 5 additions & 0 deletions e2e_correctness/merge_test/test_node5/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
memgraph_query: >
CALL merge.node(["Dog"],{name: "Rex"},{height:199},{graph:"this_one"}) YIELD node RETURN node;
neo4j_query: >
CALL apoc.merge.node(["Dog"],{name: "Rex"},{height:124727},{graph:"this_one"}) YIELD node;
1 change: 1 addition & 0 deletions e2e_correctness/merge_test/test_node6/input.cyp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE (:Dog:Dog {name: "Rex", id:0}), (:Human:Boss {name: "", id:1}),(:Human:Boss {name: "", id:2})
5 changes: 5 additions & 0 deletions e2e_correctness/merge_test/test_node6/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
memgraph_query: >
CALL merge.node(["Human","Boss"],{name: ""},{height:199},{name:"Le Boss"}) YIELD node RETURN node;
neo4j_query: >
CALL apoc.merge.node(["Human","Boss"],{name: ""},{height:124727},{name:"Le Boss"}) YIELD node;

0 comments on commit 0272fb8

Please sign in to comment.