.gitignore wasn't updated.

This commit is contained in:
Marko Budiselic 2017-06-20 15:01:11 +02:00
parent ab250d1af7
commit a7cbea3999
154 changed files with 36822 additions and 0 deletions

5
tests/qa/.arcconfig Normal file
View File

@ -0,0 +1,5 @@
{
"project_id" : "memgraph",
"conduit_uri" : "https://phabricator.memgraph.io",
"phabricator_uri" : "https://phabricator.memgraph.io"
}

7
tests/qa/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.swo
*.swn
*.swp
*~
*.pyc
ve3/
.quality_assurance_status

60
tests/qa/README.md Normal file
View File

@ -0,0 +1,60 @@
# Memgraph quality assurance
In order to test dressipi's queries agains memgraph the following commands have
to be executed:
1. ./init [Dxyz] # downloads query implementations + memgraph
# (the auth is manually for now) + optionally user can
# define arcanist diff which will be applied on the
# memgraph source code
2. ./run # compiles and runs database instance, also runs the
# test queries
TODO: automate further
## TCK Engine
Python script used to run tck tests against memgraph. To run script execute:
1. python3 tck_engine/test_executor.py
Script uses Behave to run Cucumber tests.
The following tck tests have been changed:
1. Tests where example injection did not work. Behave stores the first row
in Cucumber tables as headings and the example injection is not working in
headings. To correct this behavior, one row was added to tables where
injection was used.
2. Tests where the results were not always in the same order. Query does not
specify the result order, but tests specified it. It led to the test failure.
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.
TCK Engine problems:
1. Comparing tables with ordering.
ORDER BY x DESC
| x | y | | x | y |
| 3 | 2 | | 3 | 1 |
| 3 | 1 | | 3 | 2 |
| 1 | 4 | | 1 | 4 |
2. Properties side effects
| +properties | 1 |
| -properties | 1 |
Database is returning properties_set, not properties_created and properties_deleted.
## KPI Service
Flask application used to get results from executing tests with TCK Engine.
Application can be ran executing:
1. python3 kpi_service/kpi_service.py

88
tests/qa/continuous_integration Executable file
View File

@ -0,0 +1,88 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Continuous integration toolkit. The purpose of this script is to generate
everything which is needed for the CI environment.
List of responsibilities:
* terminate execution if any of internal scenarios fails
* creates the report file that is needed by the Jenkins plugin
to post the status on Phabricator. (.quality_assurance_status)
"""
import os
import sys
import json
import logging
log = logging.getLogger(__name__)
# constants
memgraph_suite = "memgraph_V1"
opencypher_suite = "openCypher_M06"
results_folder = os.path.join("tck_engine", "results")
memgraph_suffix = "memgraph-%s.json" % memgraph_suite
opencypher_suffix = "memgraph-%s.json" % opencypher_suite
qa_status_path = ".quality_assurance_status"
def get_newest_path(folder, suffix):
"""
:param folder: Scanned folder.
:param suffix: File suffix.
:return: Path to the newest file in the folder with the specified suffix.
"""
name_list = sorted(filter(lambda x: x.endswith(suffix),
os.listdir(folder)))
if len(name_list) <= 0:
sys.exit("Unable to find any file with suffix %s in folder %s!" %
(suffix, folder))
return os.path.join(folder, name_list.pop())
def generate_status(suite, f):
"""
:param suite: Test suite name.
:param f: Json file with status report.
:return: Status string.
"""
result = json.load(f)
total = result["total"]
passed = result["passed"]
return ("SUITE: %s, PASSED SCENARIOS: %s, TOTAL SCENARIOS: %s (%.2f%%)" %
(suite, passed, total, 100.0 * passed / total)), passed, total
if __name__ == "__main__":
# logger config
logging.basicConfig(level=logging.INFO)
# get data files (memgraph internal test + openCypher TCK test results)
memgraph_result_path = get_newest_path(results_folder, memgraph_suffix)
log.info("Memgraph result path is %s" % memgraph_result_path)
opencypher_result_path = get_newest_path(results_folder, opencypher_suffix)
log.info("openCypher result path is %s" % opencypher_result_path)
# read internal scenarios
with open(memgraph_result_path) as f:
memgraph_status, memgraph_passed, memgraph_total \
= generate_status(memgraph_suite, f)
# read opencypher scenarios
with open(opencypher_result_path) as f:
opencypher_status, _, _ = generate_status(opencypher_suite, f)
# create the report file
with open(qa_status_path, "w") as f:
f.write("Quality Assurance Status\n")
f.write("%s\n" % memgraph_status)
f.write("%s\n" % opencypher_status)
log.info("Status is generated in %s" % qa_status_path)
if memgraph_total != memgraph_passed:
sys.exit("There is a problem with internal scenarios! %s"
% memgraph_status)

View File

@ -0,0 +1,16 @@
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y python3 python3-pip \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY kpi_service /kpi_service
COPY requirements.txt /kpi_service/requirements.txt
WORKDIR /kpi_service
RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt
EXPOSE 5000
CMD python3 kpi_service.py --results /results

View File

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
'''
Filters failing scenarios from a tck test run and prints them to stdout.
'''
from argparse import ArgumentParser
import sys
def main():
argp = ArgumentParser(description=__doc__)
argp.add_argument('test_log', metavar='TEST_LOG', type=str,
help='Path to the log of a tck test run')
args = argp.parse_args()
with open(args.test_log) as f:
scenario_failed = False
scenario_lines = []
for line in f:
if line.strip().startswith('Scenario:'):
if scenario_failed:
print(''.join(scenario_lines))
scenario_failed = False
scenario_lines.clear()
if line.strip().startswith('AssertionError'):
scenario_failed = True
scenario_lines.append(line)
if __name__ == '__main__':
main()

113
tests/qa/init Executable file
View File

@ -0,0 +1,113 @@
#!/bin/bash
function print_usage_and_exit {
echo "./init [-s] [--no-clone-dependencies] [--arc-diff DIFF_ID]"
echo -e "Prepare the environment for Memgraph QA.\n"
echo "Optional arguments:"
echo -e " -s\tuse sudo apt-get for installing required packages"
echo -e " -h\tdisplay this help and exit"
echo -e " --no-clone-dependencies\tskip cloning memgraph sources"
echo -e " --arc-diff\tcheckout the DIFF_ID from Phabricator"
exit 1
}
# read arguments
arcanist_diff_id=""
clone_dependencies=true
use_sudo=false
while [[ $# -gt 0 ]]; do
case $1 in
--no-clone-dependencies)
clone_dependencies=false
shift
;;
--arc-diff)
if [[ -z $2 ]]; then
print_usage_and_exit
fi
arcanist_diff_id=$2
shift 2
;;
-s)
use_sudo=true
shift
;;
*)
# unknown option
print_usage_and_exit
;;
esac
done
# exit if any subcommand returns a non-zero status
set -e
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd ${script_dir}
required_pkgs=(git arcanist # used to clone sources
python-virtualenv python3-pip # required by 'run' script
)
# install all dependencies on debian based operating systems
for pkg in ${required_pkgs[@]}; do
if dpkg -s $pkg 2>/dev/null >/dev/null; then
echo "Found $pkg"
elif [[ $use_sudo = true ]]; then
echo "Installing $pkg"
if [[ ! `sudo apt-get -y install $pkg` ]]; then
echo "Didn't install $pkg [required]"
required_missing=true
fi
else
echo "Missing $pkg [required]"
required_missing=true
fi
done
if [[ $required_missing = true ]]; then
echo "Missing required packages. EXITING!"
echo "Please, install required packages and rerun $0 again."
exit 2
fi
# setup ve
if [ ! -d "ve3" ]; then
virtualenv -p python3 ve3
fi
source ve3/bin/activate
pip3 install --upgrade pip
pip3 install -r requirements.txt
# TODO: use pullbot and read a password from program arg or env var
# (in order to use this script inside CI infrastructure)
# clean dbms folder
dbms_folder=${script_dir}/dbms
mkdir -p ${dbms_folder}
# clone memgraph & checkout right commit
cd ${dbms_folder}
if [[ $clone_dependencies = true ]] ; then
rm -rf ${dbms_folder}/*
git clone https://phabricator.memgraph.io/diffusion/MG/memgraph.git
fi
memgraph_folder=${dbms_folder}/memgraph
cd ${memgraph_folder}
git checkout dev
# optionally apply arcanist patch
if [[ ! -z ${arcanist_diff_id} ]]; then
# nocommit is here because arc tries to commit the patch
# and it fails if user isn't set (inside CI infrastructure the user
# probably won't be defined because CI infrastructure doesn't push
# any code back to the repository)
arc patch ${arcanist_diff_id} --nocommit
fi
./init
# compile memgraph
memgraph_build_dir=${script_dir}/dbms/memgraph/build
cd ${memgraph_build_dir}
cmake ..
make -j8

View File

@ -0,0 +1,166 @@
from flask import Flask, jsonify, request
import os
import json
from argparse import ArgumentParser
app = Flask(__name__)
"""
Script runs web application used for getting test results.
Default file with results is "../tck_engine/results".
Application lists files with results or returnes result
file as json.
Default host is 0.0.0.0, and default port is 5000.
Host, port and result file can be passed as arguments of a script.
"""
def parse_args():
argp = ArgumentParser(description=__doc__)
argp.add_argument("--host", default="0.0.0.0",
help="Application host ip, default is 0.0.0.0.")
argp.add_argument("--port", default="5000",
help="Application host ip, default is 5000.")
argp.add_argument("--results", default="tck_engine/results",
help="Path where the results are stored.")
return argp.parse_args()
@app.route("/results")
def results():
"""
Function accessed with route /result. Function lists
last tail result files added. Tail is a parameter given
in the route. If the tail is not given, function lists
last ten added files. If parameter last is true, only last
test result is returned.
@return:
json list of test results
"""
l = [f for f in os.listdir(app.config["RESULTS_PATH"])
if f != ".gitignore"]
return get_ret_list(l)
@app.route("/results/<dbname>")
def results_for_db(dbname):
"""
Function accessed with route /result/<dbname>. Function
lists last tail result files added of database <dbname.
Tail is a parameter given in the route. If tail is not
given, function lists last ten added files of database
<dbname>. If param last is true, only last test result
is returned.
@param dbname:
string, database name
@return:
json list of test results
"""
print(os.listdir(app.config["RESULTS_PATH"]))
l = [f for f in os.listdir(app.config["RESULTS_PATH"])
if f != ".gitignore" and f.split('-')[1] == dbname]
return get_ret_list(l)
@app.route("/results/<dbname>/<test_suite>")
def result(dbname, test_suite):
"""
Function accessed with route /results/<dbname>/<test_suite>
Function lists last tail result files added of database <dbname>
tested on <test_suite>. Tail is a parameter given in the
route. If tail is not given, function lists last ten results.
If param last is true, only last test result is returned.
@param dbname:
string, database name
@param test_suite:
string, test suite of result file
@return:
json list of test results
"""
fname = dbname + "-" + test_suite + ".json"
l = [f for f in os.listdir(app.config["RESULTS_PATH"])
if f.endswith(fname)]
return get_ret_list(l)
def get_ret_list(l):
"""
Function returns json list of test results of files given in
list l.
@param l:
list of file names
@return:
json list of test results
"""
l.sort()
ret_list = []
for f in l:
ret_list.append(get_content(f))
return list_to_json(
ret_list,
request.args.get("last"),
request.args.get("tail")
)
def get_content(fname):
"""
Function returns data of the json file fname located in
results directory in json format.
@param fname:
string, name of the file
@return:
json of a file
"""
with open(app.config["RESULTS_PATH"] + "/" + fname) as f:
json_data = json.load(f)
return json_data
def list_to_json(l, last, tail):
"""
Function converts list to json format. If last is true,
only the first item in list is returned list, else last
tail results are returned in json list. If tail is not
given, last ten results are returned in json list.
@param l:
list to convert to json format
@param last:
string from description
@param tail:
string from description
"""
l.reverse()
if len(l) == 0:
return jsonify(results=[])
if last == "true":
return jsonify(results=[l[0]])
if tail is None:
tail = 10
else:
tail = int(tail)
if tail > len(l):
tail = len(l)
return jsonify(results=l[0:tail])
def main():
args = parse_args()
app.config.update(dict(
RESULTS_PATH=os.path.abspath(args.results)
))
app.run(
host=args.host,
port=int(args.port)
)
if __name__ == "__main__":
main()

78
tests/qa/plot_latency Executable file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env python3
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import json
from argparse import ArgumentParser
"""
Plots graph of latencies of memgraph and neo4j. Takes paths to
json of latencies as arguments.
"""
def main():
argp = ArgumentParser(description=__doc__)
argp.add_argument('--memgraph-latency',
help='Path to the json of memgraph latency')
argp.add_argument('--neo4j-latency',
help='Path to the json of neo4j latency')
args = argp.parse_args()
fig = plt.gcf()
fig.set_size_inches(10, 16)
with open(args.neo4j_latency) as json_file:
json_neo = json.load(json_file)
with open(args.memgraph_latency) as json_file:
json_mem = json.load(json_file)
tests_num = 0
time_list_neo = []
time_list_mem = []
max_time = 0
for key in json_mem['data']:
if json_neo['data'][key]['status'] == "passed" and \
json_mem['data'][key]['status'] == 'passed':
time_neo = json_neo['data'][key]['execution_time']
time_mem = json_mem['data'][key]['execution_time']
max_time = max(max_time, time_neo, time_mem)
offset = 0.01 * max_time
for key in json_mem['data']:
if json_neo['data'][key]['status'] == "passed" and \
json_mem['data'][key]['status'] == 'passed':
time_neo = json_neo['data'][key]['execution_time']
time_mem = json_mem['data'][key]['execution_time']
time_list_neo.append(time_neo)
time_list_mem.append(time_mem)
tests_num += 1
if time_neo < time_mem:
plt.plot((time_mem, time_neo), (tests_num, tests_num), color='red',
label=key, lw=0.3)
else:
plt.plot((time_mem, time_neo), (tests_num, tests_num), color='green',
label=key, lw=0.3)
ratio = '%.2f' % (max(time_neo, time_mem) / min(time_neo, time_mem))
plt.text(max(time_mem, time_neo) + offset, tests_num, key + " ---> " + \
ratio + "x", size=1)
x = range(1, tests_num + 1)
plt.plot(time_list_mem, x, marker='o', markerfacecolor='orange', color='orange',
linestyle='', markersize=0.5)
plt.plot(time_list_neo, x, marker='o', markerfacecolor='blue', color='blue',
linestyle='', markersize=0.5)
plt.margins(0.1, 0.01)
plt.savefig("latency_graph.png", dpi=2000)
if __name__ == '__main__':
main()

15
tests/qa/requirements.txt Normal file
View File

@ -0,0 +1,15 @@
appdirs==1.4.3
behave==1.2.5
click==6.7
Flask==0.12
itsdangerous==0.24
Jinja2==2.9.5
MarkupSafe==1.0
neo4j-driver==1.2.0
packaging==16.8
parse==1.8.0
parse-type==0.3.4
pyparsing==2.2.0
PyYAML==3.12
six==1.10.0
Werkzeug==0.12

82
tests/qa/run Executable file
View File

@ -0,0 +1,82 @@
#!/bin/bash
function print_usage_and_exit {
echo "./run --test-suite test_suite [--unstable]"
echo "Required arguments:"
echo -e " --test-suite test_suite\trun test_suite scenarios, test_suite must be test folder in tck_engine/tests."
echo "Optional arguments:"
echo -e " --unstable\trun unstable scenarios"
exit 1
}
# exit if any subcommand returns a non-zero status
set -e
# read arguments
unstable=false
while [[ $# -gt 0 ]]; do
case $1 in
--unstable)
unstable=true
shift
;;
--test-suite)
if [ $# -eq 1 ]; then
print_usage_and_exit
fi
test_suite=$2
shift
shift
;;
*)
# unknown option
print_usage_and_exit
;;
esac
done
if [[ "$test_suite" = "" ]]; then
print_usage_and_exit
fi
## build memgraph
# save the path where this script is
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
memgraph_src_dir=${script_dir}/dbms/memgraph
memgraph_build_dir=${script_dir}/dbms/memgraph/build
# activate virtualenv
source $script_dir/ve3/bin/activate
function cleanup_and_exit {
pkill -9 -f "${binary_name}"
exit $1
}
cd ${memgraph_build_dir}
# binary is available after the build
binary_name=$(find ${memgraph_build_dir}/ -maxdepth 1 -executable \
-name "memgraph*" | sort | tail -n 1)
# get full path to memgraph config for interpreted queries
config_path="${memgraph_src_dir}/config/testing.conf"
# run scenarios
cd ${script_dir}
tck_flags="--root tck_engine/tests/$test_suite
--no-side-effects --db memgraph"
if [[ $unstable = true ]]; then
tck_flags="$tck_flags --unstable"
fi
# the script has to be careful because memgraph process will be detached
set +e
# run memgraph
MEMGRAPH_CONFIG=${config_path} ${binary_name} 1>&2 &
python3 tck_engine/test_executor.py $tck_flags
cleanup_and_exit ${exit_code}

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
tests/qa/tck_engine/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
report/
__pycache__/
*.output

View File

@ -0,0 +1,6 @@
[behave]
stderr_capture=False
stdout_capture=False
format=progress
junit=1
junit_directory=report

View File

@ -0,0 +1,327 @@
#
# 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: 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

View File

@ -0,0 +1,141 @@
import logging
import datetime
import time
import json
import sys
import os
from steps.test_parameters import TestParameters
from neo4j.v1 import GraphDatabase, basic_auth
from steps.graph_properties import GraphProperties
from test_results import TestResults
test_results = TestResults()
"""
Executes before every step. Checks if step is execution
step and sets context variable to true if it is.
"""
def before_step(context, step):
context.execution_step = False
if step.name == "executing query":
context.execution_step = True
"""
Executes before every scenario. Initializes test parameters,
graph properties, exception and test execution time.
"""
def before_scenario(context, scenario):
context.test_parameters = TestParameters()
context.graph_properties = GraphProperties()
context.exception = None
context.execution_time = None
"""
Executes after every scenario. Pauses execution if flags are set.
Adds execution time to latency dict if it is not None.
"""
def after_scenario(context, scenario):
test_results.add_test(scenario.status)
if context.config.single_scenario or \
(context.config.single_fail and scenario.status == "failed"):
print("Press enter to continue")
sys.stdin.readline()
if context.execution_time is not None:
context.js['data'][scenario.name] = {
"execution_time": context.execution_time, "status": scenario.status
}
"""
Executes after every feature. If flag is set, pauses before
executing next scenario.
"""
def after_feature(context, feature):
if context.config.single_feature:
print("Press enter to continue")
sys.stdin.readline()
"""
Executes before running tests. Initializes driver and latency
dict and creates needed directories.
"""
def before_all(context):
timestamp = datetime.datetime.fromtimestamp(time.time()).strftime("%Y_%m_%d__%H_%M_%S")
latency_file = "latency/" + context.config.database + "/" + \
get_test_suite(context) + "/" + timestamp + ".json"
if not os.path.exists(os.path.dirname(latency_file)):
os.makedirs(os.path.dirname(latency_file))
context.driver = create_db_driver(context)
context.latency_file = latency_file
context.js = dict()
context.js["metadata"] = dict()
context.js["metadata"]["execution_time_unit"] = "seconds"
context.js["data"] = dict()
set_logging(context)
"""
Executes when testing is finished. Creates JSON files of test latency
and test results.
"""
def after_all(context):
context.driver.close()
timestamp = datetime.datetime.fromtimestamp(time.time()).strftime("%Y_%m_%d__%H_%M")
test_suite = get_test_suite(context)
file_name = context.config.output_folder + timestamp + \
"-" + context.config.database + "-" + test_suite + ".json"
js = {
"total": test_results.num_total(), "passed": test_results.num_passed(),
"test_suite": test_suite, "timestamp": timestamp, "db": context.config.database}
with open(file_name, 'w') as f:
json.dump(js, f)
with open(context.latency_file, "a") as f:
json.dump(context.js, f)
"""
Returns test suite from a test root folder.
If test root is a feature file, name of file is returned without
.feature extension.
"""
def get_test_suite(context):
root = context.config.root
if root.endswith("/"):
root = root[0:len(root) - 1]
if root.endswith("features"):
root = root[0: len(root) - len("features") - 1]
test_suite = root.split('/')[-1]
return test_suite
"""
Initializes log and sets logging level to debug.
"""
def set_logging(context):
logging.basicConfig(level="DEBUG")
log = logging.getLogger(__name__)
context.log = log
"""
Creates database driver and returns it.
"""
def create_db_driver(context):
uri = context.config.database_uri
auth_token = basic_auth(
context.config.database_username, context.config.database_password)
if context.config.database == "neo4j" or context.config.database == "memgraph":
driver = GraphDatabase.driver(uri, auth=auth_token, encrypted=0)
else:
raise "Unsupported database type"
return driver

View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -0,0 +1,12 @@
from behave import *
import graph
@given(u'the binary-tree-1 graph')
def step_impl(context):
graph.create_graph('binary-tree-1', context)
@given(u'the binary-tree-2 graph')
def step_impl(context):
graph.create_graph('binary-tree-2', context)

View File

@ -0,0 +1,80 @@
import time
def query(q, context, params={}):
"""
Function used to execute query on database. Query results are
set in context.result_list. If exception occurs, it is set on
context.exception.
@param q:
String, database query.
@param context:
behave.runner.Context, context of all tests.
@return:
List of query results.
"""
results_list = []
if (context.config.database == "neo4j" or
context.config.database == "memgraph"):
session = context.driver.session()
start = time.time()
try:
# executing query
results = session.run(q, params)
if not context.config.no_side_effects:
summary = results.summary()
add_side_effects(context, summary.counters)
results_list = list(results)
"""
This code snippet should replace code which is now
executing queries when session.transactions will be supported.
with session.begin_transaction() as tx:
results = tx.run(q, params)
summary = results.summary()
if not context.config.no_side_effects:
add_side_effects(context, summary.counters)
results_list = list(results)
tx.success = True
"""
except Exception as e:
# exception
context.exception = e
context.log.info('%s', str(e))
finally:
end = time.time()
if context.execution_step is not None and \
context.execution_step:
context.execution_time = end - start
session.close()
return results_list
def add_side_effects(context, counters):
"""
Funtion adds side effects from query to graph properties.
@param context:
behave.runner.Context, context of all tests.
"""
graph_properties = context.graph_properties
# check nodes
if counters.nodes_deleted > 0:
graph_properties.change_nodes(-counters.nodes_deleted)
if counters.nodes_created > 0:
graph_properties.change_nodes(counters.nodes_created)
# check relationships
if counters.relationships_deleted > 0:
graph_properties.change_relationships(-counters.relationships_deleted)
if counters.relationships_created > 0:
graph_properties.change_relationships(counters.relationships_created)
# check labels
if counters.labels_removed > 0:
graph_properties.change_labels(-counters.labels_removed)
if counters.labels_added > 0:
graph_properties.change_labels(counters.labels_added)
# check properties
if counters.properties_set > 0:
graph_properties.change_properties(counters.properties_set)

View File

@ -0,0 +1,224 @@
from behave import *
# TODO check for exact error?
def handle_error(context):
"""
Function checks if exception exists in context.
Exception exists if error occured in executing query
@param context:
behave.runner.Context, context of behave.
"""
assert(context.exception is not None)
@then('an error should be raised')
def error(context):
handle_error(context)
@then('a SyntaxError should be raised at compile time: NestedAggregation')
def syntax_error(context):
handle_error(context)
@then('TypeError should be raised at compile time: IncomparableValues')
def type_error(context):
handle_error(context)
@then(u'a TypeError should be raised at compile time: IncomparableValues')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: RequiresDirectedRelationship')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidRelationshipPattern')
def syntax_error(context):
handle_error(context)
@then(u'a TypeError should be raised at runtime: MapElementAccessByNonString')
def type_error(context):
handle_error(context)
@then(u'a ConstraintVerificationFailed should be raised at runtime: DeleteConnectedNode')
def step(context):
handle_error(context)
@then(u'a TypeError should be raised at runtime: ListElementAccessByNonInteger')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidArgumentType')
def step(context):
handle_error(context)
@then(u'a TypeError should be raised at runtime: InvalidElementAccess')
def step(context):
handle_error(context)
@then(u'a ArgumentError should be raised at runtime: NumberOutOfRange')
def step(context):
handle_error(context)
@then(u'a TypeError should be raised at runtime: InvalidArgumentValue')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: VariableAlreadyBound')
def step(context):
handle_error(context)
@then(u'a TypeError should be raised at runtime: IncomparableValues')
def step(context):
handle_error(context)
@then(u'a TypeError should be raised at runtime: PropertyAccessOnNonMap')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidUnicodeLiteral')
def step(context):
handle_error(context)
@then(u'a SemanticError should be raised at compile time: MergeReadOwnWrites')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidAggregation')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: NoExpressionAlias')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: UndefinedVariable')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: VariableTypeConflict')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: DifferentColumnsInUnion')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidClauseComposition')
def step(context):
handle_error(context)
@then(u'a TypeError should be raised at compile time: InvalidPropertyType')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: ColumnNameConflict')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: NoVariablesInScope')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidDelete')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: NegativeIntegerArgument')
def step(context):
handle_error(context)
@then(u'a EntityNotFound should be raised at runtime: DeletedEntityAccess')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: RelationshipUniquenessViolation')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: CreatingVarLength')
def step_impl(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidParameterUse')
def step_impl(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: FloatingPointOverflow')
def step_impl(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time InvalidArgumentExpression')
def step_impl(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time InvalidUnicodeCharacter')
def step_impl(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: NonConstantExpression')
def step_impl(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: NoSingleRelationshipType')
def step_impl(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: UnknownFunction')
def step_impl(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidNumberLiteral')
def step_impl(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidArgumentExpression')
def step(context):
handle_error(context)
@then(u'a SyntaxError should be raised at compile time: InvalidUnicodeCharacter')
def step(context):
handle_error(context)

View File

@ -0,0 +1,75 @@
import database
import os
from behave import *
def clear_graph(context):
database.query("MATCH (n) DETACH DELETE n", context)
if context.exception != None:
context.exception = None
database.query("MATCH (n) DETACH DELETE n", context)
@given('an empty graph')
def empty_graph_step(context):
clear_graph(context)
context.graph_properties.set_beginning_parameters()
@given('any graph')
def any_graph_step(context):
clear_graph(context)
context.graph_properties.set_beginning_parameters()
@given('graph "{name}"')
def graph_name(context, name):
create_graph(name, context)
def create_graph(name, context):
"""
Function deletes everything from database and creates a new
graph. Graph file name is an argument of function. Function
executes queries written in a .cypher file separated by ';'
and sets graph properties to beginning values.
"""
clear_graph(context)
path = find_graph_path(name, context.config.root)
q_marks = ["'", '"', '`']
with open(path, 'r') as f:
content = f.read().replace('\n', ' ')
q = ''
in_string = False
i = 0
while i < len(content):
ch = content[i]
if ch == '\\' and i != len(content) - 1 and content[i + 1] in q_marks:
q += ch + content[i + 1]
i += 2
else:
q += ch
if in_string and ch in q_marks:
in_string = False
elif ch in q_marks:
in_string = True
if ch == ';' and not in_string:
database.query(q, context)
q = ''
i += 1
if q.strip() != '':
database.query(q, context)
context.graph_properties.set_beginning_parameters()
def find_graph_path(name, path):
"""
Function returns path to .cypher file with given name in
given folder or subfolders. Argument path is path to a given
folder.
"""
for root, dirs, files in os.walk(path):
if name + '.cypher' in files:
return root + '/' + name + '.cypher'

View File

@ -0,0 +1,128 @@
class GraphProperties:
"""
Class used to store changes(side effects of queries)
to graph parameters(nodes, relationships, labels and
properties) when executing queries.
"""
def set_beginning_parameters(self):
"""
Method sets parameters to empty lists.
@param self:
Instance of a class.
"""
self.nodes = []
self.relationships = []
self.labels = []
self.properties = []
def __init__(self):
"""
Method sets parameters to empty lists.
@param self:
Instance of a class.
"""
self.nodes = []
self.relationships = []
self.labels = []
self.properties = []
def change_nodes(self, dif):
"""
Method adds node side effect.
@param self:
Instance of a class.
@param dif:
Int, difference between number of nodes before
and after executing query.
"""
self.nodes.append(dif)
def change_relationships(self, dif):
"""
Method adds relationship side effect.
@param self:
Instance of a class.
@param dif:
Int, difference between number of relationships
before and after executing query.
"""
self.relationships.append(dif)
def change_labels(self, dif):
"""
Method adds one label side effect.
@param self:
Instance of a class.
@param dif:
Int, difference between number of labels before
and after executing query.
"""
self.labels.append(dif)
def change_properties(self, dif):
"""
Method adds one property side effect.
@param self:
Instance of a class.
@param dif:
Int, number of properties set in query.
"""
self.properties.append(dif)
def compare(self, nodes_dif, relationships_dif, labels_dif,
properties_dif):
"""
Method used to compare side effects from executing
queries and an expected result from a cucumber test.
@param self:
Instance of a class.
@param nodes_dif:
List of all expected node side effects in order
when executing query.
@param relationships_dif:
List of all expected relationship side effects
in order when executing query.
@param labels_dif:
List of all expected label side effects in order
when executing query.
@param properties_dif:
List of all expected property side effects in order
when executing query.
@return:
True if all side effects are equal, else false.
"""
if len(nodes_dif) != len(self.nodes):
return False
if len(relationships_dif) != len(self.relationships):
return False
if len(labels_dif) != len(self.labels):
return False
if len(properties_dif) != len(self.properties):
return False
for i in range(0, len(nodes_dif)):
if nodes_dif[i] != self.nodes[i]:
return False
for i in range(0, len(relationships_dif)):
if relationships_dif[i] != self.relationships[i]:
return False
for i in range(0, len(labels_dif)):
if labels_dif[i] != self.labels[i]:
return False
for i in range(0, len(properties_dif)):
if properties_dif[i] != self.properties[i]:
return False
return True

View File

@ -0,0 +1,205 @@
def parse(el, ignore_order):
"""
Function used to parse result element. Result element can be
node, list, path, relationship, map or string. Function returns
same string for two same elements, etc. if properties of two nodes
are inverted, but labels and properties are the same, function
will return the same string for them. For two differenet
elements function will not return the same strings.
@param el:
string, element to parse.
@param ignore_order:
bool, ignore order inside of lists, etc. lists [1, 2, 3] and
[2, 3, 1] are the same if ignore_order is true, else false.
@return:
Parsed string of element.
"""
if el.startswith('(') and el.endswith(')'):
return parse_node(el, ignore_order)
if el.startswith('<') and el.endswith('>'):
return parse_path(el, ignore_order)
if el.startswith('{') and el.endswith('}'):
return parse_map(el, ignore_order)
if el.startswith('[') and el.endswith(']'):
if is_list(el):
return parse_list(el, ignore_order)
else:
return parse_rel(el, ignore_order)
return el
def is_list(el):
"""
Function returns true if string el is a list, else false.
@param el:
string, element to check.
@return:
true if el is list.
"""
if el[1] == ':':
return False
return True
def parse_path(path, ignore_order):
"""
Function used to parse path.
@param path:
string representing path
@return:
parsed path
"""
parsed_path = '<'
dif_open_closed_brackets = 0
for i in range(1, len(path) - 1):
if path[i] == '(' or path[i] == '{' or path[i] == '[':
dif_open_closed_brackets += 1
if dif_open_closed_brackets == 1:
start = i
if path[i] == ')' or path[i] == '}' or path[i] == ']':
dif_open_closed_brackets -= 1
if dif_open_closed_brackets == 0:
parsed_path += parse(path[start:(i + 1)], ignore_order)
elif dif_open_closed_brackets == 0:
parsed_path += path[i]
parsed_path += '>'
return parsed_path
def parse_node(node_str, ignore_order):
"""
Function used to parse node.
@param node:
string representing node
@return:
parsed node
"""
label = ''
labels = []
props_start = None
for i in range(1, len(node_str)):
if node_str[i] == ':' or node_str[i] == ')' or node_str[i] == '{':
if label.startswith(':'):
labels.append(label)
label = ''
label += node_str[i]
if node_str[i] == '{':
props_start = i
break
labels.sort()
parsed_node = '('
for label in labels:
parsed_node += label
if props_start is not None:
parsed_node += parse_map(
node_str[props_start:len(node_str) - 1], ignore_order)
parsed_node += ')'
return parsed_node
def parse_map(props, ignore_order):
"""
Function used to parse map.
@param props:
string representing map
@return:
parsed map
"""
dif_open_closed_brackets = 0
prop = ''
list_props = []
for i in range(1, len(props) - 1):
if props[i] == ',' and dif_open_closed_brackets == 0:
list_props.append(prop_to_str(prop, ignore_order))
prop = ''
else:
prop += props[i]
if props[i] == '(' or props[i] == '{' or props[i] == '[':
dif_open_closed_brackets += 1
elif props[i] == ')' or props[i] == '}' or props[i] == ']':
dif_open_closed_brackets -= 1
if prop != '':
list_props.append(prop_to_str(prop, ignore_order))
list_props.sort()
return '{' + ','.join(list_props) + '}'
def prop_to_str(prop, ignore_order):
"""
Function used to parse one pair of key, value in format 'key:value'.
Value must be parsed, key is string.
@param prop:
string, pair key:value to parse
@return:
parsed prop
"""
key = prop.split(':', 1)[0]
val = prop.split(':', 1)[1]
return key + ":" + parse(val, ignore_order)
def parse_list(l, ignore_order):
"""
Function used to parse list.
@param l:
string representing list
@return:
parsed list
"""
dif_open_closed_brackets = 0
el = ''
list_el = []
for i in range(1, len(l) - 1):
if l[i] == ',' and dif_open_closed_brackets == 0:
list_el.append(parse(el, ignore_order))
el = ''
else:
el += l[i]
if l[i] == '(' or l[i] == '{' or l[i] == '[':
dif_open_closed_brackets += 1
elif l[i] == ')' or l[i] == '}' or l[i] == ']':
dif_open_closed_brackets -= 1
if el != '':
list_el.append(parse(el, ignore_order))
if ignore_order:
list_el.sort()
return '[' + ','.join(list_el) + ']'
def parse_rel(rel, ignore_order):
"""
Function used to parse relationship.
@param rel:
string representing relationship
@return:
parsed relationship
"""
label = ''
labels = []
props_start = None
for i in range(1, len(rel)):
if rel[i] == ':' or rel[i] == ']' or rel[i] == '{':
if label.startswith(':'):
labels.append(label)
label = ''
label += rel[i]
if rel[i] == '{':
props_start = i
break
labels.sort()
parsed_rel = '['
for label in labels:
parsed_rel += label
if props_start is not None:
parsed_rel += parse_map(rel[props_start:len(rel) - 1], ignore_order)
parsed_rel += ']'
return parsed_rel

View File

@ -0,0 +1,345 @@
import database
import parser
from behave import *
from neo4j.v1.types import Node, Path, Relationship
@given('parameters are')
def parameters_step(context):
context.test_parameters.set_parameters_from_table(context.table)
@then('parameters are')
def parameters_step(context):
context.test_parameters.set_parameters_from_table(context.table)
@step('having executed')
def having_executed_step(context):
context.results = database.query(
context.text, context, context.test_parameters.get_parameters())
context.graph_properties.set_beginning_parameters()
@when('executing query')
def executing_query_step(context):
context.results = database.query(
context.text, context, context.test_parameters.get_parameters())
@when('executing control query')
def executing_query_step(context):
context.results = database.query(
context.text, context, context.test_parameters.get_parameters())
def parse_props(prop_json):
"""
Function used to parse properties from map of properties to string.
@param prop_json:
Dictionary, map of properties of graph element.
@return:
Map of properties in string format.
"""
if not prop_json:
return ""
properties = "{"
for prop in prop_json:
if prop_json[prop] is None:
properties += prop + ": null, "
elif isinstance(prop_json[prop], str):
properties += prop + ": " + "'" + prop_json[prop] + "', "
elif isinstance(prop_json[prop], bool):
if prop_json[prop] == True:
properties += prop + ": true, "
else:
properties += prop + ": false, "
else:
properties += prop + ": " + str(prop_json[prop]) + ", "
properties = properties[:-2]
properties += "}"
return properties
def to_string(element):
"""
Function used to parse result from database to string. Format of
the string is same as format of a result given in cucumber test.
@param element:
Can be None, string, bool, number, list, dict or any of
neo4j.v1.types Node, Path, Relationship. Element which
will be parsed.
@return:
String of parsed element.
"""
if element is None:
# parsing None
return "null"
if isinstance(element, Node):
# parsing Node
sol = "("
if element.labels:
sol += ':' + ': '.join(element.labels)
if element.properties:
if element.labels:
sol += ' '
sol += parse_props(element.properties)
sol += ")"
return sol
elif isinstance(element, Relationship):
# parsing Relationship
sol = "[:"
if element.type:
sol += element.type
if element.properties:
sol += ' '
sol += parse_props(element.properties)
sol += "]"
return sol
elif isinstance(element, Path):
# parsing Path
# TODO add longer paths
edges = []
nodes = []
for rel in element.relationships:
edges.append([rel.start, to_string(rel)])
for node in element.nodes:
nodes.append([node.id, to_string(node)])
sol = "<"
for i in range(0, len(edges)):
if edges[i][0] == nodes[i][0]:
sol += nodes[i][1] + "-" + edges[i][1] + "->"
else:
sol += nodes[i][1] + "<-" + edges[i][1] + "-"
sol += nodes[len(edges)][1]
sol += ">"
return sol
elif isinstance(element, str):
# parsing string
return "'" + element + "'"
elif isinstance(element, list):
# parsing list
sol = '['
el_str = []
for el in element:
el_str.append(to_string(el))
sol += ', '.join(el_str)
sol += ']'
return sol
elif isinstance(element, bool):
# parsing bool
if element:
return "true"
return "false"
elif isinstance(element, dict):
# parsing map
if len(element) == 0:
return '{}'
sol = '{'
for key, val in element.items():
sol += key + ':' + to_string(val) + ','
sol = sol[:-1] + '}'
return sol
elif isinstance(element, float):
# parsing float, scientific
if 'e' in str(element):
if str(element)[-3] == '-':
zeroes = int(str(element)[-2:]) - 1
num_str = ''
if str(element)[0] == '-':
num_str += '-'
num_str += '.' + zeroes * '0' + \
str(element)[:-4].replace("-", "").replace(".", "")
return num_str
return str(element)
def get_result_rows(context, ignore_order):
"""
Function returns results from database queries stored in context
as list.
@param context:
behave.runner.Context, behave context.
@param ignore_order:
bool, ignore order in result and expected list.
@return
Result rows.
"""
result_rows = []
for result in context.results:
keys = result.keys()
values = result.values()
for i in range(0, len(keys)):
result_rows.append(keys[i] + ":" + parser.parse(
to_string(values[i]).replace("\n", "\\n").replace(" ", ""), ignore_order))
return result_rows
def get_expected_rows(context, ignore_order):
"""
Fuction returns expected results as list from context table.
@param context:
behave.runner.Context, behave context.
@param ignore_order:
bool, ignore order in result and expected list.
@return
Expected rows
"""
expected_rows = []
for row in context.table:
for col in context.table.headings:
expected_rows.append(
col + ":" + parser.parse(row[col].replace(" ", ""), ignore_order))
return expected_rows
def validate(context, ignore_order):
"""
Function used to check if results from database are same
as expected results in any order.
@param context:
behave.runner.Context, behave context.
@param ignore_order:
bool, ignore order in result and expected list.
"""
result_rows = get_result_rows(context, ignore_order)
expected_rows = get_expected_rows(context, ignore_order)
context.log.info("Expected: %s", str(expected_rows))
context.log.info("Results: %s", str(result_rows))
assert(len(expected_rows) == len(result_rows))
for i in range(0, len(expected_rows)):
if expected_rows[i] in result_rows:
result_rows.remove(expected_rows[i])
else:
assert(False)
def validate_in_order(context, ignore_order):
"""
Function used to check if results from database are same
as exected results. First result from database should be
first in expected results list etc.
@param context:
behave.runner.Context, behave context.
@param ignore_order:
bool, ignore order in result and expected list.
"""
result_rows = get_result_rows(context, ignore_order)
expected_rows = get_expected_rows(context, ignore_order)
context.log.info("Expected: %s", str(expected_rows))
context.log.info("Results: %s", str(result_rows))
assert(len(expected_rows) == len(result_rows))
for i in range(0, len(expected_rows)):
if expected_rows[i] != result_rows[i]:
assert(False)
@then('the result should be')
def expected_result_step(context):
validate(context, False)
check_exception(context)
@then('the result should be, in order')
def expected_result_step(context):
validate_in_order(context, False)
check_exception(context)
@then('the result should be (ignoring element order for lists)')
def expected_result_step(context):
validate(context, True)
check_exception(context)
def check_exception(context):
if context.exception is not None:
context.log.info("Exception when eqecuting query!")
assert(False)
@then('the result should be empty')
def empty_result_step(context):
assert(len(context.results) == 0)
check_exception(context)
def side_effects_number(prop, table):
"""
Function returns an expected list of side effects for property prop
from a table given in a cucumber test.
@param prop:
String, roperty from description, can be nodes, relationships,
labels or properties.
@param table:
behave.model.Table, context table with side effects.
@return
Description.
"""
ret = []
for row in table:
sign = -1
if row[0][0] == '+':
sign = 1
if row[0][1:] == prop:
ret.append(int(row[1]) * sign)
sign = -1
row = table.headings
if row[0][0] == '+':
sign = 1
if row[0][1:] == prop:
ret.append(int(row[1]) * sign)
ret.sort()
return ret
@then('the side effects should be')
def side_effects_step(context):
if context.config.no_side_effects:
return
table = context.table
# get side effects from db queries
nodes_dif = side_effects_number("nodes", table)
relationships_dif = side_effects_number("relationships", table)
labels_dif = side_effects_number("labels", table)
properties_dif = side_effects_number("properties", table)
# compare side effects
assert(context.graph_properties.compare(nodes_dif,
relationships_dif, labels_dif, properties_dif) == True)
@then('no side effects')
def side_effects_step(context):
if context.config.no_side_effects:
return
# check if side effects are non existing
assert(context.graph_properties.compare([], [], [], []) == True)

View File

@ -0,0 +1,67 @@
import yaml
class TestParameters:
"""
Class used to store parameters from a cucumber test.
"""
def __init__(self):
"""
Constructor initializes parameters to empty dict.
"""
self.parameters = dict()
def set_parameters_from_table(self, table):
"""
Method gets table, parses parameters and sets them
in self.parameters.
@param self:
Class instance.
@param table:
behave.model.Table, table of unparsed parameters
from behave.runner.Context.
"""
par = dict()
for row in table:
par[row[0]] = self.parse_parameters(row[1])
if isinstance(par[row[0]], str) and par[row[0]].startswith("'") \
and par[row[0]].endswith("'"):
par[row[0]] = par[row[0]][1:len(par[row[0]]) - 1]
par[table.headings[0]] = self.parse_parameters(table.headings[1])
if isinstance(par[table.headings[0]], str) and \
par[table.headings[0]].startswith("'") and \
par[table.headings[0]].endswith("'"):
par[table.headings[0]] = \
par[table.headings[0]][1:len(par[table.headings[0]]) - 1]
self.parameters = par
def get_parameters(self):
"""
Method returns parameters.
@param self:
Instance of a class.
return:
Dictionary of parameters.
"""
return self.parameters
def parse_parameters(self, val):
"""
Method used for parsing parameters given in a cucumber test table
to a readable format for a database.
Integers are parsed to int values, floats to float values, bools
to bool values, null to None and structures are recursively
parsed and returned.
@param val:
String that needs to be parsed.
@return:
Format readable for a database.
"""
return yaml.load(val)

View File

@ -0,0 +1,108 @@
from behave.__main__ import main as behave_main
from behave import configuration
from argparse import ArgumentParser
import os
import sys
def parse_args():
argp = ArgumentParser(description=__doc__)
argp.add_argument("--root", default="tck_engine/tests/openCypher_M05",
help="Path to folder where tests are located, default is tck_engine/tests/openCypher_M05")
argp.add_argument(
"--stop", action="store_true", help="Stop testing after first fail.")
argp.add_argument("--no-side-effects", action="store_true",
help="Check for side effects in tests.")
argp.add_argument("--db", default="neo4j", choices=[
"neo4j", "memgraph"], help="Default is neo4j.")
argp.add_argument("--db-user", default="neo4j", help="Default is neo4j.")
argp.add_argument(
"--db-pass", default="1234", help="Default is 1234.")
argp.add_argument("--db-uri", default="bolt://localhost:7687",
help="Default is bolt://localhost:7687.")
argp.add_argument("--output-folder", default="tck_engine/results/",
help="Test result output folder, default is results/.")
argp.add_argument("--logging", default="DEBUG", choices=[
"INFO", "DEBUG"], help="Logging level, default is DEBUG.")
argp.add_argument("--unstable", action="store_true",
help="Include unstable feature from features.")
argp.add_argument("--single-fail", action="store_true",
help="Pause after failed scenario.")
argp.add_argument("--single-scenario", action="store_true",
help="Pause after every scenario.")
argp.add_argument("--single-feature", action="store_true",
help="Pause after every feature.")
return argp.parse_args()
def add_config(option, dictionary):
configuration.options.append(
((option,), dictionary)
)
def main():
"""
Script used to run behave tests with given options. List of
options is available when running python test_executor.py -help.
"""
args = parse_args()
tests_root = os.path.abspath(args.root)
# adds options to cucumber configuration
add_config("--no-side-effects",
dict(action="store_true", help="Exclude side effects."))
add_config("--database", dict(help="Choose database(memgraph/neo4j)."))
add_config("--database-password", dict(help="Database password."))
add_config("--database-username", dict(help="Database username."))
add_config("--database-uri", dict(help="Database uri."))
add_config("--output-folder", dict(
help="Folder where results of tests are written."))
add_config("--root", dict(help="Folder with test features."))
add_config("--single-fail",
dict(action="store_true", help="Pause after failed scenario."))
add_config("--single-scenario",
dict(action="store_true", help="Pause after every scenario."))
add_config("--single-feature",
dict(action="store_true", help="Pause after every feature."))
# list with all options
# options will be passed to the cucumber engine
behave_options = [tests_root]
if args.stop:
behave_options.append("--stop")
if args.no_side_effects:
behave_options.append("--no-side-effects")
if args.db != "memgraph":
behave_options.append("-e")
behave_options.append("memgraph*")
if not args.unstable:
behave_options.append("-e")
behave_options.append("unstable*")
behave_options.append("--database")
behave_options.append(args.db)
behave_options.append("--database-password")
behave_options.append(args.db_pass)
behave_options.append("--database-username")
behave_options.append(args.db_user)
behave_options.append("--database-uri")
behave_options.append(args.db_uri)
behave_options.append("--root")
behave_options.append(args.root)
if (args.single_fail):
behave_options.append("--single-fail")
if (args.single_scenario):
behave_options.append("--single-scenario")
if (args.single_feature):
behave_options.append("--single-feature")
behave_options.append("--output-folder")
behave_options.append(args.output_folder)
# runs tests with options
return behave_main(behave_options)
if __name__ == '__main__':
sys.exit(main())

View File

@ -0,0 +1,39 @@
class TestResults:
"""
Clas used to store test results. It has parameters total
and passed.
@attribute total:
int, total number of scenarios.
@attribute passed:
int, number of passed scenarios.
"""
def __init__(self):
self.total = 0
self.passed = 0
def num_passed(self):
"""
Getter for param passed.
"""
return self.passed
def num_total(self):
"""
Getter for param total.
"""
return self.total
def add_test(self, status):
"""
Method adds one scenario to current results. If
scenario passed, number of passed scenarios increases.
@param status:
string in behave 1.2.5, 'passed' if scenario passed
"""
if status == "passed":
self.passed += 1
self.total += 1

View File

@ -0,0 +1,659 @@
Feature: Test01
Scenario: Tests from graph_queries
Given an empty graph
When executing query:
"""
CREATE (g:garment {garment_id: 1234, garment_category_id: 1, conceals: 30}) RETURN g
"""
Then the result should be
| g |
| (:garment {garment_id: 1234, garment_category_id: 1, conceals: 30}) |
When executing query:
"""
MATCH(g:garment {garment_id: 1234}) SET g:AA RETURN g
"""
Then the result should be
| g |
| (:garment:AA {garment_id: 1234, garment_category_id: 1, conceals: 30}) |
When executing query:
"""
MATCH(g:garment {garment_id: 1234}) SET g:BB RETURN g
"""
Then the result should be
| g |
| (:garment:AA:BB {garment_id: 1234, garment_category_id: 1, conceals: 30}) |
When executing query:
"""
MATCH(g:garment {garment_id: 1234}) SET g:EE RETURN g
"""
Then the result should be
| g |
| (:garment:AA:BB:EE {garment_id: 1234, garment_category_id: 1, conceals: 30}) |
When executing query:
"""
CREATE (g:garment {garment_id: 2345, garment_category_id: 6, reveals: 10}) RETURN g
"""
Then the result should be
| g |
| (:garment {garment_id: 2345, garment_category_id: 6, reveals: 10}) |
When executing query:
"""
MATCH(g:garment {garment_id: 2345}) SET g:CC RETURN g
"""
Then the result should be
| g |
| (:garment:CC {garment_id: 2345, garment_category_id: 6, reveals: 10}) |
When executing query:
"""
MATCH(g:garment {garment_id: 2345}) SET g:DD RETURN g
"""
Then the result should be
| g |
| (:garment:CC:DD {garment_id: 2345, garment_category_id: 6, reveals: 10}) |
When executing query:
"""
CREATE (g:garment {garment_id: 3456, garment_category_id: 8}) RETURN g
"""
Then the result should be
| g |
| (:garment {garment_id: 3456, garment_category_id: 8}) |
When executing query:
"""
MATCH(g:garment {garment_id: 3456}) SET g:CC RETURN g
"""
Then the result should be
| g |
| (:garment:CC {garment_id: 3456, garment_category_id: 8}) |
When executing query:
"""
MATCH(g:garment {garment_id: 3456}) SET g:DD RETURN g
"""
Then the result should be
| g |
| (:garment:CC:DD {garment_id: 3456, garment_category_id: 8}) |
When executing query:
"""
CREATE (g:garment {garment_id: 4567, garment_category_id: 15}) RETURN g
"""
Then the result should be
| g |
| (:garment {garment_id: 4567, garment_category_id: 15}) |
When executing query:
"""
MATCH(g:garment {garment_id: 4567}) SET g:AA RETURN g
"""
Then the result should be
| g |
| (:garment:AA {garment_id: 4567, garment_category_id: 15}) |
When executing query:
"""
MATCH(g:garment {garment_id: 4567}) SET g:BB RETURN g
"""
Then the result should be
| g |
| (:garment:AA:BB {garment_id: 4567, garment_category_id: 15}) |
When executing query:
"""
MATCH(g:garment {garment_id: 4567}) SET g:DD RETURN g
"""
Then the result should be
| g |
| (:garment:AA:BB:DD {garment_id: 4567, garment_category_id: 15}) |
When executing query:
"""
CREATE (g:garment {garment_id: 5678, garment_category_id: 19}) RETURN g
"""
Then the result should be
| g |
| (:garment {garment_id: 5678, garment_category_id: 19}) |
When executing query:
"""
MATCH(g:garment {garment_id: 5678}) SET g:BB RETURN g
"""
Then the result should be
| g |
| (:garment:BB {garment_id: 5678, garment_category_id: 19}) |
When executing query:
"""
MATCH(g:garment {garment_id: 5678}) SET g:CC RETURN g
"""
Then the result should be
| g |
| (:garment:CC:BB {garment_id: 5678, garment_category_id: 19}) |
When executing query:
"""
MATCH(g:garment {garment_id: 5678}) SET g:EE RETURN g
"""
Then the result should be
| g |
| (:garment:CC:BB:EE {garment_id: 5678, garment_category_id: 19}) |
When executing query:
"""
CREATE (g:garment {garment_id: 6789, garment_category_id: 3}) RETURN g
"""
Then the result should be
| g |
| (:garment {garment_id: 6789, garment_category_id: 3}) |
When executing query:
"""
MATCH(g:garment {garment_id: 6789}) SET g:AA RETURN g
"""
Then the result should be
| g |
| (:garment:AA {garment_id: 6789, garment_category_id: 3}) |
When executing query:
"""
MATCH(g:garment {garment_id: 6789}) SET g:DD RETURN g
"""
Then the result should be
| g |
| (:garment:AA:DD {garment_id: 6789, garment_category_id: 3}) |
When executing query:
"""
MATCH(g:garment {garment_id: 6789}) SET g:EE RETURN g
"""
Then the result should be
| g |
| (:garment:AA:DD:EE {garment_id: 6789, garment_category_id: 3}) |
When executing query:
"""
CREATE (g:garment {garment_id: 7890, garment_category_id: 25}) RETURN g
"""
Then the result should be
| g |
| (:garment {garment_id: 7890, garment_category_id: 25}) |
When executing query:
"""
MATCH(g:garment {garment_id: 7890}) SET g:AA RETURN g
"""
Then the result should be
| g |
| (:garment:AA {garment_id: 7890, garment_category_id: 25}) |
When executing query:
"""
MATCH(g:garment {garment_id: 7890}) SET g:BB RETURN g
"""
Then the result should be
| g |
| (:garment:AA:BB {garment_id: 7890, garment_category_id: 25}) |
When executing query:
"""
MATCH(g:garment {garment_id: 7890}) SET g:CC RETURN g
"""
Then the result should be
| g |
| (:garment:AA:BB:CC {garment_id: 7890, garment_category_id: 25}) |
When executing query:
"""
MATCH(g:garment {garment_id: 7890}) SET g:EE RETURN g
"""
Then the result should be
| g |
| (:garment:AA:BB:CC:EE {garment_id: 7890, garment_category_id: 25}) |
When executing query:
"""
MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 6789}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 5678}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 3456}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r
"""
Then the result should be
| r |
| [:default_outfit] |
When executing query:
"""
CREATE (p:profile {profile_id: 111, partner_id: 55, reveals: 30}) RETURN p
"""
Then the result should be
| p |
| (:profile {profile_id: 111, partner_id: 55, reveals: 30}) |
When executing query:
"""
CREATE (p:profile {profile_id: 112, partner_id: 55}) RETURN p
"""
Then the result should be
| p |
| (:profile {profile_id: 112, partner_id: 55}) |
When executing query:
"""
CREATE (p:profile {profile_id: 112, partner_id: 77, conceals: 10}) RETURN p
"""
Then the result should be
| p |
| (:profile {profile_id: 112, partner_id: 77, conceals: 10}) |
When executing query:
"""
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 1234}) CREATE (p)-[s:score]->(g) SET s.score=1500 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1500}] |
When executing query:
"""
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 2345}) CREATE (p)-[s:score]->(g) SET s.score=1200 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1200}] |
When executing query:
"""
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 3456}) CREATE (p)-[s:score]->(g) SET s.score=1000 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1000}] |
When executing query:
"""
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 4567}) CREATE (p)-[s:score]->(g) SET s.score=1000 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1000}] |
When executing query:
"""
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 6789}) CREATE (p)-[s:score]->(g) SET s.score=1500 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1500}] |
When executing query:
"""
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 7890}) CREATE (p)-[s:score]->(g) SET s.score=1800 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1800}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 1234}) CREATE (p)-[s:score]->(g) SET s.score=2000 RETURN s
"""
Then the result should be
| s |
| [:score {score: 2000}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 4567}) CREATE (p)-[s:score]->(g) SET s.score=1500 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1500}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 5678}) CREATE (p)-[s:score]->(g) SET s.score=1000 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1000}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 6789}) CREATE (p)-[s:score]->(g) SET s.score=1600 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1600}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 7890}) CREATE (p)-[s:score]->(g) SET s.score=1900 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1900}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 1234}) CREATE (p)-[s:score]->(g) SET s.score=1500 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1500}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 2345}) CREATE (p)-[s:score]->(g) SET s.score=1300 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1300}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 3456}) CREATE (p)-[s:score]->(g) SET s.score=1300 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1300}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 5678}) CREATE (p)-[s:score]->(g) SET s.score=1200 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1200}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 6789}) CREATE (p)-[s:score]->(g) SET s.score=1700 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1700}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 7890}) CREATE (p)-[s:score]->(g) SET s.score=1900 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1900}] |
When executing query:
"""
MATCH (g:garment {garment_id: 1234}) RETURN g
"""
Then the result should be
| g |
| (:garment:AA:BB:EE {garment_id: 1234, conceals:30, garment_category_id: 1}) |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77}) RETURN p
"""
Then the result should be
| p |
| (:profile {profile_id: 112, partner_id: 77, conceals:10}) |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77})-[s:score]-(g:garment {garment_id: 1234}) RETURN s
"""
Then the result should be
| s |
| [:score {score: 1500}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77})-[s:score]-(g:garment {garment_id: 1234}) SET s.score = 3137 RETURN s
"""
Then the result should be
| s |
| [:score {score: 3137}] |
When executing query:
"""
MATCH (p:profile {profile_id: 112, partner_id: 77})-[s:score]-(g:garment {garment_id: 1234}) SET s.score = 1500 RETURN s
"""
Then the result should be
| s |
| [:score {score: 1500}] |
When executing query:
"""
MATCH (a:garment)-[:default_outfit]-(b:garment)-[:default_outfit]-(c:garment)-[:default_outfit]-(d:garment)-[:default_outfit]-(a:garment)-[:default_outfit]-(c:garment), (b:garment)-[:default_outfit]-(d:garment) WHERE a.garment_id=1234 RETURN a.garment_id,b.garment_id,c.garment_id,d.garment_id
"""
Then the result should be
| a.garment_id | b.garment_id | c.garment_id | d.garment_id |
| 1234 | 5678 | 4567 | 7890 |
| 1234 | 6789 | 4567 | 7890 |
| 1234 | 4567 | 5678 | 7890 |
| 1234 | 4567 | 6789 | 7890 |
| 1234 | 7890 | 4567 | 6789 |
| 1234 | 4567 | 7890 | 6789 |
| 1234 | 7890 | 4567 | 5678 |
| 1234 | 4567 | 7890 | 5678 |
| 1234 | 5678 | 7890 | 4567 |
| 1234 | 7890 | 5678 | 4567 |
| 1234 | 7890 | 6789 | 4567 |
| 1234 | 6789 | 7890 | 4567 |
When executing query:
"""
MATCH (a:garment)-[:default_outfit]-(b:garment)-[:default_outfit]-(c:garment)-[:default_outfit]-(d:garment)-[:default_outfit]-(a:garment)-[:default_outfit]-(c:garment), (b:garment)-[:default_outfit]-(d:garment), (e:profile {profile_id: 112, partner_id: 55})-[s1:score]-(a:garment),(e:profile {profile_id: 112, partner_id: 55})-[s2:score]-(b:garment), (e:profile {profile_id: 112, partner_id: 55})-[s3:score]-(c:garment), (e:profile {profile_id: 112, partner_id: 55})-[s4:score]-(d:garment) WHERE a.garment_id=1234 RETURN a.garment_id,b.garment_id,c.garment_id,d.garment_id, s1.score+s2.score+s3.score+s4.score ORDER BY s1.score+s2.score+s3.score+s4.score DESC LIMIT 10
"""
Then the result should be, in order
| a.garment_id | b.garment_id | c.garment_id | d.garment_id | s1.score+s2.score+s3.score+s4.score |
| 1234 | 7890 | 4567 | 6789 | 7000|
| 1234 | 7890 | 6789 | 4567 | 7000|
| 1234 | 6789 | 4567 | 7890 | 7000|
| 1234 | 6789 | 7890 | 4567 | 7000|
| 1234 | 4567 | 6789 | 7890 | 7000|
| 1234 | 4567 | 7890 | 6789 | 7000|
| 1234 | 7890 | 4567 | 5678 | 6400|
| 1234 | 7890 | 5678 | 4567 | 6400|
| 1234 | 5678 | 4567 | 7890 | 6400|
| 1234 | 5678 | 7890 | 4567 | 6400|

View File

@ -0,0 +1,43 @@
Feature: Test02
Scenario: First test from graph_queries
Given graph "dressipi_graph01"
When executing query:
"""
MATCH (a:garment)-[:default_outfit]-(b:garment)-[:default_outfit]-(c:garment)-[:default_outfit]-(d:garment)-[:default_outfit]-(a:garment)-[:default_outfit]-(c:garment), (b:garment)-[:default_outfit]-(d:garment) WHERE a.garment_id=1234 RETURN a.garment_id,b.garment_id,c.garment_id,d.garment_id
"""
Then the result should be
| a.garment_id | b.garment_id | c.garment_id | d.garment_id |
| 1234 | 5678 | 4567 | 7890 |
| 1234 | 6789 | 4567 | 7890 |
| 1234 | 4567 | 5678 | 7890 |
| 1234 | 4567 | 6789 | 7890 |
| 1234 | 7890 | 4567 | 6789 |
| 1234 | 4567 | 7890 | 6789 |
| 1234 | 7890 | 4567 | 5678 |
| 1234 | 4567 | 7890 | 5678 |
| 1234 | 5678 | 7890 | 4567 |
| 1234 | 7890 | 5678 | 4567 |
| 1234 | 7890 | 6789 | 4567 |
| 1234 | 6789 | 7890 | 4567 |
Scenario: Second test from graph_queries
Given graph "dressipi_graph01"
When executing query:
"""
MATCH (a:garment)-[:default_outfit]-(b:garment)-[:default_outfit]-(c:garment)-[:default_outfit]-(d:garment)-[:default_outfit]-(a:garment)-[:default_outfit]-(c:garment), (b:garment)-[:default_outfit]-(d:garment), (e:profile {profile_id: 112, partner_id: 55})-[s1:score]-(a:garment),(e:profile {profile_id: 112, partner_id: 55})-[s2:score]-(b:garment), (e:profile {profile_id: 112, partner_id: 55})-[s3:score]-(c:garment), (e:profile {profile_id: 112, partner_id: 55})-[s4:score]-(d:garment) WHERE a.garment_id=1234 RETURN a.garment_id,b.garment_id,c.garment_id,d.garment_id, s1.score+s2.score+s3.score+s4.score ORDER BY s1.score+s2.score+s3.score+s4.score DESC LIMIT 10
"""
Then the result should be, in order
| a.garment_id | b.garment_id | c.garment_id | d.garment_id | s1.score+s2.score+s3.score+s4.score |
| 1234 | 7890 | 4567 | 6789 | 7000|
| 1234 | 7890 | 6789 | 4567 | 7000|
| 1234 | 6789 | 4567 | 7890 | 7000|
| 1234 | 6789 | 7890 | 4567 | 7000|
| 1234 | 4567 | 6789 | 7890 | 7000|
| 1234 | 4567 | 7890 | 6789 | 7000|
| 1234 | 7890 | 4567 | 5678 | 6400|
| 1234 | 7890 | 5678 | 4567 | 6400|
| 1234 | 5678 | 4567 | 7890 | 6400|
| 1234 | 5678 | 7890 | 4567 | 6400|

View File

@ -0,0 +1,104 @@
CREATE (g:garment {garment_id: 1234, garment_category_id: 1, conceals: 30});
MATCH(g:garment {garment_id: 1234}) SET g:AA;
MATCH(g:garment {garment_id: 1234}) SET g:BB;
MATCH(g:garment {garment_id: 1234}) SET g:EE;
CREATE (g:garment {garment_id: 2345, garment_category_id: 6, reveals: 10});
MATCH(g:garment {garment_id: 2345}) SET g:CC;
MATCH(g:garment {garment_id: 2345}) SET g:DD;
CREATE (g:garment {garment_id: 3456, garment_category_id: 8});
MATCH(g:garment {garment_id: 3456}) SET g:CC;
MATCH(g:garment {garment_id: 3456}) SET g:DD;
CREATE (g:garment {garment_id: 4567, garment_category_id: 15});
MATCH(g:garment {garment_id: 4567}) SET g:AA;
MATCH(g:garment {garment_id: 4567}) SET g:BB;
MATCH(g:garment {garment_id: 4567}) SET g:DD;
CREATE (g:garment {garment_id: 5678, garment_category_id: 19});
MATCH(g:garment {garment_id: 5678}) SET g:BB;
MATCH(g:garment {garment_id: 5678}) SET g:CC;
MATCH(g:garment {garment_id: 5678}) SET g:EE;
CREATE (g:garment {garment_id: 6789, garment_category_id: 3});
MATCH(g:garment {garment_id: 6789}) SET g:AA;
MATCH(g:garment {garment_id: 6789}) SET g:DD;
MATCH(g:garment {garment_id: 6789}) SET g:EE;
CREATE (g:garment {garment_id: 7890, garment_category_id: 25});
MATCH(g:garment {garment_id: 7890}) SET g:AA;
MATCH(g:garment {garment_id: 7890}) SET g:BB;
MATCH(g:garment {garment_id: 7890}) SET g:CC;
MATCH(g:garment {garment_id: 7890}) SET g:EE;
MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 6789}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 5678}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 3456}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2);
MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2);
CREATE (p:profile {profile_id: 111, partner_id: 55, reveals: 30});
CREATE (p:profile {profile_id: 112, partner_id: 55});
CREATE (p:profile {profile_id: 112, partner_id: 77, conceals: 10});
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 1234}) CREATE (p)-[s:score]->(g) SET s.score=1500;
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 2345}) CREATE (p)-[s:score]->(g) SET s.score=1200;
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 3456}) CREATE (p)-[s:score]->(g) SET s.score=1000;
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 4567}) CREATE (p)-[s:score]->(g) SET s.score=1000;
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 6789}) CREATE (p)-[s:score]->(g) SET s.score=1500;
MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 7890}) CREATE (p)-[s:score]->(g) SET s.score=1800;
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 1234}) CREATE (p)-[s:score]->(g) SET s.score=2000;
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 4567}) CREATE (p)-[s:score]->(g) SET s.score=1500;
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 5678}) CREATE (p)-[s:score]->(g) SET s.score=1000;
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 6789}) CREATE (p)-[s:score]->(g) SET s.score=1600;
MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 7890}) CREATE (p)-[s:score]->(g) SET s.score=1900;
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 1234}) CREATE (p)-[s:score]->(g) SET s.score=1500;
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 2345}) CREATE (p)-[s:score]->(g) SET s.score=1300;
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 3456}) CREATE (p)-[s:score]->(g) SET s.score=1300;
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 5678}) CREATE (p)-[s:score]->(g) SET s.score=1200;
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 6789}) CREATE (p)-[s:score]->(g) SET s.score=1700;
MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 7890}) CREATE (p)-[s:score]->(g) SET s.score=1900;
MATCH (p:profile {profile_id: 112, partner_id: 77})-[s:score]-(g:garment {garment_id: 1234}) SET s.score = 3137;
MATCH (p:profile {profile_id: 112, partner_id: 77})-[s:score]-(g:garment {garment_id: 1234}) SET s.score = 1500;

View File

@ -0,0 +1,252 @@
Feature: Aggregations
Scenario: Count test 01:
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C)
"""
When executing query:
"""
MATCH (a) RETURN COUNT(a) AS n
"""
Then the result should be:
| n |
| 3 |
Scenario: Count test 02:
Given an empty graph
When executing query:
"""
RETURN COUNT(123) AS n
"""
Then the result should be:
| n |
| 1 |
Scenario: Count test 03:
Given an empty graph
When executing query:
"""
RETURN COUNT(true) AS n
"""
Then the result should be:
| n |
| 1 |
Scenario: Count test 04:
Given an empty graph
When executing query:
"""
RETURN COUNT('abcd') AS n
"""
Then the result should be:
| n |
| 1 |
Scenario: Count test 05:
Given an empty graph
And having executed
"""
CREATE (a{x: 0}), (b{x: 0}), (c{x: 0}), (d{x: 1}), (e{x: 1})
"""
When executing query:
"""
MATCH (a) RETURN COUNT(a) AS n, a.x
"""
Then the result should be:
| n | a.x |
| 3 | 0 |
| 2 | 1 |
Scenario: Sum test 01:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 7}), (c{x: 5}), (d{x: 'x'})
"""
When executing query:
"""
MATCH (a) RETURN SUM(a.x) AS n
"""
Then an error should be raised
Scenario: Sum test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b), (c{x: 5}), (d{x: null})
"""
When executing query:
"""
MATCH (a) RETURN SUM(a.x) AS n
"""
Then the result should be:
| n |
| 6 |
Scenario: Sum test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 0, y:3}), (b{x: 0, y:1}), (c{x: 0}), (d{x: 1, y:4}), (e{x: 1})
"""
When executing query:
"""
MATCH (a) RETURN SUM(a.y) AS n, a.x
"""
Then the result should be:
| n | a.x |
| 4 | 0 |
| 4 | 1 |
Scenario: Avg test 01:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 7}), (c{x: 5}), (d{x: 'x'})
"""
When executing query:
"""
MATCH (a) RETURN AVG(a.x) AS n
"""
Then an error should be raised
Scenario: Avg test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 1.25}), (b), (c{x: 4.75}), (d{x: null})
"""
When executing query:
"""
MATCH (a) RETURN AVG(a.x) AS n
"""
Then the result should be:
| n |
| 3.0 |
Scenario: Avg test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 0, y:3}), (b{x: 0, y:1}), (c{x: 0}), (d{x: 1, y:4}), (e{x: 1})
"""
When executing query:
"""
MATCH (a) RETURN AVG(a.y) AS n, a.x
"""
Then the result should be:
| n | a.x |
| 2.0 | 0 |
| 4.0 | 1 |
Scenario: Min test 01:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 7}), (c{x: 5}), (d{x: 'x'})
"""
When executing query:
"""
MATCH (a) RETURN MIN(a.x) AS n
"""
Then an error should be raised
Scenario: Min test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b), (c{x: 9}), (d{x: null})
"""
When executing query:
"""
MATCH (a) RETURN MIN(a.x) AS n
"""
Then the result should be:
| n |
| 1 |
Scenario: Min test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 0, y:3}), (b{x: 0, y:1}), (c{x: 0}), (d{x: 1, y:4}), (e{x: 1})
"""
When executing query:
"""
MATCH (a) RETURN MIN(a.y) AS n, a.x
"""
Then the result should be:
| n | a.x |
| 1 | 0 |
| 4 | 1 |
Scenario: Max test 01:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 7}), (c{x: 5}), (d{x: 'x'})
"""
When executing query:
"""
MATCH (a) RETURN MAX(a.x) AS n
"""
Then an error should be raised
Scenario: Max test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b), (c{x: 9}), (d{x: null})
"""
When executing query:
"""
MATCH (a) RETURN MAX(a.x) AS n
"""
Then the result should be:
| n |
| 9 |
Scenario: Max test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 0, y:3}), (b{x: 0, y:1}), (c{x: 0}), (d{x: 1, y:4}), (e{x: 1})
"""
When executing query:
"""
MATCH (a) RETURN Max(a.y) AS n, a.x
"""
Then the result should be:
| n | a.x |
| 3 | 0 |
| 4 | 1 |
Scenario: Collect test 01:
Given an empty graph
And having executed
"""
CREATE (a{x: 0}), (b{x: True}), (c{x: 'asdf'})
"""
When executing query:
"""
MATCH (a) RETURN collect(a.x) AS n
"""
Then the result should be (ignoring element order for lists)
| n |
| [0, true, 'asdf'] |
Scenario: Collect test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 0}), (b{x: True}), (c{x: 'asdf'}), (d{x: null})
"""
When executing query:
"""
MATCH (a) RETURN collect(a.x) AS n
"""
Then the result should be (ignoring element order for lists)
| n |
| [0, true, 'asdf'] |

View File

@ -0,0 +1,173 @@
Feature: Cartesian
Scenario: Match multiple patterns 01
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (a)-[:X]->(b), (c)-[:X]->(a)
"""
When executing query:
"""
MATCH (a)-[]->(), (b) CREATE (a)-[r:R]->(b) RETURN a, b, r
"""
Then the result should be:
| a | b | r |
| (:C) | (:A) | [:R] |
| (:C) | (:B) | [:R] |
| (:C) | (:C) | [:R] |
| (:A) | (:A) | [:R] |
| (:A) | (:B) | [:R] |
| (:A) | (:C) | [:R] |
Scenario: Match multiple patterns 02
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (d:D), (e:E), (f:F), (a)-[:X]->(b), (b)-[:X]->(c), (d)-[:X]->(e), (e)-[:X]->(f)
"""
When executing query:
"""
MATCH (a:B)--(b), (c:E)--(d) CREATE (b)-[r:R]->(d) return b, d, r
"""
Then the result should be:
| b | d | r |
| (:A) | (:D) | [:R] |
| (:A) | (:F) | [:R] |
| (:C) | (:D) | [:R] |
| (:C) | (:F) | [:R] |
Scenario: Match multiple patterns 03
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (d:D), (a)-[:R]->(b), (b)-[:R]->(c), (c)-[:R]->(d)
"""
When executing query:
"""
MATCH (a:B)--(b), (c:B)--(d) RETURN b, d
"""
Then the result should be:
| b | d |
| (:A) | (:C) |
| (:C) | (:A) |
Scenario: Match multiple patterns 04
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (d:D), (a)-[:R]->(b), (b)-[:R]->(c), (c)-[:R]->(d)
"""
When executing query:
"""
MATCH (a:A)--(b), (c:A)--(d) RETURN a, b, c, d
"""
Then the result should be empty
Scenario: Multiple match 01
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (d:D), (a)-[:R]->(b), (b)-[:R]->(c), (c)-[:R]->(d)
"""
When executing query:
"""
MATCH (a:B)--(b) MATCH (c:B)--(d) RETURN b, d
"""
Then the result should be:
| b | d |
| (:A) | (:A) |
| (:A) | (:C) |
| (:C) | (:A) |
| (:C) | (:C) |
Scenario: Multiple match 02
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (d:D), (a)-[:R]->(b), (b)-[:R]->(c), (c)-[:R]->(d)
"""
When executing query:
"""
MATCH (a:A)--(b) MATCH (a)--(c) RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| (:A) | (:B) | (:B) |
Scenario: Multiple match 03
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (a)-[:X]->(b), (c)-[:X]->(a)
"""
When executing query:
"""
MATCH (a)-[]->() MATCH (b) CREATE (a)-[r:R]->(b) RETURN a, b, r
"""
Then the result should be:
| a | b | r |
| (:C) | (:A) | [:R] |
| (:C) | (:B) | [:R] |
| (:C) | (:C) | [:R] |
| (:A) | (:A) | [:R] |
| (:A) | (:B) | [:R] |
| (:A) | (:C) | [:R] |
Scenario: Multiple match 04
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (d:D), (e:E), (f:F), (a)-[:X]->(b), (b)-[:X]->(c), (d)-[:X]->(e), (e)-[:X]->(f)
"""
When executing query:
"""
MATCH (a:B)--(b) MATCH (c:E)--(d) CREATE (b)-[r:R]->(d) return b, d, r
"""
Then the result should be:
| b | d | r |
| (:A) | (:D) | [:R] |
| (:A) | (:F) | [:R] |
| (:C) | (:D) | [:R] |
| (:C) | (:F) | [:R] |
Scenario: Multiple match 05
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C)
"""
When executing query:
"""
MATCH(a) MATCH(a) RETURN a
"""
Then the result should be:
| a |
| (:A) |
| (:B) |
| (:C) |
Scenario: Multiple match 06
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (a)-[:R]->(b), (b)-[:R]->(c)
"""
When executing query:
"""
MATCH (a)-[]->() MATCH (a:B) MATCH (b:C) RETURN a, b
"""
Then the result should be:
| a | b |
| (:B) | (:C) |
Scenario: Multiple match 07
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B), (c:C), (a)-[:R]->(b), (b)-[:R]->(c)
"""
When executing query:
"""
MATCH (a)-[]->() MATCH (a:B) MATCH (a:C) RETURN a
"""
Then the result should be empty

View File

@ -0,0 +1,177 @@
Feature: Create
Scenario: Create empty node without clearing database
When executing query:
"""
CREATE (n)
"""
Then the result should be empty
Scenario: Create node with label without clearing database
When executing query:
"""
CREATE (n:L:K)
"""
Then the result should be empty
Scenario: Create node with int property without clearing database
When executing query:
"""
CREATE (n{a: 1})
"""
Then the result should be empty
Scenario: Create node with float property without clearing database
When executing query:
"""
CREATE (n{a: 1.0})
"""
Then the result should be empty
Scenario: Create node with string property without clearing database
When executing query:
"""
CREATE (n{a: 'string'})
"""
Then the result should be empty
Scenario: Create node with bool properties without clearing database
When executing query:
"""
CREATE (n{a: True, b: false})
"""
Then the result should be empty
Scenario: Create node with null property without clearing database
When executing query:
"""
CREATE (n{a: NULL})
"""
Then the result should be empty
Scenario: Create node with properties without clearing database
When executing query:
"""
CREATE (n{a: 1.0, b: false, c: 1, d: 'neki"string"', e: NULL})
"""
Then the result should be empty
Scenario: Create node with properties without clearing database
When executing query:
"""
CREATE (n:L:K:T {a: 1.0, b: false, c: 1, d: 'neki"string"', e: NULL})
"""
Then the result should be empty
Scenario: Create multiple nodes connected by relationships
When executing query:
"""
CREATE (a)-[b:X]->(c)<-[d:Y]-(e)
"""
Then the result should be empty
Scenario: Create multiple nodes connected by relationships
When executing query:
"""
CREATE (n)-[:X]->(n)<-[:Y]-(n)
"""
Then the result should be empty
Scenario: Create multiple nodes connected by relationships with properties
When executing query:
"""
CREATE (a)-[b:X{a: 1.0}]->(c)<-[d:Y{d: "xyz"}]-(e)
"""
Then the result should be empty
Scenario: Create empty node without clearing database:
When executing query:
"""
CREATE (n) RETURN n
"""
Then the result should be:
| n |
|( )|
Scenario: Create node with labels without clearing database and return it
When executing query:
"""
CREATE (n:A:B) RETURN n
"""
Then the result should be:
| n |
|(:A:B)|
Scenario: Create node with properties without clearing database and return it
When executing query:
"""
CREATE (n{a: 1.0, b: FALSE, c: 1, d: 'neki"string"', e: NULL}) RETURN n
"""
Then the result should be:
| n |
|({a: 1.0, b: false, c: 1, d: 'neki"string"'}) |
Scenario: Create node with labels and properties without clearing database and return it
When executing query:
"""
CREATE (n:A:B{a: 1.0, b: False, c: 1, d: 'neki"string"', e: NULL}) RETURN n
"""
Then the result should be:
| n |
| (:A:B{a: 1.0, b: false, c: 1, d: 'neki"string"'}) |
Scenario: Create node with properties and labels without clearing database and return its properties
When executing query:
"""
CREATE (n:A:B{a: 1.0, b: false, c: 1, d: 'neki"string"', e: NULL}) RETURN n.a, n.b, n.c, n.d, n.e
"""
Then the result should be:
| n.a | n.b | n.c | n.d | n.e |
| 1.0 | false | 1 | 'neki"string"'| null |
Scenario: Create multiple nodes connected by relationships with properties and return it
When executing query:
"""
CREATE (a)-[b:X{a: 1.0}]->(c)<-[d:Y{d: "xyz"}]-(e) RETURN b
"""
Then the result should be:
| b |
| [:X{a: 1.0}] |
Scenario: Multiple create 01:
When executing query:
"""
CREATE (a)-[b:X{a: 1.0}]->(c)<-[d:Y{d: "xyz"}]-(e) CREATE (n:A:B{a: 1.0, b: False, c: 1, d: 'neki"string"', e: NULL}) RETURN b, n
"""
Then the result should be:
| n | b |
| (:A:B{a: 1.0, b: false, c: 1, d: 'neki"string"'}) | [:X{a: 1.0}] |
Scenario: Multiple create 02:
When executing query:
"""
CREATE (a)-[:X]->(b) CREATE (a)-[:Y]->(c)
"""
Then the result should be empty
Scenario: Multiple create 03:
Given an empty graph
And having executed
"""
CREATE (a:A), (b:B) CREATE (c:C), (a)-[:R]->(b) CREATE (b)-[:R]->(c)
"""
When executing query:
"""
MATCH (a)-[]->() MATCH (a:B) MATCH (b:C) RETURN a, b
"""
Then the result should be:
| a | b |
| (:B) | (:C) |
Scenario: Multiple create 04:
Given an empty graph
When executing query:
"""
CREATE (a:A) CREATE (a:B)
"""
Then an error should be raised

View File

@ -0,0 +1,133 @@
Feature: Expressions
Scenario: Test equal operator
Given an empty graph
When executing query:
"""
CREATE (a)
RETURN 1=1 and 1.0=1.0 and 'abc'='abc' and false=false and a.age is null as n
"""
Then the result should be:
| n |
| true |
Scenario: Test not equal operator
Given an empty graph
When executing query:
"""
CREATE (a{age: 1})
RETURN not 1<>1 and 1.0<>1.1 and 'abcd'<>'abc' and false<>true and a.age is not null as n
"""
Then the result should be:
| n |
| true |
Scenario: Test greater operator
Given an empty graph
When executing query:
"""
RETURN 2>1 and not 1.0>1.1 and 'abcd'>'abc' as n
"""
Then the result should be:
| n |
| true |
Scenario: Test less operator
Given an empty graph
When executing query:
"""
RETURN not 2<1 and 1.0<1.1 and not 'abcd'<'abc' as n
"""
Then the result should be:
| n |
| true |
Scenario: Test greater equal operator
Given an empty graph
When executing query:
"""
RETURN 2>=2 and not 1.0>=1.1 and 'abcd'>='abc' as n
"""
Then the result should be:
| n |
| true |
Scenario: Test less equal operator
Given an empty graph
When executing query:
"""
RETURN 2<=2 and 1.0<=1.1 and not 'abcd'<='abc' as n
"""
Then the result should be:
| n |
| true |
Scenario: Test plus operator
Given an empty graph
When executing query:
"""
RETURN 3+2=1.09+3.91 as n
"""
Then the result should be:
| n |
| true |
Scenario: Test minus operator
Given an empty graph
When executing query:
"""
RETURN 3-2=1.09-0.09 as n
"""
Then the result should be:
| n |
| true |
Scenario: Test multiply operator
Given an empty graph
When executing query:
"""
RETURN 3*2=1.5*4 as n
"""
Then the result should be:
| n |
| true |
Scenario: Test divide operator1
Given an empty graph
When executing query:
"""
RETURN 3/2<>7.5/5 as n
"""
Then the result should be:
| n |
| true |
Scenario: Test divide operator2
Given an empty graph
When executing query:
"""
RETURN 3.0/2=7.5/5 as n
"""
Then the result should be:
| n |
| true |
Scenario: Test mod operator
Given an empty graph
When executing query:
"""
RETURN 3%2=1 as n
"""
Then the result should be:
| n |
| true |
Scenario: Test one big logical equation
Given an empty graph
When executing query:
"""
RETURN not true or true and false or not ((true xor false or true) and true or false xor true ) as n
"""
Then the result should be:
| n |
| false |

View File

@ -0,0 +1,616 @@
Feature: Functions
Scenario: Sqrt test 01:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 7}), (c{x: 5}), (d{x: 'x'})
"""
When executing query:
"""
MATCH (a) RETURN SQRT(a.x) AS n
"""
Then an error should be raised
Scenario: Sqrt test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b), (c{x: 9}), (d{x: null})
"""
When executing query:
"""
MATCH (a) RETURN SQRT(a.x) AS n
"""
Then the result should be:
| n |
| 1.0 |
| null |
| 3.0 |
| null |
Scenario: ToBoolean test 01:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 0}))
"""
When executing query:
"""
MATCH (a) RETURN TOBOOLEAN(a.x) AS n
"""
Then an error should be raised
Scenario: ToBoolean test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 'TrUe'}), (b{x: 'not bool'}), (c{x: faLsE}), (d{x: null}), (e{x: 'fALse'}), (f{x: tRuE})
"""
When executing query:
"""
MATCH (a) RETURN TOBOOLEAN(a.x) AS n
"""
Then the result should be:
| n |
| true |
| null |
| false |
| null |
| false |
| true |
Scenario: ToInteger test 01:
Given an empty graph
And having executed
"""
CREATE (b{x: true}))
"""
When executing query:
"""
MATCH (a) RETURN TOINTEGER(a.x) AS n
"""
Then an error should be raised
Scenario: ToInteger test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 'not int'}), (c{x: '-12'}), (d{x: null}), (e{x: '1.2'}), (f{x: '1.9'})
"""
When executing query:
"""
MATCH (a) RETURN TOINTEGER(a.x) AS n
"""
Then the result should be:
| n |
| 1 |
| null |
| -12 |
| null |
| 1 |
| 1 |
Scenario: ToFloat test 01:
Given an empty graph
And having executed
"""
CREATE (b{x: true}))
"""
When executing query:
"""
MATCH (a) RETURN TOFLOAT(a.x) AS n
"""
Then an error should be raised
Scenario: ToFloat test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 'not float'}), (c{x: '-12'}), (d{x: null}), (e{x: '1.2'}), (f{x: 1.9})
"""
When executing query:
"""
MATCH (a) RETURN TOFLOAT(a.x) AS n
"""
Then the result should be:
| n |
| 1.0 |
| null |
| -12.0 |
| null |
| 1.2 |
| 1.9 |
Scenario: Abs test 01:
Given an empty graph
And having executed
"""
CREATE (b{x: true})
"""
When executing query:
"""
MATCH (a) RETURN ABS(a.x) AS n
"""
Then an error should be raised
Scenario: Abs test 02:
Given an empty graph
And having executed
"""
CREATE (b{x: '1.0'})
"""
When executing query:
"""
MATCH (a) RETURN ABS(a.x) AS n
"""
Then an error should be raised
Scenario: Abs test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (c{x: -12}), (d{x: null}), (e{x: -2.3}), (f{x: 1.9})
"""
When executing query:
"""
MATCH (a) RETURN ABS(a.x) AS n
"""
Then the result should be:
| n |
| 1 |
| 12 |
| null |
| 2.3 |
| 1.9 |
Scenario: Exp test 01:
Given an empty graph
And having executed
"""
CREATE (b{x: true}))
"""
When executing query:
"""
MATCH (a) RETURN EXP(a.x) AS n
"""
Then an error should be raised
Scenario: Exp test 02:
Given an empty graph
And having executed
"""
CREATE (b{x: '1.0'})),
"""
When executing query:
"""
MATCH (a) RETURN EXP(a.x) AS n
"""
Then an error should be raised
Scenario: Exp test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (c{x: -12}), (d{x: null}), (e{x: -2.3}), (f{x: 1.9})
"""
When executing query:
"""
MATCH (a) RETURN EXP(a.x) AS n
"""
Then the result should be:
| n |
| 2.718281828459045 |
| .00000614421235332821 |
| null |
| 0.10025884372280375 |
| 6.6858944422792685 |
Scenario: Log test 01:
Given an empty graph
And having executed
"""
CREATE (b{x: true}))
"""
When executing query:
"""
MATCH (a) RETURN LOG(a.x) AS n
"""
Then an error should be raised
Scenario: Log test 02:
Given an empty graph
And having executed
"""
CREATE (b{x: '1.0'})),
"""
When executing query:
"""
MATCH (a) RETURN LOG(a.x) AS n
"""
Then an error should be raised
Scenario: Log test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 0.123}), (c{x: -12}), (d{x: null}), (e{x: 27})
"""
When executing query:
"""
MATCH (a) RETURN LOG(a.x) AS n
"""
Then the result should be:
| n |
| -2.0955709236097197 |
| nan |
| null |
| 3.295836866004329 |
Scenario: Log10 test 01:
Given an empty graph
And having executed
"""
CREATE (b{x: true}))
"""
When executing query:
"""
MATCH (a) RETURN LOG10(a.x) AS n
"""
Then an error should be raised
Scenario: Log10 test 02:
Given an empty graph
And having executed
"""
CREATE (b{x: '1.0'})),
"""
When executing query:
"""
MATCH (a) RETURN LOG10(a.x) AS n
"""
Then an error should be raised
Scenario: Log10 test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 0.123}), (c{x: -12}), (d{x: null}), (e{x: 27})
"""
When executing query:
"""
MATCH (a) RETURN LOG10(a.x) AS n
"""
Then the result should be:
| n |
| -0.9100948885606021 |
| nan |
| null |
| 1.4313637641589874 |
Scenario: Sin test 01:
Given an empty graph
And having executed
"""
CREATE (b{x: true}))
"""
When executing query:
"""
MATCH (a) RETURN SIN(a.x) AS n
"""
Then an error should be raised
Scenario: Sin test 02:
Given an empty graph
And having executed
"""
CREATE (b{x: '1.0'})),
"""
When executing query:
"""
MATCH (a) RETURN SIN(a.x) AS n
"""
Then an error should be raised
Scenario: Sin test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 0.123}), (c{x: -12}), (d{x: null}), (e{x: 27})
"""
When executing query:
"""
MATCH (a) RETURN SIN(a.x) AS n
"""
Then the result should be:
| n |
| 0.12269009002431533 |
| 0.5365729180004349 |
| null |
| 0.956375928404503 |
Scenario: Cos test 01:
Given an empty graph
And having executed
"""
CREATE (b{x: true}))
"""
When executing query:
"""
MATCH (a) RETURN COS(a.x) AS n
"""
Then an error should be raised
Scenario: Cos test 02:
Given an empty graph
And having executed
"""
CREATE (b{x: '1.0'})),
"""
When executing query:
"""
MATCH (a) RETURN COS(a.x) AS n
"""
Then an error should be raised
Scenario: Cos test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 0.123}), (c{x: -12}), (d{x: null}), (e{x: 27})
"""
When executing query:
"""
MATCH (a) RETURN COS(a.x) AS n
"""
Then the result should be:
| n |
| 0.9924450321351935 |
| 0.8438539587324921 |
| null |
| -0.2921388087338362 |
Scenario: Sign test:
When executing query:
"""
RETURN SIGN(null) AS n, SIGN(123) AS a, SIGN(0) AS b, SIGN(1.23) AS c,
SIGN(-123) AS d, SIGN(-1.23) AS e, SIGN(-0.00) AS f
"""
Then the result should be:
| n | a | b | c | d | e | f |
| null | 1 | 0 | 1 | -1 | -1 | 0 |
Scenario: Tan test:
When executing query:
"""
RETURN TAN(null) AS n, TAN(123) AS a, TAN(-2.5) AS b, TAN(1.23) AS c
"""
Then the result should be:
| n | a | b | c |
| null | 0.5179274715856552 | 0.7470222972386603 | 2.819815734268152 |
Scenario: Atan test:
When executing query:
"""
RETURN ATAN(null) AS n, ATAN(1.23) AS a, ATAN(123) AS b, ATAN(0) AS c
"""
Then the result should be:
| n | a | b | c |
| null | 0.8881737743776796 | 1.5626664246149526 | 0.0 |
Scenario: Atan2 test:
When executing query:
"""
RETURN ATAN2(1, null) AS n, ATAN2(0, 0) AS a, ATAN2(2, 3) AS b, ATAN2(1.5, 2.5) AS c
"""
Then the result should be:
| n | a | b | c |
| null | 0.0 | 0.5880026035475675 | 0.5404195002705842 |
Scenario: Asin test:
When executing query:
"""
RETURN ASIN(null) AS n, ASIN(1.23) AS a, ASIN(0.48) AS b, ASIN(-0.25) AS c
"""
Then the result should be:
| n | a | b | c |
| null | nan | 0.5006547124045881 | -0.25268025514207865 |
Scenario: Acos test:
When executing query:
"""
RETURN ACOS(null) AS n, ACOS(1.23) AS a, ACOS(0.48) AS b, ACOS(-0.25) AS c
"""
Then the result should be:
| n | a | b | c |
| null | nan | 1.0701416143903084 | 1.8234765819369754 |
Scenario: Round test:
When executing query:
"""
RETURN ROUND(null) AS n, ROUND(1.49999) AS a, ROUND(-1.5) AS b, ROUND(-1.51) AS c,
ROUND(1.5) as d
"""
Then the result should be:
| n | a | b | c | d |
| null | 1.0 | -2.0 | -2.0 | 2.0 |
Scenario: Floor test:
When executing query:
"""
RETURN FLOOR(null) AS n, FLOOR(1.49999) AS a, FLOOR(-1.5) AS b, FLOOR(-1.51) AS c,
FLOOR(1.00) as d
"""
Then the result should be:
| n | a | b | c | d |
| null | 1.0 | -2.0 | -2.0 | 1.0 |
Scenario: Ceil test:
When executing query:
"""
RETURN CEIL(null) AS n, CEIL(1.49999) AS a, CEIL(-1.5) AS b, CEIL(-1.51) AS c,
CEIL(1.00) as d
"""
Then the result should be:
| n | a | b | c | d |
| null | 2.0 | -1.0 | -1.0 | 1.0 |
Scenario: Tail test:
When executing query:
"""
RETURN TAIL(null) AS n, TAIL([[1, 2], 3, 4]) AS a, TAIL([1, [2, 3, 4]]) AS b
"""
Then the result should be:
| n | a | b |
| null | [3, 4] | [[2, 3, 4]] |
Scenario: Range test:
When executing query:
"""
RETURN RANGE(1, 3) AS a, RANGE(1, 5, 2) AS b, RANGE(1, -1) AS c,
RANGE(1, -1, -3) as d
"""
Then the result should be:
| a | b | c | d |
| [1, 2, 3] | [1, 3, 5] | [] | [1] |
Scenario: Size test:
When executing query:
"""
RETURN SIZE(null) AS n, SIZE([[1, 2], 3, 4]) AS a, SIZE([1, [2, 3, 4]]) AS b
"""
Then the result should be:
| n | a | b |
| null | 3 | 2 |
Scenario: Last test:
When executing query:
"""
RETURN LAST(null) AS n, LAST([[1, 2], 3, 4]) AS a, LAST([1, [2, 3, 4]]) AS b
"""
Then the result should be:
| n | a | b |
| null | 4 | [2, 3, 4] |
Scenario: Head test:
When executing query:
"""
RETURN HEAD(null) AS n, HEAD([[1, 2], 3, 4]) AS a, HEAD([1, [2, 3, 4]]) AS b
"""
Then the result should be:
| n | a | b |
| null | [1, 2] | 1 |
Scenario: Labels test:
Given an empty graph
And having executed:
"""
CREATE(:x:y:z), (), (:a:b)
"""
When executing query:
"""
MATCH(n) RETURN LABELS(n) AS l
"""
Then the result should be:
| l |
| [] |
| ['x', 'y', 'z'] |
| ['a', 'b'] |
Scenario: Type test:
Given an empty graph
And having executed:
"""
CREATE(a), (b), (c), (a)-[:A]->(b), (a)-[:B]->(c), (b)-[:C]->(c)
"""
When executing query:
"""
MATCH ()-[r]->() RETURN TYPE(r) AS t
"""
Then the result should be:
| t |
| 'A' |
| 'B' |
| 'C' |
Scenario: Properties test1:
Given an empty graph
And having executed:
"""
CREATE(a), (b), (c), (a)-[:A{a: null}]->(b), (a)-[:B{b: true}]->(c),
(b)-[:C{c: 123}]->(c)
"""
When executing query:
"""
MATCH ()-[r]->() RETURN PROPERTIES(r) AS p
"""
Then the result should be:
| p |
| {b: true} |
| {} |
| {c: 123} |
Scenario: Properties test2:
Given an empty graph
And having executed:
"""
CREATE({a: 'x'}), ({n: 1.1}), ()
"""
When executing query:
"""
MATCH(n) RETURN PROPERTIES(n) AS p
"""
Then the result should be:
| p |
| {} |
| {a: 'x'} |
| {n: 1.1} |
Scenario: Coalesce test:
When executing query:
"""
RETURN COALESCE(null) AS n, COALESCE([null, null]) AS a,
COALESCE(null, null, 1, 2, 3) AS b
"""
Then the result should be:
| n | a | b |
| null | [null, null] | 1 |
Scenario: Endnode test:
Given an empty graph
And having executed:
"""
CREATE(a:x), (b:y), (c:z), (a)-[:A]->(b), (b)-[:B]->(c), (c)-[:C]->(b)
"""
When executing query:
"""
MATCH ()-[r]->() RETURN ENDNODE(r) AS n, ENDNODE(r):z AS a, ENDNODE(r):y AS b
"""
Then the result should be:
| n | a | b |
| (:z) | true | false |
| (:y) | false | true |
| (:y) | false | true |
Scenario: Keys test:
Given an empty graph
When executing query:
"""
CREATE (n{true: 123, a: null, b: 'x', null: 1}) RETURN KEYS(n) AS a
"""
Then the result should be (ignoring element order for lists)
| a |
| ['true', 'null', 'b'] |
Scenario: Pi test:
When executing query:
"""
RETURN PI() as n
"""
Then the result should be:
| n |
| 3.141592653589793 |

View File

@ -0,0 +1,225 @@
Feature: List operators
Scenario: In test1
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN 3 IN l as in
"""
Then the result should be:
| in |
| true |
Scenario: In test2
When executing query:
"""
WITH [1, '2', 3, 4] AS l
RETURN 2 IN l as in
"""
Then the result should be:
| in |
| false |
Scenario: In test4
When executing query:
"""
WITH [1, [2, 3], 4] AS l
RETURN [3, 2] IN l as in
"""
Then the result should be:
| in |
| false |
Scenario: In test5
When executing query:
"""
WITH [[1, 2], 3, 4] AS l
RETURN 1 IN l as in
"""
Then the result should be:
| in |
| false |
Scenario: In test6
When executing query:
"""
WITH [1, [[2, 3], 4]] AS l
RETURN [[2, 3], 4] IN l as in
"""
Then the result should be:
| in |
| true |
Scenario: In test7
When executing query:
"""
WITH [1, [[2, 3], 4]] AS l
RETURN [1, [[2, 3], 4]] IN l as in
"""
Then the result should be:
| in |
| false |
Scenario: Index test1
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN l[2] as in
"""
Then the result should be:
| in |
| 3 |
Scenario: Index test2
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN l[-2] as in
"""
Then the result should be:
| in |
| 3 |
Scenario: Index test3
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN l[2][0] as in
"""
Then an error should be raised
Scenario: Index test4
When executing query:
"""
WITH [1, 2, [3], 4] AS l
RETURN l[2][0] as in
"""
Then the result should be:
| in |
| 3 |
Scenario: Index test5
When executing query:
"""
WITH [[1, [2, [3]]], 4] AS l
RETURN l[0][1][1][0] as in
"""
Then the result should be:
| in |
| 3 |
Scenario: Slice test1
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN l[0..2] as in
"""
Then the result should be, in order:
| in |
| [1, 2] |
Scenario: Slice test2
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN l[-2..5] as in
"""
Then the result should be, in order:
| in |
| [3, 4] |
Scenario: Slice test3
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN l[-2..4] as in
"""
Then the result should be, in order:
| in |
| [3, 4] |
Scenario: Slice test4
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN l[-1..4] as in
"""
Then the result should be, in order:
| in |
| [4] |
Scenario: Slice test5
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN l[-2..-2] as in
"""
Then the result should be, in order:
| in |
| [] |
Scenario: Slice test6
When executing query:
"""
WITH [1, 2, 3, 4] AS l
RETURN l[4..-2] as in
"""
Then the result should be, in order:
| in |
| [] |
Scenario: Concatenate test1
When executing query:
"""
WITH [1, 2, 3, 4] AS l1, [5, 6, 7] AS l2
RETURN l1+l2 as in
"""
Then the result should be, in order:
| in |
| [1, 2, 3, 4, 5, 6, 7] |
Scenario: Concatenate test2
When executing query:
"""
WITH [[1, [2]]] AS l1, [[[3], 4]] AS l2
RETURN l1+l2 as in
"""
Then the result should be, in order:
| in |
| [[1, [2]], [[3], 4]] |
Scenario: Concatenate test3
When executing query:
"""
WITH [1, 2, 3, 4] AS l1, NULL AS l2
RETURN l1+l2 as in
"""
Then the result should be, in order:
| in |
| null |
Scenario: Concatenate test4
When executing query:
"""
WITH [] AS l1, [] AS l2
RETURN l1+l2 as in
"""
Then the result should be, in order:
| in |
| [] |
Scenario: Unwind test
When executing query:
"""
UNWIND [ [[1], 2], [3], 4] as l
RETURN l
"""
Then the result should be:
| l |
| [[1], 2] |
| [3] |
| 4 |

View File

@ -0,0 +1,408 @@
Feature: Match
Scenario: Create node and self relationships and match
Given an empty graph
And having executed:
"""
CREATE (n)-[:X]->(n)<-[:Y]-(n)
"""
When executing query:
"""
MATCH ()-[a]-()-[b]-() RETURN a, b
"""
Then the result should be:
| a | b |
| [:X] | [:Y] |
| [:Y] | [:X] |
Scenario: Create node and self relationship and match
Given an empty graph
And having executed:
"""
CREATE (n)-[:X]->(n)
"""
When executing query:
"""
MATCH ()-[a]-() RETURN a
"""
Then the result should be:
| a |
| [:X] |
Scenario: Create node and self relationships and match
Given an empty graph
And having executed:
"""
CREATE (n)-[:X]->(n)<-[:Y]-(n)
"""
When executing query:
"""
MATCH ()<-[a]-()-[b]->() RETURN a, b
"""
Then the result should be:
| a | b |
| [:X] | [:Y] |
| [:Y] | [:X] |
Scenario: Create multiple nodes and relationships and match
Given an empty graph
And having executed:
"""
CREATE ()-[:X]->()<-[:Y]-()
"""
When executing query:
"""
MATCH ()-[a]-()-[b]-() RETURN a, b
"""
Then the result should be:
| a | b |
| [:X] | [:Y] |
| [:Y] | [:X] |
Scenario: Create multiple nodes and relationships and match
Given an empty graph
And having executed:
"""
CREATE ()-[:X]->()<-[:Y]-()
"""
When executing query:
"""
MATCH ()<-[a]-()-[b]-() RETURN a, b
"""
Then the result should be empty
Scenario: Create cycle and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X]->()-[:Y]->()-[:Z]->(a)
"""
When executing query:
"""
MATCH ()-[a]->()-[b]->() RETURN a, b
"""
Then the result should be:
| a | b |
| [:X] | [:Y] |
| [:Y] | [:Z] |
| [:Z] | [:X] |
Scenario: Create cycle and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X]->()-[:Y]->()-[:Z]->(a)
"""
When executing query:
"""
MATCH ()-[a]-()-[b]-() RETURN a, b
"""
Then the result should be:
| a | b |
| [:X] | [:Y] |
| [:Y] | [:Z] |
| [:Z] | [:X] |
| [:X] | [:Z] |
| [:Y] | [:X] |
| [:Z] | [:Y] |
Scenario: Create cycle and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X]->()-[:Y]->()-[:Z]->(a)
"""
When executing query:
"""
MATCH ()<-[a]-()-[b]->() RETURN a, b
"""
Then the result should be empty
Scenario: Create cycle and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X]->()-[:Y]->()-[:Z]->(a)
"""
When executing query:
"""
MATCH ()-[a]->()-[]->()-[]->()-[]->() RETURN a
"""
Then the result should be empty
Scenario: Create two nodes with three relationships and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X]->(b)-[:Y]->(a)-[:Z]->(b)
"""
When executing query:
"""
MATCH ()-[a]->()-[b]->()-[c]->() RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| [:X] | [:Y] | [:Z] |
| [:Z] | [:Y] | [:X] |
Scenario: Create two nodes with three relationships and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X]->(b)-[:Y]->(a)-[:Z]->(b)
"""
When executing query:
"""
MATCH ()-[a]-()-[b]-()-[c]-() RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| [:X] | [:Y] | [:Z] |
| [:X] | [:Z] | [:Y] |
| [:Y] | [:X] | [:Z] |
| [:Y] | [:Z] | [:X] |
| [:Z] | [:Y] | [:X] |
| [:Z] | [:X] | [:Y] |
| [:X] | [:Y] | [:Z] |
| [:X] | [:Z] | [:Y] |
| [:Y] | [:X] | [:Z] |
| [:Y] | [:Z] | [:X] |
| [:Z] | [:Y] | [:X] |
| [:Z] | [:X] | [:Y] |
Scenario: Create two nodes with three relationships and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X{a: 1.0}]->(b)-[:Y]->(a)-[:Z]->(b)
"""
When executing query:
"""
MATCH ()-[a{a: 1.0}]-()-[b]-()-[c]-() RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| [:X{a: 1.0}] | [:Y] | [:Z] |
| [:X{a: 1.0}] | [:Z] | [:Y] |
| [:X{a: 1.0}] | [:Y] | [:Z] |
| [:X{a: 1.0}] | [:Z] | [:Y] |
Scenario: Create two nodes with three relationships and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X{a: 1.0}]->(b)-[:Y]->(a)-[:Z]->(b)
"""
When executing query:
"""
MATCH ()-[a{a: 1.0}]-()-[b]-()-[c:Y]-() RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| [:X{a: 1.0}] | [:Z] | [:Y] |
| [:X{a: 1.0}] | [:Z] | [:Y] |
Scenario: Create two nodes with three relationships and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X{a: 1.0}]->(b)-[:Y{a: 1.0}]->(a)-[:Z]->(b)
"""
When executing query:
"""
MATCH ()-[a{a: 1.0}]-()-[b]-()-[c:Y]-() RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| [:X{a: 1.0}] | [:Z] | [:Y{a: 1.0}] |
| [:X{a: 1.0}] | [:Z] | [:Y{a: 1.0}] |
Scenario: Create two nodes with three relationships and match
Given an empty graph
And having executed:
"""
CREATE (a)-[:X{a: 1.0}]->(b)-[:Y{a: 1.0}]->(a)-[:Z]->(b)
"""
When executing query:
"""
MATCH ()-[a{a: 1.0}]-()-[b]-()-[c{a: 1.0}]-() RETURN a, b, c, c.a as t
"""
Then the result should be:
| a | b | c | t |
| [:X{a: 1.0}] | [:Z] | [:Y{a: 1.0}] | 1.0 |
| [:X{a: 1.0}] | [:Z] | [:Y{a: 1.0}] | 1.0 |
| [:Y{a: 1.0}] | [:Z] | [:X{a: 1.0}] | 1.0 |
| [:Y{a: 1.0}] | [:Z] | [:X{a: 1.0}] | 1.0 |
Scenario: Create two nodes with three relationships and match
Given an empty graph
And having executed:
"""
CREATE (a:T{c: True})-[:X{x: 2.5}]->(:A:B)-[:Y]->()-[:Z{r: 1}]->(a)
"""
When executing query:
"""
MATCH (:T{c: True})-[a:X{x: 2.5}]->(node:A:B)-[:Y]->()-[:Z{r: 1}]->() RETURN a AS node, node AS a
"""
Then the result should be:
| node | a |
| [:X{x: 2.5}] | (:A:B) |
Scenario: Create and match with label
Given graph "graph_01"
When executing query:
"""
MATCH (n:Person) RETURN n
"""
Then the result should be:
| n |
| (:Person {age: 20}) |
| (:Person :Student {age: 20}) |
| (:Person {age: 21}) |
Scenario: Create and match with label
Given graph "graph_01"
When executing query:
"""
MATCH (n:Student) RETURN n
"""
Then the result should be:
| n |
| (:Person :Student {age: 20}) |
| (:Student {age: 21}) |
Scenario: Create, match with label and property
Given graph "graph_01"
When executing query:
"""
MATCH (n:Person {age: 20}) RETURN n AS x
"""
Then the result should be:
| x |
| (:Person {age: 20}) |
| (:Person :Student {age: 20}) |
Scenario: Create, match with label and filter property using WHERE
Given graph "graph_01"
When executing query:
"""
MATCH (n:Person) WHERE n.age = 20 RETURN n
"""
Then the result should be:
| n |
| (:Person {age: 20}) |
| (:Person :Student {age: 20}) |
Scenario: Create and match with property
Given graph "graph_01"
When executing query:
"""
MATCH (n {age: 20}) RETURN n
"""
Then the result should be:
| n |
| (:Person {age: 20}) |
| (:Person :Student {age: 20}) |
Scenario: Test match with order by
Given an empty graph
And having executed:
"""
CREATE({a: 1}), ({a: 2}), ({a: 3}), ({a: 4}), ({a: 5})
"""
When executing query:
"""
MATCH (n) RETURN n.a ORDER BY n.a
"""
Then the result should be, in order:
| n.a |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
Scenario: Test match with order by and skip
Given an empty graph
And having executed:
"""
CREATE({a: 1}), ({a: 2}), ({a: 3}), ({a: 4}), ({a: 5})
"""
When executing query:
"""
MATCH (n) RETURN n.a ORDER BY n.a SKIP 3
"""
Then the result should be, in order:
| n.a |
| 4 |
| 5 |
Scenario: Test match with order by and limit
Given an empty graph
And having executed:
"""
CREATE({a: 1}), ({a: 2}), ({a: 3}), ({a: 4}), ({a: 5})
"""
When executing query:
"""
MATCH (n) RETURN n.a ORDER BY n.a LIMIT 2
"""
Then the result should be, in order:
| n.a |
| 1 |
| 2 |
Scenario: Test match with order by, skip and limit
Given an empty graph
And having executed:
"""
CREATE({a: 1}), ({a: 2}), ({a: 3}), ({a: 4}), ({a: 5})
"""
When executing query:
"""
MATCH (n) RETURN n.a ORDER BY n.a SKIP 2 LIMIT 2
"""
Then the result should be, in order:
| n.a |
| 3 |
| 4 |
Scenario: Test match with order by and skip
Given an empty graph
And having executed:
"""
CREATE({a: 1}), ({a: 2}), ({a: 3}), ({a: 4}), ({a: 5})
"""
When executing query:
"""
MATCH (n) RETURN n.a ORDER BY n.a SKIP 6
"""
Then the result should be empty
Scenario: Test match with order by and limit
Given an empty graph
And having executed:
"""
CREATE({a: 1}), ({a: 2}), ({a: 3}), ({a: 4}), ({a: 5})
"""
When executing query:
"""
MATCH (n) RETURN n.a ORDER BY n.a LIMIT 0
"""
Then the result should be empty
Scenario: Test distinct
Given an empty graph
And having executed:
"""
CREATE({a: 1}), ({a: 4}), ({a: 3}), ({a: 1}), ({a: 4})
"""
When executing query:
"""
MATCH (n) RETURN DISTINCT n.a
"""
Then the result should be:
| n.a |
| 1 |
| 3 |
| 4 |

View File

@ -0,0 +1,64 @@
Feature: Memgraph only tests (queries in which we choose to be incompatible with neo4j)
Scenario: Multiple sets (undefined behaviour)
Given an empty graph
And having executed
"""
CREATE (n{x: 3})-[:X]->(m{x: 5})
"""
When executing query:
"""
MATCH (n)--(m) SET n.x = n.x + 1 SET m.x = m.x + 2 SET m.x = n.x RETURN n.x
"""
# TODO: Figure out if we can define a test with multiple possible outputs in cucumber,
# until then this test just documents behaviour instead of testing it.
# Then the result should be:
# | n.x | | n.x |
# | 5 | or | 7 |
# | 5 | | 7 |
Scenario: Multiple comparisons
Given an empty graph
When executing query:
"""
RETURN 1 < 10 > 5 < 7 > 6 < 8 AS x
"""
Then the result should be:
| x |
| true |
Scenario: Use deleted node
Given an empty graph
When executing query:
"""
CREATE(a:A), (b:B), (c:C), (a)-[:T]->(b) WITH a DETACH DELETE a WITH a MATCH()-[r:T]->() RETURN r
"""
Then an error should be raised
Scenario: In test3
When executing query:
"""
WITH [[1], 2, 3, 4] AS l
RETURN 1 IN l as in
"""
Then the result should be:
| in |
| false |
Scenario: In test8
When executing query:
"""
WITH [[[[1]]], 2, 3, 4] AS l
RETURN 1 IN l as in
"""
Then the result should be:
| in |
| false |
Scenario: Keyword as symbolic name
Given an empty graph
When executing query:
"""
CREATE(a:DELete)
"""
Then an error should be raised

View File

@ -0,0 +1,420 @@
Feature: Merge feature
Scenario: Merge node test01
Given an empty graph
And having executed:
"""
CREATE (:X{a: 1})
"""
And having executed:
"""
MERGE(n:X)
"""
When executing query:
"""
MATCH(n) RETURN n
"""
Then the result should be:
| n |
| (:X{a: 1}) |
Scenario: Merge node test02
Given an empty graph
And having executed:
"""
CREATE (:X)
"""
And having executed:
"""
MERGE(n:X:Y)
"""
When executing query:
"""
MATCH(n) RETURN n
"""
Then the result should be:
| n |
| (:X) |
| (:X:Y) |
Scenario: Merge node test03
Given an empty graph
And having executed:
"""
CREATE (:Y{a: 1})
"""
And having executed:
"""
MERGE(n:X)
"""
When executing query:
"""
MATCH(n) RETURN n
"""
Then the result should be:
| n |
| (:X) |
| (:Y{a: 1}) |
Scenario: Merge node test04
Given an empty graph
And having executed:
"""
CREATE ({a: 1, b: 2})
"""
And having executed:
"""
MERGE(n{a: 1, b: 2})
"""
When executing query:
"""
MATCH(n) RETURN n
"""
Then the result should be:
| n |
| ({a: 1, b: 2}) |
Scenario: Merge node test05
Given an empty graph
And having executed:
"""
CREATE ({a: 2})
"""
And having executed:
"""
MERGE(n{a: 1, b:2})
"""
When executing query:
"""
MATCH(n) RETURN n
"""
Then the result should be:
| n |
| ({a: 2}) |
| ({a: 1, b: 2}) |
Scenario: Merge node test06
Given an empty graph
And having executed:
"""
CREATE ({a: 2})
"""
And having executed:
"""
MERGE(n{a: 2, b: 2})
"""
When executing query:
"""
MATCH(n) RETURN n
"""
Then the result should be:
| n |
| ({a: 2}) |
| ({a: 2, b: 2}) |
Scenario: Merge node test07
Given an empty graph
And having executed:
"""
CREATE ({a: 2})
"""
And having executed:
"""
MERGE(n:A{a: 2})
"""
When executing query:
"""
MATCH(n) RETURN n
"""
Then the result should be:
| n |
| (:A{a: 2}) |
| ({a: 2}) |
Scenario: Merge node test08
Given an empty graph
And having executed:
"""
CREATE (:A:B{a: 2, b: 1})
"""
And having executed:
"""
MERGE(n:A{a: 2})
"""
When executing query:
"""
MATCH(n) RETURN n
"""
Then the result should be:
| n |
| (:A:B{a: 2, b: 1}) |
Scenario: Merge node test09
Given an empty graph
And having executed:
"""
CREATE (:A{a: 2}), (:A{a: 2}), (:A{a: 1})
"""
And having executed:
"""
MATCH(n:A)
MERGE(m:B{x: n.a})
"""
When executing query:
"""
MATCH(n:B) RETURN *
"""
Then the result should be:
| n |
| (:B{x: 1}) |
| (:B{x: 2}) |
Scenario: Merge relationship test01
Given an empty graph
And having executed:
"""
CREATE (a), (b), (a)-[:X]->(b)
"""
And having executed:
"""
MATCH (a)--(b)
MERGE ((a)-[r:X]-(b))
"""
When executing query:
"""
MATCH ()-[r]->() RETURN *
"""
Then the result should be:
| r |
| [:X] |
Scenario: Merge relationship test02
Given an empty graph
And having executed:
"""
CREATE (a), (b), (a)-[:X]->(b)
"""
And having executed:
"""
MATCH (a)--(b)
MERGE ((a)-[r:Y]-(b))
"""
When executing query:
"""
MATCH ()-[r]->() RETURN *
"""
Then the result should be:
| r |
| [:X] |
| [:Y] |
Scenario: Merge relationship test03
Given an empty graph
And having executed:
"""
CREATE (a), (b), (a)-[:X{a: 1}]->(b)
"""
And having executed:
"""
MATCH (a)--(b)
MERGE ((a)-[r:X{a: 1}]-(b))
"""
When executing query:
"""
MATCH ()-[r]->() RETURN *
"""
Then the result should be:
| r |
| [:X{a: 1}] |
Scenario: Merge relationship test04
Given an empty graph
And having executed:
"""
CREATE (a), (b), (a)-[:X{a: 1}]->(b)
"""
And having executed:
"""
MATCH (a)--(b)
MERGE ((a)-[r:X{a: 2}]-(b))
"""
When executing query:
"""
MATCH ()-[r]->() RETURN *
"""
Then the result should be:
| r |
| [:X{a: 1}] |
| [:X{a: 2}] |
Scenario: Merge relationship test05
Given an empty graph
And having executed:
"""
CREATE (a), (b), (a)-[:X{a: 1}]->(b)
"""
And having executed:
"""
MATCH (a)--(b)
MERGE ((a)-[r:Y{a: 1}]-(b))
"""
When executing query:
"""
MATCH ()-[r]->() RETURN *
"""
Then the result should be:
| r |
| [:X{a: 1}] |
| [:Y{a: 1}] |
Scenario: Merge relationship test06
Given an empty graph
And having executed:
"""
CREATE (a:A{a: 1}), (b:B{b: 1}), (c:C), (a)-[:X]->(c), (c)<-[:Y]-(b)
"""
And having executed:
"""
MERGE (:A)-[:X]->(:C)<-[:Y]-(:B)
"""
When executing query:
"""
MATCH (a)-[r]->(b) RETURN *
"""
Then the result should be:
| a | r | b |
| (:A{a: 1}) | [:X] | (:C) |
| (:B{b: 1}) | [:Y] | (:C) |
Scenario: Merge relationship test07
Given an empty graph
And having executed:
"""
CREATE (a:A{a: 1}), (b:B{b: 1}), (c:C), (a)-[:X{x: 1}]->(c), (c)<-[:Y{y: 1}]-(b)
"""
And having executed:
"""
MERGE (:A)-[:X]->(:D)<-[:Y]-(:B)
"""
When executing query:
"""
MATCH (a)-[r]->(b) RETURN *
"""
Then the result should be:
| a | r | b |
| (:A) | [:X] | (:D) |
| (:B) | [:Y] | (:D) |
| (:A{a: 1}) | [:X{x: 1}] | (:C) |
| (:B{b: 1}) | [:Y{y: 1}] | (:C) |
Scenario: Merge relationship test08
Given an empty graph
And having executed:
"""
CREATE (a:A:X), (b:B:Y)
"""
And having executed:
"""
MATCH (a:A), (b:B)
MERGE (a)-[:R]-(b)
"""
When executing query:
"""
MATCH (a)-[r]-(b) RETURN *
"""
Then the result should be:
| a | r | b |
| (:A:X) | [:R] | (:B:Y) |
| (:B:Y) | [:R] | (:A:X) |
Scenario: Merge relationship test09
Given an empty graph
And having executed:
"""
CREATE (a:A{a: 1}), (b:B{a: 2}), (c:C{a: 2})
"""
When executing query:
"""
MATCH (n)
MERGE (m:X{a: n.a})
MERGE (n)-[r:R]->(m)
RETURN *
"""
Then the result should be:
| n | r | m |
| (:A{a: 1}) | [:R] | (:X{a: 1}) |
| (:B{a: 2}) | [:R] | (:X{a: 2}) |
| (:C{a: 2}) | [:R] | (:X{a: 2}) |
Scenario: Merge relationship test10
Given an empty graph
And having executed:
"""
CREATE (a:A{a: 1}), (b:B{a: 2}), (c:C{a: 2})
"""
When executing query:
"""
MATCH (n)
MERGE (n)-[r:R]->(m:X{a: n.a})
RETURN *
"""
Then the result should be:
| n | r | m |
| (:A{a: 1}) | [:R] | (:X{a: 1}) |
| (:B{a: 2}) | [:R] | (:X{a: 2}) |
| (:C{a: 2}) | [:R] | (:X{a: 2}) |
Scenario: Merge OnCreate test01
Given an empty graph
When executing query:
"""
MERGE (a:X)
ON CREATE SET a.a = 1
RETURN *
"""
Then the result should be:
| a |
| (:X{a: 1}) |
Scenario: Merge OnMatch test01
Given an empty graph
And having executed:
"""
CREATE(:A), (:A), (:B)
"""
And having executed:
"""
MERGE (a:A)
ON MATCH SET a.a = 1
"""
When executing query:
"""
MATCH (n) RETURN *
"""
Then the result should be:
| n |
| (:A{a: 1}) |
| (:A{a: 1}) |
| (:B) |
Scenario: Merge with Unwind test01
Given an empty graph
And having executed:
"""
CREATE ({a: 1})
"""
And having executed:
"""
UNWIND [1, 2, 3] AS a
MERGE({a: a})
"""
When executing query:
"""
MATCH (n) RETURN *
"""
Then the result should be:
| n |
| ({a: 1}) |
| ({a: 2}) |
| ({a: 3)) |

View File

@ -0,0 +1,186 @@
Feature: String operators
Scenario: StartsWith test1
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
MATCH (n)
WHERE n.name STARTS WITH 'aim'
return n.name
"""
Then the result should be:
| n.name |
| 'aime' |
Scenario: StartsWith test2
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
MATCH (n)
WHERE n.name STARTS WITH "ai'M"
return n.name
"""
Then the result should be:
| n.name |
| 'ai'M'e' |
Scenario: StartsWith test3
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
MATCH (n)
WHERE n.name STARTS WITH 1
return n.name
"""
Then an error should be raised
Scenario: StartsWith test5
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
MATCH (n)
WHERE n.name STARTS WITH true
return n.name
"""
Then an error should be raised
Scenario: EndsWith test1
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'E"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
MATCH (n)
WHERE n.name ENDS WITH 'e'
return n.name
"""
Then the result should be:
| n.name |
| 'AiMe' |
| 'aime' |
Scenario: EndsWith test2
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
MATCH (n)
WHERE n.name ENDS WITH "M'e"
return n.name
"""
Then the result should be:
| n.name |
| 'ai'M'e' |
Scenario: EndsWith test3
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
MATCH (n)
WHERE n.name ENDS WITH 1
return n.name
"""
Then an error should be raised
Scenario: EndsWith test5
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
MATCH (n)
WHERE n.name ENDS WITH true
return n.name
"""
Then an error should be raised
Scenario: Contains test1
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
MATCH (n)
WHERE n.name CONTAINS 'iM'
return n.name
"""
Then the result should be:
| n.name |
| 'AiMe' |
Scenario: Contains test2
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
MATCH (n)
WHERE n.name CONTAINS "i'M"
return n.name
"""
Then the result should be:
| n.name |
| 'ai'M'e' |
Scenario: Contains test3
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
MATCH (n)
WHERE n.name CONTAINS 1
return n.name
"""
Then an error should be raised
Scenario: Contains test5
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
MATCH (n)
WHERE n.name CONTAINS true
return n.name
"""
Then an error should be raised

View File

@ -0,0 +1,108 @@
Feature: Unstable
Scenario: With test 01:
Given an empty graph
And having executed:
"""
CREATE (a:A), (b:B), (c:C), (d:D), (e:E), (a)-[:R]->(b), (b)-[:R]->(c), (b)-[:R]->(d), (c)-[:R]->(a), (c)-[:R]->(e), (d)-[:R]->(e)
"""
When executing query:
"""
MATCH (:A)--(a)-->() WITH a, COUNT(*) AS n WHERE n > 1 RETURN a
"""
Then the result should be:
| a |
| (:B) |
Scenario: Count test 06:
Given an empty graph
And having executed
"""
CREATE (), (), (), (), ()
"""
When executing query:
"""
MATCH (n) RETURN COUNT(*) AS n
"""
Then the result should be:
| n |
| 5 |
Scenario: Test exponential operator
When executing query:
"""
RETURN 3^2=81^0.5 as n
"""
Then the result should be:
| n |
| true |
Scenario: Test one big mathematical equation
When executing query:
"""
RETURN (3+2*4-3/2%2*10)/5.0^2.0=0.04 as n
"""
Then the result should be:
| n |
| true |
Scenario: Keys test:
When executing query:
"""
RETURN KEYS( {true: 123, a: null, b: 'x', null: null} ) AS a
"""
Then the result should be:
| a |
| ['true', 'a', 'b', 'null'] |
Scenario: StartsWith test4
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
MATCH (n)
WHERE n.name STARTS WITH null
return n.name
"""
Then the result should be empty
Scenario: EndsWith test4
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
MATCH (n)
WHERE n.name ENDS WITH null
return n.name
"""
Then the result should be empty
Scenario: Contains test4
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
MATCH (n)
WHERE n.name CONTAINS null
return n.name
"""
Then the result should be empty
Scenario: E test:
When executing query:
"""
RETURN E() as n
"""
Then the result should be:
| n |
| 2.718281828459045 |

View File

@ -0,0 +1,276 @@
Feature: Update clauses
Scenario: Match create return test
Given an empty graph
And having executed
"""
CREATE (:x_1), (:z2_), (:qw34)
"""
When executing query:
"""
MATCH (a:x_1), (b:z2_), (c:qw34)
CREATE (a)-[x:X]->(b) CREATE (b)<-[y:Y]-(c)
RETURN x, y
"""
Then the result should be:
| x | y |
| [:X] | [:Y] |
Scenario: Multiple matches in one query
Given an empty graph
And having executed
"""
CREATE (:x{age: 5}), (:y{age: 4}), (:z), (:x), (:y)
"""
When executing query:
"""
MATCH (a:x), (b:y), (c:z)
WHERE a.age=5
MATCH (b{age: 4})
RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| (:x{age: 5}) | (:y{age: 4}) | (:z) |
Scenario: Match set one property return test
Given an empty graph
And having executed
"""
CREATE (:q)-[:X]->()
"""
When executing query:
"""
MATCH (a:q)-[b]-(c)
SET a.name='Sinisa'
RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| (:q{name: 'Sinisa'}) | [:X] | () |
Scenario: Match set properties from node to node return test
Given an empty graph
And having executed
"""
CREATE (:q{name: 'Sinisa', x: 'y'})-[:X]->({name: 'V', desc: 'Traktor'})
"""
When executing query:
"""
MATCH (a:q)-[b]-(c)
SET c=a
RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| (:q{name: 'Sinisa', x: 'y'}) | [:X] | ({name: 'Sinisa', x: 'y'}) |
Scenario: Match set properties from node to relationship return test
Given an empty graph
And having executed
"""
CREATE (:q{x: 'y'})-[:X]->({y: 't'})
"""
When executing query:
"""
MATCH (a:q)-[b]-(c)
SET b=a
RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| (:q{x: 'y'}) | [:X{x: 'y'}] | ({y: 't'}) |
Scenario: Match set properties from relationship to node return test
Given an empty graph
And having executed
"""
CREATE (:q)-[:X{x: 'y'}]->({y: 't'})
"""
When executing query:
"""
MATCH (a:q)-[b]-(c)
SET a=b
RETURN a, b, c
"""
Then the result should be:
| a | b | c |
| (:q{x: 'y'}) | [:X{x: 'y'}] | ({y: 't'}) |
Scenario: Match, set properties from relationship to relationship, return test
Given an empty graph
When executing query:
"""
CREATE ()-[b:X{x: 'y'}]->()-[a:Y]->()
SET a=b
RETURN a, b
"""
Then the result should be:
| a | b |
| [:Y{x: 'y'}] | [:X{x: 'y'}] |
Scenario: Create, set adding properties, return test
Given an empty graph
When executing query:
"""
CREATE ()-[b:X{x: 'y', y: 'z'}]->()-[a:Y{x: 'z'}]->()
SET a += b
RETURN a, b
"""
Then the result should be:
| a | b |
| [:Y{x: 'y', y: 'z'}] | [:X{x: 'y', y: 'z'}] |
Scenario: Create node and add labels using set, return test
Given an empty graph
When executing query:
"""
CREATE (a)
SET a :sinisa:vu
RETURN a
"""
Then the result should be:
| a |
| (:sinisa:vu) |
Scenario: Create node and delete it
Given an empty graph
And having executed:
"""
CREATE (n)
DELETE (n)
"""
When executing query:
"""
MATCH (n)
RETURN n
"""
Then the result should be empty
Scenario: Create node with relationships and delete it, check for relationships
Given an empty graph
And having executed:
"""
CREATE (n)-[:X]->()
CREATE (n)-[:Y]->()
DETACH DELETE (n)
"""
When executing query:
"""
MATCH ()-[n]->()
RETURN n
"""
Then the result should be empty
Scenario: Create node with relationships and delete it, check for nodes
Given an empty graph
And having executed:
"""
CREATE (n:l{a: 1})-[:X]->()
CREATE (n)-[:Y]->()
DETACH DELETE (n)
"""
When executing query:
"""
MATCH (n)
RETURN n
"""
Then the result should be:
| n |
|( )|
|( )|
Scenario: Create node with relationships and delete it (without parentheses), check for nodes
Given an empty graph
And having executed:
"""
CREATE (n:l{a: 1})-[:X]->()
CREATE (n)-[:Y]->()
DETACH DELETE n
"""
When executing query:
"""
MATCH (n)
RETURN n
"""
Then the result should be:
| n |
|( )|
|( )|
Scenario: Set test:
Given an empty graph
And having executed:
"""
CREATE (a:A{x: 1}), (b:B{x: 2}), (c:C{x: 3}), (a)-[:T]->(b), (b)-[:T]->(c), (c)-[:T]->(a)
"""
And having executed:
"""
MATCH (d)--(e) WHERE abs(d.x - e.x)<=1 SET d.x=d.x+2, e.x=e.x+2
"""
When executing query:
"""
MATCH(x) RETURN x
"""
Then the result should be:
| x |
| (:A{x: 5}) |
| (:B{x: 10}) |
| (:C{x: 7}) |
Scenario: Remove 01
Given an empty graph
And having executed
"""
CREATE (a:A:B:C)
"""
When executing query:
"""
MATCH (n) REMOVE n:A:B:C RETURN n
"""
Then the result should be:
| n |
| () |
Scenario: Remove 02
Given an empty graph
And having executed
"""
CREATE (a:A:B:C)
"""
When executing query:
"""
MATCH (n) REMOVE n:B:C RETURN n
"""
Then the result should be:
| n |
| (:A) |
Scenario: Remove 03
Given an empty graph
And having executed
"""
CREATE (a{a: 1, b: 1.0, c: 's', d: false})
"""
When executing query:
"""
MATCH (n) REMOVE n:A:B, n.a REMOVE n.b, n.c, n.d RETURN n
"""
Then the result should be:
| n |
| () |
Scenario: Remove 04
Given an empty graph
And having executed
"""
CREATE (a:A:B{a: 1, b: 's', c: 1.0, d: true})
"""
When executing query:
"""
MATCH (n) REMOVE n:B, n.a, n.d RETURN n
"""
Then the result should be:
| n |
| (:A{b: 's', c: 1.0}) |

View File

@ -0,0 +1,223 @@
Feature: With
Scenario: With test 02:
Given an empty graph
And having executed
"""
CREATE (a:A{x: 1}), (b:B{x: 2}), (c:C{x: 3}), (d:D{x: 4}), (a)-[:R]->(b), (b)-[:R]->(c), (c)-[:R]->(d), (d)-[:R]->(a)
"""
When executing query:
"""
MATCH (a)--(b)
WITH a, MAX(b.x) AS s
RETURN a, s
"""
Then the result should be:
| a | s |
| (:A{x: 1}) | 4 |
| (:B{x: 2}) | 3 |
| (:C{x: 3}) | 4 |
| (:D{x: 4}) | 3 |
Scenario: With test 03:
Given an empty graph
And having executed
"""
CREATE (a:A{x: 1}), (b:B{x: 2}), (a)-[:R]->(b), (a)-[:R]->(b), (b)-[:R]->(a), (b)-[:R]->(a)
"""
When executing query:
"""
MATCH (b)--(a)--(c)
WITH a, (SUM(b.x)+SUM(c.x)) AS s
RETURN a, s
"""
Then the result should be:
| a | s |
| (:A{x: 1}) | 48 |
| (:B{x: 2}) | 24 |
Scenario: With test 04:
Given an empty graph
And having executed:
"""
CREATE (a:A{x: 1}), (b:B{x: 2}), (c:C{x: 3}), (d:D{x: 4}), (e:E{x: 5}), (a)-[:R]->(b), (b)-[:R]->(c), (b)-[:R]->(d), (c)-[:R]->(a), (c)-[:R]->(e), (d)-[:R]->(e)
"""
When executing query:
"""
MATCH (c)--(a:B)--(b)--(d)
WITH a, b, SUM(c.x)+SUM(d.x) AS n RETURN a, b, n
"""
Then the result should be:
| a | b | n |
| (:B{x: 2}) | (:A{x: 1}) | 13 |
| (:B{x: 2}) | (:C{x: 3}) | 22 |
| (:B{x: 2}) | (:D{x: 4}) | 14 |
Scenario: With test 05:
Given an empty graph
And having executed:
"""
CREATE (a:A{x: 1}), (b:B{x: 2}), (c:C{x: 3}), (d:D{x: 4}), (e:E{x: 5}), (a)-[:R]->(b), (b)-[:R]->(c), (b)-[:R]->(d), (c)-[:R]->(a), (c)-[:R]->(e), (d)-[:R]->(e)
"""
When executing query:
"""
MATCH (c)--(a:B)--(b)--(d)
WITH a, b, AVG(c.x + d.x) AS n RETURN a, b, n
"""
Then the result should be:
| a | b | n |
| (:B{x: 2}) | (:A{x: 1}) | 6.5 |
| (:B{x: 2}) | (:C{x: 3}) | 5.5 |
| (:B{x: 2}) | (:D{x: 4}) | 7.0 |
Scenario: With test 06:
Given an empty graph
And having executed:
"""
CREATE (a:A{x: 1}), (b:B{x: 2}), (c:C{x: 3}), (d:D{x: 4}), (e:E{x: 5}), (a)-[:R]->(b), (b)-[:R]->(c), (b)-[:R]->(d), (c)-[:R]->(a), (c)-[:R]->(e), (d)-[:R]->(e)
"""
When executing query:
"""
MATCH (c)--(a:B)--(b)--(d)
WITH a, b, AVG(c.x + d.x) AS n RETURN MAX(n) AS n
"""
Then the result should be:
| n |
| 7.0 |
Scenario: With test 07:
Given an empty graph
And having executed:
"""
CREATE (a:A{x: 1}), (b:B{x: 2}), (c:C{x: 3}), (d:D{x: 4}), (e:E{x: 5}), (a)-[:R]->(b), (b)-[:R]->(c), (b)-[:R]->(d), (c)-[:R]->(a), (c)-[:R]->(e), (d)-[:R]->(e)
"""
When executing query:
"""
MATCH (c)--(a:B)--(b)--(d)
WITH a, b, AVG(c.x + d.x) AS n
WITH a, MAX(n) AS n RETURN a, n
"""
Then the result should be:
| a | n |
| (:B{x: 2}) | 7.0 |
Scenario: With test 08:
Given an empty graph
When executing query:
"""
CREATE (a), (b) WITH a, b CREATE (a)-[r:R]->(b) RETURN r
"""
Then the result should be:
| r |
| [:R] |
Scenario: With test 09:
Given an empty graph
When executing query:
"""
CREATE (a), (b) WITH a, b SET a:X SET b:Y WITH a, b MATCH(x:X) RETURN x
"""
Then the result should be:
| x |
| (:X) |
Scenario: With test 10:
Given an empty graph
When executing query:
"""
CREATE (a), (b), (a)-[:R]->(b) WITH a, b SET a:X SET b:Y
WITH a MATCH(x:X)--(b) RETURN x, x AS y
"""
Then the result should be:
| x | y |
| (:X) | (:X) |
Scenario: With test 10:
Given an empty graph
And having executed:
"""
CREATE (a:A{x: 1}), (b:B{x: 2}), (c:C{x: 3}), (d:D{x: 4}), (e:E{x: 5}), (a)-[:R]->(b), (b)-[:R]->(c), (b)-[:R]->(d), (c)-[:R]->(a), (c)-[:R]->(e), (d)-[:R]->(e)
"""
When executing query:
"""
MATCH (c)--(a:B)--(b)--(d) WITH a, b, AVG(c.x + d.x) AS av WITH AVG(av) AS avg
MATCH (c)--(a:B)--(b)--(d) WITH a, b, avg, AVG(c.x + d.x) AS av WHERE av>avg RETURN av
"""
Then the result should be:
| av |
| 6.5 |
| 7.0 |
Scenario: With test 11:
Given an empty graph
And having executed:
"""
CREATE(:A{a: 1}), (:B{a: 1}), (:C{a: 1}), (:D{a: 4}), (:E{a: 5})
"""
When executing query:
"""
MATCH(n) WITH n.a AS a
ORDER BY a LIMIT 4
RETURN a
"""
Then the result should be, in order:
| a |
| 1 |
| 1 |
| 1 |
| 4 |
Scenario: With test 12:
Given an empty graph
And having executed:
"""
CREATE(:A{a: 1}), (:B{a: 5}), (:C{a: 2}), (:D{a: 3}), (:E{a: 5})
"""
When executing query:
"""
MATCH(n) WITH n.a AS a
ORDER BY a SKIP 2
RETURN a
"""
Then the result should be, in order:
| a |
| 3 |
| 5 |
| 5 |
Scenario: With test 13:
Given an empty graph
And having executed:
"""
CREATE(:A{a: 1}), (:B{a: 5}), (:C{a: 2}), (:D{a: 3}), (:E{a: 5})
"""
When executing query:
"""
MATCH(n) WITH n.a AS a
ORDER BY a
RETURN a
"""
Then the result should be, in order:
| a |
| 1 |
| 2 |
| 3 |
| 5 |
| 5 |
Scenario: With test 14:
Given an empty graph
And having executed:
"""
CREATE(:A{a: 1}), (:B{a: 5}), (:C{a: 1}), (:D{a: 3}), (:E{a: 5})
"""
When executing query:
"""
MATCH(n) WITH DISTINCT n.a AS a
RETURN a
"""
Then the result should be:
| a |
| 1 |
| 3 |
| 5 |

View File

@ -0,0 +1,4 @@
CREATE (n:Person {age: 20});
CREATE (n:Person:Student {age: 20});
CREATE (n:Person {age: 21});
CREATE (n:Student {age:21})

View File

@ -0,0 +1,471 @@
# 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

View File

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

View File

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

View File

@ -0,0 +1,207 @@
#
# 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 empty
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

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

View File

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

View File

@ -0,0 +1,395 @@
#
# 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 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 |

View File

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

View File

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

View File

@ -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 exists
"""
Then the result should be:
| exists |
| <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()` should accept type Any
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()` 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[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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

@ -0,0 +1,550 @@
#
# 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 empty
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 empty
And no side effects

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,303 @@
#
# 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

View File

@ -0,0 +1,179 @@
#
# 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 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

View File

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

View File

@ -0,0 +1,641 @@
#
# 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: 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

View File

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

View File

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

View 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: 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,266 @@
#
# 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 empty
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 |

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": []
}

View File

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

View File

@ -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": []
}

View 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.

View File

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

Some files were not shown because too many files have changed in this diff Show More