diff --git a/tests/qa/.arcconfig b/tests/qa/.arcconfig new file mode 100644 index 000000000..3f9c578bc --- /dev/null +++ b/tests/qa/.arcconfig @@ -0,0 +1,5 @@ +{ + "project_id" : "memgraph", + "conduit_uri" : "https://phabricator.memgraph.io", + "phabricator_uri" : "https://phabricator.memgraph.io" +} diff --git a/tests/qa/.gitignore b/tests/qa/.gitignore new file mode 100644 index 000000000..388af07eb --- /dev/null +++ b/tests/qa/.gitignore @@ -0,0 +1,7 @@ +*.swo +*.swn +*.swp +*~ +*.pyc +ve3/ +.quality_assurance_status diff --git a/tests/qa/README.md b/tests/qa/README.md new file mode 100644 index 000000000..134c128c2 --- /dev/null +++ b/tests/qa/README.md @@ -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 diff --git a/tests/qa/continuous_integration b/tests/qa/continuous_integration new file mode 100755 index 000000000..f7d225127 --- /dev/null +++ b/tests/qa/continuous_integration @@ -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) diff --git a/tests/.gitignore b/tests/qa/dbms/.gitignore similarity index 100% rename from tests/.gitignore rename to tests/qa/dbms/.gitignore diff --git a/tests/qa/docker/kpi_service.docker b/tests/qa/docker/kpi_service.docker new file mode 100644 index 000000000..a947e9d57 --- /dev/null +++ b/tests/qa/docker/kpi_service.docker @@ -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 + diff --git a/tests/qa/dressipi_dump.txt b/tests/qa/dressipi_dump.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/qa/filter_failing_scenarios b/tests/qa/filter_failing_scenarios new file mode 100755 index 000000000..9066521d7 --- /dev/null +++ b/tests/qa/filter_failing_scenarios @@ -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() diff --git a/tests/qa/init b/tests/qa/init new file mode 100755 index 000000000..f97c9daf4 --- /dev/null +++ b/tests/qa/init @@ -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 diff --git a/tests/qa/kpi_service/kpi_service.py b/tests/qa/kpi_service/kpi_service.py new file mode 100644 index 000000000..605da2351 --- /dev/null +++ b/tests/qa/kpi_service/kpi_service.py @@ -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/") +def results_for_db(dbname): + """ + Function accessed with route /result/. Function + lists last tail result files added of database . 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//") +def result(dbname, test_suite): + """ + Function accessed with route /results// + Function lists last tail result files added of database + tested on . 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() diff --git a/tests/qa/plot_latency b/tests/qa/plot_latency new file mode 100755 index 000000000..99224f515 --- /dev/null +++ b/tests/qa/plot_latency @@ -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() diff --git a/tests/qa/requirements.txt b/tests/qa/requirements.txt new file mode 100644 index 000000000..7e674983c --- /dev/null +++ b/tests/qa/requirements.txt @@ -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 diff --git a/tests/qa/run b/tests/qa/run new file mode 100755 index 000000000..060353366 --- /dev/null +++ b/tests/qa/run @@ -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} diff --git a/tests/qa/snapshots/default/2017_05_19__17_40_02_88544 b/tests/qa/snapshots/default/2017_05_19__17_40_02_88544 new file mode 100644 index 000000000..4ac5fc6cf Binary files /dev/null and b/tests/qa/snapshots/default/2017_05_19__17_40_02_88544 differ diff --git a/tests/qa/snapshots/default/2017_05_19__17_41_05_38733 b/tests/qa/snapshots/default/2017_05_19__17_41_05_38733 new file mode 100644 index 000000000..4ac5fc6cf Binary files /dev/null and b/tests/qa/snapshots/default/2017_05_19__17_41_05_38733 differ diff --git a/tests/qa/snapshots/default/2017_06_16__07_09_32_30299 b/tests/qa/snapshots/default/2017_06_16__07_09_32_30299 new file mode 100644 index 000000000..4ac5fc6cf Binary files /dev/null and b/tests/qa/snapshots/default/2017_06_16__07_09_32_30299 differ diff --git a/tests/qa/tck_engine/.gitignore b/tests/qa/tck_engine/.gitignore new file mode 100644 index 000000000..cb584dea2 --- /dev/null +++ b/tests/qa/tck_engine/.gitignore @@ -0,0 +1,3 @@ +report/ +__pycache__/ +*.output diff --git a/tests/qa/tck_engine/behave.ini b/tests/qa/tck_engine/behave.ini new file mode 100644 index 000000000..c29373443 --- /dev/null +++ b/tests/qa/tck_engine/behave.ini @@ -0,0 +1,6 @@ +[behave] +stderr_capture=False +stdout_capture=False +format=progress +junit=1 +junit_directory=report diff --git a/tests/qa/tck_engine/binary.todo b/tests/qa/tck_engine/binary.todo new file mode 100644 index 000000000..48703d11b --- /dev/null +++ b/tests/qa/tck_engine/binary.todo @@ -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 diff --git a/tests/qa/tck_engine/environment.py b/tests/qa/tck_engine/environment.py new file mode 100644 index 000000000..c0b6b3dd5 --- /dev/null +++ b/tests/qa/tck_engine/environment.py @@ -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 diff --git a/tests/qa/tck_engine/results/.gitignore b/tests/qa/tck_engine/results/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/tests/qa/tck_engine/results/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/qa/tck_engine/steps/binary_tree.py b/tests/qa/tck_engine/steps/binary_tree.py new file mode 100644 index 000000000..300326edd --- /dev/null +++ b/tests/qa/tck_engine/steps/binary_tree.py @@ -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) diff --git a/tests/qa/tck_engine/steps/database.py b/tests/qa/tck_engine/steps/database.py new file mode 100644 index 000000000..6e71f297e --- /dev/null +++ b/tests/qa/tck_engine/steps/database.py @@ -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) diff --git a/tests/qa/tck_engine/steps/errors.py b/tests/qa/tck_engine/steps/errors.py new file mode 100644 index 000000000..e189610f8 --- /dev/null +++ b/tests/qa/tck_engine/steps/errors.py @@ -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) diff --git a/tests/qa/tck_engine/steps/graph.py b/tests/qa/tck_engine/steps/graph.py new file mode 100644 index 000000000..5521892d2 --- /dev/null +++ b/tests/qa/tck_engine/steps/graph.py @@ -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' diff --git a/tests/qa/tck_engine/steps/graph_properties.py b/tests/qa/tck_engine/steps/graph_properties.py new file mode 100644 index 000000000..cc88e4dfb --- /dev/null +++ b/tests/qa/tck_engine/steps/graph_properties.py @@ -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 diff --git a/tests/qa/tck_engine/steps/parser.py b/tests/qa/tck_engine/steps/parser.py new file mode 100644 index 000000000..416152820 --- /dev/null +++ b/tests/qa/tck_engine/steps/parser.py @@ -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 diff --git a/tests/qa/tck_engine/steps/query.py b/tests/qa/tck_engine/steps/query.py new file mode 100644 index 000000000..78a66da21 --- /dev/null +++ b/tests/qa/tck_engine/steps/query.py @@ -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) diff --git a/tests/qa/tck_engine/steps/test_parameters.py b/tests/qa/tck_engine/steps/test_parameters.py new file mode 100644 index 000000000..7fa7dbb52 --- /dev/null +++ b/tests/qa/tck_engine/steps/test_parameters.py @@ -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) diff --git a/tests/qa/tck_engine/test_executor.py b/tests/qa/tck_engine/test_executor.py new file mode 100644 index 000000000..dac8d56b7 --- /dev/null +++ b/tests/qa/tck_engine/test_executor.py @@ -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()) diff --git a/tests/qa/tck_engine/test_results.py b/tests/qa/tck_engine/test_results.py new file mode 100644 index 000000000..7bc6cce13 --- /dev/null +++ b/tests/qa/tck_engine/test_results.py @@ -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 diff --git a/tests/qa/tck_engine/tests/dressipi_V1/features/test01.feature b/tests/qa/tck_engine/tests/dressipi_V1/features/test01.feature new file mode 100644 index 000000000..2c98a4170 --- /dev/null +++ b/tests/qa/tck_engine/tests/dressipi_V1/features/test01.feature @@ -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| + diff --git a/tests/qa/tck_engine/tests/dressipi_V1/features/test02.feature b/tests/qa/tck_engine/tests/dressipi_V1/features/test02.feature new file mode 100644 index 000000000..cbaa07028 --- /dev/null +++ b/tests/qa/tck_engine/tests/dressipi_V1/features/test02.feature @@ -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| + diff --git a/tests/qa/tck_engine/tests/dressipi_V1/graphs/dressipi_graph01.cypher b/tests/qa/tck_engine/tests/dressipi_V1/graphs/dressipi_graph01.cypher new file mode 100644 index 000000000..65d43cb2c --- /dev/null +++ b/tests/qa/tck_engine/tests/dressipi_V1/graphs/dressipi_graph01.cypher @@ -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; + + + + + + + + + + + + + + + + + + + + diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/aggregations.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/aggregations.feature new file mode 100644 index 000000000..1d9ab75e2 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/aggregations.feature @@ -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'] | diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/cartesian.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/cartesian.feature new file mode 100644 index 000000000..7cc127884 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/cartesian.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/create.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/create.feature new file mode 100644 index 000000000..0bdb159f4 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/create.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/expressions.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/expressions.feature new file mode 100644 index 000000000..25d284f97 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/expressions.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/functions.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/functions.feature new file mode 100644 index 000000000..334e8f344 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/functions.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/list_operations.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/list_operations.feature new file mode 100644 index 000000000..4c71be35c --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/list_operations.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/match.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/match.feature new file mode 100644 index 000000000..4713fdcba --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/match.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/memgraph.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/memgraph.feature new file mode 100644 index 000000000..b46063995 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/memgraph.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/merge.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/merge.feature new file mode 100644 index 000000000..75d4b3573 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/merge.feature @@ -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)) | + diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/string_operators.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/string_operators.feature new file mode 100644 index 000000000..28fec96a5 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/string_operators.feature @@ -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 + diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/unstable.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/unstable.feature new file mode 100644 index 000000000..e6c396d0e --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/unstable.feature @@ -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 | + diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/update_clauses.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/update_clauses.feature new file mode 100644 index 000000000..af04582e0 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/update_clauses.feature @@ -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}) | diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/with.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/with.feature new file mode 100644 index 000000000..84d2cd225 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/with.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/memgraph_V1/graphs/graph_01.cypher b/tests/qa/tck_engine/tests/memgraph_V1/graphs/graph_01.cypher new file mode 100644 index 000000000..4f1ea3056 --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/graphs/graph_01.cypher @@ -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}) diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/AggregationAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/AggregationAcceptance.feature new file mode 100644 index 000000000..c4c788a64 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/AggregationAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ColumnNameAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ColumnNameAcceptance.feature new file mode 100644 index 000000000..22ca0d895 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ColumnNameAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/Comparability.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/Comparability.feature new file mode 100644 index 000000000..4239a26a6 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/Comparability.feature @@ -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 rhs AS result + WHERE result + RETURN lhs, rhs + """ + Then the result should be: + | lhs | rhs | + | | | + And no side effects + + Examples: + | operator | lhs | rhs | + | < | 1 | 3.14 | + | <= | 1 | 3.14 | + | >= | 3.14 | 1 | + | > | 3.14 | 1 | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ComparisonOperatorAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ComparisonOperatorAcceptance.feature new file mode 100644 index 000000000..b74024958 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ComparisonOperatorAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/Create.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/Create.feature new file mode 100644 index 000000000..9738540f1 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/Create.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/CreateAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/CreateAcceptance.feature new file mode 100644 index 000000000..d3d79e06a --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/CreateAcceptance.feature @@ -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) | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/DeleteAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/DeleteAcceptance.feature new file mode 100644 index 000000000..03c0bf51b --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/DeleteAcceptance.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/EqualsAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/EqualsAcceptance.feature new file mode 100644 index 000000000..3782f9cad --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/EqualsAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ExpressionAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ExpressionAcceptance.feature new file mode 100644 index 000000000..4d75631ab --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ExpressionAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/FunctionsAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/FunctionsAcceptance.feature new file mode 100644 index 000000000..772ecd77b --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/FunctionsAcceptance.feature @@ -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 AS map + RETURN exists(map.name) AS exists + """ + Then the result should be: + | exists | + | | + 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 AS map + RETURN map.name IS NOT NULL + """ + Then the result should be: + | map.name IS NOT NULL | + | | + 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 |

| + When executing query: + """ + MATCH (n) + RETURN percentileDisc(n.prop, $percentile) AS p + """ + Then the result should be: + | p | + | | + 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 |

| + When executing query: + """ + MATCH (n) + RETURN percentileCont(n.prop, $percentile) AS p + """ + Then the result should be: + | p | + | | + 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 | | + 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 | | + 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, ] | 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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/JoinAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/JoinAcceptance.feature new file mode 100644 index 000000000..4b3c944f5 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/JoinAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/KeysAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/KeysAcceptance.feature new file mode 100644 index 000000000..a376bceda --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/KeysAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/LabelsAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/LabelsAcceptance.feature new file mode 100644 index 000000000..d767e37a6 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/LabelsAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/LargeCreateQuery.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/LargeCreateQuery.feature new file mode 100644 index 000000000..767989e12 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/LargeCreateQuery.feature @@ -0,0 +1,1361 @@ +# +# 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: LargeCreateQuery + + Scenario: Generate the movie graph correctly + Given any graph + When executing query: + """ + CREATE (theMatrix:Movie {title: 'The Matrix', released: 1999, tagline: 'Welcome to the Real World'}) + CREATE (keanu:Person {name: 'Keanu Reeves', born: 1964}) + CREATE (carrie:Person {name: 'Carrie-Anne Moss', born: 1967}) + CREATE (laurence:Person {name: 'Laurence Fishburne', born: 1961}) + CREATE (hugo:Person {name: 'Hugo Weaving', born: 1960}) + CREATE (andyW:Person {name: 'Andy Wachowski', born: 1967}) + CREATE (lanaW:Person {name: 'Lana Wachowski', born: 1965}) + CREATE (joelS:Person {name: 'Joel Silver', born: 1952}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Neo']}]->(theMatrix), + (carrie)-[:ACTED_IN {roles: ['Trinity']}]->(theMatrix), + (laurence)-[:ACTED_IN {roles: ['Morpheus']}]->(theMatrix), + (hugo)-[:ACTED_IN {roles: ['Agent Smith']}]->(theMatrix), + (andyW)-[:DIRECTED]->(theMatrix), + (lanaW)-[:DIRECTED]->(theMatrix), + (joelS)-[:PRODUCED]->(theMatrix) + + CREATE (emil:Person {name: 'Emil Eifrem', born: 1978}) + CREATE (emil)-[:ACTED_IN {roles: ['Emil']}]->(theMatrix) + + CREATE (theMatrixReloaded:Movie {title: 'The Matrix Reloaded', released: 2003, + tagline: 'Free your mind'}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Neo'] }]->(theMatrixReloaded), + (carrie)-[:ACTED_IN {roles: ['Trinity']}]->(theMatrixReloaded), + (laurence)-[:ACTED_IN {roles: ['Morpheus']}]->(theMatrixReloaded), + (hugo)-[:ACTED_IN {roles: ['Agent Smith']}]->(theMatrixReloaded), + (andyW)-[:DIRECTED]->(theMatrixReloaded), + (lanaW)-[:DIRECTED]->(theMatrixReloaded), + (joelS)-[:PRODUCED]->(theMatrixReloaded) + + CREATE (theMatrixRevolutions:Movie {title: 'The Matrix Revolutions', released: 2003, + tagline: 'Everything that has a beginning has an end'}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Neo']}]->(theMatrixRevolutions), + (carrie)-[:ACTED_IN {roles: ['Trinity']}]->(theMatrixRevolutions), + (laurence)-[:ACTED_IN {roles: ['Morpheus']}]->(theMatrixRevolutions), + (hugo)-[:ACTED_IN {roles: ['Agent Smith']}]->(theMatrixRevolutions), + (andyW)-[:DIRECTED]->(theMatrixRevolutions), + (lanaW)-[:DIRECTED]->(theMatrixRevolutions), + (joelS)-[:PRODUCED]->(theMatrixRevolutions) + + CREATE (theDevilsAdvocate:Movie {title: 'The Devil\'s Advocate', released: 1997, + tagline: 'Evil has its winning ways'}) + CREATE (charlize:Person {name: 'Charlize Theron', born: 1975}) + CREATE (al:Person {name: 'Al Pacino', born: 1940}) + CREATE (taylor:Person {name: 'Taylor Hackford', born: 1944}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Kevin Lomax']}]->(theDevilsAdvocate), + (charlize)-[:ACTED_IN {roles: ['Mary Ann Lomax']}]->(theDevilsAdvocate), + (al)-[:ACTED_IN {roles: ['John Milton']}]->(theDevilsAdvocate), + (taylor)-[:DIRECTED]->(theDevilsAdvocate) + + CREATE (aFewGoodMen:Movie {title: 'A Few Good Men', released: 1992, + tagline: 'Deep within the heart of the nation\'s capital, one man will stop at nothing to keep his honor, ...'}) + CREATE (tomC:Person {name: 'Tom Cruise', born: 1962}) + CREATE (jackN:Person {name: 'Jack Nicholson', born: 1937}) + CREATE (demiM:Person {name: 'Demi Moore', born: 1962}) + CREATE (kevinB:Person {name: 'Kevin Bacon', born: 1958}) + CREATE (kieferS:Person {name: 'Kiefer Sutherland', born: 1966}) + CREATE (noahW:Person {name: 'Noah Wyle', born: 1971}) + CREATE (cubaG:Person {name: 'Cuba Gooding Jr.', born: 1968}) + CREATE (kevinP:Person {name: 'Kevin Pollak', born: 1957}) + CREATE (jTW:Person {name: 'J.T. Walsh', born: 1943}) + CREATE (jamesM:Person {name: 'James Marshall', born: 1967}) + CREATE (christopherG:Person {name: 'Christopher Guest', born: 1948}) + CREATE (robR:Person {name: 'Rob Reiner', born: 1947}) + CREATE (aaronS:Person {name: 'Aaron Sorkin', born: 1961}) + CREATE + (tomC)-[:ACTED_IN {roles: ['Lt. Daniel Kaffee']}]->(aFewGoodMen), + (jackN)-[:ACTED_IN {roles: ['Col. Nathan R. Jessup']}]->(aFewGoodMen), + (demiM)-[:ACTED_IN {roles: ['Lt. Cdr. JoAnne Galloway']}]->(aFewGoodMen), + (kevinB)-[:ACTED_IN {roles: ['Capt. Jack Ross']}]->(aFewGoodMen), + (kieferS)-[:ACTED_IN {roles: ['Lt. Jonathan Kendrick']}]->(aFewGoodMen), + (noahW)-[:ACTED_IN {roles: ['Cpl. Jeffrey Barnes']}]->(aFewGoodMen), + (cubaG)-[:ACTED_IN {roles: ['Cpl. Carl Hammaker']}]->(aFewGoodMen), + (kevinP)-[:ACTED_IN {roles: ['Lt. Sam Weinberg']}]->(aFewGoodMen), + (jTW)-[:ACTED_IN {roles: ['Lt. Col. Matthew Andrew Markinson']}]->(aFewGoodMen), + (jamesM)-[:ACTED_IN {roles: ['Pfc. Louden Downey']}]->(aFewGoodMen), + (christopherG)-[:ACTED_IN {roles: ['Dr. Stone']}]->(aFewGoodMen), + (aaronS)-[:ACTED_IN {roles: ['Bar patron']}]->(aFewGoodMen), + (robR)-[:DIRECTED]->(aFewGoodMen), + (aaronS)-[:WROTE]->(aFewGoodMen) + + CREATE (topGun:Movie {title: 'Top Gun', released: 1986, + tagline: 'I feel the need, the need for speed.'}) + CREATE (kellyM:Person {name: 'Kelly McGillis', born: 1957}) + CREATE (valK:Person {name: 'Val Kilmer', born: 1959}) + CREATE (anthonyE:Person {name: 'Anthony Edwards', born: 1962}) + CREATE (tomS:Person {name: 'Tom Skerritt', born: 1933}) + CREATE (megR:Person {name: 'Meg Ryan', born: 1961}) + CREATE (tonyS:Person {name: 'Tony Scott', born: 1944}) + CREATE (jimC:Person {name: 'Jim Cash', born: 1941}) + CREATE + (tomC)-[:ACTED_IN {roles: ['Maverick']}]->(topGun), + (kellyM)-[:ACTED_IN {roles: ['Charlie']}]->(topGun), + (valK)-[:ACTED_IN {roles: ['Iceman']}]->(topGun), + (anthonyE)-[:ACTED_IN {roles: ['Goose']}]->(topGun), + (tomS)-[:ACTED_IN {roles: ['Viper']}]->(topGun), + (megR)-[:ACTED_IN {roles: ['Carole']}]->(topGun), + (tonyS)-[:DIRECTED]->(topGun), + (jimC)-[:WROTE]->(topGun) + + CREATE (jerryMaguire:Movie {title: 'Jerry Maguire', released: 2000, + tagline: 'The rest of his life begins now.'}) + CREATE (reneeZ:Person {name: 'Renee Zellweger', born: 1969}) + CREATE (kellyP:Person {name: 'Kelly Preston', born: 1962}) + CREATE (jerryO:Person {name: 'Jerry O\'Connell', born: 1974}) + CREATE (jayM:Person {name: 'Jay Mohr', born: 1970}) + CREATE (bonnieH:Person {name: 'Bonnie Hunt', born: 1961}) + CREATE (reginaK:Person {name: 'Regina King', born: 1971}) + CREATE (jonathanL:Person {name: 'Jonathan Lipnicki', born: 1996}) + CREATE (cameronC:Person {name: 'Cameron Crowe', born: 1957}) + CREATE + (tomC)-[:ACTED_IN {roles: ['Jerry Maguire']}]->(jerryMaguire), + (cubaG)-[:ACTED_IN {roles: ['Rod Tidwell']}]->(jerryMaguire), + (reneeZ)-[:ACTED_IN {roles: ['Dorothy Boyd']}]->(jerryMaguire), + (kellyP)-[:ACTED_IN {roles: ['Avery Bishop']}]->(jerryMaguire), + (jerryO)-[:ACTED_IN {roles: ['Frank Cushman']}]->(jerryMaguire), + (jayM)-[:ACTED_IN {roles: ['Bob Sugar']}]->(jerryMaguire), + (bonnieH)-[:ACTED_IN {roles: ['Laurel Boyd']}]->(jerryMaguire), + (reginaK)-[:ACTED_IN {roles: ['Marcee Tidwell']}]->(jerryMaguire), + (jonathanL)-[:ACTED_IN {roles: ['Ray Boyd']}]->(jerryMaguire), + (cameronC)-[:DIRECTED]->(jerryMaguire), + (cameronC)-[:PRODUCED]->(jerryMaguire), + (cameronC)-[:WROTE]->(jerryMaguire) + + CREATE (standByMe:Movie {title: 'Stand-By-Me', released: 1986, + tagline: 'The last real taste of innocence'}) + CREATE (riverP:Person {name: 'River Phoenix', born: 1970}) + CREATE (coreyF:Person {name: 'Corey Feldman', born: 1971}) + CREATE (wilW:Person {name: 'Wil Wheaton', born: 1972}) + CREATE (johnC:Person {name: 'John Cusack', born: 1966}) + CREATE (marshallB:Person {name: 'Marshall Bell', born: 1942}) + CREATE + (wilW)-[:ACTED_IN {roles: ['Gordie Lachance']}]->(standByMe), + (riverP)-[:ACTED_IN {roles: ['Chris Chambers']}]->(standByMe), + (jerryO)-[:ACTED_IN {roles: ['Vern Tessio']}]->(standByMe), + (coreyF)-[:ACTED_IN {roles: ['Teddy Duchamp']}]->(standByMe), + (johnC)-[:ACTED_IN {roles: ['Denny Lachance']}]->(standByMe), + (kieferS)-[:ACTED_IN {roles: ['Ace Merrill']}]->(standByMe), + (marshallB)-[:ACTED_IN {roles: ['Mr. Lachance']}]->(standByMe), + (robR)-[:DIRECTED]->(standByMe) + + CREATE (asGoodAsItGets:Movie {title: 'As-good-as-it-gets', released: 1997, + tagline: 'A comedy from the heart that goes for the throat'}) + CREATE (helenH:Person {name: 'Helen Hunt', born: 1963}) + CREATE (gregK:Person {name: 'Greg Kinnear', born: 1963}) + CREATE (jamesB:Person {name: 'James L. Brooks', born: 1940}) + CREATE + (jackN)-[:ACTED_IN {roles: ['Melvin Udall']}]->(asGoodAsItGets), + (helenH)-[:ACTED_IN {roles: ['Carol Connelly']}]->(asGoodAsItGets), + (gregK)-[:ACTED_IN {roles: ['Simon Bishop']}]->(asGoodAsItGets), + (cubaG)-[:ACTED_IN {roles: ['Frank Sachs']}]->(asGoodAsItGets), + (jamesB)-[:DIRECTED]->(asGoodAsItGets) + + CREATE (whatDreamsMayCome:Movie {title: 'What Dreams May Come', released: 1998, + tagline: 'After life there is more. The end is just the beginning.'}) + CREATE (annabellaS:Person {name: 'Annabella Sciorra', born: 1960}) + CREATE (maxS:Person {name: 'Max von Sydow', born: 1929}) + CREATE (wernerH:Person {name: 'Werner Herzog', born: 1942}) + CREATE (robin:Person {name: 'Robin Williams', born: 1951}) + CREATE (vincentW:Person {name: 'Vincent Ward', born: 1956}) + CREATE + (robin)-[:ACTED_IN {roles: ['Chris Nielsen']}]->(whatDreamsMayCome), + (cubaG)-[:ACTED_IN {roles: ['Albert Lewis']}]->(whatDreamsMayCome), + (annabellaS)-[:ACTED_IN {roles: ['Annie Collins-Nielsen']}]->(whatDreamsMayCome), + (maxS)-[:ACTED_IN {roles: ['The Tracker']}]->(whatDreamsMayCome), + (wernerH)-[:ACTED_IN {roles: ['The Face']}]->(whatDreamsMayCome), + (vincentW)-[:DIRECTED]->(whatDreamsMayCome) + + CREATE (snowFallingonCedars:Movie {title: 'Snow-Falling-on-Cedars', released: 1999, + tagline: 'First loves last. Forever.'}) + CREATE (ethanH:Person {name: 'Ethan Hawke', born: 1970}) + CREATE (rickY:Person {name: 'Rick Yune', born: 1971}) + CREATE (jamesC:Person {name: 'James Cromwell', born: 1940}) + CREATE (scottH:Person {name: 'Scott Hicks', born: 1953}) + CREATE + (ethanH)-[:ACTED_IN {roles: ['Ishmael Chambers']}]->(snowFallingonCedars), + (rickY)-[:ACTED_IN {roles: ['Kazuo Miyamoto']}]->(snowFallingonCedars), + (maxS)-[:ACTED_IN {roles: ['Nels Gudmundsson']}]->(snowFallingonCedars), + (jamesC)-[:ACTED_IN {roles: ['Judge Fielding']}]->(snowFallingonCedars), + (scottH)-[:DIRECTED]->(snowFallingonCedars) + + CREATE (youveGotMail:Movie {title: 'You\'ve Got Mail', released: 1998, + tagline: 'At-odds-in-life, in-love-on-line'}) + CREATE (parkerP:Person {name: 'Parker Posey', born: 1968}) + CREATE (daveC:Person {name: 'Dave Chappelle', born: 1973}) + CREATE (steveZ:Person {name: 'Steve Zahn', born: 1967}) + CREATE (tomH:Person {name: 'Tom Hanks', born: 1956}) + CREATE (noraE:Person {name: 'Nora Ephron', born: 1941}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Joe Fox']}]->(youveGotMail), + (megR)-[:ACTED_IN {roles: ['Kathleen Kelly']}]->(youveGotMail), + (gregK)-[:ACTED_IN {roles: ['Frank Navasky']}]->(youveGotMail), + (parkerP)-[:ACTED_IN {roles: ['Patricia Eden']}]->(youveGotMail), + (daveC)-[:ACTED_IN {roles: ['Kevin Jackson']}]->(youveGotMail), + (steveZ)-[:ACTED_IN {roles: ['George Pappas']}]->(youveGotMail), + (noraE)-[:DIRECTED]->(youveGotMail) + + CREATE (sleeplessInSeattle:Movie {title: 'Sleepless-in-Seattle', released: 1993, + tagline: 'What if someone you never met, someone you never saw, someone you never knew was the only someone for you?'}) + CREATE (ritaW:Person {name: 'Rita Wilson', born: 1956}) + CREATE (billPull:Person {name: 'Bill Pullman', born: 1953}) + CREATE (victorG:Person {name: 'Victor Garber', born: 1949}) + CREATE (rosieO:Person {name: 'Rosie O\'Donnell', born: 1962}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Sam Baldwin']}]->(sleeplessInSeattle), + (megR)-[:ACTED_IN {roles: ['Annie Reed']}]->(sleeplessInSeattle), + (ritaW)-[:ACTED_IN {roles: ['Suzy']}]->(sleeplessInSeattle), + (billPull)-[:ACTED_IN {roles: ['Walter']}]->(sleeplessInSeattle), + (victorG)-[:ACTED_IN {roles: ['Greg']}]->(sleeplessInSeattle), + (rosieO)-[:ACTED_IN {roles: ['Becky']}]->(sleeplessInSeattle), + (noraE)-[:DIRECTED]->(sleeplessInSeattle) + + CREATE (joeVersustheVolcano:Movie {title: 'Joe-Versus-the-Volcano', released: 1990, + tagline: 'A story of love'}) + CREATE (johnS:Person {name: 'John Patrick Stanley', born: 1950}) + CREATE (nathan:Person {name: 'Nathan Lane', born: 1956}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Joe Banks']}]->(joeVersustheVolcano), + (megR)-[:ACTED_IN {roles: ['DeDe', 'Angelica Graynamore', 'Patricia Graynamore']}]->(joeVersustheVolcano), + (nathan)-[:ACTED_IN {roles: ['Baw']}]->(joeVersustheVolcano), + (johnS)-[:DIRECTED]->(joeVersustheVolcano) + + CREATE (whenHarryMetSally:Movie {title: 'When-Harry-Met-Sally', released: 1998, + tagline: 'When-Harry-Met-Sally'}) + CREATE (billyC:Person {name: 'Billy Crystal', born: 1948}) + CREATE (carrieF:Person {name: 'Carrie Fisher', born: 1956}) + CREATE (brunoK:Person {name: 'Bruno Kirby', born: 1949}) + CREATE + (billyC)-[:ACTED_IN {roles: ['Harry Burns']}]->(whenHarryMetSally), + (megR)-[:ACTED_IN {roles: ['Sally Albright']}]->(whenHarryMetSally), + (carrieF)-[:ACTED_IN {roles: ['Marie']}]->(whenHarryMetSally), + (brunoK)-[:ACTED_IN {roles: ['Jess']}]->(whenHarryMetSally), + (robR)-[:DIRECTED]->(whenHarryMetSally), + (robR)-[:PRODUCED]->(whenHarryMetSally), + (noraE)-[:PRODUCED]->(whenHarryMetSally), + (noraE)-[:WROTE]->(whenHarryMetSally) + + CREATE (thatThingYouDo:Movie {title: 'That-Thing-You-Do', released: 1996, + tagline: 'There comes a time...'}) + CREATE (livT:Person {name: 'Liv Tyler', born: 1977}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Mr. White']}]->(thatThingYouDo), + (livT)-[:ACTED_IN {roles: ['Faye Dolan']}]->(thatThingYouDo), + (charlize)-[:ACTED_IN {roles: ['Tina']}]->(thatThingYouDo), + (tomH)-[:DIRECTED]->(thatThingYouDo) + + CREATE (theReplacements:Movie {title: 'The Replacements', released: 2000, + tagline: 'Pain heals, Chicks dig scars... Glory lasts forever'}) + CREATE (brooke:Person {name: 'Brooke Langton', born: 1970}) + CREATE (gene:Person {name: 'Gene Hackman', born: 1930}) + CREATE (orlando:Person {name: 'Orlando Jones', born: 1968}) + CREATE (howard:Person {name: 'Howard Deutch', born: 1950}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Shane Falco']}]->(theReplacements), + (brooke)-[:ACTED_IN {roles: ['Annabelle Farrell']}]->(theReplacements), + (gene)-[:ACTED_IN {roles: ['Jimmy McGinty']}]->(theReplacements), + (orlando)-[:ACTED_IN {roles: ['Clifford Franklin']}]->(theReplacements), + (howard)-[:DIRECTED]->(theReplacements) + + CREATE (rescueDawn:Movie {title: 'RescueDawn', released: 2006, + tagline: 'The extraordinary true story'}) + CREATE (christianB:Person {name: 'Christian Bale', born: 1974}) + CREATE (zachG:Person {name: 'Zach Grenier', born: 1954}) + CREATE + (marshallB)-[:ACTED_IN {roles: ['Admiral']}]->(rescueDawn), + (christianB)-[:ACTED_IN {roles: ['Dieter Dengler']}]->(rescueDawn), + (zachG)-[:ACTED_IN {roles: ['Squad Leader']}]->(rescueDawn), + (steveZ)-[:ACTED_IN {roles: ['Duane']}]->(rescueDawn), + (wernerH)-[:DIRECTED]->(rescueDawn) + + CREATE (theBirdcage:Movie {title: 'The-Birdcage', released: 1996, tagline: 'Come-as-you-are'}) + CREATE (mikeN:Person {name: 'Mike Nichols', born: 1931}) + CREATE + (robin)-[:ACTED_IN {roles: ['Armand Goldman']}]->(theBirdcage), + (nathan)-[:ACTED_IN {roles: ['Albert Goldman']}]->(theBirdcage), + (gene)-[:ACTED_IN {roles: ['Sen. Kevin Keeley']}]->(theBirdcage), + (mikeN)-[:DIRECTED]->(theBirdcage) + + CREATE (unforgiven:Movie {title: 'Unforgiven', released: 1992, + tagline: 'It\'s a hell of a thing, killing a man'}) + CREATE (richardH:Person {name: 'Richard Harris', born: 1930}) + CREATE (clintE:Person {name: 'Clint Eastwood', born: 1930}) + CREATE + (richardH)-[:ACTED_IN {roles: ['English Bob']}]->(unforgiven), + (clintE)-[:ACTED_IN {roles: ['Bill Munny']}]->(unforgiven), + (gene)-[:ACTED_IN {roles: ['Little Bill Daggett']}]->(unforgiven), + (clintE)-[:DIRECTED]->(unforgiven) + + CREATE (johnnyMnemonic:Movie {title: 'Johnny-Mnemonic', released: 1995, + tagline: 'The-hottest-data-in-the-coolest-head'}) + CREATE (takeshi:Person {name: 'Takeshi Kitano', born: 1947}) + CREATE (dina:Person {name: 'Dina Meyer', born: 1968}) + CREATE (iceT:Person {name: 'Ice-T', born: 1958}) + CREATE (robertL:Person {name: 'Robert Longo', born: 1953}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Johnny Mnemonic']}]->(johnnyMnemonic), + (takeshi)-[:ACTED_IN {roles: ['Takahashi']}]->(johnnyMnemonic), + (dina)-[:ACTED_IN {roles: ['Jane']}]->(johnnyMnemonic), + (iceT)-[:ACTED_IN {roles: ['J-Bone']}]->(johnnyMnemonic), + (robertL)-[:DIRECTED]->(johnnyMnemonic) + + CREATE (cloudAtlas:Movie {title: 'Cloud Atlas', released: 2012, tagline: 'Everything is connected'}) + CREATE (halleB:Person {name: 'Halle Berry', born: 1966}) + CREATE (jimB:Person {name: 'Jim Broadbent', born: 1949}) + CREATE (tomT:Person {name: 'Tom Tykwer', born: 1965}) + CREATE (davidMitchell:Person {name: 'David Mitchell', born: 1969}) + CREATE (stefanArndt:Person {name: 'Stefan Arndt', born: 1961}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Zachry', 'Dr. Henry Goose', 'Isaac Sachs', 'Dermot Hoggins']}]->(cloudAtlas), + (hugo)-[:ACTED_IN {roles: ['Bill Smoke', 'Haskell Moore', 'Tadeusz Kesselring', 'Nurse Noakes', 'Boardman Mephi', 'Old Georgie']}]->(cloudAtlas), + (halleB)-[:ACTED_IN {roles: ['Luisa Rey', 'Jocasta Ayrs', 'Ovid', 'Meronym']}]->(cloudAtlas), + (jimB)-[:ACTED_IN {roles: ['Vyvyan Ayrs', 'Captain Molyneux', 'Timothy Cavendish']}]->(cloudAtlas), + (tomT)-[:DIRECTED]->(cloudAtlas), + (andyW)-[:DIRECTED]->(cloudAtlas), + (lanaW)-[:DIRECTED]->(cloudAtlas), + (davidMitchell)-[:WROTE]->(cloudAtlas), + (stefanArndt)-[:PRODUCED]->(cloudAtlas) + + CREATE (theDaVinciCode:Movie {title: 'The Da Vinci Code', released: 2006, tagline: 'Break The Codes'}) + CREATE (ianM:Person {name: 'Ian McKellen', born: 1939}) + CREATE (audreyT:Person {name: 'Audrey Tautou', born: 1976}) + CREATE (paulB:Person {name: 'Paul Bettany', born: 1971}) + CREATE (ronH:Person {name: 'Ron Howard', born: 1954}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Dr. Robert Langdon']}]->(theDaVinciCode), + (ianM)-[:ACTED_IN {roles: ['Sir Leight Teabing']}]->(theDaVinciCode), + (audreyT)-[:ACTED_IN {roles: ['Sophie Neveu']}]->(theDaVinciCode), + (paulB)-[:ACTED_IN {roles: ['Silas']}]->(theDaVinciCode), + (ronH)-[:DIRECTED]->(theDaVinciCode) + + CREATE (vforVendetta:Movie {title: 'V for Vendetta', released: 2006, tagline: 'Freedom! Forever!'}) + CREATE (natalieP:Person {name: 'Natalie Portman', born: 1981}) + CREATE (stephenR:Person {name: 'Stephen Rea', born: 1946}) + CREATE (johnH:Person {name: 'John Hurt', born: 1940}) + CREATE (benM:Person {name: 'Ben Miles', born: 1967}) + CREATE + (hugo)-[:ACTED_IN {roles: ['V']}]->(vforVendetta), + (natalieP)-[:ACTED_IN {roles: ['Evey Hammond']}]->(vforVendetta), + (stephenR)-[:ACTED_IN {roles: ['Eric Finch']}]->(vforVendetta), + (johnH)-[:ACTED_IN {roles: ['High Chancellor Adam Sutler']}]->(vforVendetta), + (benM)-[:ACTED_IN {roles: ['Dascomb']}]->(vforVendetta), + (jamesM)-[:DIRECTED]->(vforVendetta), + (andyW)-[:PRODUCED]->(vforVendetta), + (lanaW)-[:PRODUCED]->(vforVendetta), + (joelS)-[:PRODUCED]->(vforVendetta), + (andyW)-[:WROTE]->(vforVendetta), + (lanaW)-[:WROTE]->(vforVendetta) + + CREATE (speedRacer:Movie {title: 'Speed Racer', released: 2008, tagline: 'Speed has no limits'}) + CREATE (emileH:Person {name: 'Emile Hirsch', born: 1985}) + CREATE (johnG:Person {name: 'John Goodman', born: 1960}) + CREATE (susanS:Person {name: 'Susan Sarandon', born: 1946}) + CREATE (matthewF:Person {name: 'Matthew Fox', born: 1966}) + CREATE (christinaR:Person {name: 'Christina Ricci', born: 1980}) + CREATE (rain:Person {name: 'Rain', born: 1982}) + CREATE + (emileH)-[:ACTED_IN {roles: ['Speed Racer']}]->(speedRacer), + (johnG)-[:ACTED_IN {roles: ['Pops']}]->(speedRacer), + (susanS)-[:ACTED_IN {roles: ['Mom']}]->(speedRacer), + (matthewF)-[:ACTED_IN {roles: ['Racer X']}]->(speedRacer), + (christinaR)-[:ACTED_IN {roles: ['Trixie']}]->(speedRacer), + (rain)-[:ACTED_IN {roles: ['Taejo Togokahn']}]->(speedRacer), + (benM)-[:ACTED_IN {roles: ['Cass Jones']}]->(speedRacer), + (andyW)-[:DIRECTED]->(speedRacer), + (lanaW)-[:DIRECTED]->(speedRacer), + (andyW)-[:WROTE]->(speedRacer), + (lanaW)-[:WROTE]->(speedRacer), + (joelS)-[:PRODUCED]->(speedRacer) + + CREATE (ninjaAssassin:Movie {title: 'Ninja Assassin', released: 2009, + tagline: 'Prepare to enter a secret world of assassins'}) + CREATE (naomieH:Person {name: 'Naomie Harris'}) + CREATE + (rain)-[:ACTED_IN {roles: ['Raizo']}]->(ninjaAssassin), + (naomieH)-[:ACTED_IN {roles: ['Mika Coretti']}]->(ninjaAssassin), + (rickY)-[:ACTED_IN {roles: ['Takeshi']}]->(ninjaAssassin), + (benM)-[:ACTED_IN {roles: ['Ryan Maslow']}]->(ninjaAssassin), + (jamesM)-[:DIRECTED]->(ninjaAssassin), + (andyW)-[:PRODUCED]->(ninjaAssassin), + (lanaW)-[:PRODUCED]->(ninjaAssassin), + (joelS)-[:PRODUCED]->(ninjaAssassin) + + CREATE (theGreenMile:Movie {title: 'The Green Mile', released: 1999, + tagline: 'Walk a mile you\'ll never forget.'}) + CREATE (michaelD:Person {name: 'Michael Clarke Duncan', born: 1957}) + CREATE (davidM:Person {name: 'David Morse', born: 1953}) + CREATE (samR:Person {name: 'Sam Rockwell', born: 1968}) + CREATE (garyS:Person {name: 'Gary Sinise', born: 1955}) + CREATE (patriciaC:Person {name: 'Patricia Clarkson', born: 1959}) + CREATE (frankD:Person {name: 'Frank Darabont', born: 1959}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Paul Edgecomb']}]->(theGreenMile), + (michaelD)-[:ACTED_IN {roles: ['John Coffey']}]->(theGreenMile), + (davidM)-[:ACTED_IN {roles: ['Brutus Brutal Howell']}]->(theGreenMile), + (bonnieH)-[:ACTED_IN {roles: ['Jan Edgecomb']}]->(theGreenMile), + (jamesC)-[:ACTED_IN {roles: ['Warden Hal Moores']}]->(theGreenMile), + (samR)-[:ACTED_IN {roles: ['Wild Bill Wharton']}]->(theGreenMile), + (garyS)-[:ACTED_IN {roles: ['Burt Hammersmith']}]->(theGreenMile), + (patriciaC)-[:ACTED_IN {roles: ['Melinda Moores']}]->(theGreenMile), + (frankD)-[:DIRECTED]->(theGreenMile) + + CREATE (frostNixon:Movie {title: 'Frost/Nixon', released: 2008, + tagline: '400 million people were waiting for the truth.'}) + CREATE (frankL:Person {name: 'Frank Langella', born: 1938}) + CREATE (michaelS:Person {name: 'Michael Sheen', born: 1969}) + CREATE (oliverP:Person {name: 'Oliver Platt', born: 1960}) + CREATE + (frankL)-[:ACTED_IN {roles: ['Richard Nixon']}]->(frostNixon), + (michaelS)-[:ACTED_IN {roles: ['David Frost']}]->(frostNixon), + (kevinB)-[:ACTED_IN {roles: ['Jack Brennan']}]->(frostNixon), + (oliverP)-[:ACTED_IN {roles: ['Bob Zelnick']}]->(frostNixon), + (samR)-[:ACTED_IN {roles: ['James Reston, Jr.']}]->(frostNixon), + (ronH)-[:DIRECTED]->(frostNixon) + + CREATE (hoffa:Movie {title: 'Hoffa', released: 1992, tagline: "He didn't want law. He wanted justice."}) + CREATE (dannyD:Person {name: 'Danny DeVito', born: 1944}) + CREATE (johnR:Person {name: 'John C. Reilly', born: 1965}) + CREATE + (jackN)-[:ACTED_IN {roles: ['Hoffa']}]->(hoffa), + (dannyD)-[:ACTED_IN {roles: ['Robert Bobby Ciaro']}]->(hoffa), + (jTW)-[:ACTED_IN {roles: ['Frank Fitzsimmons']}]->(hoffa), + (johnR)-[:ACTED_IN {roles: ['Peter Connelly']}]->(hoffa), + (dannyD)-[:DIRECTED]->(hoffa) + + CREATE (apollo13:Movie {title: 'Apollo 13', released: 1995, tagline: 'Houston, we have a problem.'}) + CREATE (edH:Person {name: 'Ed Harris', born: 1950}) + CREATE (billPax:Person {name: 'Bill Paxton', born: 1955}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Jim Lovell']}]->(apollo13), + (kevinB)-[:ACTED_IN {roles: ['Jack Swigert']}]->(apollo13), + (edH)-[:ACTED_IN {roles: ['Gene Kranz']}]->(apollo13), + (billPax)-[:ACTED_IN {roles: ['Fred Haise']}]->(apollo13), + (garyS)-[:ACTED_IN {roles: ['Ken Mattingly']}]->(apollo13), + (ronH)-[:DIRECTED]->(apollo13) + + CREATE (twister:Movie {title: 'Twister', released: 1996, tagline: 'Don\'t Breathe. Don\'t Look Back.'}) + CREATE (philipH:Person {name: 'Philip Seymour Hoffman', born: 1967}) + CREATE (janB:Person {name: 'Jan de Bont', born: 1943}) + CREATE + (billPax)-[:ACTED_IN {roles: ['Bill Harding']}]->(twister), + (helenH)-[:ACTED_IN {roles: ['Dr. Jo Harding']}]->(twister), + (zachG)-[:ACTED_IN {roles: ['Eddie']}]->(twister), + (philipH)-[:ACTED_IN {roles: ['Dustin Davis']}]->(twister), + (janB)-[:DIRECTED]->(twister) + + CREATE (castAway:Movie {title: 'Cast Away', released: 2000, + tagline: 'At the edge of the world, his journey begins.'}) + CREATE (robertZ:Person {name: 'Robert Zemeckis', born: 1951}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Chuck Noland']}]->(castAway), + (helenH)-[:ACTED_IN {roles: ['Kelly Frears']}]->(castAway), + (robertZ)-[:DIRECTED]->(castAway) + + CREATE (oneFlewOvertheCuckoosNest:Movie {title: 'One Flew Over the Cuckoo\'s Nest', released: 1975, + tagline: 'If he is crazy, what does that make you?'}) + CREATE (milosF:Person {name: 'Milos Forman', born: 1932}) + CREATE + (jackN)-[:ACTED_IN {roles: ['Randle McMurphy']}]->(oneFlewOvertheCuckoosNest), + (dannyD)-[:ACTED_IN {roles: ['Martini']}]->(oneFlewOvertheCuckoosNest), + (milosF)-[:DIRECTED]->(oneFlewOvertheCuckoosNest) + + CREATE (somethingsGottaGive:Movie {title: 'Something\'s Gotta Give', released: 2003}) + CREATE (dianeK:Person {name: 'Diane Keaton', born: 1946}) + CREATE (nancyM:Person {name: 'Nancy Meyers', born: 1949}) + CREATE + (jackN)-[:ACTED_IN {roles: ['Harry Sanborn']}]->(somethingsGottaGive), + (dianeK)-[:ACTED_IN {roles: ['Erica Barry']}]->(somethingsGottaGive), + (keanu)-[:ACTED_IN {roles: ['Julian Mercer']}]->(somethingsGottaGive), + (nancyM)-[:DIRECTED]->(somethingsGottaGive), + (nancyM)-[:PRODUCED]->(somethingsGottaGive), + (nancyM)-[:WROTE]->(somethingsGottaGive) + + CREATE (bicentennialMan:Movie {title: 'Bicentennial Man', released: 1999, + tagline: 'One robot\'s 200 year journey to become an ordinary man.'}) + CREATE (chrisC:Person {name: 'Chris Columbus', born: 1958}) + CREATE + (robin)-[:ACTED_IN {roles: ['Andrew Marin']}]->(bicentennialMan), + (oliverP)-[:ACTED_IN {roles: ['Rupert Burns']}]->(bicentennialMan), + (chrisC)-[:DIRECTED]->(bicentennialMan) + + CREATE (charlieWilsonsWar:Movie {title: 'Charlie Wilson\'s War', released: 2007, + tagline: 'A stiff drink. A little mascara. A lot of nerve. Who said they could not bring down the Soviet empire.'}) + CREATE (juliaR:Person {name: 'Julia Roberts', born: 1967}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Rep. Charlie Wilson']}]->(charlieWilsonsWar), + (juliaR)-[:ACTED_IN {roles: ['Joanne Herring']}]->(charlieWilsonsWar), + (philipH)-[:ACTED_IN {roles: ['Gust Avrakotos']}]->(charlieWilsonsWar), + (mikeN)-[:DIRECTED]->(charlieWilsonsWar) + + CREATE (thePolarExpress:Movie {title: 'The Polar Express', released: 2004, + tagline: 'This Holiday Season... Believe'}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Hero Boy', 'Father', 'Conductor', 'Hobo', 'Scrooge', 'Santa Claus']}]->(thePolarExpress), + (robertZ)-[:DIRECTED]->(thePolarExpress) + + CREATE (aLeagueofTheirOwn:Movie {title: 'A League of Their Own', released: 1992, + tagline: 'A league of their own'}) + CREATE (madonna:Person {name: 'Madonna', born: 1954}) + CREATE (geenaD:Person {name: 'Geena Davis', born: 1956}) + CREATE (loriP:Person {name: 'Lori Petty', born: 1963}) + CREATE (pennyM:Person {name: 'Penny Marshall', born: 1943}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Jimmy Dugan']}]->(aLeagueofTheirOwn), + (geenaD)-[:ACTED_IN {roles: ['Dottie Hinson']}]->(aLeagueofTheirOwn), + (loriP)-[:ACTED_IN {roles: ['Kit Keller']}]->(aLeagueofTheirOwn), + (rosieO)-[:ACTED_IN {roles: ['Doris Murphy']}]->(aLeagueofTheirOwn), + (madonna)-[:ACTED_IN {roles: ['Mae Mordabito']}]->(aLeagueofTheirOwn), + (billPax)-[:ACTED_IN {roles: ['Bob Hinson']}]->(aLeagueofTheirOwn), + (pennyM)-[:DIRECTED]->(aLeagueofTheirOwn) + + CREATE (paulBlythe:Person {name: 'Paul Blythe'}) + CREATE (angelaScope:Person {name: 'Angela Scope'}) + CREATE (jessicaThompson:Person {name: 'Jessica Thompson'}) + CREATE (jamesThompson:Person {name: 'James Thompson'}) + + CREATE + (jamesThompson)-[:FOLLOWS]->(jessicaThompson), + (angelaScope)-[:FOLLOWS]->(jessicaThompson), + (paulBlythe)-[:FOLLOWS]->(angelaScope) + + CREATE + (jessicaThompson)-[:REVIEWED {summary: 'An amazing journey', rating: 95}]->(cloudAtlas), + (jessicaThompson)-[:REVIEWED {summary: 'Silly, but fun', rating: 65}]->(theReplacements), + (jamesThompson)-[:REVIEWED {summary: 'The coolest football movie ever', rating: 100}]->(theReplacements), + (angelaScope)-[:REVIEWED {summary: 'Pretty funny at times', rating: 62}]->(theReplacements), + (jessicaThompson)-[:REVIEWED {summary: 'Dark, but compelling', rating: 85}]->(unforgiven), + (jessicaThompson)-[:REVIEWED {summary: 'Slapstick', rating: 45}]->(theBirdcage), + (jessicaThompson)-[:REVIEWED {summary: 'A solid romp', rating: 68}]->(theDaVinciCode), + (jamesThompson)-[:REVIEWED {summary: 'Fun, but a little far fetched', rating: 65}]->(theDaVinciCode), + (jessicaThompson)-[:REVIEWED {summary: 'You had me at Jerry', rating: 92}]->(jerryMaguire) + + """ + Then the result should be empty + And the side effects should be: + | +nodes | 171 | + | +relationships | 253 | + | +properties | 564 | + | +labels | 171 | + + Scenario: Many CREATE clauses + Given any graph + When executing query: + """ + CREATE (hf:School {name: 'Hilly Fields Technical College'}) + CREATE (hf)-[:STAFF]->(mrb:Teacher {name: 'Mr Balls'}) + CREATE (hf)-[:STAFF]->(mrspb:Teacher {name: 'Ms Packard-Bell'}) + CREATE (hf)-[:STAFF]->(mrs:Teacher {name: 'Mr Smith'}) + CREATE (hf)-[:STAFF]->(mrsa:Teacher {name: 'Mrs Adenough'}) + CREATE (hf)-[:STAFF]->(mrvdg:Teacher {name: 'Mr Van der Graaf'}) + CREATE (hf)-[:STAFF]->(msn:Teacher {name: 'Ms Noethe'}) + CREATE (hf)-[:STAFF]->(mrsn:Teacher {name: 'Mrs Noakes'}) + CREATE (hf)-[:STAFF]->(mrm:Teacher {name: 'Mr Marker'}) + CREATE (hf)-[:STAFF]->(msd:Teacher {name: 'Ms Delgado'}) + CREATE (hf)-[:STAFF]->(mrsg:Teacher {name: 'Mrs Glass'}) + CREATE (hf)-[:STAFF]->(mrf:Teacher {name: 'Mr Flint'}) + CREATE (hf)-[:STAFF]->(mrk:Teacher {name: 'Mr Kearney'}) + CREATE (hf)-[:STAFF]->(msf:Teacher {name: 'Mrs Forrester'}) + CREATE (hf)-[:STAFF]->(mrsf:Teacher {name: 'Mrs Fischer'}) + CREATE (hf)-[:STAFF]->(mrj:Teacher {name: 'Mr Jameson'}) + + CREATE (hf)-[:STUDENT]->(_001:Student {name: 'Portia Vasquez'}) + CREATE (hf)-[:STUDENT]->(_002:Student {name: 'Andrew Parks'}) + CREATE (hf)-[:STUDENT]->(_003:Student {name: 'Germane Frye'}) + CREATE (hf)-[:STUDENT]->(_004:Student {name: 'Yuli Gutierrez'}) + CREATE (hf)-[:STUDENT]->(_005:Student {name: 'Kamal Solomon'}) + CREATE (hf)-[:STUDENT]->(_006:Student {name: 'Lysandra Porter'}) + CREATE (hf)-[:STUDENT]->(_007:Student {name: 'Stella Santiago'}) + CREATE (hf)-[:STUDENT]->(_008:Student {name: 'Brenda Torres'}) + CREATE (hf)-[:STUDENT]->(_009:Student {name: 'Heidi Dunlap'}) + + CREATE (hf)-[:STUDENT]->(_010:Student {name: 'Halee Taylor'}) + CREATE (hf)-[:STUDENT]->(_011:Student {name: 'Brennan Crosby'}) + CREATE (hf)-[:STUDENT]->(_012:Student {name: 'Rooney Cook'}) + CREATE (hf)-[:STUDENT]->(_013:Student {name: 'Xavier Morrison'}) + CREATE (hf)-[:STUDENT]->(_014:Student {name: 'Zelenia Santana'}) + CREATE (hf)-[:STUDENT]->(_015:Student {name: 'Eaton Bonner'}) + CREATE (hf)-[:STUDENT]->(_016:Student {name: 'Leilani Bishop'}) + CREATE (hf)-[:STUDENT]->(_017:Student {name: 'Jamalia Pickett'}) + CREATE (hf)-[:STUDENT]->(_018:Student {name: 'Wynter Russell'}) + CREATE (hf)-[:STUDENT]->(_019:Student {name: 'Liberty Melton'}) + + CREATE (hf)-[:STUDENT]->(_020:Student {name: 'MacKensie Obrien'}) + CREATE (hf)-[:STUDENT]->(_021:Student {name: 'Oprah Maynard'}) + CREATE (hf)-[:STUDENT]->(_022:Student {name: 'Lyle Parks'}) + CREATE (hf)-[:STUDENT]->(_023:Student {name: 'Madonna Justice'}) + CREATE (hf)-[:STUDENT]->(_024:Student {name: 'Herman Frederick'}) + CREATE (hf)-[:STUDENT]->(_025:Student {name: 'Preston Stevenson'}) + CREATE (hf)-[:STUDENT]->(_026:Student {name: 'Drew Carrillo'}) + CREATE (hf)-[:STUDENT]->(_027:Student {name: 'Hamilton Woodward'}) + CREATE (hf)-[:STUDENT]->(_028:Student {name: 'Buckminster Bradley'}) + CREATE (hf)-[:STUDENT]->(_029:Student {name: 'Shea Cote'}) + + CREATE (hf)-[:STUDENT]->(_030:Student {name: 'Raymond Leonard'}) + CREATE (hf)-[:STUDENT]->(_031:Student {name: 'Gavin Branch'}) + CREATE (hf)-[:STUDENT]->(_032:Student {name: 'Kylan Powers'}) + CREATE (hf)-[:STUDENT]->(_033:Student {name: 'Hedy Bowers'}) + CREATE (hf)-[:STUDENT]->(_034:Student {name: 'Derek Church'}) + CREATE (hf)-[:STUDENT]->(_035:Student {name: 'Silas Santiago'}) + CREATE (hf)-[:STUDENT]->(_036:Student {name: 'Elton Bright'}) + CREATE (hf)-[:STUDENT]->(_037:Student {name: 'Dora Schmidt'}) + CREATE (hf)-[:STUDENT]->(_038:Student {name: 'Julian Sullivan'}) + CREATE (hf)-[:STUDENT]->(_039:Student {name: 'Willow Morton'}) + + CREATE (hf)-[:STUDENT]->(_040:Student {name: 'Blaze Hines'}) + CREATE (hf)-[:STUDENT]->(_041:Student {name: 'Felicia Tillman'}) + CREATE (hf)-[:STUDENT]->(_042:Student {name: 'Ralph Webb'}) + CREATE (hf)-[:STUDENT]->(_043:Student {name: 'Roth Gilmore'}) + CREATE (hf)-[:STUDENT]->(_044:Student {name: 'Dorothy Burgess'}) + CREATE (hf)-[:STUDENT]->(_045:Student {name: 'Lana Sandoval'}) + CREATE (hf)-[:STUDENT]->(_046:Student {name: 'Nevada Strickland'}) + CREATE (hf)-[:STUDENT]->(_047:Student {name: 'Lucian Franco'}) + CREATE (hf)-[:STUDENT]->(_048:Student {name: 'Jasper Talley'}) + CREATE (hf)-[:STUDENT]->(_049:Student {name: 'Madaline Spears'}) + + CREATE (hf)-[:STUDENT]->(_050:Student {name: 'Upton Browning'}) + CREATE (hf)-[:STUDENT]->(_051:Student {name: 'Cooper Leon'}) + CREATE (hf)-[:STUDENT]->(_052:Student {name: 'Celeste Ortega'}) + CREATE (hf)-[:STUDENT]->(_053:Student {name: 'Willa Hewitt'}) + CREATE (hf)-[:STUDENT]->(_054:Student {name: 'Rooney Bryan'}) + CREATE (hf)-[:STUDENT]->(_055:Student {name: 'Nayda Hays'}) + CREATE (hf)-[:STUDENT]->(_056:Student {name: 'Kadeem Salazar'}) + CREATE (hf)-[:STUDENT]->(_057:Student {name: 'Halee Allen'}) + CREATE (hf)-[:STUDENT]->(_058:Student {name: 'Odysseus Mayo'}) + CREATE (hf)-[:STUDENT]->(_059:Student {name: 'Kato Merrill'}) + + CREATE (hf)-[:STUDENT]->(_060:Student {name: 'Halee Juarez'}) + CREATE (hf)-[:STUDENT]->(_061:Student {name: 'Chloe Charles'}) + CREATE (hf)-[:STUDENT]->(_062:Student {name: 'Abel Montoya'}) + CREATE (hf)-[:STUDENT]->(_063:Student {name: 'Hilda Welch'}) + CREATE (hf)-[:STUDENT]->(_064:Student {name: 'Britanni Bean'}) + CREATE (hf)-[:STUDENT]->(_065:Student {name: 'Joelle Beach'}) + CREATE (hf)-[:STUDENT]->(_066:Student {name: 'Ciara Odom'}) + CREATE (hf)-[:STUDENT]->(_067:Student {name: 'Zia Williams'}) + CREATE (hf)-[:STUDENT]->(_068:Student {name: 'Darrel Bailey'}) + CREATE (hf)-[:STUDENT]->(_069:Student {name: 'Lance Mcdowell'}) + + CREATE (hf)-[:STUDENT]->(_070:Student {name: 'Clayton Bullock'}) + CREATE (hf)-[:STUDENT]->(_071:Student {name: 'Roanna Mosley'}) + CREATE (hf)-[:STUDENT]->(_072:Student {name: 'Amethyst Mcclure'}) + CREATE (hf)-[:STUDENT]->(_073:Student {name: 'Hanae Mann'}) + CREATE (hf)-[:STUDENT]->(_074:Student {name: 'Graiden Haynes'}) + CREATE (hf)-[:STUDENT]->(_075:Student {name: 'Marcia Byrd'}) + CREATE (hf)-[:STUDENT]->(_076:Student {name: 'Yoshi Joyce'}) + CREATE (hf)-[:STUDENT]->(_077:Student {name: 'Gregory Sexton'}) + CREATE (hf)-[:STUDENT]->(_078:Student {name: 'Nash Carey'}) + CREATE (hf)-[:STUDENT]->(_079:Student {name: 'Rae Stevens'}) + + CREATE (hf)-[:STUDENT]->(_080:Student {name: 'Blossom Fulton'}) + CREATE (hf)-[:STUDENT]->(_081:Student {name: 'Lev Curry'}) + CREATE (hf)-[:STUDENT]->(_082:Student {name: 'Margaret Gamble'}) + CREATE (hf)-[:STUDENT]->(_083:Student {name: 'Rylee Patterson'}) + CREATE (hf)-[:STUDENT]->(_084:Student {name: 'Harper Perkins'}) + CREATE (hf)-[:STUDENT]->(_085:Student {name: 'Kennan Murphy'}) + CREATE (hf)-[:STUDENT]->(_086:Student {name: 'Hilda Coffey'}) + CREATE (hf)-[:STUDENT]->(_087:Student {name: 'Marah Reed'}) + CREATE (hf)-[:STUDENT]->(_088:Student {name: 'Blaine Wade'}) + CREATE (hf)-[:STUDENT]->(_089:Student {name: 'Geraldine Sanders'}) + + CREATE (hf)-[:STUDENT]->(_090:Student {name: 'Kerry Rollins'}) + CREATE (hf)-[:STUDENT]->(_091:Student {name: 'Virginia Sweet'}) + CREATE (hf)-[:STUDENT]->(_092:Student {name: 'Sophia Merrill'}) + CREATE (hf)-[:STUDENT]->(_093:Student {name: 'Hedda Carson'}) + CREATE (hf)-[:STUDENT]->(_094:Student {name: 'Tamekah Charles'}) + CREATE (hf)-[:STUDENT]->(_095:Student {name: 'Knox Barton'}) + CREATE (hf)-[:STUDENT]->(_096:Student {name: 'Ariel Porter'}) + CREATE (hf)-[:STUDENT]->(_097:Student {name: 'Berk Wooten'}) + CREATE (hf)-[:STUDENT]->(_098:Student {name: 'Galena Glenn'}) + CREATE (hf)-[:STUDENT]->(_099:Student {name: 'Jolene Anderson'}) + + CREATE (hf)-[:STUDENT]->(_100:Student {name: 'Leonard Hewitt'}) + CREATE (hf)-[:STUDENT]->(_101:Student {name: 'Maris Salazar'}) + CREATE (hf)-[:STUDENT]->(_102:Student {name: 'Brian Frost'}) + CREATE (hf)-[:STUDENT]->(_103:Student {name: 'Zane Moses'}) + CREATE (hf)-[:STUDENT]->(_104:Student {name: 'Serina Finch'}) + CREATE (hf)-[:STUDENT]->(_105:Student {name: 'Anastasia Fletcher'}) + CREATE (hf)-[:STUDENT]->(_106:Student {name: 'Glenna Chapman'}) + CREATE (hf)-[:STUDENT]->(_107:Student {name: 'Mufutau Gillespie'}) + CREATE (hf)-[:STUDENT]->(_108:Student {name: 'Basil Guthrie'}) + CREATE (hf)-[:STUDENT]->(_109:Student {name: 'Theodore Marsh'}) + + CREATE (hf)-[:STUDENT]->(_110:Student {name: 'Jaime Contreras'}) + CREATE (hf)-[:STUDENT]->(_111:Student {name: 'Irma Poole'}) + CREATE (hf)-[:STUDENT]->(_112:Student {name: 'Buckminster Bender'}) + CREATE (hf)-[:STUDENT]->(_113:Student {name: 'Elton Morris'}) + CREATE (hf)-[:STUDENT]->(_114:Student {name: 'Barbara Nguyen'}) + CREATE (hf)-[:STUDENT]->(_115:Student {name: 'Tanya Kidd'}) + CREATE (hf)-[:STUDENT]->(_116:Student {name: 'Kaden Hoover'}) + CREATE (hf)-[:STUDENT]->(_117:Student {name: 'Christopher Bean'}) + CREATE (hf)-[:STUDENT]->(_118:Student {name: 'Trevor Daugherty'}) + CREATE (hf)-[:STUDENT]->(_119:Student {name: 'Rudyard Bates'}) + + CREATE (hf)-[:STUDENT]->(_120:Student {name: 'Stacy Monroe'}) + CREATE (hf)-[:STUDENT]->(_121:Student {name: 'Kieran Keller'}) + CREATE (hf)-[:STUDENT]->(_122:Student {name: 'Ivy Garrison'}) + CREATE (hf)-[:STUDENT]->(_123:Student {name: 'Miranda Haynes'}) + CREATE (hf)-[:STUDENT]->(_124:Student {name: 'Abigail Heath'}) + CREATE (hf)-[:STUDENT]->(_125:Student {name: 'Margaret Santiago'}) + CREATE (hf)-[:STUDENT]->(_126:Student {name: 'Cade Floyd'}) + CREATE (hf)-[:STUDENT]->(_127:Student {name: 'Allen Crane'}) + CREATE (hf)-[:STUDENT]->(_128:Student {name: 'Stella Gilliam'}) + CREATE (hf)-[:STUDENT]->(_129:Student {name: 'Rashad Miller'}) + + CREATE (hf)-[:STUDENT]->(_130:Student {name: 'Francis Cox'}) + CREATE (hf)-[:STUDENT]->(_131:Student {name: 'Darryl Rosario'}) + CREATE (hf)-[:STUDENT]->(_132:Student {name: 'Michael Daniels'}) + CREATE (hf)-[:STUDENT]->(_133:Student {name: 'Aretha Henderson'}) + CREATE (hf)-[:STUDENT]->(_134:Student {name: 'Roth Barrera'}) + CREATE (hf)-[:STUDENT]->(_135:Student {name: 'Yael Day'}) + CREATE (hf)-[:STUDENT]->(_136:Student {name: 'Wynter Richmond'}) + CREATE (hf)-[:STUDENT]->(_137:Student {name: 'Quyn Flowers'}) + CREATE (hf)-[:STUDENT]->(_138:Student {name: 'Yvette Marquez'}) + CREATE (hf)-[:STUDENT]->(_139:Student {name: 'Teagan Curry'}) + + CREATE (hf)-[:STUDENT]->(_140:Student {name: 'Brenden Bishop'}) + CREATE (hf)-[:STUDENT]->(_141:Student {name: 'Montana Black'}) + CREATE (hf)-[:STUDENT]->(_142:Student {name: 'Ramona Parker'}) + CREATE (hf)-[:STUDENT]->(_143:Student {name: 'Merritt Hansen'}) + CREATE (hf)-[:STUDENT]->(_144:Student {name: 'Melvin Vang'}) + CREATE (hf)-[:STUDENT]->(_145:Student {name: 'Samantha Perez'}) + CREATE (hf)-[:STUDENT]->(_146:Student {name: 'Thane Porter'}) + CREATE (hf)-[:STUDENT]->(_147:Student {name: 'Vaughan Haynes'}) + CREATE (hf)-[:STUDENT]->(_148:Student {name: 'Irma Miles'}) + CREATE (hf)-[:STUDENT]->(_149:Student {name: 'Amery Jensen'}) + + CREATE (hf)-[:STUDENT]->(_150:Student {name: 'Montana Holman'}) + CREATE (hf)-[:STUDENT]->(_151:Student {name: 'Kimberly Langley'}) + CREATE (hf)-[:STUDENT]->(_152:Student {name: 'Ebony Bray'}) + CREATE (hf)-[:STUDENT]->(_153:Student {name: 'Ishmael Pollard'}) + CREATE (hf)-[:STUDENT]->(_154:Student {name: 'Illana Thompson'}) + CREATE (hf)-[:STUDENT]->(_155:Student {name: 'Rhona Bowers'}) + CREATE (hf)-[:STUDENT]->(_156:Student {name: 'Lilah Dotson'}) + CREATE (hf)-[:STUDENT]->(_157:Student {name: 'Shelly Roach'}) + CREATE (hf)-[:STUDENT]->(_158:Student {name: 'Celeste Woodward'}) + CREATE (hf)-[:STUDENT]->(_159:Student {name: 'Christen Lynn'}) + + CREATE (hf)-[:STUDENT]->(_160:Student {name: 'Miranda Slater'}) + CREATE (hf)-[:STUDENT]->(_161:Student {name: 'Lunea Clements'}) + CREATE (hf)-[:STUDENT]->(_162:Student {name: 'Lester Francis'}) + CREATE (hf)-[:STUDENT]->(_163:Student {name: 'David Fischer'}) + CREATE (hf)-[:STUDENT]->(_164:Student {name: 'Kyra Bean'}) + CREATE (hf)-[:STUDENT]->(_165:Student {name: 'Imelda Alston'}) + CREATE (hf)-[:STUDENT]->(_166:Student {name: 'Finn Farrell'}) + CREATE (hf)-[:STUDENT]->(_167:Student {name: 'Kirby House'}) + CREATE (hf)-[:STUDENT]->(_168:Student {name: 'Amanda Zamora'}) + CREATE (hf)-[:STUDENT]->(_169:Student {name: 'Rina Franco'}) + + CREATE (hf)-[:STUDENT]->(_170:Student {name: 'Sonia Lane'}) + CREATE (hf)-[:STUDENT]->(_171:Student {name: 'Nora Jefferson'}) + CREATE (hf)-[:STUDENT]->(_172:Student {name: 'Colton Ortiz'}) + CREATE (hf)-[:STUDENT]->(_173:Student {name: 'Alden Munoz'}) + CREATE (hf)-[:STUDENT]->(_174:Student {name: 'Ferdinand Cline'}) + CREATE (hf)-[:STUDENT]->(_175:Student {name: 'Cynthia Prince'}) + CREATE (hf)-[:STUDENT]->(_176:Student {name: 'Asher Hurst'}) + CREATE (hf)-[:STUDENT]->(_177:Student {name: 'MacKensie Stevenson'}) + CREATE (hf)-[:STUDENT]->(_178:Student {name: 'Sydnee Sosa'}) + CREATE (hf)-[:STUDENT]->(_179:Student {name: 'Dante Callahan'}) + + CREATE (hf)-[:STUDENT]->(_180:Student {name: 'Isabella Santana'}) + CREATE (hf)-[:STUDENT]->(_181:Student {name: 'Raven Bowman'}) + CREATE (hf)-[:STUDENT]->(_182:Student {name: 'Kirby Bolton'}) + CREATE (hf)-[:STUDENT]->(_183:Student {name: 'Peter Shaffer'}) + CREATE (hf)-[:STUDENT]->(_184:Student {name: 'Fletcher Beard'}) + CREATE (hf)-[:STUDENT]->(_185:Student {name: 'Irene Lowe'}) + CREATE (hf)-[:STUDENT]->(_186:Student {name: 'Ella Talley'}) + CREATE (hf)-[:STUDENT]->(_187:Student {name: 'Jorden Kerr'}) + CREATE (hf)-[:STUDENT]->(_188:Student {name: 'Macey Delgado'}) + CREATE (hf)-[:STUDENT]->(_189:Student {name: 'Ulysses Graves'}) + + CREATE (hf)-[:STUDENT]->(_190:Student {name: 'Declan Blake'}) + CREATE (hf)-[:STUDENT]->(_191:Student {name: 'Lila Hurst'}) + CREATE (hf)-[:STUDENT]->(_192:Student {name: 'David Rasmussen'}) + CREATE (hf)-[:STUDENT]->(_193:Student {name: 'Desiree Cortez'}) + CREATE (hf)-[:STUDENT]->(_194:Student {name: 'Myles Horton'}) + CREATE (hf)-[:STUDENT]->(_195:Student {name: 'Rylee Willis'}) + CREATE (hf)-[:STUDENT]->(_196:Student {name: 'Kelsey Yates'}) + CREATE (hf)-[:STUDENT]->(_197:Student {name: 'Alika Stanton'}) + CREATE (hf)-[:STUDENT]->(_198:Student {name: 'Ria Campos'}) + CREATE (hf)-[:STUDENT]->(_199:Student {name: 'Elijah Hendricks'}) + + CREATE (hf)-[:STUDENT]->(_200:Student {name: 'Hayes House'}) + + CREATE (hf)-[:DEPARTMENT]->(md:Department {name: 'Mathematics'}) + CREATE (hf)-[:DEPARTMENT]->(sd:Department {name: 'Science'}) + CREATE (hf)-[:DEPARTMENT]->(ed:Department {name: 'Engineering'}) + + CREATE (pm:Subject {name: 'Pure Mathematics'}) + CREATE (am:Subject {name: 'Applied Mathematics'}) + CREATE (ph:Subject {name: 'Physics'}) + CREATE (ch:Subject {name: 'Chemistry'}) + CREATE (bi:Subject {name: 'Biology'}) + CREATE (es:Subject {name: 'Earth Science'}) + CREATE (me:Subject {name: 'Mechanical Engineering'}) + CREATE (ce:Subject {name: 'Chemical Engineering'}) + CREATE (se:Subject {name: 'Systems Engineering'}) + CREATE (ve:Subject {name: 'Civil Engineering'}) + CREATE (ee:Subject {name: 'Electrical Engineering'}) + + CREATE (sd)-[:CURRICULUM]->(ph) + CREATE (sd)-[:CURRICULUM]->(ch) + CREATE (sd)-[:CURRICULUM]->(bi) + CREATE (sd)-[:CURRICULUM]->(es) + CREATE (md)-[:CURRICULUM]->(pm) + CREATE (md)-[:CURRICULUM]->(am) + CREATE (ed)-[:CURRICULUM]->(me) + CREATE (ed)-[:CURRICULUM]->(se) + CREATE (ed)-[:CURRICULUM]->(ce) + CREATE (ed)-[:CURRICULUM]->(ee) + CREATE (ed)-[:CURRICULUM]->(ve) + + CREATE (ph)-[:TAUGHT_BY]->(mrb) + CREATE (ph)-[:TAUGHT_BY]->(mrk) + CREATE (ch)-[:TAUGHT_BY]->(mrk) + CREATE (ch)-[:TAUGHT_BY]->(mrsn) + CREATE (bi)-[:TAUGHT_BY]->(mrsn) + CREATE (bi)-[:TAUGHT_BY]->(mrsf) + CREATE (es)-[:TAUGHT_BY]->(msn) + CREATE (pm)-[:TAUGHT_BY]->(mrf) + CREATE (pm)-[:TAUGHT_BY]->(mrm) + CREATE (pm)-[:TAUGHT_BY]->(mrvdg) + CREATE (am)-[:TAUGHT_BY]->(mrsg) + CREATE (am)-[:TAUGHT_BY]->(mrspb) + CREATE (am)-[:TAUGHT_BY]->(mrvdg) + CREATE (me)-[:TAUGHT_BY]->(mrj) + CREATE (ce)-[:TAUGHT_BY]->(mrsa) + CREATE (se)-[:TAUGHT_BY]->(mrs) + CREATE (ve)-[:TAUGHT_BY]->(msd) + CREATE (ee)-[:TAUGHT_BY]->(mrsf) + + CREATE(_001)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_188) + CREATE(_002)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_198) + CREATE(_003)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_106) + CREATE(_004)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_029) + CREATE(_005)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_153) + CREATE(_006)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_061) + CREATE(_007)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_177) + CREATE(_008)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_115) + CREATE(_009)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_010)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_142) + CREATE(_011)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_043) + CREATE(_012)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_065) + CREATE(_013)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_074) + CREATE(_014)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_165) + CREATE(_015)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_016)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_086) + CREATE(_017)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_062) + CREATE(_018)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_033) + CREATE(_019)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_020)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_021)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_086) + CREATE(_022)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_121) + CREATE(_023)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_049) + CREATE(_024)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_152) + CREATE(_025)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_152) + CREATE(_026)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_085) + CREATE(_027)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_084) + CREATE(_028)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_143) + CREATE(_029)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_030)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_094) + CREATE(_031)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_125) + CREATE(_032)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_024) + CREATE(_033)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_075) + CREATE(_034)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_161) + CREATE(_035)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_197) + CREATE(_036)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_067) + CREATE(_037)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_049) + CREATE(_038)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_038) + CREATE(_039)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_040)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_149) + CREATE(_041)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_044) + CREATE(_042)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_043)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_095) + CREATE(_044)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_016) + CREATE(_045)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_021) + CREATE(_046)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_047)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_189) + CREATE(_048)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_094) + CREATE(_049)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_161) + CREATE(_050)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_098) + CREATE(_051)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_052)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_148) + CREATE(_053)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_054)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_055)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_175) + CREATE(_056)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_010) + CREATE(_057)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_042) + CREATE(_058)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_059)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_067) + CREATE(_060)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_034) + CREATE(_061)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_062)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_088) + CREATE(_063)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_142) + CREATE(_064)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_88) + CREATE(_065)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_066)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_178) + CREATE(_067)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_041) + CREATE(_068)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_069)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_109) + CREATE(_070)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_071)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_182) + CREATE(_072)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_144) + CREATE(_073)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_074)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_128) + CREATE(_075)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_149) + CREATE(_076)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_038) + CREATE(_077)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_104) + CREATE(_078)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_032) + CREATE(_079)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_080)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_081)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_174) + CREATE(_082)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_162) + CREATE(_083)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_011) + CREATE(_084)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_085)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_086)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_067) + CREATE(_087)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_173) + CREATE(_088)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_128) + CREATE(_089)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_177) + CREATE(_090)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_076) + CREATE(_091)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_137) + CREATE(_092)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_024) + CREATE(_093)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_156) + CREATE(_094)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_020) + CREATE(_095)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_112) + CREATE(_096)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_193) + CREATE(_097)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_006) + CREATE(_098)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_099)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_141) + CREATE(_100)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_001) + CREATE(_101)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_169) + CREATE(_102)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_161) + CREATE(_103)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_136) + CREATE(_104)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_125) + CREATE(_105)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_127) + CREATE(_106)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_095) + CREATE(_107)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_036) + CREATE(_108)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_074) + CREATE(_109)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_110)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_191) + CREATE(_111)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_068) + CREATE(_112)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_019) + CREATE(_113)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_035) + CREATE(_114)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_061) + CREATE(_115)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_070) + CREATE(_116)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_069) + CREATE(_117)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_096) + CREATE(_118)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_107) + CREATE(_119)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_120)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_167) + CREATE(_121)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_122)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_090) + CREATE(_123)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_004) + CREATE(_124)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_125)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_094) + CREATE(_126)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_174) + CREATE(_127)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_168) + CREATE(_128)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_084) + CREATE(_129)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_186) + CREATE(_130)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_090) + CREATE(_131)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_010) + CREATE(_132)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_031) + CREATE(_133)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_059) + CREATE(_134)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_037) + CREATE(_135)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_012) + CREATE(_136)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_197) + CREATE(_137)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_059) + CREATE(_138)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_065) + CREATE(_139)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_175) + CREATE(_140)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_170) + CREATE(_141)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_191) + CREATE(_142)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_139) + CREATE(_143)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_054) + CREATE(_144)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_176) + CREATE(_145)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_188) + CREATE(_146)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_072) + CREATE(_147)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_096) + CREATE(_148)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_108) + CREATE(_149)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_155) + CREATE(_150)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_151)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_076) + CREATE(_152)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_169) + CREATE(_153)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_179) + CREATE(_154)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_186) + CREATE(_155)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_058) + CREATE(_156)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_071) + CREATE(_157)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_073) + CREATE(_158)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_159)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_182) + CREATE(_160)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_199) + CREATE(_161)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_072) + CREATE(_162)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_014) + CREATE(_163)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_163) + CREATE(_164)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_038) + CREATE(_165)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_044) + CREATE(_166)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_136) + CREATE(_167)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_038) + CREATE(_168)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_110) + CREATE(_169)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_198) + CREATE(_170)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_178) + CREATE(_171)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_172)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_020) + CREATE(_173)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_164) + CREATE(_174)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_075) + CREATE(_175)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_175) + CREATE(_176)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_177)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_178)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_006) + CREATE(_179)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_180)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_185) + CREATE(_181)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_074) + CREATE(_182)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_183)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_184)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_185)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_186)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_187)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_188)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_014) + CREATE(_189)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_096) + CREATE(_190)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_063) + CREATE(_191)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_079) + CREATE(_192)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_121) + CREATE(_193)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_194)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_029) + CREATE(_195)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_164) + CREATE(_196)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_197)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_101) + CREATE(_198)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_039) + CREATE(_199)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_011) + CREATE(_200)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_073) + CREATE(_001)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_129) + CREATE(_002)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_078) + CREATE(_003)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_181) + CREATE(_004)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_162) + CREATE(_005)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_006)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_111) + CREATE(_007)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_027) + CREATE(_008)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_009)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_132) + CREATE(_010)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_147) + CREATE(_011)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_012)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_118) + CREATE(_013)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_014)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_015)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_107) + CREATE(_016)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_017)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_018)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_069) + CREATE(_019)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_024) + CREATE(_020)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_021)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_184) + CREATE(_022)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_023)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_024)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_075) + CREATE(_025)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_087) + CREATE(_026)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_163) + CREATE(_027)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_115) + CREATE(_028)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_042) + CREATE(_029)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_058) + CREATE(_030)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_188) + CREATE(_031)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_032)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_015) + CREATE(_033)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_130) + CREATE(_034)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_141) + CREATE(_035)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_158) + CREATE(_036)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_020) + CREATE(_037)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_102) + CREATE(_038)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_184) + CREATE(_039)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_040)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_041)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_042)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_050) + CREATE(_043)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_085) + CREATE(_044)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_025) + CREATE(_045)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_084) + CREATE(_046)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_118) + CREATE(_047)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_048)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_049)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_071) + CREATE(_050)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_178) + CREATE(_051)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_052)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_059) + CREATE(_053)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_095) + CREATE(_054)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_185) + CREATE(_055)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_108) + CREATE(_056)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_057)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_031) + CREATE(_058)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_054) + CREATE(_059)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_198) + CREATE(_060)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_138) + CREATE(_061)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_176) + CREATE(_062)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_086) + CREATE(_063)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_032) + CREATE(_064)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_101) + CREATE(_065)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_181) + CREATE(_066)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_153) + CREATE(_067)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_166) + CREATE(_068)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_069)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_027) + CREATE(_070)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_021) + CREATE(_071)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_193) + CREATE(_072)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_073)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_108) + CREATE(_074)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_174) + CREATE(_075)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_019) + CREATE(_076)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_179) + CREATE(_077)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_005) + CREATE(_078)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_014) + CREATE(_079)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_017) + CREATE(_080)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_146) + CREATE(_081)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_098) + CREATE(_082)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_083)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_084)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_161) + CREATE(_085)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_098) + CREATE(_086)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_199) + CREATE(_087)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_088)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_164) + CREATE(_089)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_064) + CREATE(_090)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_109) + CREATE(_091)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_077) + CREATE(_092)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_124) + CREATE(_093)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_181) + CREATE(_094)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_142) + CREATE(_095)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_191) + CREATE(_096)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_093) + CREATE(_097)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_031) + CREATE(_098)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_099)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_182) + CREATE(_100)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_043) + CREATE(_101)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_146) + CREATE(_102)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_141) + CREATE(_103)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_040) + CREATE(_104)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_199) + CREATE(_105)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_063) + CREATE(_106)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_180) + CREATE(_107)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_010) + CREATE(_108)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_122) + CREATE(_109)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_111) + CREATE(_110)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_065) + CREATE(_111)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_199) + CREATE(_112)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_135) + CREATE(_113)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_172) + CREATE(_114)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_096) + CREATE(_115)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_028) + CREATE(_116)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_109) + CREATE(_117)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_191) + CREATE(_118)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_169) + CREATE(_119)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_101) + CREATE(_120)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_184) + CREATE(_121)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_032) + CREATE(_122)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_127) + CREATE(_123)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_129) + CREATE(_124)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_125)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_126)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_175) + CREATE(_127)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_018) + CREATE(_128)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_165) + CREATE(_129)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_130)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_066) + CREATE(_131)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_050) + CREATE(_132)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_197) + CREATE(_133)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_111) + CREATE(_134)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_125) + CREATE(_135)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_112) + CREATE(_136)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_173) + CREATE(_137)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_181) + CREATE(_138)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_072) + CREATE(_139)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_115) + CREATE(_140)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_013) + CREATE(_141)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_142)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_143)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_144) + CREATE(_144)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_145)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_015) + CREATE(_146)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_061) + CREATE(_147)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_009) + CREATE(_148)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_149)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_176) + CREATE(_150)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_152) + CREATE(_151)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_055) + CREATE(_152)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_157) + CREATE(_153)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_090) + CREATE(_154)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_162) + CREATE(_155)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_146) + CREATE(_156)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_073) + CREATE(_157)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_044) + CREATE(_158)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_154) + CREATE(_159)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_160)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_168) + CREATE(_161)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_122) + CREATE(_162)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_015) + CREATE(_163)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_041) + CREATE(_164)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_087) + CREATE(_165)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_104) + CREATE(_166)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_167)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_019) + CREATE(_168)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_021) + CREATE(_169)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_065) + CREATE(_170)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_183) + CREATE(_171)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_147) + CREATE(_172)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_173)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_172) + CREATE(_174)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_137) + CREATE(_175)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_176)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_138) + CREATE(_177)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_078) + CREATE(_178)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_176) + CREATE(_179)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_062) + CREATE(_180)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_181)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_178) + CREATE(_182)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_173) + CREATE(_183)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_107) + CREATE(_184)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_198) + CREATE(_185)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_186)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_041) + CREATE(_187)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_076) + CREATE(_188)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_132) + CREATE(_189)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_093) + CREATE(_190)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_191)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_183) + CREATE(_192)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_193)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_194)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_195)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_054) + CREATE(_196)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_197) + CREATE(_197)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_086) + CREATE(_198)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_190) + CREATE(_199)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_143) + CREATE(_200)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_144) + CREATE(_001)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_050) + CREATE(_002)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_024) + CREATE(_003)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_135) + CREATE(_004)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_094) + CREATE(_005)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_143) + CREATE(_006)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_066) + CREATE(_007)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_193) + CREATE(_008)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_009)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_074) + CREATE(_010)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_166) + CREATE(_011)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_012)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_036) + CREATE(_013)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_016) + CREATE(_014)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_108) + CREATE(_015)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_016)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_017)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_016) + CREATE(_018)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_130) + CREATE(_019)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_013) + CREATE(_020)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_186) + CREATE(_021)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_026) + CREATE(_022)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_040) + CREATE(_023)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_064) + CREATE(_024)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_072) + CREATE(_025)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_017) + CREATE(_026)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_159) + CREATE(_027)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_076) + CREATE(_028)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_014) + CREATE(_029)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_089) + CREATE(_030)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_157) + CREATE(_031)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_029) + CREATE(_032)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_184) + CREATE(_033)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_034)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_035)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_051) + CREATE(_036)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_031) + CREATE(_037)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_038)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_039)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_023) + CREATE(_040)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_109) + CREATE(_041)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_177) + CREATE(_042)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_020) + CREATE(_043)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_069) + CREATE(_044)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_068) + CREATE(_045)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_027) + CREATE(_046)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_018) + CREATE(_047)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_154) + CREATE(_048)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_090) + CREATE(_049)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_166) + CREATE(_050)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_051)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_052)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_053)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_160) + CREATE(_054)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_088) + CREATE(_055)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_056)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_057)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_110) + CREATE(_058)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_060) + CREATE(_059)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_084) + CREATE(_060)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_030) + CREATE(_061)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_170) + CREATE(_062)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_027) + CREATE(_063)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_018) + CREATE(_064)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_004) + CREATE(_065)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_138) + CREATE(_066)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_009) + CREATE(_067)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_172) + CREATE(_068)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_077) + CREATE(_069)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_112) + CREATE(_070)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_069) + CREATE(_071)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_018) + CREATE(_072)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_172) + CREATE(_073)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_053) + CREATE(_074)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_098) + CREATE(_075)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_068) + CREATE(_076)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_132) + CREATE(_077)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_134) + CREATE(_078)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_138) + CREATE(_079)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_080)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_125) + CREATE(_081)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_129) + CREATE(_082)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_048) + CREATE(_083)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_084)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_101) + CREATE(_085)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_086)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_011) + CREATE(_087)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_088)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_070) + CREATE(_089)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_008) + CREATE(_090)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_107) + CREATE(_091)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_092)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_180) + CREATE(_093)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_001) + CREATE(_094)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_095)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_135) + CREATE(_096)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_097)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_098)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_122) + CREATE(_099)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_100) + CREATE(_100)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_130) + """ + Then the result should be empty + And the side effects should be: + | +nodes | 731 | + | +relationships | 1247 | + | +labels | 730 | + | +properties | 230 | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/LargeIntegerEquality.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/LargeIntegerEquality.feature new file mode 100644 index 000000000..a69cce5f8 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/LargeIntegerEquality.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ListComprehension.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ListComprehension.feature new file mode 100644 index 000000000..2d2e4a921 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ListComprehension.feature @@ -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 + diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/Literals.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/Literals.feature new file mode 100644 index 000000000..3535c2d36 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/Literals.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MatchAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MatchAcceptance.feature new file mode 100644 index 000000000..896df709d --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MatchAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MatchAcceptance2.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MatchAcceptance2.feature new file mode 100644 index 000000000..54d2df647 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MatchAcceptance2.feature @@ -0,0 +1,1845 @@ +# +# 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: MatchAcceptance2 + + Scenario: Do not return non-existent nodes + Given an empty graph + When executing query: + """ + MATCH (n) + RETURN n + """ + Then the result should be: + | n | + And no side effects + + Scenario: Do not return non-existent relationships + Given an empty graph + When executing query: + """ + MATCH ()-[r]->() + RETURN r + """ + Then the result should be: + | r | + And no side effects + + Scenario: Do not fail when evaluating predicates with illegal operations if the AND'ed predicate evaluates to false + Given an empty graph + And having executed: + """ + CREATE (root:Root {name: 'x'}), + (child1:TextNode {id: 'text'}), + (child2:IntNode {id: 0}) + CREATE (root)-[:T]->(child1), + (root)-[:T]->(child2) + """ + When executing query: + """ + MATCH (:Root {name: 'x'})-->(i:TextNode) + WHERE i.id > 'te' + RETURN i + """ + Then the result should be: + | i | + | (:TextNode {id: 'text'}) | + And no side effects + + Scenario: Do not fail when evaluating predicates with illegal operations if the OR'd predicate evaluates to true + Given an empty graph + And having executed: + """ + CREATE (root:Root {name: 'x'}), + (child1:TextNode {id: 'text'}), + (child2:IntNode {id: 0}) + CREATE (root)-[:T]->(child1), + (root)-[:T]->(child2) + """ + When executing query: + """ + MATCH (:Root {name: 'x'})-->(i) + WHERE exists(i.id) OR i.id > 'te' + RETURN i + """ + Then the result should be: + | i | + | (:TextNode {id: 'text'}) | + | (:IntNode {id: 0}) | + And no side effects + + Scenario: Aggregation with named paths + Given an empty graph + And having executed: + """ + CREATE (n1 {num: 1}), (n2 {num: 2}), + (n3 {num: 3}), (n4 {num: 4}) + CREATE (n1)-[:T]->(n2), + (n3)-[:T]->(n4) + """ + When executing query: + """ + MATCH p = ()-[*]->() + WITH count(*) AS count, p AS p + WITH nodes(p) AS nodes + RETURN * + """ + Then the result should be: + | nodes | + | [({num: 1}), ({num: 2})] | + | [({num: 3}), ({num: 4})] | + And no side effects + + Scenario: Zero-length variable length pattern in the middle of the pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}), ({name: 'D'}), + ({name: 'E'}) + CREATE (a)-[:CONTAINS]->(b), + (b)-[:FRIEND]->(c) + """ + When executing query: + """ + MATCH (a {name: 'A'})-[:CONTAINS*0..1]->(b)-[:FRIEND*0..1]->(c) + RETURN a, b, c + """ + Then the result should be: + | a | b | c | + | ({name: 'A'}) | ({name: 'A'}) | ({name: 'A'}) | + | ({name: 'A'}) | ({name: 'B'}) | ({name: 'B'}) | + | ({name: 'A'}) | ({name: 'B'}) | ({name: 'C'}) | + And no side effects + + Scenario: Simple variable length pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}), (d {name: 'D'}) + CREATE (a)-[:CONTAINS]->(b), + (b)-[:CONTAINS]->(c), + (c)-[:CONTAINS]->(d) + """ + When executing query: + """ + MATCH (a {name: 'A'})-[*]->(x) + RETURN x + """ + Then the result should be: + | x | + | ({name: 'B'}) | + | ({name: 'C'}) | + | ({name: 'D'}) | + And no side effects + + Scenario: Variable length relationship without lower bound + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b), + (b)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH p = ({name: 'A'})-[:KNOWS*..2]->() + RETURN p + """ + Then the result should be: + | p | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})> | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})-[:KNOWS]->({name: 'C'})> | + And no side effects + + Scenario: Variable length relationship without bounds + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b), + (b)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH p = ({name: 'A'})-[:KNOWS*..]->() + RETURN p + """ + Then the result should be: + | p | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})> | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})-[:KNOWS]->({name: 'C'})> | + And no side effects + + Scenario: Returning bound nodes that are not part of the pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (c {name: 'C'}) + MATCH (a)-->(b) + RETURN a, b, c + """ + Then the result should be: + | a | b | c | + | ({name: 'A'}) | ({name: 'B'}) | ({name: 'C'}) | + And no side effects + + Scenario: Two bound nodes pointing to the same node + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (x1 {name: 'x1'}), (x2 {name: 'x2'}) + CREATE (a)-[:KNOWS]->(x1), + (a)-[:KNOWS]->(x2), + (b)-[:KNOWS]->(x1), + (b)-[:KNOWS]->(x2) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (b {name: 'B'}) + MATCH (a)-->(x)<-->(b) + RETURN x + """ + Then the result should be: + | x | + | ({name: 'x1'}) | + | ({name: 'x2'}) | + And no side effects + + Scenario: Three bound nodes pointing to the same node + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}), + (x1 {name: 'x1'}), (x2 {name: 'x2'}) + CREATE (a)-[:KNOWS]->(x1), + (a)-[:KNOWS]->(x2), + (b)-[:KNOWS]->(x1), + (b)-[:KNOWS]->(x2), + (c)-[:KNOWS]->(x1), + (c)-[:KNOWS]->(x2) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + MATCH (a)-->(x), (b)-->(x), (c)-->(x) + RETURN x + """ + Then the result should be: + | x | + | ({name: 'x1'}) | + | ({name: 'x2'}) | + And no side effects + + Scenario: Three bound nodes pointing to the same node with extra connections + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}), + (d {name: 'd'}), (e {name: 'e'}), (f {name: 'f'}), + (g {name: 'g'}), (h {name: 'h'}), (i {name: 'i'}), + (j {name: 'j'}), (k {name: 'k'}) + CREATE (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e), + (a)-[:KNOWS]->(f), + (a)-[:KNOWS]->(g), + (a)-[:KNOWS]->(i), + (b)-[:KNOWS]->(d), + (b)-[:KNOWS]->(e), + (b)-[:KNOWS]->(f), + (b)-[:KNOWS]->(h), + (b)-[:KNOWS]->(k), + (c)-[:KNOWS]->(d), + (c)-[:KNOWS]->(e), + (c)-[:KNOWS]->(h), + (c)-[:KNOWS]->(g), + (c)-[:KNOWS]->(j) + """ + When executing query: + """ + MATCH (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}) + MATCH (a)-->(x), (b)-->(x), (c)-->(x) + RETURN x + """ + Then the result should be: + | x | + | ({name: 'd'}) | + | ({name: 'e'}) | + And no side effects + + Scenario: MATCH with OPTIONAL MATCH in longer pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b), + (b)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH (a {name: 'A'}) + OPTIONAL MATCH (a)-[:KNOWS]->()-[:KNOWS]->(foo) + RETURN foo + """ + Then the result should be: + | foo | + | ({name: 'C'}) | + And no side effects + + Scenario: Optionally matching named paths + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + CREATE (a)-[:X]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (x) + WHERE x.name IN ['B', 'C'] + OPTIONAL MATCH p = (a)-->(x) + RETURN x, p + """ + Then the result should be: + | x | p | + | ({name: 'B'}) | <({name: 'A'})-[:X]->({name: 'B'})> | + | ({name: 'C'}) | null | + And no side effects + + Scenario: Optionally matching named paths with single and variable length patterns + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}) + CREATE (a)-[:X]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}) + OPTIONAL MATCH p = (a)-->(b)-[*]->(c) + RETURN p + """ + Then the result should be: + | p | + | null | + And no side effects + + Scenario: Optionally matching named paths with variable length patterns + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + CREATE (a)-[:X]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (x) + WHERE x.name IN ['B', 'C'] + OPTIONAL MATCH p = (a)-[r*]->(x) + RETURN r, x, p + """ + Then the result should be: + | r | x | p | + | [[:X]] | ({name: 'B'}) | <({name: 'A'})-[:X]->({name: 'B'})> | + | null | ({name: 'C'}) | null | + And no side effects + + Scenario: Matching variable length patterns from a bound node + Given an empty graph + And having executed: + """ + CREATE (a:A), (b), (c) + CREATE (a)-[:X]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH (a:A) + MATCH (a)-[r*2]->() + RETURN r + """ + Then the result should be (ignoring element order for lists): + | r | + | [[:X], [:Y]] | + And no side effects + + Scenario: Excluding connected nodes + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B {id: 1}), (:B {id: 2}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (a:A), (other:B) + OPTIONAL MATCH (a)-[r]->(other) + WITH other WHERE r IS NULL + RETURN other + """ + Then the result should be: + | other | + | (:B {id: 2}) | + And no side effects + + Scenario: Do not fail when predicates on optionally matched and missed nodes are invalid + Given an empty graph + And having executed: + """ + CREATE (a), (b {name: 'Mark'}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n)-->(x0) + OPTIONAL MATCH (x0)-->(x1) + WHERE x1.foo = 'bar' + RETURN x0.name + """ + Then the result should be: + | x0.name | + | 'Mark' | + And no side effects + + Scenario: MATCH and OPTIONAL MATCH on same pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b:B {name: 'B'}), (c:C {name: 'C'}) + CREATE (a)-[:T]->(b), + (a)-[:T]->(c) + """ + When executing query: + """ + MATCH (a)-->(b) + WHERE b:B + OPTIONAL MATCH (a)-->(c) + WHERE c:C + RETURN a.name + """ + Then the result should be: + | a.name | + | 'A' | + And no side effects + + Scenario: Matching using an undirected pattern + Given an empty graph + And having executed: + """ + CREATE (:A {id: 0})-[:ADMIN]->(:B {id: 1}) + """ + When executing query: + """ + MATCH (a)-[:ADMIN]-(b) + WHERE a:A + RETURN a.id, b.id + """ + Then the result should be: + | a.id | b.id | + | 0 | 1 | + And no side effects + + Scenario: Matching all nodes + Given an empty graph + And having executed: + """ + CREATE (:A), (:B) + """ + When executing query: + """ + MATCH (n) + RETURN n + """ + Then the result should be: + | n | + | (:A) | + | (:B) | + And no side effects + + Scenario: Comparing nodes for equality + Given an empty graph + And having executed: + """ + CREATE (:A), (:B) + """ + When executing query: + """ + MATCH (a), (b) + WHERE a <> b + RETURN a, b + """ + Then the result should be: + | a | b | + | (:A) | (:B) | + | (:B) | (:A) | + And no side effects + + Scenario: Matching using self-referencing pattern returns no result + Given an empty graph + And having executed: + """ + CREATE (a), (b), (c) + CREATE (a)-[:T]->(b), + (b)-[:T]->(c) + """ + When executing query: + """ + MATCH (a)-->(b), (b)-->(b) + RETURN b + """ + Then the result should be: + | b | + And no side effects + + Scenario: Variable length relationship in OPTIONAL MATCH + Given an empty graph + And having executed: + """ + CREATE (:A), (:B) + """ + When executing query: + """ + MATCH (a:A), (b:B) + OPTIONAL MATCH (a)-[r*]-(b) + WHERE r IS NULL + AND a <> b + RETURN b + """ + Then the result should be: + | b | + | (:B) | + And no side effects + + Scenario: Matching using relationship predicate with multiples of the same type + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (a)-[:T|:T]->(b) + RETURN b + """ + Then the result should be: + | b | + | (:B) | + And no side effects + + Scenario: ORDER BY with LIMIT + Given an empty graph + And having executed: + """ + CREATE (a:A), (n1 {x: 1}), (n2 {x: 2}), + (m1), (m2) + CREATE (a)-[:T]->(n1), + (n1)-[:T]->(m1), + (a)-[:T]->(n2), + (n2)-[:T]->(m2) + """ + When executing query: + """ + MATCH (a:A)-->(n)-->(m) + RETURN n.x, count(*) + ORDER BY n.x + LIMIT 1000 + """ + Then the result should be, in order: + | n.x | count(*) | + | 1 | 1 | + | 2 | 1 | + And no side effects + + Scenario: Simple node property predicate + Given an empty graph + And having executed: + """ + CREATE ({foo: 'bar'}) + """ + When executing query: + """ + MATCH (n) + WHERE n.foo = 'bar' + RETURN n + """ + Then the result should be: + | n | + | ({foo: 'bar'}) | + And no side effects + + Scenario: Handling direction of named paths + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:T]->(b:B) + """ + When executing query: + """ + MATCH p = (b)<--(a) + RETURN p + """ + Then the result should be: + | p | + | <(:B)<-[:T]-(:A)> | + And no side effects + + Scenario: Simple OPTIONAL MATCH on empty graph + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (n) + RETURN n + """ + Then the result should be: + | n | + | null | + And no side effects + + Scenario: OPTIONAL MATCH with previously bound nodes + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + OPTIONAL MATCH (n)-[:NOT_EXIST]->(x) + RETURN n, x + """ + Then the result should be: + | n | x | + | () | null | + And no side effects + + Scenario: `collect()` filtering nulls + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + OPTIONAL MATCH (n)-[:NOT_EXIST]->(x) + RETURN n, collect(x) + """ + Then the result should be: + | n | collect(x) | + | () | [] | + And no side effects + + Scenario: Multiple anonymous nodes in a pattern + Given an empty graph + And having executed: + """ + CREATE (:A) + """ + When executing query: + """ + MATCH (a)<--()<--(b)-->()-->(c) + WHERE a:A + RETURN c + """ + Then the result should be: + | c | + And no side effects + + Scenario: Matching a relationship pattern using a label predicate + Given an empty graph + And having executed: + """ + CREATE (a), (b1:Foo), (b2) + CREATE (a)-[:T]->(b1), + (a)-[:T]->(b2) + """ + When executing query: + """ + MATCH (a)-->(b:Foo) + RETURN b + """ + Then the result should be: + | b | + | (:Foo) | + And no side effects + + Scenario: Matching a relationship pattern using a label predicate on both sides + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T1]->(:B), + (:B)-[:T2]->(:A), + (:B)-[:T3]->(:B), + (:A)-[:T4]->(:A) + """ + When executing query: + """ + MATCH (:A)-[r]->(:B) + RETURN r + """ + Then the result should be: + | r | + | [:T1] | + And no side effects + + Scenario: Matching nodes using multiple labels + Given an empty graph + And having executed: + """ + CREATE (:A:B:C), (:A:B), (:A:C), (:B:C), + (:A), (:B), (:C) + """ + When executing query: + """ + MATCH (a:A:B:C) + RETURN a + """ + Then the result should be: + | a | + | (:A:B:C) | + And no side effects + + Scenario: Returning label predicate expression + Given an empty graph + And having executed: + """ + CREATE (), (:Foo) + """ + When executing query: + """ + MATCH (n) + RETURN (n:Foo) + """ + Then the result should be: + | (n:Foo) | + | true | + | false | + And no side effects + + Scenario: Matching with many predicates and larger pattern + Given an empty graph + And having executed: + """ + CREATE (advertiser {name: 'advertiser1', id: 0}), + (thing {name: 'Color', id: 1}), + (red {name: 'red'}), + (p1 {name: 'product1'}), + (p2 {name: 'product4'}) + CREATE (advertiser)-[:ADV_HAS_PRODUCT]->(p1), + (advertiser)-[:ADV_HAS_PRODUCT]->(p2), + (thing)-[:AA_HAS_VALUE]->(red), + (p1)-[:AP_HAS_VALUE]->(red), + (p2)-[:AP_HAS_VALUE]->(red) + """ + And parameters are: + | 1 | 0 | + | 2 | 1 | + When executing query: + """ + MATCH (advertiser)-[:ADV_HAS_PRODUCT]->(out)-[:AP_HAS_VALUE]->(red)<-[:AA_HAS_VALUE]-(a) + WHERE advertiser.id = $1 + AND a.id = $2 + AND red.name = 'red' + AND out.name = 'product1' + RETURN out.name + """ + Then the result should be: + | out.name | + | 'product1' | + And no side effects + + Scenario: Matching using a simple pattern with label predicate + Given an empty graph + And having executed: + """ + CREATE (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}), + (c), (d) + CREATE (a)-[:T]->(c), + (b)-[:T]->(d) + """ + When executing query: + """ + MATCH (n:Person)-->() + WHERE n.name = 'Bob' + RETURN n + """ + Then the result should be: + | n | + | (:Person {name: 'Bob'}) | + And no side effects + + Scenario: Matching disconnected patterns + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:T]->(b), + (a)-[:T]->(c) + """ + When executing query: + """ + MATCH (a)-->(b) + MATCH (c)-->(d) + RETURN a, b, c, d + """ + Then the result should be: + | a | b | c | d | + | (:A) | (:B) | (:A) | (:B) | + | (:A) | (:B) | (:A) | (:C) | + | (:A) | (:C) | (:A) | (:B) | + | (:A) | (:C) | (:A) | (:C) | + And no side effects + + Scenario: Non-optional matches should not return nulls + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B {id: 1}), (c:C {id: 2}), (d:D) + CREATE (a)-[:T]->(b), + (a)-[:T]->(c), + (a)-[:T]->(d), + (b)-[:T]->(c), + (b)-[:T]->(d), + (c)-[:T]->(d) + """ + When executing query: + """ + MATCH (a)--(b)--(c)--(d)--(a), (b)--(d) + WHERE a.id = 1 + AND c.id = 2 + RETURN d + """ + Then the result should be: + | d | + | (:A) | + | (:D) | + And no side effects + + Scenario: Handling cyclic patterns + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}) + CREATE (a)-[:A]->(b), + (b)-[:B]->(a), + (b)-[:B]->(c) + """ + When executing query: + """ + MATCH (a)-[:A]->()-[:B]->(a) + RETURN a.name + """ + Then the result should be: + | a.name | + | 'a' | + And no side effects + + Scenario: Handling cyclic patterns when separated into two parts + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}) + CREATE (a)-[:A]->(b), + (b)-[:B]->(a), + (b)-[:B]->(c) + """ + When executing query: + """ + MATCH (a)-[:A]->(b), (b)-[:B]->(a) + RETURN a.name + """ + Then the result should be: + | a.name | + | 'a' | + And no side effects + + Scenario: Handling fixed-length variable length pattern + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH (a)-[r*1..1]->(b) + RETURN r + """ + Then the result should be: + | r | + | [[:T]] | + And no side effects + + Scenario: Matching from null nodes should return no results owing to finding no matches + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (a) + WITH a + MATCH (a)-->(b) + RETURN b + """ + Then the result should be: + | b | + And no side effects + + Scenario: Matching from null nodes should return no results owing to matches being filtered out + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + OPTIONAL MATCH (a:Label) + WITH a + MATCH (a)-->(b) + RETURN b + """ + Then the result should be: + | b | + And no side effects + + Scenario: Optionally matching from null nodes should return null + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (a) + WITH a + OPTIONAL MATCH (a)-->(b) + RETURN b + """ + Then the result should be: + | b | + | null | + And no side effects + + Scenario: OPTIONAL MATCH returns null + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (a) + RETURN a + """ + Then the result should be: + | a | + | null | + And no side effects + + Scenario: Zero-length named path + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH p = (a) + RETURN p + """ + Then the result should be: + | p | + | <()> | + And no side effects + + Scenario: Variable-length named path + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH p = ()-[*0..]->() + RETURN p + """ + Then the result should be: + | p | + | <()> | + And no side effects + + Scenario: Matching with aggregation + Given an empty graph + And having executed: + """ + CREATE ({prop: 42}) + """ + When executing query: + """ + MATCH (n) + RETURN n.prop AS n, count(n) AS count + """ + Then the result should be: + | n | count | + | 42 | 1 | + And no side effects + + Scenario: Matching using a relationship that is already bound + Given an empty graph + And having executed: + """ + CREATE ()-[:T1]->(), + ()-[:T2]->() + """ + When executing query: + """ + MATCH ()-[r1]->() + WITH r1 AS r2 + MATCH ()-[r2]->() + RETURN r2 AS rel + """ + Then the result should be: + | rel | + | [:T1] | + | [:T2] | + And no side effects + + Scenario: Matching using a relationship that is already bound, in conjunction with aggregation + Given an empty graph + And having executed: + """ + CREATE ()-[:T1]->(), + ()-[:T2]->() + """ + When executing query: + """ + MATCH ()-[r1]->() + WITH r1 AS r2, count(*) AS c + ORDER BY c + MATCH ()-[r2]->() + RETURN r2 AS rel + """ + Then the result should be: + | rel | + | [:T1] | + | [:T2] | + And no side effects + + Scenario: Matching using a relationship that is already bound, in conjunction with aggregation and ORDER BY + Given an empty graph + And having executed: + """ + CREATE ()-[:T1 {id: 0}]->(), + ()-[:T2 {id: 1}]->() + """ + When executing query: + """ + MATCH (a)-[r]->(b) + WITH a, r, b, count(*) AS c + ORDER BY c + MATCH (a)-[r]->(b) + RETURN r AS rel + ORDER BY rel.id + """ + Then the result should be, in order: + | rel | + | [:T1 {id: 0}] | + | [:T2 {id: 1}] | + And no side effects + + Scenario: Matching with LIMIT and optionally matching using a relationship that is already bound + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH ()-[r]->() + WITH r + LIMIT 1 + OPTIONAL MATCH (a2)-[r]->(b2) + RETURN a2, r, b2 + """ + Then the result should be: + | a2 | r | b2 | + | (:A) | [:T] | (:B) | + And no side effects + + Scenario: Matching with LIMIT and optionally matching using a relationship and node that are both already bound + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + OPTIONAL MATCH (a1)-[r]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + | (:A) | [:T] | (:B) | + And no side effects + + Scenario: Matching with LIMIT, then matching again using a relationship and node that are both already bound along with an additional predicate + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + MATCH (a1:X)-[r]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + And no side effects + + Scenario: Matching with LIMIT and predicates, then matching again using a relationship and node that are both already bound along with a duplicate predicate + Given an empty graph + And having executed: + """ + CREATE (:X:Y)-[:T]->() + """ + When executing query: + """ + MATCH (a1:X:Y)-[r]->() + WITH r, a1 + LIMIT 1 + MATCH (a1:Y)-[r]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + | (:X:Y) | [:T] | () | + And no side effects + + Scenario: Matching twice with conflicting relationship types on same relationship + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH (a1)-[r:T]->() + WITH r, a1 + LIMIT 1 + MATCH (a1)-[r:Y]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + And no side effects + + Scenario: Matching twice with duplicate relationship types on same relationship + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r:T]->() WITH r, a1 + LIMIT 1 + MATCH (a1)-[r:T]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + | (:A) | [:T] | (:B) | + And no side effects + + Scenario: Matching relationships into a list and matching variable length using the list + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:Y]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH ()-[r1]->()-[r2]->() + WITH [r1, r2] AS rs + LIMIT 1 + MATCH (first)-[rs*]->(second) + RETURN first, second + """ + Then the result should be: + | first | second | + | (:A) | (:C) | + And no side effects + + Scenario: Matching relationships into a list and matching variable length using the list, with bound nodes + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:Y]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH (a)-[r1]->()-[r2]->(b) + WITH [r1, r2] AS rs, a AS first, b AS second + LIMIT 1 + MATCH (first)-[rs*]->(second) + RETURN first, second + """ + Then the result should be: + | first | second | + | (:A) | (:C) | + And no side effects + + Scenario: Matching relationships into a list and matching variable length using the list, with bound nodes, wrong direction + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:Y]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH (a)-[r1]->()-[r2]->(b) + WITH [r1, r2] AS rs, a AS second, b AS first + LIMIT 1 + MATCH (first)-[rs*]->(second) + RETURN first, second + """ + Then the result should be: + | first | second | + And no side effects + + Scenario: Matching and optionally matching with bound nodes in reverse direction + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + OPTIONAL MATCH (a1)<-[r]-(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + | (:A) | [:T] | null | + And no side effects + + Scenario: Matching and optionally matching with unbound nodes and equality predicate in reverse direction + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + OPTIONAL MATCH (a2)<-[r]-(b2) + WHERE a1 = a2 + RETURN a1, r, b2, a2 + """ + Then the result should be: + | a1 | r | b2 | a2 | + | (:A) | [:T] | null | null | + And no side effects + + Scenario: Fail when using property access on primitive type + Given an empty graph + And having executed: + """ + CREATE ({prop: 42}) + """ + When executing query: + """ + MATCH (n) + WITH n.prop AS n2 + RETURN n2.prop + """ + Then a TypeError should be raised at runtime: PropertyAccessOnNonMap + + Scenario: Matching and returning ordered results, with LIMIT + Given an empty graph + And having executed: + """ + CREATE ({bar: 1}), ({bar: 3}), ({bar: 2}) + """ + When executing query: + """ + MATCH (foo) + RETURN foo.bar AS x + ORDER BY x DESC + LIMIT 4 + """ + Then the result should be, in order: + | x | + | 3 | + | 2 | + | 1 | + And no side effects + + Scenario: Counting an empty graph + Given an empty graph + When executing query: + """ + MATCH (a) + RETURN count(a) > 0 + """ + Then the result should be: + | count(a) > 0 | + | false | + And no side effects + + Scenario: Matching variable length pattern with property predicate + Given an empty graph + And having executed: + """ + CREATE (a:Artist:A), (b:Artist:B), (c:Artist:C) + CREATE (a)-[:WORKED_WITH {year: 1987}]->(b), + (b)-[:WORKED_WITH {year: 1988}]->(c) + """ + When executing query: + """ + MATCH (a:Artist)-[:WORKED_WITH* {year: 1988}]->(b:Artist) + RETURN * + """ + Then the result should be: + | a | b | + | (:Artist:B) | (:Artist:C) | + And no side effects + + Scenario: Variable length pattern checking labels on endnodes + Given an empty graph + And having executed: + """ + CREATE (a:Label {id: 0}), (b:Label {id: 1}), (c:Label {id: 2}) + CREATE (a)-[:T]->(b), + (b)-[:T]->(c) + """ + When executing query: + """ + MATCH (a), (b) + WHERE a.id = 0 + AND (a)-[:T]->(b:Label) + OR (a)-[:T*]->(b:MissingLabel) + RETURN DISTINCT b + """ + Then the result should be: + | b | + | (:Label {id: 1}) | + And no side effects + + Scenario: Variable length pattern with label predicate on both sides + Given an empty graph + And having executed: + """ + CREATE (a:Blue), (b:Red), (c:Green), (d:Yellow) + CREATE (a)-[:T]->(b), + (b)-[:T]->(c), + (b)-[:T]->(d) + """ + When executing query: + """ + MATCH (a:Blue)-[r*]->(b:Green) + RETURN count(r) + """ + Then the result should be: + | count(r) | + | 1 | + And no side effects + + Scenario: Undirected named path + Given an empty graph + And having executed: + """ + CREATE (a:Movie), (b) + CREATE (b)-[:T]->(a) + """ + When executing query: + """ + MATCH p = (n:Movie)--(m) + RETURN p + LIMIT 1 + """ + Then the result should be: + | p | + | <(:Movie)<-[:T]-()> | + And no side effects + + Scenario: Named path with WITH + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH p = (a) + WITH p + RETURN p + """ + Then the result should be: + | p | + | <()> | + And no side effects + + Scenario: Named path with alternating directed/undirected relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (b)-[:T]->(a), + (c)-[:T]->(b) + """ + When executing query: + """ + MATCH p = (n)-->(m)--(o) + RETURN p + """ + Then the result should be: + | p | + | <(:C)-[:T]->(:B)-[:T]->(:A)> | + And no side effects + + Scenario: Named path with multiple alternating directed/undirected relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C), (d:D) + CREATE (b)-[:T]->(a), + (c)-[:T]->(b), + (d)-[:T]->(c) + """ + When executing query: + """ + MATCH path = (n)-->(m)--(o)--(p) + RETURN path + """ + Then the result should be: + | path | + | <(:D)-[:T]->(:C)-[:T]->(:B)-[:T]->(:A)> | + And no side effects + + Scenario: Named path with undirected fixed variable length pattern + Given an empty graph + And having executed: + """ + CREATE (db1:Start), (db2:End), (mid), (other) + CREATE (mid)-[:CONNECTED_TO]->(db1), + (mid)-[:CONNECTED_TO]->(db2), + (mid)-[:CONNECTED_TO]->(db2), + (mid)-[:CONNECTED_TO]->(other), + (mid)-[:CONNECTED_TO]->(other) + """ + When executing query: + """ + MATCH topRoute = (:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO*3..3]-(:End) + RETURN topRoute + """ + Then the result should be: + | topRoute | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + And no side effects + + Scenario: Returning a node property value + Given an empty graph + And having executed: + """ + CREATE ({prop: 1}) + """ + When executing query: + """ + MATCH (a) + RETURN a.prop + """ + Then the result should be: + | a.prop | + | 1 | + And no side effects + + Scenario: Returning a relationship property value + Given an empty graph + And having executed: + """ + CREATE ()-[:T {prop: 1}]->() + """ + When executing query: + """ + MATCH ()-[r]->() + RETURN r.prop + """ + Then the result should be: + | r.prop | + | 1 | + And no side effects + + Scenario: Projecting nodes and relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (a)-[r]->() + RETURN a AS foo, r AS bar + """ + Then the result should be: + | foo | bar | + | (:A) | [:T] | + And no side effects + + Scenario: Missing node property should become null + Given an empty graph + And having executed: + """ + CREATE ({foo: 1}) + """ + When executing query: + """ + MATCH (a) + RETURN a.bar + """ + Then the result should be: + | a.bar | + | null | + And no side effects + + Scenario: Missing relationship property should become null + Given an empty graph + And having executed: + """ + CREATE ()-[:T {foo: 1}]->() + """ + When executing query: + """ + MATCH ()-[r]->() + RETURN r.bar + """ + Then the result should be: + | r.bar | + | null | + And no side effects + + Scenario: Returning multiple node property values + Given an empty graph + And having executed: + """ + CREATE ({name: 'Philip J. Fry', age: 2046, seasons: [1, 2, 3, 4, 5, 6, 7]}) + """ + When executing query: + """ + MATCH (a) + RETURN a.name, a.age, a.seasons + """ + Then the result should be: + | a.name | a.age | a.seasons | + | 'Philip J. Fry' | 2046 | [1, 2, 3, 4, 5, 6, 7] | + And no side effects + + Scenario: Adding a property and a literal in projection + Given an empty graph + And having executed: + """ + CREATE ({prop: 1}) + """ + When executing query: + """ + MATCH (a) + RETURN a.prop + 1 AS foo + """ + Then the result should be: + | foo | + | 2 | + And no side effects + + Scenario: Adding list properties in projection + Given an empty graph + And having executed: + """ + CREATE ({prop1: [1, 2, 3], prop2: [4, 5]}) + """ + When executing query: + """ + MATCH (a) + RETURN a.prop2 + a.prop1 AS foo + """ + Then the result should be: + | foo | + | [4, 5, 1, 2, 3] | + And no side effects + + Scenario: Variable length relationship variables are lists of relationships + Given an empty graph + And having executed: + """ + CREATE (a), (b), (c) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH ()-[r*0..1]-() + RETURN last(r) AS l + """ + Then the result should be: + | l | + | [:T] | + | [:T] | + | null | + | null | + | null | + And no side effects + + Scenario: Variable length patterns and nulls + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + """ + When executing query: + """ + MATCH (a:A) + OPTIONAL MATCH (a)-[:FOO]->(b:B) + OPTIONAL MATCH (b)<-[:BAR*]-(c:B) + RETURN a, b, c + """ + Then the result should be: + | a | b | c | + | (:A) | null | null | + And no side effects + + Scenario: Projecting a list of nodes and relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n)-[r]->(m) + RETURN [n, r, m] AS r + """ + Then the result should be: + | r | + | [(:A), [:T], (:B)] | + And no side effects + + Scenario: Projecting a map of nodes and relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n)-[r]->(m) + RETURN {node1: n, rel: r, node2: m} AS m + """ + Then the result should be: + | m | + | {node1: (:A), rel: [:T], node2: (:B)} | + And no side effects + + Scenario: Respecting direction when matching existing path + Given an empty graph + And having executed: + """ + CREATE (a {prop: 'a'}), (b {prop: 'b'}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH p = ({prop: 'a'})-->({prop: 'b'}) + RETURN p + """ + Then the result should be: + | p | + | <({prop: 'a'})-[:T]->({prop: 'b'})> | + And no side effects + + Scenario: Respecting direction when matching non-existent path + Given an empty graph + And having executed: + """ + CREATE (a {prop: 'a'}), (b {prop: 'b'}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH p = ({prop: 'a'})<--({prop: 'b'}) + RETURN p + """ + Then the result should be: + | p | + And no side effects + + Scenario: Respecting direction when matching non-existent path with multiple directions + Given an empty graph + And having executed: + """ + CREATE (a), (b) + CREATE (a)-[:T]->(b), + (b)-[:T]->(a) + """ + When executing query: + """ + MATCH p = (n)-->(k)<--(n) + RETURN p + """ + Then the result should be: + | p | + And no side effects + + Scenario: Matching path with both directions should respect other directions + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T1]->(b), + (b)-[:T2]->(a) + """ + When executing query: + """ + MATCH p = (n)<-->(k)<--(n) + RETURN p + """ + Then the result should be: + | p | + | <(:A)<-[:T2]-(:B)<-[:T1]-(:A)> | + | <(:B)<-[:T1]-(:A)<-[:T2]-(:B)> | + And no side effects + + Scenario: Matching path with multiple bidirectional relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T1]->(b), + (b)-[:T2]->(a) + """ + When executing query: + """ + MATCH p=(n)<-->(k)<-->(n) + RETURN p + """ + Then the result should be: + | p | + | <(:A)<-[:T2]-(:B)<-[:T1]-(:A)> | + | <(:A)-[:T1]->(:B)-[:T2]->(:A)> | + | <(:B)<-[:T1]-(:A)<-[:T2]-(:B)> | + | <(:B)-[:T2]->(:A)-[:T1]->(:B)> | + And no side effects + + Scenario: Matching nodes with many labels + Given an empty graph + And having executed: + """ + CREATE (a:A:B:C:D:E:F:G:H:I:J:K:L:M), + (b:U:V:W:X:Y:Z) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n:A:B:C:D:E:F:G:H:I:J:K:L:M)-[:T]->(m:Z:Y:X:W:V:U) + RETURN n, m + """ + Then the result should be: + | n | m | + | (:A:B:C:D:E:F:G:H:I:J:K:L:M) | (:Z:Y:X:W:V:U) | + And no side effects + + Scenario: Matching longer variable length paths + Given an empty graph + And having executed: + """ + CREATE (a {prop: 'start'}), (b {prop: 'end'}) + WITH * + UNWIND range(1, 20) AS i + CREATE (n {prop: i}) + WITH [a] + collect(n) + [b] AS nodeList + UNWIND range(0, size(nodeList) - 2, 1) AS i + WITH nodeList[i] AS n1, nodeList[i+1] AS n2 + CREATE (n1)-[:T]->(n2) + """ + When executing query: + """ + MATCH (n {prop: 'start'})-[:T*]->(m {prop: 'end'}) + RETURN m + """ + Then the result should be: + | m | + | ({prop: 'end'}) | + And no side effects + + Scenario: Counting rows after MATCH, MERGE, OPTIONAL MATCH + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T1]->(b), + (b)-[:T2]->(a) + """ + When executing query: + """ + MATCH (a) + MERGE (b) + WITH * + OPTIONAL MATCH (a)--(b) + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 6 | + And no side effects + + Scenario: Matching a self-loop + Given an empty graph + And having executed: + """ + CREATE (a) + CREATE (a)-[:T]->(a) + """ + When executing query: + """ + MATCH ()-[r]-() + RETURN type(r) AS r + """ + Then the result should be: + | r | + | 'T' | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MergeIntoAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MergeIntoAcceptance.feature new file mode 100644 index 000000000..d4cfdc662 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MergeIntoAcceptance.feature @@ -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'] | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MergeNodeAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MergeNodeAcceptance.feature new file mode 100644 index 000000000..2f3fde3a3 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MergeNodeAcceptance.feature @@ -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 | + diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MergeRelationshipAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MergeRelationshipAcceptance.feature new file mode 100644 index 000000000..b734c9952 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MergeRelationshipAcceptance.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MiscellaneousErrorAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MiscellaneousErrorAcceptance.feature new file mode 100644 index 000000000..5246b7558 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/MiscellaneousErrorAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/NullAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/NullAcceptance.feature new file mode 100644 index 000000000..ae2112c19 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/NullAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/OptionalMatch.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/OptionalMatch.feature new file mode 100644 index 000000000..a32ff63ff --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/OptionalMatch.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/OptionalMatchAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/OptionalMatchAcceptance.feature new file mode 100644 index 000000000..3f448749d --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/OptionalMatchAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/OrderByAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/OrderByAcceptance.feature new file mode 100644 index 000000000..777679e77 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/OrderByAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/PatternComprehension.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/PatternComprehension.feature new file mode 100644 index 000000000..d1d849c31 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/PatternComprehension.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/RemoveAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/RemoveAcceptance.feature new file mode 100644 index 000000000..f890a3b2b --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/RemoveAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ReturnAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ReturnAcceptance.feature new file mode 100644 index 000000000..c8f13bc72 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ReturnAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ReturnAcceptance2.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ReturnAcceptance2.feature new file mode 100644 index 000000000..fab52d66a --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/ReturnAcceptance2.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SemanticErrorAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SemanticErrorAcceptance.feature new file mode 100644 index 000000000..3c1da45f3 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SemanticErrorAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SetAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SetAcceptance.feature new file mode 100644 index 000000000..4ca95ae21 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SetAcceptance.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SkipLimitAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SkipLimitAcceptance.feature new file mode 100644 index 000000000..1bbd22407 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SkipLimitAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/StartingPointAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/StartingPointAcceptance.feature new file mode 100644 index 000000000..30fee35c0 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/StartingPointAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/StartsWithAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/StartsWithAcceptance.feature new file mode 100644 index 000000000..ebc4ece99 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/StartsWithAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SyntaxErrorAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SyntaxErrorAcceptance.feature new file mode 100644 index 000000000..929f2a8ca --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/SyntaxErrorAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/TernaryLogicAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/TernaryLogicAcceptance.feature new file mode 100644 index 000000000..c1580e0d4 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/TernaryLogicAcceptance.feature @@ -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 | | + | rhs | | + When executing query: + """ + RETURN $lhs AND $rhs AS result + """ + Then the result should be: + | 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 | | + | rhs | | + When executing query: + """ + RETURN $lhs OR $rhs AS result + """ + Then the result should be: + | 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 | | + | rhs | | + When executing query: + """ + RETURN $lhs XOR $rhs AS result + """ + Then the result should be: + | 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 | | + | coll | | + When executing query: + """ + RETURN $elt IN $coll AS result + """ + Then the result should be: + | 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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/TriadicSelection.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/TriadicSelection.feature new file mode 100644 index 000000000..ff218e3c0 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/TriadicSelection.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/TypeConversionFunctions.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/TypeConversionFunctions.feature new file mode 100644 index 000000000..e4631681a --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/TypeConversionFunctions.feature @@ -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, ] 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, ] | 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, ] | 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, '', ] | 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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/UnionAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/UnionAcceptance.feature new file mode 100644 index 000000000..1de1fdee5 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/UnionAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/UnwindAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/UnwindAcceptance.feature new file mode 100644 index 000000000..3ade083fb --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/UnwindAcceptance.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/VarLengthAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/VarLengthAcceptance.feature new file mode 100644 index 000000000..049393b6e --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/VarLengthAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/VarLengthAcceptance2.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/VarLengthAcceptance2.feature new file mode 100644 index 000000000..a5a09ebd2 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/VarLengthAcceptance2.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/WhereAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/WhereAcceptance.feature new file mode 100644 index 000000000..cf828a7e3 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/WhereAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/features/WithAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/WithAcceptance.feature new file mode 100644 index 000000000..7d238f0e7 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/features/WithAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-1/binary-tree-1.cypher b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-1/binary-tree-1.cypher new file mode 100644 index 000000000..cd901b34c --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-1/binary-tree-1.cypher @@ -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); diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-1/binary-tree-1.json b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-1/binary-tree-1.json new file mode 100644 index 000000000..18d8c1996 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-1/binary-tree-1.json @@ -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": [] +} diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-2/binary-tree-2.cypher b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-2/binary-tree-2.cypher new file mode 100644 index 000000000..09462a35a --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-2/binary-tree-2.cypher @@ -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); diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-2/binary-tree-2.json b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-2/binary-tree-2.json new file mode 100644 index 000000000..3284d652a --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/binary-tree-2/binary-tree-2.json @@ -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": [] +} diff --git a/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/named-graphs.adoc b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/named-graphs.adoc new file mode 100644 index 000000000..2b366fd69 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M05/tck/graphs/named-graphs.adoc @@ -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. diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/AggregationAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/AggregationAcceptance.feature new file mode 100644 index 000000000..8473b6a33 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/AggregationAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ColumnNameAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ColumnNameAcceptance.feature new file mode 100644 index 000000000..22ca0d895 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ColumnNameAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/Comparability.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/Comparability.feature new file mode 100644 index 000000000..4239a26a6 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/Comparability.feature @@ -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 rhs AS result + WHERE result + RETURN lhs, rhs + """ + Then the result should be: + | lhs | rhs | + | | | + And no side effects + + Examples: + | operator | lhs | rhs | + | < | 1 | 3.14 | + | <= | 1 | 3.14 | + | >= | 3.14 | 1 | + | > | 3.14 | 1 | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ComparisonOperatorAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ComparisonOperatorAcceptance.feature new file mode 100644 index 000000000..8611cdd16 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ComparisonOperatorAcceptance.feature @@ -0,0 +1,208 @@ +# +# Copyright 2017 "Neo Technology", +# Network Engine for Objects in Lund AB (http://neotechnology.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Feature: ComparisonOperatorAcceptance + + Scenario: Handling numerical ranges 1 + Given an empty graph + And having executed: + """ + UNWIND [1, 2, 3] AS i + CREATE ({value: i}) + """ + When executing query: + """ + MATCH (n) + WHERE 1 < n.value < 3 + RETURN n.value + """ + Then the result should be: + | n.value | + | 2 | + And no side effects + + Scenario: Handling numerical ranges 2 + Given an empty graph + And having executed: + """ + UNWIND [1, 2, 3] AS i + CREATE ({value: i}) + """ + When executing query: + """ + MATCH (n) + WHERE 1 < n.value <= 3 + RETURN n.value + """ + Then the result should be: + | n.value | + | 2 | + | 3 | + And no side effects + + Scenario: Handling numerical ranges 3 + Given an empty graph + And having executed: + """ + UNWIND [1, 2, 3] AS i + CREATE ({value: i}) + """ + When executing query: + """ + MATCH (n) + WHERE 1 <= n.value < 3 + RETURN n.value + """ + Then the result should be: + | n.value | + | 1 | + | 2 | + And no side effects + + Scenario: Handling numerical ranges 4 + Given an empty graph + And having executed: + """ + UNWIND [1, 2, 3] AS i + CREATE ({value: i}) + """ + When executing query: + """ + MATCH (n) + WHERE 1 <= n.value <= 3 + RETURN n.value + """ + Then the result should be: + | n.value | + | 1 | + | 2 | + | 3 | + And no side effects + + Scenario: Handling string ranges 1 + Given an empty graph + And having executed: + """ + UNWIND ['a', 'b', 'c'] AS c + CREATE ({value: c}) + """ + When executing query: + """ + MATCH (n) + WHERE 'a' < n.value < 'c' + RETURN n.value + """ + Then the result should be: + | n.value | + | 'b' | + And no side effects + + Scenario: Handling string ranges 2 + Given an empty graph + And having executed: + """ + UNWIND ['a', 'b', 'c'] AS c + CREATE ({value: c}) + """ + When executing query: + """ + MATCH (n) + WHERE 'a' < n.value <= 'c' + RETURN n.value + """ + Then the result should be: + | n.value | + | 'b' | + | 'c' | + And no side effects + + Scenario: Handling string ranges 3 + Given an empty graph + And having executed: + """ + UNWIND ['a', 'b', 'c'] AS c + CREATE ({value: c}) + """ + When executing query: + """ + MATCH (n) + WHERE 'a' <= n.value < 'c' + RETURN n.value + """ + Then the result should be: + | n.value | + | 'a' | + | 'b' | + And no side effects + + Scenario: Handling string ranges 4 + Given an empty graph + And having executed: + """ + UNWIND ['a', 'b', 'c'] AS c + CREATE ({value: c}) + """ + When executing query: + """ + MATCH (n) + WHERE 'a' <= n.value <= 'c' + RETURN n.value + """ + Then the result should be: + | n.value | + | 'a' | + | 'b' | + | 'c' | + And no side effects + + Scenario: Handling empty range + Given an empty graph + And having executed: + """ + CREATE ({value: 3}) + """ + When executing query: + """ + MATCH (n) + WHERE 10 < n.value <= 3 + RETURN n.value + """ + Then the result should be: + | n.value | + And no side effects + + Scenario: Handling long chains of operators + Given an empty graph + And having executed: + """ + CREATE (a:A {prop1: 3, prop2: 4}) + CREATE (b:B {prop1: 4, prop2: 5}) + CREATE (c:C {prop1: 4, prop2: 4}) + CREATE (a)-[:R]->(b) + CREATE (b)-[:R]->(c) + CREATE (c)-[:R]->(a) + """ + When executing query: + """ + MATCH (n)-->(m) + WHERE n.prop1 < m.prop1 = n.prop2 <> m.prop2 + RETURN labels(m) + """ + Then the result should be: + | labels(m) | + | ['B'] | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/Create.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/Create.feature new file mode 100644 index 000000000..9738540f1 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/Create.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/CreateAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/CreateAcceptance.feature new file mode 100644 index 000000000..d3d79e06a --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/CreateAcceptance.feature @@ -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) | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/DeleteAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/DeleteAcceptance.feature new file mode 100644 index 000000000..484684784 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/DeleteAcceptance.feature @@ -0,0 +1,372 @@ +# +# Copyright 2017 "Neo Technology", +# Network Engine for Objects in Lund AB (http://neotechnology.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Feature: DeleteAcceptance + + Scenario: Delete nodes + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + DELETE n + """ + Then the result should be empty + And the side effects should be: + | -nodes | 1 | + + Scenario: Detach delete node + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + DETACH DELETE n + """ + Then the result should be empty + And the side effects should be: + | -nodes | 1 | + + Scenario: Delete relationships + Given an empty graph + And having executed: + """ + UNWIND range(0, 2) AS i + CREATE ()-[:R]->() + """ + When executing query: + """ + MATCH ()-[r]-() + DELETE r + """ + Then the result should be empty + And the side effects should be: + | -relationships | 3 | + + Scenario: Deleting connected nodes + Given an empty graph + And having executed: + """ + CREATE (x:X) + CREATE (x)-[:R]->() + CREATE (x)-[:R]->() + CREATE (x)-[:R]->() + """ + When executing query: + """ + MATCH (n:X) + DELETE n + """ + Then a ConstraintVerificationFailed should be raised at runtime: DeleteConnectedNode + + Scenario: Detach deleting connected nodes and relationships + Given an empty graph + And having executed: + """ + CREATE (x:X) + CREATE (x)-[:R]->() + CREATE (x)-[:R]->() + CREATE (x)-[:R]->() + """ + When executing query: + """ + MATCH (n:X) + DETACH DELETE n + """ + Then the result should be empty + And the side effects should be: + | -nodes | 1 | + | -relationships | 3 | + + Scenario: Detach deleting paths + Given an empty graph + And having executed: + """ + CREATE (x:X), (n1), (n2), (n3) + CREATE (x)-[:R]->(n1) + CREATE (n1)-[:R]->(n2) + CREATE (n2)-[:R]->(n3) + """ + When executing query: + """ + MATCH p = (:X)-->()-->()-->() + DETACH DELETE p + """ + Then the result should be empty + And the side effects should be: + | -nodes | 4 | + | -relationships | 3 | + + Scenario: Undirected expand followed by delete and count + Given an empty graph + And having executed: + """ + CREATE ()-[:R]->() + """ + When executing query: + """ + MATCH (a)-[r]-(b) + DELETE r, a, b + RETURN count(*) AS c + """ + Then the result should be: + | c | + | 2 | + And the side effects should be: + | -nodes | 2 | + | -relationships | 1 | + + Scenario: Undirected variable length expand followed by delete and count + Given an empty graph + And having executed: + """ + CREATE (n1), (n2), (n3) + CREATE (n1)-[:R]->(n2) + CREATE (n2)-[:R]->(n3) + """ + When executing query: + """ + MATCH (a)-[*]-(b) + DETACH DELETE a, b + RETURN count(*) AS c + """ + Then the result should be: + | c | + | 6 | + And the side effects should be: + | -nodes | 3 | + | -relationships | 2 | + + Scenario: Create and delete in same query + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH () + CREATE (n) + DELETE n + """ + Then the result should be empty + And the side effects should be: + | +nodes | 1 | + | -nodes | 1 | + + Scenario: Delete optionally matched relationship + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + OPTIONAL MATCH (n)-[r]-() + DELETE n, r + """ + Then the result should be empty + And the side effects should be: + | -nodes | 1 | + + Scenario: Delete on null node + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (n) + DELETE n + """ + Then the result should be empty + And no side effects + + Scenario: Detach delete on null node + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (n) + DETACH DELETE n + """ + Then the result should be empty + And no side effects + + Scenario: Delete on null path + Given an empty graph + When executing query: + """ + OPTIONAL MATCH p = ()-->() + DETACH DELETE p + """ + Then the result should be empty + And no side effects + + Scenario: Delete node from a list + Given an empty graph + And having executed: + """ + CREATE (u:User) + CREATE (u)-[:FRIEND]->() + CREATE (u)-[:FRIEND]->() + CREATE (u)-[:FRIEND]->() + CREATE (u)-[:FRIEND]->() + """ + And parameters are: + | friendIndex | 1 | + When executing query: + """ + MATCH (:User)-[:FRIEND]->(n) + WITH collect(n) AS friends + DETACH DELETE friends[$friendIndex] + """ + Then the result should be empty + And the side effects should be: + | -nodes | 1 | + | -relationships | 1 | + + Scenario: Delete relationship from a list + Given an empty graph + And having executed: + """ + CREATE (u:User) + CREATE (u)-[:FRIEND]->() + CREATE (u)-[:FRIEND]->() + CREATE (u)-[:FRIEND]->() + CREATE (u)-[:FRIEND]->() + """ + And parameters are: + | friendIndex | 1 | + When executing query: + """ + MATCH (:User)-[r:FRIEND]->() + WITH collect(r) AS friendships + DETACH DELETE friendships[$friendIndex] + """ + Then the result should be empty + And the side effects should be: + | -relationships | 1 | + + Scenario: Delete nodes from a map + Given an empty graph + And having executed: + """ + CREATE (:User), (:User) + """ + When executing query: + """ + MATCH (u:User) + WITH {key: u} AS nodes + DELETE nodes.key + """ + Then the result should be empty + And the side effects should be: + | -nodes | 2 | + + Scenario: Delete relationships from a map + Given an empty graph + And having executed: + """ + CREATE (a:User), (b:User) + CREATE (a)-[:R]->(b) + CREATE (b)-[:R]->(a) + """ + When executing query: + """ + MATCH (:User)-[r]->(:User) + WITH {key: r} AS rels + DELETE rels.key + """ + Then the result should be empty + And the side effects should be: + | -relationships | 2 | + + Scenario: Detach delete nodes from nested map/list + Given an empty graph + And having executed: + """ + CREATE (a:User), (b:User) + CREATE (a)-[:R]->(b) + CREATE (b)-[:R]->(a) + """ + When executing query: + """ + MATCH (u:User) + WITH {key: collect(u)} AS nodeMap + DETACH DELETE nodeMap.key[0] + """ + Then the result should be empty + And the side effects should be: + | -nodes | 1 | + | -relationships | 2 | + + Scenario: Delete relationships from nested map/list + Given an empty graph + And having executed: + """ + CREATE (a:User), (b:User) + CREATE (a)-[:R]->(b) + CREATE (b)-[:R]->(a) + """ + When executing query: + """ + MATCH (:User)-[r]->(:User) + WITH {key: {key: collect(r)}} AS rels + DELETE rels.key.key[0] + """ + Then the result should be empty + And the side effects should be: + | -relationships | 1 | + + Scenario: Delete paths from nested map/list + Given an empty graph + And having executed: + """ + CREATE (a:User), (b:User) + CREATE (a)-[:R]->(b) + CREATE (b)-[:R]->(a) + """ + When executing query: + """ + MATCH p = (:User)-[r]->(:User) + WITH {key: collect(p)} AS pathColls + DELETE pathColls.key[0], pathColls.key[1] + """ + Then the result should be empty + And the side effects should be: + | -nodes | 2 | + | -relationships | 2 | + + Scenario: Delete relationship with bidirectional matching + Given an empty graph + And having executed: + """ + CREATE ()-[:T {id: 42}]->() + """ + When executing query: + """ + MATCH p = ()-[r:T]-() + WHERE r.id = 42 + DELETE r + """ + Then the result should be empty + And the side effects should be: + | -relationships | 1 | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/EqualsAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/EqualsAcceptance.feature new file mode 100644 index 000000000..3782f9cad --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/EqualsAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ExpressionAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ExpressionAcceptance.feature new file mode 100644 index 000000000..4d75631ab --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ExpressionAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/FunctionsAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/FunctionsAcceptance.feature new file mode 100644 index 000000000..08fb52623 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/FunctionsAcceptance.feature @@ -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 AS map + RETURN exists(map.name) AS result + """ + Then the result should be: + | 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 AS map + RETURN map.name IS NOT NULL + """ + Then the result should be: + | map.name IS NOT NULL | + | | + 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 |

| + When executing query: + """ + MATCH (n) + RETURN percentileDisc(n.prop, $percentile) AS p + """ + Then the result should be: + | p | + | | + 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 |

| + When executing query: + """ + MATCH (n) + RETURN percentileCont(n.prop, $percentile) AS p + """ + Then the result should be: + | p | + | | + 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 | | + 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 | | + 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, ] | type(x) ] AS list + """ + Then a TypeError should be raised at runtime: InvalidArgumentValue + + Examples: + | invalid | + | 0 | + | 1.0 | + | true | + | '' | + | [] | + + Scenario: `labels()` should accept type Any + Given an empty graph + And having executed: + """ + CREATE (:Foo), (:Foo:Bar) + """ + When executing query: + """ + MATCH (a) + WITH [a, 1] AS list + RETURN labels(list[0]) AS l + """ + Then the result should be (ignoring element order for lists): + | l | + | ['Foo'] | + | ['Foo', 'Bar'] | + And no side effects + + Scenario: `labels()` failing on a path + Given an empty graph + And having executed: + """ + CREATE (:Foo), (:Foo:Bar) + """ + When executing query: + """ + MATCH p = (a) + RETURN labels(p) AS l + """ + Then a SyntaxError should be raised at compile time: InvalidArgumentType + + Scenario: `labels()` failing on invalid arguments + Given an empty graph + And having executed: + """ + CREATE (:Foo), (:Foo:Bar) + """ + When executing query: + """ + MATCH (a) + WITH [a, 1] AS list + RETURN labels(list[1]) AS l + """ + Then a TypeError should be raised at runtime: InvalidArgumentValue + + Scenario: `exists()` is case insensitive + Given an empty graph + And having executed: + """ + CREATE (a:X {prop: 42}), (:X) + """ + When executing query: + """ + MATCH (n:X) + RETURN n, EXIsTS(n.prop) AS b + """ + Then the result should be: + | n | b | + | (:X {prop: 42}) | true | + | (:X) | false | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/JoinAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/JoinAcceptance.feature new file mode 100644 index 000000000..4b3c944f5 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/JoinAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/KeysAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/KeysAcceptance.feature new file mode 100644 index 000000000..a376bceda --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/KeysAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/LabelsAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/LabelsAcceptance.feature new file mode 100644 index 000000000..d767e37a6 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/LabelsAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/LargeCreateQuery.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/LargeCreateQuery.feature new file mode 100644 index 000000000..767989e12 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/LargeCreateQuery.feature @@ -0,0 +1,1361 @@ +# +# 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: LargeCreateQuery + + Scenario: Generate the movie graph correctly + Given any graph + When executing query: + """ + CREATE (theMatrix:Movie {title: 'The Matrix', released: 1999, tagline: 'Welcome to the Real World'}) + CREATE (keanu:Person {name: 'Keanu Reeves', born: 1964}) + CREATE (carrie:Person {name: 'Carrie-Anne Moss', born: 1967}) + CREATE (laurence:Person {name: 'Laurence Fishburne', born: 1961}) + CREATE (hugo:Person {name: 'Hugo Weaving', born: 1960}) + CREATE (andyW:Person {name: 'Andy Wachowski', born: 1967}) + CREATE (lanaW:Person {name: 'Lana Wachowski', born: 1965}) + CREATE (joelS:Person {name: 'Joel Silver', born: 1952}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Neo']}]->(theMatrix), + (carrie)-[:ACTED_IN {roles: ['Trinity']}]->(theMatrix), + (laurence)-[:ACTED_IN {roles: ['Morpheus']}]->(theMatrix), + (hugo)-[:ACTED_IN {roles: ['Agent Smith']}]->(theMatrix), + (andyW)-[:DIRECTED]->(theMatrix), + (lanaW)-[:DIRECTED]->(theMatrix), + (joelS)-[:PRODUCED]->(theMatrix) + + CREATE (emil:Person {name: 'Emil Eifrem', born: 1978}) + CREATE (emil)-[:ACTED_IN {roles: ['Emil']}]->(theMatrix) + + CREATE (theMatrixReloaded:Movie {title: 'The Matrix Reloaded', released: 2003, + tagline: 'Free your mind'}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Neo'] }]->(theMatrixReloaded), + (carrie)-[:ACTED_IN {roles: ['Trinity']}]->(theMatrixReloaded), + (laurence)-[:ACTED_IN {roles: ['Morpheus']}]->(theMatrixReloaded), + (hugo)-[:ACTED_IN {roles: ['Agent Smith']}]->(theMatrixReloaded), + (andyW)-[:DIRECTED]->(theMatrixReloaded), + (lanaW)-[:DIRECTED]->(theMatrixReloaded), + (joelS)-[:PRODUCED]->(theMatrixReloaded) + + CREATE (theMatrixRevolutions:Movie {title: 'The Matrix Revolutions', released: 2003, + tagline: 'Everything that has a beginning has an end'}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Neo']}]->(theMatrixRevolutions), + (carrie)-[:ACTED_IN {roles: ['Trinity']}]->(theMatrixRevolutions), + (laurence)-[:ACTED_IN {roles: ['Morpheus']}]->(theMatrixRevolutions), + (hugo)-[:ACTED_IN {roles: ['Agent Smith']}]->(theMatrixRevolutions), + (andyW)-[:DIRECTED]->(theMatrixRevolutions), + (lanaW)-[:DIRECTED]->(theMatrixRevolutions), + (joelS)-[:PRODUCED]->(theMatrixRevolutions) + + CREATE (theDevilsAdvocate:Movie {title: 'The Devil\'s Advocate', released: 1997, + tagline: 'Evil has its winning ways'}) + CREATE (charlize:Person {name: 'Charlize Theron', born: 1975}) + CREATE (al:Person {name: 'Al Pacino', born: 1940}) + CREATE (taylor:Person {name: 'Taylor Hackford', born: 1944}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Kevin Lomax']}]->(theDevilsAdvocate), + (charlize)-[:ACTED_IN {roles: ['Mary Ann Lomax']}]->(theDevilsAdvocate), + (al)-[:ACTED_IN {roles: ['John Milton']}]->(theDevilsAdvocate), + (taylor)-[:DIRECTED]->(theDevilsAdvocate) + + CREATE (aFewGoodMen:Movie {title: 'A Few Good Men', released: 1992, + tagline: 'Deep within the heart of the nation\'s capital, one man will stop at nothing to keep his honor, ...'}) + CREATE (tomC:Person {name: 'Tom Cruise', born: 1962}) + CREATE (jackN:Person {name: 'Jack Nicholson', born: 1937}) + CREATE (demiM:Person {name: 'Demi Moore', born: 1962}) + CREATE (kevinB:Person {name: 'Kevin Bacon', born: 1958}) + CREATE (kieferS:Person {name: 'Kiefer Sutherland', born: 1966}) + CREATE (noahW:Person {name: 'Noah Wyle', born: 1971}) + CREATE (cubaG:Person {name: 'Cuba Gooding Jr.', born: 1968}) + CREATE (kevinP:Person {name: 'Kevin Pollak', born: 1957}) + CREATE (jTW:Person {name: 'J.T. Walsh', born: 1943}) + CREATE (jamesM:Person {name: 'James Marshall', born: 1967}) + CREATE (christopherG:Person {name: 'Christopher Guest', born: 1948}) + CREATE (robR:Person {name: 'Rob Reiner', born: 1947}) + CREATE (aaronS:Person {name: 'Aaron Sorkin', born: 1961}) + CREATE + (tomC)-[:ACTED_IN {roles: ['Lt. Daniel Kaffee']}]->(aFewGoodMen), + (jackN)-[:ACTED_IN {roles: ['Col. Nathan R. Jessup']}]->(aFewGoodMen), + (demiM)-[:ACTED_IN {roles: ['Lt. Cdr. JoAnne Galloway']}]->(aFewGoodMen), + (kevinB)-[:ACTED_IN {roles: ['Capt. Jack Ross']}]->(aFewGoodMen), + (kieferS)-[:ACTED_IN {roles: ['Lt. Jonathan Kendrick']}]->(aFewGoodMen), + (noahW)-[:ACTED_IN {roles: ['Cpl. Jeffrey Barnes']}]->(aFewGoodMen), + (cubaG)-[:ACTED_IN {roles: ['Cpl. Carl Hammaker']}]->(aFewGoodMen), + (kevinP)-[:ACTED_IN {roles: ['Lt. Sam Weinberg']}]->(aFewGoodMen), + (jTW)-[:ACTED_IN {roles: ['Lt. Col. Matthew Andrew Markinson']}]->(aFewGoodMen), + (jamesM)-[:ACTED_IN {roles: ['Pfc. Louden Downey']}]->(aFewGoodMen), + (christopherG)-[:ACTED_IN {roles: ['Dr. Stone']}]->(aFewGoodMen), + (aaronS)-[:ACTED_IN {roles: ['Bar patron']}]->(aFewGoodMen), + (robR)-[:DIRECTED]->(aFewGoodMen), + (aaronS)-[:WROTE]->(aFewGoodMen) + + CREATE (topGun:Movie {title: 'Top Gun', released: 1986, + tagline: 'I feel the need, the need for speed.'}) + CREATE (kellyM:Person {name: 'Kelly McGillis', born: 1957}) + CREATE (valK:Person {name: 'Val Kilmer', born: 1959}) + CREATE (anthonyE:Person {name: 'Anthony Edwards', born: 1962}) + CREATE (tomS:Person {name: 'Tom Skerritt', born: 1933}) + CREATE (megR:Person {name: 'Meg Ryan', born: 1961}) + CREATE (tonyS:Person {name: 'Tony Scott', born: 1944}) + CREATE (jimC:Person {name: 'Jim Cash', born: 1941}) + CREATE + (tomC)-[:ACTED_IN {roles: ['Maverick']}]->(topGun), + (kellyM)-[:ACTED_IN {roles: ['Charlie']}]->(topGun), + (valK)-[:ACTED_IN {roles: ['Iceman']}]->(topGun), + (anthonyE)-[:ACTED_IN {roles: ['Goose']}]->(topGun), + (tomS)-[:ACTED_IN {roles: ['Viper']}]->(topGun), + (megR)-[:ACTED_IN {roles: ['Carole']}]->(topGun), + (tonyS)-[:DIRECTED]->(topGun), + (jimC)-[:WROTE]->(topGun) + + CREATE (jerryMaguire:Movie {title: 'Jerry Maguire', released: 2000, + tagline: 'The rest of his life begins now.'}) + CREATE (reneeZ:Person {name: 'Renee Zellweger', born: 1969}) + CREATE (kellyP:Person {name: 'Kelly Preston', born: 1962}) + CREATE (jerryO:Person {name: 'Jerry O\'Connell', born: 1974}) + CREATE (jayM:Person {name: 'Jay Mohr', born: 1970}) + CREATE (bonnieH:Person {name: 'Bonnie Hunt', born: 1961}) + CREATE (reginaK:Person {name: 'Regina King', born: 1971}) + CREATE (jonathanL:Person {name: 'Jonathan Lipnicki', born: 1996}) + CREATE (cameronC:Person {name: 'Cameron Crowe', born: 1957}) + CREATE + (tomC)-[:ACTED_IN {roles: ['Jerry Maguire']}]->(jerryMaguire), + (cubaG)-[:ACTED_IN {roles: ['Rod Tidwell']}]->(jerryMaguire), + (reneeZ)-[:ACTED_IN {roles: ['Dorothy Boyd']}]->(jerryMaguire), + (kellyP)-[:ACTED_IN {roles: ['Avery Bishop']}]->(jerryMaguire), + (jerryO)-[:ACTED_IN {roles: ['Frank Cushman']}]->(jerryMaguire), + (jayM)-[:ACTED_IN {roles: ['Bob Sugar']}]->(jerryMaguire), + (bonnieH)-[:ACTED_IN {roles: ['Laurel Boyd']}]->(jerryMaguire), + (reginaK)-[:ACTED_IN {roles: ['Marcee Tidwell']}]->(jerryMaguire), + (jonathanL)-[:ACTED_IN {roles: ['Ray Boyd']}]->(jerryMaguire), + (cameronC)-[:DIRECTED]->(jerryMaguire), + (cameronC)-[:PRODUCED]->(jerryMaguire), + (cameronC)-[:WROTE]->(jerryMaguire) + + CREATE (standByMe:Movie {title: 'Stand-By-Me', released: 1986, + tagline: 'The last real taste of innocence'}) + CREATE (riverP:Person {name: 'River Phoenix', born: 1970}) + CREATE (coreyF:Person {name: 'Corey Feldman', born: 1971}) + CREATE (wilW:Person {name: 'Wil Wheaton', born: 1972}) + CREATE (johnC:Person {name: 'John Cusack', born: 1966}) + CREATE (marshallB:Person {name: 'Marshall Bell', born: 1942}) + CREATE + (wilW)-[:ACTED_IN {roles: ['Gordie Lachance']}]->(standByMe), + (riverP)-[:ACTED_IN {roles: ['Chris Chambers']}]->(standByMe), + (jerryO)-[:ACTED_IN {roles: ['Vern Tessio']}]->(standByMe), + (coreyF)-[:ACTED_IN {roles: ['Teddy Duchamp']}]->(standByMe), + (johnC)-[:ACTED_IN {roles: ['Denny Lachance']}]->(standByMe), + (kieferS)-[:ACTED_IN {roles: ['Ace Merrill']}]->(standByMe), + (marshallB)-[:ACTED_IN {roles: ['Mr. Lachance']}]->(standByMe), + (robR)-[:DIRECTED]->(standByMe) + + CREATE (asGoodAsItGets:Movie {title: 'As-good-as-it-gets', released: 1997, + tagline: 'A comedy from the heart that goes for the throat'}) + CREATE (helenH:Person {name: 'Helen Hunt', born: 1963}) + CREATE (gregK:Person {name: 'Greg Kinnear', born: 1963}) + CREATE (jamesB:Person {name: 'James L. Brooks', born: 1940}) + CREATE + (jackN)-[:ACTED_IN {roles: ['Melvin Udall']}]->(asGoodAsItGets), + (helenH)-[:ACTED_IN {roles: ['Carol Connelly']}]->(asGoodAsItGets), + (gregK)-[:ACTED_IN {roles: ['Simon Bishop']}]->(asGoodAsItGets), + (cubaG)-[:ACTED_IN {roles: ['Frank Sachs']}]->(asGoodAsItGets), + (jamesB)-[:DIRECTED]->(asGoodAsItGets) + + CREATE (whatDreamsMayCome:Movie {title: 'What Dreams May Come', released: 1998, + tagline: 'After life there is more. The end is just the beginning.'}) + CREATE (annabellaS:Person {name: 'Annabella Sciorra', born: 1960}) + CREATE (maxS:Person {name: 'Max von Sydow', born: 1929}) + CREATE (wernerH:Person {name: 'Werner Herzog', born: 1942}) + CREATE (robin:Person {name: 'Robin Williams', born: 1951}) + CREATE (vincentW:Person {name: 'Vincent Ward', born: 1956}) + CREATE + (robin)-[:ACTED_IN {roles: ['Chris Nielsen']}]->(whatDreamsMayCome), + (cubaG)-[:ACTED_IN {roles: ['Albert Lewis']}]->(whatDreamsMayCome), + (annabellaS)-[:ACTED_IN {roles: ['Annie Collins-Nielsen']}]->(whatDreamsMayCome), + (maxS)-[:ACTED_IN {roles: ['The Tracker']}]->(whatDreamsMayCome), + (wernerH)-[:ACTED_IN {roles: ['The Face']}]->(whatDreamsMayCome), + (vincentW)-[:DIRECTED]->(whatDreamsMayCome) + + CREATE (snowFallingonCedars:Movie {title: 'Snow-Falling-on-Cedars', released: 1999, + tagline: 'First loves last. Forever.'}) + CREATE (ethanH:Person {name: 'Ethan Hawke', born: 1970}) + CREATE (rickY:Person {name: 'Rick Yune', born: 1971}) + CREATE (jamesC:Person {name: 'James Cromwell', born: 1940}) + CREATE (scottH:Person {name: 'Scott Hicks', born: 1953}) + CREATE + (ethanH)-[:ACTED_IN {roles: ['Ishmael Chambers']}]->(snowFallingonCedars), + (rickY)-[:ACTED_IN {roles: ['Kazuo Miyamoto']}]->(snowFallingonCedars), + (maxS)-[:ACTED_IN {roles: ['Nels Gudmundsson']}]->(snowFallingonCedars), + (jamesC)-[:ACTED_IN {roles: ['Judge Fielding']}]->(snowFallingonCedars), + (scottH)-[:DIRECTED]->(snowFallingonCedars) + + CREATE (youveGotMail:Movie {title: 'You\'ve Got Mail', released: 1998, + tagline: 'At-odds-in-life, in-love-on-line'}) + CREATE (parkerP:Person {name: 'Parker Posey', born: 1968}) + CREATE (daveC:Person {name: 'Dave Chappelle', born: 1973}) + CREATE (steveZ:Person {name: 'Steve Zahn', born: 1967}) + CREATE (tomH:Person {name: 'Tom Hanks', born: 1956}) + CREATE (noraE:Person {name: 'Nora Ephron', born: 1941}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Joe Fox']}]->(youveGotMail), + (megR)-[:ACTED_IN {roles: ['Kathleen Kelly']}]->(youveGotMail), + (gregK)-[:ACTED_IN {roles: ['Frank Navasky']}]->(youveGotMail), + (parkerP)-[:ACTED_IN {roles: ['Patricia Eden']}]->(youveGotMail), + (daveC)-[:ACTED_IN {roles: ['Kevin Jackson']}]->(youveGotMail), + (steveZ)-[:ACTED_IN {roles: ['George Pappas']}]->(youveGotMail), + (noraE)-[:DIRECTED]->(youveGotMail) + + CREATE (sleeplessInSeattle:Movie {title: 'Sleepless-in-Seattle', released: 1993, + tagline: 'What if someone you never met, someone you never saw, someone you never knew was the only someone for you?'}) + CREATE (ritaW:Person {name: 'Rita Wilson', born: 1956}) + CREATE (billPull:Person {name: 'Bill Pullman', born: 1953}) + CREATE (victorG:Person {name: 'Victor Garber', born: 1949}) + CREATE (rosieO:Person {name: 'Rosie O\'Donnell', born: 1962}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Sam Baldwin']}]->(sleeplessInSeattle), + (megR)-[:ACTED_IN {roles: ['Annie Reed']}]->(sleeplessInSeattle), + (ritaW)-[:ACTED_IN {roles: ['Suzy']}]->(sleeplessInSeattle), + (billPull)-[:ACTED_IN {roles: ['Walter']}]->(sleeplessInSeattle), + (victorG)-[:ACTED_IN {roles: ['Greg']}]->(sleeplessInSeattle), + (rosieO)-[:ACTED_IN {roles: ['Becky']}]->(sleeplessInSeattle), + (noraE)-[:DIRECTED]->(sleeplessInSeattle) + + CREATE (joeVersustheVolcano:Movie {title: 'Joe-Versus-the-Volcano', released: 1990, + tagline: 'A story of love'}) + CREATE (johnS:Person {name: 'John Patrick Stanley', born: 1950}) + CREATE (nathan:Person {name: 'Nathan Lane', born: 1956}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Joe Banks']}]->(joeVersustheVolcano), + (megR)-[:ACTED_IN {roles: ['DeDe', 'Angelica Graynamore', 'Patricia Graynamore']}]->(joeVersustheVolcano), + (nathan)-[:ACTED_IN {roles: ['Baw']}]->(joeVersustheVolcano), + (johnS)-[:DIRECTED]->(joeVersustheVolcano) + + CREATE (whenHarryMetSally:Movie {title: 'When-Harry-Met-Sally', released: 1998, + tagline: 'When-Harry-Met-Sally'}) + CREATE (billyC:Person {name: 'Billy Crystal', born: 1948}) + CREATE (carrieF:Person {name: 'Carrie Fisher', born: 1956}) + CREATE (brunoK:Person {name: 'Bruno Kirby', born: 1949}) + CREATE + (billyC)-[:ACTED_IN {roles: ['Harry Burns']}]->(whenHarryMetSally), + (megR)-[:ACTED_IN {roles: ['Sally Albright']}]->(whenHarryMetSally), + (carrieF)-[:ACTED_IN {roles: ['Marie']}]->(whenHarryMetSally), + (brunoK)-[:ACTED_IN {roles: ['Jess']}]->(whenHarryMetSally), + (robR)-[:DIRECTED]->(whenHarryMetSally), + (robR)-[:PRODUCED]->(whenHarryMetSally), + (noraE)-[:PRODUCED]->(whenHarryMetSally), + (noraE)-[:WROTE]->(whenHarryMetSally) + + CREATE (thatThingYouDo:Movie {title: 'That-Thing-You-Do', released: 1996, + tagline: 'There comes a time...'}) + CREATE (livT:Person {name: 'Liv Tyler', born: 1977}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Mr. White']}]->(thatThingYouDo), + (livT)-[:ACTED_IN {roles: ['Faye Dolan']}]->(thatThingYouDo), + (charlize)-[:ACTED_IN {roles: ['Tina']}]->(thatThingYouDo), + (tomH)-[:DIRECTED]->(thatThingYouDo) + + CREATE (theReplacements:Movie {title: 'The Replacements', released: 2000, + tagline: 'Pain heals, Chicks dig scars... Glory lasts forever'}) + CREATE (brooke:Person {name: 'Brooke Langton', born: 1970}) + CREATE (gene:Person {name: 'Gene Hackman', born: 1930}) + CREATE (orlando:Person {name: 'Orlando Jones', born: 1968}) + CREATE (howard:Person {name: 'Howard Deutch', born: 1950}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Shane Falco']}]->(theReplacements), + (brooke)-[:ACTED_IN {roles: ['Annabelle Farrell']}]->(theReplacements), + (gene)-[:ACTED_IN {roles: ['Jimmy McGinty']}]->(theReplacements), + (orlando)-[:ACTED_IN {roles: ['Clifford Franklin']}]->(theReplacements), + (howard)-[:DIRECTED]->(theReplacements) + + CREATE (rescueDawn:Movie {title: 'RescueDawn', released: 2006, + tagline: 'The extraordinary true story'}) + CREATE (christianB:Person {name: 'Christian Bale', born: 1974}) + CREATE (zachG:Person {name: 'Zach Grenier', born: 1954}) + CREATE + (marshallB)-[:ACTED_IN {roles: ['Admiral']}]->(rescueDawn), + (christianB)-[:ACTED_IN {roles: ['Dieter Dengler']}]->(rescueDawn), + (zachG)-[:ACTED_IN {roles: ['Squad Leader']}]->(rescueDawn), + (steveZ)-[:ACTED_IN {roles: ['Duane']}]->(rescueDawn), + (wernerH)-[:DIRECTED]->(rescueDawn) + + CREATE (theBirdcage:Movie {title: 'The-Birdcage', released: 1996, tagline: 'Come-as-you-are'}) + CREATE (mikeN:Person {name: 'Mike Nichols', born: 1931}) + CREATE + (robin)-[:ACTED_IN {roles: ['Armand Goldman']}]->(theBirdcage), + (nathan)-[:ACTED_IN {roles: ['Albert Goldman']}]->(theBirdcage), + (gene)-[:ACTED_IN {roles: ['Sen. Kevin Keeley']}]->(theBirdcage), + (mikeN)-[:DIRECTED]->(theBirdcage) + + CREATE (unforgiven:Movie {title: 'Unforgiven', released: 1992, + tagline: 'It\'s a hell of a thing, killing a man'}) + CREATE (richardH:Person {name: 'Richard Harris', born: 1930}) + CREATE (clintE:Person {name: 'Clint Eastwood', born: 1930}) + CREATE + (richardH)-[:ACTED_IN {roles: ['English Bob']}]->(unforgiven), + (clintE)-[:ACTED_IN {roles: ['Bill Munny']}]->(unforgiven), + (gene)-[:ACTED_IN {roles: ['Little Bill Daggett']}]->(unforgiven), + (clintE)-[:DIRECTED]->(unforgiven) + + CREATE (johnnyMnemonic:Movie {title: 'Johnny-Mnemonic', released: 1995, + tagline: 'The-hottest-data-in-the-coolest-head'}) + CREATE (takeshi:Person {name: 'Takeshi Kitano', born: 1947}) + CREATE (dina:Person {name: 'Dina Meyer', born: 1968}) + CREATE (iceT:Person {name: 'Ice-T', born: 1958}) + CREATE (robertL:Person {name: 'Robert Longo', born: 1953}) + CREATE + (keanu)-[:ACTED_IN {roles: ['Johnny Mnemonic']}]->(johnnyMnemonic), + (takeshi)-[:ACTED_IN {roles: ['Takahashi']}]->(johnnyMnemonic), + (dina)-[:ACTED_IN {roles: ['Jane']}]->(johnnyMnemonic), + (iceT)-[:ACTED_IN {roles: ['J-Bone']}]->(johnnyMnemonic), + (robertL)-[:DIRECTED]->(johnnyMnemonic) + + CREATE (cloudAtlas:Movie {title: 'Cloud Atlas', released: 2012, tagline: 'Everything is connected'}) + CREATE (halleB:Person {name: 'Halle Berry', born: 1966}) + CREATE (jimB:Person {name: 'Jim Broadbent', born: 1949}) + CREATE (tomT:Person {name: 'Tom Tykwer', born: 1965}) + CREATE (davidMitchell:Person {name: 'David Mitchell', born: 1969}) + CREATE (stefanArndt:Person {name: 'Stefan Arndt', born: 1961}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Zachry', 'Dr. Henry Goose', 'Isaac Sachs', 'Dermot Hoggins']}]->(cloudAtlas), + (hugo)-[:ACTED_IN {roles: ['Bill Smoke', 'Haskell Moore', 'Tadeusz Kesselring', 'Nurse Noakes', 'Boardman Mephi', 'Old Georgie']}]->(cloudAtlas), + (halleB)-[:ACTED_IN {roles: ['Luisa Rey', 'Jocasta Ayrs', 'Ovid', 'Meronym']}]->(cloudAtlas), + (jimB)-[:ACTED_IN {roles: ['Vyvyan Ayrs', 'Captain Molyneux', 'Timothy Cavendish']}]->(cloudAtlas), + (tomT)-[:DIRECTED]->(cloudAtlas), + (andyW)-[:DIRECTED]->(cloudAtlas), + (lanaW)-[:DIRECTED]->(cloudAtlas), + (davidMitchell)-[:WROTE]->(cloudAtlas), + (stefanArndt)-[:PRODUCED]->(cloudAtlas) + + CREATE (theDaVinciCode:Movie {title: 'The Da Vinci Code', released: 2006, tagline: 'Break The Codes'}) + CREATE (ianM:Person {name: 'Ian McKellen', born: 1939}) + CREATE (audreyT:Person {name: 'Audrey Tautou', born: 1976}) + CREATE (paulB:Person {name: 'Paul Bettany', born: 1971}) + CREATE (ronH:Person {name: 'Ron Howard', born: 1954}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Dr. Robert Langdon']}]->(theDaVinciCode), + (ianM)-[:ACTED_IN {roles: ['Sir Leight Teabing']}]->(theDaVinciCode), + (audreyT)-[:ACTED_IN {roles: ['Sophie Neveu']}]->(theDaVinciCode), + (paulB)-[:ACTED_IN {roles: ['Silas']}]->(theDaVinciCode), + (ronH)-[:DIRECTED]->(theDaVinciCode) + + CREATE (vforVendetta:Movie {title: 'V for Vendetta', released: 2006, tagline: 'Freedom! Forever!'}) + CREATE (natalieP:Person {name: 'Natalie Portman', born: 1981}) + CREATE (stephenR:Person {name: 'Stephen Rea', born: 1946}) + CREATE (johnH:Person {name: 'John Hurt', born: 1940}) + CREATE (benM:Person {name: 'Ben Miles', born: 1967}) + CREATE + (hugo)-[:ACTED_IN {roles: ['V']}]->(vforVendetta), + (natalieP)-[:ACTED_IN {roles: ['Evey Hammond']}]->(vforVendetta), + (stephenR)-[:ACTED_IN {roles: ['Eric Finch']}]->(vforVendetta), + (johnH)-[:ACTED_IN {roles: ['High Chancellor Adam Sutler']}]->(vforVendetta), + (benM)-[:ACTED_IN {roles: ['Dascomb']}]->(vforVendetta), + (jamesM)-[:DIRECTED]->(vforVendetta), + (andyW)-[:PRODUCED]->(vforVendetta), + (lanaW)-[:PRODUCED]->(vforVendetta), + (joelS)-[:PRODUCED]->(vforVendetta), + (andyW)-[:WROTE]->(vforVendetta), + (lanaW)-[:WROTE]->(vforVendetta) + + CREATE (speedRacer:Movie {title: 'Speed Racer', released: 2008, tagline: 'Speed has no limits'}) + CREATE (emileH:Person {name: 'Emile Hirsch', born: 1985}) + CREATE (johnG:Person {name: 'John Goodman', born: 1960}) + CREATE (susanS:Person {name: 'Susan Sarandon', born: 1946}) + CREATE (matthewF:Person {name: 'Matthew Fox', born: 1966}) + CREATE (christinaR:Person {name: 'Christina Ricci', born: 1980}) + CREATE (rain:Person {name: 'Rain', born: 1982}) + CREATE + (emileH)-[:ACTED_IN {roles: ['Speed Racer']}]->(speedRacer), + (johnG)-[:ACTED_IN {roles: ['Pops']}]->(speedRacer), + (susanS)-[:ACTED_IN {roles: ['Mom']}]->(speedRacer), + (matthewF)-[:ACTED_IN {roles: ['Racer X']}]->(speedRacer), + (christinaR)-[:ACTED_IN {roles: ['Trixie']}]->(speedRacer), + (rain)-[:ACTED_IN {roles: ['Taejo Togokahn']}]->(speedRacer), + (benM)-[:ACTED_IN {roles: ['Cass Jones']}]->(speedRacer), + (andyW)-[:DIRECTED]->(speedRacer), + (lanaW)-[:DIRECTED]->(speedRacer), + (andyW)-[:WROTE]->(speedRacer), + (lanaW)-[:WROTE]->(speedRacer), + (joelS)-[:PRODUCED]->(speedRacer) + + CREATE (ninjaAssassin:Movie {title: 'Ninja Assassin', released: 2009, + tagline: 'Prepare to enter a secret world of assassins'}) + CREATE (naomieH:Person {name: 'Naomie Harris'}) + CREATE + (rain)-[:ACTED_IN {roles: ['Raizo']}]->(ninjaAssassin), + (naomieH)-[:ACTED_IN {roles: ['Mika Coretti']}]->(ninjaAssassin), + (rickY)-[:ACTED_IN {roles: ['Takeshi']}]->(ninjaAssassin), + (benM)-[:ACTED_IN {roles: ['Ryan Maslow']}]->(ninjaAssassin), + (jamesM)-[:DIRECTED]->(ninjaAssassin), + (andyW)-[:PRODUCED]->(ninjaAssassin), + (lanaW)-[:PRODUCED]->(ninjaAssassin), + (joelS)-[:PRODUCED]->(ninjaAssassin) + + CREATE (theGreenMile:Movie {title: 'The Green Mile', released: 1999, + tagline: 'Walk a mile you\'ll never forget.'}) + CREATE (michaelD:Person {name: 'Michael Clarke Duncan', born: 1957}) + CREATE (davidM:Person {name: 'David Morse', born: 1953}) + CREATE (samR:Person {name: 'Sam Rockwell', born: 1968}) + CREATE (garyS:Person {name: 'Gary Sinise', born: 1955}) + CREATE (patriciaC:Person {name: 'Patricia Clarkson', born: 1959}) + CREATE (frankD:Person {name: 'Frank Darabont', born: 1959}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Paul Edgecomb']}]->(theGreenMile), + (michaelD)-[:ACTED_IN {roles: ['John Coffey']}]->(theGreenMile), + (davidM)-[:ACTED_IN {roles: ['Brutus Brutal Howell']}]->(theGreenMile), + (bonnieH)-[:ACTED_IN {roles: ['Jan Edgecomb']}]->(theGreenMile), + (jamesC)-[:ACTED_IN {roles: ['Warden Hal Moores']}]->(theGreenMile), + (samR)-[:ACTED_IN {roles: ['Wild Bill Wharton']}]->(theGreenMile), + (garyS)-[:ACTED_IN {roles: ['Burt Hammersmith']}]->(theGreenMile), + (patriciaC)-[:ACTED_IN {roles: ['Melinda Moores']}]->(theGreenMile), + (frankD)-[:DIRECTED]->(theGreenMile) + + CREATE (frostNixon:Movie {title: 'Frost/Nixon', released: 2008, + tagline: '400 million people were waiting for the truth.'}) + CREATE (frankL:Person {name: 'Frank Langella', born: 1938}) + CREATE (michaelS:Person {name: 'Michael Sheen', born: 1969}) + CREATE (oliverP:Person {name: 'Oliver Platt', born: 1960}) + CREATE + (frankL)-[:ACTED_IN {roles: ['Richard Nixon']}]->(frostNixon), + (michaelS)-[:ACTED_IN {roles: ['David Frost']}]->(frostNixon), + (kevinB)-[:ACTED_IN {roles: ['Jack Brennan']}]->(frostNixon), + (oliverP)-[:ACTED_IN {roles: ['Bob Zelnick']}]->(frostNixon), + (samR)-[:ACTED_IN {roles: ['James Reston, Jr.']}]->(frostNixon), + (ronH)-[:DIRECTED]->(frostNixon) + + CREATE (hoffa:Movie {title: 'Hoffa', released: 1992, tagline: "He didn't want law. He wanted justice."}) + CREATE (dannyD:Person {name: 'Danny DeVito', born: 1944}) + CREATE (johnR:Person {name: 'John C. Reilly', born: 1965}) + CREATE + (jackN)-[:ACTED_IN {roles: ['Hoffa']}]->(hoffa), + (dannyD)-[:ACTED_IN {roles: ['Robert Bobby Ciaro']}]->(hoffa), + (jTW)-[:ACTED_IN {roles: ['Frank Fitzsimmons']}]->(hoffa), + (johnR)-[:ACTED_IN {roles: ['Peter Connelly']}]->(hoffa), + (dannyD)-[:DIRECTED]->(hoffa) + + CREATE (apollo13:Movie {title: 'Apollo 13', released: 1995, tagline: 'Houston, we have a problem.'}) + CREATE (edH:Person {name: 'Ed Harris', born: 1950}) + CREATE (billPax:Person {name: 'Bill Paxton', born: 1955}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Jim Lovell']}]->(apollo13), + (kevinB)-[:ACTED_IN {roles: ['Jack Swigert']}]->(apollo13), + (edH)-[:ACTED_IN {roles: ['Gene Kranz']}]->(apollo13), + (billPax)-[:ACTED_IN {roles: ['Fred Haise']}]->(apollo13), + (garyS)-[:ACTED_IN {roles: ['Ken Mattingly']}]->(apollo13), + (ronH)-[:DIRECTED]->(apollo13) + + CREATE (twister:Movie {title: 'Twister', released: 1996, tagline: 'Don\'t Breathe. Don\'t Look Back.'}) + CREATE (philipH:Person {name: 'Philip Seymour Hoffman', born: 1967}) + CREATE (janB:Person {name: 'Jan de Bont', born: 1943}) + CREATE + (billPax)-[:ACTED_IN {roles: ['Bill Harding']}]->(twister), + (helenH)-[:ACTED_IN {roles: ['Dr. Jo Harding']}]->(twister), + (zachG)-[:ACTED_IN {roles: ['Eddie']}]->(twister), + (philipH)-[:ACTED_IN {roles: ['Dustin Davis']}]->(twister), + (janB)-[:DIRECTED]->(twister) + + CREATE (castAway:Movie {title: 'Cast Away', released: 2000, + tagline: 'At the edge of the world, his journey begins.'}) + CREATE (robertZ:Person {name: 'Robert Zemeckis', born: 1951}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Chuck Noland']}]->(castAway), + (helenH)-[:ACTED_IN {roles: ['Kelly Frears']}]->(castAway), + (robertZ)-[:DIRECTED]->(castAway) + + CREATE (oneFlewOvertheCuckoosNest:Movie {title: 'One Flew Over the Cuckoo\'s Nest', released: 1975, + tagline: 'If he is crazy, what does that make you?'}) + CREATE (milosF:Person {name: 'Milos Forman', born: 1932}) + CREATE + (jackN)-[:ACTED_IN {roles: ['Randle McMurphy']}]->(oneFlewOvertheCuckoosNest), + (dannyD)-[:ACTED_IN {roles: ['Martini']}]->(oneFlewOvertheCuckoosNest), + (milosF)-[:DIRECTED]->(oneFlewOvertheCuckoosNest) + + CREATE (somethingsGottaGive:Movie {title: 'Something\'s Gotta Give', released: 2003}) + CREATE (dianeK:Person {name: 'Diane Keaton', born: 1946}) + CREATE (nancyM:Person {name: 'Nancy Meyers', born: 1949}) + CREATE + (jackN)-[:ACTED_IN {roles: ['Harry Sanborn']}]->(somethingsGottaGive), + (dianeK)-[:ACTED_IN {roles: ['Erica Barry']}]->(somethingsGottaGive), + (keanu)-[:ACTED_IN {roles: ['Julian Mercer']}]->(somethingsGottaGive), + (nancyM)-[:DIRECTED]->(somethingsGottaGive), + (nancyM)-[:PRODUCED]->(somethingsGottaGive), + (nancyM)-[:WROTE]->(somethingsGottaGive) + + CREATE (bicentennialMan:Movie {title: 'Bicentennial Man', released: 1999, + tagline: 'One robot\'s 200 year journey to become an ordinary man.'}) + CREATE (chrisC:Person {name: 'Chris Columbus', born: 1958}) + CREATE + (robin)-[:ACTED_IN {roles: ['Andrew Marin']}]->(bicentennialMan), + (oliverP)-[:ACTED_IN {roles: ['Rupert Burns']}]->(bicentennialMan), + (chrisC)-[:DIRECTED]->(bicentennialMan) + + CREATE (charlieWilsonsWar:Movie {title: 'Charlie Wilson\'s War', released: 2007, + tagline: 'A stiff drink. A little mascara. A lot of nerve. Who said they could not bring down the Soviet empire.'}) + CREATE (juliaR:Person {name: 'Julia Roberts', born: 1967}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Rep. Charlie Wilson']}]->(charlieWilsonsWar), + (juliaR)-[:ACTED_IN {roles: ['Joanne Herring']}]->(charlieWilsonsWar), + (philipH)-[:ACTED_IN {roles: ['Gust Avrakotos']}]->(charlieWilsonsWar), + (mikeN)-[:DIRECTED]->(charlieWilsonsWar) + + CREATE (thePolarExpress:Movie {title: 'The Polar Express', released: 2004, + tagline: 'This Holiday Season... Believe'}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Hero Boy', 'Father', 'Conductor', 'Hobo', 'Scrooge', 'Santa Claus']}]->(thePolarExpress), + (robertZ)-[:DIRECTED]->(thePolarExpress) + + CREATE (aLeagueofTheirOwn:Movie {title: 'A League of Their Own', released: 1992, + tagline: 'A league of their own'}) + CREATE (madonna:Person {name: 'Madonna', born: 1954}) + CREATE (geenaD:Person {name: 'Geena Davis', born: 1956}) + CREATE (loriP:Person {name: 'Lori Petty', born: 1963}) + CREATE (pennyM:Person {name: 'Penny Marshall', born: 1943}) + CREATE + (tomH)-[:ACTED_IN {roles: ['Jimmy Dugan']}]->(aLeagueofTheirOwn), + (geenaD)-[:ACTED_IN {roles: ['Dottie Hinson']}]->(aLeagueofTheirOwn), + (loriP)-[:ACTED_IN {roles: ['Kit Keller']}]->(aLeagueofTheirOwn), + (rosieO)-[:ACTED_IN {roles: ['Doris Murphy']}]->(aLeagueofTheirOwn), + (madonna)-[:ACTED_IN {roles: ['Mae Mordabito']}]->(aLeagueofTheirOwn), + (billPax)-[:ACTED_IN {roles: ['Bob Hinson']}]->(aLeagueofTheirOwn), + (pennyM)-[:DIRECTED]->(aLeagueofTheirOwn) + + CREATE (paulBlythe:Person {name: 'Paul Blythe'}) + CREATE (angelaScope:Person {name: 'Angela Scope'}) + CREATE (jessicaThompson:Person {name: 'Jessica Thompson'}) + CREATE (jamesThompson:Person {name: 'James Thompson'}) + + CREATE + (jamesThompson)-[:FOLLOWS]->(jessicaThompson), + (angelaScope)-[:FOLLOWS]->(jessicaThompson), + (paulBlythe)-[:FOLLOWS]->(angelaScope) + + CREATE + (jessicaThompson)-[:REVIEWED {summary: 'An amazing journey', rating: 95}]->(cloudAtlas), + (jessicaThompson)-[:REVIEWED {summary: 'Silly, but fun', rating: 65}]->(theReplacements), + (jamesThompson)-[:REVIEWED {summary: 'The coolest football movie ever', rating: 100}]->(theReplacements), + (angelaScope)-[:REVIEWED {summary: 'Pretty funny at times', rating: 62}]->(theReplacements), + (jessicaThompson)-[:REVIEWED {summary: 'Dark, but compelling', rating: 85}]->(unforgiven), + (jessicaThompson)-[:REVIEWED {summary: 'Slapstick', rating: 45}]->(theBirdcage), + (jessicaThompson)-[:REVIEWED {summary: 'A solid romp', rating: 68}]->(theDaVinciCode), + (jamesThompson)-[:REVIEWED {summary: 'Fun, but a little far fetched', rating: 65}]->(theDaVinciCode), + (jessicaThompson)-[:REVIEWED {summary: 'You had me at Jerry', rating: 92}]->(jerryMaguire) + + """ + Then the result should be empty + And the side effects should be: + | +nodes | 171 | + | +relationships | 253 | + | +properties | 564 | + | +labels | 171 | + + Scenario: Many CREATE clauses + Given any graph + When executing query: + """ + CREATE (hf:School {name: 'Hilly Fields Technical College'}) + CREATE (hf)-[:STAFF]->(mrb:Teacher {name: 'Mr Balls'}) + CREATE (hf)-[:STAFF]->(mrspb:Teacher {name: 'Ms Packard-Bell'}) + CREATE (hf)-[:STAFF]->(mrs:Teacher {name: 'Mr Smith'}) + CREATE (hf)-[:STAFF]->(mrsa:Teacher {name: 'Mrs Adenough'}) + CREATE (hf)-[:STAFF]->(mrvdg:Teacher {name: 'Mr Van der Graaf'}) + CREATE (hf)-[:STAFF]->(msn:Teacher {name: 'Ms Noethe'}) + CREATE (hf)-[:STAFF]->(mrsn:Teacher {name: 'Mrs Noakes'}) + CREATE (hf)-[:STAFF]->(mrm:Teacher {name: 'Mr Marker'}) + CREATE (hf)-[:STAFF]->(msd:Teacher {name: 'Ms Delgado'}) + CREATE (hf)-[:STAFF]->(mrsg:Teacher {name: 'Mrs Glass'}) + CREATE (hf)-[:STAFF]->(mrf:Teacher {name: 'Mr Flint'}) + CREATE (hf)-[:STAFF]->(mrk:Teacher {name: 'Mr Kearney'}) + CREATE (hf)-[:STAFF]->(msf:Teacher {name: 'Mrs Forrester'}) + CREATE (hf)-[:STAFF]->(mrsf:Teacher {name: 'Mrs Fischer'}) + CREATE (hf)-[:STAFF]->(mrj:Teacher {name: 'Mr Jameson'}) + + CREATE (hf)-[:STUDENT]->(_001:Student {name: 'Portia Vasquez'}) + CREATE (hf)-[:STUDENT]->(_002:Student {name: 'Andrew Parks'}) + CREATE (hf)-[:STUDENT]->(_003:Student {name: 'Germane Frye'}) + CREATE (hf)-[:STUDENT]->(_004:Student {name: 'Yuli Gutierrez'}) + CREATE (hf)-[:STUDENT]->(_005:Student {name: 'Kamal Solomon'}) + CREATE (hf)-[:STUDENT]->(_006:Student {name: 'Lysandra Porter'}) + CREATE (hf)-[:STUDENT]->(_007:Student {name: 'Stella Santiago'}) + CREATE (hf)-[:STUDENT]->(_008:Student {name: 'Brenda Torres'}) + CREATE (hf)-[:STUDENT]->(_009:Student {name: 'Heidi Dunlap'}) + + CREATE (hf)-[:STUDENT]->(_010:Student {name: 'Halee Taylor'}) + CREATE (hf)-[:STUDENT]->(_011:Student {name: 'Brennan Crosby'}) + CREATE (hf)-[:STUDENT]->(_012:Student {name: 'Rooney Cook'}) + CREATE (hf)-[:STUDENT]->(_013:Student {name: 'Xavier Morrison'}) + CREATE (hf)-[:STUDENT]->(_014:Student {name: 'Zelenia Santana'}) + CREATE (hf)-[:STUDENT]->(_015:Student {name: 'Eaton Bonner'}) + CREATE (hf)-[:STUDENT]->(_016:Student {name: 'Leilani Bishop'}) + CREATE (hf)-[:STUDENT]->(_017:Student {name: 'Jamalia Pickett'}) + CREATE (hf)-[:STUDENT]->(_018:Student {name: 'Wynter Russell'}) + CREATE (hf)-[:STUDENT]->(_019:Student {name: 'Liberty Melton'}) + + CREATE (hf)-[:STUDENT]->(_020:Student {name: 'MacKensie Obrien'}) + CREATE (hf)-[:STUDENT]->(_021:Student {name: 'Oprah Maynard'}) + CREATE (hf)-[:STUDENT]->(_022:Student {name: 'Lyle Parks'}) + CREATE (hf)-[:STUDENT]->(_023:Student {name: 'Madonna Justice'}) + CREATE (hf)-[:STUDENT]->(_024:Student {name: 'Herman Frederick'}) + CREATE (hf)-[:STUDENT]->(_025:Student {name: 'Preston Stevenson'}) + CREATE (hf)-[:STUDENT]->(_026:Student {name: 'Drew Carrillo'}) + CREATE (hf)-[:STUDENT]->(_027:Student {name: 'Hamilton Woodward'}) + CREATE (hf)-[:STUDENT]->(_028:Student {name: 'Buckminster Bradley'}) + CREATE (hf)-[:STUDENT]->(_029:Student {name: 'Shea Cote'}) + + CREATE (hf)-[:STUDENT]->(_030:Student {name: 'Raymond Leonard'}) + CREATE (hf)-[:STUDENT]->(_031:Student {name: 'Gavin Branch'}) + CREATE (hf)-[:STUDENT]->(_032:Student {name: 'Kylan Powers'}) + CREATE (hf)-[:STUDENT]->(_033:Student {name: 'Hedy Bowers'}) + CREATE (hf)-[:STUDENT]->(_034:Student {name: 'Derek Church'}) + CREATE (hf)-[:STUDENT]->(_035:Student {name: 'Silas Santiago'}) + CREATE (hf)-[:STUDENT]->(_036:Student {name: 'Elton Bright'}) + CREATE (hf)-[:STUDENT]->(_037:Student {name: 'Dora Schmidt'}) + CREATE (hf)-[:STUDENT]->(_038:Student {name: 'Julian Sullivan'}) + CREATE (hf)-[:STUDENT]->(_039:Student {name: 'Willow Morton'}) + + CREATE (hf)-[:STUDENT]->(_040:Student {name: 'Blaze Hines'}) + CREATE (hf)-[:STUDENT]->(_041:Student {name: 'Felicia Tillman'}) + CREATE (hf)-[:STUDENT]->(_042:Student {name: 'Ralph Webb'}) + CREATE (hf)-[:STUDENT]->(_043:Student {name: 'Roth Gilmore'}) + CREATE (hf)-[:STUDENT]->(_044:Student {name: 'Dorothy Burgess'}) + CREATE (hf)-[:STUDENT]->(_045:Student {name: 'Lana Sandoval'}) + CREATE (hf)-[:STUDENT]->(_046:Student {name: 'Nevada Strickland'}) + CREATE (hf)-[:STUDENT]->(_047:Student {name: 'Lucian Franco'}) + CREATE (hf)-[:STUDENT]->(_048:Student {name: 'Jasper Talley'}) + CREATE (hf)-[:STUDENT]->(_049:Student {name: 'Madaline Spears'}) + + CREATE (hf)-[:STUDENT]->(_050:Student {name: 'Upton Browning'}) + CREATE (hf)-[:STUDENT]->(_051:Student {name: 'Cooper Leon'}) + CREATE (hf)-[:STUDENT]->(_052:Student {name: 'Celeste Ortega'}) + CREATE (hf)-[:STUDENT]->(_053:Student {name: 'Willa Hewitt'}) + CREATE (hf)-[:STUDENT]->(_054:Student {name: 'Rooney Bryan'}) + CREATE (hf)-[:STUDENT]->(_055:Student {name: 'Nayda Hays'}) + CREATE (hf)-[:STUDENT]->(_056:Student {name: 'Kadeem Salazar'}) + CREATE (hf)-[:STUDENT]->(_057:Student {name: 'Halee Allen'}) + CREATE (hf)-[:STUDENT]->(_058:Student {name: 'Odysseus Mayo'}) + CREATE (hf)-[:STUDENT]->(_059:Student {name: 'Kato Merrill'}) + + CREATE (hf)-[:STUDENT]->(_060:Student {name: 'Halee Juarez'}) + CREATE (hf)-[:STUDENT]->(_061:Student {name: 'Chloe Charles'}) + CREATE (hf)-[:STUDENT]->(_062:Student {name: 'Abel Montoya'}) + CREATE (hf)-[:STUDENT]->(_063:Student {name: 'Hilda Welch'}) + CREATE (hf)-[:STUDENT]->(_064:Student {name: 'Britanni Bean'}) + CREATE (hf)-[:STUDENT]->(_065:Student {name: 'Joelle Beach'}) + CREATE (hf)-[:STUDENT]->(_066:Student {name: 'Ciara Odom'}) + CREATE (hf)-[:STUDENT]->(_067:Student {name: 'Zia Williams'}) + CREATE (hf)-[:STUDENT]->(_068:Student {name: 'Darrel Bailey'}) + CREATE (hf)-[:STUDENT]->(_069:Student {name: 'Lance Mcdowell'}) + + CREATE (hf)-[:STUDENT]->(_070:Student {name: 'Clayton Bullock'}) + CREATE (hf)-[:STUDENT]->(_071:Student {name: 'Roanna Mosley'}) + CREATE (hf)-[:STUDENT]->(_072:Student {name: 'Amethyst Mcclure'}) + CREATE (hf)-[:STUDENT]->(_073:Student {name: 'Hanae Mann'}) + CREATE (hf)-[:STUDENT]->(_074:Student {name: 'Graiden Haynes'}) + CREATE (hf)-[:STUDENT]->(_075:Student {name: 'Marcia Byrd'}) + CREATE (hf)-[:STUDENT]->(_076:Student {name: 'Yoshi Joyce'}) + CREATE (hf)-[:STUDENT]->(_077:Student {name: 'Gregory Sexton'}) + CREATE (hf)-[:STUDENT]->(_078:Student {name: 'Nash Carey'}) + CREATE (hf)-[:STUDENT]->(_079:Student {name: 'Rae Stevens'}) + + CREATE (hf)-[:STUDENT]->(_080:Student {name: 'Blossom Fulton'}) + CREATE (hf)-[:STUDENT]->(_081:Student {name: 'Lev Curry'}) + CREATE (hf)-[:STUDENT]->(_082:Student {name: 'Margaret Gamble'}) + CREATE (hf)-[:STUDENT]->(_083:Student {name: 'Rylee Patterson'}) + CREATE (hf)-[:STUDENT]->(_084:Student {name: 'Harper Perkins'}) + CREATE (hf)-[:STUDENT]->(_085:Student {name: 'Kennan Murphy'}) + CREATE (hf)-[:STUDENT]->(_086:Student {name: 'Hilda Coffey'}) + CREATE (hf)-[:STUDENT]->(_087:Student {name: 'Marah Reed'}) + CREATE (hf)-[:STUDENT]->(_088:Student {name: 'Blaine Wade'}) + CREATE (hf)-[:STUDENT]->(_089:Student {name: 'Geraldine Sanders'}) + + CREATE (hf)-[:STUDENT]->(_090:Student {name: 'Kerry Rollins'}) + CREATE (hf)-[:STUDENT]->(_091:Student {name: 'Virginia Sweet'}) + CREATE (hf)-[:STUDENT]->(_092:Student {name: 'Sophia Merrill'}) + CREATE (hf)-[:STUDENT]->(_093:Student {name: 'Hedda Carson'}) + CREATE (hf)-[:STUDENT]->(_094:Student {name: 'Tamekah Charles'}) + CREATE (hf)-[:STUDENT]->(_095:Student {name: 'Knox Barton'}) + CREATE (hf)-[:STUDENT]->(_096:Student {name: 'Ariel Porter'}) + CREATE (hf)-[:STUDENT]->(_097:Student {name: 'Berk Wooten'}) + CREATE (hf)-[:STUDENT]->(_098:Student {name: 'Galena Glenn'}) + CREATE (hf)-[:STUDENT]->(_099:Student {name: 'Jolene Anderson'}) + + CREATE (hf)-[:STUDENT]->(_100:Student {name: 'Leonard Hewitt'}) + CREATE (hf)-[:STUDENT]->(_101:Student {name: 'Maris Salazar'}) + CREATE (hf)-[:STUDENT]->(_102:Student {name: 'Brian Frost'}) + CREATE (hf)-[:STUDENT]->(_103:Student {name: 'Zane Moses'}) + CREATE (hf)-[:STUDENT]->(_104:Student {name: 'Serina Finch'}) + CREATE (hf)-[:STUDENT]->(_105:Student {name: 'Anastasia Fletcher'}) + CREATE (hf)-[:STUDENT]->(_106:Student {name: 'Glenna Chapman'}) + CREATE (hf)-[:STUDENT]->(_107:Student {name: 'Mufutau Gillespie'}) + CREATE (hf)-[:STUDENT]->(_108:Student {name: 'Basil Guthrie'}) + CREATE (hf)-[:STUDENT]->(_109:Student {name: 'Theodore Marsh'}) + + CREATE (hf)-[:STUDENT]->(_110:Student {name: 'Jaime Contreras'}) + CREATE (hf)-[:STUDENT]->(_111:Student {name: 'Irma Poole'}) + CREATE (hf)-[:STUDENT]->(_112:Student {name: 'Buckminster Bender'}) + CREATE (hf)-[:STUDENT]->(_113:Student {name: 'Elton Morris'}) + CREATE (hf)-[:STUDENT]->(_114:Student {name: 'Barbara Nguyen'}) + CREATE (hf)-[:STUDENT]->(_115:Student {name: 'Tanya Kidd'}) + CREATE (hf)-[:STUDENT]->(_116:Student {name: 'Kaden Hoover'}) + CREATE (hf)-[:STUDENT]->(_117:Student {name: 'Christopher Bean'}) + CREATE (hf)-[:STUDENT]->(_118:Student {name: 'Trevor Daugherty'}) + CREATE (hf)-[:STUDENT]->(_119:Student {name: 'Rudyard Bates'}) + + CREATE (hf)-[:STUDENT]->(_120:Student {name: 'Stacy Monroe'}) + CREATE (hf)-[:STUDENT]->(_121:Student {name: 'Kieran Keller'}) + CREATE (hf)-[:STUDENT]->(_122:Student {name: 'Ivy Garrison'}) + CREATE (hf)-[:STUDENT]->(_123:Student {name: 'Miranda Haynes'}) + CREATE (hf)-[:STUDENT]->(_124:Student {name: 'Abigail Heath'}) + CREATE (hf)-[:STUDENT]->(_125:Student {name: 'Margaret Santiago'}) + CREATE (hf)-[:STUDENT]->(_126:Student {name: 'Cade Floyd'}) + CREATE (hf)-[:STUDENT]->(_127:Student {name: 'Allen Crane'}) + CREATE (hf)-[:STUDENT]->(_128:Student {name: 'Stella Gilliam'}) + CREATE (hf)-[:STUDENT]->(_129:Student {name: 'Rashad Miller'}) + + CREATE (hf)-[:STUDENT]->(_130:Student {name: 'Francis Cox'}) + CREATE (hf)-[:STUDENT]->(_131:Student {name: 'Darryl Rosario'}) + CREATE (hf)-[:STUDENT]->(_132:Student {name: 'Michael Daniels'}) + CREATE (hf)-[:STUDENT]->(_133:Student {name: 'Aretha Henderson'}) + CREATE (hf)-[:STUDENT]->(_134:Student {name: 'Roth Barrera'}) + CREATE (hf)-[:STUDENT]->(_135:Student {name: 'Yael Day'}) + CREATE (hf)-[:STUDENT]->(_136:Student {name: 'Wynter Richmond'}) + CREATE (hf)-[:STUDENT]->(_137:Student {name: 'Quyn Flowers'}) + CREATE (hf)-[:STUDENT]->(_138:Student {name: 'Yvette Marquez'}) + CREATE (hf)-[:STUDENT]->(_139:Student {name: 'Teagan Curry'}) + + CREATE (hf)-[:STUDENT]->(_140:Student {name: 'Brenden Bishop'}) + CREATE (hf)-[:STUDENT]->(_141:Student {name: 'Montana Black'}) + CREATE (hf)-[:STUDENT]->(_142:Student {name: 'Ramona Parker'}) + CREATE (hf)-[:STUDENT]->(_143:Student {name: 'Merritt Hansen'}) + CREATE (hf)-[:STUDENT]->(_144:Student {name: 'Melvin Vang'}) + CREATE (hf)-[:STUDENT]->(_145:Student {name: 'Samantha Perez'}) + CREATE (hf)-[:STUDENT]->(_146:Student {name: 'Thane Porter'}) + CREATE (hf)-[:STUDENT]->(_147:Student {name: 'Vaughan Haynes'}) + CREATE (hf)-[:STUDENT]->(_148:Student {name: 'Irma Miles'}) + CREATE (hf)-[:STUDENT]->(_149:Student {name: 'Amery Jensen'}) + + CREATE (hf)-[:STUDENT]->(_150:Student {name: 'Montana Holman'}) + CREATE (hf)-[:STUDENT]->(_151:Student {name: 'Kimberly Langley'}) + CREATE (hf)-[:STUDENT]->(_152:Student {name: 'Ebony Bray'}) + CREATE (hf)-[:STUDENT]->(_153:Student {name: 'Ishmael Pollard'}) + CREATE (hf)-[:STUDENT]->(_154:Student {name: 'Illana Thompson'}) + CREATE (hf)-[:STUDENT]->(_155:Student {name: 'Rhona Bowers'}) + CREATE (hf)-[:STUDENT]->(_156:Student {name: 'Lilah Dotson'}) + CREATE (hf)-[:STUDENT]->(_157:Student {name: 'Shelly Roach'}) + CREATE (hf)-[:STUDENT]->(_158:Student {name: 'Celeste Woodward'}) + CREATE (hf)-[:STUDENT]->(_159:Student {name: 'Christen Lynn'}) + + CREATE (hf)-[:STUDENT]->(_160:Student {name: 'Miranda Slater'}) + CREATE (hf)-[:STUDENT]->(_161:Student {name: 'Lunea Clements'}) + CREATE (hf)-[:STUDENT]->(_162:Student {name: 'Lester Francis'}) + CREATE (hf)-[:STUDENT]->(_163:Student {name: 'David Fischer'}) + CREATE (hf)-[:STUDENT]->(_164:Student {name: 'Kyra Bean'}) + CREATE (hf)-[:STUDENT]->(_165:Student {name: 'Imelda Alston'}) + CREATE (hf)-[:STUDENT]->(_166:Student {name: 'Finn Farrell'}) + CREATE (hf)-[:STUDENT]->(_167:Student {name: 'Kirby House'}) + CREATE (hf)-[:STUDENT]->(_168:Student {name: 'Amanda Zamora'}) + CREATE (hf)-[:STUDENT]->(_169:Student {name: 'Rina Franco'}) + + CREATE (hf)-[:STUDENT]->(_170:Student {name: 'Sonia Lane'}) + CREATE (hf)-[:STUDENT]->(_171:Student {name: 'Nora Jefferson'}) + CREATE (hf)-[:STUDENT]->(_172:Student {name: 'Colton Ortiz'}) + CREATE (hf)-[:STUDENT]->(_173:Student {name: 'Alden Munoz'}) + CREATE (hf)-[:STUDENT]->(_174:Student {name: 'Ferdinand Cline'}) + CREATE (hf)-[:STUDENT]->(_175:Student {name: 'Cynthia Prince'}) + CREATE (hf)-[:STUDENT]->(_176:Student {name: 'Asher Hurst'}) + CREATE (hf)-[:STUDENT]->(_177:Student {name: 'MacKensie Stevenson'}) + CREATE (hf)-[:STUDENT]->(_178:Student {name: 'Sydnee Sosa'}) + CREATE (hf)-[:STUDENT]->(_179:Student {name: 'Dante Callahan'}) + + CREATE (hf)-[:STUDENT]->(_180:Student {name: 'Isabella Santana'}) + CREATE (hf)-[:STUDENT]->(_181:Student {name: 'Raven Bowman'}) + CREATE (hf)-[:STUDENT]->(_182:Student {name: 'Kirby Bolton'}) + CREATE (hf)-[:STUDENT]->(_183:Student {name: 'Peter Shaffer'}) + CREATE (hf)-[:STUDENT]->(_184:Student {name: 'Fletcher Beard'}) + CREATE (hf)-[:STUDENT]->(_185:Student {name: 'Irene Lowe'}) + CREATE (hf)-[:STUDENT]->(_186:Student {name: 'Ella Talley'}) + CREATE (hf)-[:STUDENT]->(_187:Student {name: 'Jorden Kerr'}) + CREATE (hf)-[:STUDENT]->(_188:Student {name: 'Macey Delgado'}) + CREATE (hf)-[:STUDENT]->(_189:Student {name: 'Ulysses Graves'}) + + CREATE (hf)-[:STUDENT]->(_190:Student {name: 'Declan Blake'}) + CREATE (hf)-[:STUDENT]->(_191:Student {name: 'Lila Hurst'}) + CREATE (hf)-[:STUDENT]->(_192:Student {name: 'David Rasmussen'}) + CREATE (hf)-[:STUDENT]->(_193:Student {name: 'Desiree Cortez'}) + CREATE (hf)-[:STUDENT]->(_194:Student {name: 'Myles Horton'}) + CREATE (hf)-[:STUDENT]->(_195:Student {name: 'Rylee Willis'}) + CREATE (hf)-[:STUDENT]->(_196:Student {name: 'Kelsey Yates'}) + CREATE (hf)-[:STUDENT]->(_197:Student {name: 'Alika Stanton'}) + CREATE (hf)-[:STUDENT]->(_198:Student {name: 'Ria Campos'}) + CREATE (hf)-[:STUDENT]->(_199:Student {name: 'Elijah Hendricks'}) + + CREATE (hf)-[:STUDENT]->(_200:Student {name: 'Hayes House'}) + + CREATE (hf)-[:DEPARTMENT]->(md:Department {name: 'Mathematics'}) + CREATE (hf)-[:DEPARTMENT]->(sd:Department {name: 'Science'}) + CREATE (hf)-[:DEPARTMENT]->(ed:Department {name: 'Engineering'}) + + CREATE (pm:Subject {name: 'Pure Mathematics'}) + CREATE (am:Subject {name: 'Applied Mathematics'}) + CREATE (ph:Subject {name: 'Physics'}) + CREATE (ch:Subject {name: 'Chemistry'}) + CREATE (bi:Subject {name: 'Biology'}) + CREATE (es:Subject {name: 'Earth Science'}) + CREATE (me:Subject {name: 'Mechanical Engineering'}) + CREATE (ce:Subject {name: 'Chemical Engineering'}) + CREATE (se:Subject {name: 'Systems Engineering'}) + CREATE (ve:Subject {name: 'Civil Engineering'}) + CREATE (ee:Subject {name: 'Electrical Engineering'}) + + CREATE (sd)-[:CURRICULUM]->(ph) + CREATE (sd)-[:CURRICULUM]->(ch) + CREATE (sd)-[:CURRICULUM]->(bi) + CREATE (sd)-[:CURRICULUM]->(es) + CREATE (md)-[:CURRICULUM]->(pm) + CREATE (md)-[:CURRICULUM]->(am) + CREATE (ed)-[:CURRICULUM]->(me) + CREATE (ed)-[:CURRICULUM]->(se) + CREATE (ed)-[:CURRICULUM]->(ce) + CREATE (ed)-[:CURRICULUM]->(ee) + CREATE (ed)-[:CURRICULUM]->(ve) + + CREATE (ph)-[:TAUGHT_BY]->(mrb) + CREATE (ph)-[:TAUGHT_BY]->(mrk) + CREATE (ch)-[:TAUGHT_BY]->(mrk) + CREATE (ch)-[:TAUGHT_BY]->(mrsn) + CREATE (bi)-[:TAUGHT_BY]->(mrsn) + CREATE (bi)-[:TAUGHT_BY]->(mrsf) + CREATE (es)-[:TAUGHT_BY]->(msn) + CREATE (pm)-[:TAUGHT_BY]->(mrf) + CREATE (pm)-[:TAUGHT_BY]->(mrm) + CREATE (pm)-[:TAUGHT_BY]->(mrvdg) + CREATE (am)-[:TAUGHT_BY]->(mrsg) + CREATE (am)-[:TAUGHT_BY]->(mrspb) + CREATE (am)-[:TAUGHT_BY]->(mrvdg) + CREATE (me)-[:TAUGHT_BY]->(mrj) + CREATE (ce)-[:TAUGHT_BY]->(mrsa) + CREATE (se)-[:TAUGHT_BY]->(mrs) + CREATE (ve)-[:TAUGHT_BY]->(msd) + CREATE (ee)-[:TAUGHT_BY]->(mrsf) + + CREATE(_001)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_188) + CREATE(_002)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_198) + CREATE(_003)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_106) + CREATE(_004)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_029) + CREATE(_005)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_153) + CREATE(_006)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_061) + CREATE(_007)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_177) + CREATE(_008)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_115) + CREATE(_009)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_010)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_142) + CREATE(_011)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_043) + CREATE(_012)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_065) + CREATE(_013)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_074) + CREATE(_014)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_165) + CREATE(_015)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_016)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_086) + CREATE(_017)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_062) + CREATE(_018)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_033) + CREATE(_019)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_020)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_021)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_086) + CREATE(_022)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_121) + CREATE(_023)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_049) + CREATE(_024)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_152) + CREATE(_025)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_152) + CREATE(_026)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_085) + CREATE(_027)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_084) + CREATE(_028)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_143) + CREATE(_029)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_030)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_094) + CREATE(_031)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_125) + CREATE(_032)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_024) + CREATE(_033)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_075) + CREATE(_034)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_161) + CREATE(_035)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_197) + CREATE(_036)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_067) + CREATE(_037)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_049) + CREATE(_038)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_038) + CREATE(_039)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_040)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_149) + CREATE(_041)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_044) + CREATE(_042)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_043)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_095) + CREATE(_044)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_016) + CREATE(_045)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_021) + CREATE(_046)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_047)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_189) + CREATE(_048)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_094) + CREATE(_049)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_161) + CREATE(_050)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_098) + CREATE(_051)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_052)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_148) + CREATE(_053)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_054)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_055)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_175) + CREATE(_056)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_010) + CREATE(_057)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_042) + CREATE(_058)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_059)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_067) + CREATE(_060)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_034) + CREATE(_061)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_062)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_088) + CREATE(_063)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_142) + CREATE(_064)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_88) + CREATE(_065)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_066)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_178) + CREATE(_067)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_041) + CREATE(_068)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_069)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_109) + CREATE(_070)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_071)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_182) + CREATE(_072)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_144) + CREATE(_073)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_074)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_128) + CREATE(_075)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_149) + CREATE(_076)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_038) + CREATE(_077)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_104) + CREATE(_078)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_032) + CREATE(_079)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_080)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_081)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_174) + CREATE(_082)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_162) + CREATE(_083)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_011) + CREATE(_084)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_085)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_086)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_067) + CREATE(_087)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_173) + CREATE(_088)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_128) + CREATE(_089)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_177) + CREATE(_090)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_076) + CREATE(_091)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_137) + CREATE(_092)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_024) + CREATE(_093)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_156) + CREATE(_094)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_020) + CREATE(_095)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_112) + CREATE(_096)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_193) + CREATE(_097)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_006) + CREATE(_098)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_099)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_141) + CREATE(_100)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_001) + CREATE(_101)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_169) + CREATE(_102)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_161) + CREATE(_103)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_136) + CREATE(_104)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_125) + CREATE(_105)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_127) + CREATE(_106)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_095) + CREATE(_107)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_036) + CREATE(_108)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_074) + CREATE(_109)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_110)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_191) + CREATE(_111)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_068) + CREATE(_112)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_019) + CREATE(_113)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_035) + CREATE(_114)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_061) + CREATE(_115)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_070) + CREATE(_116)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_069) + CREATE(_117)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_096) + CREATE(_118)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_107) + CREATE(_119)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_120)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_167) + CREATE(_121)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_122)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_090) + CREATE(_123)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_004) + CREATE(_124)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_125)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_094) + CREATE(_126)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_174) + CREATE(_127)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_168) + CREATE(_128)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_084) + CREATE(_129)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_186) + CREATE(_130)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_090) + CREATE(_131)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_010) + CREATE(_132)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_031) + CREATE(_133)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_059) + CREATE(_134)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_037) + CREATE(_135)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_012) + CREATE(_136)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_197) + CREATE(_137)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_059) + CREATE(_138)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_065) + CREATE(_139)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_175) + CREATE(_140)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_170) + CREATE(_141)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_191) + CREATE(_142)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_139) + CREATE(_143)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_054) + CREATE(_144)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_176) + CREATE(_145)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_188) + CREATE(_146)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_072) + CREATE(_147)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_096) + CREATE(_148)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_108) + CREATE(_149)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_155) + CREATE(_150)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_151)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_076) + CREATE(_152)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_169) + CREATE(_153)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_179) + CREATE(_154)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_186) + CREATE(_155)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_058) + CREATE(_156)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_071) + CREATE(_157)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_073) + CREATE(_158)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_159)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_182) + CREATE(_160)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_199) + CREATE(_161)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_072) + CREATE(_162)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_014) + CREATE(_163)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_163) + CREATE(_164)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_038) + CREATE(_165)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_044) + CREATE(_166)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_136) + CREATE(_167)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_038) + CREATE(_168)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_110) + CREATE(_169)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_198) + CREATE(_170)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_178) + CREATE(_171)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_172)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_020) + CREATE(_173)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_164) + CREATE(_174)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_075) + CREATE(_175)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_175) + CREATE(_176)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_177)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_178)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_006) + CREATE(_179)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_180)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_185) + CREATE(_181)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_074) + CREATE(_182)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_183)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_184)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_185)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_186)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_187)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_188)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_014) + CREATE(_189)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_096) + CREATE(_190)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_063) + CREATE(_191)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_079) + CREATE(_192)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_121) + CREATE(_193)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_194)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_029) + CREATE(_195)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_164) + CREATE(_196)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_197)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_101) + CREATE(_198)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_039) + CREATE(_199)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_011) + CREATE(_200)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_073) + CREATE(_001)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_129) + CREATE(_002)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_078) + CREATE(_003)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_181) + CREATE(_004)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_162) + CREATE(_005)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_006)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_111) + CREATE(_007)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_027) + CREATE(_008)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_009)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_132) + CREATE(_010)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_147) + CREATE(_011)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_012)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_118) + CREATE(_013)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_014)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_015)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_107) + CREATE(_016)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_017)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_018)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_069) + CREATE(_019)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_024) + CREATE(_020)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_021)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_184) + CREATE(_022)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_023)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_024)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_075) + CREATE(_025)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_087) + CREATE(_026)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_163) + CREATE(_027)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_115) + CREATE(_028)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_042) + CREATE(_029)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_058) + CREATE(_030)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_188) + CREATE(_031)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_032)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_015) + CREATE(_033)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_130) + CREATE(_034)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_141) + CREATE(_035)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_158) + CREATE(_036)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_020) + CREATE(_037)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_102) + CREATE(_038)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_184) + CREATE(_039)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_040)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_041)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_042)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_050) + CREATE(_043)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_085) + CREATE(_044)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_025) + CREATE(_045)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_084) + CREATE(_046)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_118) + CREATE(_047)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_048)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_049)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_071) + CREATE(_050)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_178) + CREATE(_051)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_052)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_059) + CREATE(_053)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_095) + CREATE(_054)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_185) + CREATE(_055)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_108) + CREATE(_056)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_057)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_031) + CREATE(_058)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_054) + CREATE(_059)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_198) + CREATE(_060)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_138) + CREATE(_061)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_176) + CREATE(_062)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_086) + CREATE(_063)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_032) + CREATE(_064)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_101) + CREATE(_065)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_181) + CREATE(_066)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_153) + CREATE(_067)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_166) + CREATE(_068)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_069)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_027) + CREATE(_070)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_021) + CREATE(_071)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_193) + CREATE(_072)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_073)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_108) + CREATE(_074)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_174) + CREATE(_075)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_019) + CREATE(_076)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_179) + CREATE(_077)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_005) + CREATE(_078)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_014) + CREATE(_079)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_017) + CREATE(_080)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_146) + CREATE(_081)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_098) + CREATE(_082)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_083)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_099) + CREATE(_084)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_161) + CREATE(_085)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_098) + CREATE(_086)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_199) + CREATE(_087)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_088)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_164) + CREATE(_089)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_064) + CREATE(_090)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_109) + CREATE(_091)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_077) + CREATE(_092)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_124) + CREATE(_093)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_181) + CREATE(_094)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_142) + CREATE(_095)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_191) + CREATE(_096)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_093) + CREATE(_097)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_031) + CREATE(_098)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_099)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_182) + CREATE(_100)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_043) + CREATE(_101)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_146) + CREATE(_102)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_141) + CREATE(_103)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_040) + CREATE(_104)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_199) + CREATE(_105)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_063) + CREATE(_106)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_180) + CREATE(_107)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_010) + CREATE(_108)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_122) + CREATE(_109)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_111) + CREATE(_110)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_065) + CREATE(_111)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_199) + CREATE(_112)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_135) + CREATE(_113)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_172) + CREATE(_114)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_096) + CREATE(_115)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_028) + CREATE(_116)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_109) + CREATE(_117)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_191) + CREATE(_118)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_169) + CREATE(_119)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_101) + CREATE(_120)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_184) + CREATE(_121)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_032) + CREATE(_122)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_127) + CREATE(_123)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_129) + CREATE(_124)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_125)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_126)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_175) + CREATE(_127)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_018) + CREATE(_128)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_165) + CREATE(_129)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_130)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_066) + CREATE(_131)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_050) + CREATE(_132)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_197) + CREATE(_133)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_111) + CREATE(_134)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_125) + CREATE(_135)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_112) + CREATE(_136)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_173) + CREATE(_137)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_181) + CREATE(_138)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_072) + CREATE(_139)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_115) + CREATE(_140)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_013) + CREATE(_141)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_142)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_003) + CREATE(_143)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_144) + CREATE(_144)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_145)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_015) + CREATE(_146)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_061) + CREATE(_147)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_009) + CREATE(_148)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_149)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_176) + CREATE(_150)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_152) + CREATE(_151)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_055) + CREATE(_152)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_157) + CREATE(_153)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_090) + CREATE(_154)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_162) + CREATE(_155)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_146) + CREATE(_156)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_073) + CREATE(_157)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_044) + CREATE(_158)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_154) + CREATE(_159)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_160)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_168) + CREATE(_161)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_122) + CREATE(_162)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_015) + CREATE(_163)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_041) + CREATE(_164)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_087) + CREATE(_165)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_104) + CREATE(_166)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_167)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_019) + CREATE(_168)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_021) + CREATE(_169)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_065) + CREATE(_170)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_183) + CREATE(_171)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_147) + CREATE(_172)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_173)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_172) + CREATE(_174)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_137) + CREATE(_175)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_176)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_138) + CREATE(_177)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_078) + CREATE(_178)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_176) + CREATE(_179)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_062) + CREATE(_180)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_181)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_178) + CREATE(_182)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_173) + CREATE(_183)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_107) + CREATE(_184)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_198) + CREATE(_185)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_186)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_041) + CREATE(_187)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_076) + CREATE(_188)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_132) + CREATE(_189)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_093) + CREATE(_190)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_191)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_183) + CREATE(_192)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_140) + CREATE(_193)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_194)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_117) + CREATE(_195)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_054) + CREATE(_196)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_197) + CREATE(_197)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_086) + CREATE(_198)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_190) + CREATE(_199)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_143) + CREATE(_200)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_144) + CREATE(_001)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_050) + CREATE(_002)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_024) + CREATE(_003)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_135) + CREATE(_004)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_094) + CREATE(_005)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_143) + CREATE(_006)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_066) + CREATE(_007)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_193) + CREATE(_008)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_022) + CREATE(_009)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_074) + CREATE(_010)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_166) + CREATE(_011)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_012)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_036) + CREATE(_013)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_016) + CREATE(_014)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_108) + CREATE(_015)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_083) + CREATE(_016)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_017)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_016) + CREATE(_018)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_130) + CREATE(_019)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_013) + CREATE(_020)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_186) + CREATE(_021)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_026) + CREATE(_022)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_040) + CREATE(_023)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_064) + CREATE(_024)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_072) + CREATE(_025)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_017) + CREATE(_026)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_159) + CREATE(_027)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_076) + CREATE(_028)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_014) + CREATE(_029)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_089) + CREATE(_030)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_157) + CREATE(_031)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_029) + CREATE(_032)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_184) + CREATE(_033)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_034)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_035)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_051) + CREATE(_036)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_031) + CREATE(_037)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_038)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_057) + CREATE(_039)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_023) + CREATE(_040)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_109) + CREATE(_041)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_177) + CREATE(_042)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_020) + CREATE(_043)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_069) + CREATE(_044)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_068) + CREATE(_045)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_027) + CREATE(_046)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_018) + CREATE(_047)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_154) + CREATE(_048)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_090) + CREATE(_049)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_166) + CREATE(_050)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_150) + CREATE(_051)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_045) + CREATE(_052)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_123) + CREATE(_053)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_160) + CREATE(_054)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_088) + CREATE(_055)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_196) + CREATE(_056)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_057)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_110) + CREATE(_058)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_060) + CREATE(_059)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_084) + CREATE(_060)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_030) + CREATE(_061)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_170) + CREATE(_062)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_027) + CREATE(_063)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_018) + CREATE(_064)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_004) + CREATE(_065)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_138) + CREATE(_066)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_009) + CREATE(_067)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_172) + CREATE(_068)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_077) + CREATE(_069)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_112) + CREATE(_070)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_069) + CREATE(_071)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_018) + CREATE(_072)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_172) + CREATE(_073)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_053) + CREATE(_074)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_098) + CREATE(_075)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_068) + CREATE(_076)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_132) + CREATE(_077)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_134) + CREATE(_078)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_138) + CREATE(_079)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_080)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_125) + CREATE(_081)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_129) + CREATE(_082)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_048) + CREATE(_083)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_145) + CREATE(_084)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_101) + CREATE(_085)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_131) + CREATE(_086)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_011) + CREATE(_087)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_200) + CREATE(_088)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_070) + CREATE(_089)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_008) + CREATE(_090)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_107) + CREATE(_091)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_002) + CREATE(_092)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_180) + CREATE(_093)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_001) + CREATE(_094)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_120) + CREATE(_095)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_135) + CREATE(_096)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_116) + CREATE(_097)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_171) + CREATE(_098)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_122) + CREATE(_099)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_100) + CREATE(_100)-[:BUDDY]->(:StudyBuddy)<-[:BUDDY]-(_130) + """ + Then the result should be empty + And the side effects should be: + | +nodes | 731 | + | +relationships | 1247 | + | +labels | 730 | + | +properties | 230 | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/LargeIntegerEquality.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/LargeIntegerEquality.feature new file mode 100644 index 000000000..a69cce5f8 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/LargeIntegerEquality.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ListComprehension.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ListComprehension.feature new file mode 100644 index 000000000..2d2e4a921 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ListComprehension.feature @@ -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 + diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/Literals.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/Literals.feature new file mode 100644 index 000000000..3535c2d36 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/Literals.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MatchAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MatchAcceptance.feature new file mode 100644 index 000000000..804ee3559 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MatchAcceptance.feature @@ -0,0 +1,552 @@ +# +# Copyright 2017 "Neo Technology", +# Network Engine for Objects in Lund AB (http://neotechnology.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Feature: MatchAcceptance + + Scenario: Path query should return results in written order + Given an empty graph + And having executed: + """ + CREATE (:Label1)<-[:TYPE]-(:Label2) + """ + When executing query: + """ + MATCH p = (a:Label1)<--(:Label2) + RETURN p + """ + Then the result should be: + | p | + | <(:Label1)<-[:TYPE]-(:Label2)> | + And no side effects + + Scenario: Longer path query should return results in written order + Given an empty graph + And having executed: + """ + CREATE (:Label1)<-[:T1]-(:Label2)-[:T2]->(:Label3) + """ + When executing query: + """ + MATCH p = (a:Label1)<--(:Label2)--() + RETURN p + """ + Then the result should be: + | p | + | <(:Label1)<-[:T1]-(:Label2)-[:T2]->(:Label3)> | + And no side effects + + Scenario: Use multiple MATCH clauses to do a Cartesian product + Given an empty graph + And having executed: + """ + CREATE ({value: 1}), + ({value: 2}), + ({value: 3}) + """ + When executing query: + """ + MATCH (n), (m) + RETURN n.value AS n, m.value AS m + """ + Then the result should be: + | n | m | + | 1 | 1 | + | 1 | 2 | + | 1 | 3 | + | 2 | 1 | + | 2 | 2 | + | 2 | 3 | + | 3 | 3 | + | 3 | 1 | + | 3 | 2 | + And no side effects + + Scenario: Use params in pattern matching predicates + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T {foo: 'bar'}]->(:B {name: 'me'}) + """ + And parameters are: + | param | 'bar' | + When executing query: + """ + MATCH (a)-[r]->(b) + WHERE r.foo = $param + RETURN b + """ + Then the result should be: + | b | + | (:B {name: 'me'}) | + And no side effects + + Scenario: Filter out based on node prop name + Given an empty graph + And having executed: + """ + CREATE ({name: 'Someone'})<-[:X]-()-[:X]->({name: 'Andres'}) + """ + When executing query: + """ + MATCH ()-[rel:X]-(a) + WHERE a.name = 'Andres' + RETURN a + """ + Then the result should be: + | a | + | ({name: 'Andres'}) | + And no side effects + + Scenario: Honour the column name for RETURN items + Given an empty graph + And having executed: + """ + CREATE ({name: 'Someone'}) + """ + When executing query: + """ + MATCH (a) + WITH a.name AS a + RETURN a + """ + Then the result should be: + | a | + | 'Someone' | + And no side effects + + Scenario: Filter based on rel prop name + Given an empty graph + And having executed: + """ + CREATE (:A)<-[:KNOWS {name: 'monkey'}]-()-[:KNOWS {name: 'woot'}]->(:B) + """ + When executing query: + """ + MATCH (node)-[r:KNOWS]->(a) + WHERE r.name = 'monkey' + RETURN a + """ + Then the result should be: + | a | + | (:A) | + And no side effects + + Scenario: Cope with shadowed variables + Given an empty graph + And having executed: + """ + CREATE ({value: 1, name: 'King Kong'}), + ({value: 2, name: 'Ann Darrow'}) + """ + When executing query: + """ + MATCH (n) + WITH n.name AS n + RETURN n + """ + Then the result should be: + | n | + | 'Ann Darrow' | + | 'King Kong' | + And no side effects + + Scenario: Get neighbours + Given an empty graph + And having executed: + """ + CREATE (a:A {value: 1})-[:KNOWS]->(b:B {value: 2}) + """ + When executing query: + """ + MATCH (n1)-[rel:KNOWS]->(n2) + RETURN n1, n2 + """ + Then the result should be: + | n1 | n2 | + | (:A {value: 1}) | (:B {value: 2}) | + And no side effects + + Scenario: Get two related nodes + Given an empty graph + And having executed: + """ + CREATE (a:A {value: 1}), + (a)-[:KNOWS]->(b:B {value: 2}), + (a)-[:KNOWS]->(c:C {value: 3}) + """ + When executing query: + """ + MATCH ()-[rel:KNOWS]->(x) + RETURN x + """ + Then the result should be: + | x | + | (:B {value: 2}) | + | (:C {value: 3}) | + And no side effects + + Scenario: Get related to related to + Given an empty graph + And having executed: + """ + CREATE (a:A {value: 1})-[:KNOWS]->(b:B {value: 2})-[:FRIEND]->(c:C {value: 3}) + """ + When executing query: + """ + MATCH (n)-->(a)-->(b) + RETURN b + """ + Then the result should be: + | b | + | (:C {value: 3}) | + And no side effects + + Scenario: Handle comparison between node properties + Given an empty graph + And having executed: + """ + CREATE (a:A {animal: 'monkey'}), + (b:B {animal: 'cow'}), + (c:C {animal: 'monkey'}), + (d:D {animal: 'cow'}), + (a)-[:KNOWS]->(b), + (a)-[:KNOWS]->(c), + (d)-[:KNOWS]->(b), + (d)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH (n)-[rel]->(x) + WHERE n.animal = x.animal + RETURN n, x + """ + Then the result should be: + | n | x | + | (:A {animal: 'monkey'}) | (:C {animal: 'monkey'}) | + | (:D {animal: 'cow'}) | (:B {animal: 'cow'}) | + And no side effects + + Scenario: Return two subgraphs with bound undirected relationship + Given an empty graph + And having executed: + """ + CREATE (a:A {value: 1})-[:REL {name: 'r'}]->(b:B {value: 2}) + """ + When executing query: + """ + MATCH (a)-[r {name: 'r'}]-(b) + RETURN a, b + """ + Then the result should be: + | a | b | + | (:B {value: 2}) | (:A {value: 1}) | + | (:A {value: 1}) | (:B {value: 2}) | + And no side effects + + Scenario: Return two subgraphs with bound undirected relationship and optional relationship + Given an empty graph + And having executed: + """ + CREATE (a:A {value: 1})-[:REL {name: 'r1'}]->(b:B {value: 2})-[:REL {name: 'r2'}]->(c:C {value: 3}) + """ + When executing query: + """ + MATCH (a)-[r {name: 'r1'}]-(b) + OPTIONAL MATCH (b)-[r2]-(c) + WHERE r <> r2 + RETURN a, b, c + """ + Then the result should be: + | a | b | c | + | (:A {value: 1}) | (:B {value: 2}) | (:C {value: 3}) | + | (:B {value: 2}) | (:A {value: 1}) | null | + And no side effects + + Scenario: Rel type function works as expected + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'}), + (b:B {name: 'B'}), + (c:C {name: 'C'}), + (a)-[:KNOWS]->(b), + (a)-[:HATES]->(c) + """ + When executing query: + """ + MATCH (n {name: 'A'})-[r]->(x) + WHERE type(r) = 'KNOWS' + RETURN x + """ + Then the result should be: + | x | + | (:B {name: 'B'}) | + And no side effects + + Scenario: Walk alternative relationships + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), + (b {name: 'B'}), + (c {name: 'C'}), + (a)-[:KNOWS]->(b), + (a)-[:HATES]->(c), + (a)-[:WONDERS]->(c) + """ + When executing query: + """ + MATCH (n)-[r]->(x) + WHERE type(r) = 'KNOWS' OR type(r) = 'HATES' + RETURN r + """ + Then the result should be: + | r | + | [:KNOWS] | + | [:HATES] | + And no side effects + + Scenario: Handle OR in the WHERE clause + Given an empty graph + And having executed: + """ + CREATE (a:A {p1: 12}), + (b:B {p2: 13}), + (c:C) + """ + When executing query: + """ + MATCH (n) + WHERE n.p1 = 12 OR n.p2 = 13 + RETURN n + """ + Then the result should be: + | n | + | (:A {p1: 12}) | + | (:B {p2: 13}) | + And no side effects + + Scenario: Return a simple path + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'}) + """ + When executing query: + """ + MATCH p = (a {name: 'A'})-->(b) + RETURN p + """ + Then the result should be: + | p | + | <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})> | + And no side effects + + Scenario: Return a three node path + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'})-[:KNOWS]->(c:C {name: 'C'}) + """ + When executing query: + """ + MATCH p = (a {name: 'A'})-[rel1]->(b)-[rel2]->(c) + RETURN p + """ + Then the result should be: + | p | + | <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})-[:KNOWS]->(:C {name: 'C'})> | + And no side effects + + Scenario: Do not return anything because path length does not match + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'}) + """ + When executing query: + """ + MATCH p = (n)-->(x) + WHERE length(p) = 10 + RETURN x + """ + Then the result should be: + | x | + And no side effects + + Scenario: Pass the path length test + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'}) + """ + When executing query: + """ + MATCH p = (n)-->(x) + WHERE length(p) = 1 + RETURN x + """ + Then the result should be: + | x | + | (:B {name: 'B'}) | + And no side effects + + Scenario: Return relationships by fetching them from the path - starting from the end + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(e:End) + """ + When executing query: + """ + MATCH p = (a)-[:REL*2..2]->(b:End) + RETURN relationships(p) + """ + Then the result should be: + | relationships(p) | + | [[:REL {value: 1}], [:REL {value: 2}]] | + And no side effects + + Scenario: Return relationships by fetching them from the path + Given an empty graph + And having executed: + """ + CREATE (s:Start)-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(c:C) + """ + When executing query: + """ + MATCH p = (a:Start)-[:REL*2..2]->(b) + RETURN relationships(p) + """ + Then the result should be: + | relationships(p) | + | [[:REL {value: 1}], [:REL {value: 2}]] | + And no side effects + + Scenario: Return relationships by collecting them as a list - wrong way + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(e:End) + """ + When executing query: + """ + MATCH (a)-[r:REL*2..2]->(b:End) + RETURN r + """ + Then the result should be: + | r | + | [[:REL {value: 1}], [:REL {value: 2}]] | + And no side effects + + Scenario: Return relationships by collecting them as a list - undirected + Given an empty graph + And having executed: + """ + CREATE (a:End {value: 1})-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(c:End {value: 2}) + """ + When executing query: + """ + MATCH (a)-[r:REL*2..2]-(b:End) + RETURN r + """ + Then the result should be: + | r | + | [[:REL {value:1}], [:REL {value:2}]] | + | [[:REL {value:2}], [:REL {value:1}]] | + And no side effects + + Scenario: Return relationships by collecting them as a list + Given an empty graph + And having executed: + """ + CREATE (s:Start)-[:REL {value: 1}]->(b:B)-[:REL {value: 2}]->(c:C) + """ + When executing query: + """ + MATCH (a:Start)-[r:REL*2..2]-(b) + RETURN r + """ + Then the result should be: + | r | + | [[:REL {value: 1}], [:REL {value: 2}]] | + And no side effects + + Scenario: Return a var length path + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS {value: 1}]->(b:B {name: 'B'})-[:KNOWS {value: 2}]->(c:C {name: 'C'}) + """ + When executing query: + """ + MATCH p = (n {name: 'A'})-[:KNOWS*1..2]->(x) + RETURN p + """ + Then the result should be: + | p | + | <(:A {name: 'A'})-[:KNOWS {value: 1}]->(:B {name: 'B'})> | + | <(:A {name: 'A'})-[:KNOWS {value: 1}]->(:B {name: 'B'})-[:KNOWS {value: 2}]->(:C {name: 'C'})> | + And no side effects + + Scenario: Return a var length path of length zero + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:REL]->(b:B) + """ + When executing query: + """ + MATCH p = (a)-[*0..1]->(b) + RETURN a, b, length(p) AS l + """ + Then the result should be: + | a | b | l | + | (:A) | (:A) | 0 | + | (:B) | (:B) | 0 | + | (:A) | (:B) | 1 | + And no side effects + + Scenario: Return a named var length path of length zero + Given an empty graph + And having executed: + """ + CREATE (a:A {name: 'A'})-[:KNOWS]->(b:B {name: 'B'})-[:FRIEND]->(c:C {name: 'C'}) + """ + When executing query: + """ + MATCH p = (a {name: 'A'})-[:KNOWS*0..1]->(b)-[:FRIEND*0..1]->(c) + RETURN p + """ + Then the result should be: + | p | + | <(:A {name: 'A'})> | + | <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})> | + | <(:A {name: 'A'})-[:KNOWS]->(:B {name: 'B'})-[:FRIEND]->(:C {name: 'C'})> | + And no side effects + + Scenario: Accept skip zero + Given any graph + When executing query: + """ + MATCH (n) + WHERE 1 = 0 + RETURN n SKIP 0 + """ + Then the result should be: + | n | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MatchAcceptance2.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MatchAcceptance2.feature new file mode 100644 index 000000000..54d2df647 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MatchAcceptance2.feature @@ -0,0 +1,1845 @@ +# +# 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: MatchAcceptance2 + + Scenario: Do not return non-existent nodes + Given an empty graph + When executing query: + """ + MATCH (n) + RETURN n + """ + Then the result should be: + | n | + And no side effects + + Scenario: Do not return non-existent relationships + Given an empty graph + When executing query: + """ + MATCH ()-[r]->() + RETURN r + """ + Then the result should be: + | r | + And no side effects + + Scenario: Do not fail when evaluating predicates with illegal operations if the AND'ed predicate evaluates to false + Given an empty graph + And having executed: + """ + CREATE (root:Root {name: 'x'}), + (child1:TextNode {id: 'text'}), + (child2:IntNode {id: 0}) + CREATE (root)-[:T]->(child1), + (root)-[:T]->(child2) + """ + When executing query: + """ + MATCH (:Root {name: 'x'})-->(i:TextNode) + WHERE i.id > 'te' + RETURN i + """ + Then the result should be: + | i | + | (:TextNode {id: 'text'}) | + And no side effects + + Scenario: Do not fail when evaluating predicates with illegal operations if the OR'd predicate evaluates to true + Given an empty graph + And having executed: + """ + CREATE (root:Root {name: 'x'}), + (child1:TextNode {id: 'text'}), + (child2:IntNode {id: 0}) + CREATE (root)-[:T]->(child1), + (root)-[:T]->(child2) + """ + When executing query: + """ + MATCH (:Root {name: 'x'})-->(i) + WHERE exists(i.id) OR i.id > 'te' + RETURN i + """ + Then the result should be: + | i | + | (:TextNode {id: 'text'}) | + | (:IntNode {id: 0}) | + And no side effects + + Scenario: Aggregation with named paths + Given an empty graph + And having executed: + """ + CREATE (n1 {num: 1}), (n2 {num: 2}), + (n3 {num: 3}), (n4 {num: 4}) + CREATE (n1)-[:T]->(n2), + (n3)-[:T]->(n4) + """ + When executing query: + """ + MATCH p = ()-[*]->() + WITH count(*) AS count, p AS p + WITH nodes(p) AS nodes + RETURN * + """ + Then the result should be: + | nodes | + | [({num: 1}), ({num: 2})] | + | [({num: 3}), ({num: 4})] | + And no side effects + + Scenario: Zero-length variable length pattern in the middle of the pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}), ({name: 'D'}), + ({name: 'E'}) + CREATE (a)-[:CONTAINS]->(b), + (b)-[:FRIEND]->(c) + """ + When executing query: + """ + MATCH (a {name: 'A'})-[:CONTAINS*0..1]->(b)-[:FRIEND*0..1]->(c) + RETURN a, b, c + """ + Then the result should be: + | a | b | c | + | ({name: 'A'}) | ({name: 'A'}) | ({name: 'A'}) | + | ({name: 'A'}) | ({name: 'B'}) | ({name: 'B'}) | + | ({name: 'A'}) | ({name: 'B'}) | ({name: 'C'}) | + And no side effects + + Scenario: Simple variable length pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}), (d {name: 'D'}) + CREATE (a)-[:CONTAINS]->(b), + (b)-[:CONTAINS]->(c), + (c)-[:CONTAINS]->(d) + """ + When executing query: + """ + MATCH (a {name: 'A'})-[*]->(x) + RETURN x + """ + Then the result should be: + | x | + | ({name: 'B'}) | + | ({name: 'C'}) | + | ({name: 'D'}) | + And no side effects + + Scenario: Variable length relationship without lower bound + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b), + (b)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH p = ({name: 'A'})-[:KNOWS*..2]->() + RETURN p + """ + Then the result should be: + | p | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})> | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})-[:KNOWS]->({name: 'C'})> | + And no side effects + + Scenario: Variable length relationship without bounds + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b), + (b)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH p = ({name: 'A'})-[:KNOWS*..]->() + RETURN p + """ + Then the result should be: + | p | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})> | + | <({name: 'A'})-[:KNOWS]->({name: 'B'})-[:KNOWS]->({name: 'C'})> | + And no side effects + + Scenario: Returning bound nodes that are not part of the pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (c {name: 'C'}) + MATCH (a)-->(b) + RETURN a, b, c + """ + Then the result should be: + | a | b | c | + | ({name: 'A'}) | ({name: 'B'}) | ({name: 'C'}) | + And no side effects + + Scenario: Two bound nodes pointing to the same node + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), + (x1 {name: 'x1'}), (x2 {name: 'x2'}) + CREATE (a)-[:KNOWS]->(x1), + (a)-[:KNOWS]->(x2), + (b)-[:KNOWS]->(x1), + (b)-[:KNOWS]->(x2) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (b {name: 'B'}) + MATCH (a)-->(x)<-->(b) + RETURN x + """ + Then the result should be: + | x | + | ({name: 'x1'}) | + | ({name: 'x2'}) | + And no side effects + + Scenario: Three bound nodes pointing to the same node + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}), + (x1 {name: 'x1'}), (x2 {name: 'x2'}) + CREATE (a)-[:KNOWS]->(x1), + (a)-[:KNOWS]->(x2), + (b)-[:KNOWS]->(x1), + (b)-[:KNOWS]->(x2), + (c)-[:KNOWS]->(x1), + (c)-[:KNOWS]->(x2) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + MATCH (a)-->(x), (b)-->(x), (c)-->(x) + RETURN x + """ + Then the result should be: + | x | + | ({name: 'x1'}) | + | ({name: 'x2'}) | + And no side effects + + Scenario: Three bound nodes pointing to the same node with extra connections + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}), + (d {name: 'd'}), (e {name: 'e'}), (f {name: 'f'}), + (g {name: 'g'}), (h {name: 'h'}), (i {name: 'i'}), + (j {name: 'j'}), (k {name: 'k'}) + CREATE (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e), + (a)-[:KNOWS]->(f), + (a)-[:KNOWS]->(g), + (a)-[:KNOWS]->(i), + (b)-[:KNOWS]->(d), + (b)-[:KNOWS]->(e), + (b)-[:KNOWS]->(f), + (b)-[:KNOWS]->(h), + (b)-[:KNOWS]->(k), + (c)-[:KNOWS]->(d), + (c)-[:KNOWS]->(e), + (c)-[:KNOWS]->(h), + (c)-[:KNOWS]->(g), + (c)-[:KNOWS]->(j) + """ + When executing query: + """ + MATCH (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}) + MATCH (a)-->(x), (b)-->(x), (c)-->(x) + RETURN x + """ + Then the result should be: + | x | + | ({name: 'd'}) | + | ({name: 'e'}) | + And no side effects + + Scenario: MATCH with OPTIONAL MATCH in longer pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + CREATE (a)-[:KNOWS]->(b), + (b)-[:KNOWS]->(c) + """ + When executing query: + """ + MATCH (a {name: 'A'}) + OPTIONAL MATCH (a)-[:KNOWS]->()-[:KNOWS]->(foo) + RETURN foo + """ + Then the result should be: + | foo | + | ({name: 'C'}) | + And no side effects + + Scenario: Optionally matching named paths + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + CREATE (a)-[:X]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (x) + WHERE x.name IN ['B', 'C'] + OPTIONAL MATCH p = (a)-->(x) + RETURN x, p + """ + Then the result should be: + | x | p | + | ({name: 'B'}) | <({name: 'A'})-[:X]->({name: 'B'})> | + | ({name: 'C'}) | null | + And no side effects + + Scenario: Optionally matching named paths with single and variable length patterns + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}) + CREATE (a)-[:X]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}) + OPTIONAL MATCH p = (a)-->(b)-[*]->(c) + RETURN p + """ + Then the result should be: + | p | + | null | + And no side effects + + Scenario: Optionally matching named paths with variable length patterns + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b {name: 'B'}), (c {name: 'C'}) + CREATE (a)-[:X]->(b) + """ + When executing query: + """ + MATCH (a {name: 'A'}), (x) + WHERE x.name IN ['B', 'C'] + OPTIONAL MATCH p = (a)-[r*]->(x) + RETURN r, x, p + """ + Then the result should be: + | r | x | p | + | [[:X]] | ({name: 'B'}) | <({name: 'A'})-[:X]->({name: 'B'})> | + | null | ({name: 'C'}) | null | + And no side effects + + Scenario: Matching variable length patterns from a bound node + Given an empty graph + And having executed: + """ + CREATE (a:A), (b), (c) + CREATE (a)-[:X]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH (a:A) + MATCH (a)-[r*2]->() + RETURN r + """ + Then the result should be (ignoring element order for lists): + | r | + | [[:X], [:Y]] | + And no side effects + + Scenario: Excluding connected nodes + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B {id: 1}), (:B {id: 2}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (a:A), (other:B) + OPTIONAL MATCH (a)-[r]->(other) + WITH other WHERE r IS NULL + RETURN other + """ + Then the result should be: + | other | + | (:B {id: 2}) | + And no side effects + + Scenario: Do not fail when predicates on optionally matched and missed nodes are invalid + Given an empty graph + And having executed: + """ + CREATE (a), (b {name: 'Mark'}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n)-->(x0) + OPTIONAL MATCH (x0)-->(x1) + WHERE x1.foo = 'bar' + RETURN x0.name + """ + Then the result should be: + | x0.name | + | 'Mark' | + And no side effects + + Scenario: MATCH and OPTIONAL MATCH on same pattern + Given an empty graph + And having executed: + """ + CREATE (a {name: 'A'}), (b:B {name: 'B'}), (c:C {name: 'C'}) + CREATE (a)-[:T]->(b), + (a)-[:T]->(c) + """ + When executing query: + """ + MATCH (a)-->(b) + WHERE b:B + OPTIONAL MATCH (a)-->(c) + WHERE c:C + RETURN a.name + """ + Then the result should be: + | a.name | + | 'A' | + And no side effects + + Scenario: Matching using an undirected pattern + Given an empty graph + And having executed: + """ + CREATE (:A {id: 0})-[:ADMIN]->(:B {id: 1}) + """ + When executing query: + """ + MATCH (a)-[:ADMIN]-(b) + WHERE a:A + RETURN a.id, b.id + """ + Then the result should be: + | a.id | b.id | + | 0 | 1 | + And no side effects + + Scenario: Matching all nodes + Given an empty graph + And having executed: + """ + CREATE (:A), (:B) + """ + When executing query: + """ + MATCH (n) + RETURN n + """ + Then the result should be: + | n | + | (:A) | + | (:B) | + And no side effects + + Scenario: Comparing nodes for equality + Given an empty graph + And having executed: + """ + CREATE (:A), (:B) + """ + When executing query: + """ + MATCH (a), (b) + WHERE a <> b + RETURN a, b + """ + Then the result should be: + | a | b | + | (:A) | (:B) | + | (:B) | (:A) | + And no side effects + + Scenario: Matching using self-referencing pattern returns no result + Given an empty graph + And having executed: + """ + CREATE (a), (b), (c) + CREATE (a)-[:T]->(b), + (b)-[:T]->(c) + """ + When executing query: + """ + MATCH (a)-->(b), (b)-->(b) + RETURN b + """ + Then the result should be: + | b | + And no side effects + + Scenario: Variable length relationship in OPTIONAL MATCH + Given an empty graph + And having executed: + """ + CREATE (:A), (:B) + """ + When executing query: + """ + MATCH (a:A), (b:B) + OPTIONAL MATCH (a)-[r*]-(b) + WHERE r IS NULL + AND a <> b + RETURN b + """ + Then the result should be: + | b | + | (:B) | + And no side effects + + Scenario: Matching using relationship predicate with multiples of the same type + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (a)-[:T|:T]->(b) + RETURN b + """ + Then the result should be: + | b | + | (:B) | + And no side effects + + Scenario: ORDER BY with LIMIT + Given an empty graph + And having executed: + """ + CREATE (a:A), (n1 {x: 1}), (n2 {x: 2}), + (m1), (m2) + CREATE (a)-[:T]->(n1), + (n1)-[:T]->(m1), + (a)-[:T]->(n2), + (n2)-[:T]->(m2) + """ + When executing query: + """ + MATCH (a:A)-->(n)-->(m) + RETURN n.x, count(*) + ORDER BY n.x + LIMIT 1000 + """ + Then the result should be, in order: + | n.x | count(*) | + | 1 | 1 | + | 2 | 1 | + And no side effects + + Scenario: Simple node property predicate + Given an empty graph + And having executed: + """ + CREATE ({foo: 'bar'}) + """ + When executing query: + """ + MATCH (n) + WHERE n.foo = 'bar' + RETURN n + """ + Then the result should be: + | n | + | ({foo: 'bar'}) | + And no side effects + + Scenario: Handling direction of named paths + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:T]->(b:B) + """ + When executing query: + """ + MATCH p = (b)<--(a) + RETURN p + """ + Then the result should be: + | p | + | <(:B)<-[:T]-(:A)> | + And no side effects + + Scenario: Simple OPTIONAL MATCH on empty graph + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (n) + RETURN n + """ + Then the result should be: + | n | + | null | + And no side effects + + Scenario: OPTIONAL MATCH with previously bound nodes + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + OPTIONAL MATCH (n)-[:NOT_EXIST]->(x) + RETURN n, x + """ + Then the result should be: + | n | x | + | () | null | + And no side effects + + Scenario: `collect()` filtering nulls + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + OPTIONAL MATCH (n)-[:NOT_EXIST]->(x) + RETURN n, collect(x) + """ + Then the result should be: + | n | collect(x) | + | () | [] | + And no side effects + + Scenario: Multiple anonymous nodes in a pattern + Given an empty graph + And having executed: + """ + CREATE (:A) + """ + When executing query: + """ + MATCH (a)<--()<--(b)-->()-->(c) + WHERE a:A + RETURN c + """ + Then the result should be: + | c | + And no side effects + + Scenario: Matching a relationship pattern using a label predicate + Given an empty graph + And having executed: + """ + CREATE (a), (b1:Foo), (b2) + CREATE (a)-[:T]->(b1), + (a)-[:T]->(b2) + """ + When executing query: + """ + MATCH (a)-->(b:Foo) + RETURN b + """ + Then the result should be: + | b | + | (:Foo) | + And no side effects + + Scenario: Matching a relationship pattern using a label predicate on both sides + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T1]->(:B), + (:B)-[:T2]->(:A), + (:B)-[:T3]->(:B), + (:A)-[:T4]->(:A) + """ + When executing query: + """ + MATCH (:A)-[r]->(:B) + RETURN r + """ + Then the result should be: + | r | + | [:T1] | + And no side effects + + Scenario: Matching nodes using multiple labels + Given an empty graph + And having executed: + """ + CREATE (:A:B:C), (:A:B), (:A:C), (:B:C), + (:A), (:B), (:C) + """ + When executing query: + """ + MATCH (a:A:B:C) + RETURN a + """ + Then the result should be: + | a | + | (:A:B:C) | + And no side effects + + Scenario: Returning label predicate expression + Given an empty graph + And having executed: + """ + CREATE (), (:Foo) + """ + When executing query: + """ + MATCH (n) + RETURN (n:Foo) + """ + Then the result should be: + | (n:Foo) | + | true | + | false | + And no side effects + + Scenario: Matching with many predicates and larger pattern + Given an empty graph + And having executed: + """ + CREATE (advertiser {name: 'advertiser1', id: 0}), + (thing {name: 'Color', id: 1}), + (red {name: 'red'}), + (p1 {name: 'product1'}), + (p2 {name: 'product4'}) + CREATE (advertiser)-[:ADV_HAS_PRODUCT]->(p1), + (advertiser)-[:ADV_HAS_PRODUCT]->(p2), + (thing)-[:AA_HAS_VALUE]->(red), + (p1)-[:AP_HAS_VALUE]->(red), + (p2)-[:AP_HAS_VALUE]->(red) + """ + And parameters are: + | 1 | 0 | + | 2 | 1 | + When executing query: + """ + MATCH (advertiser)-[:ADV_HAS_PRODUCT]->(out)-[:AP_HAS_VALUE]->(red)<-[:AA_HAS_VALUE]-(a) + WHERE advertiser.id = $1 + AND a.id = $2 + AND red.name = 'red' + AND out.name = 'product1' + RETURN out.name + """ + Then the result should be: + | out.name | + | 'product1' | + And no side effects + + Scenario: Matching using a simple pattern with label predicate + Given an empty graph + And having executed: + """ + CREATE (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}), + (c), (d) + CREATE (a)-[:T]->(c), + (b)-[:T]->(d) + """ + When executing query: + """ + MATCH (n:Person)-->() + WHERE n.name = 'Bob' + RETURN n + """ + Then the result should be: + | n | + | (:Person {name: 'Bob'}) | + And no side effects + + Scenario: Matching disconnected patterns + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:T]->(b), + (a)-[:T]->(c) + """ + When executing query: + """ + MATCH (a)-->(b) + MATCH (c)-->(d) + RETURN a, b, c, d + """ + Then the result should be: + | a | b | c | d | + | (:A) | (:B) | (:A) | (:B) | + | (:A) | (:B) | (:A) | (:C) | + | (:A) | (:C) | (:A) | (:B) | + | (:A) | (:C) | (:A) | (:C) | + And no side effects + + Scenario: Non-optional matches should not return nulls + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B {id: 1}), (c:C {id: 2}), (d:D) + CREATE (a)-[:T]->(b), + (a)-[:T]->(c), + (a)-[:T]->(d), + (b)-[:T]->(c), + (b)-[:T]->(d), + (c)-[:T]->(d) + """ + When executing query: + """ + MATCH (a)--(b)--(c)--(d)--(a), (b)--(d) + WHERE a.id = 1 + AND c.id = 2 + RETURN d + """ + Then the result should be: + | d | + | (:A) | + | (:D) | + And no side effects + + Scenario: Handling cyclic patterns + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}) + CREATE (a)-[:A]->(b), + (b)-[:B]->(a), + (b)-[:B]->(c) + """ + When executing query: + """ + MATCH (a)-[:A]->()-[:B]->(a) + RETURN a.name + """ + Then the result should be: + | a.name | + | 'a' | + And no side effects + + Scenario: Handling cyclic patterns when separated into two parts + Given an empty graph + And having executed: + """ + CREATE (a {name: 'a'}), (b {name: 'b'}), (c {name: 'c'}) + CREATE (a)-[:A]->(b), + (b)-[:B]->(a), + (b)-[:B]->(c) + """ + When executing query: + """ + MATCH (a)-[:A]->(b), (b)-[:B]->(a) + RETURN a.name + """ + Then the result should be: + | a.name | + | 'a' | + And no side effects + + Scenario: Handling fixed-length variable length pattern + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH (a)-[r*1..1]->(b) + RETURN r + """ + Then the result should be: + | r | + | [[:T]] | + And no side effects + + Scenario: Matching from null nodes should return no results owing to finding no matches + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (a) + WITH a + MATCH (a)-->(b) + RETURN b + """ + Then the result should be: + | b | + And no side effects + + Scenario: Matching from null nodes should return no results owing to matches being filtered out + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + OPTIONAL MATCH (a:Label) + WITH a + MATCH (a)-->(b) + RETURN b + """ + Then the result should be: + | b | + And no side effects + + Scenario: Optionally matching from null nodes should return null + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (a) + WITH a + OPTIONAL MATCH (a)-->(b) + RETURN b + """ + Then the result should be: + | b | + | null | + And no side effects + + Scenario: OPTIONAL MATCH returns null + Given an empty graph + When executing query: + """ + OPTIONAL MATCH (a) + RETURN a + """ + Then the result should be: + | a | + | null | + And no side effects + + Scenario: Zero-length named path + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH p = (a) + RETURN p + """ + Then the result should be: + | p | + | <()> | + And no side effects + + Scenario: Variable-length named path + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH p = ()-[*0..]->() + RETURN p + """ + Then the result should be: + | p | + | <()> | + And no side effects + + Scenario: Matching with aggregation + Given an empty graph + And having executed: + """ + CREATE ({prop: 42}) + """ + When executing query: + """ + MATCH (n) + RETURN n.prop AS n, count(n) AS count + """ + Then the result should be: + | n | count | + | 42 | 1 | + And no side effects + + Scenario: Matching using a relationship that is already bound + Given an empty graph + And having executed: + """ + CREATE ()-[:T1]->(), + ()-[:T2]->() + """ + When executing query: + """ + MATCH ()-[r1]->() + WITH r1 AS r2 + MATCH ()-[r2]->() + RETURN r2 AS rel + """ + Then the result should be: + | rel | + | [:T1] | + | [:T2] | + And no side effects + + Scenario: Matching using a relationship that is already bound, in conjunction with aggregation + Given an empty graph + And having executed: + """ + CREATE ()-[:T1]->(), + ()-[:T2]->() + """ + When executing query: + """ + MATCH ()-[r1]->() + WITH r1 AS r2, count(*) AS c + ORDER BY c + MATCH ()-[r2]->() + RETURN r2 AS rel + """ + Then the result should be: + | rel | + | [:T1] | + | [:T2] | + And no side effects + + Scenario: Matching using a relationship that is already bound, in conjunction with aggregation and ORDER BY + Given an empty graph + And having executed: + """ + CREATE ()-[:T1 {id: 0}]->(), + ()-[:T2 {id: 1}]->() + """ + When executing query: + """ + MATCH (a)-[r]->(b) + WITH a, r, b, count(*) AS c + ORDER BY c + MATCH (a)-[r]->(b) + RETURN r AS rel + ORDER BY rel.id + """ + Then the result should be, in order: + | rel | + | [:T1 {id: 0}] | + | [:T2 {id: 1}] | + And no side effects + + Scenario: Matching with LIMIT and optionally matching using a relationship that is already bound + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH ()-[r]->() + WITH r + LIMIT 1 + OPTIONAL MATCH (a2)-[r]->(b2) + RETURN a2, r, b2 + """ + Then the result should be: + | a2 | r | b2 | + | (:A) | [:T] | (:B) | + And no side effects + + Scenario: Matching with LIMIT and optionally matching using a relationship and node that are both already bound + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + OPTIONAL MATCH (a1)-[r]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + | (:A) | [:T] | (:B) | + And no side effects + + Scenario: Matching with LIMIT, then matching again using a relationship and node that are both already bound along with an additional predicate + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + MATCH (a1:X)-[r]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + And no side effects + + Scenario: Matching with LIMIT and predicates, then matching again using a relationship and node that are both already bound along with a duplicate predicate + Given an empty graph + And having executed: + """ + CREATE (:X:Y)-[:T]->() + """ + When executing query: + """ + MATCH (a1:X:Y)-[r]->() + WITH r, a1 + LIMIT 1 + MATCH (a1:Y)-[r]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + | (:X:Y) | [:T] | () | + And no side effects + + Scenario: Matching twice with conflicting relationship types on same relationship + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH (a1)-[r:T]->() + WITH r, a1 + LIMIT 1 + MATCH (a1)-[r:Y]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + And no side effects + + Scenario: Matching twice with duplicate relationship types on same relationship + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r:T]->() WITH r, a1 + LIMIT 1 + MATCH (a1)-[r:T]->(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + | (:A) | [:T] | (:B) | + And no side effects + + Scenario: Matching relationships into a list and matching variable length using the list + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:Y]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH ()-[r1]->()-[r2]->() + WITH [r1, r2] AS rs + LIMIT 1 + MATCH (first)-[rs*]->(second) + RETURN first, second + """ + Then the result should be: + | first | second | + | (:A) | (:C) | + And no side effects + + Scenario: Matching relationships into a list and matching variable length using the list, with bound nodes + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:Y]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH (a)-[r1]->()-[r2]->(b) + WITH [r1, r2] AS rs, a AS first, b AS second + LIMIT 1 + MATCH (first)-[rs*]->(second) + RETURN first, second + """ + Then the result should be: + | first | second | + | (:A) | (:C) | + And no side effects + + Scenario: Matching relationships into a list and matching variable length using the list, with bound nodes, wrong direction + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (a)-[:Y]->(b), + (b)-[:Y]->(c) + """ + When executing query: + """ + MATCH (a)-[r1]->()-[r2]->(b) + WITH [r1, r2] AS rs, a AS second, b AS first + LIMIT 1 + MATCH (first)-[rs*]->(second) + RETURN first, second + """ + Then the result should be: + | first | second | + And no side effects + + Scenario: Matching and optionally matching with bound nodes in reverse direction + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + OPTIONAL MATCH (a1)<-[r]-(b2) + RETURN a1, r, b2 + """ + Then the result should be: + | a1 | r | b2 | + | (:A) | [:T] | null | + And no side effects + + Scenario: Matching and optionally matching with unbound nodes and equality predicate in reverse direction + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a1)-[r]->() + WITH r, a1 + LIMIT 1 + OPTIONAL MATCH (a2)<-[r]-(b2) + WHERE a1 = a2 + RETURN a1, r, b2, a2 + """ + Then the result should be: + | a1 | r | b2 | a2 | + | (:A) | [:T] | null | null | + And no side effects + + Scenario: Fail when using property access on primitive type + Given an empty graph + And having executed: + """ + CREATE ({prop: 42}) + """ + When executing query: + """ + MATCH (n) + WITH n.prop AS n2 + RETURN n2.prop + """ + Then a TypeError should be raised at runtime: PropertyAccessOnNonMap + + Scenario: Matching and returning ordered results, with LIMIT + Given an empty graph + And having executed: + """ + CREATE ({bar: 1}), ({bar: 3}), ({bar: 2}) + """ + When executing query: + """ + MATCH (foo) + RETURN foo.bar AS x + ORDER BY x DESC + LIMIT 4 + """ + Then the result should be, in order: + | x | + | 3 | + | 2 | + | 1 | + And no side effects + + Scenario: Counting an empty graph + Given an empty graph + When executing query: + """ + MATCH (a) + RETURN count(a) > 0 + """ + Then the result should be: + | count(a) > 0 | + | false | + And no side effects + + Scenario: Matching variable length pattern with property predicate + Given an empty graph + And having executed: + """ + CREATE (a:Artist:A), (b:Artist:B), (c:Artist:C) + CREATE (a)-[:WORKED_WITH {year: 1987}]->(b), + (b)-[:WORKED_WITH {year: 1988}]->(c) + """ + When executing query: + """ + MATCH (a:Artist)-[:WORKED_WITH* {year: 1988}]->(b:Artist) + RETURN * + """ + Then the result should be: + | a | b | + | (:Artist:B) | (:Artist:C) | + And no side effects + + Scenario: Variable length pattern checking labels on endnodes + Given an empty graph + And having executed: + """ + CREATE (a:Label {id: 0}), (b:Label {id: 1}), (c:Label {id: 2}) + CREATE (a)-[:T]->(b), + (b)-[:T]->(c) + """ + When executing query: + """ + MATCH (a), (b) + WHERE a.id = 0 + AND (a)-[:T]->(b:Label) + OR (a)-[:T*]->(b:MissingLabel) + RETURN DISTINCT b + """ + Then the result should be: + | b | + | (:Label {id: 1}) | + And no side effects + + Scenario: Variable length pattern with label predicate on both sides + Given an empty graph + And having executed: + """ + CREATE (a:Blue), (b:Red), (c:Green), (d:Yellow) + CREATE (a)-[:T]->(b), + (b)-[:T]->(c), + (b)-[:T]->(d) + """ + When executing query: + """ + MATCH (a:Blue)-[r*]->(b:Green) + RETURN count(r) + """ + Then the result should be: + | count(r) | + | 1 | + And no side effects + + Scenario: Undirected named path + Given an empty graph + And having executed: + """ + CREATE (a:Movie), (b) + CREATE (b)-[:T]->(a) + """ + When executing query: + """ + MATCH p = (n:Movie)--(m) + RETURN p + LIMIT 1 + """ + Then the result should be: + | p | + | <(:Movie)<-[:T]-()> | + And no side effects + + Scenario: Named path with WITH + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH p = (a) + WITH p + RETURN p + """ + Then the result should be: + | p | + | <()> | + And no side effects + + Scenario: Named path with alternating directed/undirected relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C) + CREATE (b)-[:T]->(a), + (c)-[:T]->(b) + """ + When executing query: + """ + MATCH p = (n)-->(m)--(o) + RETURN p + """ + Then the result should be: + | p | + | <(:C)-[:T]->(:B)-[:T]->(:A)> | + And no side effects + + Scenario: Named path with multiple alternating directed/undirected relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C), (d:D) + CREATE (b)-[:T]->(a), + (c)-[:T]->(b), + (d)-[:T]->(c) + """ + When executing query: + """ + MATCH path = (n)-->(m)--(o)--(p) + RETURN path + """ + Then the result should be: + | path | + | <(:D)-[:T]->(:C)-[:T]->(:B)-[:T]->(:A)> | + And no side effects + + Scenario: Named path with undirected fixed variable length pattern + Given an empty graph + And having executed: + """ + CREATE (db1:Start), (db2:End), (mid), (other) + CREATE (mid)-[:CONNECTED_TO]->(db1), + (mid)-[:CONNECTED_TO]->(db2), + (mid)-[:CONNECTED_TO]->(db2), + (mid)-[:CONNECTED_TO]->(other), + (mid)-[:CONNECTED_TO]->(other) + """ + When executing query: + """ + MATCH topRoute = (:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO*3..3]-(:End) + RETURN topRoute + """ + Then the result should be: + | topRoute | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + | <(:Start)<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->()<-[:CONNECTED_TO]-()-[:CONNECTED_TO]->(:End)> | + And no side effects + + Scenario: Returning a node property value + Given an empty graph + And having executed: + """ + CREATE ({prop: 1}) + """ + When executing query: + """ + MATCH (a) + RETURN a.prop + """ + Then the result should be: + | a.prop | + | 1 | + And no side effects + + Scenario: Returning a relationship property value + Given an empty graph + And having executed: + """ + CREATE ()-[:T {prop: 1}]->() + """ + When executing query: + """ + MATCH ()-[r]->() + RETURN r.prop + """ + Then the result should be: + | r.prop | + | 1 | + And no side effects + + Scenario: Projecting nodes and relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (a)-[r]->() + RETURN a AS foo, r AS bar + """ + Then the result should be: + | foo | bar | + | (:A) | [:T] | + And no side effects + + Scenario: Missing node property should become null + Given an empty graph + And having executed: + """ + CREATE ({foo: 1}) + """ + When executing query: + """ + MATCH (a) + RETURN a.bar + """ + Then the result should be: + | a.bar | + | null | + And no side effects + + Scenario: Missing relationship property should become null + Given an empty graph + And having executed: + """ + CREATE ()-[:T {foo: 1}]->() + """ + When executing query: + """ + MATCH ()-[r]->() + RETURN r.bar + """ + Then the result should be: + | r.bar | + | null | + And no side effects + + Scenario: Returning multiple node property values + Given an empty graph + And having executed: + """ + CREATE ({name: 'Philip J. Fry', age: 2046, seasons: [1, 2, 3, 4, 5, 6, 7]}) + """ + When executing query: + """ + MATCH (a) + RETURN a.name, a.age, a.seasons + """ + Then the result should be: + | a.name | a.age | a.seasons | + | 'Philip J. Fry' | 2046 | [1, 2, 3, 4, 5, 6, 7] | + And no side effects + + Scenario: Adding a property and a literal in projection + Given an empty graph + And having executed: + """ + CREATE ({prop: 1}) + """ + When executing query: + """ + MATCH (a) + RETURN a.prop + 1 AS foo + """ + Then the result should be: + | foo | + | 2 | + And no side effects + + Scenario: Adding list properties in projection + Given an empty graph + And having executed: + """ + CREATE ({prop1: [1, 2, 3], prop2: [4, 5]}) + """ + When executing query: + """ + MATCH (a) + RETURN a.prop2 + a.prop1 AS foo + """ + Then the result should be: + | foo | + | [4, 5, 1, 2, 3] | + And no side effects + + Scenario: Variable length relationship variables are lists of relationships + Given an empty graph + And having executed: + """ + CREATE (a), (b), (c) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH ()-[r*0..1]-() + RETURN last(r) AS l + """ + Then the result should be: + | l | + | [:T] | + | [:T] | + | null | + | null | + | null | + And no side effects + + Scenario: Variable length patterns and nulls + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + """ + When executing query: + """ + MATCH (a:A) + OPTIONAL MATCH (a)-[:FOO]->(b:B) + OPTIONAL MATCH (b)<-[:BAR*]-(c:B) + RETURN a, b, c + """ + Then the result should be: + | a | b | c | + | (:A) | null | null | + And no side effects + + Scenario: Projecting a list of nodes and relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n)-[r]->(m) + RETURN [n, r, m] AS r + """ + Then the result should be: + | r | + | [(:A), [:T], (:B)] | + And no side effects + + Scenario: Projecting a map of nodes and relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n)-[r]->(m) + RETURN {node1: n, rel: r, node2: m} AS m + """ + Then the result should be: + | m | + | {node1: (:A), rel: [:T], node2: (:B)} | + And no side effects + + Scenario: Respecting direction when matching existing path + Given an empty graph + And having executed: + """ + CREATE (a {prop: 'a'}), (b {prop: 'b'}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH p = ({prop: 'a'})-->({prop: 'b'}) + RETURN p + """ + Then the result should be: + | p | + | <({prop: 'a'})-[:T]->({prop: 'b'})> | + And no side effects + + Scenario: Respecting direction when matching non-existent path + Given an empty graph + And having executed: + """ + CREATE (a {prop: 'a'}), (b {prop: 'b'}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH p = ({prop: 'a'})<--({prop: 'b'}) + RETURN p + """ + Then the result should be: + | p | + And no side effects + + Scenario: Respecting direction when matching non-existent path with multiple directions + Given an empty graph + And having executed: + """ + CREATE (a), (b) + CREATE (a)-[:T]->(b), + (b)-[:T]->(a) + """ + When executing query: + """ + MATCH p = (n)-->(k)<--(n) + RETURN p + """ + Then the result should be: + | p | + And no side effects + + Scenario: Matching path with both directions should respect other directions + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T1]->(b), + (b)-[:T2]->(a) + """ + When executing query: + """ + MATCH p = (n)<-->(k)<--(n) + RETURN p + """ + Then the result should be: + | p | + | <(:A)<-[:T2]-(:B)<-[:T1]-(:A)> | + | <(:B)<-[:T1]-(:A)<-[:T2]-(:B)> | + And no side effects + + Scenario: Matching path with multiple bidirectional relationships + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T1]->(b), + (b)-[:T2]->(a) + """ + When executing query: + """ + MATCH p=(n)<-->(k)<-->(n) + RETURN p + """ + Then the result should be: + | p | + | <(:A)<-[:T2]-(:B)<-[:T1]-(:A)> | + | <(:A)-[:T1]->(:B)-[:T2]->(:A)> | + | <(:B)<-[:T1]-(:A)<-[:T2]-(:B)> | + | <(:B)-[:T2]->(:A)-[:T1]->(:B)> | + And no side effects + + Scenario: Matching nodes with many labels + Given an empty graph + And having executed: + """ + CREATE (a:A:B:C:D:E:F:G:H:I:J:K:L:M), + (b:U:V:W:X:Y:Z) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n:A:B:C:D:E:F:G:H:I:J:K:L:M)-[:T]->(m:Z:Y:X:W:V:U) + RETURN n, m + """ + Then the result should be: + | n | m | + | (:A:B:C:D:E:F:G:H:I:J:K:L:M) | (:Z:Y:X:W:V:U) | + And no side effects + + Scenario: Matching longer variable length paths + Given an empty graph + And having executed: + """ + CREATE (a {prop: 'start'}), (b {prop: 'end'}) + WITH * + UNWIND range(1, 20) AS i + CREATE (n {prop: i}) + WITH [a] + collect(n) + [b] AS nodeList + UNWIND range(0, size(nodeList) - 2, 1) AS i + WITH nodeList[i] AS n1, nodeList[i+1] AS n2 + CREATE (n1)-[:T]->(n2) + """ + When executing query: + """ + MATCH (n {prop: 'start'})-[:T*]->(m {prop: 'end'}) + RETURN m + """ + Then the result should be: + | m | + | ({prop: 'end'}) | + And no side effects + + Scenario: Counting rows after MATCH, MERGE, OPTIONAL MATCH + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T1]->(b), + (b)-[:T2]->(a) + """ + When executing query: + """ + MATCH (a) + MERGE (b) + WITH * + OPTIONAL MATCH (a)--(b) + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 6 | + And no side effects + + Scenario: Matching a self-loop + Given an empty graph + And having executed: + """ + CREATE (a) + CREATE (a)-[:T]->(a) + """ + When executing query: + """ + MATCH ()-[r]-() + RETURN type(r) AS r + """ + Then the result should be: + | r | + | 'T' | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MatchingSelfRelationships.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MatchingSelfRelationships.feature new file mode 100644 index 000000000..e47a5c904 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MatchingSelfRelationships.feature @@ -0,0 +1,338 @@ +# +# Copyright 2016 "Neo Technology", +# Network Engine for Objects in Lund AB (http://neotechnology.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Feature: MatchingSelfRelationships + + Scenario: Undirected match in self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (a)-[r]-(b) + RETURN a, r, b + """ + Then the result should be: + | a | r | b | + | (:A) | [:LOOP] | (:A) | + And no side effects + + Scenario: Undirected match in self-relationship graph, count + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH ()--() + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 1 | + And no side effects + + Scenario: Undirected match of self-relationship in self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (n)-[r]-(n) + RETURN n, r + """ + Then the result should be: + | n | r | + | (:A) | [:LOOP] | + And no side effects + + Scenario: Undirected match of self-relationship in self-relationship graph, count + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (n)--(n) + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 1 | + And no side effects + + Scenario: Undirected match on simple relationship graph + Given an empty graph + And having executed: + """ + CREATE (:A)-[:LOOP]->(:B) + """ + When executing query: + """ + MATCH (a)-[r]-(b) + RETURN a, r, b + """ + Then the result should be: + | a | r | b | + | (:A) | [:LOOP] | (:B) | + | (:B) | [:LOOP] | (:A) | + And no side effects + + Scenario: Undirected match on simple relationship graph, count + Given an empty graph + And having executed: + """ + CREATE (:A)-[:LOOP]->(:B) + """ + When executing query: + """ + MATCH ()--() + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 2 | + And no side effects + + Scenario: Directed match on self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (a)-[r]->(b) + RETURN a, r, b + """ + Then the result should be: + | a | r | b | + | (:A) | [:LOOP] | (:A) | + And no side effects + + Scenario: Directed match on self-relationship graph, count + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH ()-->() + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 1 | + And no side effects + + Scenario: Directed match of self-relationship on self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (n)-[r]->(n) + RETURN n, r + """ + Then the result should be: + | n | r | + | (:A) | [:LOOP] | + And no side effects + + Scenario: Directed match of self-relationship on self-relationship graph, count + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (n)-->(n) + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 1 | + And no side effects + + Scenario: Counting undirected self-relationships in self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (n)-[r]-(n) + RETURN count(r) + """ + Then the result should be: + | count(r) | + | 1 | + And no side effects + + Scenario: Counting distinct undirected self-relationships in self-relationship graph + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a) + """ + When executing query: + """ + MATCH (n)-[r]-(n) + RETURN count(DISTINCT r) + """ + Then the result should be: + | count(DISTINCT r) | + | 1 | + And no side effects + + Scenario: Directed match of a simple relationship + Given an empty graph + And having executed: + """ + CREATE (:A)-[:LOOP]->(:B) + """ + When executing query: + """ + MATCH (a)-[r]->(b) + RETURN a, r, b + """ + Then the result should be: + | a | r | b | + | (:A) | [:LOOP] | (:B) | + And no side effects + + Scenario: Directed match of a simple relationship, count + Given an empty graph + And having executed: + """ + CREATE (:A)-[:LOOP]->(:B) + """ + When executing query: + """ + MATCH ()-->() + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 1 | + And no side effects + + Scenario: Counting directed self-relationships + Given an empty graph + And having executed: + """ + CREATE (a:A)-[:LOOP]->(a), + ()-[:T]->() + """ + When executing query: + """ + MATCH (n)-[r]->(n) + RETURN count(r) + """ + Then the result should be: + | count(r) | + | 1 | + And no side effects + + Scenario: Mixing directed and undirected pattern parts with self-relationship, simple + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T1]->(l:Looper), + (l)-[:LOOP]->(l), + (l)-[:T2]->(:B) + """ + When executing query: + """ + MATCH (x:A)-[r1]->(y)-[r2]-(z) + RETURN x, r1, y, r2, z + """ + Then the result should be: + | x | r1 | y | r2 | z | + | (:A) | [:T1] | (:Looper) | [:LOOP] | (:Looper) | + | (:A) | [:T1] | (:Looper) | [:T2] | (:B) | + And no side effects + + Scenario: Mixing directed and undirected pattern parts with self-relationship, count + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T1]->(l:Looper), + (l)-[:LOOP]->(l), + (l)-[:T2]->(:B) + """ + When executing query: + """ + MATCH (:A)-->()--() + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 2 | + And no side effects + + Scenario: Mixing directed and undirected pattern parts with self-relationship, undirected + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T1]->(l:Looper), + (l)-[:LOOP]->(l), + (l)-[:T2]->(:B) + """ + When executing query: + """ + MATCH (x)-[r1]-(y)-[r2]-(z) + RETURN x, r1, y, r2, z + """ + Then the result should be: + | x | r1 | y | r2 | z | + | (:A) | [:T1] | (:Looper) | [:LOOP] | (:Looper) | + | (:A) | [:T1] | (:Looper) | [:T2] | (:B) | + | (:Looper) | [:LOOP] | (:Looper) | [:T1] | (:A) | + | (:Looper) | [:LOOP] | (:Looper) | [:T2] | (:B) | + | (:B) | [:T2] | (:Looper) | [:LOOP] | (:Looper) | + | (:B) | [:T2] | (:Looper) | [:T1] | (:A) | + And no side effects + + Scenario: Mixing directed and undirected pattern parts with self-relationship, undirected count + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T1]->(l:Looper), + (l)-[:LOOP]->(l), + (l)-[:T2]->(:B) + """ + When executing query: + """ + MATCH ()-[]-()-[]-() + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 6 | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MergeIntoAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MergeIntoAcceptance.feature new file mode 100644 index 000000000..d4cfdc662 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MergeIntoAcceptance.feature @@ -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'] | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MergeNodeAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MergeNodeAcceptance.feature new file mode 100644 index 000000000..2f3fde3a3 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MergeNodeAcceptance.feature @@ -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 | + diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MergeRelationshipAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MergeRelationshipAcceptance.feature new file mode 100644 index 000000000..b734c9952 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MergeRelationshipAcceptance.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MiscellaneousErrorAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MiscellaneousErrorAcceptance.feature new file mode 100644 index 000000000..5246b7558 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/MiscellaneousErrorAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/NullAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/NullAcceptance.feature new file mode 100644 index 000000000..ae2112c19 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/NullAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/OptionalMatch.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/OptionalMatch.feature new file mode 100644 index 000000000..a32ff63ff --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/OptionalMatch.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/OptionalMatchAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/OptionalMatchAcceptance.feature new file mode 100644 index 000000000..3f448749d --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/OptionalMatchAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/OrderByAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/OrderByAcceptance.feature new file mode 100644 index 000000000..3136cfd2f --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/OrderByAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/PathEquality.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/PathEquality.feature new file mode 100644 index 000000000..490640d43 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/PathEquality.feature @@ -0,0 +1,35 @@ +# +# Copyright 2017 "Neo Technology", +# Network Engine for Objects in Lund AB (http://neotechnology.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Feature: PathEquality + + Scenario: Direction of traversed relationship is not significant for path equality, simple + Given an empty graph + And having executed: + """ + CREATE (n:A)-[:LOOP]->(n) + """ + When executing query: + """ + MATCH p1 = (:A)-->() + MATCH p2 = (:A)<--() + RETURN p1 = p2 + """ + Then the result should be: + | p1 = p2 | + | true | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/PatternComprehension.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/PatternComprehension.feature new file mode 100644 index 000000000..25071bd6d --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/PatternComprehension.feature @@ -0,0 +1,302 @@ +# +# Copyright 2017 "Neo Technology", +# Network Engine for Objects in Lund AB (http://neotechnology.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Feature: PatternComprehension + + Scenario: Pattern comprehension and ORDER BY + Given an empty graph + And having executed: + """ + CREATE (a {time: 10}), (b {time: 20}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (liker) + RETURN [p = (liker)--() | p] AS isNew + ORDER BY liker.time + """ + Then the result should be: + | isNew | + | [<({time: 10})-[:T]->({time: 20})>] | + | [<({time: 20})<-[:T]-({time: 10})>] | + And no side effects + + Scenario: Returning a pattern comprehension + Given an empty graph + And having executed: + """ + CREATE (a:A) + CREATE (a)-[:T]->(:B), + (a)-[:T]->(:C) + """ + When executing query: + """ + MATCH (n) + RETURN [p = (n)-->() | p] AS ps + """ + Then the result should be (ignoring element order for lists): + | ps | + | [<(:A)-[:T]->(:C)>, <(:A)-[:T]->(:B)>] | + | [] | + | [] | + And no side effects + + Scenario: Returning a pattern comprehension with label predicate + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B), (c:C), (d:D) + CREATE (a)-[:T]->(b), + (a)-[:T]->(c), + (a)-[:T]->(d) + """ + When executing query: + """ + MATCH (n:A) + RETURN [p = (n)-->(:B) | p] AS x + """ + Then the result should be: + | x | + | [<(:A)-[:T]->(:B)>] | + And no side effects + + Scenario: Returning a pattern comprehension with bound nodes + Given an empty graph + And having executed: + """ + CREATE (a:A), (b:B) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (a:A), (b:B) + RETURN [p = (a)-[*]->(b) | p] AS paths + """ + Then the result should be: + | paths | + | [<(:A)-[:T]->(:B)>] | + And no side effects + + Scenario: Using a pattern comprehension in a WITH + Given an empty graph + And having executed: + """ + CREATE (a:A) + CREATE (a)-[:T]->(:B), + (a)-[:T]->(:C) + """ + When executing query: + """ + MATCH (n)-->(b) + WITH [p = (n)-->() | p] AS ps, count(b) AS c + RETURN ps, c + """ + Then the result should be (ignoring element order for lists): + | ps | c | + | [<(:A)-[:T]->(:C)>, <(:A)-[:T]->(:B)>] | 2 | + And no side effects + + Scenario: Using a variable-length pattern comprehension in a WITH + Given an empty graph + And having executed: + """ + CREATE (:A)-[:T]->(:B) + """ + When executing query: + """ + MATCH (a:A), (b:B) + WITH [p = (a)-[*]->(b) | p] AS paths, count(a) AS c + RETURN paths, c + """ + Then the result should be: + | paths | c | + | [<(:A)-[:T]->(:B)>] | 1 | + And no side effects + + Scenario: Using pattern comprehension in RETURN + Given an empty graph + And having executed: + """ + CREATE (a:A), (:A), (:A) + CREATE (a)-[:HAS]->() + """ + When executing query: + """ + MATCH (n:A) + RETURN [p = (n)-[:HAS]->() | p] AS ps + """ + Then the result should be: + | ps | + | [<(:A)-[:HAS]->()>] | + | [] | + | [] | + And no side effects + + Scenario: Aggregating on pattern comprehension + Given an empty graph + And having executed: + """ + CREATE (a:A), (:A), (:A) + CREATE (a)-[:HAS]->() + """ + When executing query: + """ + MATCH (n:A) + RETURN count([p = (n)-[:HAS]->() | p]) AS c + """ + Then the result should be: + | c | + | 3 | + And no side effects + + Scenario: Using pattern comprehension to test existence + Given an empty graph + And having executed: + """ + CREATE (a:X {prop: 42}), (:X {prop: 43}) + CREATE (a)-[:T]->() + """ + When executing query: + """ + MATCH (n:X) + RETURN n, size([(n)--() | 1]) > 0 AS b + """ + Then the result should be: + | n | b | + | (:X {prop: 42}) | true | + | (:X {prop: 43}) | false | + And no side effects + + Scenario: Pattern comprehension inside list comprehension + Given an empty graph + And having executed: + """ + CREATE (n1:X {n: 1}), (m1:Y), (i1:Y), (i2:Y) + CREATE (n1)-[:T]->(m1), + (m1)-[:T]->(i1), + (m1)-[:T]->(i2) + CREATE (n2:X {n: 2}), (m2), (i3:L), (i4:Y) + CREATE (n2)-[:T]->(m2), + (m2)-[:T]->(i3), + (m2)-[:T]->(i4) + """ + When executing query: + """ + MATCH p = (n:X)-->(b) + RETURN n, [x IN nodes(p) | size([(x)-->(:Y) | 1])] AS list + """ + Then the result should be: + | n | list | + | (:X {n: 1}) | [1, 2] | + | (:X {n: 2}) | [0, 1] | + And no side effects + + Scenario: Get node degree via size of pattern comprehension + Given an empty graph + And having executed: + """ + CREATE (x:X), + (x)-[:T]->(), + (x)-[:T]->(), + (x)-[:T]->() + """ + When executing query: + """ + MATCH (a:X) + RETURN size([(a)-->() | 1]) AS length + """ + Then the result should be: + | length | + | 3 | + And no side effects + + Scenario: Get node degree via size of pattern comprehension that specifies a relationship type + Given an empty graph + And having executed: + """ + CREATE (x:X), + (x)-[:T]->(), + (x)-[:T]->(), + (x)-[:T]->(), + (x)-[:OTHER]->() + """ + When executing query: + """ + MATCH (a:X) + RETURN size([(a)-[:T]->() | 1]) AS length + """ + Then the result should be: + | length | + | 3 | + And no side effects + + Scenario: Get node degree via size of pattern comprehension that specifies multiple relationship types + Given an empty graph + And having executed: + """ + CREATE (x:X), + (x)-[:T]->(), + (x)-[:T]->(), + (x)-[:T]->(), + (x)-[:OTHER]->() + """ + When executing query: + """ + MATCH (a:X) + RETURN size([(a)-[:T|OTHER]->() | 1]) AS length + """ + Then the result should be: + | length | + | 4 | + And no side effects + + Scenario: Introducing new node variable in pattern comprehension + Given an empty graph + And having executed: + """ + CREATE (a), (b {prop: 'val'}) + CREATE (a)-[:T]->(b) + """ + When executing query: + """ + MATCH (n) + RETURN [(n)-[:T]->(b) | b.prop] AS list + """ + Then the result should be: + | list | + | ['val'] | + | [] | + And no side effects + + Scenario: Introducing new relationship variable in pattern comprehension + Given an empty graph + And having executed: + """ + CREATE (a), (b) + CREATE (a)-[:T {prop: 'val'}]->(b) + """ + When executing query: + """ + MATCH (n) + RETURN [(n)-[r:T]->() | r.prop] AS list + """ + Then the result should be: + | list | + | ['val'] | + | [] | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ProcedureCallAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ProcedureCallAcceptance.feature new file mode 100644 index 000000000..9f0062e2c --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ProcedureCallAcceptance.feature @@ -0,0 +1,515 @@ +# +# Copyright (c) 2002-2016 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Neo4j is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +Feature: ProcedureCallAcceptance + + Background: + Given an empty graph + + Scenario: In-query call to procedure that takes arguments fails when trying to pass them implicitly + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?): + | in | out | + When executing query: + """ + CALL test.my.proc YIELD out + RETURN out + """ + Then a SyntaxError should be raised at compile time: InvalidArgumentPassingMode + + Scenario: Standalone call to procedure that takes no arguments + And there exists a procedure test.labels() :: (label :: STRING?): + | label | + | 'A' | + | 'B' | + | 'C' | + When executing query: + """ + CALL test.labels() + """ + Then the result should be, in order: + | label | + | 'A' | + | 'B' | + | 'C' | + And no side effects + + Scenario: In-query call to procedure that takes no arguments + And there exists a procedure test.labels() :: (label :: STRING?): + | label | + | 'A' | + | 'B' | + | 'C' | + When executing query: + """ + CALL test.labels() YIELD label + RETURN label + """ + Then the result should be, in order: + | label | + | 'A' | + | 'B' | + | 'C' | + And no side effects + + Scenario: Calling the same procedure twice using the same outputs in each call + And there exists a procedure test.labels() :: (label :: STRING?): + | label | + | 'A' | + | 'B' | + | 'C' | + When executing query: + """ + CALL test.labels() YIELD label + WITH count(*) AS c + CALL test.labels() YIELD label + RETURN * + """ + Then the result should be, in order: + | label | c | + | 'A' | 3 | + | 'B' | 3 | + | 'C' | 3 | + And no side effects + + Scenario: Standalone call to VOID procedure that takes no arguments + And there exists a procedure test.doNothing() :: VOID: + | + When executing query: + """ + CALL test.doNothing() + """ + Then the result should be empty + And no side effects + + Scenario: In-query call to VOID procedure that takes no arguments + And there exists a procedure test.doNothing() :: VOID: + | + When executing query: + """ + MATCH (n) + CALL test.doNothing() + RETURN n + """ + Then the result should be empty + And no side effects + + Scenario: In-query call to VOID procedure does not consume rows + And there exists a procedure test.doNothing() :: VOID: + | + And having executed: + """ + CREATE (:A {name: 'a'}) + CREATE (:B {name: 'b'}) + CREATE (:C {name: 'c'}) + """ + When executing query: + """ + MATCH (n) + CALL test.doNothing() + RETURN n.name AS `name` + """ + Then the result should be: + | name | + | 'a' | + | 'b' | + | 'c' | + And no side effects + + Scenario: Standalone call to VOID procedure that takes no arguments, called with implicit arguments + And there exists a procedure test.doNothing() :: VOID: + | + When executing query: + """ + CALL test.doNothing + """ + Then the result should be empty + And no side effects + + Scenario: In-query call to procedure that takes no arguments and yields no results + And there exists a procedure test.doNothing() :: (): + | + When executing query: + """ + CALL test.doNothing() YIELD - RETURN 1 + """ + Then the result should be empty + And no side effects + + Scenario: Standalone call to procedure that takes no arguments and yields no results + And there exists a procedure test.doNothing() :: (): + | + When executing query: + """ + CALL test.doNothing() + """ + Then the result should be empty + And no side effects + + Scenario: Standalone call to procedure that takes no arguments and yields no results, called with implicit arguments + And there exists a procedure test.doNothing() :: (): + | + When executing query: + """ + CALL test.doNothing + """ + Then the result should be empty + And no side effects + + Scenario: In-query call to procedure with explicit arguments + And there exists a procedure test.my.proc(name :: STRING?, id :: INTEGER?) :: (city :: STRING?, country_code :: INTEGER?): + | name | id | city | country_code | + | 'Andres' | 1 | 'Malmö' | 46 | + | 'Tobias' | 1 | 'Malmö' | 46 | + | 'Mats' | 1 | 'Malmö' | 46 | + | 'Stefan' | 1 | 'Berlin' | 49 | + | 'Stefan' | 2 | 'München' | 49 | + | 'Petra' | 1 | 'London' | 44 | + When executing query: + """ + CALL test.my.proc('Stefan', 1) YIELD city, country_code + RETURN city, country_code + """ + Then the result should be, in order: + | city | country_code | + | 'Berlin' | 49 | + + Scenario: In-query call to procedure with explicit arguments that drops all result fields + And there exists a procedure test.my.proc(name :: STRING?, id :: INTEGER?) :: (city :: STRING?, country_code :: INTEGER?): + | name | id | city | country_code | + | 'Andres' | 1 | 'Malmö' | 46 | + | 'Tobias' | 1 | 'Malmö' | 46 | + | 'Mats' | 1 | 'Malmö' | 46 | + | 'Stefan' | 1 | 'Berlin' | 49 | + | 'Stefan' | 2 | 'München' | 49 | + | 'Petra' | 1 | 'London' | 44 | + When executing query: + """ + WITH 'Stefan' AS name, 1 AS id + CALL test.my.proc(name, id) YIELD - + RETURN name, id, count(*) AS count + """ + Then the result should be, in order: + | name | id | count | + | 'Stefan' | 1 | 1 | + + Scenario: Standalone call to procedure with explicit arguments + And there exists a procedure test.my.proc(name :: STRING?, id :: INTEGER?) :: (city :: STRING?, country_code :: INTEGER?): + | name | id | city | country_code | + | 'Andres' | 1 | 'Malmö' | 46 | + | 'Tobias' | 1 | 'Malmö' | 46 | + | 'Mats' | 1 | 'Malmö' | 46 | + | 'Stefan' | 1 | 'Berlin' | 49 | + | 'Stefan' | 2 | 'München' | 49 | + | 'Petra' | 1 | 'London' | 44 | + When executing query: + """ + CALL test.my.proc('Stefan', 1) + """ + Then the result should be, in order: + | city | country_code | + | 'Berlin' | 49 | + + Scenario: Standalone call to procedure with implicit arguments + And there exists a procedure test.my.proc(name :: STRING?, id :: INTEGER?) :: (city :: STRING?, country_code :: INTEGER?): + | name | id | city | country_code | + | 'Andres' | 1 | 'Malmö' | 46 | + | 'Tobias' | 1 | 'Malmö' | 46 | + | 'Mats' | 1 | 'Malmö' | 46 | + | 'Stefan' | 1 | 'Berlin' | 49 | + | 'Stefan' | 2 | 'München' | 49 | + | 'Petra' | 1 | 'London' | 44 | + And parameters are: + | name | 'Stefan' | + | id | 1 | + When executing query: + """ + CALL test.my.proc + """ + Then the result should be, in order: + | city | country_code | + | 'Berlin' | 49 | + And no side effects + + Scenario: Standalone call to procedure with argument of type NUMBER accepts value of type INTEGER + And there exists a procedure test.my.proc(in :: NUMBER?) :: (out :: STRING?): + | in | out | + | 42 | 'wisdom' | + | 42.3 | 'about right' | + When executing query: + """ + CALL test.my.proc(42) + """ + Then the result should be, in order: + | out | + | 'wisdom' | + And no side effects + + Scenario: In-query call to procedure with argument of type NUMBER accepts value of type INTEGER + And there exists a procedure test.my.proc(in :: NUMBER?) :: (out :: STRING?): + | in | out | + | 42 | 'wisdom' | + | 42.3 | 'about right' | + When executing query: + """ + CALL test.my.proc(42) YIELD out + RETURN out + """ + Then the result should be, in order: + | out | + | 'wisdom' | + And no side effects + + Scenario: Standalone call to procedure with argument of type NUMBER accepts value of type FLOAT + And there exists a procedure test.my.proc(in :: NUMBER?) :: (out :: STRING?): + | in | out | + | 42 | 'wisdom' | + | 42.3 | 'about right' | + When executing query: + """ + CALL test.my.proc(42.3) + """ + Then the result should be, in order: + | out | + | 'about right' | + And no side effects + + Scenario: In-query call to procedure with argument of type NUMBER accepts value of type FLOAT + And there exists a procedure test.my.proc(in :: NUMBER?) :: (out :: STRING?): + | in | out | + | 42 | 'wisdom' | + | 42.3 | 'about right' | + When executing query: + """ + CALL test.my.proc(42.3) YIELD out + RETURN out + """ + Then the result should be, in order: + | out | + | 'about right' | + And no side effects + + Scenario: Standalone call to procedure with argument of type FLOAT accepts value of type INTEGER + And there exists a procedure test.my.proc(in :: FLOAT?) :: (out :: STRING?): + | in | out | + | 42.0 | 'close enough' | + When executing query: + """ + CALL test.my.proc(42) + """ + Then the result should be, in order: + | out | + | 'close enough' | + And no side effects + + Scenario: In-query call to procedure with argument of type FLOAT accepts value of type INTEGER + And there exists a procedure test.my.proc(in :: FLOAT?) :: (out :: STRING?): + | in | out | + | 42.0 | 'close enough' | + When executing query: + """ + CALL test.my.proc(42) YIELD out + RETURN out + """ + Then the result should be, in order: + | out | + | 'close enough' | + And no side effects + + Scenario: Standalone call to procedure with argument of type INTEGER accepts value of type FLOAT + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: STRING?): + | in | out | + | 42 | 'close enough' | + When executing query: + """ + CALL test.my.proc(42.0) + """ + Then the result should be, in order: + | out | + | 'close enough' | + And no side effects + + Scenario: In-query call to procedure with argument of type INTEGER accepts value of type FLOAT + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: STRING?): + | in | out | + | 42 | 'close enough' | + When executing query: + """ + CALL test.my.proc(42.0) YIELD out + RETURN out + """ + Then the result should be, in order: + | out | + | 'close enough' | + And no side effects + + Scenario: Standalone call to procedure with null argument + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: STRING?): + | in | out | + | null | 'nix' | + When executing query: + """ + CALL test.my.proc(null) + """ + Then the result should be, in order: + | out | + | 'nix' | + And no side effects + + Scenario: In-query call to procedure with null argument + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: STRING?): + | in | out | + | null | 'nix' | + When executing query: + """ + CALL test.my.proc(null) YIELD out + RETURN out + """ + Then the result should be, in order: + | out | + | 'nix' | + And no side effects + + Scenario: Standalone call to procedure should fail if input type is wrong + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?): + | in | out | + When executing query: + """ + CALL test.my.proc(true) + """ + Then a SyntaxError should be raised at compile time: InvalidArgumentType + + Scenario: In-query call to procedure should fail if input type is wrong + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?): + | in | out | + When executing query: + """ + CALL test.my.proc(true) YIELD out + RETURN out + """ + Then a SyntaxError should be raised at compile time: InvalidArgumentType + + Scenario: Standalone call to procedure should fail if explicit argument is missing + And there exists a procedure test.my.proc(name :: STRING?, in :: INTEGER?) :: (out :: INTEGER?): + | name | in | out | + When executing query: + """ + CALL test.my.proc('Dobby') + """ + Then a SyntaxError should be raised at compile time: InvalidNumberOfArguments + + Scenario: In-query call to procedure should fail if explicit argument is missing + And there exists a procedure test.my.proc(name :: STRING?, in :: INTEGER?) :: (out :: INTEGER?): + | name | in | out | + When executing query: + """ + CALL test.my.proc('Dobby') YIELD out + RETURN out + """ + Then a SyntaxError should be raised at compile time: InvalidNumberOfArguments + + Scenario: Standalone call to procedure should fail if too many explicit argument are given + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?): + | in | out | + When executing query: + """ + CALL test.my.proc(1, 2, 3, 4) + """ + Then a SyntaxError should be raised at compile time: InvalidNumberOfArguments + + Scenario: In-query call to procedure should fail if too many explicit argument are given + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?): + | in | out | + When executing query: + """ + CALL test.my.proc(1, 2, 3, 4) YIELD out + RETURN out + """ + Then a SyntaxError should be raised at compile time: InvalidNumberOfArguments + + Scenario: Standalone call to procedure should fail if implicit argument is missing + And there exists a procedure test.my.proc(name :: STRING?, in :: INTEGER?) :: (out :: INTEGER?): + | name | in | out | + And parameters are: + | name | 'Stefan' | + When executing query: + """ + CALL test.my.proc + """ + Then a ParameterMissing should be raised at compile time: MissingParameter + + Scenario: In-query call to procedure that has outputs fails if no outputs are yielded + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?): + | in | out | + When executing query: + """ + CALL test.my.proc(1) + RETURN out + """ + Then a SyntaxError should be raised at compile time: UndefinedVariable + + Scenario: In-query call to procedure that both takes arguments and has outputs fails if the arguments are passed implicitly and no outputs are yielded + And there exists a procedure test.my.proc(in :: INTEGER?) :: (out :: INTEGER?): + | in | out | + When executing query: + """ + CALL test.my.proc + RETURN out + """ + Then a SyntaxError should be raised at compile time: UndefinedVariable + + Scenario: Standalone call to unknown procedure should fail + When executing query: + """ + CALL test.my.proc + """ + Then a ProcedureError should be raised at compile time: ProcedureNotFound + + Scenario: In-query call to unknown procedure should fail + When executing query: + """ + CALL test.my.proc() YIELD out + RETURN out + """ + Then a ProcedureError should be raised at compile time: ProcedureNotFound + + Scenario: In-query procedure call should fail if shadowing an already bound variable + And there exists a procedure test.labels() :: (label :: STRING?): + | label | + | 'A' | + | 'B' | + | 'C' | + When executing query: + """ + WITH 'Hi' AS label + CALL test.labels() YIELD label + RETURN * + """ + Then a SyntaxError should be raised at compile time: VariableAlreadyBound + + Scenario: In-query procedure call should fail if one of the argument expressions uses an aggregation function + And there exists a procedure test.labels(in :: INTEGER?) :: (label :: STRING?): + | in | label | + When executing query: + """ + MATCH (n) + CALL test.labels(count(n)) YIELD label + RETURN label + """ + Then a SyntaxError should be raised at compile time: InvalidAggregation diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/RemoveAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/RemoveAcceptance.feature new file mode 100644 index 000000000..846f0f0ae --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/RemoveAcceptance.feature @@ -0,0 +1,161 @@ +# +# Copyright 2017 "Neo Technology", +# Network Engine for Objects in Lund AB (http://neotechnology.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Feature: RemoveAcceptance + + Scenario: Should ignore nulls + Given an empty graph + And having executed: + """ + CREATE ({prop: 42}) + """ + When executing query: + """ + MATCH (n) + OPTIONAL MATCH (n)-[r]->() + REMOVE r.prop + RETURN n + """ + Then the result should be: + | n | + | ({prop: 42}) | + And no side effects + + Scenario: Remove a single label + Given an empty graph + And having executed: + """ + CREATE (:L {prop: 42}) + """ + When executing query: + """ + MATCH (n) + REMOVE n:L + RETURN n.prop + """ + Then the result should be: + | n.prop | + | 42 | + And the side effects should be: + | -labels | 1 | + + Scenario: Remove multiple labels + Given an empty graph + And having executed: + """ + CREATE (:L1:L2:L3 {prop: 42}) + """ + When executing query: + """ + MATCH (n) + REMOVE n:L1:L3 + RETURN labels(n) + """ + Then the result should be: + | labels(n) | + | ['L2'] | + And the side effects should be: + | -labels | 2 | + + Scenario: Remove a single node property + Given an empty graph + And having executed: + """ + CREATE (:L {prop: 42}) + """ + When executing query: + """ + MATCH (n) + REMOVE n.prop + RETURN exists(n.prop) AS still_there + """ + Then the result should be: + | still_there | + | false | + And the side effects should be: + | -properties | 1 | + + Scenario: Remove multiple node properties + Given an empty graph + And having executed: + """ + CREATE (:L {prop: 42, a: 'a', b: 'B'}) + """ + When executing query: + """ + MATCH (n) + REMOVE n.prop, n.a + RETURN size(keys(n)) AS props + """ + Then the result should be: + | props | + | 1 | + And the side effects should be: + | -properties | 2 | + + Scenario: Remove a single relationship property + Given an empty graph + And having executed: + """ + CREATE (a), (b), (a)-[:X {prop: 42}]->(b) + """ + When executing query: + """ + MATCH ()-[r]->() + REMOVE r.prop + RETURN exists(r.prop) AS still_there + """ + Then the result should be: + | still_there | + | false | + And the side effects should be: + | -properties | 1 | + + Scenario: Remove multiple relationship properties + Given an empty graph + And having executed: + """ + CREATE (a), (b), (a)-[:X {prop: 42, a: 'a', b: 'B'}]->(b) + """ + When executing query: + """ + MATCH ()-[r]->() + REMOVE r.prop, r.a + RETURN size(keys(r)) AS props + """ + Then the result should be: + | props | + | 1 | + And the side effects should be: + | -properties | 2 | + + Scenario: Remove a missing property should be a valid operation + Given an empty graph + And having executed: + """ + CREATE (), (), () + """ + When executing query: + """ + MATCH (n) + REMOVE n.prop + RETURN sum(size(keys(n))) AS totalNumberOfProps + """ + Then the result should be: + | totalNumberOfProps | + | 0 | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ReturnAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ReturnAcceptance.feature new file mode 100644 index 000000000..c8f13bc72 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ReturnAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ReturnAcceptance2.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ReturnAcceptance2.feature new file mode 100644 index 000000000..0111f9db2 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/ReturnAcceptance2.feature @@ -0,0 +1,623 @@ +# +# Copyright 2017 "Neo Technology", +# Network Engine for Objects in Lund AB (http://neotechnology.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Feature: ReturnAcceptance2 + + Scenario: Fail when returning properties of deleted nodes + Given an empty graph + And having executed: + """ + CREATE ({p: 0}) + """ + When executing query: + """ + MATCH (n) + DELETE n + RETURN n.p + """ + Then a EntityNotFound should be raised at runtime: DeletedEntityAccess + + Scenario: Fail when returning labels of deleted nodes + Given an empty graph + And having executed: + """ + CREATE (:A) + """ + When executing query: + """ + MATCH (n) + DELETE n + RETURN labels(n) + """ + Then a EntityNotFound should be raised at runtime: DeletedEntityAccess + + Scenario: Fail when returning properties of deleted relationships + Given an empty graph + And having executed: + """ + CREATE ()-[:T {p: 0}]->() + """ + When executing query: + """ + MATCH ()-[r]->() + DELETE r + RETURN r.p + """ + Then a EntityNotFound should be raised at runtime: DeletedEntityAccess + + Scenario: Do not fail when returning type of deleted relationships + Given an empty graph + And having executed: + """ + CREATE ()-[:T]->() + """ + When executing query: + """ + MATCH ()-[r]->() + DELETE r + RETURN type(r) + """ + Then the result should be: + | type(r) | + | 'T' | + And the side effects should be: + | -relationships | 1 | + + Scenario: Accept valid Unicode literal + Given any graph + When executing query: + """ + RETURN '\u01FF' AS a + """ + Then the result should be: + | a | + | 'ǿ' | + And no side effects + + Scenario: LIMIT 0 should return an empty result + Given an empty graph + And having executed: + """ + CREATE (), (), () + """ + When executing query: + """ + MATCH (n) + RETURN n + LIMIT 0 + """ + Then the result should be: + | n | + And no side effects + + Scenario: Fail when sorting on variable removed by DISTINCT + Given an empty graph + And having executed: + """ + CREATE ({name: 'A', age: 13}), ({name: 'B', age: 12}), ({name: 'C', age: 11}) + """ + When executing query: + """ + MATCH (a) + RETURN DISTINCT a.name + ORDER BY a.age + """ + Then a SyntaxError should be raised at compile time: UndefinedVariable + + Scenario: Ordering with aggregation + Given an empty graph + And having executed: + """ + CREATE ({name: 'nisse'}) + """ + When executing query: + """ + MATCH (n) + RETURN n.name, count(*) AS foo + ORDER BY n.name + """ + Then the result should be: + | n.name | foo | + | 'nisse' | 1 | + And no side effects + + Scenario: DISTINCT on nullable values + Given an empty graph + And having executed: + """ + CREATE ({name: 'Florescu'}), (), () + """ + When executing query: + """ + MATCH (n) + RETURN DISTINCT n.name + """ + Then the result should be: + | n.name | + | 'Florescu' | + | null | + And no side effects + + Scenario: Return all variables + Given an empty graph + And having executed: + """ + CREATE (:Start)-[:T]->() + """ + When executing query: + """ + MATCH p = (a:Start)-->(b) + RETURN * + """ + Then the result should be: + | p | a | b | + | <(:Start)-[:T]->()> | (:Start) | () | + And no side effects + + Scenario: Setting and returning the size of a list property + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + SET n.x = [1, 2, 3] + RETURN size(n.x) + """ + Then the result should be: + | size(n.x) | + | 3 | + And the side effects should be: + | +properties | 1 | + + Scenario: `sqrt()` returning float values + Given any graph + When executing query: + """ + RETURN sqrt(12.96) + """ + Then the result should be: + | sqrt(12.96) | + | 3.6 | + And no side effects + + Scenario: Arithmetic expressions inside aggregation + Given an empty graph + And having executed: + """ + CREATE (andres {name: 'Andres'}), + (michael {name: 'Michael'}), + (peter {name: 'Peter'}), + (bread {type: 'Bread'}), + (veggies {type: 'Veggies'}), + (meat {type: 'Meat'}) + CREATE (andres)-[:ATE {times: 10}]->(bread), + (andres)-[:ATE {times: 8}]->(veggies), + (michael)-[:ATE {times: 4}]->(veggies), + (michael)-[:ATE {times: 6}]->(bread), + (michael)-[:ATE {times: 9}]->(meat), + (peter)-[:ATE {times: 7}]->(veggies), + (peter)-[:ATE {times: 7}]->(bread), + (peter)-[:ATE {times: 4}]->(meat) + """ + When executing query: + """ + MATCH (me)-[r1:ATE]->()<-[r2:ATE]-(you) + WHERE me.name = 'Michael' + WITH me, count(DISTINCT r1) AS H1, count(DISTINCT r2) AS H2, you + MATCH (me)-[r1:ATE]->()<-[r2:ATE]-(you) + RETURN me, you, sum((1 - abs(r1.times / H1 - r2.times / H2)) * (r1.times + r2.times) / (H1 + H2)) AS sum + """ + Then the result should be: + | me | you | sum | + | ({name: 'Michael'}) | ({name: 'Andres'}) | -7 | + | ({name: 'Michael'}) | ({name: 'Peter'}) | 0 | + And no side effects + + Scenario: Matching and disregarding output, then matching again + Given an empty graph + And having executed: + """ + CREATE (andres {name: 'Andres'}), + (michael {name: 'Michael'}), + (peter {name: 'Peter'}), + (bread {type: 'Bread'}), + (veggies {type: 'Veggies'}), + (meat {type: 'Meat'}) + CREATE (andres)-[:ATE {times: 10}]->(bread), + (andres)-[:ATE {times: 8}]->(veggies), + (michael)-[:ATE {times: 4}]->(veggies), + (michael)-[:ATE {times: 6}]->(bread), + (michael)-[:ATE {times: 9}]->(meat), + (peter)-[:ATE {times: 7}]->(veggies), + (peter)-[:ATE {times: 7}]->(bread), + (peter)-[:ATE {times: 4}]->(meat) + """ + When executing query: + """ + MATCH ()-->() + WITH 1 AS x + MATCH ()-[r1]->()<--() + RETURN sum(r1.times) + """ + Then the result should be: + | sum(r1.times) | + | 776 | + And no side effects + + Scenario: Returning a list property + Given an empty graph + And having executed: + """ + CREATE ({foo: [1, 2, 3]}) + """ + When executing query: + """ + MATCH (n) + RETURN n + """ + Then the result should be: + | n | + | ({foo: [1, 2, 3]}) | + And no side effects + + Scenario: Returning a projected map + Given an empty graph + And having executed: + """ + CREATE ({foo: [1, 2, 3]}) + """ + When executing query: + """ + RETURN {a: 1, b: 'foo'} + """ + Then the result should be: + | {a: 1, b: 'foo'} | + | {a: 1, b: 'foo'} | + And no side effects + + Scenario: Returning an expression + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (a) + RETURN exists(a.id), a IS NOT NULL + """ + Then the result should be: + | exists(a.id) | a IS NOT NULL | + | false | true | + And no side effects + + Scenario: Concatenating and returning the size of literal lists + Given any graph + When executing query: + """ + RETURN size([[], []] + [[]]) AS l + """ + Then the result should be: + | l | + | 3 | + And no side effects + + Scenario: Returning nested expressions based on list property + Given an empty graph + And having executed: + """ + CREATE () + """ + When executing query: + """ + MATCH (n) + SET n.array = [1, 2, 3, 4, 5] + RETURN tail(tail(n.array)) + """ + Then the result should be: + | tail(tail(n.array)) | + | [3, 4, 5] | + And the side effects should be: + | +properties | 1 | + + Scenario: Limiting amount of rows when there are fewer left than the LIMIT argument + Given an empty graph + And having executed: + """ + UNWIND range(0, 15) AS i + CREATE ({count: i}) + """ + When executing query: + """ + MATCH (a) + RETURN a.count + ORDER BY a.count + SKIP 10 + LIMIT 10 + """ + Then the result should be, in order: + | a.count | + | 10 | + | 11 | + | 12 | + | 13 | + | 14 | + | 15 | + And no side effects + + Scenario: `substring()` with default second argument + Given any graph + When executing query: + """ + RETURN substring('0123456789', 1) AS s + """ + Then the result should be: + | s | + | '123456789' | + And no side effects + + Scenario: Returning all variables with ordering + Given an empty graph + And having executed: + """ + CREATE ({id: 1}), ({id: 10}) + """ + When executing query: + """ + MATCH (n) + RETURN * + ORDER BY n.id + """ + Then the result should be, in order: + | n | + | ({id: 1}) | + | ({id: 10}) | + And no side effects + + Scenario: Using aliased DISTINCT expression in ORDER BY + Given an empty graph + And having executed: + """ + CREATE ({id: 1}), ({id: 10}) + """ + When executing query: + """ + MATCH (n) + RETURN DISTINCT n.id AS id + ORDER BY id DESC + """ + Then the result should be, in order: + | id | + | 10 | + | 1 | + And no side effects + + Scenario: Returned columns do not change from using ORDER BY + Given an empty graph + And having executed: + """ + CREATE ({id: 1}), ({id: 10}) + """ + When executing query: + """ + MATCH (n) + RETURN DISTINCT n + ORDER BY n.id + """ + Then the result should be, in order: + | n | + | ({id: 1}) | + | ({id: 10}) | + And no side effects + + Scenario: Arithmetic expressions should propagate null values + Given any graph + When executing query: + """ + RETURN 1 + (2 - (3 * (4 / (5 ^ (6 % null))))) AS a + """ + Then the result should be: + | a | + | null | + And no side effects + + Scenario: Indexing into nested literal lists + Given any graph + When executing query: + """ + RETURN [[1]][0][0] + """ + Then the result should be: + | [[1]][0][0] | + | 1 | + And no side effects + + Scenario: Aliasing expressions + Given an empty graph + And having executed: + """ + CREATE ({id: 42}) + """ + When executing query: + """ + MATCH (a) + RETURN a.id AS a, a.id + """ + Then the result should be: + | a | a.id | + | 42 | 42 | + And no side effects + + Scenario: Projecting an arithmetic expression with aggregation + Given an empty graph + And having executed: + """ + CREATE ({id: 42}) + """ + When executing query: + """ + MATCH (a) + RETURN a, count(a) + 3 + """ + Then the result should be: + | a | count(a) + 3 | + | ({id: 42}) | 4 | + And no side effects + + Scenario: Multiple aliasing and backreferencing + Given any graph + When executing query: + """ + CREATE (m {id: 0}) + WITH {first: m.id} AS m + WITH {second: m.first} AS m + RETURN m.second + """ + Then the result should be: + | m.second | + | 0 | + And the side effects should be: + | +nodes | 1 | + | +properties | 1 | + + Scenario: Aggregating by a list property has a correct definition of equality + Given an empty graph + And having executed: + """ + CREATE ({a: [1, 2, 3]}), ({a: [1, 2, 3]}) + """ + When executing query: + """ + MATCH (a) + WITH a.a AS a, count(*) AS count + RETURN count + """ + Then the result should be: + | count | + | 2 | + And no side effects + + Scenario: Reusing variable names + Given an empty graph + And having executed: + """ + CREATE (a:Person), (b:Person), (m:Message {id: 10}) + CREATE (a)-[:LIKE {creationDate: 20160614}]->(m)-[:POSTED_BY]->(b) + """ + When executing query: + """ + MATCH (person:Person)<--(message)<-[like]-(:Person) + WITH like.creationDate AS likeTime, person AS person + ORDER BY likeTime, message.id + WITH head(collect({likeTime: likeTime})) AS latestLike, person AS person + RETURN latestLike.likeTime AS likeTime + ORDER BY likeTime + """ + Then the result should be, in order: + | likeTime | + | 20160614 | + And no side effects + + Scenario: Concatenating lists of same type + Given any graph + When executing query: + """ + RETURN [1, 10, 100] + [4, 5] AS foo + """ + Then the result should be: + | foo | + | [1, 10, 100, 4, 5] | + And no side effects + + Scenario: Appending lists of same type + Given any graph + When executing query: + """ + RETURN [false, true] + false AS foo + """ + Then the result should be: + | foo | + | [false, true, false] | + And no side effects + + Scenario: DISTINCT inside aggregation should work with lists in maps + Given an empty graph + And having executed: + """ + CREATE ({list: ['A', 'B']}), ({list: ['A', 'B']}) + """ + When executing query: + """ + MATCH (n) + RETURN count(DISTINCT {foo: n.list}) AS count + """ + Then the result should be: + | count | + | 1 | + And no side effects + + Scenario: Handling DISTINCT with lists in maps + Given an empty graph + And having executed: + """ + CREATE ({list: ['A', 'B']}), ({list: ['A', 'B']}) + """ + When executing query: + """ + MATCH (n) + WITH DISTINCT {foo: n.list} AS map + RETURN count(*) + """ + Then the result should be: + | count(*) | + | 1 | + And no side effects + + Scenario: DISTINCT inside aggregation should work with nested lists in maps + Given an empty graph + And having executed: + """ + CREATE ({list: ['A', 'B']}), ({list: ['A', 'B']}) + """ + When executing query: + """ + MATCH (n) + RETURN count(DISTINCT {foo: [[n.list, n.list], [n.list, n.list]]}) AS count + """ + Then the result should be: + | count | + | 1 | + And no side effects + + Scenario: DISTINCT inside aggregation should work with nested lists of maps in maps + Given an empty graph + And having executed: + """ + CREATE ({list: ['A', 'B']}), ({list: ['A', 'B']}) + """ + When executing query: + """ + MATCH (n) + RETURN count(DISTINCT {foo: [{bar: n.list}, {baz: {apa: n.list}}]}) AS count + """ + Then the result should be: + | count | + | 1 | + And no side effects diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SemanticErrorAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SemanticErrorAcceptance.feature new file mode 100644 index 000000000..1a1d1b43a --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SemanticErrorAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SetAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SetAcceptance.feature new file mode 100644 index 000000000..4ca95ae21 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SetAcceptance.feature @@ -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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SkipLimitAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SkipLimitAcceptance.feature new file mode 100644 index 000000000..1bbd22407 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SkipLimitAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/StartingPointAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/StartingPointAcceptance.feature new file mode 100644 index 000000000..30fee35c0 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/StartingPointAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/StartsWithAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/StartsWithAcceptance.feature new file mode 100644 index 000000000..ebc4ece99 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/StartsWithAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SyntaxErrorAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SyntaxErrorAcceptance.feature new file mode 100644 index 000000000..929f2a8ca --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/SyntaxErrorAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/TernaryLogicAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/TernaryLogicAcceptance.feature new file mode 100644 index 000000000..de0a05f98 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/TernaryLogicAcceptance.feature @@ -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 | | + | rhs | | + When executing query: + """ + RETURN $lhs AND $rhs AS result + """ + Then the result should be: + | 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 | | + | rhs | | + When executing query: + """ + RETURN $lhs OR $rhs AS result + """ + Then the result should be: + | 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 | | + | rhs | | + When executing query: + """ + RETURN $lhs XOR $rhs AS result + """ + Then the result should be: + | 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 | | + | coll | | + When executing query: + """ + RETURN $elt IN $coll AS result + """ + Then the result should be: + | 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 | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/TriadicSelection.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/TriadicSelection.feature new file mode 100644 index 000000000..ff218e3c0 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/TriadicSelection.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/TypeConversionFunctions.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/TypeConversionFunctions.feature new file mode 100644 index 000000000..98226aedb --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/TypeConversionFunctions.feature @@ -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, ] 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, ] | 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, ] | 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, '', ] | 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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/UnionAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/UnionAcceptance.feature new file mode 100644 index 000000000..1de1fdee5 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/UnionAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/UnwindAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/UnwindAcceptance.feature new file mode 100644 index 000000000..449846c16 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/UnwindAcceptance.feature @@ -0,0 +1,268 @@ +# +# Copyright 2017 "Neo Technology", +# Network Engine for Objects in Lund AB (http://neotechnology.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Feature: UnwindAcceptance + + Scenario: Unwinding a list + Given any graph + When executing query: + """ + UNWIND [1, 2, 3] AS x + RETURN x + """ + Then the result should be: + | x | + | 1 | + | 2 | + | 3 | + And no side effects + + Scenario: Unwinding a range + Given any graph + When executing query: + """ + UNWIND range(1, 3) AS x + RETURN x + """ + Then the result should be: + | x | + | 1 | + | 2 | + | 3 | + And no side effects + + Scenario: Unwinding a concatenation of lists + Given any graph + When executing query: + """ + WITH [1, 2, 3] AS first, [4, 5, 6] AS second + UNWIND (first + second) AS x + RETURN x + """ + Then the result should be: + | x | + | 1 | + | 2 | + | 3 | + | 4 | + | 5 | + | 6 | + And no side effects + + Scenario: Unwinding a collected unwound expression + Given any graph + When executing query: + """ + UNWIND RANGE(1, 2) AS row + WITH collect(row) AS rows + UNWIND rows AS x + RETURN x + """ + Then the result should be: + | x | + | 1 | + | 2 | + And no side effects + + Scenario: Unwinding a collected expression + Given an empty graph + And having executed: + """ + CREATE ({id: 1}), ({id: 2}) + """ + When executing query: + """ + MATCH (row) + WITH collect(row) AS rows + UNWIND rows AS node + RETURN node.id + """ + Then the result should be: + | node.id | + | 1 | + | 2 | + And no side effects + + Scenario: Creating nodes from an unwound parameter list + Given an empty graph + And having executed: + """ + CREATE (:Year {year: 2016}) + """ + And parameters are: + | events | [{year: 2016, id: 1}, {year: 2016, id: 2}] | + When executing query: + """ + UNWIND $events AS event + MATCH (y:Year {year: event.year}) + MERGE (e:Event {id: event.id}) + MERGE (y)<-[:IN]-(e) + RETURN e.id AS x + ORDER BY x + """ + Then the result should be, in order: + | x | + | 1 | + | 2 | + And the side effects should be: + | +nodes | 2 | + | +relationships | 2 | + | +labels | 2 | + | +properties | 2 | + + Scenario: Double unwinding a list of lists + Given any graph + When executing query: + """ + WITH [[1, 2, 3], [4, 5, 6]] AS lol + UNWIND lol AS x + UNWIND x AS y + RETURN y + """ + Then the result should be: + | y | + | 1 | + | 2 | + | 3 | + | 4 | + | 5 | + | 6 | + And no side effects + + Scenario: Unwinding the empty list + Given any graph + When executing query: + """ + UNWIND [] AS empty + RETURN empty + """ + Then the result should be: + | empty | + And no side effects + + Scenario: Unwinding null + Given any graph + When executing query: + """ + UNWIND null AS nil + RETURN nil + """ + Then the result should be: + | nil | + And no side effects + + Scenario: Unwinding list with duplicates + Given any graph + When executing query: + """ + UNWIND [1, 1, 2, 2, 3, 3, 4, 4, 5, 5] AS duplicate + RETURN duplicate + """ + Then the result should be: + | duplicate | + | 1 | + | 1 | + | 2 | + | 2 | + | 3 | + | 3 | + | 4 | + | 4 | + | 5 | + | 5 | + And no side effects + + Scenario: Unwind does not prune context + Given any graph + When executing query: + """ + WITH [1, 2, 3] AS list + UNWIND list AS x + RETURN * + """ + Then the result should be: + | list | x | + | [1, 2, 3] | 1 | + | [1, 2, 3] | 2 | + | [1, 2, 3] | 3 | + And no side effects + + Scenario: Unwind does not remove variables from scope + Given an empty graph + And having executed: + """ + CREATE (s:S), + (n), + (e:E), + (s)-[:X]->(e), + (s)-[:Y]->(e), + (n)-[:Y]->(e) + """ + When executing query: + """ + MATCH (a:S)-[:X]->(b1) + WITH a, collect(b1) AS bees + UNWIND bees AS b2 + MATCH (a)-[:Y]->(b2) + RETURN a, b2 + """ + Then the result should be: + | a | b2 | + | (:S) | (:E) | + And no side effects + + Scenario: Multiple unwinds after each other + Given any graph + When executing query: + """ + WITH [1, 2] AS xs, [3, 4] AS ys, [5, 6] AS zs + UNWIND xs AS x + UNWIND ys AS y + UNWIND zs AS z + RETURN * + """ + Then the result should be: + | x | y | z | zs | ys | xs | + | 1 | 3 | 5 | [5, 6] | [3, 4] | [1, 2] | + | 1 | 3 | 6 | [5, 6] | [3, 4] | [1, 2] | + | 1 | 4 | 5 | [5, 6] | [3, 4] | [1, 2] | + | 1 | 4 | 6 | [5, 6] | [3, 4] | [1, 2] | + | 2 | 3 | 5 | [5, 6] | [3, 4] | [1, 2] | + | 2 | 3 | 6 | [5, 6] | [3, 4] | [1, 2] | + | 2 | 4 | 5 | [5, 6] | [3, 4] | [1, 2] | + | 2 | 4 | 6 | [5, 6] | [3, 4] | [1, 2] | + And no side effects + + Scenario: Unwind with merge + Given an empty graph + And parameters are: + | props | [{login: 'login1', name: 'name1'}, {login: 'login2', name: 'name2'}] | + When executing query: + """ + UNWIND $props AS prop + MERGE (p:Person {login: prop.login}) + SET p.name = prop.name + RETURN p.name, p.login + """ + Then the result should be: + | p.name | p.login | + | 'name1' | 'login1' | + | 'name2' | 'login2' | + And the side effects should be: + | +nodes | 2 | + | +labels | 2 | + | +properties | 4 | diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/VarLengthAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/VarLengthAcceptance.feature new file mode 100644 index 000000000..049393b6e --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/VarLengthAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/VarLengthAcceptance2.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/VarLengthAcceptance2.feature new file mode 100644 index 000000000..a5a09ebd2 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/VarLengthAcceptance2.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/WhereAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/WhereAcceptance.feature new file mode 100644 index 000000000..cf828a7e3 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/WhereAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/features/WithAcceptance.feature b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/WithAcceptance.feature new file mode 100644 index 000000000..7d238f0e7 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/features/WithAcceptance.feature @@ -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 diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-1/binary-tree-1.cypher b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-1/binary-tree-1.cypher new file mode 100644 index 000000000..cd901b34c --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-1/binary-tree-1.cypher @@ -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); diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-1/binary-tree-1.json b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-1/binary-tree-1.json new file mode 100644 index 000000000..18d8c1996 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-1/binary-tree-1.json @@ -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": [] +} diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-2/binary-tree-2.cypher b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-2/binary-tree-2.cypher new file mode 100644 index 000000000..09462a35a --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-2/binary-tree-2.cypher @@ -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); diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-2/binary-tree-2.json b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-2/binary-tree-2.json new file mode 100644 index 000000000..3284d652a --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/binary-tree-2/binary-tree-2.json @@ -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": [] +} diff --git a/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/named-graphs.adoc b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/named-graphs.adoc new file mode 100644 index 000000000..2b366fd69 --- /dev/null +++ b/tests/qa/tck_engine/tests/openCypher_M06/tck/graphs/named-graphs.adoc @@ -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. diff --git a/tests/qa/tests/pilot_dressipi b/tests/qa/tests/pilot_dressipi new file mode 160000 index 000000000..39857b2c8 --- /dev/null +++ b/tests/qa/tests/pilot_dressipi @@ -0,0 +1 @@ +Subproject commit 39857b2c89de7786323c991c2444e946bbd5f914