Merge branch 'master' into Implement-constant-time-label-and-edge-type-retrieval
This commit is contained in:
@@ -16,11 +16,11 @@
|
||||
#include <list>
|
||||
#include <thread>
|
||||
|
||||
constexpr char *kProcedureHackerNews = "hacker_news";
|
||||
constexpr char *kArgumentHackerNewsVotes = "votes";
|
||||
constexpr char *kArgumentHackerNewsItemHourAge = "item_hour_age";
|
||||
constexpr char *kArgumentHackerNewsGravity = "gravity";
|
||||
constexpr char *kReturnHackerNewsScore = "score";
|
||||
constexpr char const *kProcedureHackerNews = "hacker_news";
|
||||
constexpr char const *kArgumentHackerNewsVotes = "votes";
|
||||
constexpr char const *kArgumentHackerNewsItemHourAge = "item_hour_age";
|
||||
constexpr char const *kArgumentHackerNewsGravity = "gravity";
|
||||
constexpr char const *kReturnHackerNewsScore = "score";
|
||||
|
||||
void HackerNews(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
||||
mgp::MemoryDispatcherGuard guard(memory);
|
||||
|
||||
@@ -13,6 +13,7 @@ copy_e2e_python_files(replication_show common.py)
|
||||
copy_e2e_python_files(replication_show conftest.py)
|
||||
copy_e2e_python_files(replication_show show.py)
|
||||
copy_e2e_python_files(replication_show show_while_creating_invalid_state.py)
|
||||
copy_e2e_python_files(replication_show edge_delete.py)
|
||||
copy_e2e_python_files_from_parent_folder(replication_show ".." memgraph.py)
|
||||
copy_e2e_python_files_from_parent_folder(replication_show ".." interactive_mg_runner.py)
|
||||
copy_e2e_python_files_from_parent_folder(replication_show ".." mg_utils.py)
|
||||
|
||||
56
tests/e2e/replication/edge_delete.py
Executable file
56
tests/e2e/replication/edge_delete.py
Executable file
@@ -0,0 +1,56 @@
|
||||
# Copyright 2022 Memgraph Ltd.
|
||||
#
|
||||
# Use of this software is governed by the Business Source License
|
||||
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
# License, and you may not use this file except in compliance with the Business Source License.
|
||||
#
|
||||
# As of the Change Date specified in that file, in accordance with
|
||||
# the Business Source License, use of this software will be governed
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from common import execute_and_fetch_all
|
||||
from mg_utils import mg_sleep_and_assert
|
||||
|
||||
|
||||
# BUGFIX: for issue https://github.com/memgraph/memgraph/issues/1515
|
||||
def test_replication_handles_delete_when_multiple_edges_of_same_type(connection):
|
||||
# Goal is to check the timestamp are correctly computed from the information we get from replicas.
|
||||
# 0/ Check original state of replicas.
|
||||
# 1/ Add nodes and edges to MAIN, then delete the edges.
|
||||
# 2/ Check state of replicas.
|
||||
|
||||
# 0/
|
||||
conn = connection(7687, "main")
|
||||
conn.autocommit = True
|
||||
cursor = conn.cursor()
|
||||
actual_data = set(execute_and_fetch_all(cursor, "SHOW REPLICAS;"))
|
||||
|
||||
expected_data = {
|
||||
("replica_1", "127.0.0.1:10001", "sync", 0, 0, "ready"),
|
||||
("replica_2", "127.0.0.1:10002", "async", 0, 0, "ready"),
|
||||
}
|
||||
assert actual_data == expected_data
|
||||
|
||||
# 1/
|
||||
execute_and_fetch_all(cursor, "CREATE (a)-[r:X]->(b) CREATE (a)-[:X]->(b) DELETE r;")
|
||||
|
||||
# 2/
|
||||
expected_data = {
|
||||
("replica_1", "127.0.0.1:10001", "sync", 2, 0, "ready"),
|
||||
("replica_2", "127.0.0.1:10002", "async", 2, 0, "ready"),
|
||||
}
|
||||
|
||||
def retrieve_data():
|
||||
return set(execute_and_fetch_all(cursor, "SHOW REPLICAS;"))
|
||||
|
||||
actual_data = mg_sleep_and_assert(expected_data, retrieve_data)
|
||||
assert actual_data == expected_data
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
@@ -8,6 +8,23 @@ template_validation_queries: &template_validation_queries
|
||||
validation_queries:
|
||||
- <<: *template_test_nodes_query
|
||||
- <<: *template_test_edges_query
|
||||
template_simple_cluster: &template_simple_cluster
|
||||
cluster:
|
||||
replica_1:
|
||||
args: [ "--bolt-port", "7688", "--log-level=TRACE" ]
|
||||
log_file: "replication-e2e-replica1.log"
|
||||
setup_queries: [ "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;" ]
|
||||
replica_2:
|
||||
args: ["--bolt-port", "7689", "--log-level=TRACE"]
|
||||
log_file: "replication-e2e-replica2.log"
|
||||
setup_queries: ["SET REPLICATION ROLE TO REPLICA WITH PORT 10002;"]
|
||||
main:
|
||||
args: ["--bolt-port", "7687", "--log-level=TRACE"]
|
||||
log_file: "replication-e2e-main.log"
|
||||
setup_queries: [
|
||||
"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:10001'",
|
||||
"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:10002'",
|
||||
]
|
||||
template_cluster: &template_cluster
|
||||
cluster:
|
||||
replica_1:
|
||||
@@ -83,3 +100,8 @@ workloads:
|
||||
- name: "Show while creating invalid state"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
args: ["replication/show_while_creating_invalid_state.py"]
|
||||
|
||||
- name: "Delete edge replication"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
args: ["replication/edge_delete.py"]
|
||||
<<: *template_simple_cluster
|
||||
|
||||
@@ -699,3 +699,75 @@ Feature: Match
|
||||
Then the result should be
|
||||
| date(n.time) |
|
||||
| 2021-10-05 |
|
||||
|
||||
Scenario: Variable expand with filter by size of accumulated path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {id: 1})-[:KNOWS]->(:Person {id: 2})-[:KNOWS]->(:Person {id: 3})-[:KNOWS]->(:Person {id: 4});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path = (:Person {id: 1})-[* (e, n, p | size(p) < 4)]->(:Person {id: 4}) RETURN path
|
||||
"""
|
||||
Then the result should be
|
||||
| path |
|
||||
| <(:Person{id:1})-[:KNOWS]->(:Person{id:2})-[:KNOWS]->(:Person{id:3})-[:KNOWS]->(:Person{id:4})> |
|
||||
|
||||
Scenario: Variable expand with filter by last edge type of accumulated path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {id: 1})-[:KNOWS]->(:Person {id: 2})-[:KNOWS]->(:Person {id: 3})-[:KNOWS]->(:Person {id: 4});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path = (:Person {id: 1})-[* (e, n, p | type(relationships(p)[-1]) = 'KNOWS')]->(:Person {id: 4}) RETURN path
|
||||
"""
|
||||
Then the result should be
|
||||
| path |
|
||||
| <(:Person{id:1})-[:KNOWS]->(:Person{id:2})-[:KNOWS]->(:Person{id:3})-[:KNOWS]->(:Person{id:4})> |
|
||||
|
||||
Scenario: Variable expand with too restricted filter by size of accumulated path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {id: 1})-[:KNOWS]->(:Person {id: 2})-[:KNOWS]->(:Person {id: 3})-[:KNOWS]->(:Person {id: 4});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path = (:Person {id: 1})-[* (e, n, p | size(p) < 3)]->(:Person {id: 4}) RETURN path
|
||||
"""
|
||||
Then the result should be empty
|
||||
|
||||
Scenario: Variable expand with too restricted filter by last edge type of accumulated path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {id: 1})-[:KNOWS]->(:Person {id: 2})-[:KNOWS]->(:Person {id: 3})-[:KNOWS]->(:Person {id: 4});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path = (:Person {id: 1})-[* (e, n, p | type(relationships(p)[-1]) = 'Invalid')]->(:Person {id: 4}) RETURN path
|
||||
"""
|
||||
Then the result should be empty
|
||||
|
||||
Scenario: Test DFS variable expand with filter by edge type1
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[* (e, n, p | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1'))]->(:label3) RETURN path;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> |
|
||||
|
||||
Scenario: Test DFS variable expand with filter by edge type2
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[* (e, n, p | NOT(type(e)='type2' AND type(last(relationships(p))) = 'type2'))]->(:label3) RETURN path;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path |
|
||||
| <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> |
|
||||
|
||||
@@ -203,3 +203,103 @@ Feature: All Shortest Path
|
||||
Then the result should be:
|
||||
| total_cost |
|
||||
| 20.3 |
|
||||
|
||||
Scenario: Test match AllShortest with accumulated path filtered by order of ids
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:label1 {id: 1})-[:type1 {id:1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})-[:type1 {id: 3}]->(:label4 {id: 4});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH pth=(:label1)-[*ALLSHORTEST (r, n | r.id) total_weight (e,n,p | e.id > 0 and (nodes(p)[-1]).id > (nodes(p)[-2]).id)]->(:label4) RETURN pth, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| pth | total_weight |
|
||||
| <(:label1{id:1})-[:type1{id:1}]->(:label2{id:2})-[:type1{id:2}]->(:label3{id:3})-[:type1{id:3}]->(:label4{id:4})> | 6 |
|
||||
|
||||
Scenario: Test match AllShortest with accumulated path filtered by edge type1
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*ALLSHORTEST (r, n | r.id) total_weight (e, n, p | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1'))]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> | 10 |
|
||||
|
||||
Scenario: Test match AllShortest with accumulated path filtered by edge type2
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*ALLSHORTEST (r, n | r.id) total_weight (e, n, p | NOT(type(e)='type2' AND type(last(relationships(p))) = 'type2'))]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> | 3 |
|
||||
|
||||
Scenario: Test match AllShortest with accumulated path filtered by edge type1 and accumulated weight based on edge
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*ALLSHORTEST (r, n | r.id) total_weight (e, n, p, w | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1') AND w > 0)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> | 10 |
|
||||
|
||||
Scenario: Test match AllShortest with accumulated path filtered by edge type1 and accumulated weight based on edge too restricted
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*ALLSHORTEST (r, n | r.id) total_weight (e, n, p, w | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1') AND w < 10)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be empty
|
||||
|
||||
Scenario: Test match AllShortest with accumulated path filtered by edge type1 and accumulated weight based on vertex is int
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*ALLSHORTEST (r, n | n.id) total_weight (e, n, p, w | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1') AND w > 0)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> | 4 |
|
||||
|
||||
Scenario: Test match allShortest with accumulated path filtered by edge type1 and accumulated weight based on vertex and edge are ints
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*ALLSHORTEST (r, n | n.id + coalesce(r.id, 0)) total_weight (e, n, p, w | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1') AND w > 0)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> | 14 |
|
||||
|
||||
Scenario: Test match AllShortest with accumulated path filtered by edge type1 and accumulated weight based on vertex and edge are doubles
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:label1 {id: 1})-[:type1 {id:1.5}]->(:label2 {id: 2})-[:type1 {id: 2.1}]->(:label3 {id: 3})-[:type1 {id: 3.4}]->(:label4 {id: 4});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*ALLSHORTEST (r, n | n.id + coalesce(r.id, 0)) total_weight (e, n, p, w | w > 0)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type1 {id: 1.5}]->(:label2 {id: 2})-[:type1 {id: 2.1}]->(:label3 {id: 3})> | 9.6 |
|
||||
|
||||
Scenario: Test match AllShortest with accumulated path filtered by order of ids and accumulated weight based on both vertex and edge is duration
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:station {name: "A", arrival: localTime("08:00"), departure: localTime("08:15")})-[:ride {id: 1, duration: duration("PT1H5M")}]->(:station {name: "B", arrival: localtime("09:20"), departure: localTime("09:30")})-[:ride {id: 2, duration: duration("PT30M")}]->(:station {name: "C", arrival: localTime("10:00"), departure: localTime("10:20")});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:station {name:"A"})-[*ALLSHORTEST (r, v | v.departure - v.arrival + coalesce(r.duration, duration("PT0M"))) total_weight (r,n,p,w | (nodes(p)[-1]).name > (nodes(p)[-2]).name AND not(w is null))]->(:station {name:"C"}) RETURN path, total_weight;
|
||||
"""
|
||||
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 |
|
||||
|
||||
@@ -121,3 +121,95 @@ Feature: Bfs
|
||||
Then the result should be:
|
||||
| p |
|
||||
| <(:Node {id: 2})-[:LINK {date: '2023-03'}]->(:Node {id: 3})> |
|
||||
|
||||
Scenario: Test BFS variable expand with filter by last edge type of accumulated path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:label1 {id: 1})-[:type1 {id:1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH pth=(:label1)-[*BFS (e,n,p | type(relationships(p)[-1]) = 'type1')]->(:label3) return pth;
|
||||
"""
|
||||
Then the result should be:
|
||||
| pth |
|
||||
| <(:label1{id:1})-[:type1{id:1}]->(:label2{id:2})-[:type1{id:2}]->(:label3{id:3})> |
|
||||
|
||||
Scenario: Test BFS variable expand with restict filter by last edge type of accumulated path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:label1 {id: 1})-[:type1 {id:1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH pth=(:label1)-[*BFS (e,n,p | type(relationships(p)[-1]) = 'type2')]->(:label2) return pth;
|
||||
"""
|
||||
Then the result should be empty
|
||||
|
||||
Scenario: Test BFS variable expand with filter by size of accumulated path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:label1 {id: 1})-[:type1 {id:1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH pth=(:label1)-[*BFS (e,n,p | size(p) < 3)]->(:label3) return pth;
|
||||
"""
|
||||
Then the result should be:
|
||||
| pth |
|
||||
| <(:label1{id:1})-[:type1{id:1}]->(:label2{id:2})-[:type1{id:2}]->(:label3{id:3})> |
|
||||
|
||||
Scenario: Test BFS variable expand with restict filter by size of accumulated path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:label1 {id: 1})-[:type1 {id:1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH pth=(:label1)-[*BFS (e,n,p | size(p) < 2)]->(:label3) return pth;
|
||||
"""
|
||||
Then the result should be empty
|
||||
|
||||
Scenario: Test BFS variable expand with filter by order of ids in accumulated path when target vertex is indexed
|
||||
Given graph "graph_index"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH pth=(:label1)-[*BFS (e,n,p | (nodes(p)[-1]).id > (nodes(p)[-2]).id)]->(:label4) return pth;
|
||||
"""
|
||||
Then the result should be:
|
||||
| pth |
|
||||
| <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})-[:type1 {id: 3}]->(:label4 {id: 4})> |
|
||||
|
||||
Scenario: Test BFS variable expand with filter by order of ids in accumulated path when target vertex is NOT indexed
|
||||
Given graph "graph_index"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH pth=(:label1)-[*BFS (e,n,p | (nodes(p)[-1]).id > (nodes(p)[-2]).id)]->(:label3) return pth;
|
||||
"""
|
||||
Then the result should be:
|
||||
| pth |
|
||||
| <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> |
|
||||
|
||||
Scenario: Test BFS variable expand with filter by edge type1
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*BFS (e, n, p | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1'))]->(:label3) RETURN path;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> |
|
||||
|
||||
Scenario: Test BFS variable expand with filter by edge type2
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*BFS (e, n, p | NOT(type(e)='type2' AND type(last(relationships(p))) = 'type2'))]->(:label3) RETURN path;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path |
|
||||
| <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> |
|
||||
|
||||
@@ -155,3 +155,103 @@ Feature: Weighted Shortest Path
|
||||
MATCH (n {a:'0'})-[le *wShortest 10 (e, n | e.w ) w]->(m) RETURN m.a, size(le) as s, w
|
||||
"""
|
||||
Then an error should be raised
|
||||
|
||||
Scenario: Test match wShortest with accumulated path filtered by order of ids
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:label1 {id: 1})-[:type1 {id:1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})-[:type1 {id: 3}]->(:label4 {id: 4});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH pth=(:label1)-[*WSHORTEST (r, n | r.id) total_weight (e,n,p | e.id > 0 and (nodes(p)[-1]).id > (nodes(p)[-2]).id)]->(:label4) RETURN pth, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| pth | total_weight |
|
||||
| <(:label1{id:1})-[:type1{id:1}]->(:label2{id:2})-[:type1{id:2}]->(:label3{id:3})-[:type1{id:3}]->(:label4{id:4})> | 6 |
|
||||
|
||||
Scenario: Test match wShortest with accumulated path filtered by edge type1
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*WSHORTEST (r, n | r.id) total_weight (e, n, p | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1'))]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> | 10 |
|
||||
|
||||
Scenario: Test match wShortest with accumulated path filtered by edge type2
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*WSHORTEST (r, n | r.id) total_weight (e, n, p | NOT(type(e)='type2' AND type(last(relationships(p))) = 'type2'))]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> | 3 |
|
||||
|
||||
Scenario: Test match wShortest with accumulated path filtered by edge type1 and accumulated weight based on edge
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*WSHORTEST (r, n | r.id) total_weight (e, n, p, w | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1') AND w > 0)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> | 10 |
|
||||
|
||||
Scenario: Test match wShortest with accumulated path filtered by edge type1 and accumulated weight based on edge too restricted
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*WSHORTEST (r, n | r.id) total_weight (e, n, p, w | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1') AND w < 10)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be empty
|
||||
|
||||
Scenario: Test match wShortest with accumulated path filtered by edge type1 and accumulated weight based on vertex is int
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*WSHORTEST (r, n | n.id) total_weight (e, n, p, w | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1') AND w > 0)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> | 4 |
|
||||
|
||||
Scenario: Test match wShortest with accumulated path filtered by edge type1 and accumulated weight based on vertex and edge are ints
|
||||
Given graph "graph_edges"
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*WSHORTEST (r, n | n.id + coalesce(r.id, 0)) total_weight (e, n, p, w | NOT(type(e)='type1' AND type(last(relationships(p))) = 'type1') AND w > 0)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type2 {id: 10}]->(:label3 {id: 3})> | 14 |
|
||||
|
||||
Scenario: Test match wShortest with accumulated path filtered by edge type1 and accumulated weight based on vertex and edge are doubles
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:label1 {id: 1})-[:type1 {id:1.5}]->(:label2 {id: 2})-[:type1 {id: 2.1}]->(:label3 {id: 3})-[:type1 {id: 3.4}]->(:label4 {id: 4});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:label1)-[*WSHORTEST (r, n | n.id + coalesce(r.id, 0)) total_weight (e, n, p, w | w > 0)]->(:label3) RETURN path, total_weight;
|
||||
"""
|
||||
Then the result should be:
|
||||
| path | total_weight |
|
||||
| <(:label1 {id: 1})-[:type1 {id: 1.5}]->(:label2 {id: 2})-[:type1 {id: 2.1}]->(:label3 {id: 3})> | 9.6 |
|
||||
|
||||
Scenario: Test match wShortest with accumulated path filtered by order of ids and accumulated weight based on both vertex and edge is duration
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:station {name: "A", arrival: localTime("08:00"), departure: localTime("08:15")})-[:ride {id: 1, duration: duration("PT1H5M")}]->(:station {name: "B", arrival: localtime("09:20"), departure: localTime("09:30")})-[:ride {id: 2, duration: duration("PT30M")}]->(:station {name: "C", arrival: localTime("10:00"), departure: localTime("10:20")});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(:station {name:"A"})-[*WSHORTEST (r, v | v.departure - v.arrival + coalesce(r.duration, duration("PT0M"))) total_weight (r,n,p,w | (nodes(p)[-1]).name > (nodes(p)[-2]).name AND not(w is null))]->(:station {name:"C"}) RETURN path, total_weight;
|
||||
"""
|
||||
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 |
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
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);
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX ON :label4;
|
||||
CREATE (:label1 {id: 1})-[:type1 {id:1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})-[:type1 {id:3}]->(:label4 {id: 4});
|
||||
@@ -1925,6 +1925,41 @@ TEST_P(CypherMainVisitorTest, MatchBfsReturn) {
|
||||
ASSERT_TRUE(eq);
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, MatchBfsFilterByPathReturn) {
|
||||
auto &ast_generator = *GetParam();
|
||||
{
|
||||
const auto *query = dynamic_cast<CypherQuery *>(
|
||||
ast_generator.ParseQuery("MATCH pth=(r:type1 {id: 1})<-[*BFS ..10 (e, n, p | startNode(relationships(e)[-1]) = "
|
||||
"c:type2)]->(:type3 {id: 3}) RETURN pth;"));
|
||||
ASSERT_TRUE(query);
|
||||
ASSERT_TRUE(query->single_query_);
|
||||
const auto *match = dynamic_cast<Match *>(query->single_query_->clauses_[0]);
|
||||
ASSERT_TRUE(match);
|
||||
ASSERT_EQ(match->patterns_.size(), 1U);
|
||||
ASSERT_EQ(match->patterns_[0]->atoms_.size(), 3U);
|
||||
auto *bfs = dynamic_cast<EdgeAtom *>(match->patterns_[0]->atoms_[1]);
|
||||
ASSERT_TRUE(bfs);
|
||||
EXPECT_TRUE(bfs->IsVariable());
|
||||
EXPECT_EQ(bfs->filter_lambda_.inner_edge->name_, "e");
|
||||
EXPECT_TRUE(bfs->filter_lambda_.inner_edge->user_declared_);
|
||||
EXPECT_EQ(bfs->filter_lambda_.inner_node->name_, "n");
|
||||
EXPECT_TRUE(bfs->filter_lambda_.inner_node->user_declared_);
|
||||
EXPECT_EQ(bfs->filter_lambda_.accumulated_path->name_, "p");
|
||||
EXPECT_TRUE(bfs->filter_lambda_.accumulated_path->user_declared_);
|
||||
EXPECT_EQ(bfs->filter_lambda_.accumulated_weight, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, SemanticExceptionOnBfsFilterByWeight) {
|
||||
auto &ast_generator = *GetParam();
|
||||
{
|
||||
ASSERT_THROW(ast_generator.ParseQuery(
|
||||
"MATCH pth=(:type1 {id: 1})<-[*BFS ..10 (e, n, p, w | startNode(relationships(e)[-1] AND w > 0) = "
|
||||
"c:type2)]->(:type3 {id: 3}) RETURN pth;"),
|
||||
SemanticException);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, MatchVariableLambdaSymbols) {
|
||||
auto &ast_generator = *GetParam();
|
||||
auto *query = dynamic_cast<CypherQuery *>(ast_generator.ParseQuery("MATCH () -[*]- () RETURN *"));
|
||||
@@ -1981,6 +2016,57 @@ TEST_P(CypherMainVisitorTest, MatchWShortestReturn) {
|
||||
EXPECT_TRUE(shortest->total_weight_->user_declared_);
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, MatchWShortestFilterByPathReturn) {
|
||||
auto &ast_generator = *GetParam();
|
||||
{
|
||||
const auto *query = dynamic_cast<CypherQuery *>(
|
||||
ast_generator.ParseQuery("MATCH pth=()-[r:type1 *wShortest 10 (we, wn | 42) total_weight "
|
||||
"(e, n, p | startNode(relationships(e)[-1]) = c:type3)]->(:type2) RETURN pth"));
|
||||
ASSERT_TRUE(query);
|
||||
ASSERT_TRUE(query->single_query_);
|
||||
const auto *match = dynamic_cast<Match *>(query->single_query_->clauses_[0]);
|
||||
ASSERT_TRUE(match);
|
||||
ASSERT_EQ(match->patterns_.size(), 1U);
|
||||
ASSERT_EQ(match->patterns_[0]->atoms_.size(), 3U);
|
||||
auto *shortestPath = dynamic_cast<EdgeAtom *>(match->patterns_[0]->atoms_[1]);
|
||||
ASSERT_TRUE(shortestPath);
|
||||
EXPECT_TRUE(shortestPath->IsVariable());
|
||||
EXPECT_EQ(shortestPath->filter_lambda_.inner_edge->name_, "e");
|
||||
EXPECT_TRUE(shortestPath->filter_lambda_.inner_edge->user_declared_);
|
||||
EXPECT_EQ(shortestPath->filter_lambda_.inner_node->name_, "n");
|
||||
EXPECT_TRUE(shortestPath->filter_lambda_.inner_node->user_declared_);
|
||||
EXPECT_EQ(shortestPath->filter_lambda_.accumulated_path->name_, "p");
|
||||
EXPECT_TRUE(shortestPath->filter_lambda_.accumulated_path->user_declared_);
|
||||
EXPECT_EQ(shortestPath->filter_lambda_.accumulated_weight, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, MatchWShortestFilterByPathWeightReturn) {
|
||||
auto &ast_generator = *GetParam();
|
||||
{
|
||||
const auto *query = dynamic_cast<CypherQuery *>(ast_generator.ParseQuery(
|
||||
"MATCH pth=()-[r:type1 *wShortest 10 (we, wn | 42) total_weight "
|
||||
"(e, n, p, w | startNode(relationships(e)[-1]) = c:type3 AND w < 50)]->(:type2) RETURN pth"));
|
||||
ASSERT_TRUE(query);
|
||||
ASSERT_TRUE(query->single_query_);
|
||||
const auto *match = dynamic_cast<Match *>(query->single_query_->clauses_[0]);
|
||||
ASSERT_TRUE(match);
|
||||
ASSERT_EQ(match->patterns_.size(), 1U);
|
||||
ASSERT_EQ(match->patterns_[0]->atoms_.size(), 3U);
|
||||
auto *shortestPath = dynamic_cast<EdgeAtom *>(match->patterns_[0]->atoms_[1]);
|
||||
ASSERT_TRUE(shortestPath);
|
||||
EXPECT_TRUE(shortestPath->IsVariable());
|
||||
EXPECT_EQ(shortestPath->filter_lambda_.inner_edge->name_, "e");
|
||||
EXPECT_TRUE(shortestPath->filter_lambda_.inner_edge->user_declared_);
|
||||
EXPECT_EQ(shortestPath->filter_lambda_.inner_node->name_, "n");
|
||||
EXPECT_TRUE(shortestPath->filter_lambda_.inner_node->user_declared_);
|
||||
EXPECT_EQ(shortestPath->filter_lambda_.accumulated_path->name_, "p");
|
||||
EXPECT_TRUE(shortestPath->filter_lambda_.accumulated_path->user_declared_);
|
||||
EXPECT_EQ(shortestPath->filter_lambda_.accumulated_weight->name_, "w");
|
||||
EXPECT_TRUE(shortestPath->filter_lambda_.accumulated_weight->user_declared_);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, MatchWShortestNoFilterReturn) {
|
||||
auto &ast_generator = *GetParam();
|
||||
auto *query =
|
||||
|
||||
Reference in New Issue
Block a user