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.
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/.editorconfig +0 -21
- package/.gitattributes +0 -3
- package/.github/workflows/npm-publish.yml +0 -32
- package/.github/workflows/python-publish.yml +0 -143
- package/.github/workflows/release.yml +0 -107
- package/.husky/pre-commit +0 -28
- package/.prettierrc +0 -22
- package/CODE_OF_CONDUCT.md +0 -10
- package/FlowQueryLogoIcon.png +0 -0
- package/SECURITY.md +0 -14
- package/SUPPORT.md +0 -13
- package/docs/flowquery.min.js +0 -1
- package/docs/index.html +0 -105
- package/flowquery-py/CONTRIBUTING.md +0 -127
- package/flowquery-py/README.md +0 -67
- package/flowquery-py/misc/data/test.json +0 -10
- package/flowquery-py/misc/data/users.json +0 -242
- package/flowquery-py/notebooks/TestFlowQuery.ipynb +0 -440
- package/flowquery-py/pyproject.toml +0 -121
- package/flowquery-py/setup_env.ps1 +0 -92
- package/flowquery-py/setup_env.sh +0 -87
- package/flowquery-py/src/__init__.py +0 -38
- package/flowquery-py/src/__main__.py +0 -10
- package/flowquery-py/src/compute/__init__.py +0 -6
- package/flowquery-py/src/compute/flowquery.py +0 -68
- package/flowquery-py/src/compute/runner.py +0 -64
- package/flowquery-py/src/extensibility.py +0 -52
- package/flowquery-py/src/graph/__init__.py +0 -31
- package/flowquery-py/src/graph/data.py +0 -136
- package/flowquery-py/src/graph/database.py +0 -141
- package/flowquery-py/src/graph/hops.py +0 -43
- package/flowquery-py/src/graph/node.py +0 -143
- package/flowquery-py/src/graph/node_data.py +0 -26
- package/flowquery-py/src/graph/node_reference.py +0 -50
- package/flowquery-py/src/graph/pattern.py +0 -115
- package/flowquery-py/src/graph/pattern_expression.py +0 -67
- package/flowquery-py/src/graph/patterns.py +0 -42
- package/flowquery-py/src/graph/physical_node.py +0 -41
- package/flowquery-py/src/graph/physical_relationship.py +0 -36
- package/flowquery-py/src/graph/relationship.py +0 -193
- package/flowquery-py/src/graph/relationship_data.py +0 -36
- package/flowquery-py/src/graph/relationship_match_collector.py +0 -85
- package/flowquery-py/src/graph/relationship_reference.py +0 -21
- package/flowquery-py/src/io/__init__.py +0 -5
- package/flowquery-py/src/io/command_line.py +0 -108
- package/flowquery-py/src/parsing/__init__.py +0 -17
- package/flowquery-py/src/parsing/alias.py +0 -20
- package/flowquery-py/src/parsing/alias_option.py +0 -11
- package/flowquery-py/src/parsing/ast_node.py +0 -147
- package/flowquery-py/src/parsing/base_parser.py +0 -84
- package/flowquery-py/src/parsing/components/__init__.py +0 -19
- package/flowquery-py/src/parsing/components/csv.py +0 -8
- package/flowquery-py/src/parsing/components/from_.py +0 -12
- package/flowquery-py/src/parsing/components/headers.py +0 -12
- package/flowquery-py/src/parsing/components/json.py +0 -8
- package/flowquery-py/src/parsing/components/null.py +0 -10
- package/flowquery-py/src/parsing/components/post.py +0 -8
- package/flowquery-py/src/parsing/components/text.py +0 -8
- package/flowquery-py/src/parsing/context.py +0 -50
- package/flowquery-py/src/parsing/data_structures/__init__.py +0 -15
- package/flowquery-py/src/parsing/data_structures/associative_array.py +0 -41
- package/flowquery-py/src/parsing/data_structures/json_array.py +0 -30
- package/flowquery-py/src/parsing/data_structures/key_value_pair.py +0 -38
- package/flowquery-py/src/parsing/data_structures/lookup.py +0 -51
- package/flowquery-py/src/parsing/data_structures/range_lookup.py +0 -42
- package/flowquery-py/src/parsing/expressions/__init__.py +0 -61
- package/flowquery-py/src/parsing/expressions/boolean.py +0 -20
- package/flowquery-py/src/parsing/expressions/expression.py +0 -141
- package/flowquery-py/src/parsing/expressions/expression_map.py +0 -26
- package/flowquery-py/src/parsing/expressions/f_string.py +0 -27
- package/flowquery-py/src/parsing/expressions/identifier.py +0 -21
- package/flowquery-py/src/parsing/expressions/number.py +0 -32
- package/flowquery-py/src/parsing/expressions/operator.py +0 -271
- package/flowquery-py/src/parsing/expressions/reference.py +0 -47
- package/flowquery-py/src/parsing/expressions/string.py +0 -27
- package/flowquery-py/src/parsing/functions/__init__.py +0 -127
- package/flowquery-py/src/parsing/functions/aggregate_function.py +0 -60
- package/flowquery-py/src/parsing/functions/async_function.py +0 -65
- package/flowquery-py/src/parsing/functions/avg.py +0 -55
- package/flowquery-py/src/parsing/functions/coalesce.py +0 -43
- package/flowquery-py/src/parsing/functions/collect.py +0 -75
- package/flowquery-py/src/parsing/functions/count.py +0 -79
- package/flowquery-py/src/parsing/functions/date_.py +0 -61
- package/flowquery-py/src/parsing/functions/datetime_.py +0 -62
- package/flowquery-py/src/parsing/functions/duration.py +0 -159
- package/flowquery-py/src/parsing/functions/element_id.py +0 -50
- package/flowquery-py/src/parsing/functions/function.py +0 -68
- package/flowquery-py/src/parsing/functions/function_factory.py +0 -170
- package/flowquery-py/src/parsing/functions/function_metadata.py +0 -148
- package/flowquery-py/src/parsing/functions/functions.py +0 -67
- package/flowquery-py/src/parsing/functions/head.py +0 -39
- package/flowquery-py/src/parsing/functions/id_.py +0 -49
- package/flowquery-py/src/parsing/functions/join.py +0 -49
- package/flowquery-py/src/parsing/functions/keys.py +0 -34
- package/flowquery-py/src/parsing/functions/last.py +0 -39
- package/flowquery-py/src/parsing/functions/localdatetime.py +0 -60
- package/flowquery-py/src/parsing/functions/localtime.py +0 -57
- package/flowquery-py/src/parsing/functions/max_.py +0 -49
- package/flowquery-py/src/parsing/functions/min_.py +0 -49
- package/flowquery-py/src/parsing/functions/nodes.py +0 -48
- package/flowquery-py/src/parsing/functions/predicate_function.py +0 -47
- package/flowquery-py/src/parsing/functions/predicate_sum.py +0 -49
- package/flowquery-py/src/parsing/functions/properties.py +0 -50
- package/flowquery-py/src/parsing/functions/rand.py +0 -28
- package/flowquery-py/src/parsing/functions/range_.py +0 -41
- package/flowquery-py/src/parsing/functions/reducer_element.py +0 -15
- package/flowquery-py/src/parsing/functions/relationships.py +0 -46
- package/flowquery-py/src/parsing/functions/replace.py +0 -39
- package/flowquery-py/src/parsing/functions/round_.py +0 -34
- package/flowquery-py/src/parsing/functions/schema.py +0 -40
- package/flowquery-py/src/parsing/functions/size.py +0 -34
- package/flowquery-py/src/parsing/functions/split.py +0 -54
- package/flowquery-py/src/parsing/functions/string_distance.py +0 -92
- package/flowquery-py/src/parsing/functions/stringify.py +0 -49
- package/flowquery-py/src/parsing/functions/substring.py +0 -76
- package/flowquery-py/src/parsing/functions/sum.py +0 -51
- package/flowquery-py/src/parsing/functions/tail.py +0 -37
- package/flowquery-py/src/parsing/functions/temporal_utils.py +0 -186
- package/flowquery-py/src/parsing/functions/time_.py +0 -57
- package/flowquery-py/src/parsing/functions/timestamp.py +0 -37
- package/flowquery-py/src/parsing/functions/to_float.py +0 -46
- package/flowquery-py/src/parsing/functions/to_integer.py +0 -46
- package/flowquery-py/src/parsing/functions/to_json.py +0 -35
- package/flowquery-py/src/parsing/functions/to_lower.py +0 -37
- package/flowquery-py/src/parsing/functions/to_string.py +0 -41
- package/flowquery-py/src/parsing/functions/trim.py +0 -37
- package/flowquery-py/src/parsing/functions/type_.py +0 -47
- package/flowquery-py/src/parsing/functions/value_holder.py +0 -24
- package/flowquery-py/src/parsing/logic/__init__.py +0 -15
- package/flowquery-py/src/parsing/logic/case.py +0 -28
- package/flowquery-py/src/parsing/logic/else_.py +0 -12
- package/flowquery-py/src/parsing/logic/end.py +0 -8
- package/flowquery-py/src/parsing/logic/then.py +0 -12
- package/flowquery-py/src/parsing/logic/when.py +0 -12
- package/flowquery-py/src/parsing/operations/__init__.py +0 -46
- package/flowquery-py/src/parsing/operations/aggregated_return.py +0 -25
- package/flowquery-py/src/parsing/operations/aggregated_with.py +0 -22
- package/flowquery-py/src/parsing/operations/call.py +0 -73
- package/flowquery-py/src/parsing/operations/create_node.py +0 -35
- package/flowquery-py/src/parsing/operations/create_relationship.py +0 -35
- package/flowquery-py/src/parsing/operations/delete_node.py +0 -29
- package/flowquery-py/src/parsing/operations/delete_relationship.py +0 -29
- package/flowquery-py/src/parsing/operations/group_by.py +0 -148
- package/flowquery-py/src/parsing/operations/limit.py +0 -33
- package/flowquery-py/src/parsing/operations/load.py +0 -148
- package/flowquery-py/src/parsing/operations/match.py +0 -52
- package/flowquery-py/src/parsing/operations/operation.py +0 -69
- package/flowquery-py/src/parsing/operations/order_by.py +0 -114
- package/flowquery-py/src/parsing/operations/projection.py +0 -21
- package/flowquery-py/src/parsing/operations/return_op.py +0 -88
- package/flowquery-py/src/parsing/operations/union.py +0 -115
- package/flowquery-py/src/parsing/operations/union_all.py +0 -17
- package/flowquery-py/src/parsing/operations/unwind.py +0 -42
- package/flowquery-py/src/parsing/operations/where.py +0 -43
- package/flowquery-py/src/parsing/operations/with_op.py +0 -18
- package/flowquery-py/src/parsing/parser.py +0 -1384
- package/flowquery-py/src/parsing/parser_state.py +0 -26
- package/flowquery-py/src/parsing/token_to_node.py +0 -109
- package/flowquery-py/src/tokenization/__init__.py +0 -23
- package/flowquery-py/src/tokenization/keyword.py +0 -54
- package/flowquery-py/src/tokenization/operator.py +0 -29
- package/flowquery-py/src/tokenization/string_walker.py +0 -158
- package/flowquery-py/src/tokenization/symbol.py +0 -19
- package/flowquery-py/src/tokenization/token.py +0 -693
- package/flowquery-py/src/tokenization/token_mapper.py +0 -53
- package/flowquery-py/src/tokenization/token_type.py +0 -21
- package/flowquery-py/src/tokenization/tokenizer.py +0 -214
- package/flowquery-py/src/tokenization/trie.py +0 -125
- package/flowquery-py/src/utils/__init__.py +0 -6
- package/flowquery-py/src/utils/object_utils.py +0 -20
- package/flowquery-py/src/utils/string_utils.py +0 -113
- package/flowquery-py/tests/__init__.py +0 -1
- package/flowquery-py/tests/compute/__init__.py +0 -1
- package/flowquery-py/tests/compute/test_runner.py +0 -4902
- package/flowquery-py/tests/graph/__init__.py +0 -1
- package/flowquery-py/tests/graph/test_create.py +0 -56
- package/flowquery-py/tests/graph/test_data.py +0 -73
- package/flowquery-py/tests/graph/test_match.py +0 -40
- package/flowquery-py/tests/parsing/__init__.py +0 -1
- package/flowquery-py/tests/parsing/test_context.py +0 -34
- package/flowquery-py/tests/parsing/test_expression.py +0 -248
- package/flowquery-py/tests/parsing/test_parser.py +0 -1237
- package/flowquery-py/tests/test_extensibility.py +0 -611
- package/flowquery-py/tests/tokenization/__init__.py +0 -1
- package/flowquery-py/tests/tokenization/test_token_mapper.py +0 -60
- package/flowquery-py/tests/tokenization/test_tokenizer.py +0 -198
- package/flowquery-py/tests/tokenization/test_trie.py +0 -30
- package/flowquery-vscode/.vscode-test.mjs +0 -5
- package/flowquery-vscode/.vscodeignore +0 -13
- package/flowquery-vscode/LICENSE +0 -21
- package/flowquery-vscode/README.md +0 -11
- package/flowquery-vscode/demo/FlowQueryVSCodeDemo.gif +0 -0
- package/flowquery-vscode/eslint.config.mjs +0 -25
- package/flowquery-vscode/extension.js +0 -508
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +0 -1
- package/flowquery-vscode/flowquery-worker.js +0 -66
- package/flowquery-vscode/images/FlowQueryLogoIcon.png +0 -0
- package/flowquery-vscode/jsconfig.json +0 -13
- package/flowquery-vscode/libs/page.css +0 -53
- package/flowquery-vscode/libs/table.css +0 -13
- package/flowquery-vscode/libs/tabs.css +0 -66
- package/flowquery-vscode/package-lock.json +0 -2917
- package/flowquery-vscode/package.json +0 -51
- package/flowquery-vscode/test/extension.test.js +0 -196
- package/flowquery-vscode/test/worker.test.js +0 -25
- package/flowquery-vscode/vsc-extension-quickstart.md +0 -42
- package/jest.config.js +0 -14
- package/misc/apps/RAG/README.md +0 -29
- package/misc/apps/RAG/data/chats.json +0 -302
- package/misc/apps/RAG/data/emails.json +0 -182
- package/misc/apps/RAG/data/events.json +0 -226
- package/misc/apps/RAG/data/files.json +0 -172
- package/misc/apps/RAG/data/users.json +0 -158
- package/misc/apps/RAG/jest.config.js +0 -21
- package/misc/apps/RAG/package.json +0 -48
- package/misc/apps/RAG/public/index.html +0 -18
- package/misc/apps/RAG/src/App.css +0 -42
- package/misc/apps/RAG/src/App.tsx +0 -50
- package/misc/apps/RAG/src/components/AdaptiveCardRenderer.css +0 -172
- package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +0 -380
- package/misc/apps/RAG/src/components/ApiKeySettings.tsx +0 -245
- package/misc/apps/RAG/src/components/ChatContainer.css +0 -67
- package/misc/apps/RAG/src/components/ChatContainer.tsx +0 -242
- package/misc/apps/RAG/src/components/ChatInput.css +0 -23
- package/misc/apps/RAG/src/components/ChatInput.tsx +0 -76
- package/misc/apps/RAG/src/components/ChatMessage.css +0 -160
- package/misc/apps/RAG/src/components/ChatMessage.tsx +0 -286
- package/misc/apps/RAG/src/components/FlowQueryAgent.ts +0 -708
- package/misc/apps/RAG/src/components/FlowQueryRunner.css +0 -113
- package/misc/apps/RAG/src/components/FlowQueryRunner.tsx +0 -371
- package/misc/apps/RAG/src/components/index.ts +0 -28
- package/misc/apps/RAG/src/graph/index.ts +0 -19
- package/misc/apps/RAG/src/graph/initializeGraph.ts +0 -254
- package/misc/apps/RAG/src/index.tsx +0 -29
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +0 -327
- package/misc/apps/RAG/src/prompts/index.ts +0 -10
- package/misc/apps/RAG/src/tests/graph.test.ts +0 -35
- package/misc/apps/RAG/src/utils/FlowQueryExecutor.ts +0 -130
- package/misc/apps/RAG/src/utils/FlowQueryExtractor.ts +0 -208
- package/misc/apps/RAG/src/utils/Llm.ts +0 -248
- package/misc/apps/RAG/src/utils/index.ts +0 -12
- package/misc/apps/RAG/tsconfig.json +0 -22
- package/misc/apps/RAG/webpack.config.js +0 -43
- package/misc/apps/README.md +0 -1
- package/misc/queries/analyze_catfacts.cql +0 -75
- package/misc/queries/azure_openai_completions.cql +0 -13
- package/misc/queries/azure_openai_models.cql +0 -9
- package/misc/queries/mock_pipeline.cql +0 -84
- package/misc/queries/openai_completions.cql +0 -15
- package/misc/queries/openai_models.cql +0 -13
- package/misc/queries/test.cql +0 -6
- package/misc/queries/tool_inference.cql +0 -24
- package/misc/queries/wisdom.cql +0 -6
- package/misc/queries/wisdom_letter_histogram.cql +0 -8
- package/src/compute/flowquery.ts +0 -46
- package/src/compute/runner.ts +0 -66
- package/src/extensibility.ts +0 -45
- package/src/graph/data.ts +0 -130
- package/src/graph/database.ts +0 -143
- package/src/graph/hops.ts +0 -22
- package/src/graph/node.ts +0 -122
- package/src/graph/node_data.ts +0 -18
- package/src/graph/node_reference.ts +0 -38
- package/src/graph/pattern.ts +0 -110
- package/src/graph/pattern_expression.ts +0 -48
- package/src/graph/patterns.ts +0 -36
- package/src/graph/physical_node.ts +0 -23
- package/src/graph/physical_relationship.ts +0 -23
- package/src/graph/relationship.ts +0 -167
- package/src/graph/relationship_data.ts +0 -31
- package/src/graph/relationship_match_collector.ts +0 -64
- package/src/graph/relationship_reference.ts +0 -25
- package/src/index.browser.ts +0 -46
- package/src/index.node.ts +0 -55
- package/src/index.ts +0 -12
- package/src/io/command_line.ts +0 -74
- package/src/parsing/alias.ts +0 -23
- package/src/parsing/alias_option.ts +0 -5
- package/src/parsing/ast_node.ts +0 -153
- package/src/parsing/base_parser.ts +0 -98
- package/src/parsing/components/csv.ts +0 -9
- package/src/parsing/components/from.ts +0 -12
- package/src/parsing/components/headers.ts +0 -12
- package/src/parsing/components/json.ts +0 -9
- package/src/parsing/components/null.ts +0 -9
- package/src/parsing/components/post.ts +0 -9
- package/src/parsing/components/text.ts +0 -9
- package/src/parsing/context.ts +0 -54
- package/src/parsing/data_structures/associative_array.ts +0 -43
- package/src/parsing/data_structures/json_array.ts +0 -31
- package/src/parsing/data_structures/key_value_pair.ts +0 -37
- package/src/parsing/data_structures/lookup.ts +0 -44
- package/src/parsing/data_structures/range_lookup.ts +0 -36
- package/src/parsing/expressions/boolean.ts +0 -21
- package/src/parsing/expressions/expression.ts +0 -150
- package/src/parsing/expressions/expression_map.ts +0 -22
- package/src/parsing/expressions/f_string.ts +0 -26
- package/src/parsing/expressions/identifier.ts +0 -22
- package/src/parsing/expressions/number.ts +0 -40
- package/src/parsing/expressions/operator.ts +0 -354
- package/src/parsing/expressions/reference.ts +0 -45
- package/src/parsing/expressions/string.ts +0 -34
- package/src/parsing/functions/aggregate_function.ts +0 -58
- package/src/parsing/functions/async_function.ts +0 -64
- package/src/parsing/functions/avg.ts +0 -47
- package/src/parsing/functions/coalesce.ts +0 -49
- package/src/parsing/functions/collect.ts +0 -54
- package/src/parsing/functions/count.ts +0 -54
- package/src/parsing/functions/date.ts +0 -63
- package/src/parsing/functions/datetime.ts +0 -63
- package/src/parsing/functions/duration.ts +0 -143
- package/src/parsing/functions/element_id.ts +0 -51
- package/src/parsing/functions/function.ts +0 -60
- package/src/parsing/functions/function_factory.ts +0 -195
- package/src/parsing/functions/function_metadata.ts +0 -217
- package/src/parsing/functions/functions.ts +0 -70
- package/src/parsing/functions/head.ts +0 -42
- package/src/parsing/functions/id.ts +0 -51
- package/src/parsing/functions/join.ts +0 -40
- package/src/parsing/functions/keys.ts +0 -29
- package/src/parsing/functions/last.ts +0 -42
- package/src/parsing/functions/localdatetime.ts +0 -63
- package/src/parsing/functions/localtime.ts +0 -58
- package/src/parsing/functions/max.ts +0 -37
- package/src/parsing/functions/min.ts +0 -37
- package/src/parsing/functions/nodes.ts +0 -54
- package/src/parsing/functions/predicate_function.ts +0 -48
- package/src/parsing/functions/predicate_sum.ts +0 -47
- package/src/parsing/functions/properties.ts +0 -56
- package/src/parsing/functions/rand.ts +0 -21
- package/src/parsing/functions/range.ts +0 -37
- package/src/parsing/functions/reducer_element.ts +0 -10
- package/src/parsing/functions/relationships.ts +0 -52
- package/src/parsing/functions/replace.ts +0 -38
- package/src/parsing/functions/round.ts +0 -28
- package/src/parsing/functions/schema.ts +0 -39
- package/src/parsing/functions/size.ts +0 -28
- package/src/parsing/functions/split.ts +0 -45
- package/src/parsing/functions/string_distance.ts +0 -83
- package/src/parsing/functions/stringify.ts +0 -37
- package/src/parsing/functions/substring.ts +0 -68
- package/src/parsing/functions/sum.ts +0 -41
- package/src/parsing/functions/tail.ts +0 -39
- package/src/parsing/functions/temporal_utils.ts +0 -180
- package/src/parsing/functions/time.ts +0 -58
- package/src/parsing/functions/timestamp.ts +0 -37
- package/src/parsing/functions/to_float.ts +0 -50
- package/src/parsing/functions/to_integer.ts +0 -50
- package/src/parsing/functions/to_json.ts +0 -28
- package/src/parsing/functions/to_lower.ts +0 -28
- package/src/parsing/functions/to_string.ts +0 -32
- package/src/parsing/functions/trim.ts +0 -28
- package/src/parsing/functions/type.ts +0 -39
- package/src/parsing/functions/value_holder.ts +0 -13
- package/src/parsing/logic/case.ts +0 -26
- package/src/parsing/logic/else.ts +0 -12
- package/src/parsing/logic/end.ts +0 -9
- package/src/parsing/logic/then.ts +0 -12
- package/src/parsing/logic/when.ts +0 -12
- package/src/parsing/operations/aggregated_return.ts +0 -22
- package/src/parsing/operations/aggregated_with.ts +0 -18
- package/src/parsing/operations/call.ts +0 -69
- package/src/parsing/operations/create_node.ts +0 -39
- package/src/parsing/operations/create_relationship.ts +0 -38
- package/src/parsing/operations/delete_node.ts +0 -33
- package/src/parsing/operations/delete_relationship.ts +0 -32
- package/src/parsing/operations/group_by.ts +0 -137
- package/src/parsing/operations/limit.ts +0 -31
- package/src/parsing/operations/load.ts +0 -146
- package/src/parsing/operations/match.ts +0 -54
- package/src/parsing/operations/operation.ts +0 -69
- package/src/parsing/operations/order_by.ts +0 -126
- package/src/parsing/operations/projection.ts +0 -18
- package/src/parsing/operations/return.ts +0 -76
- package/src/parsing/operations/union.ts +0 -114
- package/src/parsing/operations/union_all.ts +0 -16
- package/src/parsing/operations/unwind.ts +0 -36
- package/src/parsing/operations/where.ts +0 -42
- package/src/parsing/operations/with.ts +0 -20
- package/src/parsing/parser.ts +0 -1641
- package/src/parsing/parser_state.ts +0 -25
- package/src/parsing/token_to_node.ts +0 -114
- package/src/tokenization/keyword.ts +0 -50
- package/src/tokenization/operator.ts +0 -25
- package/src/tokenization/string_walker.ts +0 -197
- package/src/tokenization/symbol.ts +0 -15
- package/src/tokenization/token.ts +0 -764
- package/src/tokenization/token_mapper.ts +0 -53
- package/src/tokenization/token_type.ts +0 -16
- package/src/tokenization/tokenizer.ts +0 -250
- package/src/tokenization/trie.ts +0 -117
- package/src/utils/object_utils.ts +0 -17
- package/src/utils/string_utils.ts +0 -114
- package/tests/compute/runner.test.ts +0 -4559
- package/tests/extensibility.test.ts +0 -643
- package/tests/graph/create.test.ts +0 -36
- package/tests/graph/data.test.ts +0 -58
- package/tests/graph/match.test.ts +0 -29
- package/tests/parsing/context.test.ts +0 -27
- package/tests/parsing/expression.test.ts +0 -303
- package/tests/parsing/parser.test.ts +0 -1327
- package/tests/tokenization/token_mapper.test.ts +0 -47
- package/tests/tokenization/tokenizer.test.ts +0 -191
- package/tests/tokenization/trie.test.ts +0 -20
- package/tsconfig.json +0 -19
- package/typedoc.json +0 -16
- package/vscode-settings.json.recommended +0 -16
- 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
|
-
});
|