flowquery 1.0.45 → 1.0.47

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 (418) hide show
  1. package/dist/flowquery.min.js +1 -1
  2. package/dist/index.d.ts +0 -7
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +6 -4
  5. package/dist/index.js.map +1 -1
  6. package/dist/parsing/operations/unwind.d.ts +1 -1
  7. package/dist/parsing/operations/unwind.d.ts.map +1 -1
  8. package/dist/parsing/operations/unwind.js +9 -5
  9. package/dist/parsing/operations/unwind.js.map +1 -1
  10. package/package.json +4 -1
  11. package/.editorconfig +0 -21
  12. package/.gitattributes +0 -3
  13. package/.github/workflows/npm-publish.yml +0 -32
  14. package/.github/workflows/python-publish.yml +0 -143
  15. package/.github/workflows/release.yml +0 -107
  16. package/.husky/pre-commit +0 -28
  17. package/.prettierrc +0 -22
  18. package/CODE_OF_CONDUCT.md +0 -10
  19. package/FlowQueryLogoIcon.png +0 -0
  20. package/SECURITY.md +0 -14
  21. package/SUPPORT.md +0 -13
  22. package/docs/flowquery.min.js +0 -1
  23. package/docs/index.html +0 -105
  24. package/flowquery-py/CONTRIBUTING.md +0 -127
  25. package/flowquery-py/README.md +0 -67
  26. package/flowquery-py/misc/data/test.json +0 -10
  27. package/flowquery-py/misc/data/users.json +0 -242
  28. package/flowquery-py/notebooks/TestFlowQuery.ipynb +0 -440
  29. package/flowquery-py/pyproject.toml +0 -121
  30. package/flowquery-py/setup_env.ps1 +0 -92
  31. package/flowquery-py/setup_env.sh +0 -87
  32. package/flowquery-py/src/__init__.py +0 -38
  33. package/flowquery-py/src/__main__.py +0 -10
  34. package/flowquery-py/src/compute/__init__.py +0 -6
  35. package/flowquery-py/src/compute/flowquery.py +0 -68
  36. package/flowquery-py/src/compute/runner.py +0 -64
  37. package/flowquery-py/src/extensibility.py +0 -52
  38. package/flowquery-py/src/graph/__init__.py +0 -31
  39. package/flowquery-py/src/graph/data.py +0 -136
  40. package/flowquery-py/src/graph/database.py +0 -141
  41. package/flowquery-py/src/graph/hops.py +0 -43
  42. package/flowquery-py/src/graph/node.py +0 -143
  43. package/flowquery-py/src/graph/node_data.py +0 -26
  44. package/flowquery-py/src/graph/node_reference.py +0 -50
  45. package/flowquery-py/src/graph/pattern.py +0 -115
  46. package/flowquery-py/src/graph/pattern_expression.py +0 -67
  47. package/flowquery-py/src/graph/patterns.py +0 -42
  48. package/flowquery-py/src/graph/physical_node.py +0 -41
  49. package/flowquery-py/src/graph/physical_relationship.py +0 -36
  50. package/flowquery-py/src/graph/relationship.py +0 -193
  51. package/flowquery-py/src/graph/relationship_data.py +0 -36
  52. package/flowquery-py/src/graph/relationship_match_collector.py +0 -85
  53. package/flowquery-py/src/graph/relationship_reference.py +0 -21
  54. package/flowquery-py/src/io/__init__.py +0 -5
  55. package/flowquery-py/src/io/command_line.py +0 -108
  56. package/flowquery-py/src/parsing/__init__.py +0 -17
  57. package/flowquery-py/src/parsing/alias.py +0 -20
  58. package/flowquery-py/src/parsing/alias_option.py +0 -11
  59. package/flowquery-py/src/parsing/ast_node.py +0 -147
  60. package/flowquery-py/src/parsing/base_parser.py +0 -84
  61. package/flowquery-py/src/parsing/components/__init__.py +0 -19
  62. package/flowquery-py/src/parsing/components/csv.py +0 -8
  63. package/flowquery-py/src/parsing/components/from_.py +0 -12
  64. package/flowquery-py/src/parsing/components/headers.py +0 -12
  65. package/flowquery-py/src/parsing/components/json.py +0 -8
  66. package/flowquery-py/src/parsing/components/null.py +0 -10
  67. package/flowquery-py/src/parsing/components/post.py +0 -8
  68. package/flowquery-py/src/parsing/components/text.py +0 -8
  69. package/flowquery-py/src/parsing/context.py +0 -50
  70. package/flowquery-py/src/parsing/data_structures/__init__.py +0 -15
  71. package/flowquery-py/src/parsing/data_structures/associative_array.py +0 -41
  72. package/flowquery-py/src/parsing/data_structures/json_array.py +0 -30
  73. package/flowquery-py/src/parsing/data_structures/key_value_pair.py +0 -38
  74. package/flowquery-py/src/parsing/data_structures/lookup.py +0 -51
  75. package/flowquery-py/src/parsing/data_structures/range_lookup.py +0 -42
  76. package/flowquery-py/src/parsing/expressions/__init__.py +0 -61
  77. package/flowquery-py/src/parsing/expressions/boolean.py +0 -20
  78. package/flowquery-py/src/parsing/expressions/expression.py +0 -141
  79. package/flowquery-py/src/parsing/expressions/expression_map.py +0 -26
  80. package/flowquery-py/src/parsing/expressions/f_string.py +0 -27
  81. package/flowquery-py/src/parsing/expressions/identifier.py +0 -21
  82. package/flowquery-py/src/parsing/expressions/number.py +0 -32
  83. package/flowquery-py/src/parsing/expressions/operator.py +0 -271
  84. package/flowquery-py/src/parsing/expressions/reference.py +0 -47
  85. package/flowquery-py/src/parsing/expressions/string.py +0 -27
  86. package/flowquery-py/src/parsing/functions/__init__.py +0 -127
  87. package/flowquery-py/src/parsing/functions/aggregate_function.py +0 -60
  88. package/flowquery-py/src/parsing/functions/async_function.py +0 -65
  89. package/flowquery-py/src/parsing/functions/avg.py +0 -55
  90. package/flowquery-py/src/parsing/functions/coalesce.py +0 -43
  91. package/flowquery-py/src/parsing/functions/collect.py +0 -75
  92. package/flowquery-py/src/parsing/functions/count.py +0 -79
  93. package/flowquery-py/src/parsing/functions/date_.py +0 -61
  94. package/flowquery-py/src/parsing/functions/datetime_.py +0 -62
  95. package/flowquery-py/src/parsing/functions/duration.py +0 -159
  96. package/flowquery-py/src/parsing/functions/element_id.py +0 -50
  97. package/flowquery-py/src/parsing/functions/function.py +0 -68
  98. package/flowquery-py/src/parsing/functions/function_factory.py +0 -170
  99. package/flowquery-py/src/parsing/functions/function_metadata.py +0 -148
  100. package/flowquery-py/src/parsing/functions/functions.py +0 -67
  101. package/flowquery-py/src/parsing/functions/head.py +0 -39
  102. package/flowquery-py/src/parsing/functions/id_.py +0 -49
  103. package/flowquery-py/src/parsing/functions/join.py +0 -49
  104. package/flowquery-py/src/parsing/functions/keys.py +0 -34
  105. package/flowquery-py/src/parsing/functions/last.py +0 -39
  106. package/flowquery-py/src/parsing/functions/localdatetime.py +0 -60
  107. package/flowquery-py/src/parsing/functions/localtime.py +0 -57
  108. package/flowquery-py/src/parsing/functions/max_.py +0 -49
  109. package/flowquery-py/src/parsing/functions/min_.py +0 -49
  110. package/flowquery-py/src/parsing/functions/nodes.py +0 -48
  111. package/flowquery-py/src/parsing/functions/predicate_function.py +0 -47
  112. package/flowquery-py/src/parsing/functions/predicate_sum.py +0 -49
  113. package/flowquery-py/src/parsing/functions/properties.py +0 -50
  114. package/flowquery-py/src/parsing/functions/rand.py +0 -28
  115. package/flowquery-py/src/parsing/functions/range_.py +0 -41
  116. package/flowquery-py/src/parsing/functions/reducer_element.py +0 -15
  117. package/flowquery-py/src/parsing/functions/relationships.py +0 -46
  118. package/flowquery-py/src/parsing/functions/replace.py +0 -39
  119. package/flowquery-py/src/parsing/functions/round_.py +0 -34
  120. package/flowquery-py/src/parsing/functions/schema.py +0 -40
  121. package/flowquery-py/src/parsing/functions/size.py +0 -34
  122. package/flowquery-py/src/parsing/functions/split.py +0 -54
  123. package/flowquery-py/src/parsing/functions/string_distance.py +0 -92
  124. package/flowquery-py/src/parsing/functions/stringify.py +0 -49
  125. package/flowquery-py/src/parsing/functions/substring.py +0 -76
  126. package/flowquery-py/src/parsing/functions/sum.py +0 -51
  127. package/flowquery-py/src/parsing/functions/tail.py +0 -37
  128. package/flowquery-py/src/parsing/functions/temporal_utils.py +0 -186
  129. package/flowquery-py/src/parsing/functions/time_.py +0 -57
  130. package/flowquery-py/src/parsing/functions/timestamp.py +0 -37
  131. package/flowquery-py/src/parsing/functions/to_float.py +0 -46
  132. package/flowquery-py/src/parsing/functions/to_integer.py +0 -46
  133. package/flowquery-py/src/parsing/functions/to_json.py +0 -35
  134. package/flowquery-py/src/parsing/functions/to_lower.py +0 -37
  135. package/flowquery-py/src/parsing/functions/to_string.py +0 -41
  136. package/flowquery-py/src/parsing/functions/trim.py +0 -37
  137. package/flowquery-py/src/parsing/functions/type_.py +0 -47
  138. package/flowquery-py/src/parsing/functions/value_holder.py +0 -24
  139. package/flowquery-py/src/parsing/logic/__init__.py +0 -15
  140. package/flowquery-py/src/parsing/logic/case.py +0 -28
  141. package/flowquery-py/src/parsing/logic/else_.py +0 -12
  142. package/flowquery-py/src/parsing/logic/end.py +0 -8
  143. package/flowquery-py/src/parsing/logic/then.py +0 -12
  144. package/flowquery-py/src/parsing/logic/when.py +0 -12
  145. package/flowquery-py/src/parsing/operations/__init__.py +0 -46
  146. package/flowquery-py/src/parsing/operations/aggregated_return.py +0 -25
  147. package/flowquery-py/src/parsing/operations/aggregated_with.py +0 -22
  148. package/flowquery-py/src/parsing/operations/call.py +0 -73
  149. package/flowquery-py/src/parsing/operations/create_node.py +0 -35
  150. package/flowquery-py/src/parsing/operations/create_relationship.py +0 -35
  151. package/flowquery-py/src/parsing/operations/delete_node.py +0 -29
  152. package/flowquery-py/src/parsing/operations/delete_relationship.py +0 -29
  153. package/flowquery-py/src/parsing/operations/group_by.py +0 -148
  154. package/flowquery-py/src/parsing/operations/limit.py +0 -33
  155. package/flowquery-py/src/parsing/operations/load.py +0 -148
  156. package/flowquery-py/src/parsing/operations/match.py +0 -52
  157. package/flowquery-py/src/parsing/operations/operation.py +0 -69
  158. package/flowquery-py/src/parsing/operations/order_by.py +0 -114
  159. package/flowquery-py/src/parsing/operations/projection.py +0 -21
  160. package/flowquery-py/src/parsing/operations/return_op.py +0 -88
  161. package/flowquery-py/src/parsing/operations/union.py +0 -115
  162. package/flowquery-py/src/parsing/operations/union_all.py +0 -17
  163. package/flowquery-py/src/parsing/operations/unwind.py +0 -38
  164. package/flowquery-py/src/parsing/operations/where.py +0 -43
  165. package/flowquery-py/src/parsing/operations/with_op.py +0 -18
  166. package/flowquery-py/src/parsing/parser.py +0 -1384
  167. package/flowquery-py/src/parsing/parser_state.py +0 -26
  168. package/flowquery-py/src/parsing/token_to_node.py +0 -109
  169. package/flowquery-py/src/tokenization/__init__.py +0 -23
  170. package/flowquery-py/src/tokenization/keyword.py +0 -54
  171. package/flowquery-py/src/tokenization/operator.py +0 -29
  172. package/flowquery-py/src/tokenization/string_walker.py +0 -158
  173. package/flowquery-py/src/tokenization/symbol.py +0 -19
  174. package/flowquery-py/src/tokenization/token.py +0 -693
  175. package/flowquery-py/src/tokenization/token_mapper.py +0 -53
  176. package/flowquery-py/src/tokenization/token_type.py +0 -21
  177. package/flowquery-py/src/tokenization/tokenizer.py +0 -214
  178. package/flowquery-py/src/tokenization/trie.py +0 -125
  179. package/flowquery-py/src/utils/__init__.py +0 -6
  180. package/flowquery-py/src/utils/object_utils.py +0 -20
  181. package/flowquery-py/src/utils/string_utils.py +0 -113
  182. package/flowquery-py/tests/__init__.py +0 -1
  183. package/flowquery-py/tests/compute/__init__.py +0 -1
  184. package/flowquery-py/tests/compute/test_runner.py +0 -4879
  185. package/flowquery-py/tests/graph/__init__.py +0 -1
  186. package/flowquery-py/tests/graph/test_create.py +0 -56
  187. package/flowquery-py/tests/graph/test_data.py +0 -73
  188. package/flowquery-py/tests/graph/test_match.py +0 -40
  189. package/flowquery-py/tests/parsing/__init__.py +0 -1
  190. package/flowquery-py/tests/parsing/test_context.py +0 -34
  191. package/flowquery-py/tests/parsing/test_expression.py +0 -248
  192. package/flowquery-py/tests/parsing/test_parser.py +0 -1237
  193. package/flowquery-py/tests/test_extensibility.py +0 -611
  194. package/flowquery-py/tests/tokenization/__init__.py +0 -1
  195. package/flowquery-py/tests/tokenization/test_token_mapper.py +0 -60
  196. package/flowquery-py/tests/tokenization/test_tokenizer.py +0 -198
  197. package/flowquery-py/tests/tokenization/test_trie.py +0 -30
  198. package/flowquery-vscode/.vscode-test.mjs +0 -5
  199. package/flowquery-vscode/.vscodeignore +0 -13
  200. package/flowquery-vscode/LICENSE +0 -21
  201. package/flowquery-vscode/README.md +0 -11
  202. package/flowquery-vscode/demo/FlowQueryVSCodeDemo.gif +0 -0
  203. package/flowquery-vscode/eslint.config.mjs +0 -25
  204. package/flowquery-vscode/extension.js +0 -508
  205. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +0 -1
  206. package/flowquery-vscode/flowquery-worker.js +0 -66
  207. package/flowquery-vscode/images/FlowQueryLogoIcon.png +0 -0
  208. package/flowquery-vscode/jsconfig.json +0 -13
  209. package/flowquery-vscode/libs/page.css +0 -53
  210. package/flowquery-vscode/libs/table.css +0 -13
  211. package/flowquery-vscode/libs/tabs.css +0 -66
  212. package/flowquery-vscode/package-lock.json +0 -2917
  213. package/flowquery-vscode/package.json +0 -51
  214. package/flowquery-vscode/test/extension.test.js +0 -196
  215. package/flowquery-vscode/test/worker.test.js +0 -25
  216. package/flowquery-vscode/vsc-extension-quickstart.md +0 -42
  217. package/jest.config.js +0 -8
  218. package/misc/apps/RAG/README.md +0 -29
  219. package/misc/apps/RAG/data/chats.json +0 -302
  220. package/misc/apps/RAG/data/emails.json +0 -182
  221. package/misc/apps/RAG/data/events.json +0 -226
  222. package/misc/apps/RAG/data/files.json +0 -172
  223. package/misc/apps/RAG/data/users.json +0 -158
  224. package/misc/apps/RAG/jest.config.js +0 -21
  225. package/misc/apps/RAG/package.json +0 -48
  226. package/misc/apps/RAG/public/index.html +0 -18
  227. package/misc/apps/RAG/src/App.css +0 -42
  228. package/misc/apps/RAG/src/App.tsx +0 -50
  229. package/misc/apps/RAG/src/components/AdaptiveCardRenderer.css +0 -172
  230. package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +0 -380
  231. package/misc/apps/RAG/src/components/ApiKeySettings.tsx +0 -245
  232. package/misc/apps/RAG/src/components/ChatContainer.css +0 -67
  233. package/misc/apps/RAG/src/components/ChatContainer.tsx +0 -242
  234. package/misc/apps/RAG/src/components/ChatInput.css +0 -23
  235. package/misc/apps/RAG/src/components/ChatInput.tsx +0 -76
  236. package/misc/apps/RAG/src/components/ChatMessage.css +0 -160
  237. package/misc/apps/RAG/src/components/ChatMessage.tsx +0 -286
  238. package/misc/apps/RAG/src/components/FlowQueryAgent.ts +0 -708
  239. package/misc/apps/RAG/src/components/FlowQueryRunner.css +0 -113
  240. package/misc/apps/RAG/src/components/FlowQueryRunner.tsx +0 -371
  241. package/misc/apps/RAG/src/components/index.ts +0 -28
  242. package/misc/apps/RAG/src/graph/index.ts +0 -19
  243. package/misc/apps/RAG/src/graph/initializeGraph.ts +0 -254
  244. package/misc/apps/RAG/src/index.tsx +0 -29
  245. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +0 -327
  246. package/misc/apps/RAG/src/prompts/index.ts +0 -10
  247. package/misc/apps/RAG/src/tests/graph.test.ts +0 -35
  248. package/misc/apps/RAG/src/utils/FlowQueryExecutor.ts +0 -130
  249. package/misc/apps/RAG/src/utils/FlowQueryExtractor.ts +0 -208
  250. package/misc/apps/RAG/src/utils/Llm.ts +0 -248
  251. package/misc/apps/RAG/src/utils/index.ts +0 -12
  252. package/misc/apps/RAG/tsconfig.json +0 -22
  253. package/misc/apps/RAG/webpack.config.js +0 -43
  254. package/misc/apps/README.md +0 -1
  255. package/misc/queries/analyze_catfacts.cql +0 -75
  256. package/misc/queries/azure_openai_completions.cql +0 -13
  257. package/misc/queries/azure_openai_models.cql +0 -9
  258. package/misc/queries/mock_pipeline.cql +0 -84
  259. package/misc/queries/openai_completions.cql +0 -15
  260. package/misc/queries/openai_models.cql +0 -13
  261. package/misc/queries/test.cql +0 -6
  262. package/misc/queries/tool_inference.cql +0 -24
  263. package/misc/queries/wisdom.cql +0 -6
  264. package/misc/queries/wisdom_letter_histogram.cql +0 -8
  265. package/src/compute/flowquery.ts +0 -46
  266. package/src/compute/runner.ts +0 -66
  267. package/src/extensibility.ts +0 -45
  268. package/src/graph/data.ts +0 -130
  269. package/src/graph/database.ts +0 -143
  270. package/src/graph/hops.ts +0 -22
  271. package/src/graph/node.ts +0 -122
  272. package/src/graph/node_data.ts +0 -18
  273. package/src/graph/node_reference.ts +0 -38
  274. package/src/graph/pattern.ts +0 -110
  275. package/src/graph/pattern_expression.ts +0 -48
  276. package/src/graph/patterns.ts +0 -36
  277. package/src/graph/physical_node.ts +0 -23
  278. package/src/graph/physical_relationship.ts +0 -23
  279. package/src/graph/relationship.ts +0 -167
  280. package/src/graph/relationship_data.ts +0 -31
  281. package/src/graph/relationship_match_collector.ts +0 -64
  282. package/src/graph/relationship_reference.ts +0 -25
  283. package/src/index.browser.ts +0 -46
  284. package/src/index.node.ts +0 -55
  285. package/src/index.ts +0 -12
  286. package/src/io/command_line.ts +0 -74
  287. package/src/parsing/alias.ts +0 -23
  288. package/src/parsing/alias_option.ts +0 -5
  289. package/src/parsing/ast_node.ts +0 -153
  290. package/src/parsing/base_parser.ts +0 -98
  291. package/src/parsing/components/csv.ts +0 -9
  292. package/src/parsing/components/from.ts +0 -12
  293. package/src/parsing/components/headers.ts +0 -12
  294. package/src/parsing/components/json.ts +0 -9
  295. package/src/parsing/components/null.ts +0 -9
  296. package/src/parsing/components/post.ts +0 -9
  297. package/src/parsing/components/text.ts +0 -9
  298. package/src/parsing/context.ts +0 -54
  299. package/src/parsing/data_structures/associative_array.ts +0 -43
  300. package/src/parsing/data_structures/json_array.ts +0 -31
  301. package/src/parsing/data_structures/key_value_pair.ts +0 -37
  302. package/src/parsing/data_structures/lookup.ts +0 -44
  303. package/src/parsing/data_structures/range_lookup.ts +0 -36
  304. package/src/parsing/expressions/boolean.ts +0 -21
  305. package/src/parsing/expressions/expression.ts +0 -150
  306. package/src/parsing/expressions/expression_map.ts +0 -22
  307. package/src/parsing/expressions/f_string.ts +0 -26
  308. package/src/parsing/expressions/identifier.ts +0 -22
  309. package/src/parsing/expressions/number.ts +0 -40
  310. package/src/parsing/expressions/operator.ts +0 -354
  311. package/src/parsing/expressions/reference.ts +0 -45
  312. package/src/parsing/expressions/string.ts +0 -34
  313. package/src/parsing/functions/aggregate_function.ts +0 -58
  314. package/src/parsing/functions/async_function.ts +0 -64
  315. package/src/parsing/functions/avg.ts +0 -47
  316. package/src/parsing/functions/coalesce.ts +0 -49
  317. package/src/parsing/functions/collect.ts +0 -54
  318. package/src/parsing/functions/count.ts +0 -54
  319. package/src/parsing/functions/date.ts +0 -63
  320. package/src/parsing/functions/datetime.ts +0 -63
  321. package/src/parsing/functions/duration.ts +0 -143
  322. package/src/parsing/functions/element_id.ts +0 -51
  323. package/src/parsing/functions/function.ts +0 -60
  324. package/src/parsing/functions/function_factory.ts +0 -195
  325. package/src/parsing/functions/function_metadata.ts +0 -217
  326. package/src/parsing/functions/functions.ts +0 -70
  327. package/src/parsing/functions/head.ts +0 -42
  328. package/src/parsing/functions/id.ts +0 -51
  329. package/src/parsing/functions/join.ts +0 -40
  330. package/src/parsing/functions/keys.ts +0 -29
  331. package/src/parsing/functions/last.ts +0 -42
  332. package/src/parsing/functions/localdatetime.ts +0 -63
  333. package/src/parsing/functions/localtime.ts +0 -58
  334. package/src/parsing/functions/max.ts +0 -37
  335. package/src/parsing/functions/min.ts +0 -37
  336. package/src/parsing/functions/nodes.ts +0 -54
  337. package/src/parsing/functions/predicate_function.ts +0 -48
  338. package/src/parsing/functions/predicate_sum.ts +0 -47
  339. package/src/parsing/functions/properties.ts +0 -56
  340. package/src/parsing/functions/rand.ts +0 -21
  341. package/src/parsing/functions/range.ts +0 -37
  342. package/src/parsing/functions/reducer_element.ts +0 -10
  343. package/src/parsing/functions/relationships.ts +0 -52
  344. package/src/parsing/functions/replace.ts +0 -38
  345. package/src/parsing/functions/round.ts +0 -28
  346. package/src/parsing/functions/schema.ts +0 -39
  347. package/src/parsing/functions/size.ts +0 -28
  348. package/src/parsing/functions/split.ts +0 -45
  349. package/src/parsing/functions/string_distance.ts +0 -83
  350. package/src/parsing/functions/stringify.ts +0 -37
  351. package/src/parsing/functions/substring.ts +0 -68
  352. package/src/parsing/functions/sum.ts +0 -41
  353. package/src/parsing/functions/tail.ts +0 -39
  354. package/src/parsing/functions/temporal_utils.ts +0 -180
  355. package/src/parsing/functions/time.ts +0 -58
  356. package/src/parsing/functions/timestamp.ts +0 -37
  357. package/src/parsing/functions/to_float.ts +0 -50
  358. package/src/parsing/functions/to_integer.ts +0 -50
  359. package/src/parsing/functions/to_json.ts +0 -28
  360. package/src/parsing/functions/to_lower.ts +0 -28
  361. package/src/parsing/functions/to_string.ts +0 -32
  362. package/src/parsing/functions/trim.ts +0 -28
  363. package/src/parsing/functions/type.ts +0 -39
  364. package/src/parsing/functions/value_holder.ts +0 -13
  365. package/src/parsing/logic/case.ts +0 -26
  366. package/src/parsing/logic/else.ts +0 -12
  367. package/src/parsing/logic/end.ts +0 -9
  368. package/src/parsing/logic/then.ts +0 -12
  369. package/src/parsing/logic/when.ts +0 -12
  370. package/src/parsing/operations/aggregated_return.ts +0 -22
  371. package/src/parsing/operations/aggregated_with.ts +0 -18
  372. package/src/parsing/operations/call.ts +0 -69
  373. package/src/parsing/operations/create_node.ts +0 -39
  374. package/src/parsing/operations/create_relationship.ts +0 -38
  375. package/src/parsing/operations/delete_node.ts +0 -33
  376. package/src/parsing/operations/delete_relationship.ts +0 -32
  377. package/src/parsing/operations/group_by.ts +0 -137
  378. package/src/parsing/operations/limit.ts +0 -31
  379. package/src/parsing/operations/load.ts +0 -146
  380. package/src/parsing/operations/match.ts +0 -54
  381. package/src/parsing/operations/operation.ts +0 -69
  382. package/src/parsing/operations/order_by.ts +0 -126
  383. package/src/parsing/operations/projection.ts +0 -18
  384. package/src/parsing/operations/return.ts +0 -76
  385. package/src/parsing/operations/union.ts +0 -114
  386. package/src/parsing/operations/union_all.ts +0 -16
  387. package/src/parsing/operations/unwind.ts +0 -32
  388. package/src/parsing/operations/where.ts +0 -42
  389. package/src/parsing/operations/with.ts +0 -20
  390. package/src/parsing/parser.ts +0 -1641
  391. package/src/parsing/parser_state.ts +0 -25
  392. package/src/parsing/token_to_node.ts +0 -114
  393. package/src/tokenization/keyword.ts +0 -50
  394. package/src/tokenization/operator.ts +0 -25
  395. package/src/tokenization/string_walker.ts +0 -197
  396. package/src/tokenization/symbol.ts +0 -15
  397. package/src/tokenization/token.ts +0 -764
  398. package/src/tokenization/token_mapper.ts +0 -53
  399. package/src/tokenization/token_type.ts +0 -16
  400. package/src/tokenization/tokenizer.ts +0 -250
  401. package/src/tokenization/trie.ts +0 -117
  402. package/src/utils/object_utils.ts +0 -17
  403. package/src/utils/string_utils.ts +0 -114
  404. package/tests/compute/runner.test.ts +0 -4538
  405. package/tests/extensibility.test.ts +0 -643
  406. package/tests/graph/create.test.ts +0 -36
  407. package/tests/graph/data.test.ts +0 -58
  408. package/tests/graph/match.test.ts +0 -29
  409. package/tests/parsing/context.test.ts +0 -27
  410. package/tests/parsing/expression.test.ts +0 -303
  411. package/tests/parsing/parser.test.ts +0 -1327
  412. package/tests/tokenization/token_mapper.test.ts +0 -47
  413. package/tests/tokenization/tokenizer.test.ts +0 -191
  414. package/tests/tokenization/trie.test.ts +0 -20
  415. package/tsconfig.json +0 -18
  416. package/typedoc.json +0 -16
  417. package/vscode-settings.json.recommended +0 -16
  418. package/webpack.config.js +0 -26
@@ -1,4538 +0,0 @@
1
- import Runner from "../../src/compute/runner";
2
- import Database from "../../src/graph/database";
3
- import Node from "../../src/graph/node";
4
- import Relationship from "../../src/graph/relationship";
5
- import AsyncFunction from "../../src/parsing/functions/async_function";
6
- import { FunctionDef } from "../../src/parsing/functions/function_metadata";
7
-
8
- // Test classes for CALL operation tests - defined at module level for Prettier compatibility
9
- @FunctionDef({
10
- description: "Asynchronous function for testing CALL operation",
11
- category: "async",
12
- parameters: [],
13
- output: { description: "Yields test values", type: "any" },
14
- })
15
- class CallTestFunction extends AsyncFunction {
16
- constructor() {
17
- super();
18
- this._expectedParameterCount = 0;
19
- }
20
- public async *generate(): AsyncGenerator<any> {
21
- yield { result: 1, dummy: "a" };
22
- yield { result: 2, dummy: "b" };
23
- yield { result: 3, dummy: "c" };
24
- }
25
- }
26
-
27
- @FunctionDef({
28
- description: "Asynchronous function for testing CALL operation with no yielded expressions",
29
- category: "async",
30
- parameters: [],
31
- output: { description: "Yields test values", type: "any" },
32
- })
33
- class CallTestFunctionNoObject extends AsyncFunction {
34
- constructor() {
35
- super();
36
- this._expectedParameterCount = 0;
37
- }
38
- public async *generate(): AsyncGenerator<any> {
39
- yield 1;
40
- yield 2;
41
- yield 3;
42
- }
43
- }
44
-
45
- test("Test return", async () => {
46
- const runner = new Runner("return 1 + 2 as sum");
47
- await runner.run();
48
- const results = runner.results;
49
- expect(results.length).toBe(1);
50
- expect(results[0]).toEqual({ sum: 3 });
51
- });
52
-
53
- test("Test return with multiple expressions", async () => {
54
- const runner = new Runner("return 1 + 2 as sum, 3 + 4 as sum2");
55
- await runner.run();
56
- const results = runner.results;
57
- expect(results.length).toBe(1);
58
- expect(results[0]).toEqual({ sum: 3, sum2: 7 });
59
- });
60
-
61
- test("Test unwind and return", async () => {
62
- const runner = new Runner("unwind [1, 2, 3] as num return num");
63
- await runner.run();
64
- const results = runner.results;
65
- expect(results.length).toBe(3);
66
- expect(results[0]).toEqual({ num: 1 });
67
- expect(results[1]).toEqual({ num: 2 });
68
- expect(results[2]).toEqual({ num: 3 });
69
- });
70
-
71
- test("Test load and return", async () => {
72
- const runner = new Runner(
73
- 'load json from "https://jsonplaceholder.typicode.com/todos" as todo return todo'
74
- );
75
- await runner.run();
76
- const results = runner.results;
77
- expect(results.length).toBeGreaterThan(0);
78
- });
79
-
80
- test("Test load with post and return", async () => {
81
- const runner = new Runner(
82
- 'load json from "https://jsonplaceholder.typicode.com/posts" post {userId: 1} as data return data'
83
- );
84
- await runner.run();
85
- const results = runner.results;
86
- expect(results.length).toBe(1);
87
- });
88
-
89
- test("Test aggregated return", async () => {
90
- const runner = new Runner(
91
- "unwind [1, 1, 2, 2] as i unwind [1, 2, 3, 4] as j return i, sum(j) as sum"
92
- );
93
- await runner.run();
94
- const results = runner.results;
95
- expect(results.length).toBe(2);
96
- expect(results[0]).toEqual({ i: 1, sum: 20 });
97
- expect(results[1]).toEqual({ i: 2, sum: 20 });
98
- });
99
-
100
- test("Test aggregated return with string", async () => {
101
- const runner = new Runner(
102
- 'unwind [1, 1, 2, 2] as i unwind ["a", "b", "c", "d"] as j return i, sum(j) as sum'
103
- );
104
- await runner.run();
105
- const results = runner.results;
106
- expect(results.length).toBe(2);
107
- expect(results[0]).toEqual({ i: 1, sum: "abcdabcd" });
108
- expect(results[1]).toEqual({ i: 2, sum: "abcdabcd" });
109
- });
110
-
111
- test("Test aggregated return with object", async () => {
112
- const runner = new Runner(
113
- "unwind [1, 1, 2, 2] as i unwind [1, 2, 3 4] as j return i, {sum: sum(j)} as sum"
114
- );
115
- await runner.run();
116
- const results = runner.results;
117
- expect(results.length).toBe(2);
118
- expect(results[0]).toEqual({ i: 1, sum: { sum: 20 } });
119
- expect(results[1]).toEqual({ i: 2, sum: { sum: 20 } });
120
- });
121
-
122
- test("Test aggregated return with array", async () => {
123
- const runner = new Runner(
124
- "unwind [1, 1, 2, 2] as i unwind [1, 2, 3 4] as j return i, [sum(j)] as sum"
125
- );
126
- await runner.run();
127
- const results = runner.results;
128
- expect(results.length).toBe(2);
129
- expect(results[0]).toEqual({ i: 1, sum: [20] });
130
- expect(results[1]).toEqual({ i: 2, sum: [20] });
131
- });
132
-
133
- test("Test aggregated return with multiple aggregates", async () => {
134
- const runner = new Runner(
135
- "unwind [1, 1, 2, 2] as i unwind [1, 2, 3, 4] as j return i, sum(j) as sum, avg(j) as avg"
136
- );
137
- await runner.run();
138
- const results = runner.results;
139
- expect(results.length).toBe(2);
140
- expect(results[0]).toEqual({ i: 1, sum: 20, avg: 2.5 });
141
- expect(results[1]).toEqual({ i: 2, sum: 20, avg: 2.5 });
142
- });
143
-
144
- test("Test count", async () => {
145
- const runner = new Runner(
146
- "unwind [1, 1, 2, 2] as i unwind [1, 2, 3, 4] as j return i, count(j) as cnt"
147
- );
148
- await runner.run();
149
- const results = runner.results;
150
- expect(results.length).toBe(2);
151
- expect(results[0]).toEqual({ i: 1, cnt: 8 });
152
- expect(results[1]).toEqual({ i: 2, cnt: 8 });
153
- });
154
-
155
- test("Test count distinct", async () => {
156
- const runner = new Runner(
157
- `
158
- unwind [1, 1, 2, 2] as i
159
- unwind [1, 2, 1, 2] as j
160
- return i, count(distinct j) as cnt
161
- `
162
- );
163
- await runner.run();
164
- const results = runner.results;
165
- expect(results.length).toBe(2);
166
- expect(results[0]).toEqual({ i: 1, cnt: 2 });
167
- expect(results[1]).toEqual({ i: 2, cnt: 2 });
168
- });
169
-
170
- test("Test count with strings", async () => {
171
- const runner = new Runner(
172
- `
173
- unwind ["a", "b", "a", "c"] as s
174
- return count(s) as cnt
175
- `
176
- );
177
- await runner.run();
178
- const results = runner.results;
179
- expect(results.length).toBe(1);
180
- expect(results[0]).toEqual({ cnt: 4 });
181
- });
182
-
183
- test("Test count distinct with strings", async () => {
184
- const runner = new Runner(
185
- `
186
- unwind ["a", "b", "a", "c"] as s
187
- return count(distinct s) as cnt
188
- `
189
- );
190
- await runner.run();
191
- const results = runner.results;
192
- expect(results.length).toBe(1);
193
- expect(results[0]).toEqual({ cnt: 3 });
194
- });
195
-
196
- test("Test avg with null", async () => {
197
- const runner = new Runner("return avg(null) as avg");
198
- await runner.run();
199
- const results = runner.results;
200
- expect(results.length).toBe(1);
201
- expect(results[0]).toEqual({ avg: null });
202
- });
203
-
204
- test("Test sum with null", async () => {
205
- const runner = new Runner("return sum(null) as sum");
206
- await runner.run();
207
- const results = runner.results;
208
- expect(results.length).toBe(1);
209
- expect(results[0]).toEqual({ sum: null });
210
- });
211
-
212
- test("Test avg with one value", async () => {
213
- const runner = new Runner("return avg(1) as avg");
214
- await runner.run();
215
- const results = runner.results;
216
- expect(results.length).toBe(1);
217
- expect(results[0]).toEqual({ avg: 1 });
218
- });
219
-
220
- test("Test min", async () => {
221
- const runner = new Runner("unwind [3, 1, 4, 1, 5, 9] as n return min(n) as minimum");
222
- await runner.run();
223
- const results = runner.results;
224
- expect(results.length).toBe(1);
225
- expect(results[0]).toEqual({ minimum: 1 });
226
- });
227
-
228
- test("Test max", async () => {
229
- const runner = new Runner("unwind [3, 1, 4, 1, 5, 9] as n return max(n) as maximum");
230
- await runner.run();
231
- const results = runner.results;
232
- expect(results.length).toBe(1);
233
- expect(results[0]).toEqual({ maximum: 9 });
234
- });
235
-
236
- test("Test min with grouped values", async () => {
237
- const runner = new Runner(
238
- "unwind [1, 1, 2, 2] as i unwind [10, 20, 30, 40] as j return i, min(j) as minimum"
239
- );
240
- await runner.run();
241
- const results = runner.results;
242
- expect(results.length).toBe(2);
243
- expect(results[0]).toEqual({ i: 1, minimum: 10 });
244
- expect(results[1]).toEqual({ i: 2, minimum: 10 });
245
- });
246
-
247
- test("Test max with grouped values", async () => {
248
- const runner = new Runner(
249
- "unwind [1, 1, 2, 2] as i unwind [10, 20, 30, 40] as j return i, max(j) as maximum"
250
- );
251
- await runner.run();
252
- const results = runner.results;
253
- expect(results.length).toBe(2);
254
- expect(results[0]).toEqual({ i: 1, maximum: 40 });
255
- expect(results[1]).toEqual({ i: 2, maximum: 40 });
256
- });
257
-
258
- test("Test min with null", async () => {
259
- const runner = new Runner("return min(null) as minimum");
260
- await runner.run();
261
- const results = runner.results;
262
- expect(results.length).toBe(1);
263
- expect(results[0]).toEqual({ minimum: null });
264
- });
265
-
266
- test("Test max with null", async () => {
267
- const runner = new Runner("return max(null) as maximum");
268
- await runner.run();
269
- const results = runner.results;
270
- expect(results.length).toBe(1);
271
- expect(results[0]).toEqual({ maximum: null });
272
- });
273
-
274
- test("Test min with strings", async () => {
275
- const runner = new Runner('unwind ["cherry", "apple", "banana"] as s return min(s) as minimum');
276
- await runner.run();
277
- const results = runner.results;
278
- expect(results.length).toBe(1);
279
- expect(results[0]).toEqual({ minimum: "apple" });
280
- });
281
-
282
- test("Test max with strings", async () => {
283
- const runner = new Runner('unwind ["cherry", "apple", "banana"] as s return max(s) as maximum');
284
- await runner.run();
285
- const results = runner.results;
286
- expect(results.length).toBe(1);
287
- expect(results[0]).toEqual({ maximum: "cherry" });
288
- });
289
-
290
- test("Test min and max together", async () => {
291
- const runner = new Runner(
292
- "unwind [3, 1, 4, 1, 5, 9] as n return min(n) as minimum, max(n) as maximum"
293
- );
294
- await runner.run();
295
- const results = runner.results;
296
- expect(results.length).toBe(1);
297
- expect(results[0]).toEqual({ minimum: 1, maximum: 9 });
298
- });
299
-
300
- test("Test with and return", async () => {
301
- const runner = new Runner("with 1 as a return a");
302
- await runner.run();
303
- const results = runner.results;
304
- expect(results.length).toBe(1);
305
- expect(results[0]).toEqual({ a: 1 });
306
- });
307
-
308
- test("Test nested aggregate functions", async () => {
309
- expect(() => {
310
- new Runner("unwind [1, 2, 3, 4] as i return sum(sum(i)) as sum");
311
- }).toThrow("Aggregate functions cannot be nested");
312
- });
313
-
314
- test("Test with and return with unwind", async () => {
315
- const runner = new Runner("with [1, 2, 3] as a unwind a as b return b as renamed");
316
- await runner.run();
317
- const results = runner.results;
318
- expect(results.length).toBe(3);
319
- expect(results[0]).toEqual({ renamed: 1 });
320
- expect(results[1]).toEqual({ renamed: 2 });
321
- expect(results[2]).toEqual({ renamed: 3 });
322
- });
323
-
324
- test("Test predicate function", async () => {
325
- const runner = new Runner("RETURN sum(n in [1, 2, 3] | n where n > 1) as sum");
326
- await runner.run();
327
- const results = runner.results;
328
- expect(results.length).toBe(1);
329
- expect(results[0]).toEqual({ sum: 5 });
330
- });
331
-
332
- test("Test predicate without where", async () => {
333
- const runner = new Runner("RETURN sum(n in [1, 2, 3] | n) as sum");
334
- await runner.run();
335
- const results = runner.results;
336
- expect(results.length).toBe(1);
337
- expect(results[0]).toEqual({ sum: 6 });
338
- });
339
-
340
- test("Test predicate with return expression", async () => {
341
- const runner = new Runner("RETURN sum(n in [1+2+3, 2, 3] | n^2) as sum");
342
- await runner.run();
343
- const results = runner.results;
344
- expect(results.length).toBe(1);
345
- expect(results[0]).toEqual({ sum: 49 });
346
- });
347
-
348
- test("Test range function", async () => {
349
- const runner = new Runner("RETURN range(1, 3) as range");
350
- await runner.run();
351
- const results = runner.results;
352
- expect(results.length).toBe(1);
353
- expect(results[0]).toEqual({ range: [1, 2, 3] });
354
- });
355
-
356
- test("Test range function with unwind and case", async () => {
357
- const runner = new Runner(
358
- "unwind range(1, 3) as num return case when num > 1 then num else null end as ret"
359
- );
360
- await runner.run();
361
- const results = runner.results;
362
- expect(results.length).toBe(3);
363
- expect(results[0]).toEqual({ ret: null });
364
- expect(results[1]).toEqual({ ret: 2 });
365
- expect(results[2]).toEqual({ ret: 3 });
366
- });
367
-
368
- test("Test size function", async () => {
369
- const runner = new Runner("RETURN size([1, 2, 3]) as size");
370
- await runner.run();
371
- const results = runner.results;
372
- expect(results.length).toBe(1);
373
- expect(results[0]).toEqual({ size: 3 });
374
- });
375
-
376
- test("Test rand and round functions", async () => {
377
- const runner = new Runner("RETURN round(rand() * 10) as rand");
378
- await runner.run();
379
- const results = runner.results;
380
- expect(results.length).toBe(1);
381
- expect(results[0].rand).toBeLessThanOrEqual(10);
382
- });
383
-
384
- test("Test split function", async () => {
385
- const runner = new Runner('RETURN split("a,b,c", ",") as split');
386
- await runner.run();
387
- const results = runner.results;
388
- expect(results.length).toBe(1);
389
- expect(results[0]).toEqual({ split: ["a", "b", "c"] });
390
- });
391
-
392
- test("Test f-string", async () => {
393
- const runner = new Runner(
394
- 'with range(1,3) as numbers RETURN f"hello {sum(n in numbers | n)}" as f'
395
- );
396
- await runner.run();
397
- const results = runner.results;
398
- expect(results.length).toBe(1);
399
- expect(results[0]).toEqual({ f: "hello 6" });
400
- });
401
-
402
- test("Test aggregated with and return", async () => {
403
- const runner = new Runner(
404
- `
405
- unwind [1, 1, 2, 2] as i
406
- unwind range(1, 3) as j
407
- with i, sum(j) as sum
408
- return i, sum
409
- `
410
- );
411
- await runner.run();
412
- const results = runner.results;
413
- expect(results.length).toBe(2);
414
- expect(results[0]).toEqual({ i: 1, sum: 12 });
415
- expect(results[1]).toEqual({ i: 2, sum: 12 });
416
- });
417
-
418
- test("Test aggregated with on empty result set", async () => {
419
- const runner = new Runner(
420
- `
421
- unwind [] as i
422
- unwind [1, 2] as j
423
- with i, count(j) as cnt
424
- return i, cnt
425
- `
426
- );
427
- await runner.run();
428
- const results = runner.results;
429
- expect(results.length).toBe(0);
430
- });
431
-
432
- test("Test aggregated with using collect and return", async () => {
433
- const runner = new Runner(
434
- `
435
- unwind [1, 1, 2, 2] as i
436
- unwind range(1, 3) as j
437
- with i, collect(j) as collected
438
- return i, collected
439
- `
440
- );
441
- await runner.run();
442
- const results = runner.results;
443
- expect(results.length).toBe(2);
444
- expect(results[0]).toEqual({ i: 1, collected: [1, 2, 3, 1, 2, 3] });
445
- expect(results[1]).toEqual({ i: 2, collected: [1, 2, 3, 1, 2, 3] });
446
- });
447
-
448
- test("Test collect distinct", async () => {
449
- const runner = new Runner(
450
- `
451
- unwind [1, 1, 2, 2] as i
452
- unwind range(1, 3) as j
453
- with i, collect(distinct j) as collected
454
- return i, collected
455
- `
456
- );
457
- await runner.run();
458
- const results = runner.results;
459
- expect(results.length).toBe(2);
460
- expect(results[0]).toEqual({ i: 1, collected: [1, 2, 3] });
461
- expect(results[1]).toEqual({ i: 2, collected: [1, 2, 3] });
462
- });
463
-
464
- test("Test collect distinct with associative array", async () => {
465
- const runner = new Runner(
466
- `
467
- unwind [1, 1, 2, 2] as i
468
- unwind range(1, 3) as j
469
- with i, collect(distinct {j: j}) as collected
470
- return i, collected
471
- `
472
- );
473
- await runner.run();
474
- const results = runner.results;
475
- expect(results.length).toBe(2);
476
- expect(results[0]).toEqual({ i: 1, collected: [{ j: 1 }, { j: 2 }, { j: 3 }] });
477
- expect(results[1]).toEqual({ i: 2, collected: [{ j: 1 }, { j: 2 }, { j: 3 }] });
478
- });
479
-
480
- test("Test return distinct", async () => {
481
- const runner = new Runner(
482
- `
483
- unwind [1, 1, 2, 2, 3, 3] as i
484
- return distinct i
485
- `
486
- );
487
- await runner.run();
488
- const results = runner.results;
489
- expect(results.length).toBe(3);
490
- expect(results[0]).toEqual({ i: 1 });
491
- expect(results[1]).toEqual({ i: 2 });
492
- expect(results[2]).toEqual({ i: 3 });
493
- });
494
-
495
- test("Test return distinct with multiple expressions", async () => {
496
- const runner = new Runner(
497
- `
498
- unwind [1, 1, 2, 2] as i
499
- unwind [10, 10, 20, 20] as j
500
- return distinct i, j
501
- `
502
- );
503
- await runner.run();
504
- const results = runner.results;
505
- expect(results.length).toBe(4);
506
- expect(results[0]).toEqual({ i: 1, j: 10 });
507
- expect(results[1]).toEqual({ i: 1, j: 20 });
508
- expect(results[2]).toEqual({ i: 2, j: 10 });
509
- expect(results[3]).toEqual({ i: 2, j: 20 });
510
- });
511
-
512
- test("Test with distinct", async () => {
513
- const runner = new Runner(
514
- `
515
- unwind [1, 1, 2, 2, 3, 3] as i
516
- with distinct i as i
517
- return i
518
- `
519
- );
520
- await runner.run();
521
- const results = runner.results;
522
- expect(results.length).toBe(3);
523
- expect(results[0]).toEqual({ i: 1 });
524
- expect(results[1]).toEqual({ i: 2 });
525
- expect(results[2]).toEqual({ i: 3 });
526
- });
527
-
528
- test("Test with distinct and aggregation", async () => {
529
- const runner = new Runner(
530
- `
531
- unwind [1, 1, 2, 2] as i
532
- with distinct i as i
533
- return sum(i) as total
534
- `
535
- );
536
- await runner.run();
537
- const results = runner.results;
538
- expect(results.length).toBe(1);
539
- expect(results[0]).toEqual({ total: 3 });
540
- });
541
-
542
- test("Test return distinct with strings", async () => {
543
- const runner = new Runner(
544
- `
545
- unwind ["a", "b", "a", "c", "b"] as x
546
- return distinct x
547
- `
548
- );
549
- await runner.run();
550
- const results = runner.results;
551
- expect(results.length).toBe(3);
552
- expect(results[0]).toEqual({ x: "a" });
553
- expect(results[1]).toEqual({ x: "b" });
554
- expect(results[2]).toEqual({ x: "c" });
555
- });
556
-
557
- test("Test join function", async () => {
558
- const runner = new Runner('RETURN join(["a", "b", "c"], ",") as join');
559
- await runner.run();
560
- const results = runner.results;
561
- expect(results.length).toBe(1);
562
- expect(results[0]).toEqual({ join: "a,b,c" });
563
- });
564
-
565
- test("Test join function with empty array", async () => {
566
- const runner = new Runner('RETURN join([], ",") as join');
567
- await runner.run();
568
- const results = runner.results;
569
- expect(results.length).toBe(1);
570
- expect(results[0]).toEqual({ join: "" });
571
- });
572
-
573
- test("Test tojson function", async () => {
574
- const runner = new Runner('RETURN tojson(\'{"a": 1, "b": 2}\') as tojson');
575
- await runner.run();
576
- const results = runner.results;
577
- expect(results.length).toBe(1);
578
- expect(results[0]).toEqual({ tojson: { a: 1, b: 2 } });
579
- });
580
-
581
- test("Test tojson function with lookup", async () => {
582
- const runner = new Runner('RETURN tojson(\'{"a": 1, "b": 2}\').a as tojson');
583
- await runner.run();
584
- const results = runner.results;
585
- expect(results.length).toBe(1);
586
- expect(results[0]).toEqual({ tojson: 1 });
587
- });
588
-
589
- test("Test replace function", async () => {
590
- const runner = new Runner('RETURN replace("hello", "l", "x") as replace');
591
- await runner.run();
592
- const results = runner.results;
593
- expect(results.length).toBe(1);
594
- expect(results[0]).toEqual({ replace: "hexxo" });
595
- });
596
-
597
- test("Test string_distance function", async () => {
598
- const runner = new Runner('RETURN string_distance("kitten", "sitting") as dist');
599
- await runner.run();
600
- const results = runner.results;
601
- expect(results.length).toBe(1);
602
- expect(results[0].dist).toBeCloseTo(3 / 7, 10);
603
- });
604
-
605
- test("Test string_distance function with identical strings", async () => {
606
- const runner = new Runner('RETURN string_distance("hello", "hello") as dist');
607
- await runner.run();
608
- const results = runner.results;
609
- expect(results.length).toBe(1);
610
- expect(results[0]).toEqual({ dist: 0 });
611
- });
612
-
613
- test("Test string_distance function with empty string", async () => {
614
- const runner = new Runner('RETURN string_distance("", "abc") as dist');
615
- await runner.run();
616
- const results = runner.results;
617
- expect(results.length).toBe(1);
618
- expect(results[0]).toEqual({ dist: 1 });
619
- });
620
-
621
- test("Test string_distance function with both empty strings", async () => {
622
- const runner = new Runner('RETURN string_distance("", "") as dist');
623
- await runner.run();
624
- const results = runner.results;
625
- expect(results.length).toBe(1);
626
- expect(results[0]).toEqual({ dist: 0 });
627
- });
628
-
629
- test("Test f-string with escaped braces", async () => {
630
- const runner = new Runner(
631
- 'with range(1,3) as numbers RETURN f"hello {{sum(n in numbers | n)}}" as f'
632
- );
633
- await runner.run();
634
- const results = runner.results;
635
- expect(results.length).toBe(1);
636
- expect(results[0]).toEqual({ f: "hello {sum(n in numbers | n)}" });
637
- });
638
-
639
- test("Test predicate function with collection from lookup", async () => {
640
- const runner = new Runner("RETURN sum(n in tojson('{\"a\": [1, 2, 3]}').a | n) as sum");
641
- await runner.run();
642
- const results = runner.results;
643
- expect(results.length).toBe(1);
644
- expect(results[0]).toEqual({ sum: 6 });
645
- });
646
-
647
- test("Test stringify function", async () => {
648
- const runner = new Runner("RETURN stringify({a: 1, b: 2}) as stringify");
649
- await runner.run();
650
- const results = runner.results;
651
- expect(results.length).toBe(1);
652
- expect(results[0]).toEqual({
653
- stringify: '{\n "a": 1,\n "b": 2\n}',
654
- });
655
- });
656
-
657
- test("Test toString function with number", async () => {
658
- const runner = new Runner("RETURN toString(42) as result");
659
- await runner.run();
660
- const results = runner.results;
661
- expect(results.length).toBe(1);
662
- expect(results[0]).toEqual({ result: "42" });
663
- });
664
-
665
- test("Test toString function with boolean", async () => {
666
- const runner = new Runner("RETURN toString(true) as result");
667
- await runner.run();
668
- const results = runner.results;
669
- expect(results.length).toBe(1);
670
- expect(results[0]).toEqual({ result: "true" });
671
- });
672
-
673
- test("Test toString function with object", async () => {
674
- const runner = new Runner("RETURN toString({a: 1}) as result");
675
- await runner.run();
676
- const results = runner.results;
677
- expect(results.length).toBe(1);
678
- expect(results[0]).toEqual({ result: '{"a":1}' });
679
- });
680
-
681
- test("Test toLower function", async () => {
682
- const runner = new Runner('RETURN toLower("Hello World") as result');
683
- await runner.run();
684
- const results = runner.results;
685
- expect(results.length).toBe(1);
686
- expect(results[0]).toEqual({ result: "hello world" });
687
- });
688
-
689
- test("Test toLower function with all uppercase", async () => {
690
- const runner = new Runner('RETURN toLower("FOO BAR") as result');
691
- await runner.run();
692
- const results = runner.results;
693
- expect(results.length).toBe(1);
694
- expect(results[0]).toEqual({ result: "foo bar" });
695
- });
696
-
697
- test("Test trim function", async () => {
698
- const runner = new Runner('RETURN trim(" hello ") as result');
699
- await runner.run();
700
- const results = runner.results;
701
- expect(results.length).toBe(1);
702
- expect(results[0]).toEqual({ result: "hello" });
703
- });
704
-
705
- test("Test trim function with tabs and newlines", async () => {
706
- const runner = new Runner('WITH "\tfoo\n" AS s RETURN trim(s) as result');
707
- await runner.run();
708
- const results = runner.results;
709
- expect(results.length).toBe(1);
710
- expect(results[0]).toEqual({ result: "foo" });
711
- });
712
-
713
- test("Test trim function with no whitespace", async () => {
714
- const runner = new Runner('RETURN trim("hello") as result');
715
- await runner.run();
716
- const results = runner.results;
717
- expect(results.length).toBe(1);
718
- expect(results[0]).toEqual({ result: "hello" });
719
- });
720
-
721
- test("Test trim function with empty string", async () => {
722
- const runner = new Runner('RETURN trim("") as result');
723
- await runner.run();
724
- const results = runner.results;
725
- expect(results.length).toBe(1);
726
- expect(results[0]).toEqual({ result: "" });
727
- });
728
-
729
- test("Test substring function with start and length", async () => {
730
- const runner = new Runner('RETURN substring("hello", 1, 3) as result');
731
- await runner.run();
732
- const results = runner.results;
733
- expect(results.length).toBe(1);
734
- expect(results[0]).toEqual({ result: "ell" });
735
- });
736
-
737
- test("Test substring function with start only", async () => {
738
- const runner = new Runner('RETURN substring("hello", 2) as result');
739
- await runner.run();
740
- const results = runner.results;
741
- expect(results.length).toBe(1);
742
- expect(results[0]).toEqual({ result: "llo" });
743
- });
744
-
745
- test("Test substring function with zero start", async () => {
746
- const runner = new Runner('RETURN substring("hello", 0, 5) as result');
747
- await runner.run();
748
- const results = runner.results;
749
- expect(results.length).toBe(1);
750
- expect(results[0]).toEqual({ result: "hello" });
751
- });
752
-
753
- test("Test substring function with zero length", async () => {
754
- const runner = new Runner('RETURN substring("hello", 1, 0) as result');
755
- await runner.run();
756
- const results = runner.results;
757
- expect(results.length).toBe(1);
758
- expect(results[0]).toEqual({ result: "" });
759
- });
760
-
761
- // --- Null propagation tests ---
762
-
763
- test("Test toLower with null returns null", async () => {
764
- const runner = new Runner("RETURN toLower(null) as result");
765
- await runner.run();
766
- const results = runner.results;
767
- expect(results.length).toBe(1);
768
- expect(results[0]).toEqual({ result: null });
769
- });
770
-
771
- test("Test trim with null returns null", async () => {
772
- const runner = new Runner("RETURN trim(null) as result");
773
- await runner.run();
774
- const results = runner.results;
775
- expect(results.length).toBe(1);
776
- expect(results[0]).toEqual({ result: null });
777
- });
778
-
779
- test("Test replace with null returns null", async () => {
780
- const runner = new Runner("RETURN replace(null, 'a', 'b') as result");
781
- await runner.run();
782
- const results = runner.results;
783
- expect(results.length).toBe(1);
784
- expect(results[0]).toEqual({ result: null });
785
- });
786
-
787
- test("Test substring with null returns null", async () => {
788
- const runner = new Runner("RETURN substring(null, 0, 3) as result");
789
- await runner.run();
790
- const results = runner.results;
791
- expect(results.length).toBe(1);
792
- expect(results[0]).toEqual({ result: null });
793
- });
794
-
795
- test("Test split with null returns null", async () => {
796
- const runner = new Runner("RETURN split(null, ',') as result");
797
- await runner.run();
798
- const results = runner.results;
799
- expect(results.length).toBe(1);
800
- expect(results[0]).toEqual({ result: null });
801
- });
802
-
803
- test("Test size with null returns null", async () => {
804
- const runner = new Runner("RETURN size(null) as result");
805
- await runner.run();
806
- const results = runner.results;
807
- expect(results.length).toBe(1);
808
- expect(results[0]).toEqual({ result: null });
809
- });
810
-
811
- test("Test round with null returns null", async () => {
812
- const runner = new Runner("RETURN round(null) as result");
813
- await runner.run();
814
- const results = runner.results;
815
- expect(results.length).toBe(1);
816
- expect(results[0]).toEqual({ result: null });
817
- });
818
-
819
- test("Test join with null returns null", async () => {
820
- const runner = new Runner("RETURN join(null, ',') as result");
821
- await runner.run();
822
- const results = runner.results;
823
- expect(results.length).toBe(1);
824
- expect(results[0]).toEqual({ result: null });
825
- });
826
-
827
- test("Test string_distance with null returns null", async () => {
828
- const runner = new Runner("RETURN string_distance(null, 'hello') as result");
829
- await runner.run();
830
- const results = runner.results;
831
- expect(results.length).toBe(1);
832
- expect(results[0]).toEqual({ result: null });
833
- });
834
-
835
- test("Test stringify with null returns null", async () => {
836
- const runner = new Runner("RETURN stringify(null) as result");
837
- await runner.run();
838
- const results = runner.results;
839
- expect(results.length).toBe(1);
840
- expect(results[0]).toEqual({ result: null });
841
- });
842
-
843
- test("Test toJson with null returns null", async () => {
844
- const runner = new Runner("RETURN tojson(null) as result");
845
- await runner.run();
846
- const results = runner.results;
847
- expect(results.length).toBe(1);
848
- expect(results[0]).toEqual({ result: null });
849
- });
850
-
851
- test("Test range with null returns null", async () => {
852
- const runner = new Runner("RETURN range(null, 5) as result");
853
- await runner.run();
854
- const results = runner.results;
855
- expect(results.length).toBe(1);
856
- expect(results[0]).toEqual({ result: null });
857
- });
858
-
859
- test("Test toString with null returns null", async () => {
860
- const runner = new Runner("RETURN toString(null) as result");
861
- await runner.run();
862
- const results = runner.results;
863
- expect(results.length).toBe(1);
864
- expect(results[0]).toEqual({ result: null });
865
- });
866
-
867
- test("Test keys with null returns null", async () => {
868
- const runner = new Runner("RETURN keys(null) as result");
869
- await runner.run();
870
- const results = runner.results;
871
- expect(results.length).toBe(1);
872
- expect(results[0]).toEqual({ result: null });
873
- });
874
-
875
- test("Test associative array with key which is keyword", async () => {
876
- const runner = new Runner("RETURN {return: 1} as aa");
877
- await runner.run();
878
- const results = runner.results;
879
- expect(results.length).toBe(1);
880
- expect(results[0]).toEqual({ aa: { return: 1 } });
881
- });
882
-
883
- test("Test lookup which is keyword", async () => {
884
- const runner = new Runner("RETURN {return: 1}.return as aa");
885
- await runner.run();
886
- const results = runner.results;
887
- expect(results.length).toBe(1);
888
- expect(results[0]).toEqual({ aa: 1 });
889
- });
890
-
891
- test("Test lookup which is keyword with bracket notation", async () => {
892
- const runner = new Runner('RETURN {return: 1}["return"] as aa');
893
- await runner.run();
894
- const results = runner.results;
895
- expect(results.length).toBe(1);
896
- expect(results[0]).toEqual({ aa: 1 });
897
- });
898
-
899
- test("Test return with expression alias which starts with keyword", async () => {
900
- const runner = new Runner('RETURN 1 as return1, ["hello", "world"] as notes');
901
- await runner.run();
902
- const results = runner.results;
903
- expect(results.length).toBe(1);
904
- expect(results[0]).toEqual({ return1: 1, notes: ["hello", "world"] });
905
- });
906
-
907
- test("Test load which should throw error", async () => {
908
- const runner = new Runner('load json from "http://non_existing" as data return data');
909
- runner
910
- .run()
911
- .then()
912
- .catch((e) => {
913
- expect(e.message).toBe(
914
- "Failed to load data from http://non_existing. Error: TypeError: fetch failed"
915
- );
916
- });
917
- });
918
-
919
- test("Test return with where clause", async () => {
920
- const runner = new Runner("unwind range(1,100) as n with n return n where n >= 20 and n <= 30");
921
- await runner.run();
922
- const results = runner.results;
923
- expect(results.length).toBe(11);
924
- expect(results[0]).toEqual({ n: 20 });
925
- expect(results[10]).toEqual({ n: 30 });
926
- });
927
-
928
- test("Test return with where clause and expression alias", async () => {
929
- const runner = new Runner(
930
- "unwind range(1,100) as n with n return n as number where n >= 20 and n <= 30"
931
- );
932
- await runner.run();
933
- const results = runner.results;
934
- expect(results.length).toBe(11);
935
- expect(results[0]).toEqual({ number: 20 });
936
- expect(results[10]).toEqual({ number: 30 });
937
- });
938
-
939
- test("Test aggregated return with where clause", async () => {
940
- const runner = new Runner(
941
- "unwind range(1,100) as n with n where n >= 20 and n <= 30 return sum(n) as sum"
942
- );
943
- await runner.run();
944
- const results = runner.results;
945
- expect(results.length).toBe(1);
946
- expect(results[0]).toEqual({ sum: 275 });
947
- });
948
-
949
- test("Test chained aggregated return with where clause", async () => {
950
- const runner = new Runner(
951
- `
952
- unwind [1, 1, 2, 2] as i
953
- unwind range(1, 4) as j
954
- return i, sum(j) as sum
955
- where i = 1
956
- `
957
- );
958
- await runner.run();
959
- const results = runner.results;
960
- expect(results.length).toBe(1);
961
- expect(results[0]).toEqual({ i: 1, sum: 20 });
962
- });
963
-
964
- test("Test predicate function with collection from function", async () => {
965
- const runner = new Runner(
966
- `
967
- unwind range(1, 10) as i
968
- unwind range(1, 10) as j
969
- return i, sum(j), avg(j), sum(n in collect(j) | n) as sum
970
- `
971
- );
972
- await runner.run();
973
- const results = runner.results;
974
- expect(results.length).toBe(10);
975
- expect(results[0]).toEqual({ i: 1, expr1: 55, expr2: 5.5, sum: 55 });
976
- });
977
-
978
- test("Test limit", async () => {
979
- const runner = new Runner(
980
- `
981
- unwind range(1, 10) as i
982
- unwind range(1, 10) as j
983
- limit 5
984
- return j
985
- `
986
- );
987
- await runner.run();
988
- const results = runner.results;
989
- expect(results.length).toBe(50);
990
- });
991
-
992
- test("Test limit as last operation", async () => {
993
- const runner = new Runner(
994
- `
995
- unwind range(1, 10) as i
996
- return i
997
- limit 5
998
- `
999
- );
1000
- await runner.run();
1001
- const results = runner.results;
1002
- expect(results.length).toBe(5);
1003
- });
1004
-
1005
- test("Test range lookup", async () => {
1006
- const runner = new Runner(
1007
- `
1008
- with range(1, 10) as numbers
1009
- return
1010
- numbers[:] as subset1,
1011
- numbers[0:3] as subset2,
1012
- numbers[:-2] as subset3
1013
- `
1014
- );
1015
- await runner.run();
1016
- const results = runner.results;
1017
- expect(results.length).toBe(1);
1018
- expect(results[0]).toEqual({
1019
- subset1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
1020
- subset2: [1, 2, 3],
1021
- subset3: [1, 2, 3, 4, 5, 6, 7, 8],
1022
- });
1023
- });
1024
-
1025
- test("Test return -1", async () => {
1026
- const runner = new Runner("return -1 as num");
1027
- await runner.run();
1028
- const results = runner.results;
1029
- expect(results.length).toBe(1);
1030
- expect(results[0]).toEqual({ num: -1 });
1031
- });
1032
-
1033
- test("Unwind range lookup", async () => {
1034
- const runner = new Runner(`
1035
- with range(1,10) as arr
1036
- unwind arr[2:-2] as a
1037
- return a
1038
- `);
1039
- await runner.run();
1040
- const results = runner.results;
1041
- expect(results.length).toBe(6);
1042
- expect(results[0]).toEqual({ a: 3 });
1043
- expect(results[5]).toEqual({ a: 8 });
1044
- });
1045
-
1046
- test("Test range with size", async () => {
1047
- const runner = new Runner(`
1048
- with range(1,10) as data
1049
- return range(0, size(data)-1) as indices
1050
- `);
1051
- await runner.run();
1052
- const results = runner.results;
1053
- expect(results.length).toBe(1);
1054
- expect(results[0]).toEqual({ indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] });
1055
- });
1056
-
1057
- test("Test keys function", async () => {
1058
- const runner = new Runner('RETURN keys({name: "Alice", age: 30}) as keys');
1059
- await runner.run();
1060
- const results = runner.results;
1061
- expect(results.length).toBe(1);
1062
- expect(results[0]).toEqual({ keys: ["name", "age"] });
1063
- });
1064
-
1065
- test("Test properties function with map", async () => {
1066
- const runner = new Runner('RETURN properties({name: "Alice", age: 30}) as props');
1067
- await runner.run();
1068
- const results = runner.results;
1069
- expect(results.length).toBe(1);
1070
- expect(results[0]).toEqual({ props: { name: "Alice", age: 30 } });
1071
- });
1072
-
1073
- test("Test properties function with node", async () => {
1074
- await new Runner(`
1075
- CREATE VIRTUAL (:Animal) AS {
1076
- UNWIND [
1077
- {id: 1, name: 'Dog', legs: 4},
1078
- {id: 2, name: 'Cat', legs: 4}
1079
- ] AS record
1080
- RETURN record.id AS id, record.name AS name, record.legs AS legs
1081
- }
1082
- `).run();
1083
- const match = new Runner(`
1084
- MATCH (a:Animal)
1085
- RETURN properties(a) AS props
1086
- `);
1087
- await match.run();
1088
- const results = match.results;
1089
- expect(results.length).toBe(2);
1090
- expect(results[0]).toEqual({ props: { name: "Dog", legs: 4 } });
1091
- expect(results[1]).toEqual({ props: { name: "Cat", legs: 4 } });
1092
- });
1093
-
1094
- test("Test properties function with null", async () => {
1095
- const runner = new Runner("RETURN properties(null) as props");
1096
- await runner.run();
1097
- const results = runner.results;
1098
- expect(results.length).toBe(1);
1099
- expect(results[0]).toEqual({ props: null });
1100
- });
1101
-
1102
- test("Test nodes function", async () => {
1103
- await new Runner(`
1104
- CREATE VIRTUAL (:City) AS {
1105
- UNWIND [
1106
- {id: 1, name: 'New York'},
1107
- {id: 2, name: 'Boston'}
1108
- ] AS record
1109
- RETURN record.id AS id, record.name AS name
1110
- }
1111
- `).run();
1112
- await new Runner(`
1113
- CREATE VIRTUAL (:City)-[:CONNECTED_TO]-(:City) AS {
1114
- UNWIND [
1115
- {left_id: 1, right_id: 2}
1116
- ] AS record
1117
- RETURN record.left_id AS left_id, record.right_id AS right_id
1118
- }
1119
- `).run();
1120
- const match = new Runner(`
1121
- MATCH p=(:City)-[:CONNECTED_TO]-(:City)
1122
- RETURN nodes(p) AS cities
1123
- `);
1124
- await match.run();
1125
- const results = match.results;
1126
- expect(results.length).toBe(1);
1127
- expect(results[0].cities.length).toBe(2);
1128
- expect(results[0].cities[0].id).toBe(1);
1129
- expect(results[0].cities[0].name).toBe("New York");
1130
- expect(results[0].cities[1].id).toBe(2);
1131
- expect(results[0].cities[1].name).toBe("Boston");
1132
- });
1133
-
1134
- test("Test relationships function", async () => {
1135
- await new Runner(`
1136
- CREATE VIRTUAL (:City) AS {
1137
- UNWIND [
1138
- {id: 1, name: 'New York'},
1139
- {id: 2, name: 'Boston'}
1140
- ] AS record
1141
- RETURN record.id AS id, record.name AS name
1142
- }
1143
- `).run();
1144
- await new Runner(`
1145
- CREATE VIRTUAL (:City)-[:CONNECTED_TO]-(:City) AS {
1146
- UNWIND [
1147
- {left_id: 1, right_id: 2, distance: 190}
1148
- ] AS record
1149
- RETURN record.left_id AS left_id, record.right_id AS right_id, record.distance AS distance
1150
- }
1151
- `).run();
1152
- const match = new Runner(`
1153
- MATCH p=(:City)-[:CONNECTED_TO]-(:City)
1154
- RETURN relationships(p) AS rels
1155
- `);
1156
- await match.run();
1157
- const results = match.results;
1158
- expect(results.length).toBe(1);
1159
- expect(results[0].rels.length).toBe(1);
1160
- expect(results[0].rels[0].type).toBe("CONNECTED_TO");
1161
- expect(results[0].rels[0].properties.distance).toBe(190);
1162
- });
1163
-
1164
- test("Test nodes function with null", async () => {
1165
- const runner = new Runner("RETURN nodes(null) as n");
1166
- await runner.run();
1167
- const results = runner.results;
1168
- expect(results.length).toBe(1);
1169
- expect(results[0]).toEqual({ n: [] });
1170
- });
1171
-
1172
- test("Test relationships function with null", async () => {
1173
- const runner = new Runner("RETURN relationships(null) as r");
1174
- await runner.run();
1175
- const results = runner.results;
1176
- expect(results.length).toBe(1);
1177
- expect(results[0]).toEqual({ r: [] });
1178
- });
1179
-
1180
- test("Test type function", async () => {
1181
- const runner = new Runner(`
1182
- RETURN type(123) as type1,
1183
- type("hello") as type2,
1184
- type([1, 2, 3]) as type3,
1185
- type({a: 1, b: 2}) as type4,
1186
- type(null) as type5
1187
- `);
1188
- await runner.run();
1189
- const results = runner.results;
1190
- expect(results.length).toBe(1);
1191
- expect(results[0]).toEqual({
1192
- type1: "number",
1193
- type2: "string",
1194
- type3: "array",
1195
- type4: "object",
1196
- type5: "null",
1197
- });
1198
- });
1199
-
1200
- test("Test call operation with async function", async () => {
1201
- const runner = new Runner("CALL calltestfunction() YIELD result RETURN result");
1202
- await runner.run();
1203
- const results = runner.results;
1204
- expect(results.length).toBe(3);
1205
- expect(results[0]).toEqual({ result: 1 });
1206
- expect(results[1]).toEqual({ result: 2 });
1207
- expect(results[2]).toEqual({ result: 3 });
1208
- });
1209
-
1210
- test("Test call operation with aggregation", async () => {
1211
- const runner = new Runner("CALL calltestfunction() YIELD result RETURN sum(result) as total");
1212
- await runner.run();
1213
- const results = runner.results;
1214
- expect(results.length).toBe(1);
1215
- expect(results[0]).toEqual({ total: 6 });
1216
- });
1217
-
1218
- test("Test call operation as last operation", async () => {
1219
- const runner = new Runner("CALL calltestfunction()");
1220
- await runner.run();
1221
- const results = runner.results;
1222
- expect(results.length).toBe(3);
1223
- expect(results[0]).toEqual({ result: 1, dummy: "a" });
1224
- expect(results[1]).toEqual({ result: 2, dummy: "b" });
1225
- expect(results[2]).toEqual({ result: 3, dummy: "c" });
1226
- });
1227
-
1228
- test("Test call operation as last operation with yield", async () => {
1229
- const runner = new Runner("CALL calltestfunction() YIELD result");
1230
- await runner.run();
1231
- const results = runner.results;
1232
- expect(results.length).toBe(3);
1233
- expect(results[0]).toEqual({ result: 1 });
1234
- expect(results[1]).toEqual({ result: 2 });
1235
- expect(results[2]).toEqual({ result: 3 });
1236
- });
1237
-
1238
- test("Test call operation with no yielded expressions", async () => {
1239
- expect(() => {
1240
- const runner = new Runner("CALL calltestfunctionnoobject() RETURN 1");
1241
- }).toThrow("CALL operations must have a YIELD clause unless they are the last operation");
1242
- });
1243
-
1244
- test("Test create node operation", async () => {
1245
- const db = Database.getInstance();
1246
- const runner = new Runner(`
1247
- CREATE VIRTUAL (:Person) AS {
1248
- with 1 as x
1249
- RETURN x
1250
- }
1251
- `);
1252
- await runner.run();
1253
- const results = runner.results;
1254
- expect(results.length).toBe(0);
1255
- expect(db.getNode(new Node(null, "Person"))).not.toBeNull();
1256
- });
1257
-
1258
- test("Test create node and match operations", async () => {
1259
- const create = new Runner(`
1260
- CREATE VIRTUAL (:Person) AS {
1261
- unwind [
1262
- {id: 1, name: 'Person 1'},
1263
- {id: 2, name: 'Person 2'}
1264
- ] as record
1265
- RETURN record.id as id, record.name as name
1266
- }
1267
- `);
1268
- await create.run();
1269
- const match = new Runner("MATCH (n:Person) RETURN n");
1270
- await match.run();
1271
- const results = match.results;
1272
- expect(results.length).toBe(2);
1273
- expect(results[0].n).toBeDefined();
1274
- expect(results[0].n.id).toBe(1);
1275
- expect(results[0].n.name).toBe("Person 1");
1276
- expect(results[1].n).toBeDefined();
1277
- expect(results[1].n.id).toBe(2);
1278
- expect(results[1].n.name).toBe("Person 2");
1279
- });
1280
-
1281
- test("Test complex match operation", async () => {
1282
- await new Runner(`
1283
- CREATE VIRTUAL (:Person) AS {
1284
- unwind [
1285
- {id: 1, name: 'Person 1', age: 30},
1286
- {id: 2, name: 'Person 2', age: 25},
1287
- {id: 3, name: 'Person 3', age: 35}
1288
- ] as record
1289
- RETURN record.id as id, record.name as name, record.age as age
1290
- }
1291
- `).run();
1292
- const match = new Runner(`
1293
- MATCH (n:Person)
1294
- WHERE n.age > 29
1295
- RETURN n.name AS name, n.age AS age
1296
- `);
1297
- await match.run();
1298
- const results = match.results;
1299
- expect(results.length).toBe(2);
1300
- expect(results[0]).toEqual({ name: "Person 1", age: 30 });
1301
- expect(results[1]).toEqual({ name: "Person 3", age: 35 });
1302
- });
1303
-
1304
- test("Test match", async () => {
1305
- await new Runner(`
1306
- CREATE VIRTUAL (:Person) AS {
1307
- unwind [
1308
- {id: 1, name: 'Person 1'},
1309
- {id: 2, name: 'Person 2'}
1310
- ] as record
1311
- RETURN record.id as id, record.name as name
1312
- }
1313
- `).run();
1314
- const match = new Runner(`
1315
- MATCH (n:Person)
1316
- RETURN n.name AS name
1317
- `);
1318
- await match.run();
1319
- const results = match.results;
1320
- expect(results.length).toBe(2);
1321
- expect(results[0]).toEqual({ name: "Person 1" });
1322
- expect(results[1]).toEqual({ name: "Person 2" });
1323
- });
1324
-
1325
- test("Test match with nested join", async () => {
1326
- await new Runner(`
1327
- CREATE VIRTUAL (:Person) AS {
1328
- unwind [
1329
- {id: 1, name: 'Person 1'},
1330
- {id: 2, name: 'Person 2'}
1331
- ] as record
1332
- RETURN record.id as id, record.name as name
1333
- }
1334
- `).run();
1335
- const match = new Runner(`
1336
- MATCH (a:Person), (b:Person)
1337
- WHERE a.id <> b.id
1338
- RETURN a.name AS name1, b.name AS name2
1339
- `);
1340
- await match.run();
1341
- const results = match.results;
1342
- expect(results.length).toBe(2);
1343
- expect(results[0]).toEqual({ name1: "Person 1", name2: "Person 2" });
1344
- expect(results[1]).toEqual({ name1: "Person 2", name2: "Person 1" });
1345
- });
1346
-
1347
- test("Test match with graph pattern", async () => {
1348
- await new Runner(`
1349
- CREATE VIRTUAL (:User) AS {
1350
- UNWIND [
1351
- {id: 1, name: 'User 1', manager_id: null},
1352
- {id: 2, name: 'User 2', manager_id: 1},
1353
- {id: 3, name: 'User 3', manager_id: 1},
1354
- {id: 4, name: 'User 4', manager_id: 2}
1355
- ] AS record
1356
- RETURN record.id AS id, record.name AS name, record.manager_id AS manager_id
1357
- }
1358
- `).run();
1359
- await new Runner(`
1360
- CREATE VIRTUAL (:User)-[:MANAGED_BY]-(:User) AS {
1361
- UNWIND [
1362
- {id: 1, manager_id: null},
1363
- {id: 2, manager_id: 1},
1364
- {id: 3, manager_id: 1},
1365
- {id: 4, manager_id: 2}
1366
- ] AS record
1367
- RETURN record.id AS left_id, record.manager_id AS right_id
1368
- }
1369
- `).run();
1370
- const match = new Runner(`
1371
- MATCH (user:User)-[r:MANAGED_BY]-(manager:User)
1372
- RETURN user.name AS user, manager.name AS manager
1373
- `);
1374
- await match.run();
1375
- const results = match.results;
1376
- expect(results.length).toBe(3);
1377
- expect(results[0]).toEqual({ user: "User 2", manager: "User 1" });
1378
- expect(results[1]).toEqual({ user: "User 3", manager: "User 1" });
1379
- expect(results[2]).toEqual({ user: "User 4", manager: "User 2" });
1380
- });
1381
-
1382
- test("Test match with multiple hop graph pattern", async () => {
1383
- await new Runner(`
1384
- CREATE VIRTUAL (:Person) AS {
1385
- unwind [
1386
- {id: 1, name: 'Person 1'},
1387
- {id: 2, name: 'Person 2'},
1388
- {id: 3, name: 'Person 3'},
1389
- {id: 4, name: 'Person 4'}
1390
- ] as record
1391
- RETURN record.id as id, record.name as name
1392
- }
1393
- `).run();
1394
- await new Runner(`
1395
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1396
- unwind [
1397
- {left_id: 1, right_id: 2},
1398
- {left_id: 2, right_id: 3}
1399
- ] as record
1400
- RETURN record.left_id as left_id, record.right_id as right_id
1401
- }
1402
- `).run();
1403
- const match = new Runner(`
1404
- MATCH (a:Person)-[:KNOWS*]-(c:Person)
1405
- RETURN a.name AS name1, c.name AS name2
1406
- `);
1407
- await match.run();
1408
- const results = match.results;
1409
- expect(results.length).toBe(7);
1410
- // Results are interleaved: each person's zero-hop comes before their multi-hop matches
1411
- // Person 1: zero-hop, then 1-hop to P2, then 2-hop to P3
1412
- expect(results[0]).toEqual({ name1: "Person 1", name2: "Person 1" });
1413
- expect(results[1]).toEqual({ name1: "Person 1", name2: "Person 2" });
1414
- expect(results[2]).toEqual({ name1: "Person 1", name2: "Person 3" });
1415
- // Person 2: zero-hop, then 1-hop to P3
1416
- expect(results[3]).toEqual({ name1: "Person 2", name2: "Person 2" });
1417
- expect(results[4]).toEqual({ name1: "Person 2", name2: "Person 3" });
1418
- // Person 3 and 4: only zero-hop matches
1419
- expect(results[5]).toEqual({ name1: "Person 3", name2: "Person 3" });
1420
- expect(results[6]).toEqual({ name1: "Person 4", name2: "Person 4" });
1421
- });
1422
-
1423
- test("Test match with double graph pattern", async () => {
1424
- await new Runner(`
1425
- CREATE VIRTUAL (:Person) AS {
1426
- unwind [
1427
- {id: 1, name: 'Person 1'},
1428
- {id: 2, name: 'Person 2'},
1429
- {id: 3, name: 'Person 3'},
1430
- {id: 4, name: 'Person 4'}
1431
- ] as record
1432
- RETURN record.id as id, record.name as name
1433
- }
1434
- `).run();
1435
- await new Runner(`
1436
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1437
- unwind [
1438
- {left_id: 1, right_id: 2},
1439
- {left_id: 2, right_id: 3},
1440
- {left_id: 3, right_id: 4}
1441
- ] as record
1442
- RETURN record.left_id as left_id, record.right_id as right_id
1443
- }
1444
- `).run();
1445
- const match = new Runner(`
1446
- MATCH (a:Person)-[:KNOWS]-(b:Person)-[:KNOWS]-(c:Person)
1447
- RETURN a.name AS name1, b.name AS name2, c.name AS name3
1448
- `);
1449
- await match.run();
1450
- const results = match.results;
1451
- expect(results.length).toBe(2);
1452
- expect(results[0]).toEqual({ name1: "Person 1", name2: "Person 2", name3: "Person 3" });
1453
- expect(results[1]).toEqual({ name1: "Person 2", name2: "Person 3", name3: "Person 4" });
1454
- });
1455
-
1456
- test("Test match with referenced to previous variable", async () => {
1457
- await new Runner(`
1458
- CREATE VIRTUAL (:Person) AS {
1459
- unwind [
1460
- {id: 1, name: 'Person 1'},
1461
- {id: 2, name: 'Person 2'},
1462
- {id: 3, name: 'Person 3'},
1463
- {id: 4, name: 'Person 4'}
1464
- ] as record
1465
- RETURN record.id as id, record.name as name
1466
- }
1467
- `).run();
1468
- await new Runner(`
1469
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1470
- unwind [
1471
- {left_id: 1, right_id: 2},
1472
- {left_id: 2, right_id: 3},
1473
- {left_id: 3, right_id: 4}
1474
- ] as record
1475
- RETURN record.left_id as left_id, record.right_id as right_id
1476
- }
1477
- `).run();
1478
- const match = new Runner(`
1479
- MATCH (a:Person)-[:KNOWS]-(b:Person)
1480
- MATCH (b)-[:KNOWS]-(c:Person)
1481
- RETURN a.name AS name1, b.name AS name2, c.name AS name3
1482
- `);
1483
- await match.run();
1484
- const results = match.results;
1485
- expect(results.length).toBe(2);
1486
- expect(results[0]).toEqual({ name1: "Person 1", name2: "Person 2", name3: "Person 3" });
1487
- expect(results[1]).toEqual({ name1: "Person 2", name2: "Person 3", name3: "Person 4" });
1488
- });
1489
-
1490
- test("Test match with aggregated with and subsequent match", async () => {
1491
- await new Runner(`
1492
- CREATE VIRTUAL (:User) AS {
1493
- unwind [
1494
- {id: 1, name: 'Alice'},
1495
- {id: 2, name: 'Bob'},
1496
- {id: 3, name: 'Carol'}
1497
- ] as record
1498
- RETURN record.id as id, record.name as name
1499
- }
1500
- `).run();
1501
- await new Runner(`
1502
- CREATE VIRTUAL (:User)-[:KNOWS]-(:User) AS {
1503
- unwind [
1504
- {left_id: 1, right_id: 2},
1505
- {left_id: 1, right_id: 3}
1506
- ] as record
1507
- RETURN record.left_id as left_id, record.right_id as right_id
1508
- }
1509
- `).run();
1510
- await new Runner(`
1511
- CREATE VIRTUAL (:Project) AS {
1512
- unwind [
1513
- {id: 1, name: 'Project A'},
1514
- {id: 2, name: 'Project B'}
1515
- ] as record
1516
- RETURN record.id as id, record.name as name
1517
- }
1518
- `).run();
1519
- await new Runner(`
1520
- CREATE VIRTUAL (:User)-[:WORKS_ON]-(:Project) AS {
1521
- unwind [
1522
- {left_id: 1, right_id: 1},
1523
- {left_id: 1, right_id: 2}
1524
- ] as record
1525
- RETURN record.left_id as left_id, record.right_id as right_id
1526
- }
1527
- `).run();
1528
- const match = new Runner(`
1529
- MATCH (u:User)-[:KNOWS]->(s:User)
1530
- WITH u, count(s) as acquaintances
1531
- MATCH (u)-[:WORKS_ON]->(p:Project)
1532
- RETURN u.name as name, acquaintances, collect(p.name) as projects
1533
- `);
1534
- await match.run();
1535
- const results = match.results;
1536
- expect(results.length).toBe(1);
1537
- expect(results[0]).toEqual({
1538
- name: "Alice",
1539
- acquaintances: 2,
1540
- projects: ["Project A", "Project B"],
1541
- });
1542
- });
1543
-
1544
- test("Test match and return full node", async () => {
1545
- await new Runner(`
1546
- CREATE VIRTUAL (:Person) AS {
1547
- unwind [
1548
- {id: 1, name: 'Person 1'},
1549
- {id: 2, name: 'Person 2'}
1550
- ] as record
1551
- RETURN record.id as id, record.name as name
1552
- }
1553
- `).run();
1554
- const match = new Runner(`
1555
- MATCH (n:Person)
1556
- RETURN n
1557
- `);
1558
- await match.run();
1559
- const results = match.results;
1560
- expect(results.length).toBe(2);
1561
- expect(results[0].n).toBeDefined();
1562
- expect(results[0].n.id).toBe(1);
1563
- expect(results[0].n.name).toBe("Person 1");
1564
- expect(results[1].n).toBeDefined();
1565
- expect(results[1].n.id).toBe(2);
1566
- expect(results[1].n.name).toBe("Person 2");
1567
- });
1568
-
1569
- test("Test return graph pattern", async () => {
1570
- await new Runner(`
1571
- CREATE VIRTUAL (:Person) AS {
1572
- unwind [
1573
- {id: 1, name: 'Person 1'},
1574
- {id: 2, name: 'Person 2'}
1575
- ] as record
1576
- RETURN record.id as id, record.name as name
1577
- }
1578
- `).run();
1579
- await new Runner(`
1580
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1581
- unwind [
1582
- {left_id: 1, since: "2020-01-01", right_id: 2}
1583
- ] as record
1584
- RETURN record.left_id as left_id, record.since as since, record.right_id as right_id
1585
- }
1586
- `).run();
1587
- const match = new Runner(`
1588
- MATCH p=(:Person)-[:KNOWS]-(:Person)
1589
- RETURN p AS pattern
1590
- `);
1591
- await match.run();
1592
- const results = match.results;
1593
- expect(results.length).toBe(1);
1594
- expect(results[0].pattern).toBeDefined();
1595
- expect(results[0].pattern.length).toBe(3);
1596
- expect(results[0].pattern[0].id).toBe(1);
1597
- expect(results[0].pattern[1].properties.since).toBe("2020-01-01");
1598
- expect(results[0].pattern[2].id).toBe(2);
1599
- });
1600
-
1601
- test("Test circular graph pattern", async () => {
1602
- await new Runner(`
1603
- CREATE VIRTUAL (:Person) AS {
1604
- unwind [
1605
- {id: 1, name: 'Person 1'},
1606
- {id: 2, name: 'Person 2'}
1607
- ] as record
1608
- RETURN record.id as id, record.name as name
1609
- }
1610
- `).run();
1611
- await new Runner(`
1612
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1613
- unwind [
1614
- {left_id: 1, right_id: 2},
1615
- {left_id: 2, right_id: 1}
1616
- ] as record
1617
- RETURN record.left_id as left_id, record.right_id as right_id
1618
- }
1619
- `).run();
1620
- const match = new Runner(`
1621
- MATCH p=(:Person)-[:KNOWS]-(:Person)-[:KNOWS]-(:Person)
1622
- RETURN p AS pattern
1623
- `);
1624
- await match.run();
1625
- const results = match.results;
1626
- expect(results.length).toBe(2);
1627
- expect(results[0].pattern).toBeDefined();
1628
- expect(results[0].pattern.length).toBe(5);
1629
- expect(results[0].pattern[0].id).toBe(1);
1630
- expect(results[0].pattern[1].id).toBeUndefined();
1631
- expect(results[0].pattern[2].id).toBe(2);
1632
- expect(results[0].pattern[3].id).toBeUndefined();
1633
- expect(results[0].pattern[4].id).toBe(1);
1634
- });
1635
-
1636
- test("Test circular graph pattern with variable length should not revisit nodes", async () => {
1637
- await new Runner(`
1638
- CREATE VIRTUAL (:Person) AS {
1639
- unwind [
1640
- {id: 1, name: 'Person 1'},
1641
- {id: 2, name: 'Person 2'}
1642
- ] as record
1643
- RETURN record.id as id, record.name as name
1644
- }
1645
- `).run();
1646
- await new Runner(`
1647
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1648
- unwind [
1649
- {left_id: 1, right_id: 2},
1650
- {left_id: 2, right_id: 1}
1651
- ] as record
1652
- RETURN record.left_id as left_id, record.right_id as right_id
1653
- }
1654
- `).run();
1655
- const match = new Runner(`
1656
- MATCH p=(:Person)-[:KNOWS*]-(:Person)
1657
- RETURN p AS pattern
1658
- `);
1659
- await match.run();
1660
- const results = match.results;
1661
- // Circular graph 1↔2: cycles are skipped, only acyclic paths are returned
1662
- expect(results.length).toBe(6);
1663
- });
1664
-
1665
- test("Test multi-hop match with min hops constraint *1..", async () => {
1666
- await new Runner(`
1667
- CREATE VIRTUAL (:Person) AS {
1668
- unwind [
1669
- {id: 1, name: 'Person 1'},
1670
- {id: 2, name: 'Person 2'},
1671
- {id: 3, name: 'Person 3'},
1672
- {id: 4, name: 'Person 4'}
1673
- ] as record
1674
- RETURN record.id as id, record.name as name
1675
- }
1676
- `).run();
1677
- await new Runner(`
1678
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1679
- unwind [
1680
- {left_id: 1, right_id: 2},
1681
- {left_id: 2, right_id: 3},
1682
- {left_id: 3, right_id: 4}
1683
- ] as record
1684
- RETURN record.left_id as left_id, record.right_id as right_id
1685
- }
1686
- `).run();
1687
- const match = new Runner(`
1688
- MATCH (a:Person)-[:KNOWS*1..]->(b:Person)
1689
- RETURN a.name AS name1, b.name AS name2
1690
- `);
1691
- await match.run();
1692
- const results = match.results;
1693
- // *1.. means at least 1 hop, so no zero-hop (self) matches
1694
- // Person 1: 1-hop to P2, 2-hop to P3, 3-hop to P4
1695
- // Person 2: 1-hop to P3, 2-hop to P4
1696
- // Person 3: 1-hop to P4
1697
- // Person 4: no outgoing edges
1698
- expect(results.length).toBe(6);
1699
- expect(results[0]).toEqual({ name1: "Person 1", name2: "Person 2" });
1700
- expect(results[1]).toEqual({ name1: "Person 1", name2: "Person 3" });
1701
- expect(results[2]).toEqual({ name1: "Person 1", name2: "Person 4" });
1702
- expect(results[3]).toEqual({ name1: "Person 2", name2: "Person 3" });
1703
- expect(results[4]).toEqual({ name1: "Person 2", name2: "Person 4" });
1704
- expect(results[5]).toEqual({ name1: "Person 3", name2: "Person 4" });
1705
- });
1706
-
1707
- test("Test multi-hop match with min hops constraint *2..", async () => {
1708
- await new Runner(`
1709
- CREATE VIRTUAL (:Person) AS {
1710
- unwind [
1711
- {id: 1, name: 'Person 1'},
1712
- {id: 2, name: 'Person 2'},
1713
- {id: 3, name: 'Person 3'},
1714
- {id: 4, name: 'Person 4'}
1715
- ] as record
1716
- RETURN record.id as id, record.name as name
1717
- }
1718
- `).run();
1719
- await new Runner(`
1720
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1721
- unwind [
1722
- {left_id: 1, right_id: 2},
1723
- {left_id: 2, right_id: 3},
1724
- {left_id: 3, right_id: 4}
1725
- ] as record
1726
- RETURN record.left_id as left_id, record.right_id as right_id
1727
- }
1728
- `).run();
1729
- const match = new Runner(`
1730
- MATCH (a:Person)-[:KNOWS*2..]->(b:Person)
1731
- RETURN a.name AS name1, b.name AS name2
1732
- `);
1733
- await match.run();
1734
- const results = match.results;
1735
- // *2.. means at least 2 hops
1736
- // Person 1: 2-hop to P3, 3-hop to P4
1737
- // Person 2: 2-hop to P4
1738
- expect(results.length).toBe(3);
1739
- expect(results[0]).toEqual({ name1: "Person 1", name2: "Person 3" });
1740
- expect(results[1]).toEqual({ name1: "Person 1", name2: "Person 4" });
1741
- expect(results[2]).toEqual({ name1: "Person 2", name2: "Person 4" });
1742
- });
1743
-
1744
- test("Test multi-hop match with variable length relationships", async () => {
1745
- await new Runner(`
1746
- CREATE VIRTUAL (:Person) AS {
1747
- unwind [
1748
- {id: 1, name: 'Person 1'},
1749
- {id: 2, name: 'Person 2'},
1750
- {id: 3, name: 'Person 3'},
1751
- {id: 4, name: 'Person 4'}
1752
- ] as record
1753
- RETURN record.id as id, record.name as name
1754
- }
1755
- `).run();
1756
- await new Runner(`
1757
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1758
- unwind [
1759
- {left_id: 1, right_id: 2},
1760
- {left_id: 2, right_id: 3},
1761
- {left_id: 3, right_id: 4}
1762
- ] as record
1763
- RETURN record.left_id as left_id, record.right_id as right_id
1764
- }
1765
- `).run();
1766
- const match = new Runner(`
1767
- MATCH (a:Person)-[r:KNOWS*0..3]->(b:Person)
1768
- RETURN a, r, b
1769
- `);
1770
- await match.run();
1771
- const results = match.results;
1772
- expect(results.length).toBe(10);
1773
-
1774
- // Results are interleaved: each person's zero-hop comes before their multi-hop matches
1775
- // Note: first zero-hop has r=null, subsequent zero-hops may have r=[] or stale value
1776
-
1777
- // Person 1's results: zero-hop, 1-hop to P2, 2-hop to P3, 3-hop to P4
1778
- expect(results[0].a.id).toBe(1);
1779
- expect(results[0].b.id).toBe(1);
1780
- // First zero-hop has r=null
1781
- expect(results[0].r).toBe(null);
1782
-
1783
- expect(results[1].a.id).toBe(1);
1784
- expect(results[1].b.id).toBe(2);
1785
- expect(results[2].a.id).toBe(1);
1786
- expect(results[2].b.id).toBe(3);
1787
- expect(results[3].a.id).toBe(1);
1788
- expect(results[3].b.id).toBe(4);
1789
-
1790
- // Person 2's results: zero-hop, 1-hop to P3, 2-hop to P4
1791
- expect(results[4].a.id).toBe(2);
1792
- expect(results[4].b.id).toBe(2);
1793
- expect(results[5].a.id).toBe(2);
1794
- expect(results[5].b.id).toBe(3);
1795
- expect(results[6].a.id).toBe(2);
1796
- expect(results[6].b.id).toBe(4);
1797
-
1798
- // Person 3's results: zero-hop, 1-hop to P4
1799
- expect(results[7].a.id).toBe(3);
1800
- expect(results[7].b.id).toBe(3);
1801
- expect(results[8].a.id).toBe(3);
1802
- expect(results[8].b.id).toBe(4);
1803
-
1804
- // Person 4's result: zero-hop only
1805
- expect(results[9].a.id).toBe(4);
1806
- expect(results[9].b.id).toBe(4);
1807
- });
1808
-
1809
- test("Test return match pattern with variable length relationships", async () => {
1810
- await new Runner(`
1811
- CREATE VIRTUAL (:Person) AS {
1812
- unwind [
1813
- {id: 1, name: 'Person 1'},
1814
- {id: 2, name: 'Person 2'},
1815
- {id: 3, name: 'Person 3'},
1816
- {id: 4, name: 'Person 4'}
1817
- ] as record
1818
- RETURN record.id as id, record.name as name
1819
- }
1820
- `).run();
1821
- await new Runner(`
1822
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1823
- unwind [
1824
- {left_id: 1, right_id: 2},
1825
- {left_id: 2, right_id: 3},
1826
- {left_id: 3, right_id: 4}
1827
- ] as record
1828
- RETURN record.left_id as left_id, record.right_id as right_id
1829
- }
1830
- `).run();
1831
- const match = new Runner(`
1832
- MATCH p=(a:Person)-[:KNOWS*0..3]->(b:Person)
1833
- RETURN p AS pattern
1834
- `);
1835
- await match.run();
1836
- const results = match.results;
1837
- expect(results.length).toBe(10);
1838
-
1839
- // Index 0: Person 1 zero-hop - pattern = [node1] (single node, no duplicate)
1840
- expect(results[0].pattern.length).toBe(1);
1841
- expect(results[0].pattern[0].id).toBe(1);
1842
-
1843
- // Index 1: Person 1 -> Person 2 (1-hop): pattern = [node1, rel, node2]
1844
- expect(results[1].pattern.length).toBe(3);
1845
- expect(results[1].pattern[0].id).toBe(1);
1846
- expect(results[1].pattern[1].startNode.id).toBe(1);
1847
- expect(results[1].pattern[1].endNode.id).toBe(2);
1848
- expect(results[1].pattern[2].id).toBe(2);
1849
-
1850
- // Index 2: Person 1 -> Person 3 (2-hop): pattern length = 5
1851
- expect(results[2].pattern.length).toBe(5);
1852
- expect(results[2].pattern[0].id).toBe(1);
1853
-
1854
- // Index 3: Person 1 -> Person 4 (3-hop): pattern length = 7
1855
- expect(results[3].pattern.length).toBe(7);
1856
- expect(results[3].pattern[0].id).toBe(1);
1857
- expect(results[3].pattern[6].id).toBe(4);
1858
-
1859
- // Index 4: Person 2 zero-hop - pattern = [node2] (single node)
1860
- expect(results[4].pattern.length).toBe(1);
1861
- expect(results[4].pattern[0].id).toBe(2);
1862
-
1863
- // Index 5: Person 2 -> Person 3 (1-hop)
1864
- expect(results[5].pattern.length).toBe(3);
1865
-
1866
- // Index 6: Person 2 -> Person 4 (2-hop)
1867
- expect(results[6].pattern.length).toBe(5);
1868
-
1869
- // Index 7: Person 3 zero-hop - pattern = [node3] (single node)
1870
- expect(results[7].pattern.length).toBe(1);
1871
- expect(results[7].pattern[0].id).toBe(3);
1872
-
1873
- // Index 8: Person 3 -> Person 4 (1-hop)
1874
- expect(results[8].pattern.length).toBe(3);
1875
-
1876
- // Index 9: Person 4 zero-hop - pattern = [node4] (single node)
1877
- expect(results[9].pattern.length).toBe(1);
1878
- expect(results[9].pattern[0].id).toBe(4);
1879
- });
1880
-
1881
- test("Test statement with graph pattern in where clause", async () => {
1882
- await new Runner(`
1883
- CREATE VIRTUAL (:Person) AS {
1884
- unwind [
1885
- {id: 1, name: 'Person 1'},
1886
- {id: 2, name: 'Person 2'},
1887
- {id: 3, name: 'Person 3'},
1888
- {id: 4, name: 'Person 4'}
1889
- ] as record
1890
- RETURN record.id as id, record.name as name
1891
- }
1892
- `).run();
1893
- await new Runner(`
1894
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1895
- unwind [
1896
- {left_id: 1, right_id: 2},
1897
- {left_id: 2, right_id: 3},
1898
- {left_id: 3, right_id: 4}
1899
- ] as record
1900
- RETURN record.left_id as left_id, record.right_id as right_id
1901
- }
1902
- `).run();
1903
- // Test positive match
1904
- const match = new Runner(`
1905
- MATCH (a:Person), (b:Person)
1906
- WHERE (a)-[:KNOWS]->(b)
1907
- RETURN a.name AS name1, b.name AS name2
1908
- `);
1909
- await match.run();
1910
- const results = match.results;
1911
- expect(results.length).toBe(3);
1912
- expect(results[0]).toEqual({ name1: "Person 1", name2: "Person 2" });
1913
- expect(results[1]).toEqual({ name1: "Person 2", name2: "Person 3" });
1914
- expect(results[2]).toEqual({ name1: "Person 3", name2: "Person 4" });
1915
-
1916
- // Test negative match
1917
- const nomatch = new Runner(`
1918
- MATCH (a:Person), (b:Person)
1919
- WHERE (a)-[:KNOWS]->(b) <> true
1920
- RETURN a.name AS name1, b.name AS name2
1921
- `);
1922
- await nomatch.run();
1923
- const noresults = nomatch.results;
1924
- expect(noresults.length).toBe(13);
1925
- expect(noresults[0]).toEqual({ name1: "Person 1", name2: "Person 1" });
1926
- expect(noresults[1]).toEqual({ name1: "Person 1", name2: "Person 3" });
1927
- expect(noresults[2]).toEqual({ name1: "Person 1", name2: "Person 4" });
1928
- expect(noresults[3]).toEqual({ name1: "Person 2", name2: "Person 1" });
1929
- expect(noresults[4]).toEqual({ name1: "Person 2", name2: "Person 2" });
1930
- expect(noresults[5]).toEqual({ name1: "Person 2", name2: "Person 4" });
1931
- expect(noresults[6]).toEqual({ name1: "Person 3", name2: "Person 1" });
1932
- expect(noresults[7]).toEqual({ name1: "Person 3", name2: "Person 2" });
1933
- expect(noresults[8]).toEqual({ name1: "Person 3", name2: "Person 3" });
1934
- expect(noresults[9]).toEqual({ name1: "Person 4", name2: "Person 1" });
1935
- expect(noresults[10]).toEqual({ name1: "Person 4", name2: "Person 2" });
1936
- expect(noresults[11]).toEqual({ name1: "Person 4", name2: "Person 3" });
1937
- expect(noresults[12]).toEqual({ name1: "Person 4", name2: "Person 4" });
1938
- });
1939
-
1940
- test("Test person who does not know anyone", async () => {
1941
- await new Runner(`
1942
- CREATE VIRTUAL (:Person) AS {
1943
- unwind [
1944
- {id: 1, name: 'Person 1'},
1945
- {id: 2, name: 'Person 2'},
1946
- {id: 3, name: 'Person 3'}
1947
- ] as record
1948
- RETURN record.id as id, record.name as name
1949
- }
1950
- `).run();
1951
- await new Runner(`
1952
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
1953
- unwind [
1954
- {left_id: 1, right_id: 2},
1955
- {left_id: 2, right_id: 1}
1956
- ] as record
1957
- RETURN record.left_id as left_id, record.right_id as right_id
1958
- }
1959
- `).run();
1960
- const match = new Runner(`
1961
- MATCH (a:Person)
1962
- WHERE NOT (a)-[:KNOWS]->(:Person)
1963
- RETURN a.name AS name
1964
- `);
1965
- await match.run();
1966
- const results = match.results;
1967
- expect(results.length).toBe(1);
1968
- expect(results[0]).toEqual({ name: "Person 3" });
1969
- });
1970
-
1971
- test("Test manager chain", async () => {
1972
- await new Runner(`
1973
- CREATE VIRTUAL (:Employee) AS {
1974
- unwind [
1975
- {id: 1, name: 'Employee 1'},
1976
- {id: 2, name: 'Employee 2'},
1977
- {id: 3, name: 'Employee 3'},
1978
- {id: 4, name: 'Employee 4'}
1979
- ] as record
1980
- RETURN record.id as id, record.name as name
1981
- }
1982
- `).run();
1983
- await new Runner(`
1984
- CREATE VIRTUAL (:Employee)-[:MANAGED_BY]-(:Employee) AS {
1985
- unwind [
1986
- {left_id: 2, right_id: 1},
1987
- {left_id: 3, right_id: 2},
1988
- {left_id: 4, right_id: 2}
1989
- ] as record
1990
- RETURN record.left_id as left_id, record.right_id as right_id
1991
- }
1992
- `).run();
1993
- const match = new Runner(`
1994
- MATCH p=(e:Employee)-[:MANAGED_BY*]->(m:Employee)
1995
- WHERE NOT (m)-[:MANAGED_BY]->(:Employee)
1996
- RETURN p
1997
- `);
1998
- await match.run();
1999
- const results = match.results;
2000
- // 4 results: includes CEO (Employee 1) with zero-hop match (empty management chain)
2001
- expect(results.length).toBe(4);
2002
- });
2003
-
2004
- test("Test equality comparison", async () => {
2005
- const runner = new Runner(`
2006
- unwind range(1,10) as i
2007
- return i=5 as \`isEqual\`, i<>5 as \`isNotEqual\`
2008
- `);
2009
- await runner.run();
2010
- const results = runner.results;
2011
- expect(results.length).toBe(10);
2012
- for (let index = 0; index < results.length; index++) {
2013
- const result = results[index];
2014
- if (index + 1 === 5) {
2015
- expect(result).toEqual({ isEqual: 1, isNotEqual: 0 });
2016
- } else {
2017
- expect(result).toEqual({ isEqual: 0, isNotEqual: 1 });
2018
- }
2019
- }
2020
- });
2021
-
2022
- test("Test match with constraints", async () => {
2023
- await new Runner(`
2024
- CREATE VIRTUAL (:Employee) AS {
2025
- unwind [
2026
- {id: 1, name: 'Employee 1'},
2027
- {id: 2, name: 'Employee 2'},
2028
- {id: 3, name: 'Employee 3'},
2029
- {id: 4, name: 'Employee 4'}
2030
- ] as record
2031
- RETURN record.id as id, record.name as name
2032
- }
2033
- `).run();
2034
- const match = new Runner(`
2035
- match (e:Employee{name:'Employee 1'})
2036
- return e.name as name
2037
- `);
2038
- await match.run();
2039
- const results = match.results;
2040
- expect(results.length).toBe(1);
2041
- expect(results[0].name).toBe("Employee 1");
2042
- });
2043
-
2044
- test("Test match with leftward relationship direction", async () => {
2045
- await new Runner(`
2046
- CREATE VIRTUAL (:Person) AS {
2047
- unwind [
2048
- {id: 1, name: 'Person 1'},
2049
- {id: 2, name: 'Person 2'},
2050
- {id: 3, name: 'Person 3'}
2051
- ] as record
2052
- RETURN record.id as id, record.name as name
2053
- }
2054
- `).run();
2055
- await new Runner(`
2056
- CREATE VIRTUAL (:Person)-[:REPORTS_TO]-(:Person) AS {
2057
- unwind [
2058
- {left_id: 2, right_id: 1},
2059
- {left_id: 3, right_id: 1}
2060
- ] as record
2061
- RETURN record.left_id as left_id, record.right_id as right_id
2062
- }
2063
- `).run();
2064
- // Rightward: left_id -> right_id (2->1, 3->1)
2065
- const rightMatch = new Runner(`
2066
- MATCH (a:Person)-[:REPORTS_TO]->(b:Person)
2067
- RETURN a.name AS employee, b.name AS manager
2068
- `);
2069
- await rightMatch.run();
2070
- const rightResults = rightMatch.results;
2071
- expect(rightResults.length).toBe(2);
2072
- expect(rightResults[0]).toEqual({ employee: "Person 2", manager: "Person 1" });
2073
- expect(rightResults[1]).toEqual({ employee: "Person 3", manager: "Person 1" });
2074
-
2075
- // Leftward: right_id -> left_id (1->2, 1->3) — reverse traversal
2076
- const leftMatch = new Runner(`
2077
- MATCH (m:Person)<-[:REPORTS_TO]-(e:Person)
2078
- RETURN m.name AS manager, e.name AS employee
2079
- `);
2080
- await leftMatch.run();
2081
- const leftResults = leftMatch.results;
2082
- expect(leftResults.length).toBe(2);
2083
- expect(leftResults[0]).toEqual({ manager: "Person 1", employee: "Person 2" });
2084
- expect(leftResults[1]).toEqual({ manager: "Person 1", employee: "Person 3" });
2085
- });
2086
-
2087
- test("Test match with leftward direction produces same results as rightward with swapped data", async () => {
2088
- await new Runner(`
2089
- CREATE VIRTUAL (:City) AS {
2090
- unwind [
2091
- {id: 1, name: 'New York'},
2092
- {id: 2, name: 'Boston'},
2093
- {id: 3, name: 'Chicago'}
2094
- ] as record
2095
- RETURN record.id as id, record.name as name
2096
- }
2097
- `).run();
2098
- await new Runner(`
2099
- CREATE VIRTUAL (:City)-[:ROUTE]-(:City) AS {
2100
- unwind [
2101
- {left_id: 1, right_id: 2},
2102
- {left_id: 1, right_id: 3}
2103
- ] as record
2104
- RETURN record.left_id as left_id, record.right_id as right_id
2105
- }
2106
- `).run();
2107
- // Leftward from destination: find where right_id matches, follow left_id
2108
- const match = new Runner(`
2109
- MATCH (dest:City)<-[:ROUTE]-(origin:City)
2110
- RETURN dest.name AS destination, origin.name AS origin
2111
- `);
2112
- await match.run();
2113
- const results = match.results;
2114
- expect(results.length).toBe(2);
2115
- expect(results[0]).toEqual({ destination: "Boston", origin: "New York" });
2116
- expect(results[1]).toEqual({ destination: "Chicago", origin: "New York" });
2117
- });
2118
-
2119
- test("Test match with leftward variable-length relationships", async () => {
2120
- await new Runner(`
2121
- CREATE VIRTUAL (:Person) AS {
2122
- unwind [
2123
- {id: 1, name: 'Person 1'},
2124
- {id: 2, name: 'Person 2'},
2125
- {id: 3, name: 'Person 3'}
2126
- ] as record
2127
- RETURN record.id as id, record.name as name
2128
- }
2129
- `).run();
2130
- await new Runner(`
2131
- CREATE VIRTUAL (:Person)-[:MANAGES]-(:Person) AS {
2132
- unwind [
2133
- {left_id: 1, right_id: 2},
2134
- {left_id: 2, right_id: 3}
2135
- ] as record
2136
- RETURN record.left_id as left_id, record.right_id as right_id
2137
- }
2138
- `).run();
2139
- // Leftward variable-length: traverse from right_id to left_id
2140
- // Person 3 can reach Person 2 (1 hop) and Person 1 (2 hops)
2141
- const match = new Runner(`
2142
- MATCH (a:Person)<-[:MANAGES*]-(b:Person)
2143
- RETURN a.name AS name1, b.name AS name2
2144
- `);
2145
- await match.run();
2146
- const results = match.results;
2147
- // Zero-hop results for all 3 persons + multi-hop results
2148
- // Leftward indexes on right_id. find(id) looks up right_id=id, follows left_id.
2149
- // right_id=1: no records → Person 1 zero-hop only
2150
- // right_id=2: record {left_id:1, right_id:2} → Person 2 → Person 1, then recurse find(1) → no more
2151
- // right_id=3: record {left_id:2, right_id:3} → Person 3 → Person 2, then recurse find(2) → Person 1
2152
- expect(results.length).toBe(6);
2153
- // Person 1: zero-hop
2154
- expect(results[0]).toEqual({ name1: "Person 1", name2: "Person 1" });
2155
- // Person 2: zero-hop, then reaches Person 1
2156
- expect(results[1]).toEqual({ name1: "Person 2", name2: "Person 2" });
2157
- expect(results[2]).toEqual({ name1: "Person 2", name2: "Person 1" });
2158
- // Person 3: zero-hop, then reaches Person 2, then Person 1
2159
- expect(results[3]).toEqual({ name1: "Person 3", name2: "Person 3" });
2160
- expect(results[4]).toEqual({ name1: "Person 3", name2: "Person 2" });
2161
- expect(results[5]).toEqual({ name1: "Person 3", name2: "Person 1" });
2162
- });
2163
-
2164
- test("Test match with leftward double graph pattern", async () => {
2165
- await new Runner(`
2166
- CREATE VIRTUAL (:Person) AS {
2167
- unwind [
2168
- {id: 1, name: 'Person 1'},
2169
- {id: 2, name: 'Person 2'},
2170
- {id: 3, name: 'Person 3'},
2171
- {id: 4, name: 'Person 4'}
2172
- ] as record
2173
- RETURN record.id as id, record.name as name
2174
- }
2175
- `).run();
2176
- await new Runner(`
2177
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
2178
- unwind [
2179
- {left_id: 1, right_id: 2},
2180
- {left_id: 2, right_id: 3},
2181
- {left_id: 3, right_id: 4}
2182
- ] as record
2183
- RETURN record.left_id as left_id, record.right_id as right_id
2184
- }
2185
- `).run();
2186
- // Leftward chain: (c)<-[:KNOWS]-(b)<-[:KNOWS]-(a)
2187
- // First rel: find right_id=c, follow left_id to b
2188
- // Second rel: find right_id=b, follow left_id to a
2189
- const match = new Runner(`
2190
- MATCH (c:Person)<-[:KNOWS]-(b:Person)<-[:KNOWS]-(a:Person)
2191
- RETURN a.name AS name1, b.name AS name2, c.name AS name3
2192
- `);
2193
- await match.run();
2194
- const results = match.results;
2195
- expect(results.length).toBe(2);
2196
- expect(results[0]).toEqual({ name1: "Person 1", name2: "Person 2", name3: "Person 3" });
2197
- expect(results[1]).toEqual({ name1: "Person 2", name2: "Person 3", name3: "Person 4" });
2198
- });
2199
-
2200
- test("Test optional match with no matching relationship", async () => {
2201
- await new Runner(`
2202
- CREATE VIRTUAL (:Person) AS {
2203
- unwind [
2204
- {id: 1, name: 'Person 1'},
2205
- {id: 2, name: 'Person 2'},
2206
- {id: 3, name: 'Person 3'}
2207
- ] as record
2208
- RETURN record.id as id, record.name as name
2209
- }
2210
- `).run();
2211
- await new Runner(`
2212
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
2213
- unwind [
2214
- {left_id: 1, right_id: 2}
2215
- ] as record
2216
- RETURN record.left_id as left_id, record.right_id as right_id
2217
- }
2218
- `).run();
2219
- // Person 3 has no KNOWS relationship, so OPTIONAL MATCH should return null for friend
2220
- const match = new Runner(`
2221
- MATCH (a:Person)
2222
- OPTIONAL MATCH (a)-[:KNOWS]->(b:Person)
2223
- RETURN a.name AS name, b AS friend
2224
- `);
2225
- await match.run();
2226
- const results = match.results;
2227
- expect(results.length).toBe(3);
2228
- expect(results[0].name).toBe("Person 1");
2229
- expect(results[0].friend).toBeDefined();
2230
- expect(results[0].friend.name).toBe("Person 2");
2231
- expect(results[1].name).toBe("Person 2");
2232
- expect(results[1].friend).toBeNull();
2233
- expect(results[2].name).toBe("Person 3");
2234
- expect(results[2].friend).toBeNull();
2235
- });
2236
-
2237
- test("Test optional match property access on null node returns null", async () => {
2238
- await new Runner(`
2239
- CREATE VIRTUAL (:Person) AS {
2240
- unwind [
2241
- {id: 1, name: 'Person 1'},
2242
- {id: 2, name: 'Person 2'},
2243
- {id: 3, name: 'Person 3'}
2244
- ] as record
2245
- RETURN record.id as id, record.name as name
2246
- }
2247
- `).run();
2248
- await new Runner(`
2249
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
2250
- unwind [
2251
- {left_id: 1, right_id: 2}
2252
- ] as record
2253
- RETURN record.left_id as left_id, record.right_id as right_id
2254
- }
2255
- `).run();
2256
- // When accessing b.name and b is null (no match), should return null
2257
- const match = new Runner(`
2258
- MATCH (a:Person)
2259
- OPTIONAL MATCH (a)-[:KNOWS]->(b:Person)
2260
- RETURN a.name AS name, b.name AS friend_name
2261
- `);
2262
- await match.run();
2263
- const results = match.results;
2264
- expect(results.length).toBe(3);
2265
- expect(results[0]).toEqual({ name: "Person 1", friend_name: "Person 2" });
2266
- expect(results[1]).toEqual({ name: "Person 2", friend_name: null });
2267
- expect(results[2]).toEqual({ name: "Person 3", friend_name: null });
2268
- });
2269
-
2270
- test("Test optional match where all nodes match", async () => {
2271
- await new Runner(`
2272
- CREATE VIRTUAL (:Person) AS {
2273
- unwind [
2274
- {id: 1, name: 'Person 1'},
2275
- {id: 2, name: 'Person 2'}
2276
- ] as record
2277
- RETURN record.id as id, record.name as name
2278
- }
2279
- `).run();
2280
- await new Runner(`
2281
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
2282
- unwind [
2283
- {left_id: 1, right_id: 2},
2284
- {left_id: 2, right_id: 1}
2285
- ] as record
2286
- RETURN record.left_id as left_id, record.right_id as right_id
2287
- }
2288
- `).run();
2289
- // All persons have KNOWS relationships, so no null values
2290
- const match = new Runner(`
2291
- MATCH (a:Person)
2292
- OPTIONAL MATCH (a)-[:KNOWS]->(b:Person)
2293
- RETURN a.name AS name, b.name AS friend
2294
- `);
2295
- await match.run();
2296
- const results = match.results;
2297
- expect(results.length).toBe(2);
2298
- expect(results[0]).toEqual({ name: "Person 1", friend: "Person 2" });
2299
- expect(results[1]).toEqual({ name: "Person 2", friend: "Person 1" });
2300
- });
2301
-
2302
- test("Test optional match with no data returns nulls", async () => {
2303
- await new Runner(`
2304
- CREATE VIRTUAL (:Person) AS {
2305
- unwind [
2306
- {id: 1, name: 'Person 1'},
2307
- {id: 2, name: 'Person 2'}
2308
- ] as record
2309
- RETURN record.id as id, record.name as name
2310
- }
2311
- `).run();
2312
- await new Runner(`
2313
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
2314
- unwind [] as record
2315
- RETURN record.left_id as left_id, record.right_id as right_id
2316
- }
2317
- `).run();
2318
- // KNOWS relationship type exists but has no data
2319
- const match = new Runner(`
2320
- MATCH (a:Person)
2321
- OPTIONAL MATCH (a)-[:KNOWS]->(b:Person)
2322
- RETURN a.name AS name, b AS friend
2323
- `);
2324
- await match.run();
2325
- const results = match.results;
2326
- expect(results.length).toBe(2);
2327
- expect(results[0].name).toBe("Person 1");
2328
- expect(results[0].friend).toBeNull();
2329
- expect(results[1].name).toBe("Person 2");
2330
- expect(results[1].friend).toBeNull();
2331
- });
2332
-
2333
- test("Test optional match with aggregation", async () => {
2334
- await new Runner(`
2335
- CREATE VIRTUAL (:Person) AS {
2336
- unwind [
2337
- {id: 1, name: 'Person 1'},
2338
- {id: 2, name: 'Person 2'},
2339
- {id: 3, name: 'Person 3'}
2340
- ] as record
2341
- RETURN record.id as id, record.name as name
2342
- }
2343
- `).run();
2344
- await new Runner(`
2345
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
2346
- unwind [
2347
- {left_id: 1, right_id: 2},
2348
- {left_id: 1, right_id: 3}
2349
- ] as record
2350
- RETURN record.left_id as left_id, record.right_id as right_id
2351
- }
2352
- `).run();
2353
- // Collect friends per person; Person 2 and 3 have no friends
2354
- const match = new Runner(`
2355
- MATCH (a:Person)
2356
- OPTIONAL MATCH (a)-[:KNOWS]->(b:Person)
2357
- RETURN a.name AS name, collect(b) AS friends
2358
- `);
2359
- await match.run();
2360
- const results = match.results;
2361
- expect(results.length).toBe(3);
2362
- expect(results[0].name).toBe("Person 1");
2363
- expect(results[0].friends.length).toBe(2);
2364
- expect(results[1].name).toBe("Person 2");
2365
- expect(results[1].friends.length).toBe(1); // null is collected
2366
- expect(results[2].name).toBe("Person 3");
2367
- expect(results[2].friends.length).toBe(1); // null is collected
2368
- });
2369
-
2370
- test("Test standalone optional match returns data when label exists", async () => {
2371
- await new Runner(`
2372
- CREATE VIRTUAL (:Person) AS {
2373
- unwind [
2374
- {id: 1, name: 'Person 1'},
2375
- {id: 2, name: 'Person 2'}
2376
- ] as record
2377
- RETURN record.id as id, record.name as name
2378
- }
2379
- `).run();
2380
- await new Runner(`
2381
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
2382
- unwind [
2383
- {left_id: 1, right_id: 2}
2384
- ] as record
2385
- RETURN record.left_id as left_id, record.right_id as right_id
2386
- }
2387
- `).run();
2388
- // Standalone OPTIONAL MATCH with relationship where only Person 1 has a match
2389
- const match = new Runner(`
2390
- OPTIONAL MATCH (a:Person)-[:KNOWS]->(b:Person)
2391
- RETURN a.name AS name, b.name AS friend
2392
- `);
2393
- await match.run();
2394
- const results = match.results;
2395
- expect(results.length).toBe(1);
2396
- expect(results[0]).toEqual({ name: "Person 1", friend: "Person 2" });
2397
- });
2398
-
2399
- test("Test optional match returns full node when matched", async () => {
2400
- await new Runner(`
2401
- CREATE VIRTUAL (:Person) AS {
2402
- unwind [
2403
- {id: 1, name: 'Person 1'},
2404
- {id: 2, name: 'Person 2'}
2405
- ] as record
2406
- RETURN record.id as id, record.name as name
2407
- }
2408
- `).run();
2409
- // OPTIONAL MATCH on existing label returns actual nodes
2410
- const match = new Runner(`
2411
- OPTIONAL MATCH (n:Person)
2412
- RETURN n.name AS name
2413
- `);
2414
- await match.run();
2415
- const results = match.results;
2416
- expect(results.length).toBe(2);
2417
- expect(results[0]).toEqual({ name: "Person 1" });
2418
- expect(results[1]).toEqual({ name: "Person 2" });
2419
- });
2420
-
2421
- test("Test schema() returns nodes and relationships with sample data", async () => {
2422
- await new Runner(`
2423
- CREATE VIRTUAL (:Animal) AS {
2424
- UNWIND [
2425
- {id: 1, species: 'Cat', legs: 4},
2426
- {id: 2, species: 'Dog', legs: 4}
2427
- ] AS record
2428
- RETURN record.id AS id, record.species AS species, record.legs AS legs
2429
- }
2430
- `).run();
2431
- await new Runner(`
2432
- CREATE VIRTUAL (:Animal)-[:CHASES]-(:Animal) AS {
2433
- UNWIND [
2434
- {left_id: 2, right_id: 1, speed: 'fast'}
2435
- ] AS record
2436
- RETURN record.left_id AS left_id, record.right_id AS right_id, record.speed AS speed
2437
- }
2438
- `).run();
2439
-
2440
- const runner = new Runner(
2441
- "CALL schema() YIELD kind, label, type, from_label, to_label, properties, sample RETURN kind, label, type, from_label, to_label, properties, sample"
2442
- );
2443
- await runner.run();
2444
- const results = runner.results;
2445
-
2446
- const animal = results.find((r: any) => r.kind === "Node" && r.label === "Animal");
2447
- expect(animal).toBeDefined();
2448
- expect(animal.properties).toEqual(["species", "legs"]);
2449
- expect(animal.sample).toBeDefined();
2450
- expect(animal.sample).not.toHaveProperty("id");
2451
- expect(animal.sample).toHaveProperty("species");
2452
- expect(animal.sample).toHaveProperty("legs");
2453
-
2454
- const chases = results.find((r: any) => r.kind === "Relationship" && r.type === "CHASES");
2455
- expect(chases).toBeDefined();
2456
- expect(chases.from_label).toBe("Animal");
2457
- expect(chases.to_label).toBe("Animal");
2458
- expect(chases.properties).toEqual(["speed"]);
2459
- expect(chases.sample).toBeDefined();
2460
- expect(chases.sample).not.toHaveProperty("left_id");
2461
- expect(chases.sample).not.toHaveProperty("right_id");
2462
- expect(chases.sample).toHaveProperty("speed");
2463
- });
2464
-
2465
- test("Test reserved keywords as identifiers", async () => {
2466
- const runner = new Runner(`
2467
- WITH 1 AS return
2468
- RETURN return
2469
- `);
2470
- await runner.run();
2471
- const results = runner.results;
2472
- expect(results.length).toBe(1);
2473
- expect(results[0].return).toBe(1);
2474
- });
2475
-
2476
- test("Test reserved keywords as parts of identifiers", async () => {
2477
- const runner = new Runner(`
2478
- unwind [
2479
- {from: "Alice", to: "Bob", organizer: "Charlie"},
2480
- {from: "Bob", to: "Charlie", organizer: "Alice"},
2481
- {from: "Charlie", to: "Alice", organizer: "Bob"}
2482
- ] as data
2483
- return data.from as from, data.to as to, data.organizer as organizer
2484
- `);
2485
- await runner.run();
2486
- const results = runner.results;
2487
- expect(results.length).toBe(3);
2488
- expect(results[0]).toEqual({ from: "Alice", to: "Bob", organizer: "Charlie" });
2489
- expect(results[1]).toEqual({ from: "Bob", to: "Charlie", organizer: "Alice" });
2490
- expect(results[2]).toEqual({ from: "Charlie", to: "Alice", organizer: "Bob" });
2491
- });
2492
-
2493
- test("Test reserved keywords as relationship types and labels", async () => {
2494
- await new Runner(`
2495
- CREATE VIRTUAL (:Return) AS {
2496
- unwind [
2497
- {id: 1, name: 'Node 1'},
2498
- {id: 2, name: 'Node 2'}
2499
- ] as record
2500
- RETURN record.id as id, record.name as name
2501
- }
2502
- `).run();
2503
- await new Runner(`
2504
- CREATE VIRTUAL (:Return)-[:With]-(:Return) AS {
2505
- unwind [
2506
- {left_id: 1, right_id: 2}
2507
- ] as record
2508
- RETURN record.left_id as left_id, record.right_id as right_id
2509
- }
2510
- `).run();
2511
- const match = new Runner(`
2512
- MATCH (a:Return)-[:With]->(b:Return)
2513
- RETURN a.name AS name1, b.name AS name2
2514
- `);
2515
- await match.run();
2516
- const results = match.results;
2517
- expect(results.length).toBe(1);
2518
- expect(results[0]).toEqual({ name1: "Node 1", name2: "Node 2" });
2519
- });
2520
-
2521
- test("Test match with node reference passed through WITH", async () => {
2522
- await new Runner(`
2523
- CREATE VIRTUAL (:User) AS {
2524
- UNWIND [
2525
- {id: 1, name: 'Alice', mail: 'alice@test.com', jobTitle: 'CEO'},
2526
- {id: 2, name: 'Bob', mail: 'bob@test.com', jobTitle: 'VP'},
2527
- {id: 3, name: 'Carol', mail: 'carol@test.com', jobTitle: 'VP'},
2528
- {id: 4, name: 'Dave', mail: 'dave@test.com', jobTitle: 'Engineer'}
2529
- ] AS record
2530
- RETURN record.id AS id, record.name AS name, record.mail AS mail, record.jobTitle AS jobTitle
2531
- }
2532
- `).run();
2533
- await new Runner(`
2534
- CREATE VIRTUAL (:User)-[:MANAGES]-(:User) AS {
2535
- UNWIND [
2536
- {left_id: 1, right_id: 2},
2537
- {left_id: 1, right_id: 3},
2538
- {left_id: 2, right_id: 4}
2539
- ] AS record
2540
- RETURN record.left_id AS left_id, record.right_id AS right_id
2541
- }
2542
- `).run();
2543
- // Equivalent to:
2544
- // MATCH (ceo:User)-[:MANAGES]->(dr1:User)
2545
- // WHERE ceo.jobTitle = 'CEO'
2546
- // WITH ceo, dr1
2547
- // MATCH (ceo)-[:MANAGES]->(dr2:User)
2548
- // WHERE dr1.mail <> dr2.mail
2549
- // RETURN ceo, dr1, dr2
2550
- const match = new Runner(`
2551
- MATCH (ceo:User)-[:MANAGES]->(dr1:User)
2552
- WHERE ceo.jobTitle = 'CEO'
2553
- WITH ceo, dr1
2554
- MATCH (ceo)-[:MANAGES]->(dr2:User)
2555
- WHERE dr1.mail <> dr2.mail
2556
- RETURN ceo.name AS ceo, dr1.name AS dr1, dr2.name AS dr2
2557
- `);
2558
- await match.run();
2559
- const results = match.results;
2560
- // CEO (Alice) manages Bob and Carol. All distinct pairs:
2561
- // (Alice, Bob, Carol) and (Alice, Carol, Bob)
2562
- expect(results.length).toBe(2);
2563
- expect(results[0]).toEqual({ ceo: "Alice", dr1: "Bob", dr2: "Carol" });
2564
- expect(results[1]).toEqual({ ceo: "Alice", dr1: "Carol", dr2: "Bob" });
2565
- });
2566
-
2567
- test("Test match with node reference reuse with label", async () => {
2568
- await new Runner(`
2569
- CREATE VIRTUAL (:RefLabelUser) AS {
2570
- UNWIND [
2571
- {id: 1, name: 'Alice', jobTitle: 'CEO'},
2572
- {id: 2, name: 'Bob', jobTitle: 'VP'},
2573
- {id: 3, name: 'Carol', jobTitle: 'VP'},
2574
- {id: 4, name: 'Dave', jobTitle: 'Engineer'}
2575
- ] AS record
2576
- RETURN record.id AS id, record.name AS name, record.jobTitle AS jobTitle
2577
- }
2578
- `).run();
2579
- await new Runner(`
2580
- CREATE VIRTUAL (:RefLabelUser)-[:MANAGES]-(:RefLabelUser) AS {
2581
- UNWIND [
2582
- {left_id: 1, right_id: 2},
2583
- {left_id: 1, right_id: 3},
2584
- {left_id: 2, right_id: 4}
2585
- ] AS record
2586
- RETURN record.left_id AS left_id, record.right_id AS right_id
2587
- }
2588
- `).run();
2589
- // Uses (ceo:RefLabelUser) with label in both MATCH clauses.
2590
- // Previously this would create a new node instead of a NodeReference.
2591
- const match = new Runner(`
2592
- MATCH (ceo:RefLabelUser)-[:MANAGES]->(dr1:RefLabelUser)
2593
- WHERE ceo.jobTitle = 'CEO'
2594
- WITH ceo, dr1
2595
- MATCH (ceo:RefLabelUser)-[:MANAGES]->(dr2:RefLabelUser)
2596
- WHERE dr1.name <> dr2.name
2597
- RETURN ceo.name AS ceo, dr1.name AS dr1, dr2.name AS dr2
2598
- `);
2599
- await match.run();
2600
- const results = match.results;
2601
- expect(results.length).toBe(2);
2602
- expect(results[0]).toEqual({ ceo: "Alice", dr1: "Bob", dr2: "Carol" });
2603
- expect(results[1]).toEqual({ ceo: "Alice", dr1: "Carol", dr2: "Bob" });
2604
- });
2605
-
2606
- test("Test WHERE with IS NULL", async () => {
2607
- const runner = new Runner(`
2608
- unwind [{name: 'Alice', age: 30}, {name: 'Bob'}] as person
2609
- with person.name as name, person.age as age
2610
- where age IS NULL
2611
- return name
2612
- `);
2613
- await runner.run();
2614
- const results = runner.results;
2615
- expect(results.length).toBe(1);
2616
- expect(results[0]).toEqual({ name: "Bob" });
2617
- });
2618
-
2619
- test("Test WHERE with IS NOT NULL", async () => {
2620
- const runner = new Runner(`
2621
- unwind [{name: 'Alice', age: 30}, {name: 'Bob'}] as person
2622
- with person.name as name, person.age as age
2623
- where age IS NOT NULL
2624
- return name, age
2625
- `);
2626
- await runner.run();
2627
- const results = runner.results;
2628
- expect(results.length).toBe(1);
2629
- expect(results[0]).toEqual({ name: "Alice", age: 30 });
2630
- });
2631
-
2632
- test("Test WHERE with IS NOT NULL filters multiple results", async () => {
2633
- const runner = new Runner(`
2634
- unwind [{name: 'Alice', age: 30}, {name: 'Bob'}, {name: 'Carol', age: 25}] as person
2635
- with person.name as name, person.age as age
2636
- where age IS NOT NULL
2637
- return name, age
2638
- `);
2639
- await runner.run();
2640
- const results = runner.results;
2641
- expect(results.length).toBe(2);
2642
- expect(results[0]).toEqual({ name: "Alice", age: 30 });
2643
- expect(results[1]).toEqual({ name: "Carol", age: 25 });
2644
- });
2645
-
2646
- test("Test WHERE with IN list check", async () => {
2647
- const runner = new Runner(`
2648
- unwind range(1, 10) as n
2649
- with n
2650
- where n IN [2, 4, 6, 8]
2651
- return n
2652
- `);
2653
- await runner.run();
2654
- const results = runner.results;
2655
- expect(results.length).toBe(4);
2656
- expect(results.map((r: any) => r.n)).toEqual([2, 4, 6, 8]);
2657
- });
2658
-
2659
- test("Test WHERE with NOT IN list check", async () => {
2660
- const runner = new Runner(`
2661
- unwind range(1, 5) as n
2662
- with n
2663
- where n NOT IN [2, 4]
2664
- return n
2665
- `);
2666
- await runner.run();
2667
- const results = runner.results;
2668
- expect(results.length).toBe(3);
2669
- expect(results.map((r: any) => r.n)).toEqual([1, 3, 5]);
2670
- });
2671
-
2672
- test("Test WHERE with IN string list", async () => {
2673
- const runner = new Runner(`
2674
- unwind ['apple', 'banana', 'cherry', 'date'] as fruit
2675
- with fruit
2676
- where fruit IN ['banana', 'date']
2677
- return fruit
2678
- `);
2679
- await runner.run();
2680
- const results = runner.results;
2681
- expect(results.length).toBe(2);
2682
- expect(results.map((r: any) => r.fruit)).toEqual(["banana", "date"]);
2683
- });
2684
-
2685
- test("Test WHERE with IN combined with AND", async () => {
2686
- const runner = new Runner(`
2687
- unwind range(1, 20) as n
2688
- with n
2689
- where n IN [1, 5, 10, 15, 20] AND n > 5
2690
- return n
2691
- `);
2692
- await runner.run();
2693
- const results = runner.results;
2694
- expect(results.length).toBe(3);
2695
- expect(results.map((r: any) => r.n)).toEqual([10, 15, 20]);
2696
- });
2697
-
2698
- test("Test WHERE with AND before IN", async () => {
2699
- const runner = new Runner(`
2700
- unwind ['expert', 'intermediate', 'beginner'] as proficiency
2701
- with proficiency where 1=1 and proficiency in ['expert']
2702
- return proficiency
2703
- `);
2704
- await runner.run();
2705
- const results = runner.results;
2706
- expect(results.length).toBe(1);
2707
- expect(results[0]).toEqual({ proficiency: "expert" });
2708
- });
2709
-
2710
- test("Test WHERE with AND before NOT IN", async () => {
2711
- const runner = new Runner(`
2712
- unwind ['expert', 'intermediate', 'beginner'] as proficiency
2713
- with proficiency where 1=1 and proficiency not in ['expert']
2714
- return proficiency
2715
- `);
2716
- await runner.run();
2717
- const results = runner.results;
2718
- expect(results.length).toBe(2);
2719
- expect(results.map((r: any) => r.proficiency)).toEqual(["intermediate", "beginner"]);
2720
- });
2721
-
2722
- test("Test WHERE with OR before IN", async () => {
2723
- const runner = new Runner(`
2724
- unwind range(1, 10) as n
2725
- with n where 1=0 or n in [3, 7]
2726
- return n
2727
- `);
2728
- await runner.run();
2729
- const results = runner.results;
2730
- expect(results.length).toBe(2);
2731
- expect(results.map((r: any) => r.n)).toEqual([3, 7]);
2732
- });
2733
-
2734
- test("Test IN as return expression with AND in WHERE", async () => {
2735
- const runner = new Runner(`
2736
- unwind ['expert', 'intermediate', 'beginner'] as proficiency
2737
- with proficiency where 1=1 and proficiency in ['expert']
2738
- return proficiency, proficiency in ['expert'] as isExpert
2739
- `);
2740
- await runner.run();
2741
- const results = runner.results;
2742
- expect(results.length).toBe(1);
2743
- expect(results[0]).toEqual({ proficiency: "expert", isExpert: 1 });
2744
- });
2745
-
2746
- test("Test WHERE with CONTAINS", async () => {
2747
- const runner = new Runner(`
2748
- unwind ['apple', 'banana', 'grape', 'pineapple'] as fruit
2749
- with fruit
2750
- where fruit CONTAINS 'apple'
2751
- return fruit
2752
- `);
2753
- await runner.run();
2754
- const results = runner.results;
2755
- expect(results.length).toBe(2);
2756
- expect(results.map((r: any) => r.fruit)).toEqual(["apple", "pineapple"]);
2757
- });
2758
-
2759
- test("Test WHERE with NOT CONTAINS", async () => {
2760
- const runner = new Runner(`
2761
- unwind ['apple', 'banana', 'grape', 'pineapple'] as fruit
2762
- with fruit
2763
- where fruit NOT CONTAINS 'apple'
2764
- return fruit
2765
- `);
2766
- await runner.run();
2767
- const results = runner.results;
2768
- expect(results.length).toBe(2);
2769
- expect(results.map((r: any) => r.fruit)).toEqual(["banana", "grape"]);
2770
- });
2771
-
2772
- test("Test WHERE with STARTS WITH", async () => {
2773
- const runner = new Runner(`
2774
- unwind ['apple', 'apricot', 'banana', 'avocado'] as fruit
2775
- with fruit
2776
- where fruit STARTS WITH 'ap'
2777
- return fruit
2778
- `);
2779
- await runner.run();
2780
- const results = runner.results;
2781
- expect(results.length).toBe(2);
2782
- expect(results.map((r: any) => r.fruit)).toEqual(["apple", "apricot"]);
2783
- });
2784
-
2785
- test("Test WHERE with NOT STARTS WITH", async () => {
2786
- const runner = new Runner(`
2787
- unwind ['apple', 'apricot', 'banana', 'avocado'] as fruit
2788
- with fruit
2789
- where fruit NOT STARTS WITH 'ap'
2790
- return fruit
2791
- `);
2792
- await runner.run();
2793
- const results = runner.results;
2794
- expect(results.length).toBe(2);
2795
- expect(results.map((r: any) => r.fruit)).toEqual(["banana", "avocado"]);
2796
- });
2797
-
2798
- test("Test WHERE with ENDS WITH", async () => {
2799
- const runner = new Runner(`
2800
- unwind ['apple', 'pineapple', 'banana', 'grape'] as fruit
2801
- with fruit
2802
- where fruit ENDS WITH 'ple'
2803
- return fruit
2804
- `);
2805
- await runner.run();
2806
- const results = runner.results;
2807
- expect(results.length).toBe(2);
2808
- expect(results.map((r: any) => r.fruit)).toEqual(["apple", "pineapple"]);
2809
- });
2810
-
2811
- test("Test WHERE with NOT ENDS WITH", async () => {
2812
- const runner = new Runner(`
2813
- unwind ['apple', 'pineapple', 'banana', 'grape'] as fruit
2814
- with fruit
2815
- where fruit NOT ENDS WITH 'ple'
2816
- return fruit
2817
- `);
2818
- await runner.run();
2819
- const results = runner.results;
2820
- expect(results.length).toBe(2);
2821
- expect(results.map((r: any) => r.fruit)).toEqual(["banana", "grape"]);
2822
- });
2823
-
2824
- test("Test WHERE with CONTAINS combined with AND", async () => {
2825
- const runner = new Runner(`
2826
- unwind ['apple', 'pineapple', 'applesauce', 'banana'] as fruit
2827
- with fruit
2828
- where fruit CONTAINS 'apple' AND fruit STARTS WITH 'pine'
2829
- return fruit
2830
- `);
2831
- await runner.run();
2832
- const results = runner.results;
2833
- expect(results.length).toBe(1);
2834
- expect(results[0].fruit).toBe("pineapple");
2835
- });
2836
-
2837
- test("Test collected nodes and re-matching", async () => {
2838
- await new Runner(`
2839
- CREATE VIRTUAL (:Person) AS {
2840
- unwind [
2841
- {id: 1, name: 'Person 1'},
2842
- {id: 2, name: 'Person 2'},
2843
- {id: 3, name: 'Person 3'},
2844
- {id: 4, name: 'Person 4'}
2845
- ] as record
2846
- RETURN record.id as id, record.name as name
2847
- }
2848
- `).run();
2849
- await new Runner(`
2850
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
2851
- unwind [
2852
- {left_id: 1, right_id: 2},
2853
- {left_id: 2, right_id: 3},
2854
- {left_id: 3, right_id: 4}
2855
- ] as record
2856
- RETURN record.left_id as left_id, record.right_id as right_id
2857
- }
2858
- `).run();
2859
- const match = new Runner(`
2860
- MATCH (a:Person)-[:KNOWS*0..3]->(b:Person)
2861
- WITH collect(a) AS persons, b
2862
- UNWIND persons AS p
2863
- match (p)-[:KNOWS]->(:Person)
2864
- return p.name AS name
2865
- `);
2866
- await match.run();
2867
- const results = match.results;
2868
- expect(results.length).toBe(9);
2869
- const names = results.map((r: any) => r.name);
2870
- expect(names).toContain("Person 1");
2871
- expect(names).toContain("Person 2");
2872
- expect(names).toContain("Person 3");
2873
- });
2874
-
2875
- test("Test collected patterns and unwind", async () => {
2876
- await new Runner(`
2877
- CREATE VIRTUAL (:Person) AS {
2878
- unwind [
2879
- {id: 1, name: 'Person 1'},
2880
- {id: 2, name: 'Person 2'},
2881
- {id: 3, name: 'Person 3'},
2882
- {id: 4, name: 'Person 4'}
2883
- ] as record
2884
- RETURN record.id as id, record.name as name
2885
- }
2886
- `).run();
2887
- await new Runner(`
2888
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
2889
- unwind [
2890
- {left_id: 1, right_id: 2},
2891
- {left_id: 2, right_id: 3},
2892
- {left_id: 3, right_id: 4}
2893
- ] as record
2894
- RETURN record.left_id as left_id, record.right_id as right_id
2895
- }
2896
- `).run();
2897
- const match = new Runner(`
2898
- MATCH p=(a:Person)-[:KNOWS*0..3]->(b:Person)
2899
- WITH collect(p) AS patterns
2900
- UNWIND patterns AS pattern
2901
- RETURN pattern
2902
- `);
2903
- await match.run();
2904
- const results = match.results;
2905
- expect(results.length).toBe(10);
2906
- // Index 0: Person 1 zero-hop - pattern = [node1] (single node)
2907
- expect(results[0].pattern.length).toBe(1);
2908
- expect(results[0].pattern[0].id).toBe(1);
2909
-
2910
- // Index 1: Person 1 -> Person 2 (1-hop)
2911
- expect(results[1].pattern.length).toBe(3);
2912
-
2913
- // Index 2: Person 1 -> Person 2 -> Person 3 (2-hop)
2914
- expect(results[2].pattern.length).toBe(5);
2915
-
2916
- // Index 3: Person 1 -> Person 2 -> Person 3 -> Person 4 (3-hop)
2917
- expect(results[3].pattern.length).toBe(7);
2918
-
2919
- // Index 4: Person 2 zero-hop - pattern = [node2]
2920
- expect(results[4].pattern.length).toBe(1);
2921
- expect(results[4].pattern[0].id).toBe(2);
2922
-
2923
- // Index 5: Person 2 -> Person 3 (1-hop)
2924
- expect(results[5].pattern.length).toBe(3);
2925
-
2926
- // Index 6: Person 2 -> Person 3 -> Person 4 (2-hop)
2927
- expect(results[6].pattern.length).toBe(5);
2928
-
2929
- // Index 7: Person 3 zero-hop - pattern = [node3]
2930
- expect(results[7].pattern.length).toBe(1);
2931
- expect(results[7].pattern[0].id).toBe(3);
2932
-
2933
- // Index 8: Person 3 -> Person 4 (1-hop)
2934
- expect(results[8].pattern.length).toBe(3);
2935
-
2936
- // Index 9: Person 4 zero-hop - pattern = [node4]
2937
- expect(results[9].pattern.length).toBe(1);
2938
- expect(results[9].pattern[0].id).toBe(4);
2939
- });
2940
-
2941
- // ============================================================
2942
- // Add operator tests
2943
- // ============================================================
2944
-
2945
- test("Test add two integers", async () => {
2946
- const runner = new Runner("return 1 + 2 as result");
2947
- await runner.run();
2948
- const results = runner.results;
2949
- expect(results.length).toBe(1);
2950
- expect(results[0]).toEqual({ result: 3 });
2951
- });
2952
-
2953
- test("Test add negative number", async () => {
2954
- const runner = new Runner("return -3 + 7 as result");
2955
- await runner.run();
2956
- const results = runner.results;
2957
- expect(results.length).toBe(1);
2958
- expect(results[0]).toEqual({ result: 4 });
2959
- });
2960
-
2961
- test("Test add to negative result", async () => {
2962
- const runner = new Runner("return 0 - 10 + 4 as result");
2963
- await runner.run();
2964
- const results = runner.results;
2965
- expect(results.length).toBe(1);
2966
- expect(results[0]).toEqual({ result: -6 });
2967
- });
2968
-
2969
- test("Test add zero", async () => {
2970
- const runner = new Runner("return 42 + 0 as result");
2971
- await runner.run();
2972
- const results = runner.results;
2973
- expect(results.length).toBe(1);
2974
- expect(results[0]).toEqual({ result: 42 });
2975
- });
2976
-
2977
- test("Test add floating point numbers", async () => {
2978
- const runner = new Runner("return 1.5 + 2.3 as result");
2979
- await runner.run();
2980
- const results = runner.results;
2981
- expect(results.length).toBe(1);
2982
- expect(results[0].result).toBeCloseTo(3.8);
2983
- });
2984
-
2985
- test("Test add integer and float", async () => {
2986
- const runner = new Runner("return 1 + 0.5 as result");
2987
- await runner.run();
2988
- const results = runner.results;
2989
- expect(results.length).toBe(1);
2990
- expect(results[0].result).toBeCloseTo(1.5);
2991
- });
2992
-
2993
- test("Test add strings", async () => {
2994
- const runner = new Runner('return "hello" + " world" as result');
2995
- await runner.run();
2996
- const results = runner.results;
2997
- expect(results.length).toBe(1);
2998
- expect(results[0]).toEqual({ result: "hello world" });
2999
- });
3000
-
3001
- test("Test add empty strings", async () => {
3002
- const runner = new Runner('return "" + "" as result');
3003
- await runner.run();
3004
- const results = runner.results;
3005
- expect(results.length).toBe(1);
3006
- expect(results[0]).toEqual({ result: "" });
3007
- });
3008
-
3009
- test("Test add string and empty string", async () => {
3010
- const runner = new Runner('return "hello" + "" as result');
3011
- await runner.run();
3012
- const results = runner.results;
3013
- expect(results.length).toBe(1);
3014
- expect(results[0]).toEqual({ result: "hello" });
3015
- });
3016
-
3017
- test("Test add two lists", async () => {
3018
- const runner = new Runner("return [1, 2] + [3, 4] as result");
3019
- await runner.run();
3020
- const results = runner.results;
3021
- expect(results.length).toBe(1);
3022
- expect(results[0]).toEqual({ result: [1, 2, 3, 4] });
3023
- });
3024
-
3025
- test("Test add empty list to list", async () => {
3026
- const runner = new Runner("return [1, 2, 3] + [] as result");
3027
- await runner.run();
3028
- const results = runner.results;
3029
- expect(results.length).toBe(1);
3030
- expect(results[0]).toEqual({ result: [1, 2, 3] });
3031
- });
3032
-
3033
- test("Test add two empty lists", async () => {
3034
- const runner = new Runner("return [] + [] as result");
3035
- await runner.run();
3036
- const results = runner.results;
3037
- expect(results.length).toBe(1);
3038
- expect(results[0]).toEqual({ result: [] });
3039
- });
3040
-
3041
- test("Test add lists with mixed types", async () => {
3042
- const runner = new Runner('return [1, "a"] + [2, "b"] as result');
3043
- await runner.run();
3044
- const results = runner.results;
3045
- expect(results.length).toBe(1);
3046
- expect(results[0]).toEqual({ result: [1, "a", 2, "b"] });
3047
- });
3048
-
3049
- test("Test add chained three numbers", async () => {
3050
- const runner = new Runner("return 1 + 2 + 3 as result");
3051
- await runner.run();
3052
- const results = runner.results;
3053
- expect(results.length).toBe(1);
3054
- expect(results[0]).toEqual({ result: 6 });
3055
- });
3056
-
3057
- test("Test add chained multiple numbers", async () => {
3058
- const runner = new Runner("return 10 + 20 + 30 + 40 as result");
3059
- await runner.run();
3060
- const results = runner.results;
3061
- expect(results.length).toBe(1);
3062
- expect(results[0]).toEqual({ result: 100 });
3063
- });
3064
-
3065
- test("Test add large numbers", async () => {
3066
- const runner = new Runner("return 1000000 + 2000000 as result");
3067
- await runner.run();
3068
- const results = runner.results;
3069
- expect(results.length).toBe(1);
3070
- expect(results[0]).toEqual({ result: 3000000 });
3071
- });
3072
-
3073
- test("Test add with unwind", async () => {
3074
- const runner = new Runner("unwind [1, 2, 3] as x return x + 10 as result");
3075
- await runner.run();
3076
- const results = runner.results;
3077
- expect(results.length).toBe(3);
3078
- expect(results[0]).toEqual({ result: 11 });
3079
- expect(results[1]).toEqual({ result: 12 });
3080
- expect(results[2]).toEqual({ result: 13 });
3081
- });
3082
-
3083
- test("Test add with multiple return expressions", async () => {
3084
- const runner = new Runner("return 1 + 2 as sum1, 3 + 4 as sum2, 5 + 6 as sum3");
3085
- await runner.run();
3086
- const results = runner.results;
3087
- expect(results.length).toBe(1);
3088
- expect(results[0]).toEqual({ sum1: 3, sum2: 7, sum3: 11 });
3089
- });
3090
-
3091
- test("Test add mixed with other operators", async () => {
3092
- const runner = new Runner("return 2 + 3 * 4 as result");
3093
- await runner.run();
3094
- const results = runner.results;
3095
- expect(results.length).toBe(1);
3096
- expect(results[0]).toEqual({ result: 14 });
3097
- });
3098
-
3099
- test("Test add with parentheses", async () => {
3100
- const runner = new Runner("return (2 + 3) * 4 as result");
3101
- await runner.run();
3102
- const results = runner.results;
3103
- expect(results.length).toBe(1);
3104
- expect(results[0]).toEqual({ result: 20 });
3105
- });
3106
-
3107
- test("Test add nested lists", async () => {
3108
- const runner = new Runner("return [[1, 2]] + [[3, 4]] as result");
3109
- await runner.run();
3110
- const results = runner.results;
3111
- expect(results.length).toBe(1);
3112
- expect(results[0]).toEqual({
3113
- result: [
3114
- [1, 2],
3115
- [3, 4],
3116
- ],
3117
- });
3118
- });
3119
-
3120
- test("Test add with with clause", async () => {
3121
- const runner = new Runner("with 5 as a, 10 as b return a + b as result");
3122
- await runner.run();
3123
- const results = runner.results;
3124
- expect(results.length).toBe(1);
3125
- expect(results[0]).toEqual({ result: 15 });
3126
- });
3127
-
3128
- // ============================================================
3129
- // UNION and UNION ALL tests
3130
- // ============================================================
3131
-
3132
- test("Test UNION with simple values", async () => {
3133
- const runner = new Runner("WITH 1 AS x RETURN x UNION WITH 2 AS x RETURN x");
3134
- await runner.run();
3135
- const results = runner.results;
3136
- expect(results.length).toBe(2);
3137
- expect(results).toEqual([{ x: 1 }, { x: 2 }]);
3138
- });
3139
-
3140
- test("Test UNION removes duplicates", async () => {
3141
- const runner = new Runner("WITH 1 AS x RETURN x UNION WITH 1 AS x RETURN x");
3142
- await runner.run();
3143
- const results = runner.results;
3144
- expect(results.length).toBe(1);
3145
- expect(results).toEqual([{ x: 1 }]);
3146
- });
3147
-
3148
- test("Test UNION ALL keeps duplicates", async () => {
3149
- const runner = new Runner("WITH 1 AS x RETURN x UNION ALL WITH 1 AS x RETURN x");
3150
- await runner.run();
3151
- const results = runner.results;
3152
- expect(results.length).toBe(2);
3153
- expect(results).toEqual([{ x: 1 }, { x: 1 }]);
3154
- });
3155
-
3156
- test("Test UNION with multiple columns", async () => {
3157
- const runner = new Runner(
3158
- "WITH 1 AS a, 'hello' AS b RETURN a, b UNION WITH 2 AS a, 'world' AS b RETURN a, b"
3159
- );
3160
- await runner.run();
3161
- const results = runner.results;
3162
- expect(results.length).toBe(2);
3163
- expect(results).toEqual([
3164
- { a: 1, b: "hello" },
3165
- { a: 2, b: "world" },
3166
- ]);
3167
- });
3168
-
3169
- test("Test UNION ALL with multiple columns", async () => {
3170
- const runner = new Runner(
3171
- "WITH 1 AS a RETURN a UNION ALL WITH 2 AS a RETURN a UNION ALL WITH 3 AS a RETURN a"
3172
- );
3173
- await runner.run();
3174
- const results = runner.results;
3175
- expect(results.length).toBe(3);
3176
- expect(results).toEqual([{ a: 1 }, { a: 2 }, { a: 3 }]);
3177
- });
3178
-
3179
- test("Test chained UNION removes duplicates across all branches", async () => {
3180
- const runner = new Runner(
3181
- "WITH 1 AS x RETURN x UNION WITH 2 AS x RETURN x UNION WITH 1 AS x RETURN x"
3182
- );
3183
- await runner.run();
3184
- const results = runner.results;
3185
- expect(results.length).toBe(2);
3186
- expect(results).toEqual([{ x: 1 }, { x: 2 }]);
3187
- });
3188
-
3189
- test("Test UNION with unwind", async () => {
3190
- const runner = new Runner("UNWIND [1, 2] AS x RETURN x UNION UNWIND [3, 4] AS x RETURN x");
3191
- await runner.run();
3192
- const results = runner.results;
3193
- expect(results.length).toBe(4);
3194
- expect(results).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }]);
3195
- });
3196
-
3197
- test("Test UNION with mismatched columns throws error", async () => {
3198
- const runner = new Runner("WITH 1 AS x RETURN x UNION WITH 2 AS y RETURN y");
3199
- await expect(runner.run()).rejects.toThrow(
3200
- "All sub queries in a UNION must have the same return column names"
3201
- );
3202
- });
3203
-
3204
- test("Test UNION with empty left side", async () => {
3205
- const runner = new Runner("UNWIND [] AS x RETURN x UNION WITH 1 AS x RETURN x");
3206
- await runner.run();
3207
- const results = runner.results;
3208
- expect(results.length).toBe(1);
3209
- expect(results).toEqual([{ x: 1 }]);
3210
- });
3211
-
3212
- test("Test UNION with empty right side", async () => {
3213
- const runner = new Runner("WITH 1 AS x RETURN x UNION UNWIND [] AS x RETURN x");
3214
- await runner.run();
3215
- const results = runner.results;
3216
- expect(results.length).toBe(1);
3217
- expect(results).toEqual([{ x: 1 }]);
3218
- });
3219
-
3220
- test("Test language name hits query with virtual graph", async () => {
3221
- // Create Language nodes
3222
- await new Runner(`
3223
- CREATE VIRTUAL (:Language) AS {
3224
- UNWIND [
3225
- {id: 1, name: 'Python'},
3226
- {id: 2, name: 'JavaScript'},
3227
- {id: 3, name: 'TypeScript'}
3228
- ] AS record
3229
- RETURN record.id AS id, record.name AS name
3230
- }
3231
- `).run();
3232
-
3233
- // Create Chat nodes with messages
3234
- await new Runner(`
3235
- CREATE VIRTUAL (:Chat) AS {
3236
- UNWIND [
3237
- {id: 1, name: 'Dev Discussion', messages: [
3238
- {From: 'Alice', SentDateTime: '2025-01-01T10:00:00', Content: 'I love Python and JavaScript'},
3239
- {From: 'Bob', SentDateTime: '2025-01-01T10:05:00', Content: 'What languages do you prefer?'}
3240
- ]},
3241
- {id: 2, name: 'General', messages: [
3242
- {From: 'Charlie', SentDateTime: '2025-01-02T09:00:00', Content: 'The weather is nice today'},
3243
- {From: 'Alice', SentDateTime: '2025-01-02T09:05:00', Content: 'TypeScript is great for language tooling'}
3244
- ]}
3245
- ] AS record
3246
- RETURN record.id AS id, record.name AS name, record.messages AS messages
3247
- }
3248
- `).run();
3249
-
3250
- // Create User nodes
3251
- await new Runner(`
3252
- CREATE VIRTUAL (:User) AS {
3253
- UNWIND [
3254
- {id: 1, displayName: 'Alice'},
3255
- {id: 2, displayName: 'Bob'},
3256
- {id: 3, displayName: 'Charlie'}
3257
- ] AS record
3258
- RETURN record.id AS id, record.displayName AS displayName
3259
- }
3260
- `).run();
3261
-
3262
- // Create PARTICIPATES_IN relationships
3263
- await new Runner(`
3264
- CREATE VIRTUAL (:User)-[:PARTICIPATES_IN]-(:Chat) AS {
3265
- UNWIND [
3266
- {left_id: 1, right_id: 1},
3267
- {left_id: 2, right_id: 1},
3268
- {left_id: 3, right_id: 2},
3269
- {left_id: 1, right_id: 2}
3270
- ] AS record
3271
- RETURN record.left_id AS left_id, record.right_id AS right_id
3272
- }
3273
- `).run();
3274
-
3275
- // Run the original query (using 'sender' alias since 'from' is a reserved keyword)
3276
- const runner = new Runner(`
3277
- MATCH (l:Language)
3278
- WITH collect(distinct l.name) AS langs
3279
- MATCH (c:Chat)
3280
- UNWIND c.messages AS msg
3281
- WITH c, msg, langs,
3282
- sum(lang IN langs | 1 where toLower(msg.Content) CONTAINS toLower(lang)) AS langNameHits
3283
- WHERE toLower(msg.Content) CONTAINS "language"
3284
- OR toLower(msg.Content) CONTAINS "languages"
3285
- OR langNameHits > 0
3286
- OPTIONAL MATCH (u:User)-[:PARTICIPATES_IN]->(c)
3287
- RETURN
3288
- c.name AS chat,
3289
- collect(distinct u.displayName) AS participants,
3290
- msg.From AS sender,
3291
- msg.SentDateTime AS sentDateTime,
3292
- msg.Content AS message
3293
- `);
3294
- await runner.run();
3295
- const results = runner.results;
3296
-
3297
- // Messages that mention a language name or the word "language(s)":
3298
- // 1. "I love Python and JavaScript" - langNameHits=2 (matches Python and JavaScript)
3299
- // 2. "What languages do you prefer?" - contains "languages"
3300
- // 3. "TypeScript is great for language tooling" - langNameHits=1, also contains "language"
3301
- expect(results.length).toBe(3);
3302
- expect(results[0].chat).toBe("Dev Discussion");
3303
- expect(results[0].message).toBe("I love Python and JavaScript");
3304
- expect(results[0].sender).toBe("Alice");
3305
- expect(results[1].chat).toBe("Dev Discussion");
3306
- expect(results[1].message).toBe("What languages do you prefer?");
3307
- expect(results[1].sender).toBe("Bob");
3308
- expect(results[2].chat).toBe("General");
3309
- expect(results[2].message).toBe("TypeScript is great for language tooling");
3310
- expect(results[2].sender).toBe("Alice");
3311
- });
3312
-
3313
- test("Test sum with empty collected array", async () => {
3314
- // Reproduces the original bug: collect on empty input should yield []
3315
- // and sum over that empty array should return 0, not throw
3316
- const runner = new Runner(`
3317
- UNWIND [] AS lang
3318
- WITH collect(distinct lang) AS langs
3319
- UNWIND ['hello', 'world'] AS msg
3320
- WITH msg, langs, sum(l IN langs | 1 where toLower(msg) CONTAINS toLower(l)) AS hits
3321
- RETURN msg, hits
3322
- `);
3323
- await runner.run();
3324
- const results = runner.results;
3325
- expect(results.length).toBe(2);
3326
- expect(results[0]).toEqual({ msg: "hello", hits: 0 });
3327
- expect(results[1]).toEqual({ msg: "world", hits: 0 });
3328
- });
3329
-
3330
- test("Test sum where all elements filtered returns 0", async () => {
3331
- const runner = new Runner("RETURN sum(n in [1, 2, 3] | n where n > 100) as sum");
3332
- await runner.run();
3333
- const results = runner.results;
3334
- expect(results.length).toBe(1);
3335
- expect(results[0]).toEqual({ sum: 0 });
3336
- });
3337
-
3338
- test("Test sum over empty array returns 0", async () => {
3339
- const runner = new Runner("WITH [] AS arr RETURN sum(n in arr | n) as sum");
3340
- await runner.run();
3341
- const results = runner.results;
3342
- expect(results.length).toBe(1);
3343
- expect(results[0]).toEqual({ sum: 0 });
3344
- });
3345
-
3346
- test("Test match with ORed relationship types", async () => {
3347
- await new Runner(`
3348
- CREATE VIRTUAL (:Person) AS {
3349
- unwind [
3350
- {id: 1, name: 'Alice'},
3351
- {id: 2, name: 'Bob'},
3352
- {id: 3, name: 'Charlie'}
3353
- ] as record
3354
- RETURN record.id as id, record.name as name
3355
- }
3356
- `).run();
3357
- await new Runner(`
3358
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
3359
- unwind [
3360
- {left_id: 1, right_id: 2}
3361
- ] as record
3362
- RETURN record.left_id as left_id, record.right_id as right_id
3363
- }
3364
- `).run();
3365
- await new Runner(`
3366
- CREATE VIRTUAL (:Person)-[:FOLLOWS]-(:Person) AS {
3367
- unwind [
3368
- {left_id: 2, right_id: 3}
3369
- ] as record
3370
- RETURN record.left_id as left_id, record.right_id as right_id
3371
- }
3372
- `).run();
3373
- const match = new Runner(`
3374
- MATCH (a:Person)-[:KNOWS|FOLLOWS]->(b:Person)
3375
- RETURN a.name AS name1, b.name AS name2
3376
- `);
3377
- await match.run();
3378
- const results = match.results;
3379
- expect(results.length).toBe(2);
3380
- expect(results[0]).toEqual({ name1: "Alice", name2: "Bob" });
3381
- expect(results[1]).toEqual({ name1: "Bob", name2: "Charlie" });
3382
- });
3383
-
3384
- test("Test match with ORed relationship types with optional colon syntax", async () => {
3385
- await new Runner(`
3386
- CREATE VIRTUAL (:Animal) AS {
3387
- unwind [
3388
- {id: 1, name: 'Cat'},
3389
- {id: 2, name: 'Dog'},
3390
- {id: 3, name: 'Fish'}
3391
- ] as record
3392
- RETURN record.id as id, record.name as name
3393
- }
3394
- `).run();
3395
- await new Runner(`
3396
- CREATE VIRTUAL (:Animal)-[:CHASES]-(:Animal) AS {
3397
- unwind [
3398
- {left_id: 1, right_id: 2}
3399
- ] as record
3400
- RETURN record.left_id as left_id, record.right_id as right_id
3401
- }
3402
- `).run();
3403
- await new Runner(`
3404
- CREATE VIRTUAL (:Animal)-[:EATS]-(:Animal) AS {
3405
- unwind [
3406
- {left_id: 1, right_id: 3}
3407
- ] as record
3408
- RETURN record.left_id as left_id, record.right_id as right_id
3409
- }
3410
- `).run();
3411
- const match = new Runner(`
3412
- MATCH (a:Animal)-[:CHASES|:EATS]->(b:Animal)
3413
- RETURN a.name AS name1, b.name AS name2
3414
- `);
3415
- await match.run();
3416
- const results = match.results;
3417
- expect(results.length).toBe(2);
3418
- expect(results[0]).toEqual({ name1: "Cat", name2: "Dog" });
3419
- expect(results[1]).toEqual({ name1: "Cat", name2: "Fish" });
3420
- });
3421
-
3422
- test("Test match with ORed relationship types returns correct type in relationship variable", async () => {
3423
- await new Runner(`
3424
- CREATE VIRTUAL (:City) AS {
3425
- unwind [
3426
- {id: 1, name: 'NYC'},
3427
- {id: 2, name: 'LA'},
3428
- {id: 3, name: 'Chicago'}
3429
- ] as record
3430
- RETURN record.id as id, record.name as name
3431
- }
3432
- `).run();
3433
- await new Runner(`
3434
- CREATE VIRTUAL (:City)-[:FLIGHT]-(:City) AS {
3435
- unwind [
3436
- {left_id: 1, right_id: 2, airline: 'Delta'}
3437
- ] as record
3438
- RETURN record.left_id as left_id, record.right_id as right_id, record.airline as airline
3439
- }
3440
- `).run();
3441
- await new Runner(`
3442
- CREATE VIRTUAL (:City)-[:TRAIN]-(:City) AS {
3443
- unwind [
3444
- {left_id: 1, right_id: 3, line: 'Amtrak'}
3445
- ] as record
3446
- RETURN record.left_id as left_id, record.right_id as right_id, record.line as line
3447
- }
3448
- `).run();
3449
- const match = new Runner(`
3450
- MATCH (a:City)-[r:FLIGHT|TRAIN]->(b:City)
3451
- RETURN a.name AS from, b.name AS to, r.type AS type
3452
- `);
3453
- await match.run();
3454
- const results = match.results;
3455
- expect(results.length).toBe(2);
3456
- expect(results[0]).toEqual({ from: "NYC", to: "LA", type: "FLIGHT" });
3457
- expect(results[1]).toEqual({ from: "NYC", to: "Chicago", type: "TRAIN" });
3458
- });
3459
-
3460
- test("Test relationship properties can be accessed directly via dot notation", async () => {
3461
- await new Runner(`
3462
- CREATE VIRTUAL (:City) AS {
3463
- unwind [
3464
- {id: 1, name: 'NYC'},
3465
- {id: 2, name: 'LA'},
3466
- {id: 3, name: 'Chicago'}
3467
- ] as record
3468
- RETURN record.id as id, record.name as name
3469
- }
3470
- `).run();
3471
- await new Runner(`
3472
- CREATE VIRTUAL (:City)-[:FLIGHT]-(:City) AS {
3473
- unwind [
3474
- {left_id: 1, right_id: 2, airline: 'Delta', duration: 5}
3475
- ] as record
3476
- RETURN record.left_id as left_id, record.right_id as right_id, record.airline as airline, record.duration as duration
3477
- }
3478
- `).run();
3479
- const match = new Runner(`
3480
- MATCH (a:City)-[r:FLIGHT]->(b:City)
3481
- RETURN a.name AS from, b.name AS to, r.airline AS airline, r.duration AS duration
3482
- `);
3483
- await match.run();
3484
- const results = match.results;
3485
- expect(results.length).toBe(1);
3486
- expect(results[0]).toEqual({ from: "NYC", to: "LA", airline: "Delta", duration: 5 });
3487
- });
3488
-
3489
- test("Test relationship properties accessible via both direct access and properties()", async () => {
3490
- await new Runner(`
3491
- CREATE VIRTUAL (:Person) AS {
3492
- unwind [
3493
- {id: 1, name: 'Alice'},
3494
- {id: 2, name: 'Bob'}
3495
- ] as record
3496
- RETURN record.id as id, record.name as name
3497
- }
3498
- `).run();
3499
- await new Runner(`
3500
- CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
3501
- unwind [
3502
- {left_id: 1, right_id: 2, since: 2020, strength: 'strong'}
3503
- ] as record
3504
- RETURN record.left_id as left_id, record.right_id as right_id, record.since as since, record.strength as strength
3505
- }
3506
- `).run();
3507
- const match = new Runner(`
3508
- MATCH (a:Person)-[r:KNOWS]->(b:Person)
3509
- RETURN a.name AS from, b.name AS to, r.since AS since, r.strength AS strength, properties(r).since AS propSince
3510
- `);
3511
- await match.run();
3512
- const results = match.results;
3513
- expect(results.length).toBe(1);
3514
- expect(results[0]).toEqual({
3515
- from: "Alice",
3516
- to: "Bob",
3517
- since: 2020,
3518
- strength: "strong",
3519
- propSince: 2020,
3520
- });
3521
- });
3522
-
3523
- test("Test coalesce returns first non-null value", async () => {
3524
- const runner = new Runner("RETURN coalesce(null, null, 'hello', 'world') as result");
3525
- await runner.run();
3526
- const results = runner.results;
3527
- expect(results.length).toBe(1);
3528
- expect(results[0]).toEqual({ result: "hello" });
3529
- });
3530
-
3531
- test("Test coalesce returns first argument when not null", async () => {
3532
- const runner = new Runner("RETURN coalesce('first', 'second') as result");
3533
- await runner.run();
3534
- const results = runner.results;
3535
- expect(results.length).toBe(1);
3536
- expect(results[0]).toEqual({ result: "first" });
3537
- });
3538
-
3539
- test("Test coalesce returns null when all arguments are null", async () => {
3540
- const runner = new Runner("RETURN coalesce(null, null, null) as result");
3541
- await runner.run();
3542
- const results = runner.results;
3543
- expect(results.length).toBe(1);
3544
- expect(results[0]).toEqual({ result: null });
3545
- });
3546
-
3547
- test("Test coalesce with single non-null argument", async () => {
3548
- const runner = new Runner("RETURN coalesce(42) as result");
3549
- await runner.run();
3550
- const results = runner.results;
3551
- expect(results.length).toBe(1);
3552
- expect(results[0]).toEqual({ result: 42 });
3553
- });
3554
-
3555
- test("Test coalesce with mixed types", async () => {
3556
- const runner = new Runner("RETURN coalesce(null, 42, 'hello') as result");
3557
- await runner.run();
3558
- const results = runner.results;
3559
- expect(results.length).toBe(1);
3560
- expect(results[0]).toEqual({ result: 42 });
3561
- });
3562
-
3563
- test("Test coalesce with property access", async () => {
3564
- const runner = new Runner(
3565
- "WITH {name: 'Alice'} AS person RETURN coalesce(person.nickname, person.name) as result"
3566
- );
3567
- await runner.run();
3568
- const results = runner.results;
3569
- expect(results.length).toBe(1);
3570
- expect(results[0]).toEqual({ result: "Alice" });
3571
- });
3572
-
3573
- // ============================================================
3574
- // Temporal / Time Functions
3575
- // ============================================================
3576
-
3577
- test("Test datetime() returns current datetime object", async () => {
3578
- const before = Date.now();
3579
- const runner = new Runner("RETURN datetime() AS dt");
3580
- await runner.run();
3581
- const after = Date.now();
3582
- const results = runner.results;
3583
- expect(results.length).toBe(1);
3584
- const dt = results[0].dt;
3585
- expect(dt).toBeDefined();
3586
- expect(typeof dt.year).toBe("number");
3587
- expect(typeof dt.month).toBe("number");
3588
- expect(typeof dt.day).toBe("number");
3589
- expect(typeof dt.hour).toBe("number");
3590
- expect(typeof dt.minute).toBe("number");
3591
- expect(typeof dt.second).toBe("number");
3592
- expect(typeof dt.millisecond).toBe("number");
3593
- expect(typeof dt.epochMillis).toBe("number");
3594
- expect(typeof dt.epochSeconds).toBe("number");
3595
- expect(typeof dt.dayOfWeek).toBe("number");
3596
- expect(typeof dt.dayOfYear).toBe("number");
3597
- expect(typeof dt.quarter).toBe("number");
3598
- expect(typeof dt.formatted).toBe("string");
3599
- // epochMillis should be between before and after
3600
- expect(dt.epochMillis).toBeGreaterThanOrEqual(before);
3601
- expect(dt.epochMillis).toBeLessThanOrEqual(after);
3602
- });
3603
-
3604
- test("Test datetime() with ISO string argument", async () => {
3605
- const runner = new Runner("RETURN datetime('2025-06-15T12:30:45.123Z') AS dt");
3606
- await runner.run();
3607
- const results = runner.results;
3608
- expect(results.length).toBe(1);
3609
- const dt = results[0].dt;
3610
- expect(dt.year).toBe(2025);
3611
- expect(dt.month).toBe(6);
3612
- expect(dt.day).toBe(15);
3613
- expect(dt.hour).toBe(12);
3614
- expect(dt.minute).toBe(30);
3615
- expect(dt.second).toBe(45);
3616
- expect(dt.millisecond).toBe(123);
3617
- expect(dt.formatted).toBe("2025-06-15T12:30:45.123Z");
3618
- });
3619
-
3620
- test("Test datetime() property access", async () => {
3621
- const runner = new Runner(
3622
- "WITH datetime('2025-06-15T12:30:45.123Z') AS dt RETURN dt.year AS year, dt.month AS month, dt.day AS day"
3623
- );
3624
- await runner.run();
3625
- const results = runner.results;
3626
- expect(results.length).toBe(1);
3627
- expect(results[0]).toEqual({ year: 2025, month: 6, day: 15 });
3628
- });
3629
-
3630
- test("Test date() returns current date object", async () => {
3631
- const runner = new Runner("RETURN date() AS d");
3632
- await runner.run();
3633
- const results = runner.results;
3634
- expect(results.length).toBe(1);
3635
- const d = results[0].d;
3636
- expect(d).toBeDefined();
3637
- expect(typeof d.year).toBe("number");
3638
- expect(typeof d.month).toBe("number");
3639
- expect(typeof d.day).toBe("number");
3640
- expect(typeof d.epochMillis).toBe("number");
3641
- expect(typeof d.dayOfWeek).toBe("number");
3642
- expect(typeof d.dayOfYear).toBe("number");
3643
- expect(typeof d.quarter).toBe("number");
3644
- expect(typeof d.formatted).toBe("string");
3645
- // Should not have time fields
3646
- expect(d.hour).toBeUndefined();
3647
- expect(d.minute).toBeUndefined();
3648
- });
3649
-
3650
- test("Test date() with ISO date string", async () => {
3651
- const runner = new Runner("RETURN date('2025-06-15') AS d");
3652
- await runner.run();
3653
- const results = runner.results;
3654
- expect(results.length).toBe(1);
3655
- const d = results[0].d;
3656
- expect(d.year).toBe(2025);
3657
- expect(d.month).toBe(6);
3658
- expect(d.day).toBe(15);
3659
- expect(d.formatted).toBe("2025-06-15");
3660
- });
3661
-
3662
- test("Test date() dayOfWeek and quarter", async () => {
3663
- // 2025-06-15 is a Sunday
3664
- const runner = new Runner("RETURN date('2025-06-15') AS d");
3665
- await runner.run();
3666
- const d = runner.results[0].d;
3667
- expect(d.dayOfWeek).toBe(7); // Sunday = 7 in ISO
3668
- expect(d.quarter).toBe(2); // June = Q2
3669
- });
3670
-
3671
- test("Test time() returns current UTC time", async () => {
3672
- const runner = new Runner("RETURN time() AS t");
3673
- await runner.run();
3674
- const results = runner.results;
3675
- expect(results.length).toBe(1);
3676
- const t = results[0].t;
3677
- expect(typeof t.hour).toBe("number");
3678
- expect(typeof t.minute).toBe("number");
3679
- expect(typeof t.second).toBe("number");
3680
- expect(typeof t.millisecond).toBe("number");
3681
- expect(typeof t.formatted).toBe("string");
3682
- expect(t.formatted).toMatch(/Z$/); // UTC time ends in Z
3683
- });
3684
-
3685
- test("Test localtime() returns current local time", async () => {
3686
- const runner = new Runner("RETURN localtime() AS t");
3687
- await runner.run();
3688
- const results = runner.results;
3689
- expect(results.length).toBe(1);
3690
- const t = results[0].t;
3691
- expect(typeof t.hour).toBe("number");
3692
- expect(typeof t.minute).toBe("number");
3693
- expect(typeof t.second).toBe("number");
3694
- expect(typeof t.millisecond).toBe("number");
3695
- expect(typeof t.formatted).toBe("string");
3696
- expect(t.formatted).not.toMatch(/Z$/); // Local time does not end in Z
3697
- });
3698
-
3699
- test("Test localdatetime() returns current local datetime", async () => {
3700
- const runner = new Runner("RETURN localdatetime() AS dt");
3701
- await runner.run();
3702
- const results = runner.results;
3703
- expect(results.length).toBe(1);
3704
- const dt = results[0].dt;
3705
- expect(typeof dt.year).toBe("number");
3706
- expect(typeof dt.month).toBe("number");
3707
- expect(typeof dt.day).toBe("number");
3708
- expect(typeof dt.hour).toBe("number");
3709
- expect(typeof dt.minute).toBe("number");
3710
- expect(typeof dt.second).toBe("number");
3711
- expect(typeof dt.millisecond).toBe("number");
3712
- expect(typeof dt.epochMillis).toBe("number");
3713
- expect(typeof dt.formatted).toBe("string");
3714
- expect(dt.formatted).not.toMatch(/Z$/); // Local datetime does not end in Z
3715
- });
3716
-
3717
- test("Test localdatetime() with string argument", async () => {
3718
- const runner = new Runner("RETURN localdatetime('2025-01-20T08:15:30.500Z') AS dt");
3719
- await runner.run();
3720
- const dt = runner.results[0].dt;
3721
- expect(typeof dt.year).toBe("number");
3722
- expect(typeof dt.hour).toBe("number");
3723
- expect(dt.epochMillis).toBeDefined();
3724
- });
3725
-
3726
- test("Test timestamp() returns epoch millis", async () => {
3727
- const before = Date.now();
3728
- const runner = new Runner("RETURN timestamp() AS ts");
3729
- await runner.run();
3730
- const after = Date.now();
3731
- const results = runner.results;
3732
- expect(results.length).toBe(1);
3733
- const ts = results[0].ts;
3734
- expect(typeof ts).toBe("number");
3735
- expect(ts).toBeGreaterThanOrEqual(before);
3736
- expect(ts).toBeLessThanOrEqual(after);
3737
- });
3738
-
3739
- test("Test datetime() epochMillis matches timestamp()", async () => {
3740
- const runner = new Runner(
3741
- "WITH datetime() AS dt, timestamp() AS ts RETURN dt.epochMillis AS dtMillis, ts AS tsMillis"
3742
- );
3743
- await runner.run();
3744
- const results = runner.results;
3745
- expect(results.length).toBe(1);
3746
- // They should be very close (within a few ms)
3747
- expect(Math.abs(results[0].dtMillis - results[0].tsMillis)).toBeLessThan(100);
3748
- });
3749
-
3750
- test("Test date() with property access in WHERE", async () => {
3751
- const runner = new Runner(
3752
- "UNWIND [1, 2, 3] AS x WITH x, date('2025-06-15') AS d WHERE d.quarter = 2 RETURN x"
3753
- );
3754
- await runner.run();
3755
- const results = runner.results;
3756
- expect(results.length).toBe(3); // All 3 pass through since Q2 = 2
3757
- });
3758
-
3759
- test("Test datetime() with map argument", async () => {
3760
- const runner = new Runner(
3761
- "RETURN datetime({year: 2024, month: 12, day: 25, hour: 10, minute: 30}) AS dt"
3762
- );
3763
- await runner.run();
3764
- const dt = runner.results[0].dt;
3765
- expect(dt.year).toBe(2024);
3766
- expect(dt.month).toBe(12);
3767
- expect(dt.day).toBe(25);
3768
- expect(dt.quarter).toBe(4); // December = Q4
3769
- });
3770
-
3771
- test("Test date() with map argument", async () => {
3772
- const runner = new Runner("RETURN date({year: 2025, month: 3, day: 1}) AS d");
3773
- await runner.run();
3774
- const d = runner.results[0].d;
3775
- expect(d.year).toBe(2025);
3776
- expect(d.month).toBe(3);
3777
- expect(d.day).toBe(1);
3778
- expect(d.quarter).toBe(1); // March = Q1
3779
- });
3780
-
3781
- test("Test id() function with node", async () => {
3782
- await new Runner(`
3783
- CREATE VIRTUAL (:Person) AS {
3784
- UNWIND [
3785
- {id: 1, name: 'Alice'},
3786
- {id: 2, name: 'Bob'}
3787
- ] AS record
3788
- RETURN record.id AS id, record.name AS name
3789
- }
3790
- `).run();
3791
- const match = new Runner(`
3792
- MATCH (n:Person)
3793
- RETURN id(n) AS nodeId
3794
- `);
3795
- await match.run();
3796
- const results = match.results;
3797
- expect(results.length).toBe(2);
3798
- expect(results[0]).toEqual({ nodeId: 1 });
3799
- expect(results[1]).toEqual({ nodeId: 2 });
3800
- });
3801
-
3802
- test("Test id() function with null", async () => {
3803
- const runner = new Runner("RETURN id(null) AS nodeId");
3804
- await runner.run();
3805
- const results = runner.results;
3806
- expect(results.length).toBe(1);
3807
- expect(results[0]).toEqual({ nodeId: null });
3808
- });
3809
-
3810
- test("Test id() function with relationship", async () => {
3811
- await new Runner(`
3812
- CREATE VIRTUAL (:City) AS {
3813
- UNWIND [
3814
- {id: 1, name: 'New York'},
3815
- {id: 2, name: 'Boston'}
3816
- ] AS record
3817
- RETURN record.id AS id, record.name AS name
3818
- }
3819
- `).run();
3820
- await new Runner(`
3821
- CREATE VIRTUAL (:City)-[:CONNECTED_TO]-(:City) AS {
3822
- UNWIND [
3823
- {left_id: 1, right_id: 2}
3824
- ] AS record
3825
- RETURN record.left_id AS left_id, record.right_id AS right_id
3826
- }
3827
- `).run();
3828
- const match = new Runner(`
3829
- MATCH (a:City)-[r:CONNECTED_TO]->(b:City)
3830
- RETURN id(r) AS relId
3831
- `);
3832
- await match.run();
3833
- const results = match.results;
3834
- expect(results.length).toBe(1);
3835
- expect(results[0]).toEqual({ relId: "CONNECTED_TO" });
3836
- });
3837
-
3838
- test("Test elementId() function with node", async () => {
3839
- await new Runner(`
3840
- CREATE VIRTUAL (:Person) AS {
3841
- UNWIND [
3842
- {id: 1, name: 'Alice'},
3843
- {id: 2, name: 'Bob'}
3844
- ] AS record
3845
- RETURN record.id AS id, record.name AS name
3846
- }
3847
- `).run();
3848
- const match = new Runner(`
3849
- MATCH (n:Person)
3850
- RETURN elementId(n) AS eid
3851
- `);
3852
- await match.run();
3853
- const results = match.results;
3854
- expect(results.length).toBe(2);
3855
- expect(results[0]).toEqual({ eid: "1" });
3856
- expect(results[1]).toEqual({ eid: "2" });
3857
- });
3858
-
3859
- test("Test elementId() function with null", async () => {
3860
- const runner = new Runner("RETURN elementId(null) AS eid");
3861
- await runner.run();
3862
- const results = runner.results;
3863
- expect(results.length).toBe(1);
3864
- expect(results[0]).toEqual({ eid: null });
3865
- });
3866
-
3867
- test("Test head() function", async () => {
3868
- const runner = new Runner("RETURN head([1, 2, 3]) AS h");
3869
- await runner.run();
3870
- expect(runner.results.length).toBe(1);
3871
- expect(runner.results[0]).toEqual({ h: 1 });
3872
- });
3873
-
3874
- test("Test head() function with empty list", async () => {
3875
- const runner = new Runner("RETURN head([]) AS h");
3876
- await runner.run();
3877
- expect(runner.results[0]).toEqual({ h: null });
3878
- });
3879
-
3880
- test("Test head() function with null", async () => {
3881
- const runner = new Runner("RETURN head(null) AS h");
3882
- await runner.run();
3883
- expect(runner.results[0]).toEqual({ h: null });
3884
- });
3885
-
3886
- test("Test tail() function", async () => {
3887
- const runner = new Runner("RETURN tail([1, 2, 3]) AS t");
3888
- await runner.run();
3889
- expect(runner.results.length).toBe(1);
3890
- expect(runner.results[0]).toEqual({ t: [2, 3] });
3891
- });
3892
-
3893
- test("Test tail() function with single element", async () => {
3894
- const runner = new Runner("RETURN tail([1]) AS t");
3895
- await runner.run();
3896
- expect(runner.results[0]).toEqual({ t: [] });
3897
- });
3898
-
3899
- test("Test tail() function with null", async () => {
3900
- const runner = new Runner("RETURN tail(null) AS t");
3901
- await runner.run();
3902
- expect(runner.results[0]).toEqual({ t: null });
3903
- });
3904
-
3905
- test("Test last() function", async () => {
3906
- const runner = new Runner("RETURN last([1, 2, 3]) AS l");
3907
- await runner.run();
3908
- expect(runner.results.length).toBe(1);
3909
- expect(runner.results[0]).toEqual({ l: 3 });
3910
- });
3911
-
3912
- test("Test last() function with empty list", async () => {
3913
- const runner = new Runner("RETURN last([]) AS l");
3914
- await runner.run();
3915
- expect(runner.results[0]).toEqual({ l: null });
3916
- });
3917
-
3918
- test("Test last() function with null", async () => {
3919
- const runner = new Runner("RETURN last(null) AS l");
3920
- await runner.run();
3921
- expect(runner.results[0]).toEqual({ l: null });
3922
- });
3923
-
3924
- test("Test toInteger() function with string", async () => {
3925
- const runner = new Runner('RETURN toInteger("42") AS i');
3926
- await runner.run();
3927
- expect(runner.results[0]).toEqual({ i: 42 });
3928
- });
3929
-
3930
- test("Test toInteger() function with float", async () => {
3931
- const runner = new Runner("RETURN toInteger(3.14) AS i");
3932
- await runner.run();
3933
- expect(runner.results[0]).toEqual({ i: 3 });
3934
- });
3935
-
3936
- test("Test toInteger() function with boolean", async () => {
3937
- const runner = new Runner("RETURN toInteger(true) AS i");
3938
- await runner.run();
3939
- expect(runner.results[0]).toEqual({ i: 1 });
3940
- });
3941
-
3942
- test("Test toInteger() function with null", async () => {
3943
- const runner = new Runner("RETURN toInteger(null) AS i");
3944
- await runner.run();
3945
- expect(runner.results[0]).toEqual({ i: null });
3946
- });
3947
-
3948
- test("Test toFloat() function with string", async () => {
3949
- const runner = new Runner('RETURN toFloat("3.14") AS f');
3950
- await runner.run();
3951
- expect(runner.results[0]).toEqual({ f: 3.14 });
3952
- });
3953
-
3954
- test("Test toFloat() function with integer", async () => {
3955
- const runner = new Runner("RETURN toFloat(42) AS f");
3956
- await runner.run();
3957
- expect(runner.results[0]).toEqual({ f: 42 });
3958
- });
3959
-
3960
- test("Test toFloat() function with boolean", async () => {
3961
- const runner = new Runner("RETURN toFloat(true) AS f");
3962
- await runner.run();
3963
- expect(runner.results[0]).toEqual({ f: 1.0 });
3964
- });
3965
-
3966
- test("Test toFloat() function with null", async () => {
3967
- const runner = new Runner("RETURN toFloat(null) AS f");
3968
- await runner.run();
3969
- expect(runner.results[0]).toEqual({ f: null });
3970
- });
3971
-
3972
- test("Test duration() with ISO 8601 string", async () => {
3973
- const runner = new Runner("RETURN duration('P1Y2M3DT4H5M6S') AS d");
3974
- await runner.run();
3975
- const d = runner.results[0].d;
3976
- expect(d.years).toBe(1);
3977
- expect(d.months).toBe(2);
3978
- expect(d.days).toBe(3);
3979
- expect(d.hours).toBe(4);
3980
- expect(d.minutes).toBe(5);
3981
- expect(d.seconds).toBe(6);
3982
- expect(d.totalMonths).toBe(14);
3983
- expect(d.formatted).toBe("P1Y2M3DT4H5M6S");
3984
- });
3985
-
3986
- test("Test duration() with map argument", async () => {
3987
- const runner = new Runner("RETURN duration({days: 14, hours: 16}) AS d");
3988
- await runner.run();
3989
- const d = runner.results[0].d;
3990
- expect(d.days).toBe(14);
3991
- expect(d.hours).toBe(16);
3992
- expect(d.totalDays).toBe(14);
3993
- expect(d.totalSeconds).toBe(57600);
3994
- });
3995
-
3996
- test("Test duration() with weeks", async () => {
3997
- const runner = new Runner("RETURN duration('P2W') AS d");
3998
- await runner.run();
3999
- const d = runner.results[0].d;
4000
- expect(d.weeks).toBe(2);
4001
- expect(d.days).toBe(14);
4002
- expect(d.totalDays).toBe(14);
4003
- });
4004
-
4005
- test("Test duration() with null", async () => {
4006
- const runner = new Runner("RETURN duration(null) AS d");
4007
- await runner.run();
4008
- expect(runner.results[0]).toEqual({ d: null });
4009
- });
4010
-
4011
- test("Test duration() with time only", async () => {
4012
- const runner = new Runner("RETURN duration('PT2H30M') AS d");
4013
- await runner.run();
4014
- const d = runner.results[0].d;
4015
- expect(d.hours).toBe(2);
4016
- expect(d.minutes).toBe(30);
4017
- expect(d.totalSeconds).toBe(9000);
4018
- expect(d.formatted).toBe("PT2H30M");
4019
- });
4020
-
4021
- // ORDER BY tests
4022
-
4023
- test("Test order by ascending", async () => {
4024
- const runner = new Runner("unwind [3, 1, 2] as x return x order by x");
4025
- await runner.run();
4026
- const results = runner.results;
4027
- expect(results.length).toBe(3);
4028
- expect(results[0]).toEqual({ x: 1 });
4029
- expect(results[1]).toEqual({ x: 2 });
4030
- expect(results[2]).toEqual({ x: 3 });
4031
- });
4032
-
4033
- test("Test order by descending", async () => {
4034
- const runner = new Runner("unwind [3, 1, 2] as x return x order by x desc");
4035
- await runner.run();
4036
- const results = runner.results;
4037
- expect(results.length).toBe(3);
4038
- expect(results[0]).toEqual({ x: 3 });
4039
- expect(results[1]).toEqual({ x: 2 });
4040
- expect(results[2]).toEqual({ x: 1 });
4041
- });
4042
-
4043
- test("Test order by ascending explicit", async () => {
4044
- const runner = new Runner("unwind [3, 1, 2] as x return x order by x asc");
4045
- await runner.run();
4046
- const results = runner.results;
4047
- expect(results.length).toBe(3);
4048
- expect(results[0]).toEqual({ x: 1 });
4049
- expect(results[1]).toEqual({ x: 2 });
4050
- expect(results[2]).toEqual({ x: 3 });
4051
- });
4052
-
4053
- test("Test order by with multiple fields", async () => {
4054
- const runner = new Runner(`
4055
- unwind [{name: 'Alice', age: 30}, {name: 'Bob', age: 25}, {name: 'Alice', age: 25}] as person
4056
- return person.name as name, person.age as age
4057
- order by name asc, age asc
4058
- `);
4059
- await runner.run();
4060
- const results = runner.results;
4061
- expect(results.length).toBe(3);
4062
- expect(results[0]).toEqual({ name: "Alice", age: 25 });
4063
- expect(results[1]).toEqual({ name: "Alice", age: 30 });
4064
- expect(results[2]).toEqual({ name: "Bob", age: 25 });
4065
- });
4066
-
4067
- test("Test order by with strings", async () => {
4068
- const runner = new Runner(
4069
- "unwind ['banana', 'apple', 'cherry'] as fruit return fruit order by fruit"
4070
- );
4071
- await runner.run();
4072
- const results = runner.results;
4073
- expect(results.length).toBe(3);
4074
- expect(results[0]).toEqual({ fruit: "apple" });
4075
- expect(results[1]).toEqual({ fruit: "banana" });
4076
- expect(results[2]).toEqual({ fruit: "cherry" });
4077
- });
4078
-
4079
- test("Test order by with aggregated return", async () => {
4080
- const runner = new Runner(`
4081
- unwind [1, 1, 2, 2, 3, 3] as x
4082
- return x, count(x) as cnt
4083
- order by x desc
4084
- `);
4085
- await runner.run();
4086
- const results = runner.results;
4087
- expect(results.length).toBe(3);
4088
- expect(results[0]).toEqual({ x: 3, cnt: 2 });
4089
- expect(results[1]).toEqual({ x: 2, cnt: 2 });
4090
- expect(results[2]).toEqual({ x: 1, cnt: 2 });
4091
- });
4092
-
4093
- test("Test order by with limit", async () => {
4094
- const runner = new Runner("unwind [3, 1, 4, 1, 5, 9, 2, 6] as x return x order by x limit 3");
4095
- await runner.run();
4096
- const results = runner.results;
4097
- expect(results.length).toBe(3);
4098
- expect(results[0]).toEqual({ x: 1 });
4099
- expect(results[1]).toEqual({ x: 1 });
4100
- expect(results[2]).toEqual({ x: 2 });
4101
- });
4102
-
4103
- test("Test order by with where", async () => {
4104
- const runner = new Runner(
4105
- "unwind [3, 1, 4, 1, 5, 9, 2, 6] as x return x where x > 2 order by x desc"
4106
- );
4107
- await runner.run();
4108
- const results = runner.results;
4109
- expect(results.length).toBe(5);
4110
- expect(results[0]).toEqual({ x: 9 });
4111
- expect(results[1]).toEqual({ x: 6 });
4112
- expect(results[2]).toEqual({ x: 5 });
4113
- expect(results[3]).toEqual({ x: 4 });
4114
- expect(results[4]).toEqual({ x: 3 });
4115
- });
4116
-
4117
- test("Test order by with property access expression", async () => {
4118
- const runner = new Runner(`
4119
- unwind [{name: 'Charlie', age: 30}, {name: 'Alice', age: 25}, {name: 'Bob', age: 35}] as person
4120
- return person.name as name, person.age as age
4121
- order by person.name asc
4122
- `);
4123
- await runner.run();
4124
- const results = runner.results;
4125
- expect(results.length).toBe(3);
4126
- expect(results[0]).toEqual({ name: "Alice", age: 25 });
4127
- expect(results[1]).toEqual({ name: "Bob", age: 35 });
4128
- expect(results[2]).toEqual({ name: "Charlie", age: 30 });
4129
- });
4130
-
4131
- test("Test order by with function expression", async () => {
4132
- const runner = new Runner(`
4133
- unwind ['BANANA', 'apple', 'Cherry'] as fruit
4134
- return fruit
4135
- order by toLower(fruit)
4136
- `);
4137
- await runner.run();
4138
- const results = runner.results;
4139
- expect(results.length).toBe(3);
4140
- expect(results[0]).toEqual({ fruit: "apple" });
4141
- expect(results[1]).toEqual({ fruit: "BANANA" });
4142
- expect(results[2]).toEqual({ fruit: "Cherry" });
4143
- });
4144
-
4145
- test("Test order by with function expression descending", async () => {
4146
- const runner = new Runner(`
4147
- unwind ['BANANA', 'apple', 'Cherry'] as fruit
4148
- return fruit
4149
- order by toLower(fruit) desc
4150
- `);
4151
- await runner.run();
4152
- const results = runner.results;
4153
- expect(results.length).toBe(3);
4154
- expect(results[0]).toEqual({ fruit: "Cherry" });
4155
- expect(results[1]).toEqual({ fruit: "BANANA" });
4156
- expect(results[2]).toEqual({ fruit: "apple" });
4157
- });
4158
-
4159
- test("Test order by with nested function expression", async () => {
4160
- const runner = new Runner(`
4161
- unwind ['Alice', 'Bob', 'ALICE', 'bob'] as name
4162
- return name
4163
- order by string_distance(toLower(name), toLower('alice')) asc
4164
- `);
4165
- await runner.run();
4166
- const results = runner.results;
4167
- expect(results.length).toBe(4);
4168
- // 'Alice' and 'ALICE' have distance 0 from 'alice', should come first
4169
- expect(results[0].name).toBe("Alice");
4170
- expect(results[1].name).toBe("ALICE");
4171
- // 'Bob' and 'bob' have higher distance from 'alice'
4172
- expect(results[2].name).toBe("Bob");
4173
- expect(results[3].name).toBe("bob");
4174
- });
4175
-
4176
- test("Test order by with arithmetic expression", async () => {
4177
- const runner = new Runner(`
4178
- unwind [{a: 3, b: 1}, {a: 1, b: 5}, {a: 2, b: 2}] as item
4179
- return item.a as a, item.b as b
4180
- order by item.a + item.b asc
4181
- `);
4182
- await runner.run();
4183
- const results = runner.results;
4184
- expect(results.length).toBe(3);
4185
- expect(results[0]).toEqual({ a: 3, b: 1 }); // sum = 4
4186
- expect(results[1]).toEqual({ a: 2, b: 2 }); // sum = 4
4187
- expect(results[2]).toEqual({ a: 1, b: 5 }); // sum = 6
4188
- });
4189
-
4190
- test("Test order by expression does not leak synthetic keys", async () => {
4191
- const runner = new Runner(`
4192
- unwind ['B', 'a', 'C'] as x
4193
- return x
4194
- order by toLower(x) asc
4195
- `);
4196
- await runner.run();
4197
- const results = runner.results;
4198
- expect(results.length).toBe(3);
4199
- // Results should only contain 'x', no __orderBy_ keys
4200
- for (const r of results) {
4201
- expect(Object.keys(r)).toEqual(["x"]);
4202
- }
4203
- expect(results[0]).toEqual({ x: "a" });
4204
- expect(results[1]).toEqual({ x: "B" });
4205
- expect(results[2]).toEqual({ x: "C" });
4206
- });
4207
-
4208
- test("Test order by with expression and limit", async () => {
4209
- const runner = new Runner(`
4210
- unwind ['BANANA', 'apple', 'Cherry', 'date', 'ELDERBERRY'] as fruit
4211
- return fruit
4212
- order by toLower(fruit) asc
4213
- limit 3
4214
- `);
4215
- await runner.run();
4216
- const results = runner.results;
4217
- expect(results.length).toBe(3);
4218
- expect(results[0]).toEqual({ fruit: "apple" });
4219
- expect(results[1]).toEqual({ fruit: "BANANA" });
4220
- expect(results[2]).toEqual({ fruit: "Cherry" });
4221
- });
4222
-
4223
- test("Test order by with mixed simple and expression fields", async () => {
4224
- const runner = new Runner(`
4225
- unwind [{name: 'Alice', score: 3}, {name: 'Alice', score: 1}, {name: 'Bob', score: 2}] as item
4226
- return item.name as name, item.score as score
4227
- order by name asc, item.score desc
4228
- `);
4229
- await runner.run();
4230
- const results = runner.results;
4231
- expect(results.length).toBe(3);
4232
- expect(results[0]).toEqual({ name: "Alice", score: 3 }); // Alice, score 3 desc
4233
- expect(results[1]).toEqual({ name: "Alice", score: 1 }); // Alice, score 1 desc
4234
- expect(results[2]).toEqual({ name: "Bob", score: 2 }); // Bob
4235
- });
4236
-
4237
- test("Test delete virtual node operation", async () => {
4238
- const db = Database.getInstance();
4239
- // Create a virtual node first
4240
- const create = new Runner(`
4241
- CREATE VIRTUAL (:DeleteTestPerson) AS {
4242
- unwind [
4243
- {id: 1, name: 'Person 1'},
4244
- {id: 2, name: 'Person 2'}
4245
- ] as record
4246
- RETURN record.id as id, record.name as name
4247
- }
4248
- `);
4249
- await create.run();
4250
- expect(db.getNode(new Node(null, "DeleteTestPerson"))).not.toBeNull();
4251
-
4252
- // Delete the virtual node
4253
- const del = new Runner("DELETE VIRTUAL (:DeleteTestPerson)");
4254
- await del.run();
4255
- expect(del.results.length).toBe(0);
4256
- expect(db.getNode(new Node(null, "DeleteTestPerson"))).toBeNull();
4257
- });
4258
-
4259
- test("Test delete virtual node then match throws", async () => {
4260
- // Create a virtual node
4261
- const create = new Runner(`
4262
- CREATE VIRTUAL (:DeleteMatchPerson) AS {
4263
- unwind [{id: 1, name: 'Alice'}] as record
4264
- RETURN record.id as id, record.name as name
4265
- }
4266
- `);
4267
- await create.run();
4268
-
4269
- // Verify it can be matched
4270
- const match1 = new Runner("MATCH (n:DeleteMatchPerson) RETURN n");
4271
- await match1.run();
4272
- expect(match1.results.length).toBe(1);
4273
-
4274
- // Delete the virtual node
4275
- const del = new Runner("DELETE VIRTUAL (:DeleteMatchPerson)");
4276
- await del.run();
4277
-
4278
- // Matching should now throw since the node is gone
4279
- const match2 = new Runner("MATCH (n:DeleteMatchPerson) RETURN n");
4280
- await expect(match2.run()).rejects.toThrow();
4281
- });
4282
-
4283
- test("Test delete virtual relationship operation", async () => {
4284
- const db = Database.getInstance();
4285
- // Create virtual nodes and relationship
4286
- await new Runner(`
4287
- CREATE VIRTUAL (:DelRelUser) AS {
4288
- unwind [
4289
- {id: 1, name: 'Alice'},
4290
- {id: 2, name: 'Bob'}
4291
- ] as record
4292
- RETURN record.id as id, record.name as name
4293
- }
4294
- `).run();
4295
-
4296
- await new Runner(`
4297
- CREATE VIRTUAL (:DelRelUser)-[:DEL_KNOWS]-(:DelRelUser) AS {
4298
- unwind [
4299
- {left_id: 1, right_id: 2}
4300
- ] as record
4301
- RETURN record.left_id as left_id, record.right_id as right_id
4302
- }
4303
- `).run();
4304
-
4305
- // Verify relationship exists
4306
- const rel = new Relationship();
4307
- rel.type = "DEL_KNOWS";
4308
- expect(db.getRelationship(rel)).not.toBeNull();
4309
-
4310
- // Delete the virtual relationship
4311
- const del = new Runner("DELETE VIRTUAL (:DelRelUser)-[:DEL_KNOWS]-(:DelRelUser)");
4312
- await del.run();
4313
- expect(del.results.length).toBe(0);
4314
- expect(db.getRelationship(rel)).toBeNull();
4315
- });
4316
-
4317
- test("Test delete virtual node leaves other nodes intact", async () => {
4318
- const db = Database.getInstance();
4319
- // Create two virtual node types
4320
- await new Runner(`
4321
- CREATE VIRTUAL (:KeepNode) AS {
4322
- unwind [{id: 1, name: 'Keep'}] as record
4323
- RETURN record.id as id, record.name as name
4324
- }
4325
- `).run();
4326
-
4327
- await new Runner(`
4328
- CREATE VIRTUAL (:RemoveNode) AS {
4329
- unwind [{id: 2, name: 'Remove'}] as record
4330
- RETURN record.id as id, record.name as name
4331
- }
4332
- `).run();
4333
-
4334
- expect(db.getNode(new Node(null, "KeepNode"))).not.toBeNull();
4335
- expect(db.getNode(new Node(null, "RemoveNode"))).not.toBeNull();
4336
-
4337
- // Delete only one
4338
- await new Runner("DELETE VIRTUAL (:RemoveNode)").run();
4339
-
4340
- // The other should still exist
4341
- expect(db.getNode(new Node(null, "KeepNode"))).not.toBeNull();
4342
- expect(db.getNode(new Node(null, "RemoveNode"))).toBeNull();
4343
-
4344
- // The remaining node can still be matched
4345
- const match = new Runner("MATCH (n:KeepNode) RETURN n");
4346
- await match.run();
4347
- expect(match.results.length).toBe(1);
4348
- expect(match.results[0].n.name).toBe("Keep");
4349
- });
4350
-
4351
- test("Test RETURN alias shadowing graph variable in same RETURN clause", async () => {
4352
- // Create User nodes with displayName, jobTitle, and department
4353
- await new Runner(`
4354
- CREATE VIRTUAL (:MentorUser) AS {
4355
- UNWIND [
4356
- {id: 1, displayName: 'Alice Smith', jobTitle: 'Senior Engineer', department: 'Engineering'},
4357
- {id: 2, displayName: 'Bob Jones', jobTitle: 'Staff Engineer', department: 'Engineering'},
4358
- {id: 3, displayName: 'Chloe Dubois', jobTitle: 'Junior Engineer', department: 'Engineering'}
4359
- ] AS record
4360
- RETURN record.id AS id, record.displayName AS displayName, record.jobTitle AS jobTitle, record.department AS department
4361
- }
4362
- `).run();
4363
-
4364
- // Create MENTORS relationships
4365
- await new Runner(`
4366
- CREATE VIRTUAL (:MentorUser)-[:MENTORS]-(:MentorUser) AS {
4367
- UNWIND [
4368
- {left_id: 1, right_id: 3},
4369
- {left_id: 2, right_id: 3}
4370
- ] AS record
4371
- RETURN record.left_id AS left_id, record.right_id AS right_id
4372
- }
4373
- `).run();
4374
-
4375
- // This query aliases mentor.displayName AS mentor, which shadows the graph variable "mentor".
4376
- // Subsequent expressions mentor.jobTitle and mentor.department should still reference the graph node.
4377
- const runner = new Runner(`
4378
- MATCH (mentor:MentorUser)-[:MENTORS]->(mentee:MentorUser)
4379
- WHERE mentee.displayName = "Chloe Dubois"
4380
- RETURN mentor.displayName AS mentor, mentor.jobTitle AS mentorJobTitle, mentor.department AS mentorDepartment
4381
- `);
4382
- await runner.run();
4383
- const results = runner.results;
4384
-
4385
- expect(results.length).toBe(2);
4386
- expect(results[0]).toEqual({
4387
- mentor: "Alice Smith",
4388
- mentorJobTitle: "Senior Engineer",
4389
- mentorDepartment: "Engineering",
4390
- });
4391
- expect(results[1]).toEqual({
4392
- mentor: "Bob Jones",
4393
- mentorJobTitle: "Staff Engineer",
4394
- mentorDepartment: "Engineering",
4395
- });
4396
- });
4397
-
4398
- test("Test chained optional match with null intermediate node", async () => {
4399
- // Create a chain: A -> B -> C (C has no outgoing REPORTS_TO)
4400
- await new Runner(`
4401
- CREATE VIRTUAL (:Employee) AS {
4402
- unwind [
4403
- {id: 1, name: 'Alice'},
4404
- {id: 2, name: 'Bob'},
4405
- {id: 3, name: 'Charlie'}
4406
- ] as record
4407
- RETURN record.id as id, record.name as name
4408
- }
4409
- `).run();
4410
- await new Runner(`
4411
- CREATE VIRTUAL (:Employee)-[:REPORTS_TO]-(:Employee) AS {
4412
- unwind [
4413
- {left_id: 1, right_id: 2},
4414
- {left_id: 2, right_id: 3}
4415
- ] as record
4416
- RETURN record.left_id as left_id, record.right_id as right_id
4417
- }
4418
- `).run();
4419
-
4420
- // Alice -> Bob -> Charlie -> null -> null
4421
- // m1=Bob, m2=Charlie, m3=null, m4=null (should not crash)
4422
- const runner = new Runner(`
4423
- MATCH (u:Employee)
4424
- WHERE u.name = "Alice"
4425
- OPTIONAL MATCH (u)-[:REPORTS_TO]->(m1:Employee)
4426
- OPTIONAL MATCH (m1)-[:REPORTS_TO]->(m2:Employee)
4427
- OPTIONAL MATCH (m2)-[:REPORTS_TO]->(m3:Employee)
4428
- OPTIONAL MATCH (m3)-[:REPORTS_TO]->(m4:Employee)
4429
- RETURN
4430
- u.name AS user,
4431
- m1.name AS manager1,
4432
- m2.name AS manager2,
4433
- m3.name AS manager3,
4434
- m4.name AS manager4
4435
- `);
4436
- await runner.run();
4437
- const results = runner.results;
4438
-
4439
- expect(results.length).toBe(1);
4440
- expect(results[0].user).toBe("Alice");
4441
- expect(results[0].manager1).toBe("Bob");
4442
- expect(results[0].manager2).toBe("Charlie");
4443
- expect(results[0].manager3).toBeNull();
4444
- expect(results[0].manager4).toBeNull();
4445
- });
4446
-
4447
- test("Test chained optional match all null from first optional", async () => {
4448
- // Create nodes with no relationships
4449
- await new Runner(`
4450
- CREATE VIRTUAL (:Worker) AS {
4451
- unwind [
4452
- {id: 1, name: 'Solo'}
4453
- ] as record
4454
- RETURN record.id as id, record.name as name
4455
- }
4456
- `).run();
4457
- await new Runner(`
4458
- CREATE VIRTUAL (:Worker)-[:MANAGES]-(:Worker) AS {
4459
- unwind [] as record
4460
- RETURN record.left_id as left_id, record.right_id as right_id
4461
- }
4462
- `).run();
4463
-
4464
- // Solo has no MANAGES relationship at all
4465
- // m1=null, m2=null, m3=null
4466
- const runner = new Runner(`
4467
- MATCH (u:Worker)
4468
- OPTIONAL MATCH (u)-[:MANAGES]->(m1:Worker)
4469
- OPTIONAL MATCH (m1)-[:MANAGES]->(m2:Worker)
4470
- OPTIONAL MATCH (m2)-[:MANAGES]->(m3:Worker)
4471
- RETURN
4472
- u.name AS user,
4473
- m1.name AS mgr1,
4474
- m2.name AS mgr2,
4475
- m3.name AS mgr3
4476
- `);
4477
- await runner.run();
4478
- const results = runner.results;
4479
-
4480
- expect(results.length).toBe(1);
4481
- expect(results[0].user).toBe("Solo");
4482
- expect(results[0].mgr1).toBeNull();
4483
- expect(results[0].mgr2).toBeNull();
4484
- expect(results[0].mgr3).toBeNull();
4485
- });
4486
-
4487
- test("Test chained optional match with mixed null and non-null paths", async () => {
4488
- // Two starting nodes: one with full chain, one with partial
4489
- await new Runner(`
4490
- CREATE VIRTUAL (:Staff) AS {
4491
- unwind [
4492
- {id: 1, name: 'Dev'},
4493
- {id: 2, name: 'Lead'},
4494
- {id: 3, name: 'Director'},
4495
- {id: 4, name: 'Intern'}
4496
- ] as record
4497
- RETURN record.id as id, record.name as name
4498
- }
4499
- `).run();
4500
- await new Runner(`
4501
- CREATE VIRTUAL (:Staff)-[:REPORTS_TO]-(:Staff) AS {
4502
- unwind [
4503
- {left_id: 1, right_id: 2},
4504
- {left_id: 2, right_id: 3}
4505
- ] as record
4506
- RETURN record.left_id as left_id, record.right_id as right_id
4507
- }
4508
- `).run();
4509
-
4510
- // Dev -> Lead -> Director -> null
4511
- // Intern -> null -> null -> null
4512
- const runner = new Runner(`
4513
- MATCH (u:Staff)
4514
- WHERE u.name = "Dev" OR u.name = "Intern"
4515
- OPTIONAL MATCH (u)-[:REPORTS_TO]->(m1:Staff)
4516
- OPTIONAL MATCH (m1)-[:REPORTS_TO]->(m2:Staff)
4517
- OPTIONAL MATCH (m2)-[:REPORTS_TO]->(m3:Staff)
4518
- RETURN
4519
- u.name AS user,
4520
- m1.name AS mgr1,
4521
- m2.name AS mgr2,
4522
- m3.name AS mgr3
4523
- `);
4524
- await runner.run();
4525
- const results = runner.results;
4526
-
4527
- expect(results.length).toBe(2);
4528
- // Dev's chain
4529
- const dev = results.find((r: any) => r.user === "Dev");
4530
- expect(dev.mgr1).toBe("Lead");
4531
- expect(dev.mgr2).toBe("Director");
4532
- expect(dev.mgr3).toBeNull();
4533
- // Intern's chain
4534
- const intern = results.find((r: any) => r.user === "Intern");
4535
- expect(intern.mgr1).toBeNull();
4536
- expect(intern.mgr2).toBeNull();
4537
- expect(intern.mgr3).toBeNull();
4538
- });