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