Fix bug on AllShortest with multiple edges between nodes (#832)
This commit is contained in:
parent
029be10f1d
commit
0819b40202
@ -1834,7 +1834,7 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
: self_(self),
|
||||
input_cursor_(self_.input_->MakeCursor(mem)),
|
||||
visited_cost_(mem),
|
||||
expanded_(mem),
|
||||
total_cost_(mem),
|
||||
next_edges_(mem),
|
||||
traversal_stack_(mem),
|
||||
pq_(mem) {}
|
||||
@ -1844,6 +1844,9 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
|
||||
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
|
||||
storage::View::OLD);
|
||||
auto create_state = [this](const VertexAccessor &vertex, int64_t depth) {
|
||||
return std::make_pair(vertex, upper_bound_set_ ? depth : 0);
|
||||
};
|
||||
|
||||
// For the given (edge, direction, weight, depth) tuple checks if they
|
||||
// satisfy the "where" condition. if so, places them in the priority
|
||||
@ -1935,73 +1938,118 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
}
|
||||
};
|
||||
|
||||
// Check if upper bound exists
|
||||
upper_bound_ = self_.upper_bound_
|
||||
? EvaluateInt(&evaluator, self_.upper_bound_, "Max depth in all shortest paths expansion")
|
||||
: std::numeric_limits<int64_t>::max();
|
||||
std::optional<VertexAccessor> start_vertex;
|
||||
auto *memory = context.evaluation_context.memory;
|
||||
|
||||
auto create_path = [this, &frame, &memory]() {
|
||||
auto ¤t_level = traversal_stack_.back();
|
||||
auto &edges_on_frame = frame[self_.common_.edge_symbol].ValueList();
|
||||
|
||||
// Clean out the current stack
|
||||
if (current_level.empty()) {
|
||||
if (!edges_on_frame.empty()) {
|
||||
if (!self_.is_reverse_)
|
||||
edges_on_frame.erase(edges_on_frame.end());
|
||||
else
|
||||
edges_on_frame.erase(edges_on_frame.begin());
|
||||
}
|
||||
traversal_stack_.pop_back();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto [current_edge, current_edge_direction, current_weight] = current_level.back();
|
||||
current_level.pop_back();
|
||||
|
||||
// Edges order depends on direction of expansion
|
||||
if (!self_.is_reverse_)
|
||||
edges_on_frame.emplace_back(current_edge);
|
||||
else
|
||||
edges_on_frame.emplace(edges_on_frame.begin(), current_edge);
|
||||
|
||||
auto next_vertex = current_edge_direction == EdgeAtom::Direction::IN ? current_edge.From() : current_edge.To();
|
||||
frame[self_.total_weight_.value()] = current_weight;
|
||||
|
||||
if (next_edges_.find({next_vertex, traversal_stack_.size()}) != next_edges_.end()) {
|
||||
auto next_vertex_edges = next_edges_[{next_vertex, traversal_stack_.size()}];
|
||||
traversal_stack_.emplace_back(std::move(next_vertex_edges));
|
||||
} else {
|
||||
// Signal the end of iteration
|
||||
utils::pmr::list<DirectedEdge> empty(memory);
|
||||
traversal_stack_.emplace_back(std::move(empty));
|
||||
}
|
||||
|
||||
if ((current_weight > visited_cost_.at(next_vertex)).ValueBool()) return false;
|
||||
|
||||
// Place destination node on the frame, handle existence flag
|
||||
if (self_.common_.existing_node) {
|
||||
const auto &node = frame[self_.common_.node_symbol];
|
||||
ExpectType(self_.common_.node_symbol, node, TypedValue::Type::Vertex);
|
||||
if (node.ValueVertex() != next_vertex) return false;
|
||||
} else {
|
||||
frame[self_.common_.node_symbol] = next_vertex;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto create_DFS_traversal_tree = [this, &context, &memory, &create_state, &expand_from_vertex]() {
|
||||
while (!pq_.empty()) {
|
||||
if (MustAbort(context)) throw HintedAbortError();
|
||||
|
||||
const auto [current_weight, current_depth, current_vertex, directed_edge] = pq_.top();
|
||||
pq_.pop();
|
||||
|
||||
const auto &[current_edge, direction, weight] = directed_edge;
|
||||
auto current_state = create_state(current_vertex, current_depth);
|
||||
|
||||
auto position = total_cost_.find(current_state);
|
||||
if (position != total_cost_.end()) {
|
||||
if ((position->second < current_weight).ValueBool()) continue;
|
||||
} else {
|
||||
total_cost_.emplace(current_state, current_weight);
|
||||
if (current_depth < upper_bound_) {
|
||||
expand_from_vertex(current_vertex, current_weight, current_depth);
|
||||
}
|
||||
}
|
||||
|
||||
// Searching for a previous vertex in the expansion
|
||||
auto prev_vertex = direction == EdgeAtom::Direction::IN ? current_edge.To() : current_edge.From();
|
||||
|
||||
// Update the parent
|
||||
if (next_edges_.find({prev_vertex, current_depth - 1}) == next_edges_.end()) {
|
||||
utils::pmr::list<DirectedEdge> empty(memory);
|
||||
next_edges_[{prev_vertex, current_depth - 1}] = std::move(empty);
|
||||
}
|
||||
next_edges_.at({prev_vertex, current_depth - 1}).emplace_back(directed_edge);
|
||||
}
|
||||
};
|
||||
|
||||
// upper_bound_set is used when storing visited edges, because with an upper bound we also consider suboptimal paths
|
||||
// if they are shorter in depth
|
||||
if (self_.upper_bound_) {
|
||||
upper_bound_ = EvaluateInt(&evaluator, self_.upper_bound_, "Max depth in all shortest path expansion");
|
||||
upper_bound_set_ = true;
|
||||
} else {
|
||||
upper_bound_ = std::numeric_limits<int64_t>::max();
|
||||
upper_bound_set_ = false;
|
||||
}
|
||||
|
||||
// Check if upper bound is valid
|
||||
if (upper_bound_ < 1) {
|
||||
throw QueryRuntimeException("Maximum depth in all shortest paths expansion must be at least 1.");
|
||||
}
|
||||
|
||||
std::optional<VertexAccessor> start_vertex;
|
||||
auto *memory = context.evaluation_context.memory;
|
||||
|
||||
// On first Pull run, traversal stack and priority queue are empty, so we start a pulling stream
|
||||
// and create a DFS traversal tree (main part of algorithm). Then we return the first path
|
||||
// created from the DFS traversal tree (basically a DFS algorithm).
|
||||
// On each subsequent Pull run, paths are created from the traversal stack and returned.
|
||||
while (true) {
|
||||
// Check if there is an external error.
|
||||
if (MustAbort(context)) throw HintedAbortError();
|
||||
|
||||
// If traversal stack if filled, the DFS traversal tree is created. Traverse the tree iteratively by
|
||||
// preserving the traversal state on stack.
|
||||
// The algorithm is run all at once by create_DFS_traversal_tree, after which we
|
||||
// traverse the tree iteratively by preserving the traversal state on stack.
|
||||
while (!traversal_stack_.empty()) {
|
||||
auto ¤t_level = traversal_stack_.back();
|
||||
auto &edges_on_frame = frame[self_.common_.edge_symbol].ValueList();
|
||||
|
||||
// Clean out the current stack
|
||||
if (current_level.empty()) {
|
||||
if (!edges_on_frame.empty()) {
|
||||
if (!self_.is_reverse_)
|
||||
edges_on_frame.erase(edges_on_frame.end());
|
||||
else
|
||||
edges_on_frame.erase(edges_on_frame.begin());
|
||||
}
|
||||
traversal_stack_.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto [current_edge, current_edge_direction, current_weight] = current_level.back();
|
||||
current_level.pop_back();
|
||||
|
||||
// Edges order depends on direction of expansion
|
||||
if (!self_.is_reverse_)
|
||||
edges_on_frame.emplace_back(current_edge);
|
||||
else
|
||||
edges_on_frame.emplace(edges_on_frame.begin(), current_edge);
|
||||
|
||||
auto next_vertex = current_edge_direction == EdgeAtom::Direction::IN ? current_edge.From() : current_edge.To();
|
||||
frame[self_.total_weight_.value()] = current_weight;
|
||||
|
||||
if (next_edges_.find({next_vertex, traversal_stack_.size()}) != next_edges_.end()) {
|
||||
auto next_vertex_edges = next_edges_[{next_vertex, traversal_stack_.size()}];
|
||||
traversal_stack_.emplace_back(std::move(next_vertex_edges));
|
||||
} else {
|
||||
// Signal the end of iteration
|
||||
utils::pmr::list<DirectedEdge> empty(memory);
|
||||
traversal_stack_.emplace_back(std::move(empty));
|
||||
}
|
||||
|
||||
if ((current_weight > visited_cost_.at(next_vertex)).ValueBool()) continue;
|
||||
|
||||
// Place destination node on the frame, handle existence flag
|
||||
if (self_.common_.existing_node) {
|
||||
const auto &node = frame[self_.common_.node_symbol];
|
||||
ExpectType(self_.common_.node_symbol, node, TypedValue::Type::Vertex);
|
||||
if (node.ValueVertex() != next_vertex) continue;
|
||||
} else {
|
||||
frame[self_.common_.node_symbol] = next_vertex;
|
||||
}
|
||||
return true;
|
||||
if (create_path()) return true;
|
||||
}
|
||||
|
||||
// If priority queue is empty start new pulling stream.
|
||||
@ -2022,9 +2070,9 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
|
||||
// Clear existing data structures.
|
||||
visited_cost_.clear();
|
||||
expanded_.clear();
|
||||
next_edges_.clear();
|
||||
traversal_stack_.clear();
|
||||
total_cost_.clear();
|
||||
|
||||
expand_from_vertex(*start_vertex, TypedValue(), 0);
|
||||
visited_cost_.emplace(*start_vertex, 0);
|
||||
@ -2032,33 +2080,9 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
}
|
||||
|
||||
// Create a DFS traversal tree from the start node
|
||||
while (!pq_.empty()) {
|
||||
if (MustAbort(context)) throw HintedAbortError();
|
||||
|
||||
const auto [current_weight, current_depth, current_vertex, directed_edge] = pq_.top();
|
||||
pq_.pop();
|
||||
|
||||
const auto &[current_edge, direction, weight] = directed_edge;
|
||||
if (expanded_.contains(current_edge)) continue;
|
||||
expanded_.emplace(current_edge);
|
||||
|
||||
// Expand only if what we've just expanded is less than max depth.
|
||||
if (current_depth < upper_bound_) {
|
||||
expand_from_vertex(current_vertex, current_weight, current_depth);
|
||||
}
|
||||
|
||||
// Searching for a previous vertex in the expansion
|
||||
auto prev_vertex = direction == EdgeAtom::Direction::IN ? current_edge.To() : current_edge.From();
|
||||
|
||||
// Update the parent
|
||||
if (next_edges_.find({prev_vertex, current_depth - 1}) == next_edges_.end()) {
|
||||
utils::pmr::list<DirectedEdge> empty(memory);
|
||||
next_edges_[{prev_vertex, current_depth - 1}] = std::move(empty);
|
||||
}
|
||||
|
||||
next_edges_.at({prev_vertex, current_depth - 1}).emplace_back(directed_edge);
|
||||
}
|
||||
create_DFS_traversal_tree();
|
||||
|
||||
// DFS traversal tree is create,
|
||||
if (start_vertex && next_edges_.find({*start_vertex, 0}) != next_edges_.end()) {
|
||||
auto start_vertex_edges = next_edges_[{*start_vertex, 0}];
|
||||
traversal_stack_.emplace_back(std::move(start_vertex_edges));
|
||||
@ -2071,9 +2095,9 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
void Reset() override {
|
||||
input_cursor_->Reset();
|
||||
visited_cost_.clear();
|
||||
expanded_.clear();
|
||||
next_edges_.clear();
|
||||
traversal_stack_.clear();
|
||||
total_cost_.clear();
|
||||
ClearQueue();
|
||||
}
|
||||
|
||||
@ -2083,6 +2107,7 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
|
||||
// Upper bound on the path length.
|
||||
int64_t upper_bound_{-1};
|
||||
bool upper_bound_set_{false};
|
||||
|
||||
struct AspStateHash {
|
||||
size_t operator()(const std::pair<VertexAccessor, int64_t> &key) const {
|
||||
@ -2094,8 +2119,8 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
using NextEdgesState = std::pair<VertexAccessor, int64_t>;
|
||||
// Maps vertices to minimum weights they got in expansion.
|
||||
utils::pmr::unordered_map<VertexAccessor, TypedValue> visited_cost_;
|
||||
// Marking the expanded edges to prevent multiple visits.
|
||||
utils::pmr::unordered_set<EdgeAccessor> expanded_;
|
||||
// Maps vertices to weights they got in expansion.
|
||||
utils::pmr::unordered_map<NextEdgesState, TypedValue, AspStateHash> total_cost_;
|
||||
// Maps the vertex with the potential expansion edge.
|
||||
utils::pmr::unordered_map<NextEdgesState, utils::pmr::list<DirectedEdge>, AspStateHash> next_edges_;
|
||||
// Stack indicating the traversal level.
|
||||
|
@ -1,191 +1,205 @@
|
||||
Feature: All Shortest Path
|
||||
|
||||
Scenario: Test match allShortest upper bound
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 1}]->({a:'1'})-[:r {w: 1}]->({a:'2'}), (n)-[:r {w: 1}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 1 (e, n | e.w ) w]->(m) RETURN m.a
|
||||
"""
|
||||
Then the result should be:
|
||||
| m.a |
|
||||
| '1' |
|
||||
| '3' |
|
||||
Scenario: Test match allShortest upper bound
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 1}]->({a:'1'})-[:r {w: 1}]->({a:'2'}), (n)-[:r {w: 1}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 1 (e, n | e.w ) w]->(m) RETURN m.a
|
||||
"""
|
||||
Then the result should be:
|
||||
| m.a |
|
||||
| '1' |
|
||||
| '3' |
|
||||
|
||||
Scenario: Test match allShortest upper bound 2
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a {a:'0'})-[:r {w: 2}]->(b {a:'1'})-[:r {w: 3}]->(c {a:'2'}),
|
||||
(a)-[:re {w: 2}]->(b),
|
||||
(b)-[:re {w:3}]->(c),
|
||||
({a: '4'})<-[:r {w: 1}]-(a),
|
||||
({a: '5'})<-[:r {w: 1}]-(a),
|
||||
(c)-[:r {w: 1}]->({a: '6'}),
|
||||
(c)-[:r {w: 1}]->({a: '7'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(n {a:'0'})-[r *allShortest ..2 (e, n | 1 ) w]->(m {a:'2'}) RETURN COUNT(path) AS c
|
||||
"""
|
||||
Then the result should be:
|
||||
| c |
|
||||
| 4 |
|
||||
Scenario: Test match allShortest upper bound 2
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a {a:'0'})-[:r {w: 2}]->(b {a:'1'})-[:r {w: 3}]->(c {a:'2'}),
|
||||
(a)-[:re {w: 2}]->(b),
|
||||
(b)-[:re {w:3}]->(c),
|
||||
({a: '4'})<-[:r {w: 1}]-(a),
|
||||
({a: '5'})<-[:r {w: 1}]-(a),
|
||||
(c)-[:r {w: 1}]->({a: '6'}),
|
||||
(c)-[:r {w: 1}]->({a: '7'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(n {a:'0'})-[r *allShortest ..2 (e, n | 1 ) w]->(m {a:'2'}) RETURN COUNT(path) AS c
|
||||
"""
|
||||
Then the result should be:
|
||||
| c |
|
||||
| 4 |
|
||||
|
||||
Scenario: Test match allShortest filtered
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 1}]->({a:'1'})-[:r {w: 1}]->({a:'2'}), (n)-[:r {w: 1}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 1 (e, n | e.w ) w (e, n | n.a = '3')]->(m) RETURN m.a
|
||||
"""
|
||||
Then the result should be:
|
||||
| m.a |
|
||||
| '3' |
|
||||
Scenario: Test match allShortest filtered
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 1}]->({a:'1'})-[:r {w: 1}]->({a:'2'}), (n)-[:r {w: 1}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 1 (e, n | e.w ) w (e, n | n.a = '3')]->(m) RETURN m.a
|
||||
"""
|
||||
Then the result should be:
|
||||
| m.a |
|
||||
| '3' |
|
||||
|
||||
Scenario: Test match allShortest resulting edge list
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 1}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 4}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 10 (e, n | e.w ) w]->(m) RETURN m.a, size(le) as s, w
|
||||
"""
|
||||
Then the result should be:
|
||||
| m.a | s | w |
|
||||
| '1' | 1 | 1 |
|
||||
| '2' | 2 | 3 |
|
||||
| '3' | 1 | 4 |
|
||||
Scenario: Test match allShortest resulting edge list
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 1}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 4}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 10 (e, n | e.w ) w]->(m) RETURN m.a, size(le) as s, w
|
||||
"""
|
||||
Then the result should be:
|
||||
| m.a | s | w |
|
||||
| '1' | 1 | 1 |
|
||||
| '2' | 2 | 3 |
|
||||
| '3' | 1 | 4 |
|
||||
|
||||
Scenario: Test match allShortest single edge type filtered
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r0 {w: 1}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le:r0 *allShortest 10 (e, n | e.w) w]->(m)
|
||||
RETURN size(le) AS s, m.a
|
||||
"""
|
||||
Then the result should be:
|
||||
| s | m.a |
|
||||
| 1 | '1' |
|
||||
Scenario: Test match allShortest single edge type filtered
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r0 {w: 1}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le:r0 *allShortest 10 (e, n | e.w) w]->(m)
|
||||
RETURN size(le) AS s, m.a
|
||||
"""
|
||||
Then the result should be:
|
||||
| s | m.a |
|
||||
| 1 | '1' |
|
||||
|
||||
Scenario: Test match allShortest multiple edge types filtered
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r0 {w: 1}]->({a:'1'})-[:r1 {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le :r0|:r1 *allShortest 10 (e, n | e.w) w]->(m) WHERE size(le) > 1
|
||||
RETURN size(le) AS s, (le[0]).w AS r0, (le[1]).w AS r1
|
||||
"""
|
||||
Then the result should be:
|
||||
| s | r0 | r1 |
|
||||
| 2 | 1 | 2 |
|
||||
Scenario: Test match allShortest multiple edge types filtered
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r0 {w: 1}]->({a:'1'})-[:r1 {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le :r0|:r1 *allShortest 10 (e, n | e.w) w]->(m) WHERE size(le) > 1
|
||||
RETURN size(le) AS s, (le[0]).w AS r0, (le[1]).w AS r1
|
||||
"""
|
||||
Then the result should be:
|
||||
| s | r0 | r1 |
|
||||
| 2 | 1 | 2 |
|
||||
|
||||
Scenario: Test match allShortest property filters
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 1}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le *allShortest 10 {w:1} (e, n | e.w ) total_weight]->(m)
|
||||
RETURN size(le) AS s, (le[0]).w AS r0
|
||||
"""
|
||||
Then the result should be:
|
||||
| s | r0 |
|
||||
| 1 | 1 |
|
||||
Scenario: Test match allShortest property filters
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 1}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le *allShortest 10 {w:1} (e, n | e.w ) total_weight]->(m)
|
||||
RETURN size(le) AS s, (le[0]).w AS r0
|
||||
"""
|
||||
Then the result should be:
|
||||
| s | r0 |
|
||||
| 1 | 1 |
|
||||
|
||||
Scenario: Test match allShortest weight not a number
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 'not a number'}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le *allShortest 10 (e, n | e.w ) total_weight]->(m)
|
||||
RETURN le, total_weight
|
||||
"""
|
||||
Then an error should be raised
|
||||
Scenario: Test match allShortest weight not a number
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 'not a number'}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le *allShortest 10 (e, n | e.w ) total_weight]->(m)
|
||||
RETURN le, total_weight
|
||||
"""
|
||||
Then an error should be raised
|
||||
|
||||
Scenario: Test match allShortest negative weight
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: -1}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le *allShortest 10 (e, n | e.w ) total_weight]->(m)
|
||||
RETURN le, total_weight
|
||||
"""
|
||||
Then an error should be raised
|
||||
Scenario: Test match allShortest negative weight
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: -1}]->({a:'1'})-[:r {w: 2}]->({a:'2'}), (n)-[:r {w: 3}]->({a:'4'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[le *allShortest 10 (e, n | e.w ) total_weight]->(m)
|
||||
RETURN le, total_weight
|
||||
"""
|
||||
Then an error should be raised
|
||||
|
||||
Scenario: Test match allShortest weight duration
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: DURATION('PT1S')}]->({a:'1'})-[:r {w: DURATION('PT2S')}]->({a:'2'}), (n)-[:r {w: DURATION('PT4S')}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 10 (e, n | e.w ) w]->(m) RETURN m.a, size(le) as s, w
|
||||
"""
|
||||
Then the result should be:
|
||||
| m.a | s | w |
|
||||
| '1' | 1 | PT1S |
|
||||
| '2' | 2 | PT3S |
|
||||
| '3' | 1 | PT4S |
|
||||
Scenario: Test match allShortest weight duration
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: DURATION('PT1S')}]->({a:'1'})-[:r {w: DURATION('PT2S')}]->({a:'2'}), (n)-[:r {w: DURATION('PT4S')}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 10 (e, n | e.w ) w]->(m) RETURN m.a, size(le) as s, w
|
||||
"""
|
||||
Then the result should be:
|
||||
| m.a | s | w |
|
||||
| '1' | 1 | PT1S |
|
||||
| '2' | 2 | PT3S |
|
||||
| '3' | 1 | PT4S |
|
||||
|
||||
Scenario: Test match allShortest weight negative duration
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: DURATION({seconds: -1})}]->({a:'1'})-[:r {w: DURATION('PT2S')}]->({a:'2'}), (n)-[:r {w: DURATION('PT4S')}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 10 (e, n | e.w ) w]->(m) RETURN m.a, size(le) as s, w
|
||||
"""
|
||||
Then an error should be raised
|
||||
Scenario: Test match allShortest weight negative duration
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: DURATION({seconds: -1})}]->({a:'1'})-[:r {w: DURATION('PT2S')}]->({a:'2'}), (n)-[:r {w: DURATION('PT4S')}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 10 (e, n | e.w ) w]->(m) RETURN m.a, size(le) as s, w
|
||||
"""
|
||||
Then an error should be raised
|
||||
|
||||
Scenario: Test match allShortest weight mixed numeric and duration as weights
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 2}]->({a:'1'})-[:r {w: DURATION('PT2S')}]->({a:'2'}), (n)-[:r {w: DURATION('PT4S')}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 10 (e, n | e.w ) w]->(m) RETURN m.a, size(le) as s, w
|
||||
"""
|
||||
Then an error should be raised
|
||||
Scenario: Test match allShortest weight mixed numeric and duration as weights
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 2}]->({a:'1'})-[:r {w: DURATION('PT2S')}]->({a:'2'}), (n)-[:r {w: DURATION('PT4S')}]->({a:'3'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-[le *allShortest 10 (e, n | e.w ) w]->(m) RETURN m.a, size(le) as s, w
|
||||
"""
|
||||
Then an error should be raised
|
||||
|
||||
Scenario: Test allShortest return both paths of same length
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 2}]->({a:'1'})-[:r {w: 3}]->({a:'2'}), (n)-[:r {w: 5}]->({a:'2'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(n {a:'0'})-[r *allShortest (e, n | e.w ) w]->(m {a:'2'}) RETURN COUNT(path);
|
||||
"""
|
||||
Then the result should be:
|
||||
| COUNT(path) |
|
||||
| 2 |
|
||||
Scenario: Test allShortest return both paths of same length
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n {a:'0'})-[:r {w: 2}]->({a:'1'})-[:r {w: 3}]->({a:'2'}), (n)-[:r {w: 5}]->({a:'2'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(n {a:'0'})-[r *allShortest (e, n | e.w ) w]->(m {a:'2'}) RETURN COUNT(path);
|
||||
"""
|
||||
Then the result should be:
|
||||
| COUNT(path) |
|
||||
| 2 |
|
||||
|
||||
Scenario: Test allShortest on different edge between two nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n:One), (o:Two), (m:Three), (n)-[:TYPE {cost: 0.3}]->(o), (o)-[:TYPE {cost: 40}]->(m), (o)-[:TYPE {cost: 20}]->(m)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p=(h:One)-[r*allshortest ..5 (e, v | e.cost) total_cost]->(k:Three) return total_cost;
|
||||
"""
|
||||
Then the result should be:
|
||||
| total_cost |
|
||||
| 20.3 |
|
||||
|
Loading…
Reference in New Issue
Block a user