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,1237 +0,0 @@
|
|
|
1
|
-
"""Tests for the FlowQuery parser."""
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from typing import AsyncIterator
|
|
5
|
-
from flowquery.parsing.parser import Parser
|
|
6
|
-
from flowquery.parsing.functions.async_function import AsyncFunction
|
|
7
|
-
from flowquery.parsing.functions.function_metadata import FunctionDef
|
|
8
|
-
from flowquery.parsing.operations.match import Match
|
|
9
|
-
from flowquery.parsing.operations.create_relationship import CreateRelationship
|
|
10
|
-
from flowquery.graph.node import Node
|
|
11
|
-
from flowquery.graph.node_reference import NodeReference
|
|
12
|
-
from flowquery.graph.relationship import Relationship
|
|
13
|
-
from flowquery.graph.relationship_reference import RelationshipReference
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# Test async function for CALL operation parsing test
|
|
17
|
-
# Named with underscore prefix to prevent pytest from trying to collect it as a test class
|
|
18
|
-
@FunctionDef({
|
|
19
|
-
"description": "Asynchronous function for testing CALL operation",
|
|
20
|
-
"category": "async",
|
|
21
|
-
"parameters": [],
|
|
22
|
-
"output": {"description": "Yields test values", "type": "any"},
|
|
23
|
-
})
|
|
24
|
-
class _Test(AsyncFunction):
|
|
25
|
-
"""Async function for CALL operation testing, registered as 'test'."""
|
|
26
|
-
|
|
27
|
-
def __init__(self):
|
|
28
|
-
super().__init__("test") # Register as 'test'
|
|
29
|
-
self._expected_parameter_count = 0
|
|
30
|
-
|
|
31
|
-
async def generate(self) -> AsyncIterator:
|
|
32
|
-
yield 1
|
|
33
|
-
yield 2
|
|
34
|
-
yield 3
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class TestParser:
|
|
38
|
-
"""Test cases for the Parser class."""
|
|
39
|
-
|
|
40
|
-
def test_parser_basic(self):
|
|
41
|
-
"""Test basic parser functionality."""
|
|
42
|
-
parser = Parser()
|
|
43
|
-
ast = parser.parse("RETURN 1, 2, 3")
|
|
44
|
-
expected = (
|
|
45
|
-
"ASTNode\n"
|
|
46
|
-
"- Return\n"
|
|
47
|
-
"-- Expression\n"
|
|
48
|
-
"--- Number (1)\n"
|
|
49
|
-
"-- Expression\n"
|
|
50
|
-
"--- Number (2)\n"
|
|
51
|
-
"-- Expression\n"
|
|
52
|
-
"--- Number (3)"
|
|
53
|
-
)
|
|
54
|
-
assert ast.print() == expected
|
|
55
|
-
|
|
56
|
-
def test_parser_with_function(self):
|
|
57
|
-
"""Test parser with function."""
|
|
58
|
-
parser = Parser()
|
|
59
|
-
ast = parser.parse("RETURN rand()")
|
|
60
|
-
expected = (
|
|
61
|
-
"ASTNode\n"
|
|
62
|
-
"- Return\n"
|
|
63
|
-
"-- Expression\n"
|
|
64
|
-
"--- Function (rand)"
|
|
65
|
-
)
|
|
66
|
-
assert ast.print() == expected
|
|
67
|
-
|
|
68
|
-
def test_parser_with_associative_array(self):
|
|
69
|
-
"""Test parser with associative array."""
|
|
70
|
-
parser = Parser()
|
|
71
|
-
ast = parser.parse("RETURN {a: 1, b: 2}")
|
|
72
|
-
expected = (
|
|
73
|
-
"ASTNode\n"
|
|
74
|
-
"- Return\n"
|
|
75
|
-
"-- Expression\n"
|
|
76
|
-
"--- AssociativeArray\n"
|
|
77
|
-
"---- KeyValuePair\n"
|
|
78
|
-
"----- String (a)\n"
|
|
79
|
-
"----- Expression\n"
|
|
80
|
-
"------ Number (1)\n"
|
|
81
|
-
"---- KeyValuePair\n"
|
|
82
|
-
"----- String (b)\n"
|
|
83
|
-
"----- Expression\n"
|
|
84
|
-
"------ Number (2)"
|
|
85
|
-
)
|
|
86
|
-
assert ast.print() == expected
|
|
87
|
-
|
|
88
|
-
def test_parser_with_json_array(self):
|
|
89
|
-
"""Test parser with JSON array."""
|
|
90
|
-
parser = Parser()
|
|
91
|
-
ast = parser.parse("RETURN [1, 2]")
|
|
92
|
-
expected = (
|
|
93
|
-
"ASTNode\n"
|
|
94
|
-
"- Return\n"
|
|
95
|
-
"-- Expression\n"
|
|
96
|
-
"--- JSONArray\n"
|
|
97
|
-
"---- Expression\n"
|
|
98
|
-
"----- Number (1)\n"
|
|
99
|
-
"---- Expression\n"
|
|
100
|
-
"----- Number (2)"
|
|
101
|
-
)
|
|
102
|
-
assert ast.print() == expected
|
|
103
|
-
|
|
104
|
-
def test_parser_with_nested_associative_array(self):
|
|
105
|
-
"""Test parser with nested associative array."""
|
|
106
|
-
parser = Parser()
|
|
107
|
-
ast = parser.parse("RETURN {a:{}}")
|
|
108
|
-
expected = (
|
|
109
|
-
"ASTNode\n"
|
|
110
|
-
"- Return\n"
|
|
111
|
-
"-- Expression\n"
|
|
112
|
-
"--- AssociativeArray\n"
|
|
113
|
-
"---- KeyValuePair\n"
|
|
114
|
-
"----- String (a)\n"
|
|
115
|
-
"----- Expression\n"
|
|
116
|
-
"------ AssociativeArray"
|
|
117
|
-
)
|
|
118
|
-
assert ast.print() == expected
|
|
119
|
-
|
|
120
|
-
def test_parser_with_multiple_operations(self):
|
|
121
|
-
"""Test parser with multiple operations."""
|
|
122
|
-
parser = Parser()
|
|
123
|
-
ast = parser.parse("WITH 1 AS n RETURN n")
|
|
124
|
-
expected = (
|
|
125
|
-
"ASTNode\n"
|
|
126
|
-
"- With\n"
|
|
127
|
-
"-- Expression (n)\n"
|
|
128
|
-
"--- Number (1)\n"
|
|
129
|
-
"- Return\n"
|
|
130
|
-
"-- Expression (n)\n"
|
|
131
|
-
"--- Reference (n)"
|
|
132
|
-
)
|
|
133
|
-
assert ast.print() == expected
|
|
134
|
-
|
|
135
|
-
def test_parser_with_comments(self):
|
|
136
|
-
"""Test parser with comments."""
|
|
137
|
-
parser = Parser()
|
|
138
|
-
ast = parser.parse("WITH 1 AS n /* comment */ RETURN n")
|
|
139
|
-
expected = (
|
|
140
|
-
"ASTNode\n"
|
|
141
|
-
"- With\n"
|
|
142
|
-
"-- Expression (n)\n"
|
|
143
|
-
"--- Number (1)\n"
|
|
144
|
-
"- Return\n"
|
|
145
|
-
"-- Expression (n)\n"
|
|
146
|
-
"--- Reference (n)"
|
|
147
|
-
)
|
|
148
|
-
assert ast.print() == expected
|
|
149
|
-
|
|
150
|
-
def test_parser_with_unwind(self):
|
|
151
|
-
"""Test parser with UNWIND."""
|
|
152
|
-
parser = Parser()
|
|
153
|
-
ast = parser.parse("UNWIND [1, 2, 3] AS n RETURN n")
|
|
154
|
-
expected = (
|
|
155
|
-
"ASTNode\n"
|
|
156
|
-
"- Unwind\n"
|
|
157
|
-
"-- Expression (n)\n"
|
|
158
|
-
"--- JSONArray\n"
|
|
159
|
-
"---- Expression\n"
|
|
160
|
-
"----- Number (1)\n"
|
|
161
|
-
"---- Expression\n"
|
|
162
|
-
"----- Number (2)\n"
|
|
163
|
-
"---- Expression\n"
|
|
164
|
-
"----- Number (3)\n"
|
|
165
|
-
"- Return\n"
|
|
166
|
-
"-- Expression (n)\n"
|
|
167
|
-
"--- Reference (n)"
|
|
168
|
-
)
|
|
169
|
-
assert ast.print() == expected
|
|
170
|
-
|
|
171
|
-
def test_unwind_with_invalid_expression(self):
|
|
172
|
-
"""Test Unwind with invalid expression."""
|
|
173
|
-
parser = Parser()
|
|
174
|
-
with pytest.raises(Exception, match="Expected array, function, reference, or lookup"):
|
|
175
|
-
parser.parse("UNWIND 1 AS n RETURN n")
|
|
176
|
-
|
|
177
|
-
def test_unwind_with_invalid_alias(self):
|
|
178
|
-
"""Test Unwind with invalid alias."""
|
|
179
|
-
parser = Parser()
|
|
180
|
-
with pytest.raises(Exception, match="Expected identifier"):
|
|
181
|
-
parser.parse("UNWIND [1, 2, 3] AS 1 RETURN n")
|
|
182
|
-
|
|
183
|
-
def test_unwind_with_missing_alias(self):
|
|
184
|
-
"""Test Unwind with missing alias."""
|
|
185
|
-
parser = Parser()
|
|
186
|
-
with pytest.raises(Exception, match="Expected alias"):
|
|
187
|
-
parser.parse("UNWIND [1, 2, 3] RETURN n")
|
|
188
|
-
|
|
189
|
-
def test_statement_with_where_clause(self):
|
|
190
|
-
"""Test statement with where clause."""
|
|
191
|
-
parser = Parser()
|
|
192
|
-
ast = parser.parse("with 1 as n where n > 0 return n")
|
|
193
|
-
expected = (
|
|
194
|
-
"ASTNode\n"
|
|
195
|
-
"- With\n"
|
|
196
|
-
"-- Expression (n)\n"
|
|
197
|
-
"--- Number (1)\n"
|
|
198
|
-
"- Where\n"
|
|
199
|
-
"-- Expression\n"
|
|
200
|
-
"--- GreaterThan\n"
|
|
201
|
-
"---- Reference (n)\n"
|
|
202
|
-
"---- Number (0)\n"
|
|
203
|
-
"- Return\n"
|
|
204
|
-
"-- Expression (n)\n"
|
|
205
|
-
"--- Reference (n)"
|
|
206
|
-
)
|
|
207
|
-
assert ast.print() == expected
|
|
208
|
-
|
|
209
|
-
def test_lookup(self):
|
|
210
|
-
"""Test lookup expression."""
|
|
211
|
-
parser = Parser()
|
|
212
|
-
ast = parser.parse("return {a: 1}.a")
|
|
213
|
-
expected = (
|
|
214
|
-
"ASTNode\n"
|
|
215
|
-
"- Return\n"
|
|
216
|
-
"-- Expression\n"
|
|
217
|
-
"--- Lookup\n"
|
|
218
|
-
"---- Identifier (a)\n"
|
|
219
|
-
"---- AssociativeArray\n"
|
|
220
|
-
"----- KeyValuePair\n"
|
|
221
|
-
"------ String (a)\n"
|
|
222
|
-
"------ Expression\n"
|
|
223
|
-
"------- Number (1)"
|
|
224
|
-
)
|
|
225
|
-
assert ast.print() == expected
|
|
226
|
-
|
|
227
|
-
def test_lookup_as_part_of_expression(self):
|
|
228
|
-
"""Test lookup as part of expression."""
|
|
229
|
-
parser = Parser()
|
|
230
|
-
ast = parser.parse("return {a: 1}.a + 1")
|
|
231
|
-
expected = (
|
|
232
|
-
"ASTNode\n"
|
|
233
|
-
"- Return\n"
|
|
234
|
-
"-- Expression\n"
|
|
235
|
-
"--- Add\n"
|
|
236
|
-
"---- Lookup\n"
|
|
237
|
-
"----- Identifier (a)\n"
|
|
238
|
-
"----- AssociativeArray\n"
|
|
239
|
-
"------ KeyValuePair\n"
|
|
240
|
-
"------- String (a)\n"
|
|
241
|
-
"------- Expression\n"
|
|
242
|
-
"-------- Number (1)\n"
|
|
243
|
-
"---- Number (1)"
|
|
244
|
-
)
|
|
245
|
-
assert ast.print() == expected
|
|
246
|
-
|
|
247
|
-
def test_lookup_with_nested_associative_array(self):
|
|
248
|
-
"""Test lookup with nested associative array."""
|
|
249
|
-
parser = Parser()
|
|
250
|
-
ast = parser.parse("return {a: {b: 1}}.a.b")
|
|
251
|
-
expected = (
|
|
252
|
-
"ASTNode\n"
|
|
253
|
-
"- Return\n"
|
|
254
|
-
"-- Expression\n"
|
|
255
|
-
"--- Lookup\n"
|
|
256
|
-
"---- Identifier (b)\n"
|
|
257
|
-
"---- Lookup\n"
|
|
258
|
-
"----- Identifier (a)\n"
|
|
259
|
-
"----- AssociativeArray\n"
|
|
260
|
-
"------ KeyValuePair\n"
|
|
261
|
-
"------- String (a)\n"
|
|
262
|
-
"------- Expression\n"
|
|
263
|
-
"-------- AssociativeArray\n"
|
|
264
|
-
"--------- KeyValuePair\n"
|
|
265
|
-
"---------- String (b)\n"
|
|
266
|
-
"---------- Expression\n"
|
|
267
|
-
"----------- Number (1)"
|
|
268
|
-
)
|
|
269
|
-
assert ast.print() == expected
|
|
270
|
-
_return = ast.first_child()
|
|
271
|
-
assert _return.first_child().value() == 1
|
|
272
|
-
|
|
273
|
-
def test_lookup_with_json_array(self):
|
|
274
|
-
"""Test lookup with JSON array."""
|
|
275
|
-
parser = Parser()
|
|
276
|
-
ast = parser.parse("return [1, 2][1]")
|
|
277
|
-
expected = (
|
|
278
|
-
"ASTNode\n"
|
|
279
|
-
"- Return\n"
|
|
280
|
-
"-- Expression\n"
|
|
281
|
-
"--- Lookup\n"
|
|
282
|
-
"---- Expression\n"
|
|
283
|
-
"----- Number (1)\n"
|
|
284
|
-
"---- JSONArray\n"
|
|
285
|
-
"----- Expression\n"
|
|
286
|
-
"------ Number (1)\n"
|
|
287
|
-
"----- Expression\n"
|
|
288
|
-
"------ Number (2)"
|
|
289
|
-
)
|
|
290
|
-
assert ast.print() == expected
|
|
291
|
-
_return = ast.first_child()
|
|
292
|
-
assert _return.first_child().value() == 2
|
|
293
|
-
|
|
294
|
-
def test_lookup_with_reserved_keyword_property_names(self):
|
|
295
|
-
"""Test lookup with reserved keyword property names like end, null, case."""
|
|
296
|
-
parser = Parser()
|
|
297
|
-
ast = parser.parse("with {end: 1, null: 2, case: 3} as x return x.end, x.null, x.case")
|
|
298
|
-
expected = (
|
|
299
|
-
"ASTNode\n"
|
|
300
|
-
"- With\n"
|
|
301
|
-
"-- Expression (x)\n"
|
|
302
|
-
"--- AssociativeArray\n"
|
|
303
|
-
"---- KeyValuePair\n"
|
|
304
|
-
"----- String (end)\n"
|
|
305
|
-
"----- Expression\n"
|
|
306
|
-
"------ Number (1)\n"
|
|
307
|
-
"---- KeyValuePair\n"
|
|
308
|
-
"----- String (null)\n"
|
|
309
|
-
"----- Expression\n"
|
|
310
|
-
"------ Number (2)\n"
|
|
311
|
-
"---- KeyValuePair\n"
|
|
312
|
-
"----- String (case)\n"
|
|
313
|
-
"----- Expression\n"
|
|
314
|
-
"------ Number (3)\n"
|
|
315
|
-
"- Return\n"
|
|
316
|
-
"-- Expression\n"
|
|
317
|
-
"--- Lookup\n"
|
|
318
|
-
"---- Identifier (end)\n"
|
|
319
|
-
"---- Reference (x)\n"
|
|
320
|
-
"-- Expression\n"
|
|
321
|
-
"--- Lookup\n"
|
|
322
|
-
"---- Identifier (null)\n"
|
|
323
|
-
"---- Reference (x)\n"
|
|
324
|
-
"-- Expression\n"
|
|
325
|
-
"--- Lookup\n"
|
|
326
|
-
"---- Identifier (case)\n"
|
|
327
|
-
"---- Reference (x)"
|
|
328
|
-
)
|
|
329
|
-
assert ast.print() == expected
|
|
330
|
-
|
|
331
|
-
def test_lookup_with_from_keyword_as_property_name(self):
|
|
332
|
-
"""Test lookup with from and to keywords as property names."""
|
|
333
|
-
parser = Parser()
|
|
334
|
-
ast = parser.parse("with {from: 1, to: 2} as x return x.from, x.to")
|
|
335
|
-
expected = (
|
|
336
|
-
"ASTNode\n"
|
|
337
|
-
"- With\n"
|
|
338
|
-
"-- Expression (x)\n"
|
|
339
|
-
"--- AssociativeArray\n"
|
|
340
|
-
"---- KeyValuePair\n"
|
|
341
|
-
"----- String (from)\n"
|
|
342
|
-
"----- Expression\n"
|
|
343
|
-
"------ Number (1)\n"
|
|
344
|
-
"---- KeyValuePair\n"
|
|
345
|
-
"----- String (to)\n"
|
|
346
|
-
"----- Expression\n"
|
|
347
|
-
"------ Number (2)\n"
|
|
348
|
-
"- Return\n"
|
|
349
|
-
"-- Expression\n"
|
|
350
|
-
"--- Lookup\n"
|
|
351
|
-
"---- Identifier (from)\n"
|
|
352
|
-
"---- Reference (x)\n"
|
|
353
|
-
"-- Expression\n"
|
|
354
|
-
"--- Lookup\n"
|
|
355
|
-
"---- Identifier (to)\n"
|
|
356
|
-
"---- Reference (x)"
|
|
357
|
-
)
|
|
358
|
-
assert ast.print() == expected
|
|
359
|
-
|
|
360
|
-
def test_camel_case_alias_starting_with_keyword(self):
|
|
361
|
-
"""Test that camelCase identifiers starting with a keyword (e.g. fromUser) are tokenized correctly."""
|
|
362
|
-
parser = Parser()
|
|
363
|
-
ast = parser.parse("LOAD JSON FROM '/data.json' AS x RETURN x.from AS fromUser")
|
|
364
|
-
output = ast.print()
|
|
365
|
-
assert "Lookup" in output
|
|
366
|
-
assert "Identifier (from)" in output
|
|
367
|
-
|
|
368
|
-
def test_from_keyword_property_in_create_virtual_subquery(self):
|
|
369
|
-
"""Test that email.from parses correctly inside a CREATE VIRTUAL subquery."""
|
|
370
|
-
parser = Parser()
|
|
371
|
-
# Should not raise - email.from should be parsed correctly even with FROM being a keyword
|
|
372
|
-
parser.parse(
|
|
373
|
-
"CREATE VIRTUAL (:Email) AS { LOAD JSON FROM '/data/emails.json' AS email "
|
|
374
|
-
"RETURN email.id AS id, email.from AS fromUser }"
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
def test_load_with_post(self):
|
|
378
|
-
"""Test load with post."""
|
|
379
|
-
parser = Parser()
|
|
380
|
-
ast = parser.parse(
|
|
381
|
-
'load json from "https://jsonplaceholder.typicode.com/posts" post {userId: 1} as data return data'
|
|
382
|
-
)
|
|
383
|
-
expected = (
|
|
384
|
-
"ASTNode\n"
|
|
385
|
-
"- Load\n"
|
|
386
|
-
"-- JSON\n"
|
|
387
|
-
"-- From\n"
|
|
388
|
-
"--- Expression\n"
|
|
389
|
-
"---- String (https://jsonplaceholder.typicode.com/posts)\n"
|
|
390
|
-
"-- Post\n"
|
|
391
|
-
"--- Expression\n"
|
|
392
|
-
"---- AssociativeArray\n"
|
|
393
|
-
"----- KeyValuePair\n"
|
|
394
|
-
"------ String (userId)\n"
|
|
395
|
-
"------ Expression\n"
|
|
396
|
-
"------- Number (1)\n"
|
|
397
|
-
"-- Alias (data)\n"
|
|
398
|
-
"- Return\n"
|
|
399
|
-
"-- Expression (data)\n"
|
|
400
|
-
"--- Reference (data)"
|
|
401
|
-
)
|
|
402
|
-
assert ast.print() == expected
|
|
403
|
-
|
|
404
|
-
def test_nested_aggregate_functions(self):
|
|
405
|
-
"""Test nested aggregate functions."""
|
|
406
|
-
parser = Parser()
|
|
407
|
-
with pytest.raises(Exception, match="Aggregate functions cannot be nested"):
|
|
408
|
-
parser.parse("RETURN sum(sum(1))")
|
|
409
|
-
|
|
410
|
-
def test_with_and_return_with_renamed_variable(self):
|
|
411
|
-
"""Test with and return with renamed variable."""
|
|
412
|
-
parser = Parser()
|
|
413
|
-
ast = parser.parse("WITH 1 AS n RETURN n AS m")
|
|
414
|
-
expected = (
|
|
415
|
-
"ASTNode\n"
|
|
416
|
-
"- With\n"
|
|
417
|
-
"-- Expression (n)\n"
|
|
418
|
-
"--- Number (1)\n"
|
|
419
|
-
"- Return\n"
|
|
420
|
-
"-- Expression (m)\n"
|
|
421
|
-
"--- Reference (n)"
|
|
422
|
-
)
|
|
423
|
-
assert ast.print() == expected
|
|
424
|
-
|
|
425
|
-
def test_with_and_return_with_variable_lookup(self):
|
|
426
|
-
"""Test with and return with variable lookup."""
|
|
427
|
-
parser = Parser()
|
|
428
|
-
ast = parser.parse("WITH {a: n} AS obj RETURN obj.a")
|
|
429
|
-
expected = (
|
|
430
|
-
"ASTNode\n"
|
|
431
|
-
"- With\n"
|
|
432
|
-
"-- Expression (obj)\n"
|
|
433
|
-
"--- AssociativeArray\n"
|
|
434
|
-
"---- KeyValuePair\n"
|
|
435
|
-
"----- String (a)\n"
|
|
436
|
-
"----- Expression\n"
|
|
437
|
-
"------ Reference (n)\n"
|
|
438
|
-
"- Return\n"
|
|
439
|
-
"-- Expression\n"
|
|
440
|
-
"--- Lookup\n"
|
|
441
|
-
"---- Identifier (a)\n"
|
|
442
|
-
"---- Reference (obj)"
|
|
443
|
-
)
|
|
444
|
-
assert ast.print() == expected
|
|
445
|
-
|
|
446
|
-
def test_unwind(self):
|
|
447
|
-
"""Test unwind."""
|
|
448
|
-
parser = Parser()
|
|
449
|
-
ast = parser.parse("WITH [1, 2, 4] as n unwind n as i return i")
|
|
450
|
-
expected = (
|
|
451
|
-
"ASTNode\n"
|
|
452
|
-
"- With\n"
|
|
453
|
-
"-- Expression (n)\n"
|
|
454
|
-
"--- JSONArray\n"
|
|
455
|
-
"---- Expression\n"
|
|
456
|
-
"----- Number (1)\n"
|
|
457
|
-
"---- Expression\n"
|
|
458
|
-
"----- Number (2)\n"
|
|
459
|
-
"---- Expression\n"
|
|
460
|
-
"----- Number (4)\n"
|
|
461
|
-
"- Unwind\n"
|
|
462
|
-
"-- Expression (i)\n"
|
|
463
|
-
"--- Reference (n)\n"
|
|
464
|
-
"- Return\n"
|
|
465
|
-
"-- Expression (i)\n"
|
|
466
|
-
"--- Reference (i)"
|
|
467
|
-
)
|
|
468
|
-
assert ast.print() == expected
|
|
469
|
-
|
|
470
|
-
def test_predicate_function(self):
|
|
471
|
-
"""Test predicate function."""
|
|
472
|
-
parser = Parser()
|
|
473
|
-
ast = parser.parse("RETURN sum(n in [1, 2, 3] | n where n > 1)")
|
|
474
|
-
expected = (
|
|
475
|
-
"ASTNode\n"
|
|
476
|
-
"- Return\n"
|
|
477
|
-
"-- Expression\n"
|
|
478
|
-
"--- PredicateFunction (sum)\n"
|
|
479
|
-
"---- Reference (n)\n"
|
|
480
|
-
"---- Expression\n"
|
|
481
|
-
"----- JSONArray\n"
|
|
482
|
-
"------ Expression\n"
|
|
483
|
-
"------- Number (1)\n"
|
|
484
|
-
"------ Expression\n"
|
|
485
|
-
"------- Number (2)\n"
|
|
486
|
-
"------ Expression\n"
|
|
487
|
-
"------- Number (3)\n"
|
|
488
|
-
"---- Expression\n"
|
|
489
|
-
"----- Reference (n)\n"
|
|
490
|
-
"---- Where\n"
|
|
491
|
-
"----- Expression\n"
|
|
492
|
-
"------ GreaterThan\n"
|
|
493
|
-
"------- Reference (n)\n"
|
|
494
|
-
"------- Number (1)"
|
|
495
|
-
)
|
|
496
|
-
assert ast.print() == expected
|
|
497
|
-
|
|
498
|
-
def test_case_statement(self):
|
|
499
|
-
"""Test case statement."""
|
|
500
|
-
parser = Parser()
|
|
501
|
-
ast = parser.parse("RETURN CASE WHEN 1 THEN 2 ELSE 3 END")
|
|
502
|
-
expected = (
|
|
503
|
-
"ASTNode\n"
|
|
504
|
-
"- Return\n"
|
|
505
|
-
"-- Expression\n"
|
|
506
|
-
"--- Case\n"
|
|
507
|
-
"---- When\n"
|
|
508
|
-
"----- Expression\n"
|
|
509
|
-
"------ Number (1)\n"
|
|
510
|
-
"---- Then\n"
|
|
511
|
-
"----- Expression\n"
|
|
512
|
-
"------ Number (2)\n"
|
|
513
|
-
"---- Else\n"
|
|
514
|
-
"----- Expression\n"
|
|
515
|
-
"------ Number (3)"
|
|
516
|
-
)
|
|
517
|
-
assert ast.print() == expected
|
|
518
|
-
|
|
519
|
-
def test_functions_with_wrong_number_of_arguments(self):
|
|
520
|
-
"""Test functions with wrong number of arguments."""
|
|
521
|
-
parser = Parser()
|
|
522
|
-
with pytest.raises(Exception, match="Function range expected 2 parameters, but got 1"):
|
|
523
|
-
parser.parse("RETURN range(1)")
|
|
524
|
-
with pytest.raises(Exception, match="Function range expected 2 parameters, but got 3"):
|
|
525
|
-
parser.parse("RETURN range(1, 2, 3)")
|
|
526
|
-
with pytest.raises(Exception, match="Function avg expected 1 parameters, but got 3"):
|
|
527
|
-
parser.parse("RETURN avg(1, 2, 3)")
|
|
528
|
-
with pytest.raises(Exception, match="Function size expected 1 parameters, but got 2"):
|
|
529
|
-
parser.parse("RETURN size(1, 2)")
|
|
530
|
-
with pytest.raises(Exception, match="Function round expected 1 parameters, but got 2"):
|
|
531
|
-
parser.parse("RETURN round(1, 2)")
|
|
532
|
-
|
|
533
|
-
def test_non_well_formed_statements(self):
|
|
534
|
-
"""Test non-well formed statements."""
|
|
535
|
-
parser = Parser()
|
|
536
|
-
with pytest.raises(Exception, match="Only one RETURN statement is allowed"):
|
|
537
|
-
parser.parse("return 1 return 1")
|
|
538
|
-
# Note: Python implementation throws "Only one RETURN" for this case too
|
|
539
|
-
with pytest.raises(Exception, match="Only one RETURN statement is allowed"):
|
|
540
|
-
parser.parse("return 1 with 1 as n")
|
|
541
|
-
|
|
542
|
-
def test_associative_array_with_backtick_string(self):
|
|
543
|
-
"""Test associative array with backtick string."""
|
|
544
|
-
parser = Parser()
|
|
545
|
-
ast = parser.parse("RETURN {`key`: `value`}")
|
|
546
|
-
expected = (
|
|
547
|
-
"ASTNode\n"
|
|
548
|
-
"- Return\n"
|
|
549
|
-
"-- Expression\n"
|
|
550
|
-
"--- AssociativeArray\n"
|
|
551
|
-
"---- KeyValuePair\n"
|
|
552
|
-
"----- String (key)\n"
|
|
553
|
-
"----- Expression\n"
|
|
554
|
-
"------ Reference (value)"
|
|
555
|
-
)
|
|
556
|
-
assert ast.print() == expected
|
|
557
|
-
|
|
558
|
-
def test_limit(self):
|
|
559
|
-
"""Test limit."""
|
|
560
|
-
parser = Parser()
|
|
561
|
-
ast = parser.parse("unwind range(1, 10) as n limit 5 return n")
|
|
562
|
-
expected = (
|
|
563
|
-
"ASTNode\n"
|
|
564
|
-
"- Unwind\n"
|
|
565
|
-
"-- Expression (n)\n"
|
|
566
|
-
"--- Function (range)\n"
|
|
567
|
-
"---- Expression\n"
|
|
568
|
-
"----- Number (1)\n"
|
|
569
|
-
"---- Expression\n"
|
|
570
|
-
"----- Number (10)\n"
|
|
571
|
-
"- Limit\n"
|
|
572
|
-
"- Return\n"
|
|
573
|
-
"-- Expression (n)\n"
|
|
574
|
-
"--- Reference (n)"
|
|
575
|
-
)
|
|
576
|
-
assert ast.print() == expected
|
|
577
|
-
|
|
578
|
-
def test_return_negative_number(self):
|
|
579
|
-
"""Test return -2."""
|
|
580
|
-
parser = Parser()
|
|
581
|
-
ast = parser.parse("return -2")
|
|
582
|
-
expected = (
|
|
583
|
-
"ASTNode\n"
|
|
584
|
-
"- Return\n"
|
|
585
|
-
"-- Expression\n"
|
|
586
|
-
"--- Number (-2)"
|
|
587
|
-
)
|
|
588
|
-
assert ast.print() == expected
|
|
589
|
-
|
|
590
|
-
def test_call_operation(self):
|
|
591
|
-
"""Test call operation."""
|
|
592
|
-
parser = Parser()
|
|
593
|
-
ast = parser.parse("CALL test() YIELD result RETURN result")
|
|
594
|
-
expected = (
|
|
595
|
-
"ASTNode\n"
|
|
596
|
-
"- Call\n"
|
|
597
|
-
"-- Expression (result)\n"
|
|
598
|
-
"--- Reference (result)\n"
|
|
599
|
-
"- Return\n"
|
|
600
|
-
"-- Expression (result)\n"
|
|
601
|
-
"--- Reference (result)"
|
|
602
|
-
)
|
|
603
|
-
assert ast.print() == expected
|
|
604
|
-
|
|
605
|
-
def test_f_string(self):
|
|
606
|
-
"""Test f-string."""
|
|
607
|
-
parser = Parser()
|
|
608
|
-
ast = parser.parse("with 1 as value RETURN f'Value is: {value}.'")
|
|
609
|
-
expected = (
|
|
610
|
-
"ASTNode\n"
|
|
611
|
-
"- With\n"
|
|
612
|
-
"-- Expression (value)\n"
|
|
613
|
-
"--- Number (1)\n"
|
|
614
|
-
"- Return\n"
|
|
615
|
-
"-- Expression\n"
|
|
616
|
-
"--- FString\n"
|
|
617
|
-
"---- String (Value is: )\n"
|
|
618
|
-
"---- Expression\n"
|
|
619
|
-
"----- Reference (value)\n"
|
|
620
|
-
"---- String (.)"
|
|
621
|
-
)
|
|
622
|
-
assert ast.print() == expected
|
|
623
|
-
|
|
624
|
-
def test_not_equal_operator(self):
|
|
625
|
-
"""Test not equal operator."""
|
|
626
|
-
parser = Parser()
|
|
627
|
-
ast = parser.parse("RETURN 1 <> 2")
|
|
628
|
-
expected = (
|
|
629
|
-
"ASTNode\n"
|
|
630
|
-
"- Return\n"
|
|
631
|
-
"-- Expression\n"
|
|
632
|
-
"--- NotEquals\n"
|
|
633
|
-
"---- Number (1)\n"
|
|
634
|
-
"---- Number (2)"
|
|
635
|
-
)
|
|
636
|
-
assert ast.print() == expected
|
|
637
|
-
|
|
638
|
-
def test_equal_operator(self):
|
|
639
|
-
"""Test equal operator."""
|
|
640
|
-
parser = Parser()
|
|
641
|
-
ast = parser.parse("RETURN 1 = 2")
|
|
642
|
-
expected = (
|
|
643
|
-
"ASTNode\n"
|
|
644
|
-
"- Return\n"
|
|
645
|
-
"-- Expression\n"
|
|
646
|
-
"--- Equals\n"
|
|
647
|
-
"---- Number (1)\n"
|
|
648
|
-
"---- Number (2)"
|
|
649
|
-
)
|
|
650
|
-
assert ast.print() == expected
|
|
651
|
-
|
|
652
|
-
def test_not_operator(self):
|
|
653
|
-
"""Test not operator."""
|
|
654
|
-
parser = Parser()
|
|
655
|
-
ast = parser.parse("RETURN NOT true")
|
|
656
|
-
expected = (
|
|
657
|
-
"ASTNode\n"
|
|
658
|
-
"- Return\n"
|
|
659
|
-
"-- Expression\n"
|
|
660
|
-
"--- Not\n"
|
|
661
|
-
"---- Expression\n"
|
|
662
|
-
"----- Boolean"
|
|
663
|
-
)
|
|
664
|
-
assert ast.print() == expected
|
|
665
|
-
|
|
666
|
-
def test_create_node_operation(self):
|
|
667
|
-
"""Test create node operation."""
|
|
668
|
-
parser = Parser()
|
|
669
|
-
ast = parser.parse(
|
|
670
|
-
"""
|
|
671
|
-
CREATE VIRTUAL (:Person) AS {
|
|
672
|
-
unwind range(1, 3) AS id
|
|
673
|
-
return id, f'Person {id}' AS name
|
|
674
|
-
}
|
|
675
|
-
"""
|
|
676
|
-
)
|
|
677
|
-
expected = (
|
|
678
|
-
"ASTNode\n"
|
|
679
|
-
"- CreateNode"
|
|
680
|
-
)
|
|
681
|
-
assert ast.print() == expected
|
|
682
|
-
|
|
683
|
-
def test_match_operation(self):
|
|
684
|
-
"""Test match operation."""
|
|
685
|
-
parser = Parser()
|
|
686
|
-
ast = parser.parse("MATCH (n:Person) RETURN n")
|
|
687
|
-
expected = (
|
|
688
|
-
"ASTNode\n"
|
|
689
|
-
"- Match\n"
|
|
690
|
-
"- Return\n"
|
|
691
|
-
"-- Expression (n)\n"
|
|
692
|
-
"--- Reference (n)"
|
|
693
|
-
)
|
|
694
|
-
assert ast.print() == expected
|
|
695
|
-
|
|
696
|
-
def test_create_relationship_operation(self):
|
|
697
|
-
"""Test create relationship operation."""
|
|
698
|
-
parser = Parser()
|
|
699
|
-
ast = parser.parse(
|
|
700
|
-
"""
|
|
701
|
-
CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
|
|
702
|
-
unwind [
|
|
703
|
-
{from_id: 1, to_id: 2, since: '2020-01-01'},
|
|
704
|
-
{from_id: 2, to_id: 3, since: '2021-01-01'}
|
|
705
|
-
] AS pair
|
|
706
|
-
return pair.from_id AS left_id, pair.to_id AS right_id
|
|
707
|
-
}
|
|
708
|
-
"""
|
|
709
|
-
)
|
|
710
|
-
expected = (
|
|
711
|
-
"ASTNode\n"
|
|
712
|
-
"- CreateRelationship"
|
|
713
|
-
)
|
|
714
|
-
assert ast.print() == expected
|
|
715
|
-
|
|
716
|
-
def test_match_with_graph_pattern_including_relationships(self):
|
|
717
|
-
"""Test match with graph pattern including relationships."""
|
|
718
|
-
parser = Parser()
|
|
719
|
-
ast = parser.parse("MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a, b")
|
|
720
|
-
expected = (
|
|
721
|
-
"ASTNode\n"
|
|
722
|
-
"- Match\n"
|
|
723
|
-
"- Return\n"
|
|
724
|
-
"-- Expression (a)\n"
|
|
725
|
-
"--- Reference (a)\n"
|
|
726
|
-
"-- Expression (b)\n"
|
|
727
|
-
"--- Reference (b)"
|
|
728
|
-
)
|
|
729
|
-
assert ast.print() == expected
|
|
730
|
-
|
|
731
|
-
def test_parse_relationship_with_hops(self):
|
|
732
|
-
"""Test parse relationship with hops."""
|
|
733
|
-
parser = Parser()
|
|
734
|
-
ast = parser.parse("MATCH (a:Test)-[:KNOWS*1..3]->(b:Test) RETURN a, b")
|
|
735
|
-
expected = (
|
|
736
|
-
"ASTNode\n"
|
|
737
|
-
"- Match\n"
|
|
738
|
-
"- Return\n"
|
|
739
|
-
"-- Expression (a)\n"
|
|
740
|
-
"--- Reference (a)\n"
|
|
741
|
-
"-- Expression (b)\n"
|
|
742
|
-
"--- Reference (b)"
|
|
743
|
-
)
|
|
744
|
-
assert ast.print() == expected
|
|
745
|
-
|
|
746
|
-
def test_parse_statement_with_graph_pattern_in_where_clause(self):
|
|
747
|
-
"""Test parse statement with graph pattern in where clause."""
|
|
748
|
-
parser = Parser()
|
|
749
|
-
ast = parser.parse("MATCH (a:Person) WHERE (a)-[:KNOWS]->(:Person) RETURN a")
|
|
750
|
-
expected = (
|
|
751
|
-
"ASTNode\n"
|
|
752
|
-
"- Match\n"
|
|
753
|
-
"- Where\n"
|
|
754
|
-
"-- Expression\n"
|
|
755
|
-
"--- PatternExpression\n"
|
|
756
|
-
"---- NodeReference\n"
|
|
757
|
-
"---- Relationship\n"
|
|
758
|
-
"---- Node\n"
|
|
759
|
-
"- Return\n"
|
|
760
|
-
"-- Expression (a)\n"
|
|
761
|
-
"--- Reference (a)"
|
|
762
|
-
)
|
|
763
|
-
assert ast.print() == expected
|
|
764
|
-
|
|
765
|
-
def test_check_pattern_expression_without_noderef(self):
|
|
766
|
-
"""Test check pattern expression without NodeReference."""
|
|
767
|
-
parser = Parser()
|
|
768
|
-
with pytest.raises(Exception, match="PatternExpression must contain at least one NodeReference"):
|
|
769
|
-
parser.parse("MATCH (a:Person) WHERE (:Person)-[:KNOWS]->(:Person) RETURN a")
|
|
770
|
-
|
|
771
|
-
def test_node_with_properties(self):
|
|
772
|
-
"""Test node with properties."""
|
|
773
|
-
parser = Parser()
|
|
774
|
-
ast = parser.parse("MATCH (a:Person{value: 'hello'}) return a")
|
|
775
|
-
expected = (
|
|
776
|
-
"ASTNode\n"
|
|
777
|
-
"- Match\n"
|
|
778
|
-
"- Return\n"
|
|
779
|
-
"-- Expression (a)\n"
|
|
780
|
-
"--- Reference (a)"
|
|
781
|
-
)
|
|
782
|
-
assert ast.print() == expected
|
|
783
|
-
match_op = ast.first_child()
|
|
784
|
-
assert isinstance(match_op, Match)
|
|
785
|
-
node = match_op.patterns[0].chain[0]
|
|
786
|
-
assert isinstance(node, Node)
|
|
787
|
-
assert node.properties.get("value") is not None
|
|
788
|
-
assert node.properties["value"].value() == "hello"
|
|
789
|
-
|
|
790
|
-
def test_where_in_create_virtual_sub_query(self):
|
|
791
|
-
"""Test WHERE in CREATE VIRTUAL sub-query."""
|
|
792
|
-
parser = Parser()
|
|
793
|
-
ast = parser.parse(
|
|
794
|
-
"CREATE VIRTUAL (:Email)-[:HAS_ATTACHMENT]-(:File) AS {"
|
|
795
|
-
" LOAD JSON FROM '/data/emails.json' AS email"
|
|
796
|
-
" WHERE email.hasAttachments = true"
|
|
797
|
-
" UNWIND email.attachments AS fileId"
|
|
798
|
-
" RETURN email.id AS left_id, fileId AS right_id"
|
|
799
|
-
" }"
|
|
800
|
-
)
|
|
801
|
-
create = ast.first_child()
|
|
802
|
-
assert isinstance(create, CreateRelationship)
|
|
803
|
-
assert create.relationship is not None
|
|
804
|
-
assert create.relationship.type == "HAS_ATTACHMENT"
|
|
805
|
-
assert create.statement is not None
|
|
806
|
-
|
|
807
|
-
def test_relationship_with_properties(self):
|
|
808
|
-
"""Test relationship with properties."""
|
|
809
|
-
parser = Parser()
|
|
810
|
-
ast = parser.parse("MATCH (:Person)-[r:LIKES{since: 2022}]->(:Food) return a")
|
|
811
|
-
expected = (
|
|
812
|
-
"ASTNode\n"
|
|
813
|
-
"- Match\n"
|
|
814
|
-
"- Return\n"
|
|
815
|
-
"-- Expression (a)\n"
|
|
816
|
-
"--- Reference (a)"
|
|
817
|
-
)
|
|
818
|
-
assert ast.print() == expected
|
|
819
|
-
match_op = ast.first_child()
|
|
820
|
-
assert isinstance(match_op, Match)
|
|
821
|
-
relationship = match_op.patterns[0].chain[1]
|
|
822
|
-
assert isinstance(relationship, Relationship)
|
|
823
|
-
assert relationship.properties.get("since") is not None
|
|
824
|
-
assert relationship.properties["since"].value() == 2022
|
|
825
|
-
|
|
826
|
-
def test_node_reference_with_label_creates_node_reference(self):
|
|
827
|
-
"""Test that reusing a variable with a label creates a NodeReference instead of a new node."""
|
|
828
|
-
parser = Parser()
|
|
829
|
-
ast = parser.parse("MATCH (n:Person)-[:KNOWS]->(n:Person) RETURN n")
|
|
830
|
-
match_op = ast.first_child()
|
|
831
|
-
assert isinstance(match_op, Match)
|
|
832
|
-
first_node = match_op.patterns[0].chain[0]
|
|
833
|
-
second_node = match_op.patterns[0].chain[2]
|
|
834
|
-
assert isinstance(first_node, Node)
|
|
835
|
-
assert first_node.identifier == "n"
|
|
836
|
-
assert first_node.label == "Person"
|
|
837
|
-
assert isinstance(second_node, NodeReference)
|
|
838
|
-
assert second_node.reference.identifier == "n"
|
|
839
|
-
assert second_node.label == "Person"
|
|
840
|
-
|
|
841
|
-
def test_relationship_reference_with_type_creates_relationship_reference(self):
|
|
842
|
-
"""Test that reusing a relationship variable with a type creates a RelationshipReference."""
|
|
843
|
-
parser = Parser()
|
|
844
|
-
ast = parser.parse("MATCH (a:Person)-[r:KNOWS]->(b:Person)-[r:KNOWS]->(c:Person) RETURN a, b, c")
|
|
845
|
-
match_op = ast.first_child()
|
|
846
|
-
assert isinstance(match_op, Match)
|
|
847
|
-
first_rel = match_op.patterns[0].chain[1]
|
|
848
|
-
second_rel = match_op.patterns[0].chain[3]
|
|
849
|
-
assert isinstance(first_rel, Relationship)
|
|
850
|
-
assert first_rel.identifier == "r"
|
|
851
|
-
assert first_rel.type == "KNOWS"
|
|
852
|
-
assert isinstance(second_rel, RelationshipReference)
|
|
853
|
-
assert second_rel.type == "KNOWS"
|
|
854
|
-
|
|
855
|
-
def test_case_statement_with_keywords_as_identifiers(self):
|
|
856
|
-
"""Test that CASE/WHEN/THEN/ELSE/END are not treated as identifiers."""
|
|
857
|
-
parser = Parser()
|
|
858
|
-
ast = parser.parse("RETURN CASE WHEN 1 THEN 2 ELSE 3 END")
|
|
859
|
-
assert "Case" in ast.print()
|
|
860
|
-
assert "When" in ast.print()
|
|
861
|
-
assert "Then" in ast.print()
|
|
862
|
-
assert "Else" in ast.print()
|
|
863
|
-
|
|
864
|
-
def test_where_with_in_list_check(self):
|
|
865
|
-
"""Test WHERE with IN list check."""
|
|
866
|
-
parser = Parser()
|
|
867
|
-
ast = parser.parse("with 1 as n where n IN [1, 2, 3] return n")
|
|
868
|
-
expected = (
|
|
869
|
-
"ASTNode\n"
|
|
870
|
-
"- With\n"
|
|
871
|
-
"-- Expression (n)\n"
|
|
872
|
-
"--- Number (1)\n"
|
|
873
|
-
"- Where\n"
|
|
874
|
-
"-- Expression\n"
|
|
875
|
-
"--- In\n"
|
|
876
|
-
"---- Reference (n)\n"
|
|
877
|
-
"---- JSONArray\n"
|
|
878
|
-
"----- Expression\n"
|
|
879
|
-
"------ Number (1)\n"
|
|
880
|
-
"----- Expression\n"
|
|
881
|
-
"------ Number (2)\n"
|
|
882
|
-
"----- Expression\n"
|
|
883
|
-
"------ Number (3)\n"
|
|
884
|
-
"- Return\n"
|
|
885
|
-
"-- Expression (n)\n"
|
|
886
|
-
"--- Reference (n)"
|
|
887
|
-
)
|
|
888
|
-
assert ast.print() == expected
|
|
889
|
-
|
|
890
|
-
def test_where_with_not_in_list_check(self):
|
|
891
|
-
"""Test WHERE with NOT IN list check."""
|
|
892
|
-
parser = Parser()
|
|
893
|
-
ast = parser.parse("with 4 as n where n NOT IN [1, 2, 3] return n")
|
|
894
|
-
expected = (
|
|
895
|
-
"ASTNode\n"
|
|
896
|
-
"- With\n"
|
|
897
|
-
"-- Expression (n)\n"
|
|
898
|
-
"--- Number (4)\n"
|
|
899
|
-
"- Where\n"
|
|
900
|
-
"-- Expression\n"
|
|
901
|
-
"--- NotIn\n"
|
|
902
|
-
"---- Reference (n)\n"
|
|
903
|
-
"---- JSONArray\n"
|
|
904
|
-
"----- Expression\n"
|
|
905
|
-
"------ Number (1)\n"
|
|
906
|
-
"----- Expression\n"
|
|
907
|
-
"------ Number (2)\n"
|
|
908
|
-
"----- Expression\n"
|
|
909
|
-
"------ Number (3)\n"
|
|
910
|
-
"- Return\n"
|
|
911
|
-
"-- Expression (n)\n"
|
|
912
|
-
"--- Reference (n)"
|
|
913
|
-
)
|
|
914
|
-
assert ast.print() == expected
|
|
915
|
-
|
|
916
|
-
def test_where_with_contains(self):
|
|
917
|
-
"""Test WHERE with CONTAINS."""
|
|
918
|
-
parser = Parser()
|
|
919
|
-
ast = parser.parse("with 'hello' as s where s CONTAINS 'ell' return s")
|
|
920
|
-
expected = (
|
|
921
|
-
"ASTNode\n"
|
|
922
|
-
"- With\n"
|
|
923
|
-
"-- Expression (s)\n"
|
|
924
|
-
"--- String (hello)\n"
|
|
925
|
-
"- Where\n"
|
|
926
|
-
"-- Expression\n"
|
|
927
|
-
"--- Contains\n"
|
|
928
|
-
"---- Reference (s)\n"
|
|
929
|
-
"---- String (ell)\n"
|
|
930
|
-
"- Return\n"
|
|
931
|
-
"-- Expression (s)\n"
|
|
932
|
-
"--- Reference (s)"
|
|
933
|
-
)
|
|
934
|
-
assert ast.print() == expected
|
|
935
|
-
|
|
936
|
-
def test_where_with_not_contains(self):
|
|
937
|
-
"""Test WHERE with NOT CONTAINS."""
|
|
938
|
-
parser = Parser()
|
|
939
|
-
ast = parser.parse("with 'hello' as s where s NOT CONTAINS 'xyz' return s")
|
|
940
|
-
expected = (
|
|
941
|
-
"ASTNode\n"
|
|
942
|
-
"- With\n"
|
|
943
|
-
"-- Expression (s)\n"
|
|
944
|
-
"--- String (hello)\n"
|
|
945
|
-
"- Where\n"
|
|
946
|
-
"-- Expression\n"
|
|
947
|
-
"--- NotContains\n"
|
|
948
|
-
"---- Reference (s)\n"
|
|
949
|
-
"---- String (xyz)\n"
|
|
950
|
-
"- Return\n"
|
|
951
|
-
"-- Expression (s)\n"
|
|
952
|
-
"--- Reference (s)"
|
|
953
|
-
)
|
|
954
|
-
assert ast.print() == expected
|
|
955
|
-
|
|
956
|
-
def test_where_with_starts_with(self):
|
|
957
|
-
"""Test WHERE with STARTS WITH."""
|
|
958
|
-
parser = Parser()
|
|
959
|
-
ast = parser.parse("with 'hello' as s where s STARTS WITH 'hel' return s")
|
|
960
|
-
expected = (
|
|
961
|
-
"ASTNode\n"
|
|
962
|
-
"- With\n"
|
|
963
|
-
"-- Expression (s)\n"
|
|
964
|
-
"--- String (hello)\n"
|
|
965
|
-
"- Where\n"
|
|
966
|
-
"-- Expression\n"
|
|
967
|
-
"--- StartsWith\n"
|
|
968
|
-
"---- Reference (s)\n"
|
|
969
|
-
"---- String (hel)\n"
|
|
970
|
-
"- Return\n"
|
|
971
|
-
"-- Expression (s)\n"
|
|
972
|
-
"--- Reference (s)"
|
|
973
|
-
)
|
|
974
|
-
assert ast.print() == expected
|
|
975
|
-
|
|
976
|
-
def test_where_with_not_starts_with(self):
|
|
977
|
-
"""Test WHERE with NOT STARTS WITH."""
|
|
978
|
-
parser = Parser()
|
|
979
|
-
ast = parser.parse("with 'hello' as s where s NOT STARTS WITH 'xyz' return s")
|
|
980
|
-
expected = (
|
|
981
|
-
"ASTNode\n"
|
|
982
|
-
"- With\n"
|
|
983
|
-
"-- Expression (s)\n"
|
|
984
|
-
"--- String (hello)\n"
|
|
985
|
-
"- Where\n"
|
|
986
|
-
"-- Expression\n"
|
|
987
|
-
"--- NotStartsWith\n"
|
|
988
|
-
"---- Reference (s)\n"
|
|
989
|
-
"---- String (xyz)\n"
|
|
990
|
-
"- Return\n"
|
|
991
|
-
"-- Expression (s)\n"
|
|
992
|
-
"--- Reference (s)"
|
|
993
|
-
)
|
|
994
|
-
assert ast.print() == expected
|
|
995
|
-
|
|
996
|
-
def test_where_with_ends_with(self):
|
|
997
|
-
"""Test WHERE with ENDS WITH."""
|
|
998
|
-
parser = Parser()
|
|
999
|
-
ast = parser.parse("with 'hello' as s where s ENDS WITH 'llo' return s")
|
|
1000
|
-
expected = (
|
|
1001
|
-
"ASTNode\n"
|
|
1002
|
-
"- With\n"
|
|
1003
|
-
"-- Expression (s)\n"
|
|
1004
|
-
"--- String (hello)\n"
|
|
1005
|
-
"- Where\n"
|
|
1006
|
-
"-- Expression\n"
|
|
1007
|
-
"--- EndsWith\n"
|
|
1008
|
-
"---- Reference (s)\n"
|
|
1009
|
-
"---- String (llo)\n"
|
|
1010
|
-
"- Return\n"
|
|
1011
|
-
"-- Expression (s)\n"
|
|
1012
|
-
"--- Reference (s)"
|
|
1013
|
-
)
|
|
1014
|
-
assert ast.print() == expected
|
|
1015
|
-
|
|
1016
|
-
def test_where_with_not_ends_with(self):
|
|
1017
|
-
"""Test WHERE with NOT ENDS WITH."""
|
|
1018
|
-
parser = Parser()
|
|
1019
|
-
ast = parser.parse("with 'hello' as s where s NOT ENDS WITH 'xyz' return s")
|
|
1020
|
-
expected = (
|
|
1021
|
-
"ASTNode\n"
|
|
1022
|
-
"- With\n"
|
|
1023
|
-
"-- Expression (s)\n"
|
|
1024
|
-
"--- String (hello)\n"
|
|
1025
|
-
"- Where\n"
|
|
1026
|
-
"-- Expression\n"
|
|
1027
|
-
"--- NotEndsWith\n"
|
|
1028
|
-
"---- Reference (s)\n"
|
|
1029
|
-
"---- String (xyz)\n"
|
|
1030
|
-
"- Return\n"
|
|
1031
|
-
"-- Expression (s)\n"
|
|
1032
|
-
"--- Reference (s)"
|
|
1033
|
-
)
|
|
1034
|
-
assert ast.print() == expected
|
|
1035
|
-
|
|
1036
|
-
def test_parenthesized_expression_with_addition(self):
|
|
1037
|
-
"""Test that (variable + number) is parsed as a parenthesized expression, not a node."""
|
|
1038
|
-
parser = Parser()
|
|
1039
|
-
ast = parser.parse("WITH 1 AS n RETURN (n + 2)")
|
|
1040
|
-
expected = (
|
|
1041
|
-
"ASTNode\n"
|
|
1042
|
-
"- With\n"
|
|
1043
|
-
"-- Expression (n)\n"
|
|
1044
|
-
"--- Number (1)\n"
|
|
1045
|
-
"- Return\n"
|
|
1046
|
-
"-- Expression\n"
|
|
1047
|
-
"--- Expression\n"
|
|
1048
|
-
"---- Add\n"
|
|
1049
|
-
"----- Reference (n)\n"
|
|
1050
|
-
"----- Number (2)"
|
|
1051
|
-
)
|
|
1052
|
-
assert ast.print() == expected
|
|
1053
|
-
|
|
1054
|
-
def test_parenthesized_expression_with_property_access(self):
|
|
1055
|
-
"""Test that (obj.property) is parsed as a parenthesized expression, not a node."""
|
|
1056
|
-
parser = Parser()
|
|
1057
|
-
ast = parser.parse("WITH {a: 1} AS obj RETURN (obj.a)")
|
|
1058
|
-
expected = (
|
|
1059
|
-
"ASTNode\n"
|
|
1060
|
-
"- With\n"
|
|
1061
|
-
"-- Expression (obj)\n"
|
|
1062
|
-
"--- AssociativeArray\n"
|
|
1063
|
-
"---- KeyValuePair\n"
|
|
1064
|
-
"----- String (a)\n"
|
|
1065
|
-
"----- Expression\n"
|
|
1066
|
-
"------ Number (1)\n"
|
|
1067
|
-
"- Return\n"
|
|
1068
|
-
"-- Expression\n"
|
|
1069
|
-
"--- Expression\n"
|
|
1070
|
-
"---- Lookup\n"
|
|
1071
|
-
"----- Identifier (a)\n"
|
|
1072
|
-
"----- Reference (obj)"
|
|
1073
|
-
)
|
|
1074
|
-
assert ast.print() == expected
|
|
1075
|
-
|
|
1076
|
-
def test_parenthesized_expression_with_multiplication(self):
|
|
1077
|
-
"""Test that (variable * number) is parsed as a parenthesized expression, not a node."""
|
|
1078
|
-
parser = Parser()
|
|
1079
|
-
ast = parser.parse("WITH 5 AS x RETURN (x * 3)")
|
|
1080
|
-
expected = (
|
|
1081
|
-
"ASTNode\n"
|
|
1082
|
-
"- With\n"
|
|
1083
|
-
"-- Expression (x)\n"
|
|
1084
|
-
"--- Number (5)\n"
|
|
1085
|
-
"- Return\n"
|
|
1086
|
-
"-- Expression\n"
|
|
1087
|
-
"--- Expression\n"
|
|
1088
|
-
"---- Multiply\n"
|
|
1089
|
-
"----- Reference (x)\n"
|
|
1090
|
-
"----- Number (3)"
|
|
1091
|
-
)
|
|
1092
|
-
assert ast.print() == expected
|
|
1093
|
-
|
|
1094
|
-
def test_optional_match_operation(self):
|
|
1095
|
-
"""Test optional match operation."""
|
|
1096
|
-
parser = Parser()
|
|
1097
|
-
ast = parser.parse("OPTIONAL MATCH (n:Person) RETURN n")
|
|
1098
|
-
expected = (
|
|
1099
|
-
"ASTNode\n"
|
|
1100
|
-
"- OptionalMatch\n"
|
|
1101
|
-
"- Return\n"
|
|
1102
|
-
"-- Expression (n)\n"
|
|
1103
|
-
"--- Reference (n)"
|
|
1104
|
-
)
|
|
1105
|
-
assert ast.print() == expected
|
|
1106
|
-
match = ast.first_child()
|
|
1107
|
-
assert isinstance(match, Match)
|
|
1108
|
-
assert match.optional is True
|
|
1109
|
-
assert match.patterns[0].start_node is not None
|
|
1110
|
-
assert match.patterns[0].start_node.label == "Person"
|
|
1111
|
-
assert match.patterns[0].start_node.identifier == "n"
|
|
1112
|
-
|
|
1113
|
-
def test_optional_match_with_relationships(self):
|
|
1114
|
-
"""Test optional match with graph pattern including relationships."""
|
|
1115
|
-
parser = Parser()
|
|
1116
|
-
ast = parser.parse("OPTIONAL MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a, b")
|
|
1117
|
-
expected = (
|
|
1118
|
-
"ASTNode\n"
|
|
1119
|
-
"- OptionalMatch\n"
|
|
1120
|
-
"- Return\n"
|
|
1121
|
-
"-- Expression (a)\n"
|
|
1122
|
-
"--- Reference (a)\n"
|
|
1123
|
-
"-- Expression (b)\n"
|
|
1124
|
-
"--- Reference (b)"
|
|
1125
|
-
)
|
|
1126
|
-
assert ast.print() == expected
|
|
1127
|
-
match = ast.first_child()
|
|
1128
|
-
assert isinstance(match, Match)
|
|
1129
|
-
assert match.optional is True
|
|
1130
|
-
assert len(match.patterns[0].chain) == 3
|
|
1131
|
-
source = match.patterns[0].chain[0]
|
|
1132
|
-
relationship = match.patterns[0].chain[1]
|
|
1133
|
-
target = match.patterns[0].chain[2]
|
|
1134
|
-
assert source.identifier == "a"
|
|
1135
|
-
assert source.label == "Person"
|
|
1136
|
-
assert relationship.type == "KNOWS"
|
|
1137
|
-
assert target.identifier == "b"
|
|
1138
|
-
assert target.label == "Person"
|
|
1139
|
-
|
|
1140
|
-
def test_match_followed_by_optional_match(self):
|
|
1141
|
-
"""Test match followed by optional match."""
|
|
1142
|
-
parser = Parser()
|
|
1143
|
-
ast = parser.parse("MATCH (a:Person) OPTIONAL MATCH (a)-[:KNOWS]->(b:Person) RETURN a, b")
|
|
1144
|
-
expected = (
|
|
1145
|
-
"ASTNode\n"
|
|
1146
|
-
"- Match\n"
|
|
1147
|
-
"- OptionalMatch\n"
|
|
1148
|
-
"- Return\n"
|
|
1149
|
-
"-- Expression (a)\n"
|
|
1150
|
-
"--- Reference (a)\n"
|
|
1151
|
-
"-- Expression (b)\n"
|
|
1152
|
-
"--- Reference (b)"
|
|
1153
|
-
)
|
|
1154
|
-
assert ast.print() == expected
|
|
1155
|
-
match = ast.first_child()
|
|
1156
|
-
assert isinstance(match, Match)
|
|
1157
|
-
assert match.optional is False
|
|
1158
|
-
optional_match = match.next
|
|
1159
|
-
assert isinstance(optional_match, Match)
|
|
1160
|
-
assert optional_match.optional is True
|
|
1161
|
-
|
|
1162
|
-
def test_regular_match_is_not_optional(self):
|
|
1163
|
-
"""Test that regular match is not optional."""
|
|
1164
|
-
parser = Parser()
|
|
1165
|
-
ast = parser.parse("MATCH (n:Person) RETURN n")
|
|
1166
|
-
match = ast.first_child()
|
|
1167
|
-
assert isinstance(match, Match)
|
|
1168
|
-
assert match.optional is False
|
|
1169
|
-
|
|
1170
|
-
def test_optional_without_match_throws_error(self):
|
|
1171
|
-
"""Test that OPTIONAL without MATCH throws error."""
|
|
1172
|
-
parser = Parser()
|
|
1173
|
-
with pytest.raises(Exception, match="Expected MATCH after OPTIONAL"):
|
|
1174
|
-
parser.parse("OPTIONAL RETURN 1")
|
|
1175
|
-
|
|
1176
|
-
# ORDER BY expression tests
|
|
1177
|
-
|
|
1178
|
-
def test_order_by_simple_identifier(self):
|
|
1179
|
-
"""Test ORDER BY with a simple identifier parses correctly."""
|
|
1180
|
-
parser = Parser()
|
|
1181
|
-
ast = parser.parse("unwind [1, 2] as x return x order by x")
|
|
1182
|
-
assert ast is not None
|
|
1183
|
-
|
|
1184
|
-
def test_order_by_property_access(self):
|
|
1185
|
-
"""Test ORDER BY with property access parses correctly."""
|
|
1186
|
-
parser = Parser()
|
|
1187
|
-
ast = parser.parse(
|
|
1188
|
-
"unwind [{name: 'Bob'}, {name: 'Alice'}] as person "
|
|
1189
|
-
"return person.name as name order by person.name asc"
|
|
1190
|
-
)
|
|
1191
|
-
assert ast is not None
|
|
1192
|
-
|
|
1193
|
-
def test_order_by_function_call(self):
|
|
1194
|
-
"""Test ORDER BY with function call parses correctly."""
|
|
1195
|
-
parser = Parser()
|
|
1196
|
-
ast = parser.parse(
|
|
1197
|
-
"unwind ['HELLO', 'WORLD'] as word "
|
|
1198
|
-
"return word order by toLower(word) asc"
|
|
1199
|
-
)
|
|
1200
|
-
assert ast is not None
|
|
1201
|
-
|
|
1202
|
-
def test_order_by_nested_function_calls(self):
|
|
1203
|
-
"""Test ORDER BY with nested function calls parses correctly."""
|
|
1204
|
-
parser = Parser()
|
|
1205
|
-
ast = parser.parse(
|
|
1206
|
-
"unwind ['Alice', 'Bob'] as name "
|
|
1207
|
-
"return name order by string_distance(toLower(name), toLower('alice')) asc"
|
|
1208
|
-
)
|
|
1209
|
-
assert ast is not None
|
|
1210
|
-
|
|
1211
|
-
def test_order_by_arithmetic_expression(self):
|
|
1212
|
-
"""Test ORDER BY with arithmetic expression parses correctly."""
|
|
1213
|
-
parser = Parser()
|
|
1214
|
-
ast = parser.parse(
|
|
1215
|
-
"unwind [{a: 3, b: 1}, {a: 1, b: 5}] as item "
|
|
1216
|
-
"return item.a as a, item.b as b order by item.a + item.b desc"
|
|
1217
|
-
)
|
|
1218
|
-
assert ast is not None
|
|
1219
|
-
|
|
1220
|
-
def test_order_by_multiple_expression_fields(self):
|
|
1221
|
-
"""Test ORDER BY with multiple expression fields parses correctly."""
|
|
1222
|
-
parser = Parser()
|
|
1223
|
-
ast = parser.parse(
|
|
1224
|
-
"unwind [{a: 1, b: 2}] as item "
|
|
1225
|
-
"return item.a as a, item.b as b "
|
|
1226
|
-
"order by toLower(item.a) asc, item.b desc"
|
|
1227
|
-
)
|
|
1228
|
-
assert ast is not None
|
|
1229
|
-
|
|
1230
|
-
def test_order_by_expression_with_limit(self):
|
|
1231
|
-
"""Test ORDER BY with expression and LIMIT parses correctly."""
|
|
1232
|
-
parser = Parser()
|
|
1233
|
-
ast = parser.parse(
|
|
1234
|
-
"unwind ['c', 'a', 'b'] as x "
|
|
1235
|
-
"return x order by toLower(x) asc limit 2"
|
|
1236
|
-
)
|
|
1237
|
-
assert ast is not None
|