diff --git a/tck/features/clauses/match/Match1.feature b/tck/features/clauses/match/Match1.feature new file mode 100644 index 0000000..fc871e2 --- /dev/null +++ b/tck/features/clauses/match/Match1.feature @@ -0,0 +1,273 @@ +# +# Copyright (c) 2015-2021 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Attribution Notice under the terms of the Apache License 2.0 +# +# This work was created by the collective efforts of the openCypher community. +# Without limiting the terms of Section 6, any Derivative Work that is not +# approved by the public consensus process of the openCypher Implementers Group +# should not be described as “Cypher” (and Cypher® is a registered trademark of +# Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or +# proposals for change that have been documented or implemented should only be +# described as "implementation extensions to Cypher" or as "proposed changes to +# Cypher that are not yet approved by the openCypher community". +# + +#encoding: utf-8 + +Feature: Match1 - Match nodes + + Scenario: [1] Match non-existent nodes returns empty + Given an empty graph + When executing query: + """ + MATCH (n) + RETURN n + """ + Then the result should be, in any order: + | n | + And no side effects + + Scenario: [2] Matching all nodes + Given an empty graph + And having executed: + """ + CREATE (:A), (:B {name: 'b'}), ({name: 'c'}) + """ + When executing query: + """ + MATCH (n) + RETURN n + """ + Then the result should be, in any order: + | n | + | (:A) | + | (:B {name: 'b'}) | + | ({name: 'c'}) | + And no side effects + + Scenario: [3] Matching nodes using multiple labels + Given an empty graph + And having executed: + """ + CREATE (:A:B:C), (:A:B), (:A:C), (:B:C), + (:A), (:B), (:C), + ({name: ':A:B:C'}), ({abc: 'abc'}), () + """ + When executing query: + """ + MATCH (a:A:B) + RETURN a + """ + Then the result should be, in any order: + | a | + | (:A:B) | + | (:A:B:C) | + And no side effects + + Scenario: [4] Simple node inline property predicate + Given an empty graph + And having executed: + """ + CREATE ({name: 'bar'}), ({name: 'monkey'}), ({firstname: 'bar'}) + """ + When executing query: + """ + MATCH (n {name: 'bar'}) + RETURN n + """ + Then the result should be, in any order: + | n | + | ({name: 'bar'}) | + And no side effects + + Scenario: [5] Use multiple MATCH clauses to do a Cartesian product + Given an empty graph + And having executed: + """ + CREATE ({num: 1}), + ({num: 2}), + ({num: 3}) + """ + When executing query: + """ + MATCH (n), (m) + RETURN n.num AS n, m.num AS m + """ + Then the result should be, in any order: + | n | m | + | 1 | 1 | + | 1 | 2 | + | 1 | 3 | + | 2 | 1 | + | 2 | 2 | + | 2 | 3 | + | 3 | 3 | + | 3 | 1 | + | 3 | 2 | + And no side effects + + Scenario: [6] Fail when using parameter as node predicate in MATCH + Given any graph + When executing query: + """ + MATCH (n $param) + RETURN n + """ + Then a SyntaxError should be raised at compile time: InvalidParameterUse + + Scenario Outline: [7] Fail when a relationship has the same variable in a preceding MATCH + Given any graph + When executing query: + """ + MATCH + MATCH (r) + RETURN r + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | pattern | + | ()-[r]-() | + | ()-[r]->() | + | ()<-[r]-() | + | (), ()-[r]-() | + | ()-[r]-(), () | + | ()-[]-(), ()-[r]-() | + | ()-[]-()-[r]-() | + | ()-[]-()-[]-(), ()-[r]-() | + | ()-[]-()-[]-(), ()-[r]-(), () | + | ()-[]-()-[]-(), (), ()-[r]-() | + | (x), (a)-[q]-(b), (s), (s)-[r]->(t)<-[]-(b) | + + Scenario Outline: [8] Fail when a path has the same variable in a preceding MATCH + Given any graph + When executing query: + """ + MATCH + MATCH (r) + RETURN r + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | pattern | + | r = ()-[]-() | + | r = ()-[]->() | + | r = ()<-[]-() | + | r = ()-[*]-() | + | r = ()-[*]->() | + | (), r = ()-[]-() | + | (), r = ()-[]->() | + | (), r = ()<-[]-() | + | (), r = ()-[*]-() | + | (), r = ()-[*]->() | + | ()-[]-(), r = ()-[]-(), () | + | r = ()-[]-(), ()-[]-(), () | + | ()-[]-()<-[]-(), r = ()-[]-() | + | (x), r = (a)-[q]-(b), (s)-[p]-(t)-[]-(b) | + | (x), (a)-[q]-(b), r = (s)-[p]-(t)-[]-(b) | + | (x), (a)-[q]-(b), r = (s)-[p]->(t)<-[]-(b) | + + Scenario Outline: [9] Fail when a relationship has the same variable in the same pattern + Given any graph + When executing query: + """ + MATCH + RETURN r + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | pattern | + | ()-[r]-(r) | + | ()-[r]->(r) | + | ()<-[r]-(r) | + | ()-[r]-()-[]-(r) | + | ()-[r*]-()-[]-(r) | + | ()-[r]-(), (r) | + | ()-[r]->(), (r) | + | ()<-[r]-(), (r) | + | ()-[r]-(), (r)-[]-() | + | ()-[r]-(), ()-[]-(r) | + | (s)-[r]-(t), (r)-[]-(t) | + | (s)-[r]-(t), (s)-[]-(r) | + | (), ()-[r]-(), (r) | + | ()-[r]-(), (), (r) | + | ()-[r]-(), (r), () | + | ()-[]-(), ()-[r]-(), (r) | + | ()-[]-()-[r]-(), ()-[]-(r) | + | ()-[]-()-[]-(), ()-[r]-(), (r) | + | ()-[]-()-[r]-(), (r), ()-[]-() | + | ()-[]-()-[r]-(), (), (r)-[]-() | + | ()-[]-()-[r*]-(), (r), ()-[]-() | + | ()-[*]-()-[r]-(), (), (r)-[]-() | + | ()-[*]-()-[r]-(), (), (r)-[*]-() | + | ()-[*]-()-[r]-(), (), ()-[*]-(r) | + | (x), (a)-[r]-(b), (s), (s)-[]->(r)<-[]-(b) | + + Scenario Outline: [10] Fail when a path has the same variable in the same pattern + Given any graph + When executing query: + """ + MATCH + RETURN r + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | pattern | + | r = ()-[]-(), (r) | + | r = ()-[]->(), (r) | + | r = ()<-[]-(), (r) | + | r = ()-[*]-(), (r) | + | r = ()-[*]->(), (r) | + | (), r = ()-[]-(), (r) | + | (), r = ()-[]->(), (r) | + | (), r = ()<-[]-(), (r) | + | (), r = ()-[*]-(), (r) | + | (), r = ()-[*]->(), (r) | + | ()-[]-(), r = ()-[]-(), (), (r) | + | r = ()-[]-(), ()-[]-(), (), (r) | + | ()-[]-()<-[]-(), r = ()-[]-(), (r) | + | (x), r = (a)-[q]-(b), (s)-[p]-(t)-[]-(b), (r) | + | (x), (a)-[q]-(b), r = (s)-[p]-(t)-[]-(b), (r) | + | (x), (a)-[q]-(b), r = (s)-[p]->(t)<-[]-(b), (r) | + | (x), r = (s)-[p]-(t)-[]-(b), (r), (a)-[q]-(b) | + | (x), r = (s)-[p]->(t)<-[]-(b), (r), (a)-[q]-(b) | + | (x), r = (s)-[p]-(t)-[]-(b), (a)-[q]-(r) | + | (x), r = (s)-[p]->(t)<-[]-(b), (r)-[q]-(b) | + + Scenario Outline: [11] Fail when matching a node variable bound to a value + Given any graph + When executing query: + """ + WITH AS n + MATCH (n) + RETURN n + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | invalid | + | true | + | 123 | + | 123.4 | + | 'foo' | + | [] | + | [10] | + | {x: 1} | + | {x: []} | diff --git a/tck/features/clauses/match/Match2.feature b/tck/features/clauses/match/Match2.feature new file mode 100644 index 0000000..cf3c3fe --- /dev/null +++ b/tck/features/clauses/match/Match2.feature @@ -0,0 +1,300 @@ +# +# Copyright (c) 2015-2021 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Attribution Notice under the terms of the Apache License 2.0 +# +# This work was created by the collective efforts of the openCypher community. +# Without limiting the terms of Section 6, any Derivative Work that is not +# approved by the public consensus process of the openCypher Implementers Group +# should not be described as “Cypher” (and Cypher® is a registered trademark of +# Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or +# proposals for change that have been documented or implemented should only be +# described as "implementation extensions to Cypher" or as "proposed changes to +# Cypher that are not yet approved by the openCypher community". +# + +#encoding: utf-8 + +Feature: Match2 - Match relationships + + Scenario: [1] Match non-existent relationships returns empty + Given an empty graph + When executing query: + """ + MATCH ()-[r]->() + RETURN r + """ + Then the result should be, in any order: + | r | + And no side effects + + Scenario: [2] Matching a relationship pattern using a label predicate on both sides + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T1]->(:B), + (:B)-[:T2]->(:A), + (:B)-[:T3]->(:B), + (:A)-[:T4]->(:A) + """ + When executing query: + """ + MATCH (:A)-[r]->(:B) + RETURN r + """ + Then the result should be, in any order: + | r | + | [:T1] | + And no side effects + + Scenario: [3] Matching a self-loop with an undirected relationship pattern + Given an empty graph + And having executed: + """ + CREATE (a) + CREATE (a)-[:T]->(a) + """ + When executing query: + """ + MATCH ()-[r]-() + RETURN type(r) AS r + """ + Then the result should be, in any order: + | r | + | 'T' | + And no side effects + + Scenario: [4] Matching a self-loop with a directed relationship pattern + Given an empty graph + And having executed: + """ + CREATE (a) + CREATE (a)-[:T]->(a) + """ + When executing query: + """ + MATCH ()-[r]->() + RETURN type(r) AS r + """ + Then the result should be, in any order: + | r | + | 'T' | + And no side effects + + Scenario: [5] Match relationship with inline property value + Given an empty graph + And having executed: + """ + CREATE (:A)<-[:KNOWS {name: 'monkey'}]-()-[:KNOWS {name: 'woot'}]->(:B) + """ + When executing query: + """ + MATCH (node)-[r:KNOWS {name: 'monkey'}]->(a) + RETURN a + """ + Then the result should be, in any order: + | a | + | (:A) | + And no side effects + + Scenario: [6] Match relationships with multiple types + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), + (b {name: 'B'}), + (c {name: 'C'}), + (a)-[:KNOWS]->(b), + (a)-[:HATES]->(c), + (a)-[:WONDERS]->(c) + """ + When executing query: + """ + MATCH (n)-[r:KNOWS|HATES]->(x) + RETURN r + """ + Then the result should be, in any order: + | r | + | [:KNOWS] | + | [:HATES] | + And no side effects + + Scenario: [7] Matching twice with conflicting relationship types on same relationship + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH (a1)-[r:T]->() + WITH r, a1 + MATCH (a1)-[r:Y]->(b2) + RETURN a1, r, b2 + """ + Then the result should be, in any order: + | a1 | r | b2 | + And no side effects + + Scenario: [8] Fail when using parameter as relationship predicate in MATCH + Given any graph + When executing query: + """ + MATCH ()-[r:FOO $param]->() + RETURN r + """ + Then a SyntaxError should be raised at compile time: InvalidParameterUse + + Scenario Outline: [9] Fail when a node has the same variable in a preceding MATCH + Given any graph + When executing query: + """ + MATCH + MATCH ()-[r]-() + RETURN r + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | pattern | + | (r) | + | (r)-[]-() | + | (r)-[]->() | + | (r)<-[]-() | + | (r)-[]-(r) | + | ()-[]->(r) | + | ()<-[]-(r) | + | ()-[]-(r) | + | (r)-[]->(r) | + | (r)<-[]-(r) | + | (r)-[]-()-[]-() | + | ()-[]-(r)-[]-() | + | (r)-[]-()-[*]-() | + | ()-[]-(r)-[*]-() | + | (r), ()-[]-() | + | (r)-[]-(), ()-[]-() | + | ()-[]-(r), ()-[]-() | + | ()-[]-(), (r)-[]-() | + | ()-[]-(), ()-[]-(r) | + | (r)-[]-(t), (s)-[]-(t) | + | (s)-[]-(r), (s)-[]-(t) | + | (s)-[]-(t), (r)-[]-(t) | + | (s)-[]-(t), (s)-[]-(r) | + | (s), (a)-[q]-(b), (r), (s)-[]-(t)-[]-(b) | + | (s), (a)-[q]-(b), (r), (s)-[]->(t)<-[]-(b) | + | (s), (a)-[q]-(b), (t), (s)-[]->(r)<-[]-(b) | + + Scenario Outline: [10] Fail when a path has the same variable in a preceding MATCH + Given any graph + When executing query: + """ + MATCH + MATCH ()-[r]-() + RETURN r + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | pattern | + | r = ()-[]-() | + | r = ()-[]->() | + | r = ()<-[]-() | + | r = ()-[*]-() | + | r = ()-[*]->() | + | r = ()<-[*]-() | + | r = ()-[p*]-() | + | r = ()-[p*]->() | + | r = ()<-[p*]-() | + | (), r = ()-[]-() | + | ()-[]-(), r = ()-[]-() | + | ()-[]->(), r = ()<-[]-() | + | ()<-[]-(), r = ()-[]->() | + | ()-[*]->(), r = ()<-[]-() | + | ()<-[p*]-(), r = ()-[*]->() | + | (x), (a)-[q]-(b), (r), (s)-[]->(t)<-[]-(b) | + | (x), (a)-[q]-(b), r = (s)-[p]->(t)<-[]-(b) | + | (x), (a)-[q*]-(b), r = (s)-[p]->(t)<-[]-(b) | + | (x), (a)-[q]-(b), r = (s)-[p*]->(t)<-[]-(b) | + + Scenario Outline: [11] Fail when a node has the same variable in the same pattern + Given any graph + When executing query: + """ + MATCH + RETURN r + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | pattern | + | (r)-[r]-() | + | (r)-[r]->() | + | (r)<-[r]-() | + | (r)-[r]-(r) | + | (r)-[r]->(r) | + | (r)<-[r]-(r) | + | (r)-[]-()-[r]-() | + | ()-[]-(r)-[r]-() | + | (r)-[]-()-[r*]-() | + | ()-[]-(r)-[r*]-() | + | (r), ()-[r]-() | + | (r)-[]-(), ()-[r]-() | + | ()-[]-(r), ()-[r]-() | + | (r)-[]-(t), (s)-[r]-(t) | + | (s)-[]-(r), (s)-[r]-(t) | + | (r), (a)-[q]-(b), (s), (s)-[r]-(t)-[]-(b) | + | (r), (a)-[q]-(b), (s), (s)-[r]->(t)<-[]-(b) | + + Scenario Outline: [12] Fail when a path has the same variable in the same pattern + Given any graph + When executing query: + """ + MATCH + RETURN r + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | pattern | + | r = ()-[]-(), ()-[r]-() | + | r = ()-[]-(), ()-[r*]-() | + | r = (a)-[p]-(s)-[]-(b), (s)-[]-(t), (t), (t)-[r]-(b) | + | r = (a)-[p]-(s)-[]-(b), (s)-[]-(t), (t), (t)-[r*]-(b) | + | r = (a)-[p]-(s)-[*]-(b), (s)-[]-(t), (t), (t)-[r*]-(b) | + | (a)-[p]-(s)-[]-(b), r = (s)-[]-(t), (t), (t)-[r*]-(b) | + | (a)-[p]-(s)-[]-(b), r = (s)-[*]-(t), (t), (t)-[r]-(b) | + | (a)-[p]-(s)-[]-(b), r = (s)-[*]-(t), (t), (t)-[r*]-(b) | + + Scenario Outline: [13] Fail when matching a relationship variable bound to a value + Given any graph + When executing query: + """ + WITH AS r + MATCH ()-[r]-() + RETURN r + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict + + Examples: + | invalid | + | true | + | 123 | + | 123.4 | + | 'foo' | + | [] | + | [10] | + | {x: 1} | + | {x: []} | diff --git a/tck/features/clauses/match/Match3.feature b/tck/features/clauses/match/Match3.feature new file mode 100644 index 0000000..205e981 --- /dev/null +++ b/tck/features/clauses/match/Match3.feature @@ -0,0 +1,574 @@ +# +# Copyright (c) 2015-2021 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Attribution Notice under the terms of the Apache License 2.0 +# +# This work was created by the collective efforts of the openCypher community. +# Without limiting the terms of Section 6, any Derivative Work that is not +# approved by the public consensus process of the openCypher Implementers Group +# should not be described as “Cypher” (and Cypher® is a registered trademark of +# Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or +# proposals for change that have been documented or implemented should only be +# described as "implementation extensions to Cypher" or as "proposed changes to +# Cypher that are not yet approved by the openCypher community". +# + +#encoding: utf-8 + +Feature: Match3 - Match fixed length patterns + + Scenario: [1] Get neighbours + Given an empty graph + And having executed: + """ + CREATE (a:A {num: 1})-[:KNOWS]->(b:B {num: 2}) + """ + When executing query: + """ + MATCH (n1)-[rel:KNOWS]->(n2) + RETURN n1, n2 + """ + Then the result should be, in any order: + | n1 | n2 | + | (:A {num: 1}) | (:B {num: 2}) | + And no side effects + + Scenario: [2] Directed match of a simple relationship + Given an empty graph + And having executed: + """ + CREATE (:A)-[:LOOP]->(:B) + """ + When executing query: + """ + MATCH (a)-[r]->(b) + RETURN a, r, b + """ + Then the result should be, in any order: + | a | r | b | + | (:A) | [:LOOP] | (:B) | + And no side effects + + Scenario: [3] Undirected match on simple relationship graph + Given an empty graph + And having executed: + """ + CREATE (:A)-[:LOOP]->(:B) + """ + When executing query: + """ + MATCH (a)-[r]-(b) + RETURN a, r, b + """ + Then the result should be, in any order: + | a | r | b | + | (:A) | [:LOOP] | (:B) | + | (:B) | [:LOOP] | (:A) | + And no side effects + + Scenario: [4] Get two related nodes + Given an empty graph + And having executed: + """ + CREATE (a:A {num: 1}), + (a)-[:KNOWS]->(b:B {num: 2}), + (a)-[:KNOWS]->(c:C {num: 3}) + """ + When executing query: + """ + MATCH ()-[rel:KNOWS]->(x) + RETURN x + """ + Then the result should be, in any order: + | x | + | (:B {num: 2}) | + | (:C {num: 3}) | + And no side effects + + Scenario: [5] Return two subgraphs with bound undirected relationship + Given an empty graph + And having executed: + """ + CREATE (a:A {num: 1})-[:REL {name: 'r'}]->(b:B {num: 2}) + """ + When executing query: + """ + MATCH (a)-[r {name: 'r'}]-(b) + RETURN a, b + """ + Then the result should be, in any order: + | a | b | + | (:B {num: 2}) | (:A {num: 1}) | + | (:A {num: 1}) | (:B {num: 2}) | + And no side effects + + Scenario: [6] Matching a relationship pattern using a label predicate + Given an empty graph + And having executed: + """ + CREATE (a), (b1:Foo), (b2) + CREATE (a)-[:T]->(b1), + (a)-[:T]->(b2) + """ + When executing query: + """ + MATCH (a)-->(b:Foo) + RETURN b + """ + Then the result should be, in any order: + | b | + | (:Foo) | + And no side effects + + Scenario: [7] Matching nodes with many labels + Given an empty graph + And having executed: + """ + CREATE (a:A:B:C:D:E:F:G:H:I:J:K:L:M), + (b:U:V:W:X:Y:Z) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n:A:B:C:D:E:F:G:H:I:J:K:L:M)-[:T]->(m:Z:Y:X:W:V:U) + RETURN n, m + """ + Then the result should be, in any order: + | n | m | + | (:A:B:C:D:E:F:G:H:I:J:K:L:M) | (:Z:Y:X:W:V:U) | + And no side effects + + Scenario: [8] Matching using relationship predicate with multiples of the same type + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (a)-[:T|:T]->(b) + RETURN b + """ + Then the result should be, in any order: + | b | + | (:B) | + And no side effects + + Scenario: [9] Get related to related to + Given an empty graph + And having executed: + """ + CREATE (a:A {num: 1})-[:KNOWS]->(b:B {num: 2})-[:FRIEND]->(c:C {num: 3}) + """ + When executing query: + """ + MATCH (n)-->(a)-->(b) + RETURN b + """ + Then the result should be, in any order: + | b | + | (:C {num: 3}) | + And no side effects + + Scenario: [10] Matching using self-referencing pattern returns no result + Given an empty graph + And having executed: + """ + CREATE (a), (b), (c) + CREATE (a)-[:T]->(b), + (b)-[:T]->(c) + """ + When executing query: + """ + MATCH (a)-->(b), (b)-->(b) + RETURN b + """ + Then the result should be, in any order: + | b | + And no side effects + + Scenario: [11] Undirected match in self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (a)-[r]-(b) + RETURN a, r, b + """ + Then the result should be, in any order: + | a | r | b | + | (:A) | [:LOOP] | (:A) | + And no side effects + + Scenario: [12] Undirected match of self-relationship in self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (n)-[r]-(n) + RETURN n, r + """ + Then the result should be, in any order: + | n | r | + | (:A) | [:LOOP] | + And no side effects + + Scenario: [13] Directed match on self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (a)-[r]->(b) + RETURN a, r, b + """ + Then the result should be, in any order: + | a | r | b | + | (:A) | [:LOOP] | (:A) | + And no side effects + + Scenario: [14] Directed match of self-relationship on self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (n)-[r]->(n) + RETURN n, r + """ + Then the result should be, in any order: + | n | r | + | (:A) | [:LOOP] | + And no side effects + + Scenario: [15] Mixing directed and undirected pattern parts with self-relationship, simple + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T1]->(l:Looper), + (l)-[:LOOP]->(l), + (l)-[:T2]->(:B) + """ + When executing query: + """ + MATCH (x:A)-[r1]->(y)-[r2]-(z) + RETURN x, r1, y, r2, z + """ + Then the result should be, in any order: + | x | r1 | y | r2 | z | + | (:A) | [:T1] | (:Looper) | [:LOOP] | (:Looper) | + | (:A) | [:T1] | (:Looper) | [:T2] | (:B) | + And no side effects + + Scenario: [16] Mixing directed and undirected pattern parts with self-relationship, undirected + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T1]->(l:Looper), + (l)-[:LOOP]->(l), + (l)-[:T2]->(:B) + """ + When executing query: + """ + MATCH (x)-[r1]-(y)-[r2]-(z) + RETURN x, r1, y, r2, z + """ + Then the result should be, in any order: + | x | r1 | y | r2 | z | + | (:A) | [:T1] | (:Looper) | [:LOOP] | (:Looper) | + | (:A) | [:T1] | (:Looper) | [:T2] | (:B) | + | (:Looper) | [:LOOP] | (:Looper) | [:T1] | (:A) | + | (:Looper) | [:LOOP] | (:Looper) | [:T2] | (:B) | + | (:B) | [:T2] | (:Looper) | [:LOOP] | (:Looper) | + | (:B) | [:T2] | (:Looper) | [:T1] | (:A) | + And no side effects + + Scenario: [17] Handling cyclic patterns + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}) + CREATE (a)-[:A]->(b), + (b)-[:B]->(a), + (b)-[:B]->(c) + """ + When executing query: + """ + MATCH (a)-[:A]->()-[:B]->(a) + RETURN a.name + """ + Then the result should be, in any order: + | a.name | + | 'a' | + And no side effects + + Scenario: [18] Handling cyclic patterns when separated into two parts + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}) + CREATE (a)-[:A]->(b), + (b)-[:B]->(a), + (b)-[:B]->(c) + """ + When executing query: + """ + MATCH (a)-[:A]->(b), (b)-[:B]->(a) + RETURN a.name + """ + Then the result should be, in any order: + | a.name | + | 'a' | + And no side effects + + Scenario: [19] Two bound nodes pointing to the same node + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (x1 {name: 'x1'}), (x2 {name: 'x2'}) + CREATE (a)-[:KNOWS]->(x1), + (a)-[:KNOWS]->(x2), + (b)-[:KNOWS]->(x1), + (b)-[:KNOWS]->(x2) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (b {name: 'B'}) + MATCH (a)-->(x)<-->(b) + RETURN x + """ + Then the result should be, in any order: + | x | + | ({name: 'x1'}) | + | ({name: 'x2'}) | + And no side effects + + Scenario: [20] Three bound nodes pointing to the same node + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}), + (x1 {name: 'x1'}), (x2 {name: 'x2'}) + CREATE (a)-[:KNOWS]->(x1), + (a)-[:KNOWS]->(x2), + (b)-[:KNOWS]->(x1), + (b)-[:KNOWS]->(x2), + (c)-[:KNOWS]->(x1), + (c)-[:KNOWS]->(x2) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + MATCH (a)-->(x), (b)-->(x), (c)-->(x) + RETURN x + """ + Then the result should be, in any order: + | x | + | ({name: 'x1'}) | + | ({name: 'x2'}) | + And no side effects + + Scenario: [21] Three bound nodes pointing to the same node with extra connections + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}), + (d {name: 'd'}), (e {name: 'e'}), (f {name: 'f'}), + (g {name: 'g'}), (h {name: 'h'}), (i {name: 'i'}), + (j {name: 'j'}), (k {name: 'k'}) + CREATE (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e), + (a)-[:KNOWS]->(f), + (a)-[:KNOWS]->(g), + (a)-[:KNOWS]->(i), + (b)-[:KNOWS]->(d), + (b)-[:KNOWS]->(e), + (b)-[:KNOWS]->(f), + (b)-[:KNOWS]->(h), + (b)-[:KNOWS]->(k), + (c)-[:KNOWS]->(d), + (c)-[:KNOWS]->(e), + (c)-[:KNOWS]->(h), + (c)-[:KNOWS]->(g), + (c)-[:KNOWS]->(j) + """ + When executing query: + """ + MATCH (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}) + MATCH (a)-->(x), (b)-->(x), (c)-->(x) + RETURN x + """ + Then the result should be, in any order: + | x | + | ({name: 'd'}) | + | ({name: 'e'}) | + And no side effects + + Scenario: [22] Returning bound nodes that are not part of the pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (c {name: 'C'}) + MATCH (a)-->(b) + RETURN a, b, c + """ + Then the result should be, in any order: + | a | b | c | + | ({name: 'A'}) | ({name: 'B'}) | ({name: 'C'}) | + And no side effects + + Scenario: [23] Matching disconnected patterns + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:T]->(b), + (a)-[:T]->(c) + """ + When executing query: + """ + MATCH (a)-->(b) + MATCH (c)-->(d) + RETURN a, b, c, d + """ + Then the result should be, in any order: + | a | b | c | d | + | (:A) | (:B) | (:A) | (:B) | + | (:A) | (:B) | (:A) | (:C) | + | (:A) | (:C) | (:A) | (:B) | + | (:A) | (:C) | (:A) | (:C) | + And no side effects + + Scenario: [24] Matching twice with duplicate relationship types on same relationship + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r:T]->() + WITH r, a1 + MATCH (a1)-[r:T]->(b2) + RETURN a1, r, b2 + """ + Then the result should be, in any order: + | a1 | r | b2 | + | (:A) | [:T] | (:B) | + And no side effects + + Scenario: [25] Matching twice with an additional node label + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + MATCH (a1:X)-[r]->(b2) + RETURN a1, r, b2 + """ + Then the result should be, in any order: + | a1 | r | b2 | + And no side effects + + Scenario: [26] Matching twice with a duplicate predicate + Given an empty graph + And having executed: + """ + CREATE (:X:Y)-[:T]->() + """ + When executing query: + """ + MATCH (a1:X:Y)-[r]->() + WITH r, a1 + MATCH (a1:Y)-[r]->(b2) + RETURN a1, r, b2 + """ + Then the result should be, in any order: + | a1 | r | b2 | + | (:X:Y) | [:T] | () | + And no side effects + + Scenario: [27] Matching from null nodes should return no results owing to finding no matches + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (a) + WITH a + MATCH (a)-->(b) + RETURN b + """ + Then the result should be, in any order: + | b | + And no side effects + + Scenario: [28] Matching from null nodes should return no results owing to matches being filtered out + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + OPTIONAL MATCH (a:TheLabel) + WITH a + MATCH (a)-->(b) + RETURN b + """ + Then the result should be, in any order: + | b | + And no side effects + + Scenario: [29] Fail when re-using a relationship in the same pattern + Given any graph + When executing query: + """ + MATCH (a)-[r]->()-[r]->(a) + RETURN r + """ + Then a SyntaxError should be raised at compile time: RelationshipUniquenessViolation + + Scenario: [30] Fail when using a list or nodes as a node + Given any graph + When executing query: + """ + MATCH (n) + WITH [n] AS users + MATCH (users)-->(messages) + RETURN messages + """ + Then a SyntaxError should be raised at compile time: VariableTypeConflict diff --git a/tck/features/clauses/match/Match4.feature b/tck/features/clauses/match/Match4.feature new file mode 100644 index 0000000..19b15d6 --- /dev/null +++ b/tck/features/clauses/match/Match4.feature @@ -0,0 +1,281 @@ +# +# Copyright (c) 2015-2021 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Attribution Notice under the terms of the Apache License 2.0 +# +# This work was created by the collective efforts of the openCypher community. +# Without limiting the terms of Section 6, any Derivative Work that is not +# approved by the public consensus process of the openCypher Implementers Group +# should not be described as “Cypher” (and Cypher® is a registered trademark of +# Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or +# proposals for change that have been documented or implemented should only be +# described as "implementation extensions to Cypher" or as "proposed changes to +# Cypher that are not yet approved by the openCypher community". +# + +#encoding: utf-8 + +Feature: Match4 - Match variable length patterns scenarios + + Scenario: [1] Handling fixed-length variable length pattern + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH (a)-[r*1..1]->(b) + RETURN r + """ + Then the result should be, in any order: + | r | + | [[:T]] | + And no side effects + + Scenario: [2] Simple variable length pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}), (d {name: 'D'}) + CREATE (a)-[:CONTAINS]->(b), + (b)-[:CONTAINS]->(c), + (c)-[:CONTAINS]->(d) + """ + When executing query: + """ + MATCH (a {name: 'A'})-[*]->(x) + RETURN x + """ + Then the result should be, in any order: + | x | + | ({name: 'B'}) | + | ({name: 'C'}) | + | ({name: 'D'}) | + And no side effects + + Scenario: [3] Zero-length variable length pattern in the middle of the pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}), ({name: 'D'}), + ({name: 'E'}) + CREATE (a)-[:CONTAINS]->(b), + (b)-[:FRIEND]->(c) + """ + When executing query: + """ + MATCH (a {name: 'A'})-[:CONTAINS*0..1]->(b)-[:FRIEND*0..1]->(c) + RETURN a, b, c + """ + Then the result should be, in any order: + | a | b | c | + | ({name: 'A'}) | ({name: 'A'}) | ({name: 'A'}) | + | ({name: 'A'}) | ({name: 'B'}) | ({name: 'B'}) | + | ({name: 'A'}) | ({name: 'B'}) | ({name: 'C'}) | + And no side effects + + Scenario: [4] Matching longer variable length paths + Given an empty graph + And having executed: + """ + CREATE (a {var: 'start'}), (b {var: 'end'}) + WITH * + UNWIND range(1, 20) AS i + CREATE (n {var: i}) + WITH [a] + collect(n) + [b] AS nodeList + UNWIND range(0, size(nodeList) - 2, 1) AS i + WITH nodeList[i] AS n1, nodeList[i+1] AS n2 + CREATE (n1)-[:T]->(n2) + """ + When executing query: + """ + MATCH (n {var: 'start'})-[:T*]->(m {var: 'end'}) + RETURN m + """ + Then the result should be, in any order: + | m | + | ({var: 'end'}) | + And no side effects + + Scenario: [5] Matching variable length pattern with property predicate + Given an empty graph + And having executed: + """ + CREATE (a:Artist:A), (b:Artist:B), (c:Artist:C) + CREATE (a)-[:WORKED_WITH {year: 1987}]->(b), + (b)-[:WORKED_WITH {year: 1988}]->(c) + """ + When executing query: + """ + MATCH (a:Artist)-[:WORKED_WITH* {year: 1988}]->(b:Artist) + RETURN * + """ + Then the result should be, in any order: + | a | b | + | (:Artist:B) | (:Artist:C) | + And no side effects + + Scenario: [6] Matching variable length patterns from a bound node + Given an empty graph + And having executed: + """ + CREATE (a:A), (b), (c) + CREATE (a)-[:X]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH (a:A) + MATCH (a)-[r*2]->() + RETURN r + """ + Then the result should be (ignoring element order for lists): + | r | + | [[:X], [:Y]] | + And no side effects + + Scenario: [7] Matching variable length patterns including a bound relationship + Given an empty graph + And having executed: + """ + CREATE (n0:Node), + (n1:Node), + (n2:Node), + (n3:Node), + (n0)-[:EDGE]->(n1), + (n1)-[:EDGE]->(n2), + (n2)-[:EDGE]->(n3) + """ + When executing query: + """ + MATCH ()-[r:EDGE]-() + MATCH p = (n)-[*0..1]-()-[r]-()-[*0..1]-(m) + RETURN count(p) AS c + """ + Then the result should be, in any order: + | c | + | 32 | + And no side effects + + Scenario: [8] Matching relationships into a list and matching variable length using the list + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:Y]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH ()-[r1]->()-[r2]->() + WITH [r1, r2] AS rs + LIMIT 1 + MATCH (first)-[rs*]->(second) + RETURN first, second + """ + Then the result should be, in any order: + | first | second | + | (:A) | (:C) | + And no side effects + + @skipGrammarCheck + Scenario: [9] Fail when asterisk operator is missing + Given an empty graph + And having executed: + """ + CREATE (n0:A {name: 'n0'}), + (n00:B {name: 'n00'}), + (n01:B {name: 'n01'}), + (n000:C {name: 'n000'}), + (n001:C {name: 'n001'}), + (n010:C {name: 'n010'}), + (n011:C {name: 'n011'}), + (n0000:D {name: 'n0000'}), + (n0001:D {name: 'n0001'}), + (n0010:D {name: 'n0010'}), + (n0011:D {name: 'n0011'}), + (n0100:D {name: 'n0100'}), + (n0101:D {name: 'n0101'}), + (n0110:D {name: 'n0110'}), + (n0111:D {name: 'n0111'}) + CREATE (n0)-[:LIKES]->(n00), + (n0)-[:LIKES]->(n01), + (n00)-[:LIKES]->(n000), + (n00)-[:LIKES]->(n001), + (n01)-[:LIKES]->(n010), + (n01)-[:LIKES]->(n011), + (n000)-[:LIKES]->(n0000), + (n000)-[:LIKES]->(n0001), + (n001)-[:LIKES]->(n0010), + (n001)-[:LIKES]->(n0011), + (n010)-[:LIKES]->(n0100), + (n010)-[:LIKES]->(n0101), + (n011)-[:LIKES]->(n0110), + (n011)-[:LIKES]->(n0111) + """ + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES..]->(c) + RETURN c.name + """ + Then a SyntaxError should be raised at compile time: InvalidRelationshipPattern + + @skipGrammarCheck + Scenario: [10] Fail on negative bound + Given an empty graph + And having executed: + """ + CREATE (n0:A {name: 'n0'}), + (n00:B {name: 'n00'}), + (n01:B {name: 'n01'}), + (n000:C {name: 'n000'}), + (n001:C {name: 'n001'}), + (n010:C {name: 'n010'}), + (n011:C {name: 'n011'}), + (n0000:D {name: 'n0000'}), + (n0001:D {name: 'n0001'}), + (n0010:D {name: 'n0010'}), + (n0011:D {name: 'n0011'}), + (n0100:D {name: 'n0100'}), + (n0101:D {name: 'n0101'}), + (n0110:D {name: 'n0110'}), + (n0111:D {name: 'n0111'}) + CREATE (n0)-[:LIKES]->(n00), + (n0)-[:LIKES]->(n01), + (n00)-[:LIKES]->(n000), + (n00)-[:LIKES]->(n001), + (n01)-[:LIKES]->(n010), + (n01)-[:LIKES]->(n011), + (n000)-[:LIKES]->(n0000), + (n000)-[:LIKES]->(n0001), + (n001)-[:LIKES]->(n0010), + (n001)-[:LIKES]->(n0011), + (n010)-[:LIKES]->(n0100), + (n010)-[:LIKES]->(n0101), + (n011)-[:LIKES]->(n0110), + (n011)-[:LIKES]->(n0111) + """ + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*-2]->(c) + RETURN c.name + """ + Then a SyntaxError should be raised at compile time: InvalidRelationshipPattern diff --git a/tck/features/clauses/match/Match5.feature b/tck/features/clauses/match/Match5.feature new file mode 100644 index 0000000..696d874 --- /dev/null +++ b/tck/features/clauses/match/Match5.feature @@ -0,0 +1,652 @@ +# +# Copyright (c) 2015-2021 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Attribution Notice under the terms of the Apache License 2.0 +# +# This work was created by the collective efforts of the openCypher community. +# Without limiting the terms of Section 6, any Derivative Work that is not +# approved by the public consensus process of the openCypher Implementers Group +# should not be described as “Cypher” (and Cypher® is a registered trademark of +# Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or +# proposals for change that have been documented or implemented should only be +# described as "implementation extensions to Cypher" or as "proposed changes to +# Cypher that are not yet approved by the openCypher community". +# + +#encoding: utf-8 + +Feature: Match5 - Match variable length patterns over given graphs scenarios + + # TODO: Replace this with a named graph (or two) + Background: + Given an empty graph + And having executed: + """ + CREATE (n0:A {name: 'n0'}), + (n00:B {name: 'n00'}), + (n01:B {name: 'n01'}), + (n000:C {name: 'n000'}), + (n001:C {name: 'n001'}), + (n010:C {name: 'n010'}), + (n011:C {name: 'n011'}), + (n0000:D {name: 'n0000'}), + (n0001:D {name: 'n0001'}), + (n0010:D {name: 'n0010'}), + (n0011:D {name: 'n0011'}), + (n0100:D {name: 'n0100'}), + (n0101:D {name: 'n0101'}), + (n0110:D {name: 'n0110'}), + (n0111:D {name: 'n0111'}) + CREATE (n0)-[:LIKES]->(n00), + (n0)-[:LIKES]->(n01), + (n00)-[:LIKES]->(n000), + (n00)-[:LIKES]->(n001), + (n01)-[:LIKES]->(n010), + (n01)-[:LIKES]->(n011), + (n000)-[:LIKES]->(n0000), + (n000)-[:LIKES]->(n0001), + (n001)-[:LIKES]->(n0010), + (n001)-[:LIKES]->(n0011), + (n010)-[:LIKES]->(n0100), + (n010)-[:LIKES]->(n0101), + (n011)-[:LIKES]->(n0110), + (n011)-[:LIKES]->(n0111) + """ + + Scenario: [1] Handling unbounded variable length match + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + | 'n0000' | + | 'n0001' | + | 'n0010' | + | 'n0011' | + | 'n0100' | + | 'n0101' | + | 'n0110' | + | 'n0111' | + And no side effects + + Scenario: [2] Handling explicitly unbounded variable length match + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*..]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + | 'n0000' | + | 'n0001' | + | 'n0010' | + | 'n0011' | + | 'n0100' | + | 'n0101' | + | 'n0110' | + | 'n0111' | + And no side effects + + Scenario: [3] Handling single bounded variable length match 1 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*0]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n0' | + And no side effects + + Scenario: [4] Handling single bounded variable length match 2 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*1]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + And no side effects + + Scenario: [5] Handling single bounded variable length match 3 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*2]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + And no side effects + + Scenario: [6] Handling upper and lower bounded variable length match 1 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*0..2]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n0' | + | 'n00' | + | 'n01' | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + And no side effects + + Scenario: [7] Handling upper and lower bounded variable length match 2 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*1..2]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + And no side effects + + Scenario: [8] Handling symmetrically bounded variable length match, bounds are zero + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*0..0]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n0' | + And no side effects + + Scenario: [9] Handling symmetrically bounded variable length match, bounds are one + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*1..1]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + And no side effects + + Scenario: [10] Handling symmetrically bounded variable length match, bounds are two + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*2..2]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + And no side effects + + Scenario: [11] Handling upper and lower bounded variable length match, empty interval 1 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*2..1]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + And no side effects + + Scenario: [12] Handling upper and lower bounded variable length match, empty interval 2 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*1..0]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + And no side effects + + Scenario: [13] Handling upper bounded variable length match, empty interval + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*..0]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + And no side effects + + Scenario: [14] Handling upper bounded variable length match 1 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*..1]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + And no side effects + + Scenario: [15] Handling upper bounded variable length match 2 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*..2]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + And no side effects + + Scenario: [16] Handling lower bounded variable length match 1 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*0..]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n0' | + | 'n00' | + | 'n01' | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + | 'n0000' | + | 'n0001' | + | 'n0010' | + | 'n0011' | + | 'n0100' | + | 'n0101' | + | 'n0110' | + | 'n0111' | + And no side effects + + Scenario: [17] Handling lower bounded variable length match 2 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*1..]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + | 'n0000' | + | 'n0001' | + | 'n0010' | + | 'n0011' | + | 'n0100' | + | 'n0101' | + | 'n0110' | + | 'n0111' | + And no side effects + + Scenario: [18] Handling lower bounded variable length match 3 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*2..]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + | 'n0000' | + | 'n0001' | + | 'n0010' | + | 'n0011' | + | 'n0100' | + | 'n0101' | + | 'n0110' | + | 'n0111' | + And no side effects + + Scenario: [19] Handling a variable length relationship and a standard relationship in chain, zero length 1 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*0]->()-[:LIKES]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + And no side effects + + Scenario: [20] Handling a variable length relationship and a standard relationship in chain, zero length 2 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES]->()-[:LIKES*0]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00' | + | 'n01' | + And no side effects + + Scenario: [21] Handling a variable length relationship and a standard relationship in chain, single length 1 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*1]->()-[:LIKES]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + And no side effects + + Scenario: [22] Handling a variable length relationship and a standard relationship in chain, single length 2 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES]->()-[:LIKES*1]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n000' | + | 'n001' | + | 'n010' | + | 'n011' | + And no side effects + + Scenario: [23] Handling a variable length relationship and a standard relationship in chain, longer 1 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES*2]->()-[:LIKES]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n0000' | + | 'n0001' | + | 'n0010' | + | 'n0011' | + | 'n0100' | + | 'n0101' | + | 'n0110' | + | 'n0111' | + And no side effects + + Scenario: [24] Handling a variable length relationship and a standard relationship in chain, longer 2 + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES]->()-[:LIKES*2]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n0000' | + | 'n0001' | + | 'n0010' | + | 'n0011' | + | 'n0100' | + | 'n0101' | + | 'n0110' | + | 'n0111' | + And no side effects + + Scenario: [25] Handling a variable length relationship and a standard relationship in chain, longer 3 + And having executed: + """ + MATCH (d:D) + CREATE (e1:E {name: d.name + '0'}), + (e2:E {name: d.name + '1'}) + CREATE (d)-[:LIKES]->(e1), + (d)-[:LIKES]->(e2) + """ + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES]->()-[:LIKES*3]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00000' | + | 'n00001' | + | 'n00010' | + | 'n00011' | + | 'n00100' | + | 'n00101' | + | 'n00110' | + | 'n00111' | + | 'n01000' | + | 'n01001' | + | 'n01010' | + | 'n01011' | + | 'n01100' | + | 'n01101' | + | 'n01110' | + | 'n01111' | + And no side effects + + Scenario: [26] Handling mixed relationship patterns and directions 1 + And having executed: + """ + MATCH (a:A)-[r]->(b) + DELETE r + CREATE (b)-[:LIKES]->(a) + """ + And having executed: + """ + MATCH (d:D) + CREATE (e1:E {name: d.name + '0'}), + (e2:E {name: d.name + '1'}) + CREATE (d)-[:LIKES]->(e1), + (d)-[:LIKES]->(e2) + """ + When executing query: + """ + MATCH (a:A) + MATCH (a)<-[:LIKES]-()-[:LIKES*3]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00000' | + | 'n00001' | + | 'n00010' | + | 'n00011' | + | 'n00100' | + | 'n00101' | + | 'n00110' | + | 'n00111' | + | 'n01000' | + | 'n01001' | + | 'n01010' | + | 'n01011' | + | 'n01100' | + | 'n01101' | + | 'n01110' | + | 'n01111' | + And no side effects + + Scenario: [27] Handling mixed relationship patterns and directions 2 + # This gets hard to follow for a human mind. The answer is named graphs, but it's not crucial to fix. + And having executed: + """ + MATCH (a)-[r]->(b) + WHERE NOT a:A + DELETE r + CREATE (b)-[:LIKES]->(a) + """ + And having executed: + """ + MATCH (d:D) + CREATE (e1:E {name: d.name + '0'}), + (e2:E {name: d.name + '1'}) + CREATE (d)-[:LIKES]->(e1), + (d)-[:LIKES]->(e2) + """ + When executing query: + """ + MATCH (a:A) + MATCH (a)-[:LIKES]->()<-[:LIKES*3]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00000' | + | 'n00001' | + | 'n00010' | + | 'n00011' | + | 'n00100' | + | 'n00101' | + | 'n00110' | + | 'n00111' | + | 'n01000' | + | 'n01001' | + | 'n01010' | + | 'n01011' | + | 'n01100' | + | 'n01101' | + | 'n01110' | + | 'n01111' | + And no side effects + + Scenario: [28] Handling mixed relationship patterns 1 + And having executed: + """ + MATCH (d:D) + CREATE (e1:E {name: d.name + '0'}), + (e2:E {name: d.name + '1'}) + CREATE (d)-[:LIKES]->(e1), + (d)-[:LIKES]->(e2) + """ + When executing query: + """ + MATCH (a:A) + MATCH (p)-[:LIKES*1]->()-[:LIKES]->()-[r:LIKES*2]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00000' | + | 'n00001' | + | 'n00010' | + | 'n00011' | + | 'n00100' | + | 'n00101' | + | 'n00110' | + | 'n00111' | + | 'n01000' | + | 'n01001' | + | 'n01010' | + | 'n01011' | + | 'n01100' | + | 'n01101' | + | 'n01110' | + | 'n01111' | + And no side effects + + Scenario: [29] Handling mixed relationship patterns 2 + And having executed: + """ + MATCH (d:D) + CREATE (e1:E {name: d.name + '0'}), + (e2:E {name: d.name + '1'}) + CREATE (d)-[:LIKES]->(e1), + (d)-[:LIKES]->(e2) + """ + When executing query: + """ + MATCH (a:A) + MATCH (p)-[:LIKES]->()-[:LIKES*2]->()-[r:LIKES]->(c) + RETURN c.name + """ + Then the result should be, in any order: + | c.name | + | 'n00000' | + | 'n00001' | + | 'n00010' | + | 'n00011' | + | 'n00100' | + | 'n00101' | + | 'n00110' | + | 'n00111' | + | 'n01000' | + | 'n01001' | + | 'n01010' | + | 'n01011' | + | 'n01100' | + | 'n01101' | + | 'n01110' | + | 'n01111' | + And no side effects diff --git a/tck/features/clauses/match/Match6.feature b/tck/features/clauses/match/Match6.feature new file mode 100644 index 0000000..228ad38 --- /dev/null +++ b/tck/features/clauses/match/Match6.feature @@ -0,0 +1,528 @@ +# +# Copyright (c) 2015-2021 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Attribution Notice under the terms of the Apache License 2.0 +# +# This work was created by the collective efforts of the openCypher community. +# Without limiting the terms of Section 6, any Derivative Work that is not +# approved by the public consensus process of the openCypher Implementers Group +# should not be described as “Cypher” (and Cypher® is a registered trademark of +# Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or +# proposals for change that have been documented or implemented should only be +# described as "implementation extensions to Cypher" or as "proposed changes to +# Cypher that are not yet approved by the openCypher community". +# + +#encoding: utf-8 + +Feature: Match6 - Match named paths scenarios + + Scenario: [1] Zero-length named path + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH p = (a) + RETURN p + """ + Then the result should be, in any order: + | p | + | <()> | + And no side effects + + Scenario: [2] Return a simple path + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'}) + """ + When executing query: + """ + MATCH p = (a {name: 'A'})-->(b) + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})> | + And no side effects + + + Scenario: [3] Return a three node path + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'})-[:KNOWS]->(c:C {name: 'C'}) + """ + When executing query: + """ + MATCH p = (a {name: 'A'})-[rel1]->(b)-[rel2]->(c) + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})-[:KNOWS]->(:C {name: 'C'})> | + And no side effects + + Scenario: [4] Respecting direction when matching non-existent path + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH p = ({name: 'a'})<--({name: 'b'}) + RETURN p + """ + Then the result should be, in any order: + | p | + And no side effects + + Scenario: [5] Path query should return results in written order + Given an empty graph + And having executed: + """ + CREATE (:Label1)<-[:TYPE]-(:Label2) + """ + When executing query: + """ + MATCH p = (a:Label1)<--(:Label2) + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:Label1)<-[:TYPE]-(:Label2)> | + And no side effects + + Scenario: [6] Handling direction of named paths + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:T]->(b:B) + """ + When executing query: + """ + MATCH p = (b)<--(a) + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:B)<-[:T]-(:A)> | + And no side effects + + Scenario: [7] Respecting direction when matching existing path + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH p = ({name: 'a'})-->({name: 'b'}) + RETURN p + """ + Then the result should be, in any order: + | p | + | <({name: 'a'})-[:T]->({name: 'b'})> | + And no side effects + + Scenario: [8] Respecting direction when matching non-existent path with multiple directions + Given an empty graph + And having executed: + """ + CREATE (a), (b) + CREATE (a)-[:T]->(b), + (b)-[:T]->(a) + """ + When executing query: + """ + MATCH p = (n)-->(k)<--(n) + RETURN p + """ + Then the result should be, in any order: + | p | + And no side effects + + Scenario: [9] Longer path query should return results in written order + Given an empty graph + And having executed: + """ + CREATE (:Label1)<-[:T1]-(:Label2)-[:T2]->(:Label3) + """ + When executing query: + """ + MATCH p = (a:Label1)<--(:Label2)--() + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:Label1)<-[:T1]-(:Label2)-[:T2]->(:Label3)> | + And no side effects + + Scenario: [10] Named path with alternating directed/undirected relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (b)-[:T]->(a), + (c)-[:T]->(b) + """ + When executing query: + """ + MATCH p = (n)-->(m)--(o) + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:C)-[:T]->(:B)-[:T]->(:A)> | + And no side effects + + Scenario: [11] Named path with multiple alternating directed/undirected relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C), (d:D) + CREATE (b)-[:T]->(a), + (c)-[:T]->(b), + (d)-[:T]->(c) + """ + When executing query: + """ + MATCH path = (n)-->(m)--(o)--(p) + RETURN path + """ + Then the result should be, in any order: + | path | + | <(:D)-[:T]->(:C)-[:T]->(:B)-[:T]->(:A)> | + And no side effects + + Scenario: [12] Matching path with multiple bidirectional relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T1]->(b), + (b)-[:T2]->(a) + """ + When executing query: + """ + MATCH p=(n)<-->(k)<-->(n) + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:A)<-[:T2]-(:B)<-[:T1]-(:A)> | + | <(:A)-[:T1]->(:B)-[:T2]->(:A)> | + | <(:B)<-[:T1]-(:A)<-[:T2]-(:B)> | + | <(:B)-[:T2]->(:A)-[:T1]->(:B)> | + And no side effects + + Scenario: [13] Matching path with both directions should respect other directions + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T1]->(b), + (b)-[:T2]->(a) + """ + When executing query: + """ + MATCH p = (n)<-->(k)<--(n) + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:A)<-[:T2]-(:B)<-[:T1]-(:A)> | + | <(:B)<-[:T1]-(:A)<-[:T2]-(:B)> | + And no side effects + + Scenario: [14] Named path with undirected fixed variable length pattern + Given an empty graph + And having executed: + """ + CREATE (db1:Start), (db2:End), (mid), (other) + CREATE (mid)-[:CONNECTED_TO]->(db1), + (mid)-[:CONNECTED_TO]->(db2), + (mid)-[:CONNECTED_TO]->(db2), + (mid)-[:CONNECTED_TO]->(other), + (mid)-[:CONNECTED_TO]->(other) + """ + When executing query: + """ + MATCH topRoute = (:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO*3..3]-(:End) + RETURN topRoute + """ + Then the result should be, in any order: + | topRoute | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + And no side effects + + Scenario: [15] Variable-length named path + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH p = ()-[*0..]->() + RETURN p + """ + Then the result should be, in any order: + | p | + | <()> | + And no side effects + + Scenario: [16] Return a var length path + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS {num: 1}]->(b:B {name: 'B'})-[:KNOWS {num: 2}]->(c:C {name: 'C'}) + """ + When executing query: + """ + MATCH p = (n {name: 'A'})-[:KNOWS*1..2]->(x) + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:A {name: 'A'})-[:KNOWS {num: 1}]->(:B {name: 'B'})> | + | <(:A {name: 'A'})-[:KNOWS {num: 1}]->(:B {name: 'B'})-[:KNOWS {num: 2}]->(:C {name: 'C'})> | + And no side effects + + Scenario: [17] Return a named var length path of length zero + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'})-[:FRIEND]->(c:C {name: 'C'}) + """ + When executing query: + """ + MATCH p = (a {name: 'A'})-[:KNOWS*0..1]->(b)-[:FRIEND*0..1]->(c) + RETURN p + """ + Then the result should be, in any order: + | p | + | <(:A {name: 'A'})> | + | <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})> | + | <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})-[:FRIEND]->(:C {name: 'C'})> | + And no side effects + + Scenario: [18] Undirected named path + Given an empty graph + And having executed: + """ + CREATE (a:Movie), (b) + CREATE (b)-[:T]->(a) + """ + When executing query: + """ + MATCH p = (n:Movie)--(m) + RETURN p + LIMIT 1 + """ + Then the result should be, in any order: + | p | + | <(:Movie)<-[:T]-()> | + And no side effects + + Scenario: [19] Variable length relationship without lower bound + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b), + (b)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH p = ({name: 'A'})-[:KNOWS*..2]->() + RETURN p + """ + Then the result should be, in any order: + | p | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})> | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})-[:KNOWS]->({name: 'C'})> | + And no side effects + + Scenario: [20] Variable length relationship without bounds + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b), + (b)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH p = ({name: 'A'})-[:KNOWS*..]->() + RETURN p + """ + Then the result should be, in any order: + | p | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})> | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})-[:KNOWS]->({name: 'C'})> | + And no side effects + + Scenario Outline: [21] Fail when a node has the same variable in a preceding MATCH + Given any graph + When executing query: + """ + MATCH + MATCH p = ()-[]-() + RETURN p + """ + Then a SyntaxError should be raised at compile time: VariableAlreadyBound + + Examples: + | pattern | + | (p)-[]-() | + | (p)-[]->() | + | (p)<-[]-() | + | ()-[]-(p) | + | ()-[]->(p) | + | ()<-[]-(p) | + | (p)-[]-(), () | + | ()-[]-(p), () | + | (p)-[]-()-[]-() | + | ()-[]-(p)-[]-() | + | ()-[]-()-[]-(p) | + | (a)-[r]-(p)-[]->(b), (t), (t)-[*]-(b) | + | (a)-[r*]-(s)-[]-(b), (p), (t)-[]-(b) | + | (a)-[r]-(p)<-[*]-(b), (t), (t)-[]-(b) | + + Scenario Outline: [22] Fail when a relationship has the same variable in a preceding MATCH + Given any graph + When executing query: + """ + MATCH + MATCH p = ()-[]-() + RETURN p + """ + Then a SyntaxError should be raised at compile time: VariableAlreadyBound + + Examples: + | pattern | + | ()-[p]-() | + | ()-[p]->() | + | ()<-[p]-() | + | ()-[p*]-() | + | ()-[p*]->() | + | ()<-[p*]-() | + | ()-[p]-(), () | + | ()-[p*]-(), () | + | ()-[p]-()-[]-() | + | ()-[p*]-()-[]-() | + | ()-[]-()-[p]-() | + | ()-[]-()-[p*]-() | + | (a)-[r]-()-[]->(b), (t), (t)-[p*]-(b) | + | (a)-[r*]-(s)-[p]-(b), (t), (t)-[]-(b) | + | (a)-[r]-(s)<-[p]-(b), (t), (t)-[]-(b) | + + Scenario Outline: [23] Fail when a node has the same variable in the same pattern + Given any graph + When executing query: + """ + MATCH + RETURN p + """ + Then a SyntaxError should be raised at compile time: VariableAlreadyBound + + Examples: + | pattern | + | p = (p)-[]-() | + | p = (p)-[]->() | + | p = (p)<-[]-() | + | p = ()-[]-(p) | + | p = ()-[]->(p) | + | p = ()<-[]-(p) | + | (p)-[]-(), p = ()-[]-() | + | (p)-[]->(), p = ()-[]-() | + | (p)<-[]-(), p = ()-[]-() | + | ()-[]-(p), p = ()-[]-() | + | ()-[]->(p), p = ()-[]-() | + | ()<-[]-(p), p = ()-[]-() | + | (p)-[]-(), (), p = ()-[]-() | + | ()-[p]-(), (), p = ()-[]-() | + | ()-[]-(p), (), p = ()-[]-() | + | (p)-[]-()-[]-(), p = ()-[]-() | + | ()-[]-(p)-[]-(), p = ()-[]-() | + | ()-[]-()-[]-(p), p = ()-[]-() | + | (a)-[r]-(p)-[]-(b), p = (s)-[]-(t), (t), (t)-[]-(b) | + | (a)-[r]-(p)<-[*]-(b), p = (s)-[]-(t), (t), (t)-[]-(b) | + + Scenario Outline: [24] Fail when a relationship has the same variable in the same pattern + Given any graph + When executing query: + """ + MATCH + RETURN p + """ + Then a SyntaxError should be raised at compile time: VariableAlreadyBound + + Examples: + | pattern | + | p = ()-[p]-() | + | p = ()-[p]->() | + | p = ()<-[p]-() | + | p = ()-[p*]-() | + | p = ()-[p*]->() | + | p = ()<-[p*]-() | + | ()-[p]-(), p = ()-[]-() | + | ()-[p]->(), p = ()-[]-() | + | ()<-[p]-(), p = ()-[]-() | + | ()-[p*]-(), p = ()-[]-() | + | ()-[p*]->(), p = ()-[]-() | + | ()<-[p*]-(), p = ()-[]-() | + | ()-[p]-(), (), p = ()-[]-() | + | ()-[p*]-(), (), p = ()-[]-() | + | ()-[p]-()-[]-(), p = ()-[]-() | + | ()-[p*]-()-[]-(), p = ()-[]-() | + | ()-[]-()-[p]-(), p = ()-[]-() | + | ()-[]-()-[p*]-(), p = ()-[]-() | + | (a)-[r]-(s)-[p]-(b), p = (s)-[]-(t), (t), (t)-[]-(b) | + | (a)-[r]-(s)<-[p*]-(b), p = (s)-[]-(t), (t), (t)-[]-(b) | + + Scenario Outline: [25] Fail when matching a path variable bound to a value + Given any graph + When executing query: + """ + WITH AS p + MATCH p = ()-[]-() + RETURN p + """ + Then a SyntaxError should be raised at compile time: VariableAlreadyBound + + Examples: + | invalid | + | true | + | 123 | + | 123.4 | + | 'foo' | + | [] | + | [10] | + | {x: 1} | + | {x: []} | diff --git a/tck/features/clauses/match/Match7.feature b/tck/features/clauses/match/Match7.feature new file mode 100644 index 0000000..34320d6 --- /dev/null +++ b/tck/features/clauses/match/Match7.feature @@ -0,0 +1,669 @@ +# +# Copyright (c) 2015-2021 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Attribution Notice under the terms of the Apache License 2.0 +# +# This work was created by the collective efforts of the openCypher community. +# Without limiting the terms of Section 6, any Derivative Work that is not +# approved by the public consensus process of the openCypher Implementers Group +# should not be described as “Cypher” (and Cypher® is a registered trademark of +# Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or +# proposals for change that have been documented or implemented should only be +# described as "implementation extensions to Cypher" or as "proposed changes to +# Cypher that are not yet approved by the openCypher community". +# + +#encoding: utf-8 + +#consider splitting into separate category optional-match +Feature: Match7 - Optional match + + Scenario: [1] Simple OPTIONAL MATCH on empty graph + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (n) + RETURN n + """ + Then the result should be, in any order: + | n | + | null | + And no side effects + + Scenario: [2] OPTIONAL MATCH with previously bound nodes + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + OPTIONAL MATCH (n)-[:NOT_EXIST]->(x) + RETURN n, x + """ + Then the result should be, in any order: + | n | x | + | () | null | + And no side effects + + Scenario: [3] OPTIONAL MATCH and bound nodes + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:A), (b:C) + OPTIONAL MATCH (x)-->(b) + RETURN x + """ + Then the result should be, in any order: + | x | + | (:A {num: 42}) | + And no side effects + + Scenario: [4] Optionally matching relationship with bound nodes in reverse direction + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + OPTIONAL MATCH (a1)<-[r]-(b2) + RETURN a1, r, b2 + """ + Then the result should be, in any order: + | a1 | r | b2 | + | (:A) | [:T] | null | + And no side effects + + Scenario: [5] Optionally matching relationship with a relationship that is already bound + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH ()-[r]->() + WITH r + LIMIT 1 + OPTIONAL MATCH (a2)-[r]->(b2) + RETURN a2, r, b2 + """ + Then the result should be, in any order: + | a2 | r | b2 | + | (:A) | [:T] | (:B) | + And no side effects + + Scenario: [6] Optionally matching relationship with a relationship and node that are both already bound + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + OPTIONAL MATCH (a1)-[r]->(b2) + RETURN a1, r, b2 + """ + Then the result should be, in any order: + | a1 | r | b2 | + | (:A) | [:T] | (:B) | + And no side effects + + Scenario: [7] MATCH with OPTIONAL MATCH in longer pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b), + (b)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH (a {name: 'A'}) + OPTIONAL MATCH (a)-[:KNOWS]->()-[:KNOWS]->(foo) + RETURN foo + """ + Then the result should be, in any order: + | foo | + | ({name: 'C'}) | + And no side effects + + Scenario: [8] Longer pattern with bound nodes without matches + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:A), (c:C) + OPTIONAL MATCH (a)-->(b)-->(c) + RETURN b + """ + Then the result should be, in any order: + | b | + | null | + And no side effects + + Scenario: [9] Longer pattern with bound nodes + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:Single), (c:C) + OPTIONAL MATCH (a)-->(b)-->(c) + RETURN b + """ + Then the result should be, in any order: + | b | + | (:A {num: 42}) | + And no side effects + + Scenario: [10] Optionally matching from null nodes should return null + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (a) + WITH a + OPTIONAL MATCH (a)-->(b) + RETURN b + """ + Then the result should be, in any order: + | b | + | null | + And no side effects + + Scenario: [11] Return two subgraphs with bound undirected relationship and optional relationship + Given an empty graph + And having executed: + """ + CREATE (a:A {num: 1})-[:REL {name: 'r1'}]->(b:B {num: 2})-[:REL {name: 'r2'}]->(c:C {num: 3}) + """ + When executing query: + """ + MATCH (a)-[r {name: 'r1'}]-(b) + OPTIONAL MATCH (b)-[r2]-(c) + WHERE r <> r2 + RETURN a, b, c + """ + Then the result should be, in any order: + | a | b | c | + | (:A {num: 1}) | (:B {num: 2}) | (:C {num: 3}) | + | (:B {num: 2}) | (:A {num: 1}) | null | + And no side effects + + Scenario: [12] Variable length optional relationships + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:Single) + OPTIONAL MATCH (a)-[*]->(b) + RETURN b + """ + Then the result should be, in any order: + | b | + | (:A {num: 42}) | + | (:B {num: 46}) | + | (:B {num: 46}) | + | (:C) | + And no side effects + + Scenario: [13] Variable length optional relationships with bound nodes + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:Single), (x:C) + OPTIONAL MATCH (a)-[*]->(x) + RETURN x + """ + Then the result should be, in any order: + | x | + | (:C) | + And no side effects + + Scenario: [14] Variable length optional relationships with length predicates + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:Single) + OPTIONAL MATCH (a)-[*3..]-(b) + RETURN b + """ + Then the result should be, in any order: + | b | + | null | + And no side effects + + Scenario: [15] Variable length patterns and nulls + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + """ + When executing query: + """ + MATCH (a:A) + OPTIONAL MATCH (a)-[:FOO]->(b:B) + OPTIONAL MATCH (b)<-[:BAR*]-(c:B) + RETURN a, b, c + """ + Then the result should be, in any order: + | a | b | c | + | (:A) | null | null | + And no side effects + + Scenario: [16] Optionally matching named paths - null result + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:A) + OPTIONAL MATCH p = (a)-[:X]->(b) + RETURN p + """ + Then the result should be, in any order: + | p | + | null | + And no side effects + + Scenario: [17] Optionally matching named paths - existing result + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + CREATE (a)-[:X]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (x) + WHERE x.name IN ['B', 'C'] + OPTIONAL MATCH p = (a)-->(x) + RETURN x, p + """ + Then the result should be, in any order: + | x | p | + | ({name: 'B'}) | <({name: 'A'})-[:X]->({name: 'B'})> | + | ({name: 'C'}) | null | + And no side effects + + Scenario: [18] Named paths inside optional matches with node predicates + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:A), (b:B) + OPTIONAL MATCH p = (a)-[:X]->(b) + RETURN p + """ + Then the result should be, in any order: + | p | + | null | + And no side effects + + Scenario: [19] Optionally matching named paths with single and variable length patterns + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}) + CREATE (a)-[:X]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}) + OPTIONAL MATCH p = (a)-->(b)-[*]->(c) + RETURN p + """ + Then the result should be, in any order: + | p | + | null | + And no side effects + + Scenario: [20] Variable length optional relationships with bound nodes, no matches + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:A), (b:B) + OPTIONAL MATCH p = (a)-[*]->(b) + RETURN p + """ + Then the result should be, in any order: + | p | + | null | + And no side effects + + Scenario: [21] Handling optional matches between nulls + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + OPTIONAL MATCH (a:NotThere) + OPTIONAL MATCH (b:NotThere) + WITH a, b + OPTIONAL MATCH (b)-[r:NOR_THIS]->(a) + RETURN a, b, r + """ + Then the result should be, in any order: + | a | b | r | + | null | null | null | + And no side effects + + Scenario: [22] MATCH after OPTIONAL MATCH + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:Single) + OPTIONAL MATCH (a)-->(b:NonExistent) + OPTIONAL MATCH (a)-->(c:NonExistent) + WITH coalesce(b, c) AS x + MATCH (x)-->(d) + RETURN d + """ + Then the result should be, in any order: + | d | + And no side effects + + Scenario: [23] OPTIONAL MATCH with labels on the optional end node + Given an empty graph + And having executed: + """ + CREATE (:X), (x:X), (y1:Y), (y2:Y:Z) + CREATE (x)-[:REL]->(y1), + (x)-[:REL]->(y2) + """ + When executing query: + """ + MATCH (a:X) + OPTIONAL MATCH (a)-->(b:Y) + RETURN b + """ + Then the result should be, in any order: + | b | + | null | + | (:Y) | + | (:Y:Z) | + And no side effects + + Scenario: [24] Optionally matching self-loops + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:B) + OPTIONAL MATCH (a)-[r]-(a) + RETURN r + """ + Then the result should be, in any order: + | r | + | [:LOOP] | + And no side effects + + Scenario: [25] Optionally matching self-loops without matches + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a) + WHERE NOT (a:B) + OPTIONAL MATCH (a)-[r]->(a) + RETURN r + """ + Then the result should be, in any order: + | r | + | null | + | null | + | null | + And no side effects + + Scenario: [26] Handling correlated optional matches; first does not match implies second does not match + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (a:A), (b:B) + OPTIONAL MATCH (a)-->(x) + OPTIONAL MATCH (x)-[r]->(b) + RETURN x, r + """ + Then the result should be, in any order: + | x | r | + | (:C) | null | + And no side effects + + Scenario: [27] Handling optional matches between optionally matched entities + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + OPTIONAL MATCH (a:NotThere) + WITH a + MATCH (b:B) + WITH a, b + OPTIONAL MATCH (b)-[r:NOR_THIS]->(a) + RETURN a, b, r + """ + Then the result should be, in any order: + | a | b | r | + | null | (:B {num: 46}) | null | + And no side effects + + Scenario: [28] Handling optional matches with inline label predicate + Given an empty graph + And having executed: + """ + CREATE (s:Single), (a:A {num: 42}), + (b:B {num: 46}), (c:C) + CREATE (s)-[:REL]->(a), + (s)-[:REL]->(b), + (a)-[:REL]->(c), + (b)-[:LOOP]->(b) + """ + When executing query: + """ + MATCH (n:Single) + OPTIONAL MATCH (n)-[r]-(m:NonExistent) + RETURN r + """ + Then the result should be, in any order: + | r | + | null | + And no side effects + + Scenario: [29] Satisfies the open world assumption, relationships between same nodes + Given an empty graph + And having executed: + """ + CREATE (a:Player), (b:Team) + CREATE (a)-[:PLAYS_FOR]->(b), + (a)-[:SUPPORTS]->(b) + """ + When executing query: + """ + MATCH (p:Player)-[:PLAYS_FOR]->(team:Team) + OPTIONAL MATCH (p)-[s:SUPPORTS]->(team) + RETURN count(*) AS matches, s IS NULL AS optMatch + """ + Then the result should be, in any order: + | matches | optMatch | + | 1 | false | + And no side effects + + Scenario: [30] Satisfies the open world assumption, single relationship + Given an empty graph + And having executed: + """ + CREATE (a:Player), (b:Team) + CREATE (a)-[:PLAYS_FOR]->(b) + """ + When executing query: + """ + MATCH (p:Player)-[:PLAYS_FOR]->(team:Team) + OPTIONAL MATCH (p)-[s:SUPPORTS]->(team) + RETURN count(*) AS matches, s IS NULL AS optMatch + """ + Then the result should be, in any order: + | matches | optMatch | + | 1 | true | + And no side effects + + Scenario: [31] Satisfies the open world assumption, relationships between different nodes + Given an empty graph + And having executed: + """ + CREATE (a:Player), (b:Team), (c:Team) + CREATE (a)-[:PLAYS_FOR]->(b), + (a)-[:SUPPORTS]->(c) + """ + When executing query: + """ + MATCH (p:Player)-[:PLAYS_FOR]->(team:Team) + OPTIONAL MATCH (p)-[s:SUPPORTS]->(team) + RETURN count(*) AS matches, s IS NULL AS optMatch + """ + Then the result should be, in any order: + | matches | optMatch | + | 1 | true | + And no side effects diff --git a/tck/features/clauses/match/Match8.feature b/tck/features/clauses/match/Match8.feature new file mode 100644 index 0000000..890f48f --- /dev/null +++ b/tck/features/clauses/match/Match8.feature @@ -0,0 +1,104 @@ +# +# Copyright (c) 2015-2021 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Attribution Notice under the terms of the Apache License 2.0 +# +# This work was created by the collective efforts of the openCypher community. +# Without limiting the terms of Section 6, any Derivative Work that is not +# approved by the public consensus process of the openCypher Implementers Group +# should not be described as “Cypher” (and Cypher® is a registered trademark of +# Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or +# proposals for change that have been documented or implemented should only be +# described as "implementation extensions to Cypher" or as "proposed changes to +# Cypher that are not yet approved by the openCypher community". +# + +#encoding: utf-8 + +Feature: Match8 - Match clause interoperation with other clauses + + Scenario: [1] Pattern independent of bound variables results in cross product + Given an empty graph + And having executed: + """ + CREATE (:A), (:B) + """ + When executing query: + """ + MATCH (a) + WITH a + MATCH (b) + RETURN a, b + """ + Then the result should be, in any order: + | a | b | + | (:A) | (:A) | + | (:A) | (:B) | + | (:B) | (:A) | + | (:B) | (:B) | + And no side effects + + Scenario: [2] Counting rows after MATCH, MERGE, OPTIONAL MATCH + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T1]->(b), + (b)-[:T2]->(a) + """ + When executing query: + """ + MATCH (a) + MERGE (b) + WITH * + OPTIONAL MATCH (a)--(b) + RETURN count(*) + """ + Then the result should be, in any order: + | count(*) | + | 6 | + And no side effects + + Scenario: [3] Matching and disregarding output, then matching again + Given an empty graph + And having executed: + """ + CREATE (andres {name: 'Andres'}), + (michael {name: 'Michael'}), + (peter {name: 'Peter'}), + (bread {type: 'Bread'}), + (veggies {type: 'Veggies'}), + (meat {type: 'Meat'}) + CREATE (andres)-[:ATE {times: 10}]->(bread), + (andres)-[:ATE {times: 8}]->(veggies), + (michael)-[:ATE {times: 4}]->(veggies), + (michael)-[:ATE {times: 6}]->(bread), + (michael)-[:ATE {times: 9}]->(meat), + (peter)-[:ATE {times: 7}]->(veggies), + (peter)-[:ATE {times: 7}]->(bread), + (peter)-[:ATE {times: 4}]->(meat) + """ + When executing query: + """ + MATCH ()-->() + WITH 1 AS x + MATCH ()-[r1]->()<--() + RETURN sum(r1.times) + """ + Then the result should be, in any order: + | sum(r1.times) | + | 776 | + And no side effects diff --git a/tck/features/clauses/match/Match9.feature b/tck/features/clauses/match/Match9.feature new file mode 100644 index 0000000..f7cd0f2 --- /dev/null +++ b/tck/features/clauses/match/Match9.feature @@ -0,0 +1,200 @@ +# +# Copyright (c) 2015-2021 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Attribution Notice under the terms of the Apache License 2.0 +# +# This work was created by the collective efforts of the openCypher community. +# Without limiting the terms of Section 6, any Derivative Work that is not +# approved by the public consensus process of the openCypher Implementers Group +# should not be described as “Cypher” (and Cypher® is a registered trademark of +# Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or +# proposals for change that have been documented or implemented should only be +# described as "implementation extensions to Cypher" or as "proposed changes to +# Cypher that are not yet approved by the openCypher community". +# + +#encoding: utf-8 + +Feature: Match9 - Match deprecated scenarios + + Scenario: [1] Variable length relationship variables are lists of relationships + Given an empty graph + And having executed: + """ + CREATE (a), (b), (c) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH ()-[r*0..1]-() + RETURN last(r) AS l + """ + Then the result should be, in any order: + | l | + | [:T] | + | [:T] | + | null | + | null | + | null | + And no side effects + + Scenario: [2] Return relationships by collecting them as a list - directed, one way + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:REL {num: 1}]->(b:B)-[:REL {num: 2}]->(e:End) + """ + When executing query: + """ + MATCH (a)-[r:REL*2..2]->(b:End) + RETURN r + """ + Then the result should be, in any order: + | r | + | [[:REL {num: 1}], [:REL {num: 2}]] | + And no side effects + + Scenario: [3] Return relationships by collecting them as a list - undirected, starting from two extremes + Given an empty graph + And having executed: + """ + CREATE (a:End)-[:REL {num: 1}]->(b:B)-[:REL {num: 2}]->(c:End) + """ + When executing query: + """ + MATCH (a)-[r:REL*2..2]-(b:End) + RETURN r + """ + Then the result should be, in any order: + | r | + | [[:REL {num:1}], [:REL {num:2}]] | + | [[:REL {num:2}], [:REL {num:1}]] | + And no side effects + + Scenario: [4] Return relationships by collecting them as a list - undirected, starting from one extreme + Given an empty graph + And having executed: + """ + CREATE (s:Start)-[:REL {num: 1}]->(b:B)-[:REL {num: 2}]->(c:C) + """ + When executing query: + """ + MATCH (a:Start)-[r:REL*2..2]-(b) + RETURN r + """ + Then the result should be, in any order: + | r | + | [[:REL {num: 1}], [:REL {num: 2}]] | + And no side effects + + Scenario: [5] Variable length pattern with label predicate on both sides + Given an empty graph + And having executed: + """ + CREATE (a:Blue), (b:Red), (c:Green), (d:Yellow) + CREATE (a)-[:T]->(b), + (b)-[:T]->(c), + (b)-[:T]->(d) + """ + When executing query: + """ + MATCH (a:Blue)-[r*]->(b:Green) + RETURN count(r) + """ + Then the result should be, in any order: + | count(r) | + | 1 | + And no side effects + + Scenario: [6] Matching relationships into a list and matching variable length using the list, with bound nodes + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:Y]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH (a)-[r1]->()-[r2]->(b) + WITH [r1, r2] AS rs, a AS first, b AS second + LIMIT 1 + MATCH (first)-[rs*]->(second) + RETURN first, second + """ + Then the result should be, in any order: + | first | second | + | (:A) | (:C) | + And no side effects + + Scenario: [7] Matching relationships into a list and matching variable length using the list, with bound nodes, wrong direction + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:Y]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH (a)-[r1]->()-[r2]->(b) + WITH [r1, r2] AS rs, a AS second, b AS first + LIMIT 1 + MATCH (first)-[rs*]->(second) + RETURN first, second + """ + Then the result should be, in any order: + | first | second | + And no side effects + + Scenario: [8] Variable length relationship in OPTIONAL MATCH + Given an empty graph + And having executed: + """ + CREATE (:A), (:B) + """ + When executing query: + """ + MATCH (a:A), (b:B) + OPTIONAL MATCH (a)-[r*]-(b) + WHERE r IS NULL + AND a <> b + RETURN b + """ + Then the result should be, in any order: + | b | + | (:B) | + And no side effects + + Scenario: [9] Optionally matching named paths with variable length patterns + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + CREATE (a)-[:X]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (x) + WHERE x.name IN ['B', 'C'] + OPTIONAL MATCH p = (a)-[r*]->(x) + RETURN r, x, p + """ + Then the result should be, in any order: + | r | x | p | + | [[:X]] | ({name: 'B'}) | <({name: 'A'})-[:X]->({name: 'B'})> | + | null | ({name: 'C'}) | null | + And no side effects diff --git a/tck/features/clauses/match/convert.js b/tck/features/clauses/match/convert.js new file mode 100644 index 0000000..a1fa52b --- /dev/null +++ b/tck/features/clauses/match/convert.js @@ -0,0 +1,33 @@ +// find and count kinds of test case vocabulary +// usage: deno run --allow-read convert.js *.feature + + +for (const f of Deno.args) { + const counts = {file: f} + const count = type => {counts[type] = counts[type] || 0; counts[type]++} + const t = (await Deno.readTextFile(`./${f}`)).split(/\n/) + const upto = end => {const l = t.shift(); if(!l.match(end)) upto(end)} + + let n = t.length + while (t.length) { + let l = t.shift() + if (l.match(/^ *#/)) count('#') + else if (l.match(/^$/)) count('Blank') + else if (l.match(/^Feature:/)) count('Feature') + else if (l.match(/^ Scenario:/)) count('Scenario') + else if (l.match(/^ Scenario Outline:/)) count('Outline') + else if (l.match(/^ Examples:/)) count('Examples') + else if (l.match(/^ """/)) {count('"""'); upto(/"""/)} + else if (l.match(/^ \| /)) count('|') + else if (l.match(/^ Given /)) count('Given') + else if (l.match(/^ And /)) count('And') + else if (l.match(/^ When /)) count('When') + else if (l.match(/^ Then /)) count('Then') + else if (l.match(/^ @skipGrammarCheck/)) count('Skip') + else if (l.match(/^ Background:/)) count('Background') + else count(`More ${l.split(/ +/)[1]}`) // expect no more to match + } + + console.error(counts) +} + diff --git a/tck/features/clauses/match/find.js b/tck/features/clauses/match/find.js new file mode 100644 index 0000000..3c59981 --- /dev/null +++ b/tck/features/clauses/match/find.js @@ -0,0 +1,20 @@ +// find and print test queries with two MATCH ops +// usage: deno run --allow-read find.js *.feature + +for (const f of Deno.args) { + console.log(f) + const t = (await Deno.readTextFile(`./${f}`)).split(/\n/) + let n = t.length + while (t.length) { + if (t.shift().endsWith('When executing query:')) { + t.shift() + const l = [] + while (t.length && !t[0].includes('"""')) { + l.push(t.shift().trim()) + } + const m = l.join(" ") + if (m.match(/MATCH.*MATCH/)) + console.log(n - t.length - l.length + 1, m) + } + } +} \ No newline at end of file