Merge branch 'master' into Implement-constant-time-label-and-edge-type-retrieval

This commit is contained in:
Marko Budiselić
2023-12-02 20:08:14 +01:00
committed by GitHub
40 changed files with 1250 additions and 141 deletions

View File

@@ -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);

View File

@@ -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)

View 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"]))

View File

@@ -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

View File

@@ -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})> |

View File

@@ -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 |

View File

@@ -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})> |

View File

@@ -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 |

View File

@@ -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);

View File

@@ -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});

View File

@@ -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 =