Add openCypher 09 tck tests
Summary: Added latest tck openCypher tests. Made sure the file is parseable and that it returns coverage. Also bumped continuous integration configuration to use the 09 version. Reviewers: mferencevic, teon.banek, buda Reviewed By: buda Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1197
This commit is contained in:
parent
b8acefb1e3
commit
f83cc31779
@ -22,7 +22,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
# constants
|
||||
memgraph_suite = "memgraph_V1"
|
||||
extra_suites = ["openCypher_M06"]
|
||||
extra_suites = ["openCypher_M09"]
|
||||
results_folder = os.path.join("tck_engine", "results")
|
||||
suite_suffix = "memgraph-{}.json"
|
||||
qa_status_path = ".quality_assurance_status"
|
||||
|
@ -0,0 +1,162 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: Aggregation
|
||||
|
||||
Scenario: `max()` over strings
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['a', 'b', 'B', null, 'abc', 'abc1'] AS i
|
||||
RETURN max(i)
|
||||
"""
|
||||
Then the result should be:
|
||||
| max(i) |
|
||||
| 'b' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `min()` over strings
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['a', 'b', 'B', null, 'abc', 'abc1'] AS i
|
||||
RETURN min(i)
|
||||
"""
|
||||
Then the result should be:
|
||||
| min(i) |
|
||||
| 'B' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `max()` over integers
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 2, 0, null, -1] AS x
|
||||
RETURN max(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| max(x) |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `min()` over integers
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 2, 0, null, -1] AS x
|
||||
RETURN min(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| min(x) |
|
||||
| -1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `max()` over floats
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1.0, 2.0, 0.5, null] AS x
|
||||
RETURN max(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| max(x) |
|
||||
| 2.0 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `min()` over floats
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1.0, 2.0, 0.5, null] AS x
|
||||
RETURN min(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| min(x) |
|
||||
| 0.5 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `max()` over mixed numeric values
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 2.0, 5, null, 3.2, 0.1] AS x
|
||||
RETURN max(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| max(x) |
|
||||
| 5 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `min()` over mixed numeric values
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 2.0, 5, null, 3.2, 0.1] AS x
|
||||
RETURN min(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| min(x) |
|
||||
| 0.1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `max()` over mixed values
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 'a', null, [1, 2], 0.2, 'b'] AS x
|
||||
RETURN max(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| max(x) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `min()` over mixed values
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 'a', null, [1, 2], 0.2, 'b'] AS x
|
||||
RETURN min(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| min(x) |
|
||||
| [1, 2] |
|
||||
And no side effects
|
||||
|
||||
Scenario: `max()` over list values
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [[1], [2], [2, 1]] AS x
|
||||
RETURN max(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| max(x) |
|
||||
| [2, 1] |
|
||||
And no side effects
|
||||
|
||||
Scenario: `min()` over list values
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [[1], [2], [2, 1]] AS x
|
||||
RETURN min(x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| min(x) |
|
||||
| [1] |
|
||||
And no side effects
|
@ -0,0 +1,473 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: AggregationAcceptance
|
||||
|
||||
Scenario: Support multiple divisions in aggregate function
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(0, 7250) AS i
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN count(n) / 60 / 60 AS count
|
||||
"""
|
||||
Then the result should be:
|
||||
| count |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Support column renaming for aggregates as well
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(0, 10) AS i
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
RETURN count(*) AS columnName
|
||||
"""
|
||||
Then the result should be:
|
||||
| columnName |
|
||||
| 11 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aggregates inside normal functions
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(0, 10) AS i
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN size(collect(a))
|
||||
"""
|
||||
Then the result should be:
|
||||
| size(collect(a)) |
|
||||
| 11 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handle aggregates inside non-aggregate expressions
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a {name: 'Andres'})<-[:FATHER]-(child)
|
||||
RETURN {foo: a.name='Andres', kids: collect(child.name)}
|
||||
"""
|
||||
Then the result should be:
|
||||
| {foo: a.name='Andres', kids: collect(child.name)} |
|
||||
And no side effects
|
||||
|
||||
Scenario: Count nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:L), (b1), (b2)
|
||||
CREATE (a)-[:A]->(b1), (a)-[:A]->(b2)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:L)-[rel]->(b)
|
||||
RETURN a, count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | count(*) |
|
||||
| (:L) | 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Sort on aggregate function and normal property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({division: 'Sweden'})
|
||||
CREATE ({division: 'Germany'})
|
||||
CREATE ({division: 'England'})
|
||||
CREATE ({division: 'Sweden'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.division, count(*)
|
||||
ORDER BY count(*) DESC, n.division ASC
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| n.division | count(*) |
|
||||
| 'Sweden' | 2 |
|
||||
| 'England' | 1 |
|
||||
| 'Germany' | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aggregate on property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({x: 33})
|
||||
CREATE ({x: 33})
|
||||
CREATE ({x: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.x, count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.x | count(*) |
|
||||
| 42 | 1 |
|
||||
| 33 | 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Count non-null values
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({y: 'a', x: 33})
|
||||
CREATE ({y: 'a'})
|
||||
CREATE ({y: 'b', x: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.y, count(n.x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.y | count(n.x) |
|
||||
| 'a' | 1 |
|
||||
| 'b' | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Sum non-null values
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({y: 'a', x: 33})
|
||||
CREATE ({y: 'a'})
|
||||
CREATE ({y: 'a', x: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.y, sum(n.x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.y | sum(n.x) |
|
||||
| 'a' | 75 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handle aggregation on functions
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:L), (b1), (b2)
|
||||
CREATE (a)-[:A]->(b1), (a)-[:A]->(b2)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p=(a:L)-[*]->(b)
|
||||
RETURN b, avg(length(p))
|
||||
"""
|
||||
Then the result should be:
|
||||
| b | avg(length(p)) |
|
||||
| () | 1.0 |
|
||||
| () | 1.0 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Distinct on unbound node
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a)
|
||||
RETURN count(DISTINCT a)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(DISTINCT a) |
|
||||
| 0 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Distinct on null
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN count(DISTINCT a.foo)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(DISTINCT a.foo) |
|
||||
| 0 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Collect distinct nulls
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [null, null] AS x
|
||||
RETURN collect(DISTINCT x) AS c
|
||||
"""
|
||||
Then the result should be:
|
||||
| c |
|
||||
| [] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Collect distinct values mixed with nulls
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [null, 1, null] AS x
|
||||
RETURN collect(DISTINCT x) AS c
|
||||
"""
|
||||
Then the result should be:
|
||||
| c |
|
||||
| [1] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aggregate on list values
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({color: ['red']})
|
||||
CREATE ({color: ['blue']})
|
||||
CREATE ({color: ['red']})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN DISTINCT a.color, count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.color | count(*) |
|
||||
| ['red'] | 2 |
|
||||
| ['blue'] | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aggregates in aggregates
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN count(count(*))
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NestedAggregation
|
||||
|
||||
Scenario: Aggregates with arithmetics
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
RETURN count(*) * 10 AS c
|
||||
"""
|
||||
Then the result should be:
|
||||
| c |
|
||||
| 10 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aggregates ordered by arithmetics
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A), (:X), (:X)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:X)
|
||||
RETURN count(a) * 10 + count(b) * 5 AS x
|
||||
ORDER BY x
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| x |
|
||||
| 30 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Multiple aggregates on same variable
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN count(n), collect(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(n) | collect(n) |
|
||||
| 1 | [()] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Simple counting of nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(1, 100) AS i
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 100 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aggregation of named paths
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B), (c:C), (d:D), (e:E), (f:F)
|
||||
CREATE (a)-[:R]->(b)
|
||||
CREATE (c)-[:R]->(d)
|
||||
CREATE (d)-[:R]->(e)
|
||||
CREATE (e)-[:R]->(f)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (a)-[*]->(b)
|
||||
RETURN collect(nodes(p)) AS paths, length(p) AS l
|
||||
ORDER BY l
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| paths | l |
|
||||
| [[(:A), (:B)], [(:C), (:D)], [(:D), (:E)], [(:E), (:F)]] | 1 |
|
||||
| [[(:C), (:D), (:E)], [(:D), (:E), (:F)]] | 2 |
|
||||
| [[(:C), (:D), (:E), (:F)]] | 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aggregation with `min()`
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:T {name: 'a'}), (b:T {name: 'b'}), (c:T {name: 'c'})
|
||||
CREATE (a)-[:R]->(b)
|
||||
CREATE (a)-[:R]->(c)
|
||||
CREATE (c)-[:R]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (a:T {name: 'a'})-[:R*]->(other:T)
|
||||
WHERE other <> a
|
||||
WITH a, other, min(length(p)) AS len
|
||||
RETURN a.name AS name, collect(other.name) AS others, len
|
||||
"""
|
||||
Then the result should be (ignoring element order for lists):
|
||||
| name | others | len |
|
||||
| 'a' | ['c', 'b'] | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handle subexpression in aggregation also occurring as standalone expression with nested aggregation in a literal map
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A), (:B {prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
RETURN coalesce(a.prop, b.prop) AS foo,
|
||||
b.prop AS bar,
|
||||
{y: count(b)} AS baz
|
||||
"""
|
||||
Then the result should be:
|
||||
| foo | bar | baz |
|
||||
| 42 | 42 | {y: 1} |
|
||||
And no side effects
|
||||
|
||||
Scenario: Projection during aggregation in WITH before MERGE and after WITH with predicate
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A {prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [42] AS props
|
||||
WITH props WHERE props > 32
|
||||
WITH DISTINCT props AS p
|
||||
MERGE (a:A {prop: p})
|
||||
RETURN a.prop AS prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| prop |
|
||||
| 42 |
|
||||
And no side effects
|
||||
|
||||
Scenario: No overflow during summation
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND range(1000000, 2000000) AS i
|
||||
WITH i
|
||||
LIMIT 3000
|
||||
RETURN sum(i)
|
||||
"""
|
||||
Then the result should be:
|
||||
| sum(i) |
|
||||
| 3004498500 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Counting with loops
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a), (a)-[:R]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]-()
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `max()` should aggregate strings
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['a', 'b', 'B', null, 'abc', 'abc1'] AS i
|
||||
RETURN max(i)
|
||||
"""
|
||||
Then the result should be:
|
||||
| max(i) |
|
||||
| 'b' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `min()` should aggregate strings
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['a', 'b', 'B', null, 'abc', 'abc1'] AS i
|
||||
RETURN min(i)
|
||||
"""
|
||||
Then the result should be:
|
||||
| min(i) |
|
||||
| 'B' |
|
||||
And no side effects
|
@ -0,0 +1,68 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: ColumnNameAcceptance
|
||||
|
||||
Background:
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
|
||||
Scenario: Keeping used expression 1
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN cOuNt( * )
|
||||
"""
|
||||
Then the result should be:
|
||||
| cOuNt( * ) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Keeping used expression 2
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-->(b)
|
||||
RETURN nOdEs( p )
|
||||
"""
|
||||
Then the result should be:
|
||||
| nOdEs( p ) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Keeping used expression 3
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-->(b)
|
||||
RETURN coUnt( dIstInct p )
|
||||
"""
|
||||
Then the result should be:
|
||||
| coUnt( dIstInct p ) |
|
||||
| 0 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Keeping used expression 4
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-->(b)
|
||||
RETURN aVg( n.aGe )
|
||||
"""
|
||||
Then the result should be:
|
||||
| aVg( n.aGe ) |
|
||||
| null |
|
||||
And no side effects
|
@ -0,0 +1,87 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: Comparability
|
||||
|
||||
Scenario: Comparing strings and integers using > in an AND'd predicate
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (root:Root)-[:T]->(:Child {id: 0}),
|
||||
(root)-[:T]->(:Child {id: 'xx'}),
|
||||
(root)-[:T]->(:Child)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (:Root)-->(i:Child)
|
||||
WHERE exists(i.id) AND i.id > 'x'
|
||||
RETURN i.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| i.id |
|
||||
| 'xx' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Comparing strings and integers using > in a OR'd predicate
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (root:Root)-[:T]->(:Child {id: 0}),
|
||||
(root)-[:T]->(:Child {id: 'xx'}),
|
||||
(root)-[:T]->(:Child)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (:Root)-->(i:Child)
|
||||
WHERE NOT exists(i.id) OR i.id > 'x'
|
||||
RETURN i.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| i.id |
|
||||
| 'xx' |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario Outline: Comparing across types yields null, except numbers
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-[r]->()
|
||||
WITH [n, r, p, '', 1, 3.14, true, null, [], {}] AS types
|
||||
UNWIND range(0, size(types) - 1) AS i
|
||||
UNWIND range(0, size(types) - 1) AS j
|
||||
WITH types[i] AS lhs, types[j] AS rhs
|
||||
WHERE i <> j
|
||||
WITH lhs, rhs, lhs <operator> rhs AS result
|
||||
WHERE result
|
||||
RETURN lhs, rhs
|
||||
"""
|
||||
Then the result should be:
|
||||
| lhs | rhs |
|
||||
| <lhs> | <rhs> |
|
||||
And no side effects
|
||||
|
||||
Examples:
|
||||
| operator | lhs | rhs |
|
||||
| < | 1 | 3.14 |
|
||||
| <= | 1 | 3.14 |
|
||||
| >= | 3.14 | 1 |
|
||||
| > | 3.14 | 1 |
|
@ -0,0 +1,208 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: ComparisonOperatorAcceptance
|
||||
|
||||
Scenario: Handling numerical ranges 1
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND [1, 2, 3] AS i
|
||||
CREATE ({value: i})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 1 < n.value < 3
|
||||
RETURN n.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.value |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling numerical ranges 2
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND [1, 2, 3] AS i
|
||||
CREATE ({value: i})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 1 < n.value <= 3
|
||||
RETURN n.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.value |
|
||||
| 2 |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling numerical ranges 3
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND [1, 2, 3] AS i
|
||||
CREATE ({value: i})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 1 <= n.value < 3
|
||||
RETURN n.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.value |
|
||||
| 1 |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling numerical ranges 4
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND [1, 2, 3] AS i
|
||||
CREATE ({value: i})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 1 <= n.value <= 3
|
||||
RETURN n.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.value |
|
||||
| 1 |
|
||||
| 2 |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling string ranges 1
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND ['a', 'b', 'c'] AS c
|
||||
CREATE ({value: c})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 'a' < n.value < 'c'
|
||||
RETURN n.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.value |
|
||||
| 'b' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling string ranges 2
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND ['a', 'b', 'c'] AS c
|
||||
CREATE ({value: c})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 'a' < n.value <= 'c'
|
||||
RETURN n.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.value |
|
||||
| 'b' |
|
||||
| 'c' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling string ranges 3
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND ['a', 'b', 'c'] AS c
|
||||
CREATE ({value: c})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 'a' <= n.value < 'c'
|
||||
RETURN n.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.value |
|
||||
| 'a' |
|
||||
| 'b' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling string ranges 4
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND ['a', 'b', 'c'] AS c
|
||||
CREATE ({value: c})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 'a' <= n.value <= 'c'
|
||||
RETURN n.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.value |
|
||||
| 'a' |
|
||||
| 'b' |
|
||||
| 'c' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling empty range
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({value: 3})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 10 < n.value <= 3
|
||||
RETURN n.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.value |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling long chains of operators
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {prop1: 3, prop2: 4})
|
||||
CREATE (b:B {prop1: 4, prop2: 5})
|
||||
CREATE (c:C {prop1: 4, prop2: 4})
|
||||
CREATE (a)-[:R]->(b)
|
||||
CREATE (b)-[:R]->(c)
|
||||
CREATE (c)-[:R]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)-->(m)
|
||||
WHERE n.prop1 < m.prop1 = n.prop2 <> m.prop2
|
||||
RETURN labels(m)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(m) |
|
||||
| ['B'] |
|
||||
And no side effects
|
@ -0,0 +1,71 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: Create
|
||||
|
||||
Scenario: Creating a node
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
|
||||
Scenario: Creating two nodes
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (), ()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
|
||||
Scenario: Creating two nodes and a relationship
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()-[:TYPE]->()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Creating a node with a label
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (:Label)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 1 |
|
||||
|
||||
Scenario: Creating a node with a property
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ({created: true})
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
@ -0,0 +1,522 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: CreateAcceptance
|
||||
|
||||
Scenario: Create a single node with multiple labels
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (:A:B:C:D)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 4 |
|
||||
|
||||
Scenario: Combine MATCH and CREATE
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (), ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
CREATE ()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
|
||||
Scenario: Combine MATCH, WITH and CREATE
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (), ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
CREATE ()
|
||||
WITH *
|
||||
MATCH ()
|
||||
CREATE ()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 10 |
|
||||
|
||||
Scenario: Newly-created nodes not visible to preceding MATCH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
CREATE ()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
|
||||
Scenario: Create a single node with properties
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n {prop: 'foo'})
|
||||
RETURN n.prop AS p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| 'foo' |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Creating a node with null properties should not return those properties
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n {id: 12, property: null})
|
||||
RETURN n.id AS id
|
||||
"""
|
||||
Then the result should be:
|
||||
| id |
|
||||
| 12 |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Creating a relationship with null properties should not return those properties
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()-[r:X {id: 12, property: null}]->()
|
||||
RETURN r.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| r.id |
|
||||
| 12 |
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Create a simple pattern
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()-[:R]->()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Create a self loop
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (root:R)-[:LINK]->(root)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +relationships | 1 |
|
||||
| +labels | 1 |
|
||||
|
||||
Scenario: Create a self loop using MATCH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:R)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (root:R)
|
||||
CREATE (root)-[:LINK]->(root)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Create nodes and relationships
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a), (b),
|
||||
(a)-[:R]->(b)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Create a relationship with a property
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()-[:R {prop: 42}]->()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Create a relationship with the correct direction
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:X)
|
||||
CREATE (:Y)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (x:X), (y:Y)
|
||||
CREATE (x)<-[:TYPE]-(y)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH (x:X)<-[:TYPE]-(y:Y)
|
||||
RETURN x, y
|
||||
"""
|
||||
Then the result should be:
|
||||
| x | y |
|
||||
| (:X) | (:Y) |
|
||||
|
||||
Scenario: Create a relationship and an end node from a matched starting node
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Begin)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (x:Begin)
|
||||
CREATE (x)-[:TYPE]->(:End)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +relationships | 1 |
|
||||
| +labels | 1 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH (x:Begin)-[:TYPE]->()
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| (:Begin) |
|
||||
|
||||
Scenario: Create a single node after a WITH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (), ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
CREATE ()
|
||||
WITH *
|
||||
CREATE ()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 4 |
|
||||
|
||||
Scenario: Create a relationship with a reversed direction
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (:A)<-[:R]-(:B)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
| +labels | 2 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH (a:A)<-[:R]-(b:B)
|
||||
RETURN a, b
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
| (:A) | (:B) |
|
||||
|
||||
Scenario: Create a pattern with multiple hops
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (:A)-[:R]->(:B)-[:R]->(:C)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 3 |
|
||||
| +relationships | 2 |
|
||||
| +labels | 3 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH (a:A)-[:R]->(b:B)-[:R]->(c:C)
|
||||
RETURN a, b, c
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b | c |
|
||||
| (:A) | (:B) | (:C) |
|
||||
|
||||
Scenario: Create a pattern with multiple hops in the reverse direction
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (:A)<-[:R]-(:B)<-[:R]-(:C)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 3 |
|
||||
| +relationships | 2 |
|
||||
| +labels | 3 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH (a)<-[:R]-(b)<-[:R]-(c)
|
||||
RETURN a, b, c
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b | c |
|
||||
| (:A) | (:B) | (:C) |
|
||||
|
||||
Scenario: Create a pattern with multiple hops in varying directions
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (:A)-[:R]->(:B)<-[:R]-(:C)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 3 |
|
||||
| +relationships | 2 |
|
||||
| +labels | 3 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH (a:A)-[r1:R]->(b:B)<-[r2:R]-(c:C)
|
||||
RETURN a, b, c
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b | c |
|
||||
| (:A) | (:B) | (:C) |
|
||||
|
||||
Scenario: Create a pattern with multiple hops with multiple types and varying directions
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()-[:R1]->()<-[:R2]-()-[:R3]->()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 4 |
|
||||
| +relationships | 3 |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r1:R1]->()<-[r2:R2]-()-[r3:R3]->()
|
||||
RETURN r1, r2, r3
|
||||
"""
|
||||
Then the result should be:
|
||||
| r1 | r2 | r3 |
|
||||
| [:R1] | [:R2] | [:R3] |
|
||||
|
||||
Scenario: Nodes are not created when aliases are applied to variable names
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({foo: 1})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
MATCH (m)
|
||||
WITH n AS a, m AS b
|
||||
CREATE (a)-[:T]->(b)
|
||||
RETURN a, b
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
| ({foo: 1}) | ({foo: 1}) |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Only a single node is created when an alias is applied to a variable name
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:X)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH n AS a
|
||||
CREATE (a)-[:T]->()
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:X) |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Nodes are not created when aliases are applied to variable names multiple times
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({foo: 'A'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
MATCH (m)
|
||||
WITH n AS a, m AS b
|
||||
CREATE (a)-[:T]->(b)
|
||||
WITH a AS x, b AS y
|
||||
CREATE (x)-[:T]->(y)
|
||||
RETURN x, y
|
||||
"""
|
||||
Then the result should be:
|
||||
| x | y |
|
||||
| ({foo: 'A'}) | ({foo: 'A'}) |
|
||||
And the side effects should be:
|
||||
| +relationships | 2 |
|
||||
|
||||
Scenario: Only a single node is created when an alias is applied to a variable name multiple times
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({foo: 5})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH n AS a
|
||||
CREATE (a)-[:T]->()
|
||||
WITH a AS x
|
||||
CREATE (x)-[:T]->()
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| ({foo: 5}) |
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 2 |
|
||||
|
||||
Scenario: A bound node should be recognized after projection with WITH + WITH
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a)
|
||||
WITH a
|
||||
WITH *
|
||||
CREATE (b)
|
||||
CREATE (a)<-[:T]-(b)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: A bound node should be recognized after projection with WITH + UNWIND
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a)
|
||||
WITH a
|
||||
UNWIND [0] AS i
|
||||
CREATE (b)
|
||||
CREATE (a)<-[:T]-(b)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: A bound node should be recognized after projection with WITH + MERGE node
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a)
|
||||
WITH a
|
||||
MERGE ()
|
||||
CREATE (b)
|
||||
CREATE (a)<-[:T]-(b)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: A bound node should be recognized after projection with WITH + MERGE pattern
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a)
|
||||
WITH a
|
||||
MERGE (x)
|
||||
MERGE (y)
|
||||
MERGE (x)-[:T]->(y)
|
||||
CREATE (b)
|
||||
CREATE (a)<-[:T]-(b)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 2 |
|
||||
|
||||
Scenario: Fail when trying to create using an undirected relationship pattern
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ({id: 2})-[r:KNOWS]-({id: 1})
|
||||
RETURN r
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: RequiresDirectedRelationship
|
||||
|
||||
Scenario: Creating a pattern with multiple hops and changing directions
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (:A)<-[:R1]-(:B)-[:R2]->(:C)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 3 |
|
||||
| +relationships | 2 |
|
||||
| +labels | 3 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH (a:A)<-[r1:R1]-(b:B)-[r2:R2]->(c:C)
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b | c | r1 | r2 |
|
||||
| (:A) | (:B) | (:C) | [:R1] | [:R2] |
|
@ -0,0 +1,375 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: DeleteAcceptance
|
||||
|
||||
Scenario: Delete nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
DELETE n
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -nodes | 1 |
|
||||
|
||||
Scenario: Detach delete node
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
DETACH DELETE n
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -nodes | 1 |
|
||||
|
||||
Scenario: Delete relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(0, 2) AS i
|
||||
CREATE ()-[:R]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]-()
|
||||
DELETE r
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -relationships | 3 |
|
||||
|
||||
Scenario: Deleting connected nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (x:X)
|
||||
CREATE (x)-[:R]->()
|
||||
CREATE (x)-[:R]->()
|
||||
CREATE (x)-[:R]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:X)
|
||||
DELETE n
|
||||
"""
|
||||
Then a ConstraintVerificationFailed should be raised at runtime: DeleteConnectedNode
|
||||
|
||||
Scenario: Detach deleting connected nodes and relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (x:X)
|
||||
CREATE (x)-[:R]->()
|
||||
CREATE (x)-[:R]->()
|
||||
CREATE (x)-[:R]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:X)
|
||||
DETACH DELETE n
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -nodes | 1 |
|
||||
| -relationships | 3 |
|
||||
| -labels | 1 |
|
||||
|
||||
Scenario: Detach deleting paths
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (x:X), (n1), (n2), (n3)
|
||||
CREATE (x)-[:R]->(n1)
|
||||
CREATE (n1)-[:R]->(n2)
|
||||
CREATE (n2)-[:R]->(n3)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (:X)-->()-->()-->()
|
||||
DETACH DELETE p
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -nodes | 4 |
|
||||
| -relationships | 3 |
|
||||
| -labels | 1 |
|
||||
|
||||
Scenario: Undirected expand followed by delete and count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:R]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[r]-(b)
|
||||
DELETE r, a, b
|
||||
RETURN count(*) AS c
|
||||
"""
|
||||
Then the result should be:
|
||||
| c |
|
||||
| 2 |
|
||||
And the side effects should be:
|
||||
| -nodes | 2 |
|
||||
| -relationships | 1 |
|
||||
|
||||
Scenario: Undirected variable length expand followed by delete and count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n1), (n2), (n3)
|
||||
CREATE (n1)-[:R]->(n2)
|
||||
CREATE (n2)-[:R]->(n3)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[*]-(b)
|
||||
DETACH DELETE a, b
|
||||
RETURN count(*) AS c
|
||||
"""
|
||||
Then the result should be:
|
||||
| c |
|
||||
| 6 |
|
||||
And the side effects should be:
|
||||
| -nodes | 3 |
|
||||
| -relationships | 2 |
|
||||
|
||||
Scenario: Create and delete in same query
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
CREATE (n)
|
||||
DELETE n
|
||||
"""
|
||||
Then the result should be empty
|
||||
And no side effects
|
||||
|
||||
Scenario: Delete optionally matched relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
OPTIONAL MATCH (n)-[r]-()
|
||||
DELETE n, r
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -nodes | 1 |
|
||||
|
||||
Scenario: Delete on null node
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (n)
|
||||
DELETE n
|
||||
"""
|
||||
Then the result should be empty
|
||||
And no side effects
|
||||
|
||||
Scenario: Detach delete on null node
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (n)
|
||||
DETACH DELETE n
|
||||
"""
|
||||
Then the result should be empty
|
||||
And no side effects
|
||||
|
||||
Scenario: Delete on null path
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH p = ()-->()
|
||||
DETACH DELETE p
|
||||
"""
|
||||
Then the result should be empty
|
||||
And no side effects
|
||||
|
||||
Scenario: Delete node from a list
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (u:User)
|
||||
CREATE (u)-[:FRIEND]->()
|
||||
CREATE (u)-[:FRIEND]->()
|
||||
CREATE (u)-[:FRIEND]->()
|
||||
CREATE (u)-[:FRIEND]->()
|
||||
"""
|
||||
And parameters are:
|
||||
| friendIndex | 1 |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (:User)-[:FRIEND]->(n)
|
||||
WITH collect(n) AS friends
|
||||
DETACH DELETE friends[$friendIndex]
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -nodes | 1 |
|
||||
| -relationships | 1 |
|
||||
|
||||
Scenario: Delete relationship from a list
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (u:User)
|
||||
CREATE (u)-[:FRIEND]->()
|
||||
CREATE (u)-[:FRIEND]->()
|
||||
CREATE (u)-[:FRIEND]->()
|
||||
CREATE (u)-[:FRIEND]->()
|
||||
"""
|
||||
And parameters are:
|
||||
| friendIndex | 1 |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (:User)-[r:FRIEND]->()
|
||||
WITH collect(r) AS friendships
|
||||
DETACH DELETE friendships[$friendIndex]
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -relationships | 1 |
|
||||
|
||||
Scenario: Delete nodes from a map
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:User), (:User)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (u:User)
|
||||
WITH {key: u} AS nodes
|
||||
DELETE nodes.key
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -nodes | 2 |
|
||||
| -labels | 1 |
|
||||
|
||||
Scenario: Delete relationships from a map
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:User), (b:User)
|
||||
CREATE (a)-[:R]->(b)
|
||||
CREATE (b)-[:R]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (:User)-[r]->(:User)
|
||||
WITH {key: r} AS rels
|
||||
DELETE rels.key
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -relationships | 2 |
|
||||
|
||||
Scenario: Detach delete nodes from nested map/list
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:User), (b:User)
|
||||
CREATE (a)-[:R]->(b)
|
||||
CREATE (b)-[:R]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (u:User)
|
||||
WITH {key: collect(u)} AS nodeMap
|
||||
DETACH DELETE nodeMap.key[0]
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -nodes | 1 |
|
||||
| -relationships | 2 |
|
||||
|
||||
Scenario: Delete relationships from nested map/list
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:User), (b:User)
|
||||
CREATE (a)-[:R]->(b)
|
||||
CREATE (b)-[:R]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (:User)-[r]->(:User)
|
||||
WITH {key: {key: collect(r)}} AS rels
|
||||
DELETE rels.key.key[0]
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -relationships | 1 |
|
||||
|
||||
Scenario: Delete paths from nested map/list
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:User), (b:User)
|
||||
CREATE (a)-[:R]->(b)
|
||||
CREATE (b)-[:R]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (:User)-[r]->(:User)
|
||||
WITH {key: collect(p)} AS pathColls
|
||||
DELETE pathColls.key[0], pathColls.key[1]
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -nodes | 2 |
|
||||
| -relationships | 2 |
|
||||
| -labels | 1 |
|
||||
|
||||
Scenario: Delete relationship with bidirectional matching
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T {id: 42}]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = ()-[r:T]-()
|
||||
WHERE r.id = 42
|
||||
DELETE r
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| -relationships | 1 |
|
||||
| -properties | 1 |
|
@ -0,0 +1,111 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: EqualsAcceptance
|
||||
|
||||
Scenario: Number-typed integer comparison
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 0})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
WITH collect([0, 0.0]) AS numbers
|
||||
UNWIND numbers AS arr
|
||||
WITH arr[0] AS expected
|
||||
MATCH (n) WHERE toInteger(n.id) = expected
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| ({id: 0}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Number-typed float comparison
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 0})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
WITH collect([0.5, 0]) AS numbers
|
||||
UNWIND numbers AS arr
|
||||
WITH arr[0] AS expected
|
||||
MATCH (n) WHERE toInteger(n.id) = expected
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
And no side effects
|
||||
|
||||
Scenario: Any-typed string comparison
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 0})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
WITH collect(['0', 0]) AS things
|
||||
UNWIND things AS arr
|
||||
WITH arr[0] AS expected
|
||||
MATCH (n) WHERE toInteger(n.id) = expected
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
And no side effects
|
||||
|
||||
Scenario: Comparing nodes to nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH a
|
||||
MATCH (b)
|
||||
WHERE a = b
|
||||
RETURN count(b)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(b) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Comparing relationships to relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[a]->()
|
||||
WITH a
|
||||
MATCH ()-[b]->()
|
||||
WHERE a = b
|
||||
RETURN count(b)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(b) |
|
||||
| 1 |
|
||||
And no side effects
|
@ -0,0 +1,248 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: ExpressionAcceptance
|
||||
|
||||
Background:
|
||||
Given any graph
|
||||
|
||||
Scenario: IN should work with nested list subscripting
|
||||
When executing query:
|
||||
"""
|
||||
WITH [[1, 2, 3]] AS list
|
||||
RETURN 3 IN list[0] AS r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| true |
|
||||
And no side effects
|
||||
|
||||
Scenario: IN should work with nested literal list subscripting
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 3 IN [[1, 2, 3]][0] AS r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| true |
|
||||
And no side effects
|
||||
|
||||
Scenario: IN should work with list slices
|
||||
When executing query:
|
||||
"""
|
||||
WITH [1, 2, 3] AS list
|
||||
RETURN 3 IN list[0..1] AS r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| false |
|
||||
And no side effects
|
||||
|
||||
Scenario: IN should work with literal list slices
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 3 IN [1, 2, 3][0..1] AS r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| false |
|
||||
And no side effects
|
||||
|
||||
Scenario: Execute n[0]
|
||||
When executing query:
|
||||
"""
|
||||
RETURN [1, 2, 3][0] AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Execute n['name'] in read queries
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'Apa'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {name: 'Apa'})
|
||||
RETURN n['nam' + 'e'] AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| 'Apa' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Execute n['name'] in update queries
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n {name: 'Apa'})
|
||||
RETURN n['nam' + 'e'] AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| 'Apa' |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Use dynamic property lookup based on parameters when there is no type information
|
||||
And parameters are:
|
||||
| expr | {name: 'Apa'} |
|
||||
| idx | 'name' |
|
||||
When executing query:
|
||||
"""
|
||||
WITH $expr AS expr, $idx AS idx
|
||||
RETURN expr[idx] AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| 'Apa' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Use dynamic property lookup based on parameters when there is lhs type information
|
||||
And parameters are:
|
||||
| idx | 'name' |
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n {name: 'Apa'})
|
||||
RETURN n[$idx] AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| 'Apa' |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Use dynamic property lookup based on parameters when there is rhs type information
|
||||
And parameters are:
|
||||
| expr | {name: 'Apa'} |
|
||||
| idx | 'name' |
|
||||
When executing query:
|
||||
"""
|
||||
WITH $expr AS expr, $idx AS idx
|
||||
RETURN expr[toString(idx)] AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| 'Apa' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Use collection lookup based on parameters when there is no type information
|
||||
And parameters are:
|
||||
| expr | ['Apa'] |
|
||||
| idx | 0 |
|
||||
When executing query:
|
||||
"""
|
||||
WITH $expr AS expr, $idx AS idx
|
||||
RETURN expr[idx] AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| 'Apa' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Use collection lookup based on parameters when there is lhs type information
|
||||
And parameters are:
|
||||
| idx | 0 |
|
||||
When executing query:
|
||||
"""
|
||||
WITH ['Apa'] AS expr
|
||||
RETURN expr[$idx] AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| 'Apa' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Use collection lookup based on parameters when there is rhs type information
|
||||
And parameters are:
|
||||
| expr | ['Apa'] |
|
||||
| idx | 0 |
|
||||
When executing query:
|
||||
"""
|
||||
WITH $expr AS expr, $idx AS idx
|
||||
RETURN expr[toInteger(idx)] AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| 'Apa' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Fail at runtime when attempting to index with an Int into a Map
|
||||
And parameters are:
|
||||
| expr | {name: 'Apa'} |
|
||||
| idx | 0 |
|
||||
When executing query:
|
||||
"""
|
||||
WITH $expr AS expr, $idx AS idx
|
||||
RETURN expr[idx]
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: MapElementAccessByNonString
|
||||
|
||||
Scenario: Fail at runtime when trying to index into a map with a non-string
|
||||
And parameters are:
|
||||
| expr | {name: 'Apa'} |
|
||||
| idx | 12.3 |
|
||||
When executing query:
|
||||
"""
|
||||
WITH $expr AS expr, $idx AS idx
|
||||
RETURN expr[idx]
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: MapElementAccessByNonString
|
||||
|
||||
Scenario: Fail at runtime when attempting to index with a String into a Collection
|
||||
And parameters are:
|
||||
| expr | ['Apa'] |
|
||||
| idx | 'name' |
|
||||
When executing query:
|
||||
"""
|
||||
WITH $expr AS expr, $idx AS idx
|
||||
RETURN expr[idx]
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: ListElementAccessByNonInteger
|
||||
|
||||
Scenario: Fail at runtime when trying to index into a list with a list
|
||||
And parameters are:
|
||||
| expr | ['Apa'] |
|
||||
| idx | ['Apa'] |
|
||||
When executing query:
|
||||
"""
|
||||
WITH $expr AS expr, $idx AS idx
|
||||
RETURN expr[idx]
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: ListElementAccessByNonInteger
|
||||
|
||||
Scenario: Fail at compile time when attempting to index with a non-integer into a list
|
||||
When executing query:
|
||||
"""
|
||||
WITH [1, 2, 3, 4, 5] AS list, 3.14 AS idx
|
||||
RETURN list[idx]
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Fail at runtime when trying to index something which is not a map or collection
|
||||
And parameters are:
|
||||
| expr | 100 |
|
||||
| idx | 0 |
|
||||
When executing query:
|
||||
"""
|
||||
WITH $expr AS expr, $idx AS idx
|
||||
RETURN expr[idx]
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: InvalidElementAccess
|
@ -0,0 +1,485 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: FunctionsAcceptance
|
||||
|
||||
Scenario: Run coalesce
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'Emil Eifrem', title: 'CEO'}), ({name: 'Nobody'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN coalesce(a.title, a.name)
|
||||
"""
|
||||
Then the result should be:
|
||||
| coalesce(a.title, a.name) |
|
||||
| 'CEO' |
|
||||
| 'Nobody' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Functions should return null if they get path containing unbound
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH null AS a
|
||||
OPTIONAL MATCH p = (a)-[r]->()
|
||||
RETURN length(nodes(p)), type(r), nodes(p), relationships(p)
|
||||
"""
|
||||
Then the result should be:
|
||||
| length(nodes(p)) | type(r) | nodes(p) | relationships(p) |
|
||||
| null | null | null | null |
|
||||
And no side effects
|
||||
|
||||
Scenario: `split()`
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND split('one1two', '1') AS item
|
||||
RETURN count(item) AS item
|
||||
"""
|
||||
Then the result should be:
|
||||
| item |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `properties()` on a node
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n:Person {name: 'Popeye', level: 9001})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Person)
|
||||
RETURN properties(p) AS m
|
||||
"""
|
||||
Then the result should be:
|
||||
| m |
|
||||
| {name: 'Popeye', level: 9001} |
|
||||
And no side effects
|
||||
|
||||
Scenario: `properties()` on a relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n)-[:R {name: 'Popeye', level: 9001}]->(n)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r:R]->()
|
||||
RETURN properties(r) AS m
|
||||
"""
|
||||
Then the result should be:
|
||||
| m |
|
||||
| {name: 'Popeye', level: 9001} |
|
||||
And no side effects
|
||||
|
||||
Scenario: `properties()` on a map
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN properties({name: 'Popeye', level: 9001}) AS m
|
||||
"""
|
||||
Then the result should be:
|
||||
| m |
|
||||
| {name: 'Popeye', level: 9001} |
|
||||
And no side effects
|
||||
|
||||
Scenario: `properties()` failing on an integer literal
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN properties(1)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: `properties()` failing on a string literal
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN properties('Cypher')
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: `properties()` failing on a list of booleans
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN properties([true, false])
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: `properties()` on null
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN properties(null)
|
||||
"""
|
||||
Then the result should be:
|
||||
| properties(null) |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: `reverse()`
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN reverse('raksO')
|
||||
"""
|
||||
Then the result should be:
|
||||
| reverse('raksO') |
|
||||
| 'Oskar' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `exists()` with dynamic property lookup
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {prop: 'foo'}),
|
||||
(:Person)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:Person)
|
||||
WHERE exists(n['prop'])
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:Person {prop: 'foo'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario Outline: `exists()` with literal maps
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH <map> AS map
|
||||
RETURN exists(map.name) AS result
|
||||
"""
|
||||
Then the result should be:
|
||||
| result |
|
||||
| <result> |
|
||||
And no side effects
|
||||
|
||||
Examples:
|
||||
| map | result |
|
||||
| {name: 'Mats', name2: 'Pontus'} | true |
|
||||
| {name: null} | false |
|
||||
| {notName: 0, notName2: null} | false |
|
||||
|
||||
Scenario Outline: IS NOT NULL with literal maps
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH <map> AS map
|
||||
RETURN map.name IS NOT NULL
|
||||
"""
|
||||
Then the result should be:
|
||||
| map.name IS NOT NULL |
|
||||
| <result> |
|
||||
And no side effects
|
||||
|
||||
Examples:
|
||||
| map | result |
|
||||
| {name: 'Mats', name2: 'Pontus'} | true |
|
||||
| {name: null} | false |
|
||||
| {notName: 0, notName2: null} | false |
|
||||
|
||||
Scenario Outline: `percentileDisc()`
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({prop: 10.0}),
|
||||
({prop: 20.0}),
|
||||
({prop: 30.0})
|
||||
"""
|
||||
And parameters are:
|
||||
| percentile | <p> |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN percentileDisc(n.prop, $percentile) AS p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| <result> |
|
||||
And no side effects
|
||||
|
||||
Examples:
|
||||
| p | result |
|
||||
| 0.0 | 10.0 |
|
||||
| 0.5 | 20.0 |
|
||||
| 1.0 | 30.0 |
|
||||
|
||||
Scenario Outline: `percentileCont()`
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({prop: 10.0}),
|
||||
({prop: 20.0}),
|
||||
({prop: 30.0})
|
||||
"""
|
||||
And parameters are:
|
||||
| percentile | <p> |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN percentileCont(n.prop, $percentile) AS p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| <result> |
|
||||
And no side effects
|
||||
|
||||
Examples:
|
||||
| p | result |
|
||||
| 0.0 | 10.0 |
|
||||
| 0.5 | 20.0 |
|
||||
| 1.0 | 30.0 |
|
||||
|
||||
Scenario Outline: `percentileCont()` failing on bad arguments
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({prop: 10.0})
|
||||
"""
|
||||
And parameters are:
|
||||
| param | <percentile> |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN percentileCont(n.prop, $param)
|
||||
"""
|
||||
Then a ArgumentError should be raised at runtime: NumberOutOfRange
|
||||
|
||||
Examples:
|
||||
| percentile |
|
||||
| 1000 |
|
||||
| -1 |
|
||||
| 1.1 |
|
||||
|
||||
Scenario Outline: `percentileDisc()` failing on bad arguments
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({prop: 10.0})
|
||||
"""
|
||||
And parameters are:
|
||||
| param | <percentile> |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN percentileDisc(n.prop, $param)
|
||||
"""
|
||||
Then a ArgumentError should be raised at runtime: NumberOutOfRange
|
||||
|
||||
Examples:
|
||||
| percentile |
|
||||
| 1000 |
|
||||
| -1 |
|
||||
| 1.1 |
|
||||
|
||||
Scenario: `percentileDisc()` failing in more involved query
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(0, 10) AS i
|
||||
CREATE (s:S)
|
||||
WITH s, i
|
||||
UNWIND range(0, i) AS j
|
||||
CREATE (s)-[:REL]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:S)
|
||||
WITH n, size([(n)-->() | 1]) AS deg
|
||||
WHERE deg > 2
|
||||
WITH deg
|
||||
LIMIT 100
|
||||
RETURN percentileDisc(0.90, deg), deg
|
||||
"""
|
||||
Then a ArgumentError should be raised at runtime: NumberOutOfRange
|
||||
|
||||
Scenario: `type()`
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]->()
|
||||
RETURN type(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| type(r) |
|
||||
| 'T' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `type()` on two relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T1]->()-[:T2]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r1]->()-[r2]->()
|
||||
RETURN type(r1), type(r2)
|
||||
"""
|
||||
Then the result should be:
|
||||
| type(r1) | type(r2) |
|
||||
| 'T1' | 'T2' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `type()` on null relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
OPTIONAL MATCH (a)-[r:NOT_THERE]->()
|
||||
RETURN type(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| type(r) |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: `type()` on mixed null and non-null relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
OPTIONAL MATCH (a)-[r:T]->()
|
||||
RETURN type(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| type(r) |
|
||||
| 'T' |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: `type()` handling Any type
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[r]->()
|
||||
WITH [r, 1] AS list
|
||||
RETURN type(list[0])
|
||||
"""
|
||||
Then the result should be:
|
||||
| type(list[0]) |
|
||||
| 'T' |
|
||||
And no side effects
|
||||
|
||||
Scenario Outline: `type()` failing on invalid arguments
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-[r:T]->()
|
||||
RETURN [x IN [r, <invalid>] | type(x) ] AS list
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: InvalidArgumentValue
|
||||
|
||||
Examples:
|
||||
| invalid |
|
||||
| 0 |
|
||||
| 1.0 |
|
||||
| true |
|
||||
| '' |
|
||||
| [] |
|
||||
|
||||
Scenario: `labels()` should accept type Any
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Foo), (:Foo:Bar)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH [a, 1] AS list
|
||||
RETURN labels(list[0]) AS l
|
||||
"""
|
||||
Then the result should be (ignoring element order for lists):
|
||||
| l |
|
||||
| ['Foo'] |
|
||||
| ['Foo', 'Bar'] |
|
||||
And no side effects
|
||||
|
||||
Scenario: `labels()` failing on a path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Foo), (:Foo:Bar)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (a)
|
||||
RETURN labels(p) AS l
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: `labels()` failing on invalid arguments
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Foo), (:Foo:Bar)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH [a, 1] AS list
|
||||
RETURN labels(list[1]) AS l
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: InvalidArgumentValue
|
||||
|
||||
Scenario: `exists()` is case insensitive
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:X {prop: 42}), (:X)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:X)
|
||||
RETURN n, EXIsTS(n.prop) AS b
|
||||
"""
|
||||
Then the result should be:
|
||||
| n | b |
|
||||
| (:X {prop: 42}) | true |
|
||||
| (:X) | false |
|
||||
And no side effects
|
@ -0,0 +1,66 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: ValueHashJoinAcceptance
|
||||
|
||||
Scenario: Find friends of others
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A {id: 1}),
|
||||
(:A {id: 2}),
|
||||
(:B {id: 2}),
|
||||
(:B {id: 3})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
WHERE a.id = b.id
|
||||
RETURN a, b
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
| (:A {id: 2}) | (:B {id: 2}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Should only join when matching
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(0, 1000) AS i
|
||||
CREATE (:A {id: i})
|
||||
MERGE (:B {id: i % 10})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
WHERE a.id = b.id
|
||||
RETURN a, b
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
| (:A {id: 0}) | (:B {id: 0}) |
|
||||
| (:A {id: 1}) | (:B {id: 1}) |
|
||||
| (:A {id: 2}) | (:B {id: 2}) |
|
||||
| (:A {id: 3}) | (:B {id: 3}) |
|
||||
| (:A {id: 4}) | (:B {id: 4}) |
|
||||
| (:A {id: 5}) | (:B {id: 5}) |
|
||||
| (:A {id: 6}) | (:B {id: 6}) |
|
||||
| (:A {id: 7}) | (:B {id: 7}) |
|
||||
| (:A {id: 8}) | (:B {id: 8}) |
|
||||
| (:A {id: 9}) | (:B {id: 9}) |
|
||||
And no side effects
|
@ -0,0 +1,163 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: KeysAcceptance
|
||||
|
||||
Scenario: Using `keys()` on a single node, non-empty result
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'Andres', surname: 'Lopez'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
UNWIND keys(n) AS x
|
||||
RETURN DISTINCT x AS theProps
|
||||
"""
|
||||
Then the result should be:
|
||||
| theProps |
|
||||
| 'name' |
|
||||
| 'surname' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using `keys()` on multiple nodes, non-empty result
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'Andres', surname: 'Lopez'}),
|
||||
({otherName: 'Andres', otherSurname: 'Lopez'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
UNWIND keys(n) AS x
|
||||
RETURN DISTINCT x AS theProps
|
||||
"""
|
||||
Then the result should be:
|
||||
| theProps |
|
||||
| 'name' |
|
||||
| 'surname' |
|
||||
| 'otherName' |
|
||||
| 'otherSurname' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using `keys()` on a single node, empty result
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
UNWIND keys(n) AS x
|
||||
RETURN DISTINCT x AS theProps
|
||||
"""
|
||||
Then the result should be:
|
||||
| theProps |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using `keys()` on an optionally matched node
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (n)
|
||||
UNWIND keys(n) AS x
|
||||
RETURN DISTINCT x AS theProps
|
||||
"""
|
||||
Then the result should be:
|
||||
| theProps |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using `keys()` on a relationship, non-empty result
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:KNOWS {level: 'bad', year: '2015'}]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r:KNOWS]-()
|
||||
UNWIND keys(r) AS x
|
||||
RETURN DISTINCT x AS theProps
|
||||
"""
|
||||
Then the result should be:
|
||||
| theProps |
|
||||
| 'level' |
|
||||
| 'year' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using `keys()` on a relationship, empty result
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:KNOWS]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r:KNOWS]-()
|
||||
UNWIND keys(r) AS x
|
||||
RETURN DISTINCT x AS theProps
|
||||
"""
|
||||
Then the result should be:
|
||||
| theProps |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using `keys()` on an optionally matched relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:KNOWS]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH ()-[r:KNOWS]-()
|
||||
UNWIND keys(r) AS x
|
||||
RETURN DISTINCT x AS theProps
|
||||
"""
|
||||
Then the result should be:
|
||||
| theProps |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using `keys()` on a literal map
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN keys({name: 'Alice', age: 38, address: {city: 'London', residential: true}}) AS k
|
||||
"""
|
||||
Then the result should be:
|
||||
| k |
|
||||
| ['name', 'age', 'address'] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using `keys()` on a parameter map
|
||||
Given any graph
|
||||
And parameters are:
|
||||
| param | {name: 'Alice', age: 38, address: {city: 'London', residential: true}} |
|
||||
When executing query:
|
||||
"""
|
||||
RETURN keys($param) AS k
|
||||
"""
|
||||
Then the result should be (ignoring element order for lists):
|
||||
| k |
|
||||
| ['address', 'name', 'age'] |
|
||||
And no side effects
|
@ -0,0 +1,247 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: LabelsAcceptance
|
||||
|
||||
Background:
|
||||
Given an empty graph
|
||||
|
||||
Scenario: Adding a single label
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
SET n:Foo
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| ['Foo'] |
|
||||
And the side effects should be:
|
||||
| +labels | 1 |
|
||||
|
||||
Scenario: Ignore space before colon
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
SET n :Foo
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| ['Foo'] |
|
||||
And the side effects should be:
|
||||
| +labels | 1 |
|
||||
|
||||
Scenario: Adding multiple labels
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
SET n:Foo:Bar
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| ['Foo', 'Bar'] |
|
||||
And the side effects should be:
|
||||
| +labels | 2 |
|
||||
|
||||
Scenario: Ignoring intermediate whitespace 1
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
SET n :Foo :Bar
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| ['Foo', 'Bar'] |
|
||||
And the side effects should be:
|
||||
| +labels | 2 |
|
||||
|
||||
Scenario: Ignoring intermediate whitespace 2
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
SET n :Foo:Bar
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| ['Foo', 'Bar'] |
|
||||
And the side effects should be:
|
||||
| +labels | 2 |
|
||||
|
||||
Scenario: Creating node without label
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (node)
|
||||
RETURN labels(node)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(node) |
|
||||
| [] |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
|
||||
Scenario: Creating node with two labels
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (node:Foo:Bar {name: 'Mattias'})
|
||||
RETURN labels(node)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(node) |
|
||||
| ['Foo', 'Bar'] |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 2 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Ignore space when creating node with labels
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (node :Foo:Bar)
|
||||
RETURN labels(node)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(node) |
|
||||
| ['Foo', 'Bar'] |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 2 |
|
||||
|
||||
Scenario: Create node with label in pattern
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n:Person)-[:OWNS]->(:Dog)
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| ['Person'] |
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
| +labels | 2 |
|
||||
|
||||
Scenario: Fail when adding a new label predicate on a node that is already bound 1
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n:Foo)-[:T1]->(),
|
||||
(n:Bar)-[:T2]->()
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Fail when adding new label predicate on a node that is already bound 2
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()<-[:T2]-(n:Foo),
|
||||
(n:Bar)<-[:T1]-()
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Fail when adding new label predicate on a node that is already bound 3
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n:Foo)
|
||||
CREATE (n:Bar)-[:OWNS]->(:Dog)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Fail when adding new label predicate on a node that is already bound 4
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n {})
|
||||
CREATE (n:Bar)-[:OWNS]->(:Dog)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Fail when adding new label predicate on a node that is already bound 5
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n:Foo)
|
||||
CREATE (n {})-[:OWNS]->(:Dog)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Using `labels()` in return clauses
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| [] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Removing a label
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Foo:Bar)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
REMOVE n:Foo
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| ['Bar'] |
|
||||
And the side effects should be:
|
||||
| -labels | 1 |
|
||||
|
||||
Scenario: Removing a non-existent label
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Foo)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
REMOVE n:Bar
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| ['Foo'] |
|
||||
And no side effects
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,80 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: LargeIntegerEquality
|
||||
|
||||
Background:
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {id: 4611686018427387905})
|
||||
"""
|
||||
|
||||
Scenario: Does not lose precision
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Label)
|
||||
RETURN p.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| p.id |
|
||||
| 4611686018427387905 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling inlined equality of large integer
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Label {id: 4611686018427387905})
|
||||
RETURN p.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| p.id |
|
||||
| 4611686018427387905 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling explicit equality of large integer
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Label)
|
||||
WHERE p.id = 4611686018427387905
|
||||
RETURN p.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| p.id |
|
||||
| 4611686018427387905 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling inlined equality of large integer, non-equal values
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Label {id : 4611686018427387900})
|
||||
RETURN p.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| p.id |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling explicit equality of large integer, non-equal values
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Label)
|
||||
WHERE p.id = 4611686018427387900
|
||||
RETURN p.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| p.id |
|
||||
And no side effects
|
@ -0,0 +1,75 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: ListComprehension
|
||||
|
||||
Scenario: Returning a list comprehension
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)
|
||||
CREATE (a)-[:T]->(:B),
|
||||
(a)-[:T]->(:C)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-->()
|
||||
RETURN [x IN collect(p) | head(nodes(x))] AS p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| [(:A), (:A)] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using a list comprehension in a WITH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)
|
||||
CREATE (a)-[:T]->(:B),
|
||||
(a)-[:T]->(:C)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n:A)-->()
|
||||
WITH [x IN collect(p) | head(nodes(x))] AS p, count(n) AS c
|
||||
RETURN p, c
|
||||
"""
|
||||
Then the result should be:
|
||||
| p | c |
|
||||
| [(:A), (:A)] | 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using a list comprehension in a WHERE
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {prop: 'c'})
|
||||
CREATE (a)-[:T]->(:B),
|
||||
(a)-[:T]->(:C)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)-->(b)
|
||||
WHERE n.prop IN [x IN labels(b) | lower(x)]
|
||||
RETURN b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| (:C) |
|
||||
And no side effects
|
||||
|
@ -0,0 +1,131 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: Literals
|
||||
|
||||
Background:
|
||||
Given any graph
|
||||
|
||||
Scenario: Return an integer
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 1 AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return a float
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 1.0 AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| 1.0 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return a float in exponent form
|
||||
When executing query:
|
||||
"""
|
||||
RETURN -1e-9 AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| -.000000001 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return a boolean
|
||||
When executing query:
|
||||
"""
|
||||
RETURN true AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| true |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return a single-quoted string
|
||||
When executing query:
|
||||
"""
|
||||
RETURN '' AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| '' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return a double-quoted string
|
||||
When executing query:
|
||||
"""
|
||||
RETURN "" AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| '' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return null
|
||||
When executing query:
|
||||
"""
|
||||
RETURN null AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return an empty list
|
||||
When executing query:
|
||||
"""
|
||||
RETURN [] AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| [] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return a nonempty list
|
||||
When executing query:
|
||||
"""
|
||||
RETURN [0, 1, 2] AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| [0, 1, 2] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return an empty map
|
||||
When executing query:
|
||||
"""
|
||||
RETURN {} AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| {} |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return a nonempty map
|
||||
When executing query:
|
||||
"""
|
||||
RETURN {k1: 0, k2: 'string'} AS literal
|
||||
"""
|
||||
Then the result should be:
|
||||
| literal |
|
||||
| {k1: 0, k2: 'string'} |
|
||||
And no side effects
|
@ -0,0 +1,552 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: MatchAcceptance
|
||||
|
||||
Scenario: 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:
|
||||
| p |
|
||||
| <(:Label1)<-[:TYPE]-(:Label2)> |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| p |
|
||||
| <(:Label1)<-[:T1]-(:Label2)-[:T2]->(:Label3)> |
|
||||
And no side effects
|
||||
|
||||
Scenario: Use multiple MATCH clauses to do a Cartesian product
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({value: 1}),
|
||||
({value: 2}),
|
||||
({value: 3})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n), (m)
|
||||
RETURN n.value AS n, m.value AS m
|
||||
"""
|
||||
Then the result should be:
|
||||
| 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: Use params in pattern matching predicates
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:T {foo: 'bar'}]->(:B {name: 'me'})
|
||||
"""
|
||||
And parameters are:
|
||||
| param | 'bar' |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[r]->(b)
|
||||
WHERE r.foo = $param
|
||||
RETURN b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| (:B {name: 'me'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Filter out based on node prop name
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'Someone'})<-[:X]-()-[:X]->({name: 'Andres'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[rel:X]-(a)
|
||||
WHERE a.name = 'Andres'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| ({name: 'Andres'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Honour the column name for RETURN items
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'Someone'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH a.name AS a
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| 'Someone' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Filter based on rel prop name
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)<-[:KNOWS {name: 'monkey'}]-()-[:KNOWS {name: 'woot'}]->(:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (node)-[r:KNOWS]->(a)
|
||||
WHERE r.name = 'monkey'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:A) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Cope with shadowed variables
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({value: 1, name: 'King Kong'}),
|
||||
({value: 2, name: 'Ann Darrow'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH n.name AS n
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| 'Ann Darrow' |
|
||||
| 'King Kong' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Get neighbours
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {value: 1})-[:KNOWS]->(b:B {value: 2})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n1)-[rel:KNOWS]->(n2)
|
||||
RETURN n1, n2
|
||||
"""
|
||||
Then the result should be:
|
||||
| n1 | n2 |
|
||||
| (:A {value: 1}) | (:B {value: 2}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Get two related nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {value: 1}),
|
||||
(a)-[:KNOWS]->(b:B {value: 2}),
|
||||
(a)-[:KNOWS]->(c:C {value: 3})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[rel:KNOWS]->(x)
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| (:B {value: 2}) |
|
||||
| (:C {value: 3}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Get related to related to
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {value: 1})-[:KNOWS]->(b:B {value: 2})-[:FRIEND]->(c:C {value: 3})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)-->(a)-->(b)
|
||||
RETURN b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| (:C {value: 3}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handle comparison between node properties
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {animal: 'monkey'}),
|
||||
(b:B {animal: 'cow'}),
|
||||
(c:C {animal: 'monkey'}),
|
||||
(d:D {animal: 'cow'}),
|
||||
(a)-[:KNOWS]->(b),
|
||||
(a)-[:KNOWS]->(c),
|
||||
(d)-[:KNOWS]->(b),
|
||||
(d)-[:KNOWS]->(c)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)-[rel]->(x)
|
||||
WHERE n.animal = x.animal
|
||||
RETURN n, x
|
||||
"""
|
||||
Then the result should be:
|
||||
| n | x |
|
||||
| (:A {animal: 'monkey'}) | (:C {animal: 'monkey'}) |
|
||||
| (:D {animal: 'cow'}) | (:B {animal: 'cow'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return two subgraphs with bound undirected relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {value: 1})-[:REL {name: 'r'}]->(b:B {value: 2})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[r {name: 'r'}]-(b)
|
||||
RETURN a, b
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
| (:B {value: 2}) | (:A {value: 1}) |
|
||||
| (:A {value: 1}) | (:B {value: 2}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return two subgraphs with bound undirected relationship and optional relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {value: 1})-[:REL {name: 'r1'}]->(b:B {value: 2})-[:REL {name: 'r2'}]->(c:C {value: 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:
|
||||
| a | b | c |
|
||||
| (:A {value: 1}) | (:B {value: 2}) | (:C {value: 3}) |
|
||||
| (:B {value: 2}) | (:A {value: 1}) | null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Rel type function works as expected
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {name: 'A'}),
|
||||
(b:B {name: 'B'}),
|
||||
(c:C {name: 'C'}),
|
||||
(a)-[:KNOWS]->(b),
|
||||
(a)-[:HATES]->(c)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {name: 'A'})-[r]->(x)
|
||||
WHERE type(r) = 'KNOWS'
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| (:B {name: 'B'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Walk alternative relationships
|
||||
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]->(x)
|
||||
WHERE type(r) = 'KNOWS' OR type(r) = 'HATES'
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| [:KNOWS] |
|
||||
| [:HATES] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handle OR in the WHERE clause
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {p1: 12}),
|
||||
(b:B {p2: 13}),
|
||||
(c:C)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE n.p1 = 12 OR n.p2 = 13
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:A {p1: 12}) |
|
||||
| (:B {p2: 13}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| p |
|
||||
| <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})> |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| p |
|
||||
| <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})-[:KNOWS]->(:C {name: 'C'})> |
|
||||
And no side effects
|
||||
|
||||
Scenario: Do not return anything because path length does not match
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-->(x)
|
||||
WHERE length(p) = 10
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
And no side effects
|
||||
|
||||
Scenario: Pass the path length test
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-->(x)
|
||||
WHERE length(p) = 1
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| (:B {name: 'B'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return relationships by fetching them from the path - starting from the end
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(e:End)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (a)-[:REL*2..2]->(b:End)
|
||||
RETURN relationships(p)
|
||||
"""
|
||||
Then the result should be:
|
||||
| relationships(p) |
|
||||
| [[:REL {value: 1}], [:REL {value: 2}]] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return relationships by fetching them from the path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (s:Start)-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(c:C)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (a:Start)-[:REL*2..2]->(b)
|
||||
RETURN relationships(p)
|
||||
"""
|
||||
Then the result should be:
|
||||
| relationships(p) |
|
||||
| [[:REL {value: 1}], [:REL {value: 2}]] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return relationships by collecting them as a list - directed, one way
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(e:End)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[r:REL*2..2]->(b:End)
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| [[:REL {value: 1}], [:REL {value: 2}]] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return relationships by collecting them as a list - undirected, starting from two extremes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:End)-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(c:End)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[r:REL*2..2]-(b:End)
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| [[:REL {value:1}], [:REL {value:2}]] |
|
||||
| [[:REL {value:2}], [:REL {value:1}]] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return relationships by collecting them as a list - undirected, starting from one extreme
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (s:Start)-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(c:C)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:Start)-[r:REL*2..2]-(b)
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| [[:REL {value: 1}], [:REL {value: 2}]] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return a var length path
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {name: 'A'})-[:KNOWS {value: 1}]->(b:B {name: 'B'})-[:KNOWS {value: 2}]->(c:C {name: 'C'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n {name: 'A'})-[:KNOWS*1..2]->(x)
|
||||
RETURN p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| <(:A {name: 'A'})-[:KNOWS {value: 1}]->(:B {name: 'B'})> |
|
||||
| <(:A {name: 'A'})-[:KNOWS {value: 1}]->(:B {name: 'B'})-[:KNOWS {value: 2}]->(:C {name: 'C'})> |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return a var length path of length zero
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:REL]->(b:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (a)-[*0..1]->(b)
|
||||
RETURN a, b, length(p) AS l
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b | l |
|
||||
| (:A) | (:A) | 0 |
|
||||
| (:B) | (:B) | 0 |
|
||||
| (:A) | (:B) | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| 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: Accept skip zero
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE 1 = 0
|
||||
RETURN n SKIP 0
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
And no side effects
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,338 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: MatchingSelfRelationships
|
||||
|
||||
Scenario: 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:
|
||||
| a | r | b |
|
||||
| (:A) | [:LOOP] | (:A) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Undirected match in self-relationship graph, count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:LOOP]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()--()
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| n | r |
|
||||
| (:A) | [:LOOP] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Undirected match of self-relationship in self-relationship graph, count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:LOOP]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)--(n)
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| a | r | b |
|
||||
| (:A) | [:LOOP] | (:B) |
|
||||
| (:B) | [:LOOP] | (:A) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Undirected match on simple relationship graph, count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:LOOP]->(:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()--()
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| a | r | b |
|
||||
| (:A) | [:LOOP] | (:A) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Directed match on self-relationship graph, count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:LOOP]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-->()
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| n | r |
|
||||
| (:A) | [:LOOP] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Directed match of self-relationship on self-relationship graph, count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:LOOP]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)-->(n)
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Counting undirected self-relationships in self-relationship graph
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:LOOP]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)-[r]-(n)
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Counting distinct undirected self-relationships in self-relationship graph
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:LOOP]->(a)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)-[r]-(n)
|
||||
RETURN count(DISTINCT r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(DISTINCT r) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| a | r | b |
|
||||
| (:A) | [:LOOP] | (:B) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Directed match of a simple relationship, count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:LOOP]->(:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-->()
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Counting directed self-relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)-[:LOOP]->(a),
|
||||
()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)-[r]->(n)
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| x | r1 | y | r2 | z |
|
||||
| (:A) | [:T1] | (:Looper) | [:LOOP] | (:Looper) |
|
||||
| (:A) | [:T1] | (:Looper) | [:T2] | (:B) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Mixing directed and undirected pattern parts with self-relationship, count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:T1]->(l:Looper),
|
||||
(l)-[:LOOP]->(l),
|
||||
(l)-[:T2]->(:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (:A)-->()--()
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| 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: Mixing directed and undirected pattern parts with self-relationship, undirected count
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:T1]->(l:Looper),
|
||||
(l)-[:LOOP]->(l),
|
||||
(l)-[:T2]->(:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[]-()-[]-()
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 6 |
|
||||
And no side effects
|
@ -0,0 +1,154 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: MergeIntoAcceptance
|
||||
|
||||
Background:
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A {name: 'A'}), (:B {name: 'B'})
|
||||
"""
|
||||
|
||||
Scenario: Updating one property with ON CREATE
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a {name: 'A'}), (b {name: 'B'})
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
ON CREATE SET r.name = 'foo'
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
| +properties | 1 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH ()-[r:TYPE]->()
|
||||
RETURN [key IN keys(r) | key + '->' + r[key]] AS keyValue
|
||||
"""
|
||||
Then the result should be:
|
||||
| keyValue |
|
||||
| ['name->foo'] |
|
||||
|
||||
Scenario: Null-setting one property with ON CREATE
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a {name: 'A'}), (b {name: 'B'})
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
ON CREATE SET r.name = null
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH ()-[r:TYPE]->()
|
||||
RETURN [key IN keys(r) | key + '->' + r[key]] AS keyValue
|
||||
"""
|
||||
Then the result should be:
|
||||
| keyValue |
|
||||
| [] |
|
||||
|
||||
Scenario: Copying properties from node with ON CREATE
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a {name: 'A'}), (b {name: 'B'})
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
ON CREATE SET r = a
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
| +properties | 1 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH ()-[r:TYPE]->()
|
||||
RETURN [key IN keys(r) | key + '->' + r[key]] AS keyValue
|
||||
"""
|
||||
Then the result should be:
|
||||
| keyValue |
|
||||
| ['name->A'] |
|
||||
|
||||
Scenario: Copying properties from node with ON MATCH
|
||||
And having executed:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
CREATE (a)-[:TYPE {foo: 'bar'}]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a {name: 'A'}), (b {name: 'B'})
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
ON MATCH SET r = a
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
| -properties | 1 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH ()-[r:TYPE]->()
|
||||
RETURN [key IN keys(r) | key + '->' + r[key]] AS keyValue
|
||||
"""
|
||||
Then the result should be:
|
||||
| keyValue |
|
||||
| ['name->A'] |
|
||||
|
||||
Scenario: Copying properties from literal map with ON CREATE
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a {name: 'A'}), (b {name: 'B'})
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
ON CREATE SET r += {foo: 'bar', bar: 'baz'}
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
| +properties | 2 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH ()-[r:TYPE]->()
|
||||
RETURN [key IN keys(r) | key + '->' + r[key]] AS keyValue
|
||||
"""
|
||||
Then the result should be (ignoring element order for lists):
|
||||
| keyValue |
|
||||
| ['foo->bar', 'bar->baz'] |
|
||||
|
||||
Scenario: Copying properties from literal map with ON MATCH
|
||||
And having executed:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
CREATE (a)-[:TYPE {foo: 'bar'}]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a {name: 'A'}), (b {name: 'B'})
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
ON MATCH SET r += {foo: 'baz', bar: 'baz'}
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +properties | 2 |
|
||||
| -properties | 1 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH ()-[r:TYPE]->()
|
||||
RETURN [key IN keys(r) | key + '->' + r[key]] AS keyValue
|
||||
"""
|
||||
Then the result should be (ignoring element order for lists):
|
||||
| keyValue |
|
||||
| ['foo->baz', 'bar->baz'] |
|
@ -0,0 +1,483 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: MergeNodeAcceptance
|
||||
|
||||
Scenario: Merge node when no nodes exist
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a)
|
||||
RETURN count(*) AS n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
|
||||
Scenario: Merge node with label
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:Label)
|
||||
RETURN labels(a)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(a) |
|
||||
| ['Label'] |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 1 |
|
||||
|
||||
Scenario: Merge node with label add label on create
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:Label)
|
||||
ON CREATE SET a:Foo
|
||||
RETURN labels(a)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(a) |
|
||||
| ['Label', 'Foo'] |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 2 |
|
||||
|
||||
Scenario: Merge node with label add property on create
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:Label)
|
||||
ON CREATE SET a.prop = 42
|
||||
RETURN a.prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.prop |
|
||||
| 42 |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Merge node with label when it exists
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {id: 1})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:Label)
|
||||
RETURN a.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.id |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Merge node should create when it doesn't match, properties
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a {prop: 43})
|
||||
RETURN a.prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.prop |
|
||||
| 43 |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Merge node should create when it doesn't match, properties and label
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:Label {prop: 43})
|
||||
RETURN a.prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.prop |
|
||||
| 43 |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Merge node with prop and label
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:Label {prop: 42})
|
||||
RETURN a.prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.prop |
|
||||
| 42 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Merge node with label add label on match when it exists
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:Label)
|
||||
ON MATCH SET a:Foo
|
||||
RETURN labels(a)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(a) |
|
||||
| ['Label', 'Foo'] |
|
||||
And the side effects should be:
|
||||
| +labels | 1 |
|
||||
|
||||
Scenario: Merge node with label add property on update when it exists
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:Label)
|
||||
ON CREATE SET a.prop = 42
|
||||
RETURN a.prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.prop |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Merge node and set property on match
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:Label)
|
||||
ON MATCH SET a.prop = 42
|
||||
RETURN a.prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.prop |
|
||||
| 42 |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Should work when finding multiple elements
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (:X)
|
||||
CREATE (:X)
|
||||
MERGE (:X)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +labels | 1 |
|
||||
|
||||
Scenario: Should handle argument properly
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({x: 42}),
|
||||
({x: 'not42'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
WITH 42 AS x
|
||||
MERGE (c:N {x: x})
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Should handle arguments properly with only write clauses
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a {p: 1})
|
||||
MERGE ({v: a.p})
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +properties | 2 |
|
||||
|
||||
Scenario: Should be able to merge using property from match
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {name: 'A', bornIn: 'New York'})
|
||||
CREATE (:Person {name: 'B', bornIn: 'Ohio'})
|
||||
CREATE (:Person {name: 'C', bornIn: 'New Jersey'})
|
||||
CREATE (:Person {name: 'D', bornIn: 'New York'})
|
||||
CREATE (:Person {name: 'E', bornIn: 'Ohio'})
|
||||
CREATE (:Person {name: 'F', bornIn: 'New Jersey'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (person:Person)
|
||||
MERGE (city:City {name: person.bornIn})
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 3 |
|
||||
| +labels | 1 |
|
||||
| +properties | 3 |
|
||||
|
||||
Scenario: Should be able to use properties from match in ON CREATE
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {bornIn: 'New York'}),
|
||||
(:Person {bornIn: 'Ohio'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (person:Person)
|
||||
MERGE (city:City)
|
||||
ON CREATE SET city.name = person.bornIn
|
||||
RETURN person.bornIn
|
||||
"""
|
||||
Then the result should be:
|
||||
| person.bornIn |
|
||||
| 'New York' |
|
||||
| 'Ohio' |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Should be able to use properties from match in ON MATCH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {bornIn: 'New York'}),
|
||||
(:Person {bornIn: 'Ohio'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (person:Person)
|
||||
MERGE (city:City)
|
||||
ON MATCH SET city.name = person.bornIn
|
||||
RETURN person.bornIn
|
||||
"""
|
||||
Then the result should be:
|
||||
| person.bornIn |
|
||||
| 'New York' |
|
||||
| 'Ohio' |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Should be able to use properties from match in ON MATCH and ON CREATE
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {bornIn: 'New York'}),
|
||||
(:Person {bornIn: 'Ohio'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (person:Person)
|
||||
MERGE (city:City)
|
||||
ON MATCH SET city.name = person.bornIn
|
||||
ON CREATE SET city.name = person.bornIn
|
||||
RETURN person.bornIn
|
||||
"""
|
||||
Then the result should be:
|
||||
| person.bornIn |
|
||||
| 'New York' |
|
||||
| 'Ohio' |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Should be able to set labels on match
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a)
|
||||
ON MATCH SET a:L
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +labels | 1 |
|
||||
|
||||
Scenario: Should be able to set labels on match and on create
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (), ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
MERGE (a:L)
|
||||
ON MATCH SET a:M1
|
||||
ON CREATE SET a:M2
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 3 |
|
||||
|
||||
Scenario: Should support updates while merging
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND [0, 1, 2] AS x
|
||||
UNWIND [0, 1, 2] AS y
|
||||
CREATE ({x: x, y: y})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (foo)
|
||||
WITH foo.x AS x, foo.y AS y
|
||||
MERGE (:N {x: x, y: y + 1})
|
||||
MERGE (:N {x: x, y: y})
|
||||
MERGE (:N {x: x + 1, y: y})
|
||||
RETURN x, y
|
||||
"""
|
||||
Then the result should be:
|
||||
| x | y |
|
||||
| 0 | 0 |
|
||||
| 0 | 1 |
|
||||
| 0 | 2 |
|
||||
| 1 | 0 |
|
||||
| 1 | 1 |
|
||||
| 1 | 2 |
|
||||
| 2 | 0 |
|
||||
| 2 | 1 |
|
||||
| 2 | 2 |
|
||||
And the side effects should be:
|
||||
| +nodes | 15 |
|
||||
| +labels | 1 |
|
||||
| +properties | 30 |
|
||||
|
||||
Scenario: Merge must properly handle multiple labels
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:L:A {prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (test:L:B {prop: 42})
|
||||
RETURN labels(test) AS labels
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels |
|
||||
| ['L', 'B'] |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +labels | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Merge followed by multiple creates
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (t:T {id: 42})
|
||||
CREATE (f:R)
|
||||
CREATE (t)-[:REL]->(f)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
| +labels | 2 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Unwind combined with merge
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 2, 3, 4] AS int
|
||||
MERGE (n {id: int})
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 4 |
|
||||
And the side effects should be:
|
||||
| +nodes | 4 |
|
||||
| +properties | 4 |
|
||||
|
||||
Scenario: Merges should not be able to match on deleted nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A {value: 1}),
|
||||
(:A {value: 2})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)
|
||||
DELETE a
|
||||
MERGE (a2:A)
|
||||
RETURN a2.value
|
||||
"""
|
||||
Then the result should be:
|
||||
| a2.value |
|
||||
| null |
|
||||
| null |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| -nodes | 2 |
|
||||
| -properties | 2 |
|
||||
|
||||
Scenario: ON CREATE on created nodes
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (b)
|
||||
ON CREATE SET b.created = 1
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
@ -0,0 +1,599 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: MergeRelationshipAcceptance
|
||||
|
||||
Scenario: Creating a relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A), (:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Matching a relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
CREATE (a)-[:TYPE]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Matching two relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
CREATE (a)-[:TYPE]->(b)
|
||||
CREATE (a)-[:TYPE]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Filtering relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
CREATE (a)-[:TYPE {name: 'r1'}]->(b)
|
||||
CREATE (a)-[:TYPE {name: 'r2'}]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:TYPE {name: 'r2'}]->(b)
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Creating relationship when all matches filtered out
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
CREATE (a)-[:TYPE {name: 'r1'}]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:TYPE {name: 'r2'}]->(b)
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Matching incoming relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
CREATE (b)-[:TYPE]->(a)
|
||||
CREATE (a)-[:TYPE]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)<-[r:TYPE]-(b)
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Creating relationship with property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:TYPE {name: 'Lola'}]->(b)
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Using ON CREATE on a node
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A), (:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[:KNOWS]->(b)
|
||||
ON CREATE SET b.created = 1
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Using ON CREATE on a relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A), (:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
ON CREATE SET r.name = 'Lola'
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Using ON MATCH on created node
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A), (:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[:KNOWS]->(b)
|
||||
ON MATCH SET b.created = 1
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Using ON MATCH on created relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A), (:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:KNOWS]->(b)
|
||||
ON MATCH SET r.created = 1
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Using ON MATCH on a relationship
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
CREATE (a)-[:TYPE]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
ON MATCH SET r.name = 'Lola'
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Using ON CREATE and ON MATCH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A {id: 1}), (b:B {id: 2})
|
||||
CREATE (a)-[:TYPE]->(b)
|
||||
CREATE (:A {id: 3}), (:B {id: 4})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:TYPE]->(b)
|
||||
ON CREATE SET r.name = 'Lola'
|
||||
ON MATCH SET r.name = 'RUN'
|
||||
RETURN count(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(r) |
|
||||
| 4 |
|
||||
And the side effects should be:
|
||||
| +relationships | 3 |
|
||||
| +properties | 4 |
|
||||
|
||||
Scenario: Creating relationship using merged nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a:A)
|
||||
MERGE (b:B)
|
||||
MERGE (a)-[:FOO]->(b)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Mixing MERGE with CREATE
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
MERGE (a)-[:KNOWS]->(b)
|
||||
CREATE (b)-[:KNOWS]->(c:C)
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| +nodes | 3 |
|
||||
| +relationships | 2 |
|
||||
| +labels | 3 |
|
||||
|
||||
Scenario: Introduce named paths 1
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a {x: 1})
|
||||
MERGE (b {x: 2})
|
||||
MERGE p = (a)-[:R]->(b)
|
||||
RETURN p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| <({x: 1})-[:R]->({x: 2})> |
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
| +properties | 2 |
|
||||
|
||||
Scenario: Introduce named paths 2
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MERGE p = (a {x: 1})
|
||||
RETURN p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| <({x: 1})> |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Use outgoing direction when unspecified
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a {id: 2}), (b {id: 1})
|
||||
MERGE (a)-[r:KNOWS]-(b)
|
||||
RETURN startNode(r).id AS s, endNode(r).id AS e
|
||||
"""
|
||||
Then the result should be:
|
||||
| s | e |
|
||||
| 2 | 1 |
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
| +properties | 2 |
|
||||
|
||||
Scenario: Match outgoing relationship when direction unspecified
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a {id: 1}), (b {id: 2})
|
||||
CREATE (a)-[:KNOWS]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a {id: 2}), (b {id: 1})
|
||||
MERGE (a)-[r:KNOWS]-(b)
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| [:KNOWS] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Match both incoming and outgoing relationships when direction unspecified
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a {id: 2}), (b {id: 1}), (c {id: 1}), (d {id: 2})
|
||||
CREATE (a)-[:KNOWS {name: 'ab'}]->(b)
|
||||
CREATE (c)-[:KNOWS {name: 'cd'}]->(d)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a {id: 2})--(b {id: 1})
|
||||
MERGE (a)-[r:KNOWS]-(b)
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| [:KNOWS {name: 'ab'}] |
|
||||
| [:KNOWS {name: 'cd'}] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Fail when imposing new predicates on a variable that is already bound
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a:Foo)
|
||||
MERGE (a)-[r:KNOWS]->(a:Bar)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Using list properties via variable
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a:Foo), (b:Bar)
|
||||
WITH a, b
|
||||
UNWIND ['a,b', 'a,b'] AS str
|
||||
WITH a, b, split(str, ',') AS roles
|
||||
MERGE (a)-[r:FB {foobar: roles}]->(b)
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 2 |
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
| +labels | 2 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Matching using list property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
CREATE (a)-[:T {prop: [42, 43]}]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
MERGE (a)-[r:T {prop: [42, 43]}]->(b)
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using bound variables from other updating clause
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a), (b)
|
||||
MERGE (a)-[:X]->(b)
|
||||
RETURN count(a)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(a) |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: UNWIND with multiple merges
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['Keanu Reeves', 'Hugo Weaving', 'Carrie-Anne Moss', 'Laurence Fishburne'] AS actor
|
||||
MERGE (m:Movie {name: 'The Matrix'})
|
||||
MERGE (p:Person {name: actor})
|
||||
MERGE (p)-[:ACTED_IN]->(m)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 5 |
|
||||
| +relationships | 4 |
|
||||
| +labels | 2 |
|
||||
| +properties | 5 |
|
||||
|
||||
Scenario: Do not match on deleted entities
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)
|
||||
CREATE (b1:B {value: 0}), (b2:B {value: 1})
|
||||
CREATE (c1:C), (c2:C)
|
||||
CREATE (a)-[:REL]->(b1),
|
||||
(a)-[:REL]->(b2),
|
||||
(b1)-[:REL]->(c1),
|
||||
(b2)-[:REL]->(c2)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[ab]->(b:B)-[bc]->(c:C)
|
||||
DELETE ab, bc, b, c
|
||||
MERGE (newB:B {value: 1})
|
||||
MERGE (a)-[:REL]->(newB)
|
||||
MERGE (newC:C)
|
||||
MERGE (newB)-[:REL]->(newC)
|
||||
"""
|
||||
Then the result should be empty
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| -nodes | 4 |
|
||||
| +relationships | 2 |
|
||||
| -relationships | 4 |
|
||||
| +properties | 1 |
|
||||
| -properties | 2 |
|
||||
|
||||
Scenario: Do not match on deleted relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
CREATE (a)-[:T {name: 'rel1'}]->(b),
|
||||
(a)-[:T {name: 'rel2'}]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[t:T]->(b)
|
||||
DELETE t
|
||||
MERGE (a)-[t2:T {name: 'rel3'}]->(b)
|
||||
RETURN t2.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| t2.name |
|
||||
| 'rel3' |
|
||||
| 'rel3' |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
| -relationships | 2 |
|
||||
| +properties | 1 |
|
||||
| -properties | 2 |
|
||||
|
||||
Scenario: Aliasing of existing nodes 1
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 0})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
MATCH (m)
|
||||
WITH n AS a, m AS b
|
||||
MERGE (a)-[r:T]->(b)
|
||||
RETURN a.id AS a, b.id AS b
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
| 0 | 0 |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Aliasing of existing nodes 2
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 0})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH n AS a, n AS b
|
||||
MERGE (a)-[r:T]->(b)
|
||||
RETURN a.id AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| 0 |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Double aliasing of existing nodes 1
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 0})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
MATCH (m)
|
||||
WITH n AS a, m AS b
|
||||
MERGE (a)-[:T]->(b)
|
||||
WITH a AS x, b AS y
|
||||
MERGE (a)
|
||||
MERGE (b)
|
||||
MERGE (a)-[:T]->(b)
|
||||
RETURN x.id AS x, y.id AS y
|
||||
"""
|
||||
Then the result should be:
|
||||
| x | y |
|
||||
| 0 | 0 |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
||||
|
||||
Scenario: Double aliasing of existing nodes 2
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 0})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH n AS a
|
||||
MERGE (c)
|
||||
MERGE (a)-[:T]->(c)
|
||||
WITH a AS x
|
||||
MERGE (c)
|
||||
MERGE (x)-[:T]->(c)
|
||||
RETURN x.id AS x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| 0 |
|
||||
And the side effects should be:
|
||||
| +relationships | 1 |
|
@ -0,0 +1,210 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: MiscellaneousErrorAcceptance
|
||||
|
||||
Background:
|
||||
Given any graph
|
||||
|
||||
Scenario: Failing on incorrect unicode literal
|
||||
When executing query:
|
||||
"""
|
||||
RETURN '\uH'
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidUnicodeLiteral
|
||||
|
||||
Scenario: Failing on merging relationship with null property
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a), (b)
|
||||
MERGE (a)-[r:X {p: null}]->(b)
|
||||
"""
|
||||
Then a SemanticError should be raised at compile time: MergeReadOwnWrites
|
||||
|
||||
Scenario: Failing on merging node with null property
|
||||
When executing query:
|
||||
"""
|
||||
MERGE ({p: null})
|
||||
"""
|
||||
Then a SemanticError should be raised at compile time: MergeReadOwnWrites
|
||||
|
||||
Scenario: Failing on aggregation in WHERE
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE count(a) > 10
|
||||
RETURN a
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidAggregation
|
||||
|
||||
Scenario: Failing on aggregation in ORDER BY after RETURN
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.prop1
|
||||
ORDER BY max(n.prop2)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidAggregation
|
||||
|
||||
Scenario: Failing on aggregation in ORDER BY after WITH
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH n.prop1 AS foo
|
||||
ORDER BY max(n.prop2)
|
||||
RETURN foo AS foo
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidAggregation
|
||||
|
||||
Scenario: Failing when not aliasing expressions in WITH
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH a, count(*)
|
||||
RETURN a
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NoExpressionAlias
|
||||
|
||||
Scenario: Failing when using undefined variable in pattern
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
CREATE (a)-[:KNOWS]->(b {name: missing})
|
||||
RETURN b
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: Failing when using undefined variable in SET
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
SET a.name = missing
|
||||
RETURN a
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: Failing when using undefined variable in DELETE
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
DELETE x
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: Failing when using a variable that is already bound in CREATE
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
CREATE (a {name: 'foo'})
|
||||
RETURN a
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Failing when using a path variable that is already bound
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (a)
|
||||
WITH p, a
|
||||
MATCH p = (a)-->(b)
|
||||
RETURN a
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Failing when using a list as a node
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH [n] AS users
|
||||
MATCH (users)-->(messages)
|
||||
RETURN messages
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableTypeConflict
|
||||
|
||||
Scenario: Failing when using a variable length relationship as a single relationship
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
MATCH (n)-[r*]->()
|
||||
WHERE r.foo = 'apa'
|
||||
RETURN r
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when UNION has different columns
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 1 AS a
|
||||
UNION
|
||||
RETURN 2 AS b
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: DifferentColumnsInUnion
|
||||
|
||||
Scenario: Failing when mixing UNION and UNION ALL
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 1 AS a
|
||||
UNION
|
||||
RETURN 2 AS a
|
||||
UNION ALL
|
||||
RETURN 3 AS a
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidClauseComposition
|
||||
|
||||
Scenario: Failing when creating without direction
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a)-[:FOO]-(b)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: RequiresDirectedRelationship
|
||||
|
||||
Scenario: Failing when creating with two directions
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a)<-[:FOO]->(b)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: RequiresDirectedRelationship
|
||||
|
||||
Scenario: Failing when deleting a label
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
DELETE n:Person
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidDelete
|
||||
|
||||
Scenario: Failing when setting a list of maps as a property
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a)
|
||||
SET a.foo = [{x: 1}]
|
||||
"""
|
||||
Then a TypeError should be raised at compile time: InvalidPropertyType
|
||||
|
||||
Scenario: Failing when multiple columns have the same name
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 1 AS a, 2 AS a
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: ColumnNameConflict
|
||||
|
||||
Scenario: Failing when using RETURN * without variables in scope
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
RETURN *
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NoVariablesInScope
|
@ -0,0 +1,122 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: NullAcceptance
|
||||
|
||||
Scenario: Ignore null when setting property
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a:DoesNotExist)
|
||||
SET a.prop = 42
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Ignore null when removing property
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a:DoesNotExist)
|
||||
REMOVE a.prop
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Ignore null when setting properties using an appending map
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a:DoesNotExist)
|
||||
SET a += {prop: 42}
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Ignore null when setting properties using an overriding map
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a:DoesNotExist)
|
||||
SET a = {prop: 42}
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Ignore null when setting label
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a:DoesNotExist)
|
||||
SET a:L
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Ignore null when removing label
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a:DoesNotExist)
|
||||
REMOVE a:L
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Ignore null when deleting node
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a:DoesNotExist)
|
||||
DELETE a
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Ignore null when deleting relationship
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH ()-[r:DoesNotExist]-()
|
||||
DELETE r
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| null |
|
||||
And no side effects
|
@ -0,0 +1,74 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: OptionalMatch
|
||||
|
||||
Scenario: 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:
|
||||
| matches | optMatch |
|
||||
| 1 | false |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| matches | optMatch |
|
||||
| 1 | true |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| matches | optMatch |
|
||||
| 1 | true |
|
||||
And no side effects
|
@ -0,0 +1,325 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: OptionalMatchAcceptance
|
||||
|
||||
Background:
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (s:Single), (a:A {prop: 42}),
|
||||
(b:B {prop: 46}), (c:C)
|
||||
CREATE (s)-[:REL]->(a),
|
||||
(s)-[:REL]->(b),
|
||||
(a)-[:REL]->(c),
|
||||
(b)-[:LOOP]->(b)
|
||||
"""
|
||||
|
||||
Scenario: Return null when no matches due to inline label predicate
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:Single)
|
||||
OPTIONAL MATCH (n)-[r]-(m:NonExistent)
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return null when no matches due to label predicate in WHERE
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:Single)
|
||||
OPTIONAL MATCH (n)-[r]-(m)
|
||||
WHERE m:NonExistent
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Respect predicates on the OPTIONAL MATCH
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:Single)
|
||||
OPTIONAL MATCH (n)-[r]-(m)
|
||||
WHERE m.prop = 42
|
||||
RETURN m
|
||||
"""
|
||||
Then the result should be:
|
||||
| m |
|
||||
| (:A {prop: 42}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returning label predicate on null node
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:Single)
|
||||
OPTIONAL MATCH (n)-[r:TYPE]-(m)
|
||||
RETURN m:TYPE
|
||||
"""
|
||||
Then the result should be:
|
||||
| m:TYPE |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: MATCH after OPTIONAL MATCH
|
||||
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:
|
||||
| d |
|
||||
And no side effects
|
||||
|
||||
Scenario: WITH after OPTIONAL MATCH
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a:A)
|
||||
WITH a AS a
|
||||
MATCH (b:B)
|
||||
RETURN a, b
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
| (:A {prop: 42}) | (:B {prop: 46}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Named paths in optional matches
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)
|
||||
OPTIONAL MATCH p = (a)-[:X]->(b)
|
||||
RETURN p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: OPTIONAL MATCH and bound nodes
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:C)
|
||||
OPTIONAL MATCH (x)-->(b)
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| (:A {prop: 42}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: OPTIONAL MATCH with labels on the optional end node
|
||||
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:
|
||||
| b |
|
||||
| null |
|
||||
| (:Y) |
|
||||
| (:Y:Z) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Named paths inside optional matches with node predicates
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
OPTIONAL MATCH p = (a)-[:X]->(b)
|
||||
RETURN p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Variable length optional relationships
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:Single)
|
||||
OPTIONAL MATCH (a)-[*]->(b)
|
||||
RETURN b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| (:A {prop: 42}) |
|
||||
| (:B {prop: 46}) |
|
||||
| (:B {prop: 46}) |
|
||||
| (:C) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Variable length optional relationships with length predicates
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:Single)
|
||||
OPTIONAL MATCH (a)-[*3..]-(b)
|
||||
RETURN b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Optionally matching self-loops
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:B)
|
||||
OPTIONAL MATCH (a)-[r]-(a)
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| [:LOOP] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Optionally matching self-loops without matches
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE NOT (a:B)
|
||||
OPTIONAL MATCH (a)-[r]->(a)
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| null |
|
||||
| null |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Variable length optional relationships with bound nodes
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:Single), (x:C)
|
||||
OPTIONAL MATCH (a)-[*]->(x)
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| (:C) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Variable length optional relationships with bound nodes, no matches
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
OPTIONAL MATCH p = (a)-[*]->(b)
|
||||
RETURN p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Longer pattern with bound nodes
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:Single), (c:C)
|
||||
OPTIONAL MATCH (a)-->(b)-->(c)
|
||||
RETURN b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| (:A {prop: 42}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Longer pattern with bound nodes without matches
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (c:C)
|
||||
OPTIONAL MATCH (a)-->(b)-->(c)
|
||||
RETURN b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling correlated optional matches; first does not match implies second does not match
|
||||
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:
|
||||
| x | r |
|
||||
| (:C) | null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling optional matches between optionally matched entities
|
||||
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:
|
||||
| a | b | r |
|
||||
| null | (:B {prop: 46}) | null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling optional matches between nulls
|
||||
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:
|
||||
| a | b | r |
|
||||
| null | null | null |
|
||||
And no side effects
|
||||
|
||||
Scenario: OPTIONAL MATCH and `collect()`
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:DoesExist {property: 42})
|
||||
CREATE (:DoesExist {property: 43})
|
||||
CREATE (:DoesExist {property: 44})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (f:DoesExist)
|
||||
OPTIONAL MATCH (n:DoesNotExist)
|
||||
RETURN collect(DISTINCT n.property) AS a, collect(DISTINCT f.property) AS b
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
| [] | [42, 43, 44] |
|
||||
And no side effects
|
@ -0,0 +1,293 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: OrderByAcceptance
|
||||
|
||||
Background:
|
||||
Given an empty graph
|
||||
|
||||
Scenario: ORDER BY should return results in ascending order
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n1 {prop: 1}),
|
||||
(n2 {prop: 3}),
|
||||
(n3 {prop: -5})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.prop AS prop
|
||||
ORDER BY n.prop
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| prop |
|
||||
| -5 |
|
||||
| 1 |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY DESC should return results in descending order
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n1 {prop: 1}),
|
||||
(n2 {prop: 3}),
|
||||
(n3 {prop: -5})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.prop AS prop
|
||||
ORDER BY n.prop DESC
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| prop |
|
||||
| 3 |
|
||||
| 1 |
|
||||
| -5 |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY of a column introduced in RETURN should return salient results in ascending order
|
||||
When executing query:
|
||||
"""
|
||||
WITH [0, 1] AS prows, [[2], [3, 4]] AS qrows
|
||||
UNWIND prows AS p
|
||||
UNWIND qrows[p] AS q
|
||||
WITH p, count(q) AS rng
|
||||
RETURN p
|
||||
ORDER BY rng
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| p |
|
||||
| 0 |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Renaming columns before ORDER BY should return results in ascending order
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n1 {prop: 1}),
|
||||
(n2 {prop: 3}),
|
||||
(n3 {prop: -5})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.prop AS n
|
||||
ORDER BY n + 2
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| n |
|
||||
| -5 |
|
||||
| 1 |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handle projections with ORDER BY - GH#4937
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (c1:Crew {name: 'Neo', rank: 1}),
|
||||
(c2:Crew {name: 'Neo', rank: 2}),
|
||||
(c3:Crew {name: 'Neo', rank: 3}),
|
||||
(c4:Crew {name: 'Neo', rank: 4}),
|
||||
(c5:Crew {name: 'Neo', rank: 5})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (c:Crew {name: 'Neo'})
|
||||
WITH c, 0 AS relevance
|
||||
RETURN c.rank AS rank
|
||||
ORDER BY relevance, c.rank
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| rank |
|
||||
| 1 |
|
||||
| 2 |
|
||||
| 3 |
|
||||
| 4 |
|
||||
| 5 |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY should order booleans in the expected order
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [true, false] AS bools
|
||||
RETURN bools
|
||||
ORDER BY bools
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| bools |
|
||||
| false |
|
||||
| true |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY DESC should order booleans in the expected order
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [true, false] AS bools
|
||||
RETURN bools
|
||||
ORDER BY bools DESC
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| bools |
|
||||
| true |
|
||||
| false |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY should order strings in the expected order
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['.*', '', ' ', 'one'] AS strings
|
||||
RETURN strings
|
||||
ORDER BY strings
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| strings |
|
||||
| '' |
|
||||
| ' ' |
|
||||
| '.*' |
|
||||
| 'one' |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY DESC should order strings in the expected order
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['.*', '', ' ', 'one'] AS strings
|
||||
RETURN strings
|
||||
ORDER BY strings DESC
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| strings |
|
||||
| 'one' |
|
||||
| '.*' |
|
||||
| ' ' |
|
||||
| '' |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY should order ints in the expected order
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 3, 2] AS ints
|
||||
RETURN ints
|
||||
ORDER BY ints
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| ints |
|
||||
| 1 |
|
||||
| 2 |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY DESC should order ints in the expected order
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 3, 2] AS ints
|
||||
RETURN ints
|
||||
ORDER BY ints DESC
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| ints |
|
||||
| 3 |
|
||||
| 2 |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY should order floats in the expected order
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1.5, 1.3, 999.99] AS floats
|
||||
RETURN floats
|
||||
ORDER BY floats
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| floats |
|
||||
| 1.3 |
|
||||
| 1.5 |
|
||||
| 999.99 |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY DESC should order floats in the expected order
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1.5, 1.3, 999.99] AS floats
|
||||
RETURN floats
|
||||
ORDER BY floats DESC
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| floats |
|
||||
| 999.99 |
|
||||
| 1.5 |
|
||||
| 1.3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handle ORDER BY with LIMIT 1
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (s:Person {name: 'Steven'}),
|
||||
(c:Person {name: 'Craig'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Person)
|
||||
RETURN p.name AS name
|
||||
ORDER BY p.name
|
||||
LIMIT 1
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| name |
|
||||
| 'Craig' |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY with LIMIT 0 should not generate errors
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Person)
|
||||
RETURN p.name AS name
|
||||
ORDER BY p.name
|
||||
LIMIT 0
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| name |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY with negative parameter for LIMIT should not generate errors
|
||||
And parameters are:
|
||||
| limit | -1 |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Person)
|
||||
RETURN p.name AS name
|
||||
ORDER BY p.name
|
||||
LIMIT $`limit`
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| name |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY with a negative LIMIT should fail with a syntax exception
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (s:Person {name: 'Steven'}),
|
||||
(c:Person {name: 'Craig'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Person)
|
||||
RETURN p.name AS name
|
||||
ORDER BY p.name
|
||||
LIMIT -1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NegativeIntegerArgument
|
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: PathEquality
|
||||
|
||||
Scenario: Direction of traversed relationship is not significant for path equality, simple
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n:A)-[:LOOP]->(n)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p1 = (:A)-->()
|
||||
MATCH p2 = (:A)<--()
|
||||
RETURN p1 = p2
|
||||
"""
|
||||
Then the result should be:
|
||||
| p1 = p2 |
|
||||
| true |
|
||||
And no side effects
|
@ -0,0 +1,302 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: PatternComprehension
|
||||
|
||||
Scenario: Pattern comprehension and ORDER BY
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a {time: 10}), (b {time: 20})
|
||||
CREATE (a)-[:T]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (liker)
|
||||
RETURN [p = (liker)--() | p] AS isNew
|
||||
ORDER BY liker.time
|
||||
"""
|
||||
Then the result should be:
|
||||
| isNew |
|
||||
| [<({time: 10})-[:T]->({time: 20})>] |
|
||||
| [<({time: 20})<-[:T]-({time: 10})>] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returning a pattern comprehension
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)
|
||||
CREATE (a)-[:T]->(:B),
|
||||
(a)-[:T]->(:C)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN [p = (n)-->() | p] AS ps
|
||||
"""
|
||||
Then the result should be:
|
||||
| ps |
|
||||
| [<(:A)-[:T]->(:C)>, <(:A)-[:T]->(:B)>] |
|
||||
| [] |
|
||||
| [] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returning a pattern comprehension with label predicate
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B), (c:C), (d:D)
|
||||
CREATE (a)-[:T]->(b),
|
||||
(a)-[:T]->(c),
|
||||
(a)-[:T]->(d)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
RETURN [p = (n)-->(:B) | p] AS x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| [<(:A)-[:T]->(:B)>] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returning a pattern comprehension with bound nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (b:B)
|
||||
CREATE (a)-[:T]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
RETURN [p = (a)-[*]->(b) | p] AS paths
|
||||
"""
|
||||
Then the result should be:
|
||||
| paths |
|
||||
| [<(:A)-[:T]->(:B)>] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using a pattern comprehension in a WITH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A)
|
||||
CREATE (a)-[:T]->(:B),
|
||||
(a)-[:T]->(:C)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)-->(b)
|
||||
WITH [p = (n)-->() | p] AS ps, count(b) AS c
|
||||
RETURN ps, c
|
||||
"""
|
||||
Then the result should be:
|
||||
| ps | c |
|
||||
| [<(:A)-[:T]->(:C)>, <(:A)-[:T]->(:B)>] | 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using a variable-length pattern comprehension in a WITH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:T]->(:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A), (b:B)
|
||||
WITH [p = (a)-[*]->(b) | p] AS paths, count(a) AS c
|
||||
RETURN paths, c
|
||||
"""
|
||||
Then the result should be:
|
||||
| paths | c |
|
||||
| [<(:A)-[:T]->(:B)>] | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using pattern comprehension in RETURN
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (:A), (:A)
|
||||
CREATE (a)-[:HAS]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
RETURN [p = (n)-[:HAS]->() | p] AS ps
|
||||
"""
|
||||
Then the result should be:
|
||||
| ps |
|
||||
| [<(:A)-[:HAS]->()>] |
|
||||
| [] |
|
||||
| [] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aggregating on pattern comprehension
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (:A), (:A)
|
||||
CREATE (a)-[:HAS]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
RETURN count([p = (n)-[:HAS]->() | p]) AS c
|
||||
"""
|
||||
Then the result should be:
|
||||
| c |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using pattern comprehension to test existence
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:X {prop: 42}), (:X {prop: 43})
|
||||
CREATE (a)-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:X)
|
||||
RETURN n, size([(n)--() | 1]) > 0 AS b
|
||||
"""
|
||||
Then the result should be:
|
||||
| n | b |
|
||||
| (:X {prop: 42}) | true |
|
||||
| (:X {prop: 43}) | false |
|
||||
And no side effects
|
||||
|
||||
Scenario: Pattern comprehension inside list comprehension
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (n1:X {n: 1}), (m1:Y), (i1:Y), (i2:Y)
|
||||
CREATE (n1)-[:T]->(m1),
|
||||
(m1)-[:T]->(i1),
|
||||
(m1)-[:T]->(i2)
|
||||
CREATE (n2:X {n: 2}), (m2), (i3:L), (i4:Y)
|
||||
CREATE (n2)-[:T]->(m2),
|
||||
(m2)-[:T]->(i3),
|
||||
(m2)-[:T]->(i4)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n:X)-->(b)
|
||||
RETURN n, [x IN nodes(p) | size([(x)-->(:Y) | 1])] AS list
|
||||
"""
|
||||
Then the result should be:
|
||||
| n | list |
|
||||
| (:X {n: 1}) | [1, 2] |
|
||||
| (:X {n: 2}) | [0, 1] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Get node degree via size of pattern comprehension
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (x:X),
|
||||
(x)-[:T]->(),
|
||||
(x)-[:T]->(),
|
||||
(x)-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:X)
|
||||
RETURN size([(a)-->() | 1]) AS length
|
||||
"""
|
||||
Then the result should be:
|
||||
| length |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Get node degree via size of pattern comprehension that specifies a relationship type
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (x:X),
|
||||
(x)-[:T]->(),
|
||||
(x)-[:T]->(),
|
||||
(x)-[:T]->(),
|
||||
(x)-[:OTHER]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:X)
|
||||
RETURN size([(a)-[:T]->() | 1]) AS length
|
||||
"""
|
||||
Then the result should be:
|
||||
| length |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Get node degree via size of pattern comprehension that specifies multiple relationship types
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (x:X),
|
||||
(x)-[:T]->(),
|
||||
(x)-[:T]->(),
|
||||
(x)-[:T]->(),
|
||||
(x)-[:OTHER]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:X)
|
||||
RETURN size([(a)-[:T|OTHER]->() | 1]) AS length
|
||||
"""
|
||||
Then the result should be:
|
||||
| length |
|
||||
| 4 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Introducing new node variable in pattern comprehension
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a), (b {prop: 'val'})
|
||||
CREATE (a)-[:T]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN [(n)-[:T]->(b) | b.prop] AS list
|
||||
"""
|
||||
Then the result should be:
|
||||
| list |
|
||||
| ['val'] |
|
||||
| [] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Introducing new relationship variable in pattern comprehension
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a), (b)
|
||||
CREATE (a)-[:T {prop: 'val'}]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN [(n)-[r:T]->() | r.prop] AS list
|
||||
"""
|
||||
Then the result should be:
|
||||
| list |
|
||||
| ['val'] |
|
||||
| [] |
|
||||
And no side effects
|
@ -0,0 +1,517 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: ProcedureCallAcceptance
|
||||
|
||||
Background:
|
||||
Given an empty graph
|
||||
|
||||
Scenario: In-query call to procedure that takes arguments fails when trying to pass them implicitly
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| in | out |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentPassingMode
|
||||
|
||||
Scenario: Standalone call to procedure that takes no arguments
|
||||
And there exists a procedure test.labels() :: (label :: STRING?):
|
||||
| label |
|
||||
| 'A' |
|
||||
| 'B' |
|
||||
| 'C' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.labels()
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| label |
|
||||
| 'A' |
|
||||
| 'B' |
|
||||
| 'C' |
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to procedure that takes no arguments
|
||||
And there exists a procedure test.labels() :: (label :: STRING?):
|
||||
| label |
|
||||
| 'A' |
|
||||
| 'B' |
|
||||
| 'C' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.labels() YIELD label
|
||||
RETURN label
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| label |
|
||||
| 'A' |
|
||||
| 'B' |
|
||||
| 'C' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Calling the same procedure twice using the same outputs in each call
|
||||
And there exists a procedure test.labels() :: (label :: STRING?):
|
||||
| label |
|
||||
| 'A' |
|
||||
| 'B' |
|
||||
| 'C' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.labels() YIELD label
|
||||
WITH count(*) AS c
|
||||
CALL test.labels() YIELD label
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| c | label |
|
||||
| 3 | 'A' |
|
||||
| 3 | 'B' |
|
||||
| 3 | 'C' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to VOID procedure that takes no arguments
|
||||
And there exists a procedure test.doNothing() :: VOID:
|
||||
|
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.doNothing()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to VOID procedure that takes no arguments
|
||||
And there exists a procedure test.doNothing() :: VOID:
|
||||
|
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
CALL test.doNothing()
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to VOID procedure does not consume rows
|
||||
And there exists a procedure test.doNothing() :: VOID:
|
||||
|
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A {name: 'a'})
|
||||
CREATE (:B {name: 'b'})
|
||||
CREATE (:C {name: 'c'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
CALL test.doNothing()
|
||||
RETURN n.name AS `name`
|
||||
"""
|
||||
Then the result should be:
|
||||
| name |
|
||||
| 'a' |
|
||||
| 'b' |
|
||||
| 'c' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to VOID procedure that takes no arguments, called with implicit arguments
|
||||
And there exists a procedure test.doNothing() :: VOID:
|
||||
|
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.doNothing
|
||||
"""
|
||||
Then the result should be empty
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to procedure that takes no arguments and yields no results
|
||||
And there exists a procedure test.doNothing() :: ():
|
||||
|
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.doNothing() YIELD - RETURN 1
|
||||
"""
|
||||
Then the result should be:
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure that takes no arguments and yields no results
|
||||
And there exists a procedure test.doNothing() :: ():
|
||||
|
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.doNothing()
|
||||
"""
|
||||
Then the result should be empty
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure that takes no arguments and yields no results, called with implicit arguments
|
||||
And there exists a procedure test.doNothing() :: ():
|
||||
|
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.doNothing
|
||||
"""
|
||||
Then the result should be empty
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to procedure with explicit arguments
|
||||
And there exists a procedure test.my.proc(name :: STRING?, id :: INTEGER?) :: (city :: STRING?, country_code :: INTEGER?):
|
||||
| name | id | city | country_code |
|
||||
| 'Andres' | 1 | 'Malmö' | 46 |
|
||||
| 'Tobias' | 1 | 'Malmö' | 46 |
|
||||
| 'Mats' | 1 | 'Malmö' | 46 |
|
||||
| 'Stefan' | 1 | 'Berlin' | 49 |
|
||||
| 'Stefan' | 2 | 'München' | 49 |
|
||||
| 'Petra' | 1 | 'London' | 44 |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc('Stefan', 1) YIELD city, country_code
|
||||
RETURN city, country_code
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| city | country_code |
|
||||
| 'Berlin' | 49 |
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to procedure with explicit arguments that drops all result fields
|
||||
And there exists a procedure test.my.proc(name :: STRING?, id :: INTEGER?) :: (city :: STRING?, country_code :: INTEGER?):
|
||||
| name | id | city | country_code |
|
||||
| 'Andres' | 1 | 'Malmö' | 46 |
|
||||
| 'Tobias' | 1 | 'Malmö' | 46 |
|
||||
| 'Mats' | 1 | 'Malmö' | 46 |
|
||||
| 'Stefan' | 1 | 'Berlin' | 49 |
|
||||
| 'Stefan' | 2 | 'München' | 49 |
|
||||
| 'Petra' | 1 | 'London' | 44 |
|
||||
When executing query:
|
||||
"""
|
||||
WITH 'Stefan' AS name, 1 AS id
|
||||
CALL test.my.proc(name, id) YIELD -
|
||||
RETURN name, id, count(*) AS count
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| name | id | count |
|
||||
| 'Stefan' | 1 | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure with explicit arguments
|
||||
And there exists a procedure test.my.proc(name :: STRING?, id :: INTEGER?) :: (city :: STRING?, country_code :: INTEGER?):
|
||||
| name | id | city | country_code |
|
||||
| 'Andres' | 1 | 'Malmö' | 46 |
|
||||
| 'Tobias' | 1 | 'Malmö' | 46 |
|
||||
| 'Mats' | 1 | 'Malmö' | 46 |
|
||||
| 'Stefan' | 1 | 'Berlin' | 49 |
|
||||
| 'Stefan' | 2 | 'München' | 49 |
|
||||
| 'Petra' | 1 | 'London' | 44 |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc('Stefan', 1)
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| city | country_code |
|
||||
| 'Berlin' | 49 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure with implicit arguments
|
||||
And there exists a procedure test.my.proc(name :: STRING?, id :: INTEGER?) :: (city :: STRING?, country_code :: INTEGER?):
|
||||
| name | id | city | country_code |
|
||||
| 'Andres' | 1 | 'Malmö' | 46 |
|
||||
| 'Tobias' | 1 | 'Malmö' | 46 |
|
||||
| 'Mats' | 1 | 'Malmö' | 46 |
|
||||
| 'Stefan' | 1 | 'Berlin' | 49 |
|
||||
| 'Stefan' | 2 | 'München' | 49 |
|
||||
| 'Petra' | 1 | 'London' | 44 |
|
||||
And parameters are:
|
||||
| name | 'Stefan' |
|
||||
| id | 1 |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| city | country_code |
|
||||
| 'Berlin' | 49 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure with argument of type NUMBER accepts value of type INTEGER
|
||||
And there exists a procedure test.my.proc(in :: NUMBER?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| 42 | 'wisdom' |
|
||||
| 42.3 | 'about right' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(42)
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'wisdom' |
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to procedure with argument of type NUMBER accepts value of type INTEGER
|
||||
And there exists a procedure test.my.proc(in :: NUMBER?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| 42 | 'wisdom' |
|
||||
| 42.3 | 'about right' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(42) YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'wisdom' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure with argument of type NUMBER accepts value of type FLOAT
|
||||
And there exists a procedure test.my.proc(in :: NUMBER?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| 42 | 'wisdom' |
|
||||
| 42.3 | 'about right' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(42.3)
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'about right' |
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to procedure with argument of type NUMBER accepts value of type FLOAT
|
||||
And there exists a procedure test.my.proc(in :: NUMBER?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| 42 | 'wisdom' |
|
||||
| 42.3 | 'about right' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(42.3) YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'about right' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure with argument of type FLOAT accepts value of type INTEGER
|
||||
And there exists a procedure test.my.proc(in :: FLOAT?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| 42.0 | 'close enough' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(42)
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'close enough' |
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to procedure with argument of type FLOAT accepts value of type INTEGER
|
||||
And there exists a procedure test.my.proc(in :: FLOAT?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| 42.0 | 'close enough' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(42) YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'close enough' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure with argument of type INTEGER accepts value of type FLOAT
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| 42 | 'close enough' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(42.0)
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'close enough' |
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to procedure with argument of type INTEGER accepts value of type FLOAT
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| 42 | 'close enough' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(42.0) YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'close enough' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure with null argument
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| null | 'nix' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(null)
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'nix' |
|
||||
And no side effects
|
||||
|
||||
Scenario: In-query call to procedure with null argument
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: STRING?):
|
||||
| in | out |
|
||||
| null | 'nix' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(null) YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| out |
|
||||
| 'nix' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Standalone call to procedure should fail if input type is wrong
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| in | out |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(true)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: In-query call to procedure should fail if input type is wrong
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| in | out |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(true) YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Standalone call to procedure should fail if explicit argument is missing
|
||||
And there exists a procedure test.my.proc(name :: STRING?, in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| name | in | out |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc('Dobby')
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidNumberOfArguments
|
||||
|
||||
Scenario: In-query call to procedure should fail if explicit argument is missing
|
||||
And there exists a procedure test.my.proc(name :: STRING?, in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| name | in | out |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc('Dobby') YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidNumberOfArguments
|
||||
|
||||
Scenario: Standalone call to procedure should fail if too many explicit argument are given
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| in | out |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(1, 2, 3, 4)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidNumberOfArguments
|
||||
|
||||
Scenario: In-query call to procedure should fail if too many explicit argument are given
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| in | out |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(1, 2, 3, 4) YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidNumberOfArguments
|
||||
|
||||
Scenario: Standalone call to procedure should fail if implicit argument is missing
|
||||
And there exists a procedure test.my.proc(name :: STRING?, in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| name | in | out |
|
||||
And parameters are:
|
||||
| name | 'Stefan' |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc
|
||||
"""
|
||||
Then a ParameterMissing should be raised at compile time: MissingParameter
|
||||
|
||||
Scenario: In-query call to procedure that has outputs fails if no outputs are yielded
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| in | out |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc(1)
|
||||
RETURN out
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: In-query call to procedure that both takes arguments and has outputs fails if the arguments are passed implicitly and no outputs are yielded
|
||||
And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?):
|
||||
| in | out |
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc
|
||||
RETURN out
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: Standalone call to unknown procedure should fail
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc
|
||||
"""
|
||||
Then a ProcedureError should be raised at compile time: ProcedureNotFound
|
||||
|
||||
Scenario: In-query call to unknown procedure should fail
|
||||
When executing query:
|
||||
"""
|
||||
CALL test.my.proc() YIELD out
|
||||
RETURN out
|
||||
"""
|
||||
Then a ProcedureError should be raised at compile time: ProcedureNotFound
|
||||
|
||||
Scenario: In-query procedure call should fail if shadowing an already bound variable
|
||||
And there exists a procedure test.labels() :: (label :: STRING?):
|
||||
| label |
|
||||
| 'A' |
|
||||
| 'B' |
|
||||
| 'C' |
|
||||
When executing query:
|
||||
"""
|
||||
WITH 'Hi' AS label
|
||||
CALL test.labels() YIELD label
|
||||
RETURN *
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: In-query procedure call should fail if one of the argument expressions uses an aggregation function
|
||||
And there exists a procedure test.labels(in :: INTEGER?) :: (label :: STRING?):
|
||||
| in | label |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
CALL test.labels(count(n)) YIELD label
|
||||
RETURN label
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidAggregation
|
@ -0,0 +1,161 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: RemoveAcceptance
|
||||
|
||||
Scenario: Should ignore nulls
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
OPTIONAL MATCH (n)-[r]->()
|
||||
REMOVE r.prop
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| ({prop: 42}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Remove a single label
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:L {prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
REMOVE n:L
|
||||
RETURN n.prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.prop |
|
||||
| 42 |
|
||||
And the side effects should be:
|
||||
| -labels | 1 |
|
||||
|
||||
Scenario: Remove multiple labels
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:L1:L2:L3 {prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
REMOVE n:L1:L3
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then the result should be:
|
||||
| labels(n) |
|
||||
| ['L2'] |
|
||||
And the side effects should be:
|
||||
| -labels | 2 |
|
||||
|
||||
Scenario: Remove a single node property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:L {prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
REMOVE n.prop
|
||||
RETURN exists(n.prop) AS still_there
|
||||
"""
|
||||
Then the result should be:
|
||||
| still_there |
|
||||
| false |
|
||||
And the side effects should be:
|
||||
| -properties | 1 |
|
||||
|
||||
Scenario: Remove multiple node properties
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:L {prop: 42, a: 'a', b: 'B'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
REMOVE n.prop, n.a
|
||||
RETURN size(keys(n)) AS props
|
||||
"""
|
||||
Then the result should be:
|
||||
| props |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| -properties | 2 |
|
||||
|
||||
Scenario: Remove a single relationship property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a), (b), (a)-[:X {prop: 42}]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]->()
|
||||
REMOVE r.prop
|
||||
RETURN exists(r.prop) AS still_there
|
||||
"""
|
||||
Then the result should be:
|
||||
| still_there |
|
||||
| false |
|
||||
And the side effects should be:
|
||||
| -properties | 1 |
|
||||
|
||||
Scenario: Remove multiple relationship properties
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a), (b), (a)-[:X {prop: 42, a: 'a', b: 'B'}]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]->()
|
||||
REMOVE r.prop, r.a
|
||||
RETURN size(keys(r)) AS props
|
||||
"""
|
||||
Then the result should be:
|
||||
| props |
|
||||
| 1 |
|
||||
And the side effects should be:
|
||||
| -properties | 2 |
|
||||
|
||||
Scenario: Remove a missing property should be a valid operation
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (), (), ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
REMOVE n.prop
|
||||
RETURN sum(size(keys(n))) AS totalNumberOfProps
|
||||
"""
|
||||
Then the result should be:
|
||||
| totalNumberOfProps |
|
||||
| 0 |
|
||||
And no side effects
|
@ -0,0 +1,312 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: ReturnAcceptanceTest
|
||||
|
||||
Scenario: Allow addition
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 1337, version: 99})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.id = 1337
|
||||
RETURN a.version + 5
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.version + 5 |
|
||||
| 104 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Limit to two hits
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 1, 1, 1, 1] AS i
|
||||
RETURN i
|
||||
LIMIT 2
|
||||
"""
|
||||
Then the result should be:
|
||||
| i |
|
||||
| 1 |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Limit to two hits with explicit order
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'A'}),
|
||||
({name: 'B'}),
|
||||
({name: 'C'}),
|
||||
({name: 'D'}),
|
||||
({name: 'E'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
ORDER BY n.name ASC
|
||||
LIMIT 2
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| ({name: 'A'}) |
|
||||
| ({name: 'B'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Start the result from the second row
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'A'}),
|
||||
({name: 'B'}),
|
||||
({name: 'C'}),
|
||||
({name: 'D'}),
|
||||
({name: 'E'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
ORDER BY n.name ASC
|
||||
SKIP 2
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| n |
|
||||
| ({name: 'C'}) |
|
||||
| ({name: 'D'}) |
|
||||
| ({name: 'E'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Start the result from the second row by param
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'A'}),
|
||||
({name: 'B'}),
|
||||
({name: 'C'}),
|
||||
({name: 'D'}),
|
||||
({name: 'E'})
|
||||
"""
|
||||
And parameters are:
|
||||
| skipAmount | 2 |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
ORDER BY n.name ASC
|
||||
SKIP $skipAmount
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| n |
|
||||
| ({name: 'C'}) |
|
||||
| ({name: 'D'}) |
|
||||
| ({name: 'E'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Get rows in the middle
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'A'}),
|
||||
({name: 'B'}),
|
||||
({name: 'C'}),
|
||||
({name: 'D'}),
|
||||
({name: 'E'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
ORDER BY n.name ASC
|
||||
SKIP 2
|
||||
LIMIT 2
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| n |
|
||||
| ({name: 'C'}) |
|
||||
| ({name: 'D'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Get rows in the middle by param
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'A'}),
|
||||
({name: 'B'}),
|
||||
({name: 'C'}),
|
||||
({name: 'D'}),
|
||||
({name: 'E'})
|
||||
"""
|
||||
And parameters are:
|
||||
| s | 2 |
|
||||
| l | 2 |
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
ORDER BY n.name ASC
|
||||
SKIP $s
|
||||
LIMIT $l
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| n |
|
||||
| ({name: 'C'}) |
|
||||
| ({name: 'D'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Sort on aggregated function
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({division: 'A', age: 22}),
|
||||
({division: 'B', age: 33}),
|
||||
({division: 'B', age: 44}),
|
||||
({division: 'C', age: 55})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.division, max(n.age)
|
||||
ORDER BY max(n.age)
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| n.division | max(n.age) |
|
||||
| 'A' | 22 |
|
||||
| 'B' | 44 |
|
||||
| 'C' | 55 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Support sort and distinct
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'A'}),
|
||||
({name: 'B'}),
|
||||
({name: 'C'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN DISTINCT a
|
||||
ORDER BY a.name
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| a |
|
||||
| ({name: 'A'}) |
|
||||
| ({name: 'B'}) |
|
||||
| ({name: 'C'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Support column renaming
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Singleton)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN a AS ColumnName
|
||||
"""
|
||||
Then the result should be:
|
||||
| ColumnName |
|
||||
| (:Singleton) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Support ordering by a property after being distinct-ified
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:T]->(:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-->(b)
|
||||
RETURN DISTINCT b
|
||||
ORDER BY b.name
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| b |
|
||||
| (:B) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Arithmetic precedence test
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 12 / 4 * 3 - 2 * 4
|
||||
"""
|
||||
Then the result should be:
|
||||
| 12 / 4 * 3 - 2 * 4 |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Arithmetic precedence with parenthesis test
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 12 / 4 * (3 - 2 * 4)
|
||||
"""
|
||||
Then the result should be:
|
||||
| 12 / 4 * (3 - 2 * 4) |
|
||||
| -15 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Count star should count everything in scope
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:L1), (:L2), (:L3)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN a, count(*)
|
||||
ORDER BY count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | count(*) |
|
||||
| (:L1) | 1 |
|
||||
| (:L2) | 1 |
|
||||
| (:L3) | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Absolute function
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN abs(-1)
|
||||
"""
|
||||
Then the result should be:
|
||||
| abs(-1) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return collection size
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN size([1, 2, 3]) AS n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| 3 |
|
||||
And no side effects
|
@ -0,0 +1,623 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: ReturnAcceptance2
|
||||
|
||||
Scenario: Fail when returning properties of deleted nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({p: 0})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
DELETE n
|
||||
RETURN n.p
|
||||
"""
|
||||
Then a EntityNotFound should be raised at runtime: DeletedEntityAccess
|
||||
|
||||
Scenario: Fail when returning labels of deleted nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
DELETE n
|
||||
RETURN labels(n)
|
||||
"""
|
||||
Then a EntityNotFound should be raised at runtime: DeletedEntityAccess
|
||||
|
||||
Scenario: Fail when returning properties of deleted relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T {p: 0}]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]->()
|
||||
DELETE r
|
||||
RETURN r.p
|
||||
"""
|
||||
Then a EntityNotFound should be raised at runtime: DeletedEntityAccess
|
||||
|
||||
Scenario: Do not fail when returning type of deleted relationships
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]->()
|
||||
DELETE r
|
||||
RETURN type(r)
|
||||
"""
|
||||
Then the result should be:
|
||||
| type(r) |
|
||||
| 'T' |
|
||||
And the side effects should be:
|
||||
| -relationships | 1 |
|
||||
|
||||
Scenario: Accept valid Unicode literal
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN '\u01FF' AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| 'ǿ' |
|
||||
And no side effects
|
||||
|
||||
Scenario: LIMIT 0 should return an empty result
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (), (), ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
LIMIT 0
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
And no side effects
|
||||
|
||||
Scenario: Fail when sorting on variable removed by DISTINCT
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'A', age: 13}), ({name: 'B', age: 12}), ({name: 'C', age: 11})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN DISTINCT a.name
|
||||
ORDER BY a.age
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: Ordering with aggregation
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'nisse'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n.name, count(*) AS foo
|
||||
ORDER BY n.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.name | foo |
|
||||
| 'nisse' | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: DISTINCT on nullable values
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'Florescu'}), (), ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN DISTINCT n.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.name |
|
||||
| 'Florescu' |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Return all variables
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Start)-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (a:Start)-->(b)
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b | p |
|
||||
| (:Start) | () | <(:Start)-[:T]->()> |
|
||||
And no side effects
|
||||
|
||||
Scenario: Setting and returning the size of a list property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
SET n.x = [1, 2, 3]
|
||||
RETURN size(n.x)
|
||||
"""
|
||||
Then the result should be:
|
||||
| size(n.x) |
|
||||
| 3 |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: `sqrt()` returning float values
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN sqrt(12.96)
|
||||
"""
|
||||
Then the result should be:
|
||||
| sqrt(12.96) |
|
||||
| 3.6 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Arithmetic expressions inside aggregation
|
||||
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 (me)-[r1:ATE]->()<-[r2:ATE]-(you)
|
||||
WHERE me.name = 'Michael'
|
||||
WITH me, count(DISTINCT r1) AS H1, count(DISTINCT r2) AS H2, you
|
||||
MATCH (me)-[r1:ATE]->()<-[r2:ATE]-(you)
|
||||
RETURN me, you, sum((1 - abs(r1.times / H1 - r2.times / H2)) * (r1.times + r2.times) / (H1 + H2)) AS sum
|
||||
"""
|
||||
Then the result should be:
|
||||
| me | you | sum |
|
||||
| ({name: 'Michael'}) | ({name: 'Andres'}) | -7 |
|
||||
| ({name: 'Michael'}) | ({name: 'Peter'}) | 0 |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| sum(r1.times) |
|
||||
| 776 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returning a list property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({foo: [1, 2, 3]})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| ({foo: [1, 2, 3]}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returning a projected map
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({foo: [1, 2, 3]})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
RETURN {a: 1, b: 'foo'}
|
||||
"""
|
||||
Then the result should be:
|
||||
| {a: 1, b: 'foo'} |
|
||||
| {a: 1, b: 'foo'} |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returning an expression
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN exists(a.id), a IS NOT NULL
|
||||
"""
|
||||
Then the result should be:
|
||||
| exists(a.id) | a IS NOT NULL |
|
||||
| false | true |
|
||||
And no side effects
|
||||
|
||||
Scenario: Concatenating and returning the size of literal lists
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN size([[], []] + [[]]) AS l
|
||||
"""
|
||||
Then the result should be:
|
||||
| l |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returning nested expressions based on list property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
SET n.array = [1, 2, 3, 4, 5]
|
||||
RETURN tail(tail(n.array))
|
||||
"""
|
||||
Then the result should be:
|
||||
| tail(tail(n.array)) |
|
||||
| [3, 4, 5] |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Limiting amount of rows when there are fewer left than the LIMIT argument
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(0, 15) AS i
|
||||
CREATE ({count: i})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN a.count
|
||||
ORDER BY a.count
|
||||
SKIP 10
|
||||
LIMIT 10
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| a.count |
|
||||
| 10 |
|
||||
| 11 |
|
||||
| 12 |
|
||||
| 13 |
|
||||
| 14 |
|
||||
| 15 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `substring()` with default second argument
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN substring('0123456789', 1) AS s
|
||||
"""
|
||||
Then the result should be:
|
||||
| s |
|
||||
| '123456789' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returning all variables with ordering
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 1}), ({id: 10})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN *
|
||||
ORDER BY n.id
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| n |
|
||||
| ({id: 1}) |
|
||||
| ({id: 10}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Using aliased DISTINCT expression in ORDER BY
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 1}), ({id: 10})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN DISTINCT n.id AS id
|
||||
ORDER BY id DESC
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| id |
|
||||
| 10 |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Returned columns do not change from using ORDER BY
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 1}), ({id: 10})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN DISTINCT n
|
||||
ORDER BY n.id
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| n |
|
||||
| ({id: 1}) |
|
||||
| ({id: 10}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Arithmetic expressions should propagate null values
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 1 + (2 - (3 * (4 / (5 ^ (6 % null))))) AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: Indexing into nested literal lists
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN [[1]][0][0]
|
||||
"""
|
||||
Then the result should be:
|
||||
| [[1]][0][0] |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aliasing expressions
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN a.id AS a, a.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | a.id |
|
||||
| 42 | 42 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Projecting an arithmetic expression with aggregation
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN a, count(a) + 3
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | count(a) + 3 |
|
||||
| ({id: 42}) | 4 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Multiple aliasing and backreferencing
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (m {id: 0})
|
||||
WITH {first: m.id} AS m
|
||||
WITH {second: m.first} AS m
|
||||
RETURN m.second
|
||||
"""
|
||||
Then the result should be:
|
||||
| m.second |
|
||||
| 0 |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Aggregating by a list property has a correct definition of equality
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({a: [1, 2, 3]}), ({a: [1, 2, 3]})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH a.a AS a, count(*) AS count
|
||||
RETURN count
|
||||
"""
|
||||
Then the result should be:
|
||||
| count |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Reusing variable names
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:Person), (b:Person), (m:Message {id: 10})
|
||||
CREATE (a)-[:LIKE {creationDate: 20160614}]->(m)-[:POSTED_BY]->(b)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (person:Person)<--(message)<-[like]-(:Person)
|
||||
WITH like.creationDate AS likeTime, person AS person
|
||||
ORDER BY likeTime, message.id
|
||||
WITH head(collect({likeTime: likeTime})) AS latestLike, person AS person
|
||||
RETURN latestLike.likeTime AS likeTime
|
||||
ORDER BY likeTime
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| likeTime |
|
||||
| 20160614 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Concatenating lists of same type
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN [1, 10, 100] + [4, 5] AS foo
|
||||
"""
|
||||
Then the result should be:
|
||||
| foo |
|
||||
| [1, 10, 100, 4, 5] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Appending lists of same type
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN [false, true] + false AS foo
|
||||
"""
|
||||
Then the result should be:
|
||||
| foo |
|
||||
| [false, true, false] |
|
||||
And no side effects
|
||||
|
||||
Scenario: DISTINCT inside aggregation should work with lists in maps
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({list: ['A', 'B']}), ({list: ['A', 'B']})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN count(DISTINCT {foo: n.list}) AS count
|
||||
"""
|
||||
Then the result should be:
|
||||
| count |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling DISTINCT with lists in maps
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({list: ['A', 'B']}), ({list: ['A', 'B']})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH DISTINCT {foo: n.list} AS map
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: DISTINCT inside aggregation should work with nested lists in maps
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({list: ['A', 'B']}), ({list: ['A', 'B']})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN count(DISTINCT {foo: [[n.list, n.list], [n.list, n.list]]}) AS count
|
||||
"""
|
||||
Then the result should be:
|
||||
| count |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: DISTINCT inside aggregation should work with nested lists of maps in maps
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({list: ['A', 'B']}), ({list: ['A', 'B']})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN count(DISTINCT {foo: [{bar: n.list}, {baz: {apa: n.list}}]}) AS count
|
||||
"""
|
||||
Then the result should be:
|
||||
| count |
|
||||
| 1 |
|
||||
And no side effects
|
@ -0,0 +1,394 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: SemanticErrorAcceptance
|
||||
|
||||
Background:
|
||||
Given any graph
|
||||
|
||||
Scenario: Failing when returning an undefined variable
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
RETURN foo
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: Failing when comparing to an undefined variable
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (s)
|
||||
WHERE s.name = undefinedVariable
|
||||
AND s.age = 10
|
||||
RETURN s
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: Failing when using IN on a string literal
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE n.id IN ''
|
||||
RETURN 1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when using IN on an integer literal
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE n.id IN 1
|
||||
RETURN 1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when using IN on a float literal
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE n.id IN 1.0
|
||||
RETURN 1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when using IN on a boolean literal
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE n.id IN true
|
||||
RETURN 1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when a node is used as a relationship
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (r)
|
||||
MATCH ()-[r]-()
|
||||
RETURN r
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableTypeConflict
|
||||
|
||||
Scenario: Failing when a relationship is used as a node
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]-(r)
|
||||
RETURN r
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableTypeConflict
|
||||
|
||||
Scenario: Failing when using `type()` on a node
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (r)
|
||||
RETURN type(r)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when using `length()` on a node
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (r)
|
||||
RETURN length(r)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when re-using a relationship in the same pattern
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[r]->()-[r]->(a)
|
||||
RETURN r
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: RelationshipUniquenessViolation
|
||||
|
||||
Scenario: Failing when using NOT on string literal
|
||||
When executing query:
|
||||
"""
|
||||
RETURN NOT 'foo'
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when using variable length relationship in CREATE
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()-[:FOO*2]->()
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: CreatingVarLength
|
||||
|
||||
Scenario: Failing when using variable length relationship in MERGE
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a)
|
||||
MERGE (b)
|
||||
MERGE (a)-[:FOO*2]->(b)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: CreatingVarLength
|
||||
|
||||
Scenario: Failing when using parameter as node predicate in MATCH
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n $param)
|
||||
RETURN n
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidParameterUse
|
||||
|
||||
Scenario: Failing when using parameter as relationship predicate in MATCH
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r:FOO $param]->()
|
||||
RETURN r
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidParameterUse
|
||||
|
||||
Scenario: Failing when using parameter as node predicate in MERGE
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (n $param)
|
||||
RETURN n
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidParameterUse
|
||||
|
||||
Scenario: Failing when using parameter as relationship predicate in MERGE
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (a)
|
||||
MERGE (b)
|
||||
MERGE (a)-[r:FOO $param]->(b)
|
||||
RETURN r
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidParameterUse
|
||||
|
||||
Scenario: Failing when deleting an integer expression
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()
|
||||
DELETE 1 + 1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when using CREATE on a node that is already bound
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
CREATE (a)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Failing when using MERGE on a node that is already bound
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
CREATE (a)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Failing when using CREATE on a relationship that is already bound
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]->()
|
||||
CREATE ()-[r]->()
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Failing when using MERGE on a relationship that is already bound
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-[r]->(b)
|
||||
MERGE (a)-[r]->(b)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: VariableAlreadyBound
|
||||
|
||||
Scenario: Failing when using undefined variable in ON CREATE
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (n)
|
||||
ON CREATE SET x.foo = 1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: Failing when using undefined variable in ON MATCH
|
||||
When executing query:
|
||||
"""
|
||||
MERGE (n)
|
||||
ON MATCH SET x.foo = 1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UndefinedVariable
|
||||
|
||||
Scenario: Failing when using MATCH after OPTIONAL MATCH
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH ()-->()
|
||||
MATCH ()-->(d)
|
||||
RETURN d
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidClauseComposition
|
||||
|
||||
Scenario: Failing when float value is too large
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 1.34E999
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: FloatingPointOverflow
|
||||
|
||||
Scenario: Handling property access on the Any type
|
||||
When executing query:
|
||||
"""
|
||||
WITH [{prop: 0}, 1] AS list
|
||||
RETURN (list[0]).prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| (list[0]).prop |
|
||||
| 0 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Failing when performing property access on a non-map 1
|
||||
When executing query:
|
||||
"""
|
||||
WITH [{prop: 0}, 1] AS list
|
||||
RETURN (list[1]).prop
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: PropertyAccessOnNonMap
|
||||
|
||||
Scenario: Failing when performing property access on a non-map 2
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (n {prop: 'foo'})
|
||||
WITH n.prop AS n2
|
||||
RETURN n2.prop
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: PropertyAccessOnNonMap
|
||||
|
||||
Scenario: Failing when checking existence of a non-property and non-pattern
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN exists(n.prop + 1)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentExpression
|
||||
|
||||
Scenario: Bad arguments for `range()`
|
||||
When executing query:
|
||||
"""
|
||||
RETURN range(2, 8, 0)
|
||||
"""
|
||||
Then a ArgumentError should be raised at runtime: NumberOutOfRange
|
||||
|
||||
Scenario: Fail for invalid Unicode hyphen in subtraction
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 42 — 41
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidUnicodeCharacter
|
||||
|
||||
Scenario: Failing for `size()` on paths
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (a)-[*]->(b)
|
||||
RETURN size(p)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when using aggregation in list comprehension
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN [x IN [1, 2, 3, 4, 5] | count(*)]
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidAggregation
|
||||
|
||||
Scenario: Failing when using non-constants in SKIP
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
SKIP n.count
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NonConstantExpression
|
||||
|
||||
Scenario: Failing when using negative value in SKIP
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
SKIP -1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NegativeIntegerArgument
|
||||
|
||||
Scenario: Failing when using non-constants in LIMIT
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
LIMIT n.count
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NonConstantExpression
|
||||
|
||||
Scenario: Failing when using negative value in LIMIT
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
LIMIT -1
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NegativeIntegerArgument
|
||||
|
||||
Scenario: Failing when using floating point in LIMIT
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
LIMIT 1.7
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidArgumentType
|
||||
|
||||
Scenario: Failing when creating relationship without type
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()-->()
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NoSingleRelationshipType
|
||||
|
||||
Scenario: Failing when merging relationship without type
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a), (b)
|
||||
MERGE (a)-->(b)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NoSingleRelationshipType
|
||||
|
||||
Scenario: Failing when merging relationship without type, no colon
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a), (b)
|
||||
MERGE (a)-[NO_COLON]->(b)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NoSingleRelationshipType
|
||||
|
||||
Scenario: Failing when creating relationship with more than one type
|
||||
When executing query:
|
||||
"""
|
||||
CREATE ()-[:A|:B]->()
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NoSingleRelationshipType
|
||||
|
||||
Scenario: Failing when merging relationship with more than one type
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a), (b)
|
||||
MERGE (a)-[:A|:B]->(b)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NoSingleRelationshipType
|
@ -0,0 +1,289 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: SetAcceptance
|
||||
|
||||
Scenario: Setting a node property to null removes the existing property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A {property1: 23, property2: 46})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
SET n.property1 = null
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:A {property2: 46}) |
|
||||
And the side effects should be:
|
||||
| -properties | 1 |
|
||||
|
||||
Scenario: Setting a relationship property to null removes the existing property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:REL {property1: 12, property2: 24}]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]->()
|
||||
SET r.property1 = null
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| [:REL {property2: 24}] |
|
||||
And the side effects should be:
|
||||
| -properties | 1 |
|
||||
|
||||
Scenario: Set a property
|
||||
Given any graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A {name: 'Andres'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
WHERE n.name = 'Andres'
|
||||
SET n.name = 'Michael'
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:A {name: 'Michael'}) |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
| -properties | 1 |
|
||||
|
||||
Scenario: Set a property to an expression
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A {name: 'Andres'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
WHERE n.name = 'Andres'
|
||||
SET n.name = n.name + ' was here'
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:A {name: 'Andres was here'}) |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
| -properties | 1 |
|
||||
|
||||
Scenario: Set a property by selecting the node using a simple expression
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
SET (n).name = 'neo4j'
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:A {name: 'neo4j'}) |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Set a property by selecting the relationship using a simple expression
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:REL]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r:REL]->()
|
||||
SET (r).name = 'neo4j'
|
||||
RETURN r
|
||||
"""
|
||||
Then the result should be:
|
||||
| r |
|
||||
| [:REL {name: 'neo4j'}] |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Setting a property to null removes the property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A {name: 'Michael', age: 35})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE n.name = 'Michael'
|
||||
SET n.name = null
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:A {age: 35}) |
|
||||
And the side effects should be:
|
||||
| -properties | 1 |
|
||||
|
||||
Scenario: Add a label to a node
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
SET n:Foo
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:A:Foo) |
|
||||
And the side effects should be:
|
||||
| +labels | 1 |
|
||||
|
||||
Scenario: Adding a list property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
SET n.x = [1, 2, 3]
|
||||
RETURN [i IN n.x | i / 2.0] AS x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| [0.5, 1.0, 1.5] |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Concatenate elements onto a list property
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a {foo: [1, 2, 3]})
|
||||
SET a.foo = a.foo + [4, 5]
|
||||
RETURN a.foo
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.foo |
|
||||
| [1, 2, 3, 4, 5] |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Concatenate elements in reverse onto a list property
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
CREATE (a {foo: [3, 4, 5]})
|
||||
SET a.foo = [1, 2] + a.foo
|
||||
RETURN a.foo
|
||||
"""
|
||||
Then the result should be:
|
||||
| a.foo |
|
||||
| [1, 2, 3, 4, 5] |
|
||||
And the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Overwrite values when using +=
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:X {foo: 'A', bar: 'B'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:X {foo: 'A'})
|
||||
SET n += {bar: 'C'}
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:X {foo: 'A', bar: 'C'}) |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
| -properties | 1 |
|
||||
|
||||
Scenario: Retain old values when using +=
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:X {foo: 'A'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:X {foo: 'A'})
|
||||
SET n += {bar: 'B'}
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:X {foo: 'A', bar: 'B'}) |
|
||||
And the side effects should be:
|
||||
| +properties | 1 |
|
||||
|
||||
Scenario: Explicit null values in a map remove old values
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:X {foo: 'A', bar: 'B'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:X {foo: 'A'})
|
||||
SET n += {foo: null}
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:X {bar: 'B'}) |
|
||||
And the side effects should be:
|
||||
| -properties | 1 |
|
||||
|
||||
Scenario: Non-existent values in a property map are removed with SET =
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:X {foo: 'A', bar: 'B'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:X {foo: 'A'})
|
||||
SET n = {foo: 'B', baz: 'C'}
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:X {foo: 'B', baz: 'C'}) |
|
||||
And the side effects should be:
|
||||
| +properties | 2 |
|
||||
| -properties | 2 |
|
@ -0,0 +1,71 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: SkipLimitAcceptanceTest
|
||||
|
||||
Background:
|
||||
Given any graph
|
||||
|
||||
Scenario: SKIP with an expression that depends on variables should fail
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n) RETURN n SKIP n.count
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NonConstantExpression
|
||||
|
||||
Scenario: LIMIT with an expression that depends on variables should fail
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n) RETURN n LIMIT n.count
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NonConstantExpression
|
||||
|
||||
Scenario: SKIP with an expression that does not depend on variables
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(1, 10) AS i
|
||||
CREATE ({nr: i})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH n SKIP toInteger(rand()*9)
|
||||
WITH count(*) AS count
|
||||
RETURN count > 0 AS nonEmpty
|
||||
"""
|
||||
Then the result should be:
|
||||
| nonEmpty |
|
||||
| true |
|
||||
And no side effects
|
||||
|
||||
|
||||
Scenario: LIMIT with an expression that does not depend on variables
|
||||
And having executed:
|
||||
"""
|
||||
UNWIND range(1, 3) AS i
|
||||
CREATE ({nr: i})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH n LIMIT toInteger(ceil(1.7))
|
||||
RETURN count(*) AS count
|
||||
"""
|
||||
Then the result should be:
|
||||
| count |
|
||||
| 2 |
|
||||
And no side effects
|
@ -0,0 +1,76 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: StartingPointAcceptance
|
||||
|
||||
Scenario: Find all nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'a'}),
|
||||
({name: 'b'}),
|
||||
({name: 'c'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| ({name: 'a'}) |
|
||||
| ({name: 'b'}) |
|
||||
| ({name: 'c'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Find labelled nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'a'}),
|
||||
(:Person),
|
||||
(:Animal),
|
||||
(:Animal)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:Animal)
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| (:Animal) |
|
||||
| (:Animal) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Find nodes by property
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({prop: 1}),
|
||||
({prop: 2})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE n.prop = 2
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| ({prop: 2}) |
|
||||
And no side effects
|
@ -0,0 +1,360 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: StartsWithAcceptance
|
||||
|
||||
Background:
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {name: 'ABCDEF'}), (:Label {name: 'AB'}),
|
||||
(:Label {name: 'abcdef'}), (:Label {name: 'ab'}),
|
||||
(:Label {name: ''}), (:Label)
|
||||
"""
|
||||
|
||||
Scenario: Finding exact matches
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name STARTS WITH 'ABCDEF'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:Label {name: 'ABCDEF'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding beginning of string
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name STARTS WITH 'ABC'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:Label {name: 'ABCDEF'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding end of string 1
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name ENDS WITH 'DEF'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:Label {name: 'ABCDEF'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding end of string 2
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name ENDS WITH 'AB'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:Label {name: 'AB'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding middle of string
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name STARTS WITH 'a'
|
||||
AND a.name ENDS WITH 'f'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:Label {name: 'abcdef'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding the empty string
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name STARTS WITH ''
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:Label {name: 'ABCDEF'}) |
|
||||
| (:Label {name: 'AB'}) |
|
||||
| (:Label {name: 'abcdef'}) |
|
||||
| (:Label {name: 'ab'}) |
|
||||
| (:Label {name: ''}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding when the middle is known
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name CONTAINS 'CD'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:Label {name: 'ABCDEF'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding strings starting with whitespace
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {name: ' Foo '}),
|
||||
(:Label {name: '\nFoo\n'}),
|
||||
(:Label {name: '\tFoo\t'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name STARTS WITH ' '
|
||||
RETURN a.name AS name
|
||||
"""
|
||||
Then the result should be:
|
||||
| name |
|
||||
| ' Foo ' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding strings starting with newline
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {name: ' Foo '}),
|
||||
(:Label {name: '\nFoo\n'}),
|
||||
(:Label {name: '\tFoo\t'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name STARTS WITH '\n'
|
||||
RETURN a.name AS name
|
||||
"""
|
||||
Then the result should be:
|
||||
| name |
|
||||
| '\nFoo\n' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding strings ending with newline
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {name: ' Foo '}),
|
||||
(:Label {name: '\nFoo\n'}),
|
||||
(:Label {name: '\tFoo\t'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name ENDS WITH '\n'
|
||||
RETURN a.name AS name
|
||||
"""
|
||||
Then the result should be:
|
||||
| name |
|
||||
| '\nFoo\n' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding strings ending with whitespace
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {name: ' Foo '}),
|
||||
(:Label {name: '\nFoo\n'}),
|
||||
(:Label {name: '\tFoo\t'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name ENDS WITH ' '
|
||||
RETURN a.name AS name
|
||||
"""
|
||||
Then the result should be:
|
||||
| name |
|
||||
| ' Foo ' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding strings containing whitespace
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {name: ' Foo '}),
|
||||
(:Label {name: '\nFoo\n'}),
|
||||
(:Label {name: '\tFoo\t'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name CONTAINS ' '
|
||||
RETURN a.name AS name
|
||||
"""
|
||||
Then the result should be:
|
||||
| name |
|
||||
| ' Foo ' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Finding strings containing newline
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Label {name: ' Foo '}),
|
||||
(:Label {name: '\nFoo\n'}),
|
||||
(:Label {name: '\tFoo\t'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name CONTAINS '\n'
|
||||
RETURN a.name AS name
|
||||
"""
|
||||
Then the result should be:
|
||||
| name |
|
||||
| '\nFoo\n' |
|
||||
And no side effects
|
||||
|
||||
Scenario: No string starts with null
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name STARTS WITH null
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
And no side effects
|
||||
|
||||
Scenario: No string does not start with null
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE NOT a.name STARTS WITH null
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
And no side effects
|
||||
|
||||
Scenario: No string ends with null
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name ENDS WITH null
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
And no side effects
|
||||
|
||||
Scenario: No string does not end with null
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE NOT a.name ENDS WITH null
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
And no side effects
|
||||
|
||||
Scenario: No string contains null
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name CONTAINS null
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
And no side effects
|
||||
|
||||
Scenario: No string does not contain null
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE NOT a.name CONTAINS null
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
And no side effects
|
||||
|
||||
Scenario: Combining string operators
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE a.name STARTS WITH 'A'
|
||||
AND a.name CONTAINS 'C'
|
||||
AND a.name ENDS WITH 'EF'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:Label {name: 'ABCDEF'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: NOT with CONTAINS
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WHERE NOT a.name CONTAINS 'b'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:Label {name: 'ABCDEF'}) |
|
||||
| (:Label {name: 'AB'}) |
|
||||
| (:Label {name: ''}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling non-string operands for STARTS WITH
|
||||
When executing query:
|
||||
"""
|
||||
WITH [1, 3.14, true, [], {}, null] AS operands
|
||||
UNWIND operands AS op1
|
||||
UNWIND operands AS op2
|
||||
WITH op1 STARTS WITH op2 AS v
|
||||
RETURN v, count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| v | count(*) |
|
||||
| null | 36 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling non-string operands for CONTAINS
|
||||
When executing query:
|
||||
"""
|
||||
WITH [1, 3.14, true, [], {}, null] AS operands
|
||||
UNWIND operands AS op1
|
||||
UNWIND operands AS op2
|
||||
WITH op1 STARTS WITH op2 AS v
|
||||
RETURN v, count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| v | count(*) |
|
||||
| null | 36 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling non-string operands for ENDS WITH
|
||||
When executing query:
|
||||
"""
|
||||
WITH [1, 3.14, true, [], {}, null] AS operands
|
||||
UNWIND operands AS op1
|
||||
UNWIND operands AS op2
|
||||
WITH op1 STARTS WITH op2 AS v
|
||||
RETURN v, count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| v | count(*) |
|
||||
| null | 36 |
|
||||
And no side effects
|
@ -0,0 +1,50 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: SyntaxErrorAcceptance
|
||||
|
||||
Background:
|
||||
Given any graph
|
||||
|
||||
Scenario: Using a non-existent function
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
RETURN foo(a)
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: UnknownFunction
|
||||
|
||||
Scenario: Using `rand()` in aggregations
|
||||
When executing query:
|
||||
"""
|
||||
RETURN count(rand())
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: NonConstantExpression
|
||||
|
||||
Scenario: Supplying invalid hexadecimal literal 1
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 0x23G34
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidNumberLiteral
|
||||
|
||||
Scenario: Supplying invalid hexadecimal literal 2
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 0x23j
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidNumberLiteral
|
@ -0,0 +1,161 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: TernaryLogicAcceptanceTest
|
||||
|
||||
Background:
|
||||
Given any graph
|
||||
|
||||
Scenario: The inverse of a null is a null
|
||||
When executing query:
|
||||
"""
|
||||
RETURN NOT null AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: A literal null IS null
|
||||
When executing query:
|
||||
"""
|
||||
RETURN null IS NULL AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| true |
|
||||
And no side effects
|
||||
|
||||
Scenario: A literal null is not IS NOT null
|
||||
When executing query:
|
||||
"""
|
||||
RETURN null IS NOT NULL AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| false |
|
||||
And no side effects
|
||||
|
||||
Scenario: It is unknown - i.e. null - if a null is equal to a null
|
||||
When executing query:
|
||||
"""
|
||||
RETURN null = null AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario: It is unknown - i.e. null - if a null is not equal to a null
|
||||
When executing query:
|
||||
"""
|
||||
RETURN null <> null AS value
|
||||
"""
|
||||
Then the result should be:
|
||||
| value |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario Outline: Using null in AND
|
||||
And parameters are:
|
||||
| par | val |
|
||||
| lhs | <lhs> |
|
||||
| rhs | <rhs> |
|
||||
When executing query:
|
||||
"""
|
||||
RETURN $lhs AND $rhs AS result
|
||||
"""
|
||||
Then the result should be:
|
||||
| result |
|
||||
| <result> |
|
||||
And no side effects
|
||||
|
||||
Examples:
|
||||
| lhs | rhs | result |
|
||||
| null | null | null |
|
||||
| null | true | null |
|
||||
| true | null | null |
|
||||
| null | false | false |
|
||||
| false | null | false |
|
||||
|
||||
Scenario Outline: Using null in OR
|
||||
And parameters are:
|
||||
| par | val |
|
||||
| lhs | <lhs> |
|
||||
| rhs | <rhs> |
|
||||
When executing query:
|
||||
"""
|
||||
RETURN $lhs OR $rhs AS result
|
||||
"""
|
||||
Then the result should be:
|
||||
| result |
|
||||
| <result> |
|
||||
And no side effects
|
||||
|
||||
Examples:
|
||||
| lhs | rhs | result |
|
||||
| null | null | null |
|
||||
| null | true | true |
|
||||
| true | null | true |
|
||||
| null | false | null |
|
||||
| false | null | null |
|
||||
|
||||
Scenario Outline: Using null in XOR
|
||||
And parameters are:
|
||||
| par | val |
|
||||
| lhs | <lhs> |
|
||||
| rhs | <rhs> |
|
||||
When executing query:
|
||||
"""
|
||||
RETURN $lhs XOR $rhs AS result
|
||||
"""
|
||||
Then the result should be:
|
||||
| result |
|
||||
| <result> |
|
||||
And no side effects
|
||||
|
||||
Examples:
|
||||
| lhs | rhs | result |
|
||||
| null | null | null |
|
||||
| null | true | null |
|
||||
| true | null | null |
|
||||
| null | false | null |
|
||||
| false | null | null |
|
||||
|
||||
Scenario Outline: Using null in IN
|
||||
And parameters are:
|
||||
| par | val |
|
||||
| elt | <elt> |
|
||||
| coll | <coll> |
|
||||
When executing query:
|
||||
"""
|
||||
RETURN $elt IN $coll AS result
|
||||
"""
|
||||
Then the result should be:
|
||||
| result |
|
||||
| <result> |
|
||||
And no side effects
|
||||
|
||||
Examples:
|
||||
| elt | coll | result |
|
||||
| null | null | null |
|
||||
| null | [1, 2, 3] | null |
|
||||
| null | [1, 2, 3, null] | null |
|
||||
| null | [] | false |
|
||||
| 1 | [1, 2, 3, null] | true |
|
||||
| 1 | [null, 1] | true |
|
||||
| 5 | [1, 2, 3, null] | null |
|
@ -0,0 +1,327 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: TriadicSelection
|
||||
|
||||
Scenario: Handling triadic friend of a friend
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b)-->(c)
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b2' |
|
||||
| 'b3' |
|
||||
| 'c11' |
|
||||
| 'c12' |
|
||||
| 'c21' |
|
||||
| 'c22' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is not a friend
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b3' |
|
||||
| 'c11' |
|
||||
| 'c12' |
|
||||
| 'c21' |
|
||||
| 'c22' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is not a friend with different relationship type
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:FOLLOWS]->(c)
|
||||
WITH c WHERE r IS NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b2' |
|
||||
| 'c11' |
|
||||
| 'c12' |
|
||||
| 'c21' |
|
||||
| 'c22' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is not a friend with superset of relationship type
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r]->(c)
|
||||
WITH c WHERE r IS NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'c11' |
|
||||
| 'c12' |
|
||||
| 'c21' |
|
||||
| 'c22' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is not a friend with implicit subset of relationship type
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b3' |
|
||||
| 'b4' |
|
||||
| 'c11' |
|
||||
| 'c12' |
|
||||
| 'c21' |
|
||||
| 'c22' |
|
||||
| 'c31' |
|
||||
| 'c32' |
|
||||
| 'c41' |
|
||||
| 'c42' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is not a friend with explicit subset of relationship type
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS|FOLLOWS]->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b3' |
|
||||
| 'b4' |
|
||||
| 'c11' |
|
||||
| 'c12' |
|
||||
| 'c21' |
|
||||
| 'c22' |
|
||||
| 'c31' |
|
||||
| 'c32' |
|
||||
| 'c41' |
|
||||
| 'c42' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is not a friend with same labels
|
||||
Given the binary-tree-2 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b:X)-->(c:X)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b3' |
|
||||
| 'c11' |
|
||||
| 'c21' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is not a friend with different labels
|
||||
Given the binary-tree-2 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b:X)-->(c:Y)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'c12' |
|
||||
| 'c22' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is not a friend with implicit subset of labels
|
||||
Given the binary-tree-2 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b)-->(c:X)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b3' |
|
||||
| 'c11' |
|
||||
| 'c21' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is not a friend with implicit superset of labels
|
||||
Given the binary-tree-2 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b:X)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b3' |
|
||||
| 'c11' |
|
||||
| 'c12' |
|
||||
| 'c21' |
|
||||
| 'c22' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is a friend
|
||||
Given the binary-tree-2 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NOT NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b2' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is a friend with different relationship type
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:FOLLOWS]->(c)
|
||||
WITH c WHERE r IS NOT NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b3' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is a friend with superset of relationship type
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r]->(c)
|
||||
WITH c WHERE r IS NOT NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b2' |
|
||||
| 'b3' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is a friend with implicit subset of relationship type
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NOT NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b1' |
|
||||
| 'b2' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is a friend with explicit subset of relationship type
|
||||
Given the binary-tree-1 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS|FOLLOWS]->(b)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NOT NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b1' |
|
||||
| 'b2' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is a friend with same labels
|
||||
Given the binary-tree-2 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b:X)-->(c:X)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NOT NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b2' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is a friend with different labels
|
||||
Given the binary-tree-2 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b:X)-->(c:Y)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NOT NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is a friend with implicit subset of labels
|
||||
Given the binary-tree-2 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b)-->(c:X)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NOT NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b2' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling triadic friend of a friend that is a friend with implicit superset of labels
|
||||
Given the binary-tree-2 graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[:KNOWS]->(b:X)-->(c)
|
||||
OPTIONAL MATCH (a)-[r:KNOWS]->(c)
|
||||
WITH c WHERE r IS NOT NULL
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'b2' |
|
||||
And no side effects
|
@ -0,0 +1,416 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: TypeConversionFunctions
|
||||
|
||||
Scenario: `toBoolean()` on valid literal string
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN toBoolean('true') AS b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| true |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toBoolean()` on booleans
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [true, false] AS b
|
||||
RETURN toBoolean(b) AS b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| true |
|
||||
| false |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toBoolean()` on variables with valid string values
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['true', 'false'] AS s
|
||||
RETURN toBoolean(s) AS b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| true |
|
||||
| false |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toBoolean()` on invalid strings
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [null, '', ' tru ', 'f alse'] AS things
|
||||
RETURN toBoolean(things) AS b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| null |
|
||||
| null |
|
||||
| null |
|
||||
| null |
|
||||
And no side effects
|
||||
|
||||
Scenario Outline: `toBoolean()` on invalid types
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [true, <invalid>] AS list
|
||||
RETURN toBoolean(list[1]) AS b
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: InvalidArgumentValue
|
||||
|
||||
Examples:
|
||||
| invalid |
|
||||
| [] |
|
||||
| {} |
|
||||
| 1 |
|
||||
| 1.0 |
|
||||
|
||||
|
||||
Scenario: `toInteger()`
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Person {age: '42'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (p:Person { age: '42' })
|
||||
WITH *
|
||||
MATCH (n)
|
||||
RETURN toInteger(n.age) AS age
|
||||
"""
|
||||
Then the result should be:
|
||||
| age |
|
||||
| 42 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toInteger()` on float
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH 82.9 AS weight
|
||||
RETURN toInteger(weight)
|
||||
"""
|
||||
Then the result should be:
|
||||
| toInteger(weight) |
|
||||
| 82 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toInteger()` returning null on non-numerical string
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH 'foo' AS foo_string, '' AS empty_string
|
||||
RETURN toInteger(foo_string) AS foo, toInteger(empty_string) AS empty
|
||||
"""
|
||||
Then the result should be:
|
||||
| foo | empty |
|
||||
| null | null |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toInteger()` handling mixed number types
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [2, 2.9] AS numbers
|
||||
RETURN [n IN numbers | toInteger(n)] AS int_numbers
|
||||
"""
|
||||
Then the result should be:
|
||||
| int_numbers |
|
||||
| [2, 2] |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toInteger()` handling Any type
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [2, 2.9, '1.7'] AS things
|
||||
RETURN [n IN things | toInteger(n)] AS int_numbers
|
||||
"""
|
||||
Then the result should be:
|
||||
| int_numbers |
|
||||
| [2, 2, 1] |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toInteger()` on a list of strings
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH ['2', '2.9', 'foo'] AS numbers
|
||||
RETURN [n IN numbers | toInteger(n)] AS int_numbers
|
||||
"""
|
||||
Then the result should be:
|
||||
| int_numbers |
|
||||
| [2, 2, null] |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toInteger()` on a complex-typed expression
|
||||
Given any graph
|
||||
And parameters are:
|
||||
| param | 1 |
|
||||
When executing query:
|
||||
"""
|
||||
RETURN toInteger(1 - $param) AS result
|
||||
"""
|
||||
Then the result should be:
|
||||
| result |
|
||||
| 0 |
|
||||
And no side effects
|
||||
|
||||
Scenario Outline: `toInteger()` failing on invalid arguments
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-[r:T]->()
|
||||
RETURN [x IN [1, <invalid>] | toInteger(x) ] AS list
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: InvalidArgumentValue
|
||||
|
||||
Examples:
|
||||
| invalid |
|
||||
| true |
|
||||
| [] |
|
||||
| {} |
|
||||
| n |
|
||||
| r |
|
||||
| p |
|
||||
|
||||
Scenario: `toFloat()`
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Movie {rating: 4})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (m:Movie { rating: 4 })
|
||||
WITH *
|
||||
MATCH (n)
|
||||
RETURN toFloat(n.rating) AS float
|
||||
"""
|
||||
Then the result should be:
|
||||
| float |
|
||||
| 4.0 |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toFloat()` on mixed number types
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [3.4, 3] AS numbers
|
||||
RETURN [n IN numbers | toFloat(n)] AS float_numbers
|
||||
"""
|
||||
Then the result should be:
|
||||
| float_numbers |
|
||||
| [3.4, 3.0] |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toFloat()` returning null on non-numerical string
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH 'foo' AS foo_string, '' AS empty_string
|
||||
RETURN toFloat(foo_string) AS foo, toFloat(empty_string) AS empty
|
||||
"""
|
||||
Then the result should be:
|
||||
| foo | empty |
|
||||
| null | null |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toFloat()` handling Any type
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [3.4, 3, '5'] AS numbers
|
||||
RETURN [n IN numbers | toFloat(n)] AS float_numbers
|
||||
"""
|
||||
Then the result should be:
|
||||
| float_numbers |
|
||||
| [3.4, 3.0, 5.0] |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toFloat()` on a list of strings
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH ['1', '2', 'foo'] AS numbers
|
||||
RETURN [n IN numbers | toFloat(n)] AS float_numbers
|
||||
"""
|
||||
Then the result should be:
|
||||
| float_numbers |
|
||||
| [1.0, 2.0, null] |
|
||||
And no side effects
|
||||
|
||||
Scenario Outline: `toFloat()` failing on invalid arguments
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-[r:T]->()
|
||||
RETURN [x IN [1.0, <invalid>] | toFloat(x) ] AS list
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: InvalidArgumentValue
|
||||
|
||||
Examples:
|
||||
| invalid |
|
||||
| true |
|
||||
| [] |
|
||||
| {} |
|
||||
| n |
|
||||
| r |
|
||||
| p |
|
||||
|
||||
Scenario: `toString()`
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Movie {rating: 4})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (m:Movie { rating: 4 })
|
||||
WITH *
|
||||
MATCH (n)
|
||||
RETURN toString(n.rating)
|
||||
"""
|
||||
Then the result should be:
|
||||
| toString(n.rating) |
|
||||
| '4' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toString()` handling boolean properties
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Movie {watched: true})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (m:Movie)
|
||||
RETURN toString(m.watched)
|
||||
"""
|
||||
Then the result should be:
|
||||
| toString(m.watched) |
|
||||
| 'true' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toString()` handling inlined boolean
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN toString(1 < 0) AS bool
|
||||
"""
|
||||
Then the result should be:
|
||||
| bool |
|
||||
| 'false' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toString()` handling boolean literal
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN toString(true) AS bool
|
||||
"""
|
||||
Then the result should be:
|
||||
| bool |
|
||||
| 'true' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toString()` should work on Any type
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN [x IN [1, 2.3, true, 'apa'] | toString(x) ] AS list
|
||||
"""
|
||||
Then the result should be:
|
||||
| list |
|
||||
| ['1', '2.3', 'true', 'apa'] |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toString()` on a list of integers
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [1, 2, 3] AS numbers
|
||||
RETURN [n IN numbers | toString(n)] AS string_numbers
|
||||
"""
|
||||
Then the result should be:
|
||||
| string_numbers |
|
||||
| ['1', '2', '3'] |
|
||||
And no side effects
|
||||
|
||||
Scenario Outline: `toString()` failing on invalid arguments
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:T]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH p = (n)-[r:T]->()
|
||||
RETURN [x IN [1, '', <invalid>] | toString(x) ] AS list
|
||||
"""
|
||||
Then a TypeError should be raised at runtime: InvalidArgumentValue
|
||||
|
||||
Examples:
|
||||
| invalid |
|
||||
| [] |
|
||||
| {} |
|
||||
| n |
|
||||
| r |
|
||||
| p |
|
||||
|
||||
Scenario: `toString()` should accept potentially correct types 1
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['male', 'female', null] AS gen
|
||||
RETURN coalesce(toString(gen), 'x') AS result
|
||||
"""
|
||||
Then the result should be:
|
||||
| result |
|
||||
| 'male' |
|
||||
| 'female' |
|
||||
| 'x' |
|
||||
And no side effects
|
||||
|
||||
Scenario: `toString()` should accept potentially correct types 2
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND ['male', 'female', null] AS gen
|
||||
RETURN toString(coalesce(gen, 'x')) AS result
|
||||
"""
|
||||
Then the result should be:
|
||||
| result |
|
||||
| 'male' |
|
||||
| 'female' |
|
||||
| 'x' |
|
||||
And no side effects
|
@ -0,0 +1,99 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: UnionAcceptance
|
||||
|
||||
Scenario: Should be able to create text output from union queries
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A), (:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)
|
||||
RETURN a AS a
|
||||
UNION
|
||||
MATCH (b:B)
|
||||
RETURN b AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:A) |
|
||||
| (:B) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Two elements, both unique, not distinct
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 1 AS x
|
||||
UNION ALL
|
||||
RETURN 2 AS x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| 1 |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Two elements, both unique, distinct
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 1 AS x
|
||||
UNION
|
||||
RETURN 2 AS x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| 1 |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Three elements, two unique, distinct
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 2 AS x
|
||||
UNION
|
||||
RETURN 1 AS x
|
||||
UNION
|
||||
RETURN 2 AS x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| 2 |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Three elements, two unique, not distinct
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN 2 AS x
|
||||
UNION ALL
|
||||
RETURN 1 AS x
|
||||
UNION ALL
|
||||
RETURN 2 AS x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| 2 |
|
||||
| 1 |
|
||||
| 2 |
|
||||
And no side effects
|
@ -0,0 +1,268 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: UnwindAcceptance
|
||||
|
||||
Scenario: Unwinding a list
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 2, 3] AS x
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| 1 |
|
||||
| 2 |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwinding a range
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND range(1, 3) AS x
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| 1 |
|
||||
| 2 |
|
||||
| 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwinding a concatenation of lists
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [1, 2, 3] AS first, [4, 5, 6] AS second
|
||||
UNWIND (first + second) AS x
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| 1 |
|
||||
| 2 |
|
||||
| 3 |
|
||||
| 4 |
|
||||
| 5 |
|
||||
| 6 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwinding a collected unwound expression
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND RANGE(1, 2) AS row
|
||||
WITH collect(row) AS rows
|
||||
UNWIND rows AS x
|
||||
RETURN x
|
||||
"""
|
||||
Then the result should be:
|
||||
| x |
|
||||
| 1 |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwinding a collected expression
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({id: 1}), ({id: 2})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (row)
|
||||
WITH collect(row) AS rows
|
||||
UNWIND rows AS node
|
||||
RETURN node.id
|
||||
"""
|
||||
Then the result should be:
|
||||
| node.id |
|
||||
| 1 |
|
||||
| 2 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Creating nodes from an unwound parameter list
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Year {year: 2016})
|
||||
"""
|
||||
And parameters are:
|
||||
| events | [{year: 2016, id: 1}, {year: 2016, id: 2}] |
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND $events AS event
|
||||
MATCH (y:Year {year: event.year})
|
||||
MERGE (e:Event {id: event.id})
|
||||
MERGE (y)<-[:IN]-(e)
|
||||
RETURN e.id AS x
|
||||
ORDER BY x
|
||||
"""
|
||||
Then the result should be, in order:
|
||||
| x |
|
||||
| 1 |
|
||||
| 2 |
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +relationships | 2 |
|
||||
| +labels | 1 |
|
||||
| +properties | 2 |
|
||||
|
||||
Scenario: Double unwinding a list of lists
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [[1, 2, 3], [4, 5, 6]] AS lol
|
||||
UNWIND lol AS x
|
||||
UNWIND x AS y
|
||||
RETURN y
|
||||
"""
|
||||
Then the result should be:
|
||||
| y |
|
||||
| 1 |
|
||||
| 2 |
|
||||
| 3 |
|
||||
| 4 |
|
||||
| 5 |
|
||||
| 6 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwinding the empty list
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [] AS empty
|
||||
RETURN empty
|
||||
"""
|
||||
Then the result should be:
|
||||
| empty |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwinding null
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND null AS nil
|
||||
RETURN nil
|
||||
"""
|
||||
Then the result should be:
|
||||
| nil |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwinding list with duplicates
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND [1, 1, 2, 2, 3, 3, 4, 4, 5, 5] AS duplicate
|
||||
RETURN duplicate
|
||||
"""
|
||||
Then the result should be:
|
||||
| duplicate |
|
||||
| 1 |
|
||||
| 1 |
|
||||
| 2 |
|
||||
| 2 |
|
||||
| 3 |
|
||||
| 3 |
|
||||
| 4 |
|
||||
| 4 |
|
||||
| 5 |
|
||||
| 5 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwind does not prune context
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [1, 2, 3] AS list
|
||||
UNWIND list AS x
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| list | x |
|
||||
| [1, 2, 3] | 1 |
|
||||
| [1, 2, 3] | 2 |
|
||||
| [1, 2, 3] | 3 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwind does not remove variables from scope
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (s:S),
|
||||
(n),
|
||||
(e:E),
|
||||
(s)-[:X]->(e),
|
||||
(s)-[:Y]->(e),
|
||||
(n)-[:Y]->(e)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:S)-[:X]->(b1)
|
||||
WITH a, collect(b1) AS bees
|
||||
UNWIND bees AS b2
|
||||
MATCH (a)-[:Y]->(b2)
|
||||
RETURN a, b2
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b2 |
|
||||
| (:S) | (:E) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Multiple unwinds after each other
|
||||
Given any graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH [1, 2] AS xs, [3, 4] AS ys, [5, 6] AS zs
|
||||
UNWIND xs AS x
|
||||
UNWIND ys AS y
|
||||
UNWIND zs AS z
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| x | xs | y | ys | z | zs |
|
||||
| 1 | [1, 2] | 3 | [3, 4] | 5 | [5, 6] |
|
||||
| 1 | [1, 2] | 3 | [3, 4] | 6 | [5, 6] |
|
||||
| 1 | [1, 2] | 4 | [3, 4] | 5 | [5, 6] |
|
||||
| 1 | [1, 2] | 4 | [3, 4] | 6 | [5, 6] |
|
||||
| 2 | [1, 2] | 3 | [3, 4] | 5 | [5, 6] |
|
||||
| 2 | [1, 2] | 3 | [3, 4] | 6 | [5, 6] |
|
||||
| 2 | [1, 2] | 4 | [3, 4] | 5 | [5, 6] |
|
||||
| 2 | [1, 2] | 4 | [3, 4] | 6 | [5, 6] |
|
||||
And no side effects
|
||||
|
||||
Scenario: Unwind with merge
|
||||
Given an empty graph
|
||||
And parameters are:
|
||||
| props | [{login: 'login1', name: 'name1'}, {login: 'login2', name: 'name2'}] |
|
||||
When executing query:
|
||||
"""
|
||||
UNWIND $props AS prop
|
||||
MERGE (p:Person {login: prop.login})
|
||||
SET p.name = prop.name
|
||||
RETURN p.name, p.login
|
||||
"""
|
||||
Then the result should be:
|
||||
| p.name | p.login |
|
||||
| 'name1' | 'login1' |
|
||||
| 'name2' | 'login2' |
|
||||
And the side effects should be:
|
||||
| +nodes | 2 |
|
||||
| +labels | 1 |
|
||||
| +properties | 4 |
|
@ -0,0 +1,657 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: VarLengthAcceptance
|
||||
|
||||
# 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: Handling unbounded variable length match
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)
|
||||
MATCH (a)-[:LIKES*]->(c)
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
| 'n0000' |
|
||||
| 'n0001' |
|
||||
| 'n0010' |
|
||||
| 'n0011' |
|
||||
| 'n0100' |
|
||||
| 'n0101' |
|
||||
| 'n0110' |
|
||||
| 'n0111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handling explicitly unbounded variable length match
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)
|
||||
MATCH (a)-[:LIKES*..]->(c)
|
||||
RETURN c.name
|
||||
"""
|
||||
Then the result should be:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
| 'n0000' |
|
||||
| 'n0001' |
|
||||
| 'n0010' |
|
||||
| 'n0011' |
|
||||
| 'n0100' |
|
||||
| 'n0101' |
|
||||
| 'n0110' |
|
||||
| 'n0111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Fail when asterisk operator is missing
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)
|
||||
MATCH (a)-[:LIKES..]->(c)
|
||||
RETURN c.name
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidRelationshipPattern
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n0' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n0' |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n0' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Fail on negative bound
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)
|
||||
MATCH (a)-[:LIKES*-2]->(c)
|
||||
RETURN c.name
|
||||
"""
|
||||
Then a SyntaxError should be raised at compile time: InvalidRelationshipPattern
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n0' |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
| 'n0000' |
|
||||
| 'n0001' |
|
||||
| 'n0010' |
|
||||
| 'n0011' |
|
||||
| 'n0100' |
|
||||
| 'n0101' |
|
||||
| 'n0110' |
|
||||
| 'n0111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
| 'n0000' |
|
||||
| 'n0001' |
|
||||
| 'n0010' |
|
||||
| 'n0011' |
|
||||
| 'n0100' |
|
||||
| 'n0101' |
|
||||
| 'n0110' |
|
||||
| 'n0111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
| 'n0000' |
|
||||
| 'n0001' |
|
||||
| 'n0010' |
|
||||
| 'n0011' |
|
||||
| 'n0100' |
|
||||
| 'n0101' |
|
||||
| 'n0110' |
|
||||
| 'n0111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00' |
|
||||
| 'n01' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n000' |
|
||||
| 'n001' |
|
||||
| 'n010' |
|
||||
| 'n011' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n0000' |
|
||||
| 'n0001' |
|
||||
| 'n0010' |
|
||||
| 'n0011' |
|
||||
| 'n0100' |
|
||||
| 'n0101' |
|
||||
| 'n0110' |
|
||||
| 'n0111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n0000' |
|
||||
| 'n0001' |
|
||||
| 'n0010' |
|
||||
| 'n0011' |
|
||||
| 'n0100' |
|
||||
| 'n0101' |
|
||||
| 'n0110' |
|
||||
| 'n0111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00000' |
|
||||
| 'n00001' |
|
||||
| 'n00010' |
|
||||
| 'n00011' |
|
||||
| 'n00100' |
|
||||
| 'n00101' |
|
||||
| 'n00110' |
|
||||
| 'n00111' |
|
||||
| 'n01000' |
|
||||
| 'n01001' |
|
||||
| 'n01010' |
|
||||
| 'n01011' |
|
||||
| 'n01100' |
|
||||
| 'n01101' |
|
||||
| 'n01110' |
|
||||
| 'n01111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00000' |
|
||||
| 'n00001' |
|
||||
| 'n00010' |
|
||||
| 'n00011' |
|
||||
| 'n00100' |
|
||||
| 'n00101' |
|
||||
| 'n00110' |
|
||||
| 'n00111' |
|
||||
| 'n01000' |
|
||||
| 'n01001' |
|
||||
| 'n01010' |
|
||||
| 'n01011' |
|
||||
| 'n01100' |
|
||||
| 'n01101' |
|
||||
| 'n01110' |
|
||||
| 'n01111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00000' |
|
||||
| 'n00001' |
|
||||
| 'n00010' |
|
||||
| 'n00011' |
|
||||
| 'n00100' |
|
||||
| 'n00101' |
|
||||
| 'n00110' |
|
||||
| 'n00111' |
|
||||
| 'n01000' |
|
||||
| 'n01001' |
|
||||
| 'n01010' |
|
||||
| 'n01011' |
|
||||
| 'n01100' |
|
||||
| 'n01101' |
|
||||
| 'n01110' |
|
||||
| 'n01111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00000' |
|
||||
| 'n00001' |
|
||||
| 'n00010' |
|
||||
| 'n00011' |
|
||||
| 'n00100' |
|
||||
| 'n00101' |
|
||||
| 'n00110' |
|
||||
| 'n00111' |
|
||||
| 'n01000' |
|
||||
| 'n01001' |
|
||||
| 'n01010' |
|
||||
| 'n01011' |
|
||||
| 'n01100' |
|
||||
| 'n01101' |
|
||||
| 'n01110' |
|
||||
| 'n01111' |
|
||||
And no side effects
|
||||
|
||||
Scenario: 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:
|
||||
| c.name |
|
||||
| 'n00000' |
|
||||
| 'n00001' |
|
||||
| 'n00010' |
|
||||
| 'n00011' |
|
||||
| 'n00100' |
|
||||
| 'n00101' |
|
||||
| 'n00110' |
|
||||
| 'n00111' |
|
||||
| 'n01000' |
|
||||
| 'n01001' |
|
||||
| 'n01010' |
|
||||
| 'n01011' |
|
||||
| 'n01100' |
|
||||
| 'n01101' |
|
||||
| 'n01110' |
|
||||
| 'n01111' |
|
||||
And no side effects
|
@ -0,0 +1,41 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: VarLengthAcceptance2
|
||||
|
||||
Scenario: Handling relationships that are already bound in variable length paths
|
||||
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:
|
||||
| c |
|
||||
| 32 |
|
||||
And no side effects
|
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: WhereAcceptance
|
||||
|
||||
Scenario: NOT and false
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'a'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WHERE NOT(n.name = 'apa' AND false)
|
||||
RETURN n
|
||||
"""
|
||||
Then the result should be:
|
||||
| n |
|
||||
| ({name: 'a'}) |
|
||||
And no side effects
|
@ -0,0 +1,363 @@
|
||||
#
|
||||
# Copyright (c) 2015-2018 "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.
|
||||
#
|
||||
|
||||
Feature: WithAcceptance
|
||||
|
||||
Scenario: Passing on pattern nodes
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:REL]->(:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)
|
||||
WITH a
|
||||
MATCH (a)-->(b)
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
| (:A) | (:B) |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY and LIMIT can be used
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:A), (), (), (),
|
||||
(a)-[:REL]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)
|
||||
WITH a
|
||||
ORDER BY a.name
|
||||
LIMIT 1
|
||||
MATCH (a)-->(b)
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| (:A) |
|
||||
And no side effects
|
||||
|
||||
Scenario: No dependencies between the query parts
|
||||
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:
|
||||
| a | b |
|
||||
| (:A) | (:A) |
|
||||
| (:A) | (:B) |
|
||||
| (:B) | (:A) |
|
||||
| (:B) | (:B) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Aliasing
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:Begin {prop: 42}),
|
||||
(:End {prop: 42}),
|
||||
(:End {prop: 3})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:Begin)
|
||||
WITH a.prop AS property
|
||||
MATCH (b:End)
|
||||
WHERE property = b.prop
|
||||
RETURN b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| (:End {prop: 42}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handle dependencies across WITH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:End {prop: 42, id: 0}),
|
||||
(:End {prop: 3}),
|
||||
(:Begin {prop: a.id})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:Begin)
|
||||
WITH a.prop AS property
|
||||
LIMIT 1
|
||||
MATCH (b)
|
||||
WHERE b.id = property
|
||||
RETURN b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| (:End {prop: 42, id: 0}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Handle dependencies across WITH with SKIP
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a {prop: 'A', key: 0, id: 0}),
|
||||
({prop: 'B', key: a.id, id: 1}),
|
||||
({prop: 'C', key: 0, id: 2})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH a.prop AS property, a.key AS idToUse
|
||||
ORDER BY property
|
||||
SKIP 1
|
||||
MATCH (b)
|
||||
WHERE b.id = idToUse
|
||||
RETURN DISTINCT b
|
||||
"""
|
||||
Then the result should be:
|
||||
| b |
|
||||
| ({prop: 'A', key: 0, id: 0}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: WHERE after WITH should filter results
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'A'}),
|
||||
({name: 'B'}),
|
||||
({name: 'C'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH a
|
||||
WHERE a.name = 'B'
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| ({name: 'B'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: WHERE after WITH can filter on top of an aggregation
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a {name: 'A'}),
|
||||
(b {name: 'B'})
|
||||
CREATE (a)-[:REL]->(),
|
||||
(a)-[:REL]->(),
|
||||
(a)-[:REL]->(),
|
||||
(b)-[:REL]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)-->()
|
||||
WITH a, count(*) AS relCount
|
||||
WHERE relCount > 1
|
||||
RETURN a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| ({name: 'A'}) |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY on an aggregating key
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({bar: 'A'}),
|
||||
({bar: 'A'}),
|
||||
({bar: 'B'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH a.bar AS bars, count(*) AS relCount
|
||||
ORDER BY a.bar
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| bars | relCount |
|
||||
| 'A' | 2 |
|
||||
| 'B' | 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: ORDER BY a DISTINCT column
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({bar: 'A'}),
|
||||
({bar: 'A'}),
|
||||
({bar: 'B'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH DISTINCT a.bar AS bars
|
||||
ORDER BY a.bar
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| bars |
|
||||
| 'A' |
|
||||
| 'B' |
|
||||
And no side effects
|
||||
|
||||
Scenario: WHERE on a DISTINCT column
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({bar: 'A'}),
|
||||
({bar: 'A'}),
|
||||
({bar: 'B'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a)
|
||||
WITH DISTINCT a.bar AS bars
|
||||
WHERE a.bar = 'B'
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| bars |
|
||||
| 'B' |
|
||||
And no side effects
|
||||
|
||||
Scenario: A simple pattern with one bound endpoint
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:REL]->(:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (a:A)-[r:REL]->(b:B)
|
||||
WITH a AS b, b AS tmp, r AS r
|
||||
WITH b AS a, r
|
||||
LIMIT 1
|
||||
MATCH (a)-[r]->(b)
|
||||
RETURN a, r, b
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | r | b |
|
||||
| (:A) | [:REL] | (:B) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Null handling
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
OPTIONAL MATCH (a:Start)
|
||||
WITH a
|
||||
MATCH (a)-->(b)
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| a | b |
|
||||
And no side effects
|
||||
|
||||
Scenario: Nested maps
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
WITH {foo: {bar: 'baz'}} AS nestedMap
|
||||
RETURN nestedMap.foo.bar
|
||||
"""
|
||||
Then the result should be:
|
||||
| nestedMap.foo.bar |
|
||||
| 'baz' |
|
||||
And no side effects
|
||||
|
||||
Scenario: Connected components succeeding WITH
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (:A)-[:REL]->(:X)
|
||||
CREATE (:B)
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:A)
|
||||
WITH n
|
||||
LIMIT 1
|
||||
MATCH (m:B), (n)-->(x:X)
|
||||
RETURN *
|
||||
"""
|
||||
Then the result should be:
|
||||
| m | n | x |
|
||||
| (:B) | (:A) | (:X) |
|
||||
And no side effects
|
||||
|
||||
Scenario: Single WITH using a predicate and aggregation
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({prop: 43}), ({prop: 42})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
WITH n
|
||||
WHERE n.prop = 42
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Multiple WITHs using a predicate and aggregation
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a {name: 'David'}),
|
||||
(b {name: 'Other'}),
|
||||
(c {name: 'NotOther'}),
|
||||
(d {name: 'NotOther2'}),
|
||||
(a)-[:REL]->(b),
|
||||
(a)-[:REL]->(c),
|
||||
(a)-[:REL]->(d),
|
||||
(b)-[:REL]->(),
|
||||
(b)-[:REL]->(),
|
||||
(c)-[:REL]->(),
|
||||
(c)-[:REL]->(),
|
||||
(d)-[:REL]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (david {name: 'David'})--(otherPerson)-->()
|
||||
WITH otherPerson, count(*) AS foaf
|
||||
WHERE foaf > 1
|
||||
WITH otherPerson
|
||||
WHERE otherPerson.name <> 'NotOther'
|
||||
RETURN count(*)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(*) |
|
||||
| 1 |
|
||||
And no side effects
|
@ -0,0 +1,29 @@
|
||||
CREATE (a:A {name: 'a'}),
|
||||
(b1:X {name: 'b1'}),
|
||||
(b2:X {name: 'b2'}),
|
||||
(b3:X {name: 'b3'}),
|
||||
(b4:X {name: 'b4'}),
|
||||
(c11:X {name: 'c11'}),
|
||||
(c12:X {name: 'c12'}),
|
||||
(c21:X {name: 'c21'}),
|
||||
(c22:X {name: 'c22'}),
|
||||
(c31:X {name: 'c31'}),
|
||||
(c32:X {name: 'c32'}),
|
||||
(c41:X {name: 'c41'}),
|
||||
(c42:X {name: 'c42'})
|
||||
CREATE (a)-[:KNOWS]->(b1),
|
||||
(a)-[:KNOWS]->(b2),
|
||||
(a)-[:FOLLOWS]->(b3),
|
||||
(a)-[:FOLLOWS]->(b4)
|
||||
CREATE (b1)-[:FRIEND]->(c11),
|
||||
(b1)-[:FRIEND]->(c12),
|
||||
(b2)-[:FRIEND]->(c21),
|
||||
(b2)-[:FRIEND]->(c22),
|
||||
(b3)-[:FRIEND]->(c31),
|
||||
(b3)-[:FRIEND]->(c32),
|
||||
(b4)-[:FRIEND]->(c41),
|
||||
(b4)-[:FRIEND]->(c42)
|
||||
CREATE (b1)-[:FRIEND]->(b2),
|
||||
(b2)-[:FRIEND]->(b3),
|
||||
(b3)-[:FRIEND]->(b4),
|
||||
(b4)-[:FRIEND]->(b1);
|
@ -0,0 +1,29 @@
|
||||
CREATE (a:A {name: 'a'}),
|
||||
(b1:X {name: 'b1'}),
|
||||
(b2:X {name: 'b2'}),
|
||||
(b3:X {name: 'b3'}),
|
||||
(b4:X {name: 'b4'}),
|
||||
(c11:X {name: 'c11'}),
|
||||
(c12:Y {name: 'c12'}),
|
||||
(c21:X {name: 'c21'}),
|
||||
(c22:Y {name: 'c22'}),
|
||||
(c31:X {name: 'c31'}),
|
||||
(c32:Y {name: 'c32'}),
|
||||
(c41:X {name: 'c41'}),
|
||||
(c42:Y {name: 'c42'})
|
||||
CREATE (a)-[:KNOWS]->(b1),
|
||||
(a)-[:KNOWS]->(b2),
|
||||
(a)-[:FOLLOWS]->(b3),
|
||||
(a)-[:FOLLOWS]->(b4)
|
||||
CREATE (b1)-[:FRIEND]->(c11),
|
||||
(b1)-[:FRIEND]->(c12),
|
||||
(b2)-[:FRIEND]->(c21),
|
||||
(b2)-[:FRIEND]->(c22),
|
||||
(b3)-[:FRIEND]->(c31),
|
||||
(b3)-[:FRIEND]->(c32),
|
||||
(b4)-[:FRIEND]->(c41),
|
||||
(b4)-[:FRIEND]->(c42)
|
||||
CREATE (b1)-[:FRIEND]->(b2),
|
||||
(b2)-[:FRIEND]->(b3),
|
||||
(b3)-[:FRIEND]->(b4),
|
||||
(b4)-[:FRIEND]->(b1);
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
This graph is based upon YAGO, which is derived from Wikipedia.
|
||||
The idea is to enlarge it over time.
|
||||
http://www.mpi-inf.mpg.de/departments/databases-and-information-systems/research/yago-naga/yago/
|
||||
*/
|
||||
|
||||
CREATE (rachel:Person:Actor {name: 'Rachel Kempson', birthyear: 1910})
|
||||
CREATE (michael:Person:Actor {name: 'Michael Redgrave', birthyear: 1908})
|
||||
CREATE (vanessa:Person:Actor {name: 'Vanessa Redgrave', birthyear: 1937})
|
||||
CREATE (corin:Person:Actor {name: 'Corin Redgrave', birthyear: 1939})
|
||||
CREATE (liam:Person:Actor {name: 'Liam Neeson', birthyear: 1952})
|
||||
CREATE (natasha:Person:Actor {name: 'Natasha Richardson', birthyear: 1963})
|
||||
CREATE (richard:Person:Actor {name: 'Richard Harris', birthyear: 1930})
|
||||
CREATE (dennis:Person:Actor {name: 'Dennis Quaid', birthyear: 1954})
|
||||
CREATE (lindsay:Person:Actor {name: 'Lindsay Lohan', birthyear: 1986})
|
||||
CREATE (jemma:Person:Actor {name: 'Jemma Redgrave', birthyear: 1965})
|
||||
CREATE (roy:Person:Actor {name: 'Roy Redgrave', birthyear: 1873})
|
||||
|
||||
CREATE (john:Person {name: 'John Williams', birthyear: 1932})
|
||||
CREATE (christopher:Person {name: 'Christopher Nolan', birthyear: 1970})
|
||||
|
||||
CREATE (newyork:City {name: 'New York'})
|
||||
CREATE (london:City {name: 'London'})
|
||||
CREATE (houston:City {name: 'Houston'})
|
||||
|
||||
CREATE (mrchips:Film {title: 'Goodbye, Mr. Chips'})
|
||||
CREATE (batmanbegins:Film {title: 'Batman Begins'})
|
||||
CREATE (harrypotter:Film {title: 'Harry Potter and the Sorcerer\'s Stone'})
|
||||
CREATE (parent:Film {title: 'The Parent Trap'})
|
||||
CREATE (camelot:Film {title: 'Camelot'})
|
||||
|
||||
CREATE (rachel)-[:HAS_CHILD]->(vanessa),
|
||||
(rachel)-[:HAS_CHILD]->(corin),
|
||||
(michael)-[:HAS_CHILD]->(vanessa),
|
||||
(michael)-[:HAS_CHILD]->(corin),
|
||||
(corin)-[:HAS_CHILD]->(jemma),
|
||||
(vanessa)-[:HAS_CHILD]->(natasha),
|
||||
(roy)-[:HAS_CHILD]->(michael),
|
||||
|
||||
(rachel)-[:MARRIED]->(michael),
|
||||
(michael)-[:MARRIED]->(rachel),
|
||||
(natasha)-[:MARRIED]->(liam),
|
||||
(liam)-[:MARRIED]->(natasha),
|
||||
|
||||
(vanessa)-[:BORN_IN]->(london),
|
||||
(natasha)-[:BORN_IN]->(london),
|
||||
(christopher)-[:BORN_IN]->(london),
|
||||
(dennis)-[:BORN_IN]->(houston),
|
||||
(lindsay)-[:BORN_IN]->(newyork),
|
||||
(john)-[:BORN_IN]->(newyork),
|
||||
|
||||
(christopher)-[:DIRECTED]->(batmanbegins),
|
||||
|
||||
(john)-[:WROTE_MUSIC_FOR]->(harrypotter),
|
||||
(john)-[:WROTE_MUSIC_FOR]->(mrchips),
|
||||
|
||||
(michael)-[:ACTED_IN {charactername: 'The Headmaster'}]->(mrchips),
|
||||
(vanessa)-[:ACTED_IN {charactername: 'Guenevere'}]->(camelot),
|
||||
(richard)-[:ACTED_IN {charactername: 'King Arthur'}]->(camelot),
|
||||
(richard)-[:ACTED_IN {charactername: 'Albus Dumbledore'}]->(harrypotter),
|
||||
(natasha)-[:ACTED_IN {charactername: 'Liz James'}]->(parent),
|
||||
(dennis)-[:ACTED_IN {charactername: 'Nick Parker'}]->(parent),
|
||||
(lindsay)-[:ACTED_IN {charactername: 'Halle/Annie'}]->(parent),
|
||||
(liam)-[:ACTED_IN {charactername: 'Henri Ducard'}]->(batmanbegins)
|
Loading…
Reference in New Issue
Block a user