flowquery 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/python-publish.yml +97 -0
- package/dist/compute/runner.d.ts +3 -2
- package/dist/compute/runner.d.ts.map +1 -1
- package/dist/compute/runner.js +7 -7
- package/dist/compute/runner.js.map +1 -1
- package/dist/flowquery.min.js +1 -1
- package/dist/graph/data.d.ts +31 -0
- package/dist/graph/data.d.ts.map +1 -0
- package/dist/graph/data.js +110 -0
- package/dist/graph/data.js.map +1 -0
- package/dist/graph/database.d.ts +20 -0
- package/dist/graph/database.d.ts.map +1 -0
- package/dist/graph/database.js +77 -0
- package/dist/graph/database.js.map +1 -0
- package/dist/graph/hops.d.ts +11 -0
- package/dist/graph/hops.d.ts.map +1 -0
- package/dist/graph/hops.js +25 -0
- package/dist/graph/hops.js.map +1 -0
- package/dist/graph/node.d.ts +35 -0
- package/dist/graph/node.d.ts.map +1 -0
- package/dist/graph/node.js +113 -0
- package/dist/graph/node.js.map +1 -0
- package/dist/graph/node_data.d.ts +11 -0
- package/dist/graph/node_data.d.ts.map +1 -0
- package/dist/graph/node_data.js +20 -0
- package/dist/graph/node_data.js.map +1 -0
- package/dist/graph/node_reference.d.ts +10 -0
- package/dist/graph/node_reference.d.ts.map +1 -0
- package/dist/graph/node_reference.js +52 -0
- package/dist/graph/node_reference.js.map +1 -0
- package/dist/graph/pattern.d.ts +18 -0
- package/dist/graph/pattern.d.ts.map +1 -0
- package/dist/graph/pattern.js +114 -0
- package/dist/graph/pattern.js.map +1 -0
- package/dist/graph/pattern_expression.d.ts +14 -0
- package/dist/graph/pattern_expression.d.ts.map +1 -0
- package/dist/graph/pattern_expression.js +58 -0
- package/dist/graph/pattern_expression.js.map +1 -0
- package/dist/graph/patterns.d.ts +11 -0
- package/dist/graph/patterns.d.ts.map +1 -0
- package/dist/graph/patterns.js +49 -0
- package/dist/graph/patterns.js.map +1 -0
- package/dist/graph/physical_node.d.ts +10 -0
- package/dist/graph/physical_node.d.ts.map +1 -0
- package/dist/graph/physical_node.js +40 -0
- package/dist/graph/physical_node.js.map +1 -0
- package/dist/graph/physical_relationship.d.ts +10 -0
- package/dist/graph/physical_relationship.d.ts.map +1 -0
- package/dist/graph/physical_relationship.js +40 -0
- package/dist/graph/physical_relationship.js.map +1 -0
- package/dist/graph/relationship.d.ts +40 -0
- package/dist/graph/relationship.d.ts.map +1 -0
- package/dist/graph/relationship.js +124 -0
- package/dist/graph/relationship.js.map +1 -0
- package/dist/graph/relationship_data.d.ts +12 -0
- package/dist/graph/relationship_data.d.ts.map +1 -0
- package/dist/graph/relationship_data.js +40 -0
- package/dist/graph/relationship_data.js.map +1 -0
- package/dist/graph/relationship_match_collector.d.ts +19 -0
- package/dist/graph/relationship_match_collector.d.ts.map +1 -0
- package/dist/graph/relationship_match_collector.js +55 -0
- package/dist/graph/relationship_match_collector.js.map +1 -0
- package/dist/graph/relationship_reference.d.ts +8 -0
- package/dist/graph/relationship_reference.d.ts.map +1 -0
- package/dist/graph/relationship_reference.js +37 -0
- package/dist/graph/relationship_reference.js.map +1 -0
- package/dist/parsing/base_parser.d.ts +1 -0
- package/dist/parsing/base_parser.d.ts.map +1 -1
- package/dist/parsing/base_parser.js +4 -1
- package/dist/parsing/base_parser.js.map +1 -1
- package/dist/parsing/context.d.ts +2 -2
- package/dist/parsing/context.js +5 -5
- package/dist/parsing/expressions/boolean.d.ts +8 -0
- package/dist/parsing/expressions/boolean.d.ts.map +1 -0
- package/dist/parsing/expressions/boolean.js +26 -0
- package/dist/parsing/expressions/boolean.js.map +1 -0
- package/dist/parsing/expressions/expression.d.ts +4 -1
- package/dist/parsing/expressions/expression.d.ts.map +1 -1
- package/dist/parsing/expressions/expression.js +15 -8
- package/dist/parsing/expressions/expression.js.map +1 -1
- package/dist/parsing/expressions/expression_map.d.ts +1 -0
- package/dist/parsing/expressions/expression_map.d.ts.map +1 -1
- package/dist/parsing/expressions/expression_map.js +3 -0
- package/dist/parsing/expressions/expression_map.js.map +1 -1
- package/dist/parsing/expressions/operator.d.ts +1 -1
- package/dist/parsing/expressions/operator.d.ts.map +1 -1
- package/dist/parsing/expressions/operator.js.map +1 -1
- package/dist/parsing/functions/function_factory.d.ts +13 -13
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +20 -18
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/operations/call.d.ts.map +1 -1
- package/dist/parsing/operations/call.js +3 -1
- package/dist/parsing/operations/call.js.map +1 -1
- package/dist/parsing/operations/create_node.d.ts +14 -0
- package/dist/parsing/operations/create_node.d.ts.map +1 -0
- package/dist/parsing/operations/create_node.js +51 -0
- package/dist/parsing/operations/create_node.js.map +1 -0
- package/dist/parsing/operations/create_relationship.d.ts +14 -0
- package/dist/parsing/operations/create_relationship.d.ts.map +1 -0
- package/dist/parsing/operations/create_relationship.js +51 -0
- package/dist/parsing/operations/create_relationship.js.map +1 -0
- package/dist/parsing/operations/match.d.ts +15 -0
- package/dist/parsing/operations/match.d.ts.map +1 -0
- package/dist/parsing/operations/match.js +45 -0
- package/dist/parsing/operations/match.js.map +1 -0
- package/dist/parsing/operations/operation.d.ts +1 -0
- package/dist/parsing/operations/operation.d.ts.map +1 -1
- package/dist/parsing/operations/operation.js +6 -0
- package/dist/parsing/operations/operation.js.map +1 -1
- package/dist/parsing/operations/return.d.ts +1 -0
- package/dist/parsing/operations/return.d.ts.map +1 -1
- package/dist/parsing/operations/return.js +7 -1
- package/dist/parsing/operations/return.js.map +1 -1
- package/dist/parsing/operations/where.d.ts +1 -1
- package/dist/parsing/operations/where.d.ts.map +1 -1
- package/dist/parsing/operations/where.js +4 -0
- package/dist/parsing/operations/where.js.map +1 -1
- package/dist/parsing/parser.d.ts +10 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +344 -5
- package/dist/parsing/parser.js.map +1 -1
- package/dist/parsing/token_to_node.d.ts.map +1 -1
- package/dist/parsing/token_to_node.js +7 -0
- package/dist/parsing/token_to_node.js.map +1 -1
- package/dist/tokenization/keyword.d.ts +1 -0
- package/dist/tokenization/keyword.d.ts.map +1 -1
- package/dist/tokenization/keyword.js +1 -0
- package/dist/tokenization/keyword.js.map +1 -1
- package/dist/tokenization/token.d.ts +4 -0
- package/dist/tokenization/token.d.ts.map +1 -1
- package/dist/tokenization/token.js +14 -1
- package/dist/tokenization/token.js.map +1 -1
- package/dist/tokenization/token_type.d.ts +1 -0
- package/dist/tokenization/token_type.d.ts.map +1 -1
- package/dist/tokenization/token_type.js +1 -0
- package/dist/tokenization/token_type.js.map +1 -1
- package/dist/tokenization/tokenizer.d.ts +2 -1
- package/dist/tokenization/tokenizer.d.ts.map +1 -1
- package/dist/tokenization/tokenizer.js +25 -12
- package/dist/tokenization/tokenizer.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/README.md +166 -0
- package/flowquery-py/pyproject.toml +75 -0
- package/flowquery-py/setup_env.ps1 +92 -0
- package/flowquery-py/setup_env.sh +87 -0
- package/flowquery-py/src/__init__.py +34 -0
- package/flowquery-py/src/__main__.py +10 -0
- package/flowquery-py/src/compute/__init__.py +5 -0
- package/flowquery-py/src/compute/runner.py +60 -0
- package/flowquery-py/src/extensibility.py +52 -0
- package/flowquery-py/src/graph/__init__.py +31 -0
- package/flowquery-py/src/graph/data.py +118 -0
- package/flowquery-py/src/graph/database.py +82 -0
- package/flowquery-py/src/graph/hops.py +43 -0
- package/flowquery-py/src/graph/node.py +112 -0
- package/flowquery-py/src/graph/node_data.py +26 -0
- package/flowquery-py/src/graph/node_reference.py +49 -0
- package/flowquery-py/src/graph/pattern.py +125 -0
- package/flowquery-py/src/graph/pattern_expression.py +62 -0
- package/flowquery-py/src/graph/patterns.py +42 -0
- package/flowquery-py/src/graph/physical_node.py +40 -0
- package/flowquery-py/src/graph/physical_relationship.py +36 -0
- package/flowquery-py/src/graph/relationship.py +135 -0
- package/flowquery-py/src/graph/relationship_data.py +33 -0
- package/flowquery-py/src/graph/relationship_match_collector.py +77 -0
- package/flowquery-py/src/graph/relationship_reference.py +21 -0
- package/flowquery-py/src/io/__init__.py +5 -0
- package/flowquery-py/src/io/command_line.py +67 -0
- package/flowquery-py/src/parsing/__init__.py +17 -0
- package/flowquery-py/src/parsing/alias.py +20 -0
- package/flowquery-py/src/parsing/alias_option.py +11 -0
- package/flowquery-py/src/parsing/ast_node.py +146 -0
- package/flowquery-py/src/parsing/base_parser.py +84 -0
- package/flowquery-py/src/parsing/components/__init__.py +19 -0
- package/flowquery-py/src/parsing/components/csv.py +8 -0
- package/flowquery-py/src/parsing/components/from_.py +10 -0
- package/flowquery-py/src/parsing/components/headers.py +12 -0
- package/flowquery-py/src/parsing/components/json.py +8 -0
- package/flowquery-py/src/parsing/components/null.py +10 -0
- package/flowquery-py/src/parsing/components/post.py +8 -0
- package/flowquery-py/src/parsing/components/text.py +8 -0
- package/flowquery-py/src/parsing/context.py +50 -0
- package/flowquery-py/src/parsing/data_structures/__init__.py +15 -0
- package/flowquery-py/src/parsing/data_structures/associative_array.py +41 -0
- package/flowquery-py/src/parsing/data_structures/json_array.py +30 -0
- package/flowquery-py/src/parsing/data_structures/key_value_pair.py +38 -0
- package/flowquery-py/src/parsing/data_structures/lookup.py +49 -0
- package/flowquery-py/src/parsing/data_structures/range_lookup.py +42 -0
- package/flowquery-py/src/parsing/expressions/__init__.py +57 -0
- package/flowquery-py/src/parsing/expressions/boolean.py +20 -0
- package/flowquery-py/src/parsing/expressions/expression.py +138 -0
- package/flowquery-py/src/parsing/expressions/expression_map.py +26 -0
- package/flowquery-py/src/parsing/expressions/f_string.py +27 -0
- package/flowquery-py/src/parsing/expressions/identifier.py +20 -0
- package/flowquery-py/src/parsing/expressions/number.py +32 -0
- package/flowquery-py/src/parsing/expressions/operator.py +169 -0
- package/flowquery-py/src/parsing/expressions/reference.py +47 -0
- package/flowquery-py/src/parsing/expressions/string.py +27 -0
- package/flowquery-py/src/parsing/functions/__init__.py +75 -0
- package/flowquery-py/src/parsing/functions/aggregate_function.py +60 -0
- package/flowquery-py/src/parsing/functions/async_function.py +62 -0
- package/flowquery-py/src/parsing/functions/avg.py +55 -0
- package/flowquery-py/src/parsing/functions/collect.py +75 -0
- package/flowquery-py/src/parsing/functions/function.py +68 -0
- package/flowquery-py/src/parsing/functions/function_factory.py +173 -0
- package/flowquery-py/src/parsing/functions/function_metadata.py +149 -0
- package/flowquery-py/src/parsing/functions/functions.py +59 -0
- package/flowquery-py/src/parsing/functions/join.py +47 -0
- package/flowquery-py/src/parsing/functions/keys.py +34 -0
- package/flowquery-py/src/parsing/functions/predicate_function.py +46 -0
- package/flowquery-py/src/parsing/functions/predicate_sum.py +47 -0
- package/flowquery-py/src/parsing/functions/rand.py +28 -0
- package/flowquery-py/src/parsing/functions/range_.py +34 -0
- package/flowquery-py/src/parsing/functions/reducer_element.py +15 -0
- package/flowquery-py/src/parsing/functions/replace.py +37 -0
- package/flowquery-py/src/parsing/functions/round_.py +32 -0
- package/flowquery-py/src/parsing/functions/size.py +32 -0
- package/flowquery-py/src/parsing/functions/split.py +47 -0
- package/flowquery-py/src/parsing/functions/stringify.py +47 -0
- package/flowquery-py/src/parsing/functions/sum.py +51 -0
- package/flowquery-py/src/parsing/functions/to_json.py +33 -0
- package/flowquery-py/src/parsing/functions/type_.py +47 -0
- package/flowquery-py/src/parsing/functions/value_holder.py +24 -0
- package/flowquery-py/src/parsing/logic/__init__.py +15 -0
- package/flowquery-py/src/parsing/logic/case.py +29 -0
- package/flowquery-py/src/parsing/logic/else_.py +12 -0
- package/flowquery-py/src/parsing/logic/end.py +8 -0
- package/flowquery-py/src/parsing/logic/then.py +12 -0
- package/flowquery-py/src/parsing/logic/when.py +10 -0
- package/flowquery-py/src/parsing/operations/__init__.py +35 -0
- package/flowquery-py/src/parsing/operations/aggregated_return.py +24 -0
- package/flowquery-py/src/parsing/operations/aggregated_with.py +22 -0
- package/flowquery-py/src/parsing/operations/call.py +74 -0
- package/flowquery-py/src/parsing/operations/create_node.py +34 -0
- package/flowquery-py/src/parsing/operations/create_relationship.py +34 -0
- package/flowquery-py/src/parsing/operations/group_by.py +130 -0
- package/flowquery-py/src/parsing/operations/limit.py +22 -0
- package/flowquery-py/src/parsing/operations/load.py +140 -0
- package/flowquery-py/src/parsing/operations/match.py +29 -0
- package/flowquery-py/src/parsing/operations/operation.py +69 -0
- package/flowquery-py/src/parsing/operations/projection.py +21 -0
- package/flowquery-py/src/parsing/operations/return_op.py +50 -0
- package/flowquery-py/src/parsing/operations/unwind.py +37 -0
- package/flowquery-py/src/parsing/operations/where.py +41 -0
- package/flowquery-py/src/parsing/operations/with_op.py +18 -0
- package/flowquery-py/src/parsing/parser.py +1011 -0
- package/flowquery-py/src/parsing/token_to_node.py +109 -0
- package/flowquery-py/src/tokenization/__init__.py +23 -0
- package/flowquery-py/src/tokenization/keyword.py +48 -0
- package/flowquery-py/src/tokenization/operator.py +29 -0
- package/flowquery-py/src/tokenization/string_walker.py +158 -0
- package/flowquery-py/src/tokenization/symbol.py +19 -0
- package/flowquery-py/src/tokenization/token.py +659 -0
- package/flowquery-py/src/tokenization/token_mapper.py +52 -0
- package/flowquery-py/src/tokenization/token_type.py +21 -0
- package/flowquery-py/src/tokenization/tokenizer.py +214 -0
- package/flowquery-py/src/tokenization/trie.py +124 -0
- package/flowquery-py/src/utils/__init__.py +6 -0
- package/flowquery-py/src/utils/object_utils.py +20 -0
- package/flowquery-py/src/utils/string_utils.py +113 -0
- package/flowquery-py/tests/__init__.py +1 -0
- package/flowquery-py/tests/compute/__init__.py +1 -0
- package/flowquery-py/tests/compute/test_runner.py +1335 -0
- package/flowquery-py/tests/graph/__init__.py +1 -0
- package/flowquery-py/tests/graph/test_create.py +56 -0
- package/flowquery-py/tests/graph/test_data.py +73 -0
- package/flowquery-py/tests/graph/test_match.py +40 -0
- package/flowquery-py/tests/parsing/__init__.py +1 -0
- package/flowquery-py/tests/parsing/test_context.py +34 -0
- package/flowquery-py/tests/parsing/test_expression.py +49 -0
- package/flowquery-py/tests/parsing/test_parser.py +674 -0
- package/flowquery-py/tests/test_extensibility.py +611 -0
- package/flowquery-py/tests/tokenization/__init__.py +1 -0
- package/flowquery-py/tests/tokenization/test_token_mapper.py +60 -0
- package/flowquery-py/tests/tokenization/test_tokenizer.py +164 -0
- package/flowquery-py/tests/tokenization/test_trie.py +30 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/misc/apps/RAG/package.json +1 -1
- package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +76 -8
- package/misc/apps/RAG/src/components/index.ts +19 -10
- package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +21 -26
- package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +24 -25
- package/misc/apps/RAG/src/plugins/loaders/Form.ts +163 -147
- package/misc/apps/RAG/src/plugins/loaders/Llm.ts +103 -90
- package/misc/apps/RAG/src/plugins/loaders/MockData.ts +80 -130
- package/misc/apps/RAG/src/plugins/loaders/Table.ts +104 -101
- package/misc/apps/RAG/src/plugins/loaders/Weather.ts +47 -36
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +89 -78
- package/package.json +1 -1
- package/src/compute/runner.ts +24 -19
- package/src/graph/data.ts +112 -0
- package/src/graph/database.ts +63 -0
- package/src/graph/hops.ts +22 -0
- package/src/graph/node.ts +99 -0
- package/src/graph/node_data.ts +18 -0
- package/src/graph/node_reference.ts +33 -0
- package/src/graph/pattern.ts +101 -0
- package/src/graph/pattern_expression.ts +37 -0
- package/src/graph/patterns.ts +36 -0
- package/src/graph/physical_node.ts +23 -0
- package/src/graph/physical_relationship.ts +23 -0
- package/src/graph/relationship.ts +116 -0
- package/src/graph/relationship_data.ts +27 -0
- package/src/graph/relationship_match_collector.ts +58 -0
- package/src/graph/relationship_reference.ts +24 -0
- package/src/parsing/base_parser.ts +20 -14
- package/src/parsing/context.ts +14 -14
- package/src/parsing/expressions/boolean.ts +21 -0
- package/src/parsing/expressions/expression.ts +34 -26
- package/src/parsing/expressions/expression_map.ts +3 -0
- package/src/parsing/expressions/operator.ts +19 -1
- package/src/parsing/functions/function_factory.ts +45 -45
- package/src/parsing/operations/call.ts +3 -1
- package/src/parsing/operations/create_node.ts +39 -0
- package/src/parsing/operations/create_relationship.ts +38 -0
- package/src/parsing/operations/match.ts +31 -0
- package/src/parsing/operations/operation.ts +3 -0
- package/src/parsing/operations/return.ts +11 -7
- package/src/parsing/operations/where.ts +10 -6
- package/src/parsing/parser.ts +346 -8
- package/src/parsing/token_to_node.ts +6 -0
- package/src/tokenization/keyword.ts +41 -40
- package/src/tokenization/token.ts +21 -1
- package/src/tokenization/token_type.ts +2 -1
- package/src/tokenization/tokenizer.ts +52 -31
- package/tests/compute/runner.test.ts +660 -6
- package/tests/extensibility.test.ts +97 -93
- package/tests/graph/create.test.ts +36 -0
- package/tests/graph/data.test.ts +58 -0
- package/tests/graph/match.test.ts +29 -0
- package/tests/parsing/parser.test.ts +276 -8
- package/tests/tokenization/tokenizer.test.ts +107 -17
|
@@ -0,0 +1,1335 @@
|
|
|
1
|
+
"""Tests for the FlowQuery Runner."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from typing import AsyncIterator
|
|
5
|
+
from flowquery.compute.runner import Runner
|
|
6
|
+
from flowquery.parsing.functions.async_function import AsyncFunction
|
|
7
|
+
from flowquery.parsing.functions.function_metadata import FunctionDef
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Test classes for CALL operation tests
|
|
11
|
+
@FunctionDef({
|
|
12
|
+
"description": "Asynchronous function for testing CALL operation",
|
|
13
|
+
"category": "async",
|
|
14
|
+
"parameters": [],
|
|
15
|
+
"output": {"description": "Yields test values", "type": "any"},
|
|
16
|
+
})
|
|
17
|
+
class _CallTestFunction(AsyncFunction):
|
|
18
|
+
"""Test async function for CALL operation."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__("calltestfunction")
|
|
22
|
+
self._expected_parameter_count = 0
|
|
23
|
+
|
|
24
|
+
async def generate(self) -> AsyncIterator:
|
|
25
|
+
yield {"result": 1, "dummy": "a"}
|
|
26
|
+
yield {"result": 2, "dummy": "b"}
|
|
27
|
+
yield {"result": 3, "dummy": "c"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@FunctionDef({
|
|
31
|
+
"description": "Asynchronous function for testing CALL operation with no yielded expressions",
|
|
32
|
+
"category": "async",
|
|
33
|
+
"parameters": [],
|
|
34
|
+
"output": {"description": "Yields test values", "type": "any"},
|
|
35
|
+
})
|
|
36
|
+
class _CallTestFunctionNoObject(AsyncFunction):
|
|
37
|
+
"""Test async function for CALL operation without object output."""
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
super().__init__("calltestfunctionnoobject")
|
|
41
|
+
self._expected_parameter_count = 0
|
|
42
|
+
|
|
43
|
+
async def generate(self) -> AsyncIterator:
|
|
44
|
+
yield 1
|
|
45
|
+
yield 2
|
|
46
|
+
yield 3
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestRunner:
|
|
50
|
+
"""Test cases for the Runner class."""
|
|
51
|
+
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_return(self):
|
|
54
|
+
"""Test return operation."""
|
|
55
|
+
runner = Runner("return 1 + 2 as sum")
|
|
56
|
+
await runner.run()
|
|
57
|
+
results = runner.results
|
|
58
|
+
assert len(results) == 1
|
|
59
|
+
assert results[0] == {"sum": 3}
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_return_with_multiple_expressions(self):
|
|
63
|
+
"""Test return with multiple expressions."""
|
|
64
|
+
runner = Runner("return 1 + 2 as sum, 3 + 4 as sum2")
|
|
65
|
+
await runner.run()
|
|
66
|
+
results = runner.results
|
|
67
|
+
assert len(results) == 1
|
|
68
|
+
assert results[0] == {"sum": 3, "sum2": 7}
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_unwind_and_return(self):
|
|
72
|
+
"""Test unwind and return."""
|
|
73
|
+
runner = Runner("unwind [1, 2, 3] as num return num")
|
|
74
|
+
await runner.run()
|
|
75
|
+
results = runner.results
|
|
76
|
+
assert len(results) == 3
|
|
77
|
+
assert results[0] == {"num": 1}
|
|
78
|
+
assert results[1] == {"num": 2}
|
|
79
|
+
assert results[2] == {"num": 3}
|
|
80
|
+
|
|
81
|
+
@pytest.mark.asyncio
|
|
82
|
+
async def test_load_and_return(self):
|
|
83
|
+
"""Test load and return."""
|
|
84
|
+
runner = Runner(
|
|
85
|
+
'load json from "https://jsonplaceholder.typicode.com/todos" as todo return todo'
|
|
86
|
+
)
|
|
87
|
+
await runner.run()
|
|
88
|
+
results = runner.results
|
|
89
|
+
assert len(results) > 0
|
|
90
|
+
|
|
91
|
+
@pytest.mark.asyncio
|
|
92
|
+
async def test_load_with_post_and_return(self):
|
|
93
|
+
"""Test load with post and return."""
|
|
94
|
+
runner = Runner(
|
|
95
|
+
'load json from "https://jsonplaceholder.typicode.com/posts" post {userId: 1} as data return data'
|
|
96
|
+
)
|
|
97
|
+
await runner.run()
|
|
98
|
+
results = runner.results
|
|
99
|
+
assert len(results) == 1
|
|
100
|
+
|
|
101
|
+
@pytest.mark.asyncio
|
|
102
|
+
async def test_load_which_should_throw_error(self):
|
|
103
|
+
"""Test load which should throw error."""
|
|
104
|
+
runner = Runner('load json from "http://non_existing" as data return data')
|
|
105
|
+
with pytest.raises(Exception) as exc_info:
|
|
106
|
+
await runner.run()
|
|
107
|
+
assert "non_existing" in str(exc_info.value).lower() or "failed" in str(exc_info.value).lower()
|
|
108
|
+
|
|
109
|
+
@pytest.mark.asyncio
|
|
110
|
+
async def test_aggregated_return(self):
|
|
111
|
+
"""Test aggregated return."""
|
|
112
|
+
runner = Runner(
|
|
113
|
+
"unwind [1, 1, 2, 2] as i unwind [1, 2, 3, 4] as j return i, sum(j) as sum"
|
|
114
|
+
)
|
|
115
|
+
await runner.run()
|
|
116
|
+
results = runner.results
|
|
117
|
+
assert len(results) == 2
|
|
118
|
+
assert results[0] == {"i": 1, "sum": 20}
|
|
119
|
+
assert results[1] == {"i": 2, "sum": 20}
|
|
120
|
+
|
|
121
|
+
@pytest.mark.asyncio
|
|
122
|
+
async def test_aggregated_return_with_string(self):
|
|
123
|
+
"""Test aggregated return with string."""
|
|
124
|
+
runner = Runner(
|
|
125
|
+
'unwind [1, 1, 2, 2] as i unwind ["a", "b", "c", "d"] as j return i, sum(j) as sum'
|
|
126
|
+
)
|
|
127
|
+
await runner.run()
|
|
128
|
+
results = runner.results
|
|
129
|
+
assert len(results) == 2
|
|
130
|
+
assert results[0] == {"i": 1, "sum": "abcdabcd"}
|
|
131
|
+
assert results[1] == {"i": 2, "sum": "abcdabcd"}
|
|
132
|
+
|
|
133
|
+
@pytest.mark.asyncio
|
|
134
|
+
async def test_aggregated_return_with_object(self):
|
|
135
|
+
"""Test aggregated return with object."""
|
|
136
|
+
runner = Runner(
|
|
137
|
+
"unwind [1, 1, 2, 2] as i unwind [1, 2, 3, 4] as j return i, {sum: sum(j)} as sum"
|
|
138
|
+
)
|
|
139
|
+
await runner.run()
|
|
140
|
+
results = runner.results
|
|
141
|
+
assert len(results) == 2
|
|
142
|
+
assert results[0] == {"i": 1, "sum": {"sum": 20}}
|
|
143
|
+
assert results[1] == {"i": 2, "sum": {"sum": 20}}
|
|
144
|
+
|
|
145
|
+
@pytest.mark.asyncio
|
|
146
|
+
async def test_aggregated_return_with_array(self):
|
|
147
|
+
"""Test aggregated return with array."""
|
|
148
|
+
runner = Runner(
|
|
149
|
+
"unwind [1, 1, 2, 2] as i unwind [1, 2, 3, 4] as j return i, [sum(j)] as sum"
|
|
150
|
+
)
|
|
151
|
+
await runner.run()
|
|
152
|
+
results = runner.results
|
|
153
|
+
assert len(results) == 2
|
|
154
|
+
assert results[0] == {"i": 1, "sum": [20]}
|
|
155
|
+
assert results[1] == {"i": 2, "sum": [20]}
|
|
156
|
+
|
|
157
|
+
@pytest.mark.asyncio
|
|
158
|
+
async def test_aggregated_return_with_multiple_aggregates(self):
|
|
159
|
+
"""Test aggregated return with multiple aggregates."""
|
|
160
|
+
runner = Runner(
|
|
161
|
+
"unwind [1, 1, 2, 2] as i unwind [1, 2, 3, 4] as j return i, sum(j) as sum, avg(j) as avg"
|
|
162
|
+
)
|
|
163
|
+
await runner.run()
|
|
164
|
+
results = runner.results
|
|
165
|
+
assert len(results) == 2
|
|
166
|
+
assert results[0] == {"i": 1, "sum": 20, "avg": 2.5}
|
|
167
|
+
assert results[1] == {"i": 2, "sum": 20, "avg": 2.5}
|
|
168
|
+
|
|
169
|
+
@pytest.mark.asyncio
|
|
170
|
+
async def test_avg_with_null(self):
|
|
171
|
+
"""Test avg with null."""
|
|
172
|
+
runner = Runner("return avg(null) as avg")
|
|
173
|
+
await runner.run()
|
|
174
|
+
results = runner.results
|
|
175
|
+
assert len(results) == 1
|
|
176
|
+
assert results[0] == {"avg": None}
|
|
177
|
+
|
|
178
|
+
@pytest.mark.asyncio
|
|
179
|
+
async def test_sum_with_null(self):
|
|
180
|
+
"""Test sum with null."""
|
|
181
|
+
runner = Runner("return sum(null) as sum")
|
|
182
|
+
await runner.run()
|
|
183
|
+
results = runner.results
|
|
184
|
+
assert len(results) == 1
|
|
185
|
+
assert results[0] == {"sum": None}
|
|
186
|
+
|
|
187
|
+
@pytest.mark.asyncio
|
|
188
|
+
async def test_avg_with_one_value(self):
|
|
189
|
+
"""Test avg with one value."""
|
|
190
|
+
runner = Runner("return avg(1) as avg")
|
|
191
|
+
await runner.run()
|
|
192
|
+
results = runner.results
|
|
193
|
+
assert len(results) == 1
|
|
194
|
+
assert results[0] == {"avg": 1}
|
|
195
|
+
|
|
196
|
+
@pytest.mark.asyncio
|
|
197
|
+
async def test_with_and_return(self):
|
|
198
|
+
"""Test with and return."""
|
|
199
|
+
runner = Runner("with 1 as a return a")
|
|
200
|
+
await runner.run()
|
|
201
|
+
results = runner.results
|
|
202
|
+
assert len(results) == 1
|
|
203
|
+
assert results[0] == {"a": 1}
|
|
204
|
+
|
|
205
|
+
def test_nested_aggregate_functions(self):
|
|
206
|
+
"""Test nested aggregate functions throw error."""
|
|
207
|
+
with pytest.raises(Exception, match="Aggregate functions cannot be nested"):
|
|
208
|
+
Runner("unwind [1, 2, 3, 4] as i return sum(sum(i)) as sum")
|
|
209
|
+
|
|
210
|
+
@pytest.mark.asyncio
|
|
211
|
+
async def test_with_and_return_with_unwind(self):
|
|
212
|
+
"""Test with and return with unwind."""
|
|
213
|
+
runner = Runner("with [1, 2, 3] as a unwind a as b return b as renamed")
|
|
214
|
+
await runner.run()
|
|
215
|
+
results = runner.results
|
|
216
|
+
assert len(results) == 3
|
|
217
|
+
assert results[0] == {"renamed": 1}
|
|
218
|
+
assert results[1] == {"renamed": 2}
|
|
219
|
+
assert results[2] == {"renamed": 3}
|
|
220
|
+
|
|
221
|
+
@pytest.mark.asyncio
|
|
222
|
+
async def test_predicate_function(self):
|
|
223
|
+
"""Test predicate function."""
|
|
224
|
+
runner = Runner("RETURN sum(n in [1, 2, 3] | n where n > 1) as sum")
|
|
225
|
+
await runner.run()
|
|
226
|
+
results = runner.results
|
|
227
|
+
assert len(results) == 1
|
|
228
|
+
assert results[0] == {"sum": 5}
|
|
229
|
+
|
|
230
|
+
@pytest.mark.asyncio
|
|
231
|
+
async def test_predicate_without_where(self):
|
|
232
|
+
"""Test predicate without where."""
|
|
233
|
+
runner = Runner("RETURN sum(n in [1, 2, 3] | n) as sum")
|
|
234
|
+
await runner.run()
|
|
235
|
+
results = runner.results
|
|
236
|
+
assert len(results) == 1
|
|
237
|
+
assert results[0] == {"sum": 6}
|
|
238
|
+
|
|
239
|
+
@pytest.mark.asyncio
|
|
240
|
+
async def test_predicate_with_return_expression(self):
|
|
241
|
+
"""Test predicate with return expression."""
|
|
242
|
+
runner = Runner("RETURN sum(n in [1+2+3, 2, 3] | n^2) as sum")
|
|
243
|
+
await runner.run()
|
|
244
|
+
results = runner.results
|
|
245
|
+
assert len(results) == 1
|
|
246
|
+
assert results[0] == {"sum": 49}
|
|
247
|
+
|
|
248
|
+
@pytest.mark.asyncio
|
|
249
|
+
async def test_range_function(self):
|
|
250
|
+
"""Test range function."""
|
|
251
|
+
runner = Runner("RETURN range(1, 3) as range")
|
|
252
|
+
await runner.run()
|
|
253
|
+
results = runner.results
|
|
254
|
+
assert len(results) == 1
|
|
255
|
+
assert results[0] == {"range": [1, 2, 3]}
|
|
256
|
+
|
|
257
|
+
@pytest.mark.asyncio
|
|
258
|
+
async def test_range_function_with_unwind_and_case(self):
|
|
259
|
+
"""Test range function with unwind and case."""
|
|
260
|
+
runner = Runner(
|
|
261
|
+
"unwind range(1, 3) as num return case when num > 1 then num else null end as ret"
|
|
262
|
+
)
|
|
263
|
+
await runner.run()
|
|
264
|
+
results = runner.results
|
|
265
|
+
assert len(results) == 3
|
|
266
|
+
assert results[0] == {"ret": None}
|
|
267
|
+
assert results[1] == {"ret": 2}
|
|
268
|
+
assert results[2] == {"ret": 3}
|
|
269
|
+
|
|
270
|
+
@pytest.mark.asyncio
|
|
271
|
+
async def test_size_function(self):
|
|
272
|
+
"""Test size function."""
|
|
273
|
+
runner = Runner("RETURN size([1, 2, 3]) as size")
|
|
274
|
+
await runner.run()
|
|
275
|
+
results = runner.results
|
|
276
|
+
assert len(results) == 1
|
|
277
|
+
assert results[0] == {"size": 3}
|
|
278
|
+
|
|
279
|
+
@pytest.mark.asyncio
|
|
280
|
+
async def test_rand_and_round_functions(self):
|
|
281
|
+
"""Test rand and round functions."""
|
|
282
|
+
runner = Runner("RETURN round(rand() * 10) as rand")
|
|
283
|
+
await runner.run()
|
|
284
|
+
results = runner.results
|
|
285
|
+
assert len(results) == 1
|
|
286
|
+
assert results[0]["rand"] <= 10
|
|
287
|
+
|
|
288
|
+
@pytest.mark.asyncio
|
|
289
|
+
async def test_split_function(self):
|
|
290
|
+
"""Test split function."""
|
|
291
|
+
runner = Runner('RETURN split("a,b,c", ",") as split')
|
|
292
|
+
await runner.run()
|
|
293
|
+
results = runner.results
|
|
294
|
+
assert len(results) == 1
|
|
295
|
+
assert results[0] == {"split": ["a", "b", "c"]}
|
|
296
|
+
|
|
297
|
+
@pytest.mark.asyncio
|
|
298
|
+
async def test_f_string(self):
|
|
299
|
+
"""Test f-string."""
|
|
300
|
+
runner = Runner(
|
|
301
|
+
'with range(1,3) as numbers RETURN f"hello {sum(n in numbers | n)}" as f'
|
|
302
|
+
)
|
|
303
|
+
await runner.run()
|
|
304
|
+
results = runner.results
|
|
305
|
+
assert len(results) == 1
|
|
306
|
+
assert results[0] == {"f": "hello 6"}
|
|
307
|
+
|
|
308
|
+
@pytest.mark.asyncio
|
|
309
|
+
async def test_aggregated_with_and_return(self):
|
|
310
|
+
"""Test aggregated with and return."""
|
|
311
|
+
runner = Runner(
|
|
312
|
+
"""
|
|
313
|
+
unwind [1, 1, 2, 2] as i
|
|
314
|
+
unwind range(1, 3) as j
|
|
315
|
+
with i, sum(j) as sum
|
|
316
|
+
return i, sum
|
|
317
|
+
"""
|
|
318
|
+
)
|
|
319
|
+
await runner.run()
|
|
320
|
+
results = runner.results
|
|
321
|
+
assert len(results) == 2
|
|
322
|
+
assert results[0] == {"i": 1, "sum": 12}
|
|
323
|
+
assert results[1] == {"i": 2, "sum": 12}
|
|
324
|
+
|
|
325
|
+
@pytest.mark.asyncio
|
|
326
|
+
async def test_aggregated_with_using_collect_and_return(self):
|
|
327
|
+
"""Test aggregated with using collect and return."""
|
|
328
|
+
runner = Runner(
|
|
329
|
+
"""
|
|
330
|
+
unwind [1, 1, 2, 2] as i
|
|
331
|
+
unwind range(1, 3) as j
|
|
332
|
+
with i, collect(j) as collected
|
|
333
|
+
return i, collected
|
|
334
|
+
"""
|
|
335
|
+
)
|
|
336
|
+
await runner.run()
|
|
337
|
+
results = runner.results
|
|
338
|
+
assert len(results) == 2
|
|
339
|
+
assert results[0] == {"i": 1, "collected": [1, 2, 3, 1, 2, 3]}
|
|
340
|
+
assert results[1] == {"i": 2, "collected": [1, 2, 3, 1, 2, 3]}
|
|
341
|
+
|
|
342
|
+
@pytest.mark.asyncio
|
|
343
|
+
async def test_collect_distinct(self):
|
|
344
|
+
"""Test collect distinct."""
|
|
345
|
+
runner = Runner(
|
|
346
|
+
"""
|
|
347
|
+
unwind [1, 1, 2, 2] as i
|
|
348
|
+
unwind range(1, 3) as j
|
|
349
|
+
with i, collect(distinct j) as collected
|
|
350
|
+
return i, collected
|
|
351
|
+
"""
|
|
352
|
+
)
|
|
353
|
+
await runner.run()
|
|
354
|
+
results = runner.results
|
|
355
|
+
assert len(results) == 2
|
|
356
|
+
assert results[0] == {"i": 1, "collected": [1, 2, 3]}
|
|
357
|
+
assert results[1] == {"i": 2, "collected": [1, 2, 3]}
|
|
358
|
+
|
|
359
|
+
@pytest.mark.asyncio
|
|
360
|
+
async def test_collect_distinct_with_associative_array(self):
|
|
361
|
+
"""Test collect distinct with associative array."""
|
|
362
|
+
runner = Runner(
|
|
363
|
+
"""
|
|
364
|
+
unwind [1, 1, 2, 2] as i
|
|
365
|
+
unwind range(1, 3) as j
|
|
366
|
+
with i, collect(distinct {j: j}) as collected
|
|
367
|
+
return i, collected
|
|
368
|
+
"""
|
|
369
|
+
)
|
|
370
|
+
await runner.run()
|
|
371
|
+
results = runner.results
|
|
372
|
+
assert len(results) == 2
|
|
373
|
+
assert results[0] == {"i": 1, "collected": [{"j": 1}, {"j": 2}, {"j": 3}]}
|
|
374
|
+
assert results[1] == {"i": 2, "collected": [{"j": 1}, {"j": 2}, {"j": 3}]}
|
|
375
|
+
|
|
376
|
+
@pytest.mark.asyncio
|
|
377
|
+
async def test_join_function(self):
|
|
378
|
+
"""Test join function."""
|
|
379
|
+
runner = Runner('RETURN join(["a", "b", "c"], ",") as join')
|
|
380
|
+
await runner.run()
|
|
381
|
+
results = runner.results
|
|
382
|
+
assert len(results) == 1
|
|
383
|
+
assert results[0] == {"join": "a,b,c"}
|
|
384
|
+
|
|
385
|
+
@pytest.mark.asyncio
|
|
386
|
+
async def test_join_function_with_empty_array(self):
|
|
387
|
+
"""Test join function with empty array."""
|
|
388
|
+
runner = Runner('RETURN join([], ",") as join')
|
|
389
|
+
await runner.run()
|
|
390
|
+
results = runner.results
|
|
391
|
+
assert len(results) == 1
|
|
392
|
+
assert results[0] == {"join": ""}
|
|
393
|
+
|
|
394
|
+
@pytest.mark.asyncio
|
|
395
|
+
async def test_tojson_function(self):
|
|
396
|
+
"""Test tojson function."""
|
|
397
|
+
runner = Runner("RETURN tojson('{\"a\": 1, \"b\": 2}') as tojson")
|
|
398
|
+
await runner.run()
|
|
399
|
+
results = runner.results
|
|
400
|
+
assert len(results) == 1
|
|
401
|
+
assert results[0] == {"tojson": {"a": 1, "b": 2}}
|
|
402
|
+
|
|
403
|
+
@pytest.mark.asyncio
|
|
404
|
+
async def test_tojson_function_with_lookup(self):
|
|
405
|
+
"""Test tojson function with lookup."""
|
|
406
|
+
runner = Runner("RETURN tojson('{\"a\": 1, \"b\": 2}').a as tojson")
|
|
407
|
+
await runner.run()
|
|
408
|
+
results = runner.results
|
|
409
|
+
assert len(results) == 1
|
|
410
|
+
assert results[0] == {"tojson": 1}
|
|
411
|
+
|
|
412
|
+
@pytest.mark.asyncio
|
|
413
|
+
async def test_replace_function(self):
|
|
414
|
+
"""Test replace function."""
|
|
415
|
+
runner = Runner('RETURN replace("hello", "l", "x") as replace')
|
|
416
|
+
await runner.run()
|
|
417
|
+
results = runner.results
|
|
418
|
+
assert len(results) == 1
|
|
419
|
+
assert results[0] == {"replace": "hexxo"}
|
|
420
|
+
|
|
421
|
+
@pytest.mark.asyncio
|
|
422
|
+
async def test_f_string_with_escaped_braces(self):
|
|
423
|
+
"""Test f-string with escaped braces."""
|
|
424
|
+
runner = Runner(
|
|
425
|
+
'with range(1,3) as numbers RETURN f"hello {{sum(n in numbers | n)}}" as f'
|
|
426
|
+
)
|
|
427
|
+
await runner.run()
|
|
428
|
+
results = runner.results
|
|
429
|
+
assert len(results) == 1
|
|
430
|
+
assert results[0] == {"f": "hello {sum(n in numbers | n)}"}
|
|
431
|
+
|
|
432
|
+
@pytest.mark.asyncio
|
|
433
|
+
async def test_predicate_function_with_collection_from_lookup(self):
|
|
434
|
+
"""Test predicate function with collection from lookup."""
|
|
435
|
+
runner = Runner("RETURN sum(n in tojson('{\"a\": [1, 2, 3]}').a | n) as sum")
|
|
436
|
+
await runner.run()
|
|
437
|
+
results = runner.results
|
|
438
|
+
assert len(results) == 1
|
|
439
|
+
assert results[0] == {"sum": 6}
|
|
440
|
+
|
|
441
|
+
@pytest.mark.asyncio
|
|
442
|
+
async def test_stringify_function(self):
|
|
443
|
+
"""Test stringify function."""
|
|
444
|
+
runner = Runner("RETURN stringify({a: 1, b: 2}) as stringify")
|
|
445
|
+
await runner.run()
|
|
446
|
+
results = runner.results
|
|
447
|
+
assert len(results) == 1
|
|
448
|
+
assert results[0] == {"stringify": '{\n "a": 1,\n "b": 2\n}'}
|
|
449
|
+
|
|
450
|
+
@pytest.mark.asyncio
|
|
451
|
+
async def test_associative_array_with_key_which_is_keyword(self):
|
|
452
|
+
"""Test associative array with key which is keyword."""
|
|
453
|
+
runner = Runner("RETURN {return: 1} as aa")
|
|
454
|
+
await runner.run()
|
|
455
|
+
results = runner.results
|
|
456
|
+
assert len(results) == 1
|
|
457
|
+
assert results[0] == {"aa": {"return": 1}}
|
|
458
|
+
|
|
459
|
+
@pytest.mark.asyncio
|
|
460
|
+
async def test_lookup_which_is_keyword(self):
|
|
461
|
+
"""Test lookup which is keyword."""
|
|
462
|
+
runner = Runner("RETURN {return: 1}.return as aa")
|
|
463
|
+
await runner.run()
|
|
464
|
+
results = runner.results
|
|
465
|
+
assert len(results) == 1
|
|
466
|
+
assert results[0] == {"aa": 1}
|
|
467
|
+
|
|
468
|
+
@pytest.mark.asyncio
|
|
469
|
+
async def test_lookup_which_is_keyword_bracket(self):
|
|
470
|
+
"""Test lookup which is keyword with bracket notation."""
|
|
471
|
+
runner = Runner('RETURN {return: 1}["return"] as aa')
|
|
472
|
+
await runner.run()
|
|
473
|
+
results = runner.results
|
|
474
|
+
assert len(results) == 1
|
|
475
|
+
assert results[0] == {"aa": 1}
|
|
476
|
+
|
|
477
|
+
@pytest.mark.asyncio
|
|
478
|
+
async def test_return_with_expression_alias_which_starts_with_keyword(self):
|
|
479
|
+
"""Test return with expression alias which starts with keyword."""
|
|
480
|
+
runner = Runner('RETURN 1 as return1, ["hello", "world"] as notes')
|
|
481
|
+
await runner.run()
|
|
482
|
+
results = runner.results
|
|
483
|
+
assert len(results) == 1
|
|
484
|
+
assert results[0] == {"return1": 1, "notes": ["hello", "world"]}
|
|
485
|
+
|
|
486
|
+
@pytest.mark.asyncio
|
|
487
|
+
async def test_return_with_where_clause(self):
|
|
488
|
+
"""Test return with where clause."""
|
|
489
|
+
runner = Runner("unwind range(1,100) as n with n return n where n >= 20 and n <= 30")
|
|
490
|
+
await runner.run()
|
|
491
|
+
results = runner.results
|
|
492
|
+
assert len(results) == 11
|
|
493
|
+
assert results[0] == {"n": 20}
|
|
494
|
+
assert results[10] == {"n": 30}
|
|
495
|
+
|
|
496
|
+
@pytest.mark.asyncio
|
|
497
|
+
async def test_return_with_where_clause_and_expression_alias(self):
|
|
498
|
+
"""Test return with where clause and expression alias."""
|
|
499
|
+
runner = Runner(
|
|
500
|
+
"unwind range(1,100) as n with n return n as number where n >= 20 and n <= 30"
|
|
501
|
+
)
|
|
502
|
+
await runner.run()
|
|
503
|
+
results = runner.results
|
|
504
|
+
assert len(results) == 11
|
|
505
|
+
assert results[0] == {"number": 20}
|
|
506
|
+
assert results[10] == {"number": 30}
|
|
507
|
+
|
|
508
|
+
@pytest.mark.asyncio
|
|
509
|
+
async def test_aggregated_return_with_where_clause(self):
|
|
510
|
+
"""Test aggregated return with where clause."""
|
|
511
|
+
runner = Runner(
|
|
512
|
+
"unwind range(1,100) as n with n where n >= 20 and n <= 30 return sum(n) as sum"
|
|
513
|
+
)
|
|
514
|
+
await runner.run()
|
|
515
|
+
results = runner.results
|
|
516
|
+
assert len(results) == 1
|
|
517
|
+
assert results[0] == {"sum": 275}
|
|
518
|
+
|
|
519
|
+
@pytest.mark.asyncio
|
|
520
|
+
async def test_chained_aggregated_return_with_where_clause(self):
|
|
521
|
+
"""Test chained aggregated return with where clause."""
|
|
522
|
+
runner = Runner(
|
|
523
|
+
"""
|
|
524
|
+
unwind [1, 1, 2, 2] as i
|
|
525
|
+
unwind range(1, 4) as j
|
|
526
|
+
return i, sum(j) as sum
|
|
527
|
+
where i = 1
|
|
528
|
+
"""
|
|
529
|
+
)
|
|
530
|
+
await runner.run()
|
|
531
|
+
results = runner.results
|
|
532
|
+
assert len(results) == 1
|
|
533
|
+
assert results[0] == {"i": 1, "sum": 20}
|
|
534
|
+
|
|
535
|
+
@pytest.mark.asyncio
|
|
536
|
+
async def test_predicate_function_with_collection_from_function(self):
|
|
537
|
+
"""Test predicate function with collection from function."""
|
|
538
|
+
runner = Runner(
|
|
539
|
+
"""
|
|
540
|
+
unwind range(1, 10) as i
|
|
541
|
+
unwind range(1, 10) as j
|
|
542
|
+
return i, sum(j), avg(j), sum(n in collect(j) | n) as sum
|
|
543
|
+
"""
|
|
544
|
+
)
|
|
545
|
+
await runner.run()
|
|
546
|
+
results = runner.results
|
|
547
|
+
assert len(results) == 10
|
|
548
|
+
assert results[0] == {"i": 1, "expr1": 55, "expr2": 5.5, "sum": 55}
|
|
549
|
+
|
|
550
|
+
@pytest.mark.asyncio
|
|
551
|
+
async def test_limit(self):
|
|
552
|
+
"""Test limit."""
|
|
553
|
+
runner = Runner(
|
|
554
|
+
"""
|
|
555
|
+
unwind range(1, 10) as i
|
|
556
|
+
unwind range(1, 10) as j
|
|
557
|
+
limit 5
|
|
558
|
+
return j
|
|
559
|
+
"""
|
|
560
|
+
)
|
|
561
|
+
await runner.run()
|
|
562
|
+
results = runner.results
|
|
563
|
+
assert len(results) == 50
|
|
564
|
+
|
|
565
|
+
@pytest.mark.asyncio
|
|
566
|
+
async def test_range_lookup(self):
|
|
567
|
+
"""Test range lookup."""
|
|
568
|
+
runner = Runner(
|
|
569
|
+
"""
|
|
570
|
+
with range(1, 10) as numbers
|
|
571
|
+
return
|
|
572
|
+
numbers[:] as subset1,
|
|
573
|
+
numbers[0:3] as subset2,
|
|
574
|
+
numbers[:-2] as subset3
|
|
575
|
+
"""
|
|
576
|
+
)
|
|
577
|
+
await runner.run()
|
|
578
|
+
results = runner.results
|
|
579
|
+
assert len(results) == 1
|
|
580
|
+
assert results[0] == {
|
|
581
|
+
"subset1": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
582
|
+
"subset2": [1, 2, 3],
|
|
583
|
+
"subset3": [1, 2, 3, 4, 5, 6, 7, 8],
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
@pytest.mark.asyncio
|
|
587
|
+
async def test_return_negative_number(self):
|
|
588
|
+
"""Test return -1."""
|
|
589
|
+
runner = Runner("return -1 as num")
|
|
590
|
+
await runner.run()
|
|
591
|
+
results = runner.results
|
|
592
|
+
assert len(results) == 1
|
|
593
|
+
assert results[0] == {"num": -1}
|
|
594
|
+
|
|
595
|
+
@pytest.mark.asyncio
|
|
596
|
+
async def test_unwind_range_lookup(self):
|
|
597
|
+
"""Test unwind range lookup."""
|
|
598
|
+
runner = Runner(
|
|
599
|
+
"""
|
|
600
|
+
with range(1,10) as arr
|
|
601
|
+
unwind arr[2:-2] as a
|
|
602
|
+
return a
|
|
603
|
+
"""
|
|
604
|
+
)
|
|
605
|
+
await runner.run()
|
|
606
|
+
results = runner.results
|
|
607
|
+
assert len(results) == 6
|
|
608
|
+
assert results[0] == {"a": 3}
|
|
609
|
+
assert results[5] == {"a": 8}
|
|
610
|
+
|
|
611
|
+
@pytest.mark.asyncio
|
|
612
|
+
async def test_range_with_size(self):
|
|
613
|
+
"""Test range with size."""
|
|
614
|
+
runner = Runner(
|
|
615
|
+
"""
|
|
616
|
+
with range(1,10) as data
|
|
617
|
+
return range(0, size(data)-1) as indices
|
|
618
|
+
"""
|
|
619
|
+
)
|
|
620
|
+
await runner.run()
|
|
621
|
+
results = runner.results
|
|
622
|
+
assert len(results) == 1
|
|
623
|
+
assert results[0] == {"indices": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
|
|
624
|
+
|
|
625
|
+
@pytest.mark.asyncio
|
|
626
|
+
async def test_keys_function(self):
|
|
627
|
+
"""Test keys function."""
|
|
628
|
+
runner = Runner('RETURN keys({name: "Alice", age: 30}) as keys')
|
|
629
|
+
await runner.run()
|
|
630
|
+
results = runner.results
|
|
631
|
+
assert len(results) == 1
|
|
632
|
+
assert results[0] == {"keys": ["name", "age"]}
|
|
633
|
+
|
|
634
|
+
@pytest.mark.asyncio
|
|
635
|
+
async def test_type_function(self):
|
|
636
|
+
"""Test type function."""
|
|
637
|
+
runner = Runner(
|
|
638
|
+
"""
|
|
639
|
+
RETURN type(123) as type1,
|
|
640
|
+
type("hello") as type2,
|
|
641
|
+
type([1, 2, 3]) as type3,
|
|
642
|
+
type({a: 1, b: 2}) as type4,
|
|
643
|
+
type(null) as type5
|
|
644
|
+
"""
|
|
645
|
+
)
|
|
646
|
+
await runner.run()
|
|
647
|
+
results = runner.results
|
|
648
|
+
assert len(results) == 1
|
|
649
|
+
assert results[0] == {
|
|
650
|
+
"type1": "number",
|
|
651
|
+
"type2": "string",
|
|
652
|
+
"type3": "array",
|
|
653
|
+
"type4": "object",
|
|
654
|
+
"type5": "null",
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
@pytest.mark.asyncio
|
|
658
|
+
async def test_equality_comparison(self):
|
|
659
|
+
"""Test equality comparison."""
|
|
660
|
+
runner = Runner(
|
|
661
|
+
"""
|
|
662
|
+
unwind range(1,10) as i
|
|
663
|
+
return i=5 as `isEqual`, i<>5 as `isNotEqual`
|
|
664
|
+
"""
|
|
665
|
+
)
|
|
666
|
+
await runner.run()
|
|
667
|
+
results = runner.results
|
|
668
|
+
assert len(results) == 10
|
|
669
|
+
for index, result in enumerate(results):
|
|
670
|
+
if index + 1 == 5:
|
|
671
|
+
assert result == {"isEqual": 1, "isNotEqual": 0}
|
|
672
|
+
else:
|
|
673
|
+
assert result == {"isEqual": 0, "isNotEqual": 1}
|
|
674
|
+
|
|
675
|
+
@pytest.mark.asyncio
|
|
676
|
+
async def test_create_node_operation(self):
|
|
677
|
+
"""Test create node operation."""
|
|
678
|
+
runner = Runner(
|
|
679
|
+
"""
|
|
680
|
+
CREATE VIRTUAL (:TestPerson) AS {
|
|
681
|
+
with 1 as x
|
|
682
|
+
RETURN x
|
|
683
|
+
}
|
|
684
|
+
"""
|
|
685
|
+
)
|
|
686
|
+
await runner.run()
|
|
687
|
+
results = runner.results
|
|
688
|
+
assert len(results) == 0
|
|
689
|
+
|
|
690
|
+
@pytest.mark.asyncio
|
|
691
|
+
async def test_create_node_and_match_operations(self):
|
|
692
|
+
"""Test create node and match operations."""
|
|
693
|
+
create = Runner(
|
|
694
|
+
"""
|
|
695
|
+
CREATE VIRTUAL (:MatchPerson) AS {
|
|
696
|
+
unwind [
|
|
697
|
+
{id: 1, name: 'Person 1'},
|
|
698
|
+
{id: 2, name: 'Person 2'}
|
|
699
|
+
] as record
|
|
700
|
+
RETURN record.id as id, record.name as name
|
|
701
|
+
}
|
|
702
|
+
"""
|
|
703
|
+
)
|
|
704
|
+
await create.run()
|
|
705
|
+
match = Runner("MATCH (n:MatchPerson) RETURN n")
|
|
706
|
+
await match.run()
|
|
707
|
+
results = match.results
|
|
708
|
+
assert len(results) == 2
|
|
709
|
+
assert results[0]["n"] is not None
|
|
710
|
+
assert results[0]["n"]["id"] == 1
|
|
711
|
+
assert results[0]["n"]["name"] == "Person 1"
|
|
712
|
+
assert results[1]["n"] is not None
|
|
713
|
+
assert results[1]["n"]["id"] == 2
|
|
714
|
+
assert results[1]["n"]["name"] == "Person 2"
|
|
715
|
+
|
|
716
|
+
@pytest.mark.asyncio
|
|
717
|
+
async def test_complex_match_operation(self):
|
|
718
|
+
"""Test complex match operation."""
|
|
719
|
+
await Runner(
|
|
720
|
+
"""
|
|
721
|
+
CREATE VIRTUAL (:AgePerson) AS {
|
|
722
|
+
unwind [
|
|
723
|
+
{id: 1, name: 'Person 1', age: 30},
|
|
724
|
+
{id: 2, name: 'Person 2', age: 25},
|
|
725
|
+
{id: 3, name: 'Person 3', age: 35}
|
|
726
|
+
] as record
|
|
727
|
+
RETURN record.id as id, record.name as name, record.age as age
|
|
728
|
+
}
|
|
729
|
+
"""
|
|
730
|
+
).run()
|
|
731
|
+
match = Runner(
|
|
732
|
+
"""
|
|
733
|
+
MATCH (n:AgePerson)
|
|
734
|
+
WHERE n.age > 29
|
|
735
|
+
RETURN n.name AS name, n.age AS age
|
|
736
|
+
"""
|
|
737
|
+
)
|
|
738
|
+
await match.run()
|
|
739
|
+
results = match.results
|
|
740
|
+
assert len(results) == 2
|
|
741
|
+
assert results[0] == {"name": "Person 1", "age": 30}
|
|
742
|
+
assert results[1] == {"name": "Person 3", "age": 35}
|
|
743
|
+
|
|
744
|
+
@pytest.mark.asyncio
|
|
745
|
+
async def test_match(self):
|
|
746
|
+
"""Test match operation."""
|
|
747
|
+
await Runner(
|
|
748
|
+
"""
|
|
749
|
+
CREATE VIRTUAL (:SimplePerson) AS {
|
|
750
|
+
unwind [
|
|
751
|
+
{id: 1, name: 'Person 1'},
|
|
752
|
+
{id: 2, name: 'Person 2'}
|
|
753
|
+
] as record
|
|
754
|
+
RETURN record.id as id, record.name as name
|
|
755
|
+
}
|
|
756
|
+
"""
|
|
757
|
+
).run()
|
|
758
|
+
match = Runner(
|
|
759
|
+
"""
|
|
760
|
+
MATCH (n:SimplePerson)
|
|
761
|
+
RETURN n.name AS name
|
|
762
|
+
"""
|
|
763
|
+
)
|
|
764
|
+
await match.run()
|
|
765
|
+
results = match.results
|
|
766
|
+
assert len(results) == 2
|
|
767
|
+
assert results[0] == {"name": "Person 1"}
|
|
768
|
+
assert results[1] == {"name": "Person 2"}
|
|
769
|
+
|
|
770
|
+
@pytest.mark.asyncio
|
|
771
|
+
async def test_match_with_nested_join(self):
|
|
772
|
+
"""Test match with nested join."""
|
|
773
|
+
await Runner(
|
|
774
|
+
"""
|
|
775
|
+
CREATE VIRTUAL (:JoinPerson) AS {
|
|
776
|
+
unwind [
|
|
777
|
+
{id: 1, name: 'Person 1'},
|
|
778
|
+
{id: 2, name: 'Person 2'}
|
|
779
|
+
] as record
|
|
780
|
+
RETURN record.id as id, record.name as name
|
|
781
|
+
}
|
|
782
|
+
"""
|
|
783
|
+
).run()
|
|
784
|
+
match = Runner(
|
|
785
|
+
"""
|
|
786
|
+
MATCH (a:JoinPerson), (b:JoinPerson)
|
|
787
|
+
WHERE a.id <> b.id
|
|
788
|
+
RETURN a.name AS name1, b.name AS name2
|
|
789
|
+
"""
|
|
790
|
+
)
|
|
791
|
+
await match.run()
|
|
792
|
+
results = match.results
|
|
793
|
+
assert len(results) == 2
|
|
794
|
+
assert results[0] == {"name1": "Person 1", "name2": "Person 2"}
|
|
795
|
+
assert results[1] == {"name1": "Person 2", "name2": "Person 1"}
|
|
796
|
+
|
|
797
|
+
@pytest.mark.asyncio
|
|
798
|
+
async def test_match_with_graph_pattern(self):
|
|
799
|
+
"""Test match with graph pattern."""
|
|
800
|
+
await Runner(
|
|
801
|
+
"""
|
|
802
|
+
CREATE VIRTUAL (:User) AS {
|
|
803
|
+
UNWIND [
|
|
804
|
+
{id: 1, name: 'User 1', manager_id: null},
|
|
805
|
+
{id: 2, name: 'User 2', manager_id: 1},
|
|
806
|
+
{id: 3, name: 'User 3', manager_id: 1},
|
|
807
|
+
{id: 4, name: 'User 4', manager_id: 2}
|
|
808
|
+
] AS record
|
|
809
|
+
RETURN record.id AS id, record.name AS name, record.manager_id AS manager_id
|
|
810
|
+
}
|
|
811
|
+
"""
|
|
812
|
+
).run()
|
|
813
|
+
await Runner(
|
|
814
|
+
"""
|
|
815
|
+
CREATE VIRTUAL (:User)-[:MANAGED_BY]-(:User) AS {
|
|
816
|
+
UNWIND [
|
|
817
|
+
{id: 1, manager_id: null},
|
|
818
|
+
{id: 2, manager_id: 1},
|
|
819
|
+
{id: 3, manager_id: 1},
|
|
820
|
+
{id: 4, manager_id: 2}
|
|
821
|
+
] AS record
|
|
822
|
+
RETURN record.id AS left_id, record.manager_id AS right_id
|
|
823
|
+
}
|
|
824
|
+
"""
|
|
825
|
+
).run()
|
|
826
|
+
match = Runner(
|
|
827
|
+
"""
|
|
828
|
+
MATCH (user:User)-[r:MANAGED_BY]-(manager:User)
|
|
829
|
+
RETURN user.name AS user, manager.name AS manager
|
|
830
|
+
"""
|
|
831
|
+
)
|
|
832
|
+
await match.run()
|
|
833
|
+
results = match.results
|
|
834
|
+
assert len(results) == 3
|
|
835
|
+
assert results[0] == {"user": "User 2", "manager": "User 1"}
|
|
836
|
+
assert results[1] == {"user": "User 3", "manager": "User 1"}
|
|
837
|
+
assert results[2] == {"user": "User 4", "manager": "User 2"}
|
|
838
|
+
|
|
839
|
+
@pytest.mark.asyncio
|
|
840
|
+
async def test_match_with_multiple_hop_graph_pattern(self):
|
|
841
|
+
"""Test match with multiple hop graph pattern."""
|
|
842
|
+
await Runner(
|
|
843
|
+
"""
|
|
844
|
+
CREATE VIRTUAL (:HopPerson) AS {
|
|
845
|
+
unwind [
|
|
846
|
+
{id: 1, name: 'Person 1'},
|
|
847
|
+
{id: 2, name: 'Person 2'},
|
|
848
|
+
{id: 3, name: 'Person 3'},
|
|
849
|
+
{id: 4, name: 'Person 4'}
|
|
850
|
+
] as record
|
|
851
|
+
RETURN record.id as id, record.name as name
|
|
852
|
+
}
|
|
853
|
+
"""
|
|
854
|
+
).run()
|
|
855
|
+
await Runner(
|
|
856
|
+
"""
|
|
857
|
+
CREATE VIRTUAL (:HopPerson)-[:KNOWS]-(:HopPerson) AS {
|
|
858
|
+
unwind [
|
|
859
|
+
{left_id: 1, right_id: 2},
|
|
860
|
+
{left_id: 2, right_id: 3}
|
|
861
|
+
] as record
|
|
862
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
863
|
+
}
|
|
864
|
+
"""
|
|
865
|
+
).run()
|
|
866
|
+
match = Runner(
|
|
867
|
+
"""
|
|
868
|
+
MATCH (a:HopPerson)-[:KNOWS*]-(c:HopPerson)
|
|
869
|
+
RETURN a.name AS name1, c.name AS name2
|
|
870
|
+
"""
|
|
871
|
+
)
|
|
872
|
+
await match.run()
|
|
873
|
+
results = match.results
|
|
874
|
+
assert len(results) == 3
|
|
875
|
+
assert results[0] == {"name1": "Person 1", "name2": "Person 2"}
|
|
876
|
+
assert results[1] == {"name1": "Person 1", "name2": "Person 3"}
|
|
877
|
+
assert results[2] == {"name1": "Person 2", "name2": "Person 3"}
|
|
878
|
+
|
|
879
|
+
@pytest.mark.asyncio
|
|
880
|
+
async def test_match_with_double_graph_pattern(self):
|
|
881
|
+
"""Test match with double graph pattern."""
|
|
882
|
+
await Runner(
|
|
883
|
+
"""
|
|
884
|
+
CREATE VIRTUAL (:DoublePerson) AS {
|
|
885
|
+
unwind [
|
|
886
|
+
{id: 1, name: 'Person 1'},
|
|
887
|
+
{id: 2, name: 'Person 2'},
|
|
888
|
+
{id: 3, name: 'Person 3'},
|
|
889
|
+
{id: 4, name: 'Person 4'}
|
|
890
|
+
] as record
|
|
891
|
+
RETURN record.id as id, record.name as name
|
|
892
|
+
}
|
|
893
|
+
"""
|
|
894
|
+
).run()
|
|
895
|
+
await Runner(
|
|
896
|
+
"""
|
|
897
|
+
CREATE VIRTUAL (:DoublePerson)-[:KNOWS]-(:DoublePerson) AS {
|
|
898
|
+
unwind [
|
|
899
|
+
{left_id: 1, right_id: 2},
|
|
900
|
+
{left_id: 2, right_id: 3},
|
|
901
|
+
{left_id: 3, right_id: 4}
|
|
902
|
+
] as record
|
|
903
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
904
|
+
}
|
|
905
|
+
"""
|
|
906
|
+
).run()
|
|
907
|
+
match = Runner(
|
|
908
|
+
"""
|
|
909
|
+
MATCH (a:DoublePerson)-[:KNOWS]-(b:DoublePerson)-[:KNOWS]-(c:DoublePerson)
|
|
910
|
+
RETURN a.name AS name1, b.name AS name2, c.name AS name3
|
|
911
|
+
"""
|
|
912
|
+
)
|
|
913
|
+
await match.run()
|
|
914
|
+
results = match.results
|
|
915
|
+
assert len(results) == 2
|
|
916
|
+
assert results[0] == {"name1": "Person 1", "name2": "Person 2", "name3": "Person 3"}
|
|
917
|
+
assert results[1] == {"name1": "Person 2", "name2": "Person 3", "name3": "Person 4"}
|
|
918
|
+
|
|
919
|
+
@pytest.mark.asyncio
|
|
920
|
+
async def test_match_with_referenced_to_previous_variable(self):
|
|
921
|
+
"""Test match with referenced to previous variable."""
|
|
922
|
+
await Runner(
|
|
923
|
+
"""
|
|
924
|
+
CREATE VIRTUAL (:RefPerson) AS {
|
|
925
|
+
unwind [
|
|
926
|
+
{id: 1, name: 'Person 1'},
|
|
927
|
+
{id: 2, name: 'Person 2'},
|
|
928
|
+
{id: 3, name: 'Person 3'},
|
|
929
|
+
{id: 4, name: 'Person 4'}
|
|
930
|
+
] as record
|
|
931
|
+
RETURN record.id as id, record.name as name
|
|
932
|
+
}
|
|
933
|
+
"""
|
|
934
|
+
).run()
|
|
935
|
+
await Runner(
|
|
936
|
+
"""
|
|
937
|
+
CREATE VIRTUAL (:RefPerson)-[:KNOWS]-(:RefPerson) AS {
|
|
938
|
+
unwind [
|
|
939
|
+
{left_id: 1, right_id: 2},
|
|
940
|
+
{left_id: 2, right_id: 3},
|
|
941
|
+
{left_id: 3, right_id: 4}
|
|
942
|
+
] as record
|
|
943
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
944
|
+
}
|
|
945
|
+
"""
|
|
946
|
+
).run()
|
|
947
|
+
match = Runner(
|
|
948
|
+
"""
|
|
949
|
+
MATCH (a:RefPerson)-[:KNOWS]-(b:RefPerson)
|
|
950
|
+
MATCH (b)-[:KNOWS]-(c:RefPerson)
|
|
951
|
+
RETURN a.name AS name1, b.name AS name2, c.name AS name3
|
|
952
|
+
"""
|
|
953
|
+
)
|
|
954
|
+
await match.run()
|
|
955
|
+
results = match.results
|
|
956
|
+
assert len(results) == 2
|
|
957
|
+
assert results[0] == {"name1": "Person 1", "name2": "Person 2", "name3": "Person 3"}
|
|
958
|
+
assert results[1] == {"name1": "Person 2", "name2": "Person 3", "name3": "Person 4"}
|
|
959
|
+
|
|
960
|
+
@pytest.mark.asyncio
|
|
961
|
+
async def test_match_and_return_full_node(self):
|
|
962
|
+
"""Test match and return full node."""
|
|
963
|
+
await Runner(
|
|
964
|
+
"""
|
|
965
|
+
CREATE VIRTUAL (:FullPerson) AS {
|
|
966
|
+
unwind [
|
|
967
|
+
{id: 1, name: 'Person 1'},
|
|
968
|
+
{id: 2, name: 'Person 2'}
|
|
969
|
+
] as record
|
|
970
|
+
RETURN record.id as id, record.name as name
|
|
971
|
+
}
|
|
972
|
+
"""
|
|
973
|
+
).run()
|
|
974
|
+
match = Runner(
|
|
975
|
+
"""
|
|
976
|
+
MATCH (n:FullPerson)
|
|
977
|
+
RETURN n
|
|
978
|
+
"""
|
|
979
|
+
)
|
|
980
|
+
await match.run()
|
|
981
|
+
results = match.results
|
|
982
|
+
assert len(results) == 2
|
|
983
|
+
assert results[0]["n"] is not None
|
|
984
|
+
assert results[0]["n"]["id"] == 1
|
|
985
|
+
assert results[0]["n"]["name"] == "Person 1"
|
|
986
|
+
assert results[1]["n"] is not None
|
|
987
|
+
assert results[1]["n"]["id"] == 2
|
|
988
|
+
assert results[1]["n"]["name"] == "Person 2"
|
|
989
|
+
|
|
990
|
+
@pytest.mark.asyncio
|
|
991
|
+
async def test_call_operation_with_async_function(self):
|
|
992
|
+
"""Test call operation with async function."""
|
|
993
|
+
runner = Runner("CALL calltestfunction() YIELD result RETURN result")
|
|
994
|
+
await runner.run()
|
|
995
|
+
results = runner.results
|
|
996
|
+
assert len(results) == 3
|
|
997
|
+
assert results[0] == {"result": 1}
|
|
998
|
+
assert results[1] == {"result": 2}
|
|
999
|
+
assert results[2] == {"result": 3}
|
|
1000
|
+
|
|
1001
|
+
@pytest.mark.asyncio
|
|
1002
|
+
async def test_call_operation_with_aggregation(self):
|
|
1003
|
+
"""Test call operation with aggregation."""
|
|
1004
|
+
runner = Runner("CALL calltestfunction() YIELD result RETURN sum(result) as total")
|
|
1005
|
+
await runner.run()
|
|
1006
|
+
results = runner.results
|
|
1007
|
+
assert len(results) == 1
|
|
1008
|
+
assert results[0] == {"total": 6}
|
|
1009
|
+
|
|
1010
|
+
@pytest.mark.asyncio
|
|
1011
|
+
async def test_call_operation_as_last_operation(self):
|
|
1012
|
+
"""Test call operation as last operation."""
|
|
1013
|
+
runner = Runner("CALL calltestfunction()")
|
|
1014
|
+
await runner.run()
|
|
1015
|
+
results = runner.results
|
|
1016
|
+
assert len(results) == 3
|
|
1017
|
+
assert results[0] == {"result": 1, "dummy": "a"}
|
|
1018
|
+
assert results[1] == {"result": 2, "dummy": "b"}
|
|
1019
|
+
assert results[2] == {"result": 3, "dummy": "c"}
|
|
1020
|
+
|
|
1021
|
+
@pytest.mark.asyncio
|
|
1022
|
+
async def test_call_operation_as_last_operation_with_yield(self):
|
|
1023
|
+
"""Test call operation as last operation with yield."""
|
|
1024
|
+
runner = Runner("CALL calltestfunction() YIELD result")
|
|
1025
|
+
await runner.run()
|
|
1026
|
+
results = runner.results
|
|
1027
|
+
assert len(results) == 3
|
|
1028
|
+
assert results[0] == {"result": 1}
|
|
1029
|
+
assert results[1] == {"result": 2}
|
|
1030
|
+
assert results[2] == {"result": 3}
|
|
1031
|
+
|
|
1032
|
+
def test_call_operation_with_no_yielded_expressions(self):
|
|
1033
|
+
"""Test call operation with no yielded expressions throws error."""
|
|
1034
|
+
with pytest.raises(ValueError, match="CALL operations must have a YIELD clause"):
|
|
1035
|
+
Runner("CALL calltestfunctionnoobject() RETURN 1")
|
|
1036
|
+
|
|
1037
|
+
@pytest.mark.asyncio
|
|
1038
|
+
async def test_return_graph_pattern(self):
|
|
1039
|
+
"""Test return graph pattern."""
|
|
1040
|
+
await Runner(
|
|
1041
|
+
"""
|
|
1042
|
+
CREATE VIRTUAL (:PatternPerson) AS {
|
|
1043
|
+
unwind [
|
|
1044
|
+
{id: 1, name: 'Person 1'},
|
|
1045
|
+
{id: 2, name: 'Person 2'}
|
|
1046
|
+
] as record
|
|
1047
|
+
RETURN record.id as id, record.name as name
|
|
1048
|
+
}
|
|
1049
|
+
"""
|
|
1050
|
+
).run()
|
|
1051
|
+
await Runner(
|
|
1052
|
+
"""
|
|
1053
|
+
CREATE VIRTUAL (:PatternPerson)-[:KNOWS]-(:PatternPerson) AS {
|
|
1054
|
+
unwind [
|
|
1055
|
+
{left_id: 1, since: '2020-01-01', right_id: 2}
|
|
1056
|
+
] as record
|
|
1057
|
+
RETURN record.left_id as left_id, record.since as since, record.right_id as right_id
|
|
1058
|
+
}
|
|
1059
|
+
"""
|
|
1060
|
+
).run()
|
|
1061
|
+
match = Runner(
|
|
1062
|
+
"""
|
|
1063
|
+
MATCH p=(:PatternPerson)-[:KNOWS]-(:PatternPerson)
|
|
1064
|
+
RETURN p AS pattern
|
|
1065
|
+
"""
|
|
1066
|
+
)
|
|
1067
|
+
await match.run()
|
|
1068
|
+
results = match.results
|
|
1069
|
+
assert len(results) == 1
|
|
1070
|
+
assert results[0]["pattern"] is not None
|
|
1071
|
+
assert len(results[0]["pattern"]) == 3
|
|
1072
|
+
|
|
1073
|
+
@pytest.mark.asyncio
|
|
1074
|
+
async def test_circular_graph_pattern(self):
|
|
1075
|
+
"""Test circular graph pattern."""
|
|
1076
|
+
await Runner(
|
|
1077
|
+
"""
|
|
1078
|
+
CREATE VIRTUAL (:CircularPerson) AS {
|
|
1079
|
+
unwind [
|
|
1080
|
+
{id: 1, name: 'Person 1'},
|
|
1081
|
+
{id: 2, name: 'Person 2'}
|
|
1082
|
+
] as record
|
|
1083
|
+
RETURN record.id as id, record.name as name
|
|
1084
|
+
}
|
|
1085
|
+
"""
|
|
1086
|
+
).run()
|
|
1087
|
+
await Runner(
|
|
1088
|
+
"""
|
|
1089
|
+
CREATE VIRTUAL (:CircularPerson)-[:KNOWS]-(:CircularPerson) AS {
|
|
1090
|
+
unwind [
|
|
1091
|
+
{left_id: 1, right_id: 2},
|
|
1092
|
+
{left_id: 2, right_id: 1}
|
|
1093
|
+
] as record
|
|
1094
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1095
|
+
}
|
|
1096
|
+
"""
|
|
1097
|
+
).run()
|
|
1098
|
+
match = Runner(
|
|
1099
|
+
"""
|
|
1100
|
+
MATCH p=(:CircularPerson)-[:KNOWS]-(:CircularPerson)-[:KNOWS]-(:CircularPerson)
|
|
1101
|
+
RETURN p AS pattern
|
|
1102
|
+
"""
|
|
1103
|
+
)
|
|
1104
|
+
await match.run()
|
|
1105
|
+
results = match.results
|
|
1106
|
+
assert len(results) == 2
|
|
1107
|
+
|
|
1108
|
+
@pytest.mark.asyncio
|
|
1109
|
+
async def test_circular_graph_pattern_with_variable_length_should_throw_error(self):
|
|
1110
|
+
"""Test circular graph pattern with variable length should throw error."""
|
|
1111
|
+
await Runner(
|
|
1112
|
+
"""
|
|
1113
|
+
CREATE VIRTUAL (:CircularVarPerson) AS {
|
|
1114
|
+
unwind [
|
|
1115
|
+
{id: 1, name: 'Person 1'},
|
|
1116
|
+
{id: 2, name: 'Person 2'}
|
|
1117
|
+
] as record
|
|
1118
|
+
RETURN record.id as id, record.name as name
|
|
1119
|
+
}
|
|
1120
|
+
"""
|
|
1121
|
+
).run()
|
|
1122
|
+
await Runner(
|
|
1123
|
+
"""
|
|
1124
|
+
CREATE VIRTUAL (:CircularVarPerson)-[:KNOWS]-(:CircularVarPerson) AS {
|
|
1125
|
+
unwind [
|
|
1126
|
+
{left_id: 1, right_id: 2},
|
|
1127
|
+
{left_id: 2, right_id: 1}
|
|
1128
|
+
] as record
|
|
1129
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1130
|
+
}
|
|
1131
|
+
"""
|
|
1132
|
+
).run()
|
|
1133
|
+
match = Runner(
|
|
1134
|
+
"""
|
|
1135
|
+
MATCH p=(:CircularVarPerson)-[:KNOWS*]-(:CircularVarPerson)
|
|
1136
|
+
RETURN p AS pattern
|
|
1137
|
+
"""
|
|
1138
|
+
)
|
|
1139
|
+
with pytest.raises(ValueError, match="Circular relationship detected"):
|
|
1140
|
+
await match.run()
|
|
1141
|
+
|
|
1142
|
+
@pytest.mark.asyncio
|
|
1143
|
+
async def test_multi_hop_match_with_variable_length_relationships(self):
|
|
1144
|
+
"""Test multi-hop match with variable length relationships."""
|
|
1145
|
+
await Runner(
|
|
1146
|
+
"""
|
|
1147
|
+
CREATE VIRTUAL (:MultiHopPerson) AS {
|
|
1148
|
+
unwind [
|
|
1149
|
+
{id: 1, name: 'Person 1'},
|
|
1150
|
+
{id: 2, name: 'Person 2'},
|
|
1151
|
+
{id: 3, name: 'Person 3'},
|
|
1152
|
+
{id: 4, name: 'Person 4'}
|
|
1153
|
+
] as record
|
|
1154
|
+
RETURN record.id as id, record.name as name
|
|
1155
|
+
}
|
|
1156
|
+
"""
|
|
1157
|
+
).run()
|
|
1158
|
+
await Runner(
|
|
1159
|
+
"""
|
|
1160
|
+
CREATE VIRTUAL (:MultiHopPerson)-[:KNOWS]-(:MultiHopPerson) AS {
|
|
1161
|
+
unwind [
|
|
1162
|
+
{left_id: 1, right_id: 2},
|
|
1163
|
+
{left_id: 2, right_id: 3},
|
|
1164
|
+
{left_id: 3, right_id: 4}
|
|
1165
|
+
] as record
|
|
1166
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1167
|
+
}
|
|
1168
|
+
"""
|
|
1169
|
+
).run()
|
|
1170
|
+
match = Runner(
|
|
1171
|
+
"""
|
|
1172
|
+
MATCH (a:MultiHopPerson)-[r:KNOWS*0..3]->(b:MultiHopPerson)
|
|
1173
|
+
RETURN a, r, b
|
|
1174
|
+
"""
|
|
1175
|
+
)
|
|
1176
|
+
await match.run()
|
|
1177
|
+
results = match.results
|
|
1178
|
+
assert len(results) == 6
|
|
1179
|
+
|
|
1180
|
+
@pytest.mark.asyncio
|
|
1181
|
+
async def test_return_match_pattern_with_variable_length_relationships(self):
|
|
1182
|
+
"""Test return match pattern with variable length relationships."""
|
|
1183
|
+
await Runner(
|
|
1184
|
+
"""
|
|
1185
|
+
CREATE VIRTUAL (:VarLenPerson) AS {
|
|
1186
|
+
unwind [
|
|
1187
|
+
{id: 1, name: 'Person 1'},
|
|
1188
|
+
{id: 2, name: 'Person 2'},
|
|
1189
|
+
{id: 3, name: 'Person 3'},
|
|
1190
|
+
{id: 4, name: 'Person 4'}
|
|
1191
|
+
] as record
|
|
1192
|
+
RETURN record.id as id, record.name as name
|
|
1193
|
+
}
|
|
1194
|
+
"""
|
|
1195
|
+
).run()
|
|
1196
|
+
await Runner(
|
|
1197
|
+
"""
|
|
1198
|
+
CREATE VIRTUAL (:VarLenPerson)-[:KNOWS]-(:VarLenPerson) AS {
|
|
1199
|
+
unwind [
|
|
1200
|
+
{left_id: 1, right_id: 2},
|
|
1201
|
+
{left_id: 2, right_id: 3},
|
|
1202
|
+
{left_id: 3, right_id: 4}
|
|
1203
|
+
] as record
|
|
1204
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1205
|
+
}
|
|
1206
|
+
"""
|
|
1207
|
+
).run()
|
|
1208
|
+
match = Runner(
|
|
1209
|
+
"""
|
|
1210
|
+
MATCH p=(a:VarLenPerson)-[:KNOWS*0..3]->(b:VarLenPerson)
|
|
1211
|
+
RETURN p AS pattern
|
|
1212
|
+
"""
|
|
1213
|
+
)
|
|
1214
|
+
await match.run()
|
|
1215
|
+
results = match.results
|
|
1216
|
+
assert len(results) == 6
|
|
1217
|
+
|
|
1218
|
+
@pytest.mark.asyncio
|
|
1219
|
+
async def test_statement_with_graph_pattern_in_where_clause(self):
|
|
1220
|
+
"""Test statement with graph pattern in where clause."""
|
|
1221
|
+
await Runner(
|
|
1222
|
+
"""
|
|
1223
|
+
CREATE VIRTUAL (:WherePerson) AS {
|
|
1224
|
+
unwind [
|
|
1225
|
+
{id: 1, name: 'Person 1'},
|
|
1226
|
+
{id: 2, name: 'Person 2'},
|
|
1227
|
+
{id: 3, name: 'Person 3'},
|
|
1228
|
+
{id: 4, name: 'Person 4'}
|
|
1229
|
+
] as record
|
|
1230
|
+
RETURN record.id as id, record.name as name
|
|
1231
|
+
}
|
|
1232
|
+
"""
|
|
1233
|
+
).run()
|
|
1234
|
+
await Runner(
|
|
1235
|
+
"""
|
|
1236
|
+
CREATE VIRTUAL (:WherePerson)-[:KNOWS]-(:WherePerson) AS {
|
|
1237
|
+
unwind [
|
|
1238
|
+
{left_id: 1, right_id: 2},
|
|
1239
|
+
{left_id: 2, right_id: 3},
|
|
1240
|
+
{left_id: 3, right_id: 4}
|
|
1241
|
+
] as record
|
|
1242
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1243
|
+
}
|
|
1244
|
+
"""
|
|
1245
|
+
).run()
|
|
1246
|
+
match = Runner(
|
|
1247
|
+
"""
|
|
1248
|
+
MATCH (a:WherePerson), (b:WherePerson)
|
|
1249
|
+
WHERE (a)-[:KNOWS]->(b)
|
|
1250
|
+
RETURN a.name AS name1, b.name AS name2
|
|
1251
|
+
"""
|
|
1252
|
+
)
|
|
1253
|
+
await match.run()
|
|
1254
|
+
results = match.results
|
|
1255
|
+
assert len(results) == 3
|
|
1256
|
+
assert results[0] == {"name1": "Person 1", "name2": "Person 2"}
|
|
1257
|
+
assert results[1] == {"name1": "Person 2", "name2": "Person 3"}
|
|
1258
|
+
assert results[2] == {"name1": "Person 3", "name2": "Person 4"}
|
|
1259
|
+
|
|
1260
|
+
@pytest.mark.asyncio
|
|
1261
|
+
async def test_person_who_does_not_know_anyone(self):
|
|
1262
|
+
"""Test person who does not know anyone."""
|
|
1263
|
+
await Runner(
|
|
1264
|
+
"""
|
|
1265
|
+
CREATE VIRTUAL (:LonePerson) AS {
|
|
1266
|
+
unwind [
|
|
1267
|
+
{id: 1, name: 'Person 1'},
|
|
1268
|
+
{id: 2, name: 'Person 2'},
|
|
1269
|
+
{id: 3, name: 'Person 3'}
|
|
1270
|
+
] as record
|
|
1271
|
+
RETURN record.id as id, record.name as name
|
|
1272
|
+
}
|
|
1273
|
+
"""
|
|
1274
|
+
).run()
|
|
1275
|
+
await Runner(
|
|
1276
|
+
"""
|
|
1277
|
+
CREATE VIRTUAL (:LonePerson)-[:KNOWS]-(:LonePerson) AS {
|
|
1278
|
+
unwind [
|
|
1279
|
+
{left_id: 1, right_id: 2},
|
|
1280
|
+
{left_id: 2, right_id: 1}
|
|
1281
|
+
] as record
|
|
1282
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1283
|
+
}
|
|
1284
|
+
"""
|
|
1285
|
+
).run()
|
|
1286
|
+
match = Runner(
|
|
1287
|
+
"""
|
|
1288
|
+
MATCH (a:LonePerson)
|
|
1289
|
+
WHERE NOT (a)-[:KNOWS]->(:LonePerson)
|
|
1290
|
+
RETURN a.name AS name
|
|
1291
|
+
"""
|
|
1292
|
+
)
|
|
1293
|
+
await match.run()
|
|
1294
|
+
results = match.results
|
|
1295
|
+
assert len(results) == 1
|
|
1296
|
+
assert results[0] == {"name": "Person 3"}
|
|
1297
|
+
|
|
1298
|
+
@pytest.mark.asyncio
|
|
1299
|
+
async def test_manager_chain(self):
|
|
1300
|
+
"""Test manager chain."""
|
|
1301
|
+
await Runner(
|
|
1302
|
+
"""
|
|
1303
|
+
CREATE VIRTUAL (:ChainEmployee) AS {
|
|
1304
|
+
unwind [
|
|
1305
|
+
{id: 1, name: 'Employee 1'},
|
|
1306
|
+
{id: 2, name: 'Employee 2'},
|
|
1307
|
+
{id: 3, name: 'Employee 3'},
|
|
1308
|
+
{id: 4, name: 'Employee 4'}
|
|
1309
|
+
] as record
|
|
1310
|
+
RETURN record.id as id, record.name as name
|
|
1311
|
+
}
|
|
1312
|
+
"""
|
|
1313
|
+
).run()
|
|
1314
|
+
await Runner(
|
|
1315
|
+
"""
|
|
1316
|
+
CREATE VIRTUAL (:ChainEmployee)-[:MANAGED_BY]-(:ChainEmployee) AS {
|
|
1317
|
+
unwind [
|
|
1318
|
+
{left_id: 2, right_id: 1},
|
|
1319
|
+
{left_id: 3, right_id: 2},
|
|
1320
|
+
{left_id: 4, right_id: 2}
|
|
1321
|
+
] as record
|
|
1322
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1323
|
+
}
|
|
1324
|
+
"""
|
|
1325
|
+
).run()
|
|
1326
|
+
match = Runner(
|
|
1327
|
+
"""
|
|
1328
|
+
MATCH p=(e:ChainEmployee)-[:MANAGED_BY*]->(m:ChainEmployee)
|
|
1329
|
+
WHERE NOT (m)-[:MANAGED_BY]->(:ChainEmployee)
|
|
1330
|
+
RETURN p
|
|
1331
|
+
"""
|
|
1332
|
+
)
|
|
1333
|
+
await match.run()
|
|
1334
|
+
results = match.results
|
|
1335
|
+
assert len(results) == 2
|