472 lines
11 KiB
Gherkin
472 lines
11 KiB
Gherkin
|
# 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 empty
|
||
|
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
|