From 9797e0f2cdda95459fe244f2498542a75ac7afde Mon Sep 17 00:00:00 2001 From: Thomas Tran Date: Tue, 27 May 2025 17:06:51 +0200 Subject: [PATCH 1/2] Ignore negative values for maxHops --- cpp/path_module/algorithm/path.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cpp/path_module/algorithm/path.cpp b/cpp/path_module/algorithm/path.cpp index a1af9e87d..babf7aa10 100644 --- a/cpp/path_module/algorithm/path.cpp +++ b/cpp/path_module/algorithm/path.cpp @@ -29,11 +29,17 @@ Path::PathHelper::PathHelper(const mgp::Map &config) { auto value = config.At("maxHops"); if (!value.IsNull()) { - config_.max_hops = value.ValueInt(); + int64_t max_hops = value.ValueInt(); + if (max_hops >= 0) { + config_.max_hops = max_hops; + } } value = config.At("minHops"); if (!value.IsNull()) { - config_.min_hops = value.ValueInt(); + int64_t min_hops = value.ValueInt(); + if (min_hops >= 0) { + config_.min_hops = min_hops; + } } value = config.At("relationshipFilter"); From ea1758d01d3ede9264aa67ecd295e46c53ffaab1 Mon Sep 17 00:00:00 2001 From: Thomas Tran Date: Tue, 27 May 2025 17:08:12 +0200 Subject: [PATCH 2/2] Always expand graph beyond start node if filterStartNode == false --- cpp/path_module/algorithm/path.cpp | 25 ++++++++++++--------- cpp/path_module/algorithm/path.hpp | 8 +++---- e2e/path_test/test_subgraph_all_4/input.cyp | 9 ++++++++ e2e/path_test/test_subgraph_all_4/test.yml | 17 ++++++++++++++ e2e/path_test/test_subgraph_all_5/input.cyp | 9 ++++++++ e2e/path_test/test_subgraph_all_5/test.yml | 12 ++++++++++ e2e/path_test/test_subgraph_all_6/input.cyp | 9 ++++++++ e2e/path_test/test_subgraph_all_6/test.yml | 17 ++++++++++++++ e2e/path_test/test_subgraph_all_7/input.cyp | 9 ++++++++ e2e/path_test/test_subgraph_all_7/test.yml | 9 ++++++++ e2e/path_test/test_subgraph_all_8/input.cyp | 9 ++++++++ e2e/path_test/test_subgraph_all_8/test.yml | 15 +++++++++++++ e2e/path_test/test_subgraph_all_9/input.cyp | 9 ++++++++ e2e/path_test/test_subgraph_all_9/test.yml | 22 ++++++++++++++++++ 14 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 e2e/path_test/test_subgraph_all_4/input.cyp create mode 100644 e2e/path_test/test_subgraph_all_4/test.yml create mode 100644 e2e/path_test/test_subgraph_all_5/input.cyp create mode 100644 e2e/path_test/test_subgraph_all_5/test.yml create mode 100644 e2e/path_test/test_subgraph_all_6/input.cyp create mode 100644 e2e/path_test/test_subgraph_all_6/test.yml create mode 100644 e2e/path_test/test_subgraph_all_7/input.cyp create mode 100644 e2e/path_test/test_subgraph_all_7/test.yml create mode 100644 e2e/path_test/test_subgraph_all_8/input.cyp create mode 100644 e2e/path_test/test_subgraph_all_8/test.yml create mode 100644 e2e/path_test/test_subgraph_all_9/input.cyp create mode 100644 e2e/path_test/test_subgraph_all_9/test.yml diff --git a/cpp/path_module/algorithm/path.cpp b/cpp/path_module/algorithm/path.cpp index babf7aa10..7edcefaed 100644 --- a/cpp/path_module/algorithm/path.cpp +++ b/cpp/path_module/algorithm/path.cpp @@ -91,8 +91,10 @@ bool Path::PathHelper::AreLabelsValid(const LabelBools &label_bools) const { } bool Path::PathHelper::ContinueExpanding(const LabelBools &label_bools, size_t path_size) const { - return (static_cast(path_size) <= config_.max_hops && !label_bools.blacklisted && !label_bools.terminated && - (label_bools.end_node || Whitelisted(label_bools.whitelisted))); + return (static_cast(path_size) <= config_.max_hops && + ((!label_bools.blacklisted && !label_bools.terminated && + (label_bools.end_node || Whitelisted(label_bools.whitelisted))) || + (path_size == 1 && !config_.filter_start_node))); } bool Path::PathHelper::PathSizeOk(const int64_t path_size) const { @@ -440,12 +442,16 @@ void Path::PathSubgraph::ExpandFromRelationships(const std::pair> &seen) { for (const auto relationship : relationships) { auto next_node = outgoing ? relationship.To() : relationship.From(); + + if (path_data_.visited_.contains(next_node.Id().AsInt())) { + continue; + } + auto type = std::string(relationship.Type()); auto wanted_direction = path_data_.helper_.GetDirection(type); - if (path_data_.helper_.IsNotStartOrSupportsStartRel(pair.second == 0)) { - if ((wanted_direction == RelDirection::kNone && !path_data_.helper_.AnyDirected(outgoing)) || - path_data_.visited_.contains(next_node.Id().AsInt())) { + if (path_data_.helper_.IsNotStartOrFilterStartRel(pair.second == 0)) { + if (wanted_direction == RelDirection::kNone && !path_data_.helper_.AnyDirected(outgoing)) { continue; } } @@ -469,25 +475,22 @@ void Path::PathSubgraph::ExpandFromRelationships(const std::pair> queue; - std::unordered_set visited; for (const auto &node : path_data_.start_nodes_) { queue.push({node, 0}); - visited.insert(node.Id().AsInt()); + path_data_.visited_.insert(node.Id().AsInt()); } while (!queue.empty()) { diff --git a/cpp/path_module/algorithm/path.hpp b/cpp/path_module/algorithm/path.hpp index 33879e67c..30b223661 100644 --- a/cpp/path_module/algorithm/path.hpp +++ b/cpp/path_module/algorithm/path.hpp @@ -2,11 +2,11 @@ #include +#include #include +#include #include #include -#include -#include namespace Path { @@ -108,8 +108,8 @@ class PathHelper { LabelBools GetLabelBools(const mgp::Node &node) const; bool AnyDirected(bool outgoing) const { return outgoing ? config_.any_outgoing : config_.any_incoming; } - bool IsNotStartOrSupportsStartNode(bool is_start) const { return (config_.filter_start_node || !is_start); } - bool IsNotStartOrSupportsStartRel(bool is_start) const { return (config_.begin_sequence_at_start || !is_start); } + bool IsNotStartOrFiltersStartNode(bool is_start) const { return (config_.filter_start_node || !is_start); } + bool IsNotStartOrFilterStartRel(bool is_start) const { return (config_.begin_sequence_at_start || !is_start); } bool AreLabelsValid(const LabelBools &label_bools) const; bool ContinueExpanding(const LabelBools &label_bools, size_t path_size) const; diff --git a/e2e/path_test/test_subgraph_all_4/input.cyp b/e2e/path_test/test_subgraph_all_4/input.cyp new file mode 100644 index 000000000..94bb0067d --- /dev/null +++ b/e2e/path_test/test_subgraph_all_4/input.cyp @@ -0,0 +1,9 @@ +CREATE (w:Wolf)-[ca:CATCHES]->(d:Dog), (c:Cat), (m:Mouse), (h:Human); +MATCH (w:Wolf), (d:Dog), (c:Cat), (m:Mouse), (h:Human) +WITH w, d, c, m, h +CREATE (d)-[:CATCHES]->(c) +CREATE (c)-[:CATCHES]->(m) +CREATE (d)-[:FRIENDS_WITH]->(m) +CREATE (h)-[:OWNS]->(d) +CREATE (h)-[:HUNTS]->(w) +CREATE (h)-[:HATES]->(m); diff --git a/e2e/path_test/test_subgraph_all_4/test.yml b/e2e/path_test/test_subgraph_all_4/test.yml new file mode 100644 index 000000000..cde64bb08 --- /dev/null +++ b/e2e/path_test/test_subgraph_all_4/test.yml @@ -0,0 +1,17 @@ +query: > + MATCH (dog:Dog) + CALL path.subgraph_all(dog, {labelFilter: ["/Mouse"], filterStartNode: false}) + YIELD nodes, rels + RETURN nodes, rels + +output: + - nodes: + - labels: + - Mouse + properties: {} + - labels: + - Dog + properties: {} + rels: + - label: FRIENDS_WITH + properties: {} diff --git a/e2e/path_test/test_subgraph_all_5/input.cyp b/e2e/path_test/test_subgraph_all_5/input.cyp new file mode 100644 index 000000000..94bb0067d --- /dev/null +++ b/e2e/path_test/test_subgraph_all_5/input.cyp @@ -0,0 +1,9 @@ +CREATE (w:Wolf)-[ca:CATCHES]->(d:Dog), (c:Cat), (m:Mouse), (h:Human); +MATCH (w:Wolf), (d:Dog), (c:Cat), (m:Mouse), (h:Human) +WITH w, d, c, m, h +CREATE (d)-[:CATCHES]->(c) +CREATE (c)-[:CATCHES]->(m) +CREATE (d)-[:FRIENDS_WITH]->(m) +CREATE (h)-[:OWNS]->(d) +CREATE (h)-[:HUNTS]->(w) +CREATE (h)-[:HATES]->(m); diff --git a/e2e/path_test/test_subgraph_all_5/test.yml b/e2e/path_test/test_subgraph_all_5/test.yml new file mode 100644 index 000000000..562dcf65c --- /dev/null +++ b/e2e/path_test/test_subgraph_all_5/test.yml @@ -0,0 +1,12 @@ +query: > + MATCH (dog:Dog) + CALL path.subgraph_all(dog, {labelFilter: ["/Mouse"]}) + YIELD nodes, rels + RETURN nodes, rels + +output: + - nodes: + - labels: + - Mouse + properties: {} + rels: [] diff --git a/e2e/path_test/test_subgraph_all_6/input.cyp b/e2e/path_test/test_subgraph_all_6/input.cyp new file mode 100644 index 000000000..94bb0067d --- /dev/null +++ b/e2e/path_test/test_subgraph_all_6/input.cyp @@ -0,0 +1,9 @@ +CREATE (w:Wolf)-[ca:CATCHES]->(d:Dog), (c:Cat), (m:Mouse), (h:Human); +MATCH (w:Wolf), (d:Dog), (c:Cat), (m:Mouse), (h:Human) +WITH w, d, c, m, h +CREATE (d)-[:CATCHES]->(c) +CREATE (c)-[:CATCHES]->(m) +CREATE (d)-[:FRIENDS_WITH]->(m) +CREATE (h)-[:OWNS]->(d) +CREATE (h)-[:HUNTS]->(w) +CREATE (h)-[:HATES]->(m); diff --git a/e2e/path_test/test_subgraph_all_6/test.yml b/e2e/path_test/test_subgraph_all_6/test.yml new file mode 100644 index 000000000..e248da6ad --- /dev/null +++ b/e2e/path_test/test_subgraph_all_6/test.yml @@ -0,0 +1,17 @@ +query: > + MATCH (dog:Dog) + CALL path.subgraph_all(dog, {labelFilter: ["Cat", "-Dog"], filterStartNode: false}) + YIELD nodes, rels + RETURN nodes, rels + +output: + - nodes: + - labels: + - Dog + properties: {} + - labels: + - Cat + properties: {} + rels: + - label: "CATCHES" + properties: {} diff --git a/e2e/path_test/test_subgraph_all_7/input.cyp b/e2e/path_test/test_subgraph_all_7/input.cyp new file mode 100644 index 000000000..94bb0067d --- /dev/null +++ b/e2e/path_test/test_subgraph_all_7/input.cyp @@ -0,0 +1,9 @@ +CREATE (w:Wolf)-[ca:CATCHES]->(d:Dog), (c:Cat), (m:Mouse), (h:Human); +MATCH (w:Wolf), (d:Dog), (c:Cat), (m:Mouse), (h:Human) +WITH w, d, c, m, h +CREATE (d)-[:CATCHES]->(c) +CREATE (c)-[:CATCHES]->(m) +CREATE (d)-[:FRIENDS_WITH]->(m) +CREATE (h)-[:OWNS]->(d) +CREATE (h)-[:HUNTS]->(w) +CREATE (h)-[:HATES]->(m); diff --git a/e2e/path_test/test_subgraph_all_7/test.yml b/e2e/path_test/test_subgraph_all_7/test.yml new file mode 100644 index 000000000..3c56c0f11 --- /dev/null +++ b/e2e/path_test/test_subgraph_all_7/test.yml @@ -0,0 +1,9 @@ +query: > + MATCH (cat:Cat) + CALL path.subgraph_all(cat, {labelFilter: ["/Human", "Dog"]}) + YIELD nodes, rels + RETURN nodes, rels + +output: + - nodes: [] + rels: [] diff --git a/e2e/path_test/test_subgraph_all_8/input.cyp b/e2e/path_test/test_subgraph_all_8/input.cyp new file mode 100644 index 000000000..94bb0067d --- /dev/null +++ b/e2e/path_test/test_subgraph_all_8/input.cyp @@ -0,0 +1,9 @@ +CREATE (w:Wolf)-[ca:CATCHES]->(d:Dog), (c:Cat), (m:Mouse), (h:Human); +MATCH (w:Wolf), (d:Dog), (c:Cat), (m:Mouse), (h:Human) +WITH w, d, c, m, h +CREATE (d)-[:CATCHES]->(c) +CREATE (c)-[:CATCHES]->(m) +CREATE (d)-[:FRIENDS_WITH]->(m) +CREATE (h)-[:OWNS]->(d) +CREATE (h)-[:HUNTS]->(w) +CREATE (h)-[:HATES]->(m); diff --git a/e2e/path_test/test_subgraph_all_8/test.yml b/e2e/path_test/test_subgraph_all_8/test.yml new file mode 100644 index 000000000..8a2628d73 --- /dev/null +++ b/e2e/path_test/test_subgraph_all_8/test.yml @@ -0,0 +1,15 @@ +query: > + MATCH (cat:Cat) + CALL path.subgraph_all(cat, {labelFilter: ["/Human", "Dog"], filterStartNode: false}) + YIELD nodes, rels + RETURN nodes, rels + +output: + - nodes: + - labels: + - Cat + properties: {} + - labels: + - Human + properties: {} + rels: [] diff --git a/e2e/path_test/test_subgraph_all_9/input.cyp b/e2e/path_test/test_subgraph_all_9/input.cyp new file mode 100644 index 000000000..94bb0067d --- /dev/null +++ b/e2e/path_test/test_subgraph_all_9/input.cyp @@ -0,0 +1,9 @@ +CREATE (w:Wolf)-[ca:CATCHES]->(d:Dog), (c:Cat), (m:Mouse), (h:Human); +MATCH (w:Wolf), (d:Dog), (c:Cat), (m:Mouse), (h:Human) +WITH w, d, c, m, h +CREATE (d)-[:CATCHES]->(c) +CREATE (c)-[:CATCHES]->(m) +CREATE (d)-[:FRIENDS_WITH]->(m) +CREATE (h)-[:OWNS]->(d) +CREATE (h)-[:HUNTS]->(w) +CREATE (h)-[:HATES]->(m); diff --git a/e2e/path_test/test_subgraph_all_9/test.yml b/e2e/path_test/test_subgraph_all_9/test.yml new file mode 100644 index 000000000..c559fe1c1 --- /dev/null +++ b/e2e/path_test/test_subgraph_all_9/test.yml @@ -0,0 +1,22 @@ +query: > + MATCH (cat:Cat) + CALL path.subgraph_all(cat, {labelFilter: ["/Human", ">Dog"], filterStartNode: false}) + YIELD nodes, rels + RETURN nodes, rels + +output: + - nodes: + - labels: + - Cat + properties: {} + - labels: + - Dog + properties: {} + - labels: + - Human + properties: {} + rels: + - label: "CATCHES" + properties: {} + - label: "OWNS" + properties: {}