openCypher_M06 added
Reviewers: buda Reviewed By: buda Differential Revision: https://phabricator.memgraph.io/D481
This commit is contained in:
parent
d8e06d12c1
commit
f1e894d107
@ -31,6 +31,9 @@ The following tck tests have been changed:
|
||||
To correct tests, tag "the result should be" was changed with a
|
||||
tag "the result should be (ignoring element order for lists)".
|
||||
|
||||
3. Behave can't escape character '|' and it throws parse error. Query was then
|
||||
changed and result was returned with different name.
|
||||
|
||||
Comparability.feature tests are failing because integers are compared to strings
|
||||
what is not allowed in openCypher.
|
||||
|
||||
|
@ -0,0 +1,472 @@
|
||||
# Copyright 2017 "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,67 @@
|
||||
# Copyright 2017 "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,86 @@
|
||||
# Copyright 2017 "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 2017 "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
|
71
tck_engine/tests/openCypher_M06/tck/features/Create.feature
Normal file
71
tck_engine/tests/openCypher_M06/tck/features/Create.feature
Normal file
@ -0,0 +1,71 @@
|
||||
#
|
||||
# Copyright 2017 "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 any 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,521 @@
|
||||
#
|
||||
# Copyright 2017 "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 any 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 any 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 any 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 any 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 | r1 | b | r2 | c |
|
||||
| (:A) | [:R1] | (:B) | [:R2] | (:C) |
|
@ -0,0 +1,372 @@
|
||||
#
|
||||
# Copyright 2017 "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 |
|
||||
|
||||
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 |
|
||||
|
||||
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 the side effects should be:
|
||||
| +nodes | 1 |
|
||||
| -nodes | 1 |
|
||||
|
||||
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 |
|
||||
|
||||
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 |
|
||||
|
||||
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 |
|
@ -0,0 +1,111 @@
|
||||
#
|
||||
# Copyright 2017 "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 2017 "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,487 @@
|
||||
#
|
||||
# Copyright 2017 "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:
|
||||
| params | p |
|
||||
| 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:
|
||||
| params | p |
|
||||
| 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 2017 "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,162 @@
|
||||
# Copyright 2017 "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 2017 "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 2017 "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 2017 "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
|
||||
|
131
tck_engine/tests/openCypher_M06/tck/features/Literals.feature
Normal file
131
tck_engine/tests/openCypher_M06/tck/features/Literals.feature
Normal file
@ -0,0 +1,131 @@
|
||||
#
|
||||
# Copyright 2017 "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 2017 "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 - wrong 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
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a:End {value: 1})-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(c:End {value: 2})
|
||||
"""
|
||||
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
|
||||
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 2016 "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,153 @@
|
||||
#
|
||||
# Copyright 2017 "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 |
|
||||
When executing control query:
|
||||
"""
|
||||
MATCH ()-[r:TYPE]->()
|
||||
RETURN [key IN keys(r) | key + '->' + r[key]] AS keyValue
|
||||
"""
|
||||
Then the result should be:
|
||||
| keyValue |
|
||||
| ['foo->baz', 'bar->baz'] |
|
@ -0,0 +1,484 @@
|
||||
#
|
||||
# Copyright 2017 "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 (ignoring element order for lists):
|
||||
| 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 |
|
||||
| +labels | 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 | 2 |
|
||||
|
||||
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 | 3 |
|
||||
| +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 | 2 |
|
||||
|
||||
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 | 15 |
|
||||
| +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 | 2 |
|
||||
| +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 |
|
||||
| +labels | 1 |
|
||||
|
||||
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,598 @@
|
||||
#
|
||||
# Copyright 2017 "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 | 5 |
|
||||
| +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 |
|
||||
| +labels | 2 |
|
||||
| +properties | 1 |
|
||||
|
||||
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 |
|
||||
|
||||
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 2017 "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 2017 "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 2017 "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 2017 "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 2017 "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 2017 "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 2017 "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 (ignoring element order for lists):
|
||||
| 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 (ignoring element order for lists):
|
||||
| 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,515 @@
|
||||
#
|
||||
# Copyright (c) 2002-2016 "Neo Technology,"
|
||||
# Network Engine for Objects in Lund AB [http://neotechnology.com]
|
||||
#
|
||||
# This file is part of Neo4j.
|
||||
#
|
||||
# Neo4j is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
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:
|
||||
| label | c |
|
||||
| 'A' | 3 |
|
||||
| 'B' | 3 |
|
||||
| 'C' | 3 |
|
||||
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 empty
|
||||
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 empty
|
||||
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 |
|
||||
|
||||
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 |
|
||||
|
||||
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 |
|
||||
|
||||
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 2017 "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,297 @@
|
||||
#
|
||||
# Copyright 2017 "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
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ({name: 'A'}),
|
||||
({name: 'B'}),
|
||||
({name: 'C'}),
|
||||
({name: 'D'}),
|
||||
({name: 'E'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n)
|
||||
RETURN n
|
||||
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 2017 "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:
|
||||
| p | a | b |
|
||||
| <(:Start)-[:T]->()> | (:Start) | () |
|
||||
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 2017 "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,286 @@
|
||||
#
|
||||
# Copyright 2017 "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 |
|
||||
|
||||
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 |
|
||||
|
||||
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 | 2 |
|
||||
|
||||
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 | 2 |
|
||||
|
||||
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 |
|
||||
|
||||
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 any 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 | 1 |
|
@ -0,0 +1,71 @@
|
||||
#
|
||||
# Copyright 2017 "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 2017 "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 2017 "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 2017 "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 2017 "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 2017 "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 2017 "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 2017 "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 2017 "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 | 2 |
|
||||
| +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 | y | z | zs | ys | xs |
|
||||
| 1 | 3 | 5 | [5, 6] | [3, 4] | [1, 2] |
|
||||
| 1 | 3 | 6 | [5, 6] | [3, 4] | [1, 2] |
|
||||
| 1 | 4 | 5 | [5, 6] | [3, 4] | [1, 2] |
|
||||
| 1 | 4 | 6 | [5, 6] | [3, 4] | [1, 2] |
|
||||
| 2 | 3 | 5 | [5, 6] | [3, 4] | [1, 2] |
|
||||
| 2 | 3 | 6 | [5, 6] | [3, 4] | [1, 2] |
|
||||
| 2 | 4 | 5 | [5, 6] | [3, 4] | [1, 2] |
|
||||
| 2 | 4 | 6 | [5, 6] | [3, 4] | [1, 2] |
|
||||
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 | 2 |
|
||||
| +properties | 4 |
|
@ -0,0 +1,657 @@
|
||||
#
|
||||
# Copyright 2017 "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 2017 "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 2017 "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 2017 "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 | r |
|
||||
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,71 @@
|
||||
{
|
||||
"name": "binary-tree-1",
|
||||
"scripts": [
|
||||
"binary-tree-1"
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"label": "",
|
||||
"key": "",
|
||||
"count": 13,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"label": "",
|
||||
"key": "name",
|
||||
"count": 13,
|
||||
"distinct": 13
|
||||
},
|
||||
{
|
||||
"label": "A",
|
||||
"key": "",
|
||||
"count": 1,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"label": "A",
|
||||
"key": "name",
|
||||
"count": 1,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"label": "X",
|
||||
"key": "",
|
||||
"count": 12,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"label": "X",
|
||||
"key": "name",
|
||||
"count": 12,
|
||||
"distinct": 12
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"type": "",
|
||||
"key": "",
|
||||
"count": 16,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"type": "FOLLOWS",
|
||||
"key": "",
|
||||
"count": 2,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"type": "FRIEND",
|
||||
"key": "",
|
||||
"count": 12,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"type": "KNOWS",
|
||||
"key": "",
|
||||
"count": 2,
|
||||
"distinct": 1
|
||||
}
|
||||
],
|
||||
"labels": []
|
||||
}
|
@ -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,83 @@
|
||||
{
|
||||
"name": "binary-tree-2",
|
||||
"scripts": [
|
||||
"binary-tree-2"
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"label": "",
|
||||
"key": "",
|
||||
"count": 13,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"label": "",
|
||||
"key": "name",
|
||||
"count": 13,
|
||||
"distinct": 13
|
||||
},
|
||||
{
|
||||
"label": "A",
|
||||
"key": "",
|
||||
"count": 1,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"label": "A",
|
||||
"key": "name",
|
||||
"count": 1,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"label": "X",
|
||||
"key": "",
|
||||
"count": 8,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"label": "X",
|
||||
"key": "name",
|
||||
"count": 8,
|
||||
"distinct": 8
|
||||
},
|
||||
{
|
||||
"label": "Y",
|
||||
"key": "",
|
||||
"count": 4,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"label": "Y",
|
||||
"key": "name",
|
||||
"count": 4,
|
||||
"distinct": 4
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"type": "",
|
||||
"key": "",
|
||||
"count": 16,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"type": "FOLLOWS",
|
||||
"key": "",
|
||||
"count": 2,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"type": "FRIEND",
|
||||
"key": "",
|
||||
"count": 12,
|
||||
"distinct": 1
|
||||
},
|
||||
{
|
||||
"type": "KNOWS",
|
||||
"key": "",
|
||||
"count": 2,
|
||||
"distinct": 1
|
||||
}
|
||||
],
|
||||
"labels": []
|
||||
}
|
74
tck_engine/tests/openCypher_M06/tck/graphs/named-graphs.adoc
Normal file
74
tck_engine/tests/openCypher_M06/tck/graphs/named-graphs.adoc
Normal file
@ -0,0 +1,74 @@
|
||||
= Named Graphs
|
||||
|
||||
This document describes how to use the named graph descriptions and metadata to properly set up the graphs for use by the TCK.
|
||||
|
||||
== Metadata File
|
||||
|
||||
Each named graph is described using a JSON file (the metadata file), which references various Cypher script files for creating the graph.
|
||||
The metadata file also includes statistical information describing the graph composition.
|
||||
|
||||
The metadata file follows the below structure:
|
||||
|
||||
[source]
|
||||
----
|
||||
{
|
||||
"name": // the graph name
|
||||
"scripts": [] // a list of file names containing queries that create the graph
|
||||
"nodes": [ // a list of descriptions of the graph's label/property combinations
|
||||
{
|
||||
"label": // label name
|
||||
"key": // property key
|
||||
"count": // number of existing combinations
|
||||
"distinct": // number of distinct combinations
|
||||
"advice": [] // an optional list of characteristics for the combination
|
||||
}
|
||||
],
|
||||
"relationships": [ // a list of descriptions of the graphs type/property combinations
|
||||
{
|
||||
"type": // type name
|
||||
"key": // property key
|
||||
"count": // number of existing combinations
|
||||
"distinct": // number of distinct combinations
|
||||
"advice": [] // an optional list of characteristics for the combination
|
||||
}
|
||||
],
|
||||
"labels": [ // a list of all labels and their correlations with other labels
|
||||
{
|
||||
"label": // the label name
|
||||
"count": // the number of nodes with the label
|
||||
"sublabels": [ // a list of sublabels that exist on nodes with the label
|
||||
{
|
||||
"label": // the sublabel name
|
||||
"count": // the number of nodes with the sublabel and the label
|
||||
"advice": [] // an optional list of characteristics for the label/sublabel combination
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
----
|
||||
|
||||
The empty string is used as an 'any' wildcard for label names and property keys (i.e. for describing any property key, or any node with or without labels).
|
||||
|
||||
=== Advice
|
||||
|
||||
For some statistical combinations, the metadata file contains an optional piece of information (that may be disregarded), which can be useful for imposing constraints on the graph.
|
||||
This is called 'advice'.
|
||||
For label/property and type/property combinations, the supported advice are:
|
||||
|
||||
* `exists`
|
||||
** This indicates that each entity in the entry's context has the property. Additionally, the advice indicates that no included scenario will ever violate this constraint by performing updates to the graph.
|
||||
* `unique`
|
||||
** This indicates that each possible property value is at most assigned to one entity in the entry's context. Additionally, the advice guarantees that no included scenario will ever violate this constraint by performing updates to the graph.
|
||||
* `index`
|
||||
** This indicates that some scenarios include queries that try to match on entities in the entry's context via a property comparison (e.g. `MATCH (n:Label {prop: {value}}) ...`).
|
||||
|
||||
For label/sublabel combinations, the supported advice is:
|
||||
|
||||
* `implies`
|
||||
** This indicates that the existence of the label on a node implies the existence of the sublabel. In other words, if a node has the label, it will always have the sublabel.
|
||||
|
||||
=== Scripts
|
||||
|
||||
The metadata file will specify one or more script files that contain Cypher statements which are used to create the named graph.
|
||||
The statements in the script files are separated by the semicolon character.
|
Loading…
Reference in New Issue
Block a user