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.
Files changed (333) hide show
  1. package/.github/workflows/python-publish.yml +97 -0
  2. package/dist/compute/runner.d.ts +3 -2
  3. package/dist/compute/runner.d.ts.map +1 -1
  4. package/dist/compute/runner.js +7 -7
  5. package/dist/compute/runner.js.map +1 -1
  6. package/dist/flowquery.min.js +1 -1
  7. package/dist/graph/data.d.ts +31 -0
  8. package/dist/graph/data.d.ts.map +1 -0
  9. package/dist/graph/data.js +110 -0
  10. package/dist/graph/data.js.map +1 -0
  11. package/dist/graph/database.d.ts +20 -0
  12. package/dist/graph/database.d.ts.map +1 -0
  13. package/dist/graph/database.js +77 -0
  14. package/dist/graph/database.js.map +1 -0
  15. package/dist/graph/hops.d.ts +11 -0
  16. package/dist/graph/hops.d.ts.map +1 -0
  17. package/dist/graph/hops.js +25 -0
  18. package/dist/graph/hops.js.map +1 -0
  19. package/dist/graph/node.d.ts +35 -0
  20. package/dist/graph/node.d.ts.map +1 -0
  21. package/dist/graph/node.js +113 -0
  22. package/dist/graph/node.js.map +1 -0
  23. package/dist/graph/node_data.d.ts +11 -0
  24. package/dist/graph/node_data.d.ts.map +1 -0
  25. package/dist/graph/node_data.js +20 -0
  26. package/dist/graph/node_data.js.map +1 -0
  27. package/dist/graph/node_reference.d.ts +10 -0
  28. package/dist/graph/node_reference.d.ts.map +1 -0
  29. package/dist/graph/node_reference.js +52 -0
  30. package/dist/graph/node_reference.js.map +1 -0
  31. package/dist/graph/pattern.d.ts +18 -0
  32. package/dist/graph/pattern.d.ts.map +1 -0
  33. package/dist/graph/pattern.js +114 -0
  34. package/dist/graph/pattern.js.map +1 -0
  35. package/dist/graph/pattern_expression.d.ts +14 -0
  36. package/dist/graph/pattern_expression.d.ts.map +1 -0
  37. package/dist/graph/pattern_expression.js +58 -0
  38. package/dist/graph/pattern_expression.js.map +1 -0
  39. package/dist/graph/patterns.d.ts +11 -0
  40. package/dist/graph/patterns.d.ts.map +1 -0
  41. package/dist/graph/patterns.js +49 -0
  42. package/dist/graph/patterns.js.map +1 -0
  43. package/dist/graph/physical_node.d.ts +10 -0
  44. package/dist/graph/physical_node.d.ts.map +1 -0
  45. package/dist/graph/physical_node.js +40 -0
  46. package/dist/graph/physical_node.js.map +1 -0
  47. package/dist/graph/physical_relationship.d.ts +10 -0
  48. package/dist/graph/physical_relationship.d.ts.map +1 -0
  49. package/dist/graph/physical_relationship.js +40 -0
  50. package/dist/graph/physical_relationship.js.map +1 -0
  51. package/dist/graph/relationship.d.ts +40 -0
  52. package/dist/graph/relationship.d.ts.map +1 -0
  53. package/dist/graph/relationship.js +124 -0
  54. package/dist/graph/relationship.js.map +1 -0
  55. package/dist/graph/relationship_data.d.ts +12 -0
  56. package/dist/graph/relationship_data.d.ts.map +1 -0
  57. package/dist/graph/relationship_data.js +40 -0
  58. package/dist/graph/relationship_data.js.map +1 -0
  59. package/dist/graph/relationship_match_collector.d.ts +19 -0
  60. package/dist/graph/relationship_match_collector.d.ts.map +1 -0
  61. package/dist/graph/relationship_match_collector.js +55 -0
  62. package/dist/graph/relationship_match_collector.js.map +1 -0
  63. package/dist/graph/relationship_reference.d.ts +8 -0
  64. package/dist/graph/relationship_reference.d.ts.map +1 -0
  65. package/dist/graph/relationship_reference.js +37 -0
  66. package/dist/graph/relationship_reference.js.map +1 -0
  67. package/dist/parsing/base_parser.d.ts +1 -0
  68. package/dist/parsing/base_parser.d.ts.map +1 -1
  69. package/dist/parsing/base_parser.js +4 -1
  70. package/dist/parsing/base_parser.js.map +1 -1
  71. package/dist/parsing/context.d.ts +2 -2
  72. package/dist/parsing/context.js +5 -5
  73. package/dist/parsing/expressions/boolean.d.ts +8 -0
  74. package/dist/parsing/expressions/boolean.d.ts.map +1 -0
  75. package/dist/parsing/expressions/boolean.js +26 -0
  76. package/dist/parsing/expressions/boolean.js.map +1 -0
  77. package/dist/parsing/expressions/expression.d.ts +4 -1
  78. package/dist/parsing/expressions/expression.d.ts.map +1 -1
  79. package/dist/parsing/expressions/expression.js +15 -8
  80. package/dist/parsing/expressions/expression.js.map +1 -1
  81. package/dist/parsing/expressions/expression_map.d.ts +1 -0
  82. package/dist/parsing/expressions/expression_map.d.ts.map +1 -1
  83. package/dist/parsing/expressions/expression_map.js +3 -0
  84. package/dist/parsing/expressions/expression_map.js.map +1 -1
  85. package/dist/parsing/expressions/operator.d.ts +1 -1
  86. package/dist/parsing/expressions/operator.d.ts.map +1 -1
  87. package/dist/parsing/expressions/operator.js.map +1 -1
  88. package/dist/parsing/functions/function_factory.d.ts +13 -13
  89. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  90. package/dist/parsing/functions/function_factory.js +20 -18
  91. package/dist/parsing/functions/function_factory.js.map +1 -1
  92. package/dist/parsing/operations/call.d.ts.map +1 -1
  93. package/dist/parsing/operations/call.js +3 -1
  94. package/dist/parsing/operations/call.js.map +1 -1
  95. package/dist/parsing/operations/create_node.d.ts +14 -0
  96. package/dist/parsing/operations/create_node.d.ts.map +1 -0
  97. package/dist/parsing/operations/create_node.js +51 -0
  98. package/dist/parsing/operations/create_node.js.map +1 -0
  99. package/dist/parsing/operations/create_relationship.d.ts +14 -0
  100. package/dist/parsing/operations/create_relationship.d.ts.map +1 -0
  101. package/dist/parsing/operations/create_relationship.js +51 -0
  102. package/dist/parsing/operations/create_relationship.js.map +1 -0
  103. package/dist/parsing/operations/match.d.ts +15 -0
  104. package/dist/parsing/operations/match.d.ts.map +1 -0
  105. package/dist/parsing/operations/match.js +45 -0
  106. package/dist/parsing/operations/match.js.map +1 -0
  107. package/dist/parsing/operations/operation.d.ts +1 -0
  108. package/dist/parsing/operations/operation.d.ts.map +1 -1
  109. package/dist/parsing/operations/operation.js +6 -0
  110. package/dist/parsing/operations/operation.js.map +1 -1
  111. package/dist/parsing/operations/return.d.ts +1 -0
  112. package/dist/parsing/operations/return.d.ts.map +1 -1
  113. package/dist/parsing/operations/return.js +7 -1
  114. package/dist/parsing/operations/return.js.map +1 -1
  115. package/dist/parsing/operations/where.d.ts +1 -1
  116. package/dist/parsing/operations/where.d.ts.map +1 -1
  117. package/dist/parsing/operations/where.js +4 -0
  118. package/dist/parsing/operations/where.js.map +1 -1
  119. package/dist/parsing/parser.d.ts +10 -0
  120. package/dist/parsing/parser.d.ts.map +1 -1
  121. package/dist/parsing/parser.js +344 -5
  122. package/dist/parsing/parser.js.map +1 -1
  123. package/dist/parsing/token_to_node.d.ts.map +1 -1
  124. package/dist/parsing/token_to_node.js +7 -0
  125. package/dist/parsing/token_to_node.js.map +1 -1
  126. package/dist/tokenization/keyword.d.ts +1 -0
  127. package/dist/tokenization/keyword.d.ts.map +1 -1
  128. package/dist/tokenization/keyword.js +1 -0
  129. package/dist/tokenization/keyword.js.map +1 -1
  130. package/dist/tokenization/token.d.ts +4 -0
  131. package/dist/tokenization/token.d.ts.map +1 -1
  132. package/dist/tokenization/token.js +14 -1
  133. package/dist/tokenization/token.js.map +1 -1
  134. package/dist/tokenization/token_type.d.ts +1 -0
  135. package/dist/tokenization/token_type.d.ts.map +1 -1
  136. package/dist/tokenization/token_type.js +1 -0
  137. package/dist/tokenization/token_type.js.map +1 -1
  138. package/dist/tokenization/tokenizer.d.ts +2 -1
  139. package/dist/tokenization/tokenizer.d.ts.map +1 -1
  140. package/dist/tokenization/tokenizer.js +25 -12
  141. package/dist/tokenization/tokenizer.js.map +1 -1
  142. package/docs/flowquery.min.js +1 -1
  143. package/flowquery-py/README.md +166 -0
  144. package/flowquery-py/pyproject.toml +75 -0
  145. package/flowquery-py/setup_env.ps1 +92 -0
  146. package/flowquery-py/setup_env.sh +87 -0
  147. package/flowquery-py/src/__init__.py +34 -0
  148. package/flowquery-py/src/__main__.py +10 -0
  149. package/flowquery-py/src/compute/__init__.py +5 -0
  150. package/flowquery-py/src/compute/runner.py +60 -0
  151. package/flowquery-py/src/extensibility.py +52 -0
  152. package/flowquery-py/src/graph/__init__.py +31 -0
  153. package/flowquery-py/src/graph/data.py +118 -0
  154. package/flowquery-py/src/graph/database.py +82 -0
  155. package/flowquery-py/src/graph/hops.py +43 -0
  156. package/flowquery-py/src/graph/node.py +112 -0
  157. package/flowquery-py/src/graph/node_data.py +26 -0
  158. package/flowquery-py/src/graph/node_reference.py +49 -0
  159. package/flowquery-py/src/graph/pattern.py +125 -0
  160. package/flowquery-py/src/graph/pattern_expression.py +62 -0
  161. package/flowquery-py/src/graph/patterns.py +42 -0
  162. package/flowquery-py/src/graph/physical_node.py +40 -0
  163. package/flowquery-py/src/graph/physical_relationship.py +36 -0
  164. package/flowquery-py/src/graph/relationship.py +135 -0
  165. package/flowquery-py/src/graph/relationship_data.py +33 -0
  166. package/flowquery-py/src/graph/relationship_match_collector.py +77 -0
  167. package/flowquery-py/src/graph/relationship_reference.py +21 -0
  168. package/flowquery-py/src/io/__init__.py +5 -0
  169. package/flowquery-py/src/io/command_line.py +67 -0
  170. package/flowquery-py/src/parsing/__init__.py +17 -0
  171. package/flowquery-py/src/parsing/alias.py +20 -0
  172. package/flowquery-py/src/parsing/alias_option.py +11 -0
  173. package/flowquery-py/src/parsing/ast_node.py +146 -0
  174. package/flowquery-py/src/parsing/base_parser.py +84 -0
  175. package/flowquery-py/src/parsing/components/__init__.py +19 -0
  176. package/flowquery-py/src/parsing/components/csv.py +8 -0
  177. package/flowquery-py/src/parsing/components/from_.py +10 -0
  178. package/flowquery-py/src/parsing/components/headers.py +12 -0
  179. package/flowquery-py/src/parsing/components/json.py +8 -0
  180. package/flowquery-py/src/parsing/components/null.py +10 -0
  181. package/flowquery-py/src/parsing/components/post.py +8 -0
  182. package/flowquery-py/src/parsing/components/text.py +8 -0
  183. package/flowquery-py/src/parsing/context.py +50 -0
  184. package/flowquery-py/src/parsing/data_structures/__init__.py +15 -0
  185. package/flowquery-py/src/parsing/data_structures/associative_array.py +41 -0
  186. package/flowquery-py/src/parsing/data_structures/json_array.py +30 -0
  187. package/flowquery-py/src/parsing/data_structures/key_value_pair.py +38 -0
  188. package/flowquery-py/src/parsing/data_structures/lookup.py +49 -0
  189. package/flowquery-py/src/parsing/data_structures/range_lookup.py +42 -0
  190. package/flowquery-py/src/parsing/expressions/__init__.py +57 -0
  191. package/flowquery-py/src/parsing/expressions/boolean.py +20 -0
  192. package/flowquery-py/src/parsing/expressions/expression.py +138 -0
  193. package/flowquery-py/src/parsing/expressions/expression_map.py +26 -0
  194. package/flowquery-py/src/parsing/expressions/f_string.py +27 -0
  195. package/flowquery-py/src/parsing/expressions/identifier.py +20 -0
  196. package/flowquery-py/src/parsing/expressions/number.py +32 -0
  197. package/flowquery-py/src/parsing/expressions/operator.py +169 -0
  198. package/flowquery-py/src/parsing/expressions/reference.py +47 -0
  199. package/flowquery-py/src/parsing/expressions/string.py +27 -0
  200. package/flowquery-py/src/parsing/functions/__init__.py +75 -0
  201. package/flowquery-py/src/parsing/functions/aggregate_function.py +60 -0
  202. package/flowquery-py/src/parsing/functions/async_function.py +62 -0
  203. package/flowquery-py/src/parsing/functions/avg.py +55 -0
  204. package/flowquery-py/src/parsing/functions/collect.py +75 -0
  205. package/flowquery-py/src/parsing/functions/function.py +68 -0
  206. package/flowquery-py/src/parsing/functions/function_factory.py +173 -0
  207. package/flowquery-py/src/parsing/functions/function_metadata.py +149 -0
  208. package/flowquery-py/src/parsing/functions/functions.py +59 -0
  209. package/flowquery-py/src/parsing/functions/join.py +47 -0
  210. package/flowquery-py/src/parsing/functions/keys.py +34 -0
  211. package/flowquery-py/src/parsing/functions/predicate_function.py +46 -0
  212. package/flowquery-py/src/parsing/functions/predicate_sum.py +47 -0
  213. package/flowquery-py/src/parsing/functions/rand.py +28 -0
  214. package/flowquery-py/src/parsing/functions/range_.py +34 -0
  215. package/flowquery-py/src/parsing/functions/reducer_element.py +15 -0
  216. package/flowquery-py/src/parsing/functions/replace.py +37 -0
  217. package/flowquery-py/src/parsing/functions/round_.py +32 -0
  218. package/flowquery-py/src/parsing/functions/size.py +32 -0
  219. package/flowquery-py/src/parsing/functions/split.py +47 -0
  220. package/flowquery-py/src/parsing/functions/stringify.py +47 -0
  221. package/flowquery-py/src/parsing/functions/sum.py +51 -0
  222. package/flowquery-py/src/parsing/functions/to_json.py +33 -0
  223. package/flowquery-py/src/parsing/functions/type_.py +47 -0
  224. package/flowquery-py/src/parsing/functions/value_holder.py +24 -0
  225. package/flowquery-py/src/parsing/logic/__init__.py +15 -0
  226. package/flowquery-py/src/parsing/logic/case.py +29 -0
  227. package/flowquery-py/src/parsing/logic/else_.py +12 -0
  228. package/flowquery-py/src/parsing/logic/end.py +8 -0
  229. package/flowquery-py/src/parsing/logic/then.py +12 -0
  230. package/flowquery-py/src/parsing/logic/when.py +10 -0
  231. package/flowquery-py/src/parsing/operations/__init__.py +35 -0
  232. package/flowquery-py/src/parsing/operations/aggregated_return.py +24 -0
  233. package/flowquery-py/src/parsing/operations/aggregated_with.py +22 -0
  234. package/flowquery-py/src/parsing/operations/call.py +74 -0
  235. package/flowquery-py/src/parsing/operations/create_node.py +34 -0
  236. package/flowquery-py/src/parsing/operations/create_relationship.py +34 -0
  237. package/flowquery-py/src/parsing/operations/group_by.py +130 -0
  238. package/flowquery-py/src/parsing/operations/limit.py +22 -0
  239. package/flowquery-py/src/parsing/operations/load.py +140 -0
  240. package/flowquery-py/src/parsing/operations/match.py +29 -0
  241. package/flowquery-py/src/parsing/operations/operation.py +69 -0
  242. package/flowquery-py/src/parsing/operations/projection.py +21 -0
  243. package/flowquery-py/src/parsing/operations/return_op.py +50 -0
  244. package/flowquery-py/src/parsing/operations/unwind.py +37 -0
  245. package/flowquery-py/src/parsing/operations/where.py +41 -0
  246. package/flowquery-py/src/parsing/operations/with_op.py +18 -0
  247. package/flowquery-py/src/parsing/parser.py +1011 -0
  248. package/flowquery-py/src/parsing/token_to_node.py +109 -0
  249. package/flowquery-py/src/tokenization/__init__.py +23 -0
  250. package/flowquery-py/src/tokenization/keyword.py +48 -0
  251. package/flowquery-py/src/tokenization/operator.py +29 -0
  252. package/flowquery-py/src/tokenization/string_walker.py +158 -0
  253. package/flowquery-py/src/tokenization/symbol.py +19 -0
  254. package/flowquery-py/src/tokenization/token.py +659 -0
  255. package/flowquery-py/src/tokenization/token_mapper.py +52 -0
  256. package/flowquery-py/src/tokenization/token_type.py +21 -0
  257. package/flowquery-py/src/tokenization/tokenizer.py +214 -0
  258. package/flowquery-py/src/tokenization/trie.py +124 -0
  259. package/flowquery-py/src/utils/__init__.py +6 -0
  260. package/flowquery-py/src/utils/object_utils.py +20 -0
  261. package/flowquery-py/src/utils/string_utils.py +113 -0
  262. package/flowquery-py/tests/__init__.py +1 -0
  263. package/flowquery-py/tests/compute/__init__.py +1 -0
  264. package/flowquery-py/tests/compute/test_runner.py +1335 -0
  265. package/flowquery-py/tests/graph/__init__.py +1 -0
  266. package/flowquery-py/tests/graph/test_create.py +56 -0
  267. package/flowquery-py/tests/graph/test_data.py +73 -0
  268. package/flowquery-py/tests/graph/test_match.py +40 -0
  269. package/flowquery-py/tests/parsing/__init__.py +1 -0
  270. package/flowquery-py/tests/parsing/test_context.py +34 -0
  271. package/flowquery-py/tests/parsing/test_expression.py +49 -0
  272. package/flowquery-py/tests/parsing/test_parser.py +674 -0
  273. package/flowquery-py/tests/test_extensibility.py +611 -0
  274. package/flowquery-py/tests/tokenization/__init__.py +1 -0
  275. package/flowquery-py/tests/tokenization/test_token_mapper.py +60 -0
  276. package/flowquery-py/tests/tokenization/test_tokenizer.py +164 -0
  277. package/flowquery-py/tests/tokenization/test_trie.py +30 -0
  278. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  279. package/misc/apps/RAG/package.json +1 -1
  280. package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +76 -8
  281. package/misc/apps/RAG/src/components/index.ts +19 -10
  282. package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +21 -26
  283. package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +24 -25
  284. package/misc/apps/RAG/src/plugins/loaders/Form.ts +163 -147
  285. package/misc/apps/RAG/src/plugins/loaders/Llm.ts +103 -90
  286. package/misc/apps/RAG/src/plugins/loaders/MockData.ts +80 -130
  287. package/misc/apps/RAG/src/plugins/loaders/Table.ts +104 -101
  288. package/misc/apps/RAG/src/plugins/loaders/Weather.ts +47 -36
  289. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +89 -78
  290. package/package.json +1 -1
  291. package/src/compute/runner.ts +24 -19
  292. package/src/graph/data.ts +112 -0
  293. package/src/graph/database.ts +63 -0
  294. package/src/graph/hops.ts +22 -0
  295. package/src/graph/node.ts +99 -0
  296. package/src/graph/node_data.ts +18 -0
  297. package/src/graph/node_reference.ts +33 -0
  298. package/src/graph/pattern.ts +101 -0
  299. package/src/graph/pattern_expression.ts +37 -0
  300. package/src/graph/patterns.ts +36 -0
  301. package/src/graph/physical_node.ts +23 -0
  302. package/src/graph/physical_relationship.ts +23 -0
  303. package/src/graph/relationship.ts +116 -0
  304. package/src/graph/relationship_data.ts +27 -0
  305. package/src/graph/relationship_match_collector.ts +58 -0
  306. package/src/graph/relationship_reference.ts +24 -0
  307. package/src/parsing/base_parser.ts +20 -14
  308. package/src/parsing/context.ts +14 -14
  309. package/src/parsing/expressions/boolean.ts +21 -0
  310. package/src/parsing/expressions/expression.ts +34 -26
  311. package/src/parsing/expressions/expression_map.ts +3 -0
  312. package/src/parsing/expressions/operator.ts +19 -1
  313. package/src/parsing/functions/function_factory.ts +45 -45
  314. package/src/parsing/operations/call.ts +3 -1
  315. package/src/parsing/operations/create_node.ts +39 -0
  316. package/src/parsing/operations/create_relationship.ts +38 -0
  317. package/src/parsing/operations/match.ts +31 -0
  318. package/src/parsing/operations/operation.ts +3 -0
  319. package/src/parsing/operations/return.ts +11 -7
  320. package/src/parsing/operations/where.ts +10 -6
  321. package/src/parsing/parser.ts +346 -8
  322. package/src/parsing/token_to_node.ts +6 -0
  323. package/src/tokenization/keyword.ts +41 -40
  324. package/src/tokenization/token.ts +21 -1
  325. package/src/tokenization/token_type.ts +2 -1
  326. package/src/tokenization/tokenizer.ts +52 -31
  327. package/tests/compute/runner.test.ts +660 -6
  328. package/tests/extensibility.test.ts +97 -93
  329. package/tests/graph/create.test.ts +36 -0
  330. package/tests/graph/data.test.ts +58 -0
  331. package/tests/graph/match.test.ts +29 -0
  332. package/tests/parsing/parser.test.ts +276 -8
  333. 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