In BFS expansion filter by path we should shrink path to restore state prior to expansion only if the path was changed. (#1745)

This commit is contained in:
Aidar Samerkhanov 2024-02-22 09:34:08 +04:00 committed by GitHub
parent e302be98a2
commit 9a20ac494d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 60 additions and 24 deletions

View File

@ -1549,15 +1549,15 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
// for the given (edge, vertex) pair checks if they satisfy the
// "where" condition. if so, places them in the to_visit_ structure.
auto expand_pair = [this, &evaluator, &frame, &context](EdgeAccessor edge, VertexAccessor vertex) {
auto expand_pair = [this, &evaluator, &frame, &context](EdgeAccessor edge, VertexAccessor vertex) -> bool {
// if we already processed the given vertex it doesn't get expanded
if (processed_.find(vertex) != processed_.end()) return;
if (processed_.find(vertex) != processed_.end()) return false;
#ifdef MG_ENTERPRISE
if (license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!(context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
return;
return false;
}
#endif
frame[self_.filter_lambda_.inner_edge_symbol] = edge;
@ -1576,9 +1576,9 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
TypedValue result = self_.filter_lambda_.expression->Accept(evaluator);
switch (result.type()) {
case TypedValue::Type::Null:
return;
return true;
case TypedValue::Type::Bool:
if (!result.ValueBool()) return;
if (!result.ValueBool()) return true;
break;
default:
throw QueryRuntimeException("Expansion condition must evaluate to boolean or null.");
@ -1586,10 +1586,11 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
}
to_visit_next_.emplace_back(edge, vertex, std::move(curr_acc_path));
processed_.emplace(vertex, edge);
return true;
};
auto restore_frame_state_after_expansion = [this, &frame]() {
if (self_.filter_lambda_.accumulated_path_symbol) {
auto restore_frame_state_after_expansion = [this, &frame](bool was_expanded) {
if (was_expanded && self_.filter_lambda_.accumulated_path_symbol) {
frame[self_.filter_lambda_.accumulated_path_symbol.value()].ValuePath().Shrink();
}
};
@ -1601,15 +1602,15 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::IN) {
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types)).edges;
for (const auto &edge : out_edges) {
expand_pair(edge, edge.To());
restore_frame_state_after_expansion();
bool was_expanded = expand_pair(edge, edge.To());
restore_frame_state_after_expansion(was_expanded);
}
}
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types)).edges;
for (const auto &edge : in_edges) {
expand_pair(edge, edge.From());
restore_frame_state_after_expansion();
bool was_expanded = expand_pair(edge, edge.From());
restore_frame_state_after_expansion(was_expanded);
}
}
};
@ -1800,18 +1801,8 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
// For the given (edge, vertex, weight, depth) tuple checks if they
// satisfy the "where" condition. if so, places them in the priority
// queue.
auto expand_pair = [this, &evaluator, &frame, &create_state, &context](
const EdgeAccessor &edge, const VertexAccessor &vertex, const TypedValue &total_weight,
int64_t depth) {
#ifdef MG_ENTERPRISE
if (license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!(context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
return;
}
#endif
auto expand_pair = [this, &evaluator, &frame, &create_state](const EdgeAccessor &edge, const VertexAccessor &vertex,
const TypedValue &total_weight, int64_t depth) {
frame[self_.weight_lambda_->inner_edge_symbol] = edge;
frame[self_.weight_lambda_->inner_node_symbol] = vertex;
TypedValue next_weight = CalculateNextWeight(self_.weight_lambda_, total_weight, evaluator);
@ -1854,11 +1845,19 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
// Populates the priority queue structure with expansions
// from the given vertex. skips expansions that don't satisfy
// the "where" condition.
auto expand_from_vertex = [this, &expand_pair, &restore_frame_state_after_expansion](
auto expand_from_vertex = [this, &context, &expand_pair, &restore_frame_state_after_expansion](
const VertexAccessor &vertex, const TypedValue &weight, int64_t depth) {
if (self_.common_.direction != EdgeAtom::Direction::IN) {
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types)).edges;
for (const auto &edge : out_edges) {
#ifdef MG_ENTERPRISE
if (license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!(context.auth_checker->Has(edge.To(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
expand_pair(edge, edge.To(), weight, depth);
restore_frame_state_after_expansion();
}
@ -1866,6 +1865,14 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types)).edges;
for (const auto &edge : in_edges) {
#ifdef MG_ENTERPRISE
if (license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!(context.auth_checker->Has(edge.From(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
expand_pair(edge, edge.From(), weight, depth);
restore_frame_state_after_expansion();
}

View File

@ -761,6 +761,7 @@ Feature: Match
Then the result should be:
| path |
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> |
| <(:label1 {id: 1})-[:same {id: 30}]->(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> |
Scenario: Test DFS variable expand using IN edges with filter by edge type1
Given graph "graph_edges"
@ -771,6 +772,7 @@ Feature: Match
Then the result should be:
| path |
| <(:label3 {id: 3})<-[:type2 {id: 10}]-(:label1 {id: 1})> |
| <(:label3 {id: 3})<-[:type2 {id: 10}]-(:label1 {id: 1})-[:same {id: 30}]->(:label1 {id: 1})> |
Scenario: Test DFS variable expand with filter by edge type2
Given graph "graph_edges"
@ -781,6 +783,7 @@ Feature: Match
Then the result should be:
| path |
| <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> |
| <(:label1 {id: 1})-[:same {id: 30}]->(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> |
Scenario: Test DFS variable expand using IN edges with filter by edge type2
Given graph "graph_edges"
@ -791,6 +794,7 @@ Feature: Match
Then the result should be:
| path |
| <(:label3 {id: 3})<-[:type1 {id: 2}]-(:label2 {id: 2})<-[:type1 {id: 1}]-(:label1 {id: 1})> |
| <(:label3 {id: 3})<-[:type1 {id: 2}]-(:label2 {id: 2})<-[:type1 {id: 1}]-(:label1 {id: 1})-[:same {id: 30}]->(:label1 {id: 1})> |
Scenario: Using path indentifier from CREATE in MERGE
Given an empty graph

View File

@ -249,3 +249,15 @@ Feature: Bfs
Then the result should be:
| path |
| <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> |
Scenario: Test BFS variable expand with already processed vertex and loop with filter by path
Given graph "graph_edges"
When executing query:
"""
MATCH path=(:label1)-[*BFS 1..1 (e, n, p | True)]-() RETURN path;
"""
Then the result should be:
| path |
| < (:label1 {id: 1})-[:type3 {id: 20}]->(:label5 {id: 5}) > |
| < (:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3}) > |
| < (:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2}) > |

View File

@ -269,3 +269,15 @@ Feature: Weighted Shortest Path
Then the result should be:
| path | total_weight |
| <(:station {arrival: 08:00:00.000000000, departure: 08:15:00.000000000, name: 'A'})-[:ride {duration: PT1H5M, id: 1}]->(:station {arrival: 09:20:00.000000000, departure: 09:30:00.000000000, name: 'B'})-[:ride {duration: PT30M, id: 2}]->(:station {arrival: 10:00:00.000000000, departure: 10:20:00.000000000, name: 'C'})> | PT2H20M |
Scenario: Test wShortest variable expand with already processed vertex and loop with filter by path
Given graph "graph_edges"
When executing query:
"""
MATCH path=(:label1)-[*WSHORTEST ..1 (r, n | r.id) total_weight (e, n, p | True)]-() RETURN path;
"""
Then the result should be:
| path |
| < (:label1 {id: 1})-[:type3 {id: 20}]->(:label5 {id: 5}) > |
| < (:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3}) > |
| < (:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2}) > |

View File

@ -1,3 +1,4 @@
CREATE (:label1 {id: 1})-[:type1 {id:1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})-[:type1 {id: 3}]->(:label4 {id: 4});
MATCH (n :label1), (m :label3) CREATE (n)-[:type2 {id: 10}]->(m);
MATCH (n :label1) CREATE (n)-[:type3 {id: 20}]->(:label5 { id: 5 });
MATCH (n :label1) CREATE (n)-[:same {id: 30}]->(n);