flowquery 1.0.16 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/python-publish.yml +97 -0
- package/dist/compute/runner.d.ts +3 -2
- package/dist/compute/runner.d.ts.map +1 -1
- package/dist/compute/runner.js +7 -7
- package/dist/compute/runner.js.map +1 -1
- package/dist/flowquery.min.js +1 -1
- package/dist/graph/data.d.ts +31 -0
- package/dist/graph/data.d.ts.map +1 -0
- package/dist/graph/data.js +110 -0
- package/dist/graph/data.js.map +1 -0
- package/dist/graph/database.d.ts +20 -0
- package/dist/graph/database.d.ts.map +1 -0
- package/dist/graph/database.js +77 -0
- package/dist/graph/database.js.map +1 -0
- package/dist/graph/hops.d.ts +11 -0
- package/dist/graph/hops.d.ts.map +1 -0
- package/dist/graph/hops.js +25 -0
- package/dist/graph/hops.js.map +1 -0
- package/dist/graph/node.d.ts +35 -0
- package/dist/graph/node.d.ts.map +1 -0
- package/dist/graph/node.js +113 -0
- package/dist/graph/node.js.map +1 -0
- package/dist/graph/node_data.d.ts +11 -0
- package/dist/graph/node_data.d.ts.map +1 -0
- package/dist/graph/node_data.js +20 -0
- package/dist/graph/node_data.js.map +1 -0
- package/dist/graph/node_reference.d.ts +10 -0
- package/dist/graph/node_reference.d.ts.map +1 -0
- package/dist/graph/node_reference.js +52 -0
- package/dist/graph/node_reference.js.map +1 -0
- package/dist/graph/pattern.d.ts +18 -0
- package/dist/graph/pattern.d.ts.map +1 -0
- package/dist/graph/pattern.js +114 -0
- package/dist/graph/pattern.js.map +1 -0
- package/dist/graph/pattern_expression.d.ts +14 -0
- package/dist/graph/pattern_expression.d.ts.map +1 -0
- package/dist/graph/pattern_expression.js +58 -0
- package/dist/graph/pattern_expression.js.map +1 -0
- package/dist/graph/patterns.d.ts +11 -0
- package/dist/graph/patterns.d.ts.map +1 -0
- package/dist/graph/patterns.js +49 -0
- package/dist/graph/patterns.js.map +1 -0
- package/dist/graph/physical_node.d.ts +10 -0
- package/dist/graph/physical_node.d.ts.map +1 -0
- package/dist/graph/physical_node.js +40 -0
- package/dist/graph/physical_node.js.map +1 -0
- package/dist/graph/physical_relationship.d.ts +10 -0
- package/dist/graph/physical_relationship.d.ts.map +1 -0
- package/dist/graph/physical_relationship.js +40 -0
- package/dist/graph/physical_relationship.js.map +1 -0
- package/dist/graph/relationship.d.ts +40 -0
- package/dist/graph/relationship.d.ts.map +1 -0
- package/dist/graph/relationship.js +124 -0
- package/dist/graph/relationship.js.map +1 -0
- package/dist/graph/relationship_data.d.ts +12 -0
- package/dist/graph/relationship_data.d.ts.map +1 -0
- package/dist/graph/relationship_data.js +40 -0
- package/dist/graph/relationship_data.js.map +1 -0
- package/dist/graph/relationship_match_collector.d.ts +19 -0
- package/dist/graph/relationship_match_collector.d.ts.map +1 -0
- package/dist/graph/relationship_match_collector.js +55 -0
- package/dist/graph/relationship_match_collector.js.map +1 -0
- package/dist/graph/relationship_reference.d.ts +8 -0
- package/dist/graph/relationship_reference.d.ts.map +1 -0
- package/dist/graph/relationship_reference.js +37 -0
- package/dist/graph/relationship_reference.js.map +1 -0
- package/dist/parsing/base_parser.d.ts +1 -0
- package/dist/parsing/base_parser.d.ts.map +1 -1
- package/dist/parsing/base_parser.js +4 -1
- package/dist/parsing/base_parser.js.map +1 -1
- package/dist/parsing/context.d.ts +2 -2
- package/dist/parsing/context.js +5 -5
- package/dist/parsing/expressions/boolean.d.ts +8 -0
- package/dist/parsing/expressions/boolean.d.ts.map +1 -0
- package/dist/parsing/expressions/boolean.js +26 -0
- package/dist/parsing/expressions/boolean.js.map +1 -0
- package/dist/parsing/expressions/expression.d.ts +4 -1
- package/dist/parsing/expressions/expression.d.ts.map +1 -1
- package/dist/parsing/expressions/expression.js +15 -8
- package/dist/parsing/expressions/expression.js.map +1 -1
- package/dist/parsing/expressions/operator.d.ts +1 -1
- package/dist/parsing/expressions/operator.d.ts.map +1 -1
- package/dist/parsing/expressions/operator.js.map +1 -1
- package/dist/parsing/functions/function_factory.d.ts +13 -13
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +20 -18
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/operations/create_node.d.ts +14 -0
- package/dist/parsing/operations/create_node.d.ts.map +1 -0
- package/dist/parsing/operations/create_node.js +51 -0
- package/dist/parsing/operations/create_node.js.map +1 -0
- package/dist/parsing/operations/create_relationship.d.ts +14 -0
- package/dist/parsing/operations/create_relationship.d.ts.map +1 -0
- package/dist/parsing/operations/create_relationship.js +51 -0
- package/dist/parsing/operations/create_relationship.js.map +1 -0
- package/dist/parsing/operations/match.d.ts +15 -0
- package/dist/parsing/operations/match.d.ts.map +1 -0
- package/dist/parsing/operations/match.js +45 -0
- package/dist/parsing/operations/match.js.map +1 -0
- package/dist/parsing/operations/operation.d.ts +1 -0
- package/dist/parsing/operations/operation.d.ts.map +1 -1
- package/dist/parsing/operations/operation.js +6 -0
- package/dist/parsing/operations/operation.js.map +1 -1
- package/dist/parsing/operations/return.d.ts +1 -0
- package/dist/parsing/operations/return.d.ts.map +1 -1
- package/dist/parsing/operations/return.js +7 -1
- package/dist/parsing/operations/return.js.map +1 -1
- package/dist/parsing/operations/where.d.ts +1 -1
- package/dist/parsing/operations/where.d.ts.map +1 -1
- package/dist/parsing/operations/where.js +4 -0
- package/dist/parsing/operations/where.js.map +1 -1
- package/dist/parsing/parser.d.ts +10 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +344 -5
- package/dist/parsing/parser.js.map +1 -1
- package/dist/parsing/token_to_node.d.ts.map +1 -1
- package/dist/parsing/token_to_node.js +7 -0
- package/dist/parsing/token_to_node.js.map +1 -1
- package/dist/tokenization/keyword.d.ts +1 -0
- package/dist/tokenization/keyword.d.ts.map +1 -1
- package/dist/tokenization/keyword.js +1 -0
- package/dist/tokenization/keyword.js.map +1 -1
- package/dist/tokenization/token.d.ts +4 -0
- package/dist/tokenization/token.d.ts.map +1 -1
- package/dist/tokenization/token.js +14 -1
- package/dist/tokenization/token.js.map +1 -1
- package/dist/tokenization/token_type.d.ts +1 -0
- package/dist/tokenization/token_type.d.ts.map +1 -1
- package/dist/tokenization/token_type.js +1 -0
- package/dist/tokenization/token_type.js.map +1 -1
- package/dist/tokenization/tokenizer.d.ts +2 -1
- package/dist/tokenization/tokenizer.d.ts.map +1 -1
- package/dist/tokenization/tokenizer.js +25 -12
- package/dist/tokenization/tokenizer.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/README.md +166 -0
- package/flowquery-py/pyproject.toml +75 -0
- package/flowquery-py/setup_env.ps1 +92 -0
- package/flowquery-py/setup_env.sh +87 -0
- package/flowquery-py/src/__init__.py +34 -0
- package/flowquery-py/src/__main__.py +10 -0
- package/flowquery-py/src/compute/__init__.py +5 -0
- package/flowquery-py/src/compute/runner.py +60 -0
- package/flowquery-py/src/extensibility.py +52 -0
- package/flowquery-py/src/graph/__init__.py +31 -0
- package/flowquery-py/src/graph/data.py +118 -0
- package/flowquery-py/src/graph/database.py +82 -0
- package/flowquery-py/src/graph/hops.py +43 -0
- package/flowquery-py/src/graph/node.py +112 -0
- package/flowquery-py/src/graph/node_data.py +26 -0
- package/flowquery-py/src/graph/node_reference.py +49 -0
- package/flowquery-py/src/graph/pattern.py +125 -0
- package/flowquery-py/src/graph/pattern_expression.py +62 -0
- package/flowquery-py/src/graph/patterns.py +42 -0
- package/flowquery-py/src/graph/physical_node.py +40 -0
- package/flowquery-py/src/graph/physical_relationship.py +36 -0
- package/flowquery-py/src/graph/relationship.py +135 -0
- package/flowquery-py/src/graph/relationship_data.py +33 -0
- package/flowquery-py/src/graph/relationship_match_collector.py +77 -0
- package/flowquery-py/src/graph/relationship_reference.py +21 -0
- package/flowquery-py/src/io/__init__.py +5 -0
- package/flowquery-py/src/io/command_line.py +67 -0
- package/flowquery-py/src/parsing/__init__.py +17 -0
- package/flowquery-py/src/parsing/alias.py +20 -0
- package/flowquery-py/src/parsing/alias_option.py +11 -0
- package/flowquery-py/src/parsing/ast_node.py +146 -0
- package/flowquery-py/src/parsing/base_parser.py +84 -0
- package/flowquery-py/src/parsing/components/__init__.py +19 -0
- package/flowquery-py/src/parsing/components/csv.py +8 -0
- package/flowquery-py/src/parsing/components/from_.py +10 -0
- package/flowquery-py/src/parsing/components/headers.py +12 -0
- package/flowquery-py/src/parsing/components/json.py +8 -0
- package/flowquery-py/src/parsing/components/null.py +10 -0
- package/flowquery-py/src/parsing/components/post.py +8 -0
- package/flowquery-py/src/parsing/components/text.py +8 -0
- package/flowquery-py/src/parsing/context.py +50 -0
- package/flowquery-py/src/parsing/data_structures/__init__.py +15 -0
- package/flowquery-py/src/parsing/data_structures/associative_array.py +41 -0
- package/flowquery-py/src/parsing/data_structures/json_array.py +30 -0
- package/flowquery-py/src/parsing/data_structures/key_value_pair.py +38 -0
- package/flowquery-py/src/parsing/data_structures/lookup.py +49 -0
- package/flowquery-py/src/parsing/data_structures/range_lookup.py +42 -0
- package/flowquery-py/src/parsing/expressions/__init__.py +57 -0
- package/flowquery-py/src/parsing/expressions/boolean.py +20 -0
- package/flowquery-py/src/parsing/expressions/expression.py +138 -0
- package/flowquery-py/src/parsing/expressions/expression_map.py +26 -0
- package/flowquery-py/src/parsing/expressions/f_string.py +27 -0
- package/flowquery-py/src/parsing/expressions/identifier.py +20 -0
- package/flowquery-py/src/parsing/expressions/number.py +32 -0
- package/flowquery-py/src/parsing/expressions/operator.py +169 -0
- package/flowquery-py/src/parsing/expressions/reference.py +47 -0
- package/flowquery-py/src/parsing/expressions/string.py +27 -0
- package/flowquery-py/src/parsing/functions/__init__.py +75 -0
- package/flowquery-py/src/parsing/functions/aggregate_function.py +60 -0
- package/flowquery-py/src/parsing/functions/async_function.py +62 -0
- package/flowquery-py/src/parsing/functions/avg.py +55 -0
- package/flowquery-py/src/parsing/functions/collect.py +75 -0
- package/flowquery-py/src/parsing/functions/function.py +68 -0
- package/flowquery-py/src/parsing/functions/function_factory.py +173 -0
- package/flowquery-py/src/parsing/functions/function_metadata.py +149 -0
- package/flowquery-py/src/parsing/functions/functions.py +59 -0
- package/flowquery-py/src/parsing/functions/join.py +47 -0
- package/flowquery-py/src/parsing/functions/keys.py +34 -0
- package/flowquery-py/src/parsing/functions/predicate_function.py +46 -0
- package/flowquery-py/src/parsing/functions/predicate_sum.py +47 -0
- package/flowquery-py/src/parsing/functions/rand.py +28 -0
- package/flowquery-py/src/parsing/functions/range_.py +34 -0
- package/flowquery-py/src/parsing/functions/reducer_element.py +15 -0
- package/flowquery-py/src/parsing/functions/replace.py +37 -0
- package/flowquery-py/src/parsing/functions/round_.py +32 -0
- package/flowquery-py/src/parsing/functions/size.py +32 -0
- package/flowquery-py/src/parsing/functions/split.py +47 -0
- package/flowquery-py/src/parsing/functions/stringify.py +47 -0
- package/flowquery-py/src/parsing/functions/sum.py +51 -0
- package/flowquery-py/src/parsing/functions/to_json.py +33 -0
- package/flowquery-py/src/parsing/functions/type_.py +47 -0
- package/flowquery-py/src/parsing/functions/value_holder.py +24 -0
- package/flowquery-py/src/parsing/logic/__init__.py +15 -0
- package/flowquery-py/src/parsing/logic/case.py +29 -0
- package/flowquery-py/src/parsing/logic/else_.py +12 -0
- package/flowquery-py/src/parsing/logic/end.py +8 -0
- package/flowquery-py/src/parsing/logic/then.py +12 -0
- package/flowquery-py/src/parsing/logic/when.py +10 -0
- package/flowquery-py/src/parsing/operations/__init__.py +35 -0
- package/flowquery-py/src/parsing/operations/aggregated_return.py +24 -0
- package/flowquery-py/src/parsing/operations/aggregated_with.py +22 -0
- package/flowquery-py/src/parsing/operations/call.py +74 -0
- package/flowquery-py/src/parsing/operations/create_node.py +34 -0
- package/flowquery-py/src/parsing/operations/create_relationship.py +34 -0
- package/flowquery-py/src/parsing/operations/group_by.py +130 -0
- package/flowquery-py/src/parsing/operations/limit.py +22 -0
- package/flowquery-py/src/parsing/operations/load.py +140 -0
- package/flowquery-py/src/parsing/operations/match.py +29 -0
- package/flowquery-py/src/parsing/operations/operation.py +69 -0
- package/flowquery-py/src/parsing/operations/projection.py +21 -0
- package/flowquery-py/src/parsing/operations/return_op.py +50 -0
- package/flowquery-py/src/parsing/operations/unwind.py +37 -0
- package/flowquery-py/src/parsing/operations/where.py +41 -0
- package/flowquery-py/src/parsing/operations/with_op.py +18 -0
- package/flowquery-py/src/parsing/parser.py +1011 -0
- package/flowquery-py/src/parsing/token_to_node.py +109 -0
- package/flowquery-py/src/tokenization/__init__.py +23 -0
- package/flowquery-py/src/tokenization/keyword.py +48 -0
- package/flowquery-py/src/tokenization/operator.py +29 -0
- package/flowquery-py/src/tokenization/string_walker.py +158 -0
- package/flowquery-py/src/tokenization/symbol.py +19 -0
- package/flowquery-py/src/tokenization/token.py +659 -0
- package/flowquery-py/src/tokenization/token_mapper.py +52 -0
- package/flowquery-py/src/tokenization/token_type.py +21 -0
- package/flowquery-py/src/tokenization/tokenizer.py +214 -0
- package/flowquery-py/src/tokenization/trie.py +124 -0
- package/flowquery-py/src/utils/__init__.py +6 -0
- package/flowquery-py/src/utils/object_utils.py +20 -0
- package/flowquery-py/src/utils/string_utils.py +113 -0
- package/flowquery-py/tests/__init__.py +1 -0
- package/flowquery-py/tests/compute/__init__.py +1 -0
- package/flowquery-py/tests/compute/test_runner.py +1335 -0
- package/flowquery-py/tests/graph/__init__.py +1 -0
- package/flowquery-py/tests/graph/test_create.py +56 -0
- package/flowquery-py/tests/graph/test_data.py +73 -0
- package/flowquery-py/tests/graph/test_match.py +40 -0
- package/flowquery-py/tests/parsing/__init__.py +1 -0
- package/flowquery-py/tests/parsing/test_context.py +34 -0
- package/flowquery-py/tests/parsing/test_expression.py +49 -0
- package/flowquery-py/tests/parsing/test_parser.py +674 -0
- package/flowquery-py/tests/test_extensibility.py +611 -0
- package/flowquery-py/tests/tokenization/__init__.py +1 -0
- package/flowquery-py/tests/tokenization/test_token_mapper.py +60 -0
- package/flowquery-py/tests/tokenization/test_tokenizer.py +164 -0
- package/flowquery-py/tests/tokenization/test_trie.py +30 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/misc/apps/RAG/package.json +1 -1
- package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +76 -8
- package/misc/apps/RAG/src/components/index.ts +19 -10
- package/misc/apps/RAG/src/plugins/loaders/MockData.ts +70 -140
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +12 -0
- package/package.json +1 -1
- package/src/compute/runner.ts +24 -19
- package/src/graph/data.ts +112 -0
- package/src/graph/database.ts +63 -0
- package/src/graph/hops.ts +22 -0
- package/src/graph/node.ts +99 -0
- package/src/graph/node_data.ts +18 -0
- package/src/graph/node_reference.ts +33 -0
- package/src/graph/pattern.ts +101 -0
- package/src/graph/pattern_expression.ts +37 -0
- package/src/graph/patterns.ts +36 -0
- package/src/graph/physical_node.ts +23 -0
- package/src/graph/physical_relationship.ts +23 -0
- package/src/graph/relationship.ts +116 -0
- package/src/graph/relationship_data.ts +27 -0
- package/src/graph/relationship_match_collector.ts +58 -0
- package/src/graph/relationship_reference.ts +24 -0
- package/src/parsing/base_parser.ts +20 -14
- package/src/parsing/context.ts +14 -14
- package/src/parsing/expressions/boolean.ts +21 -0
- package/src/parsing/expressions/expression.ts +34 -26
- package/src/parsing/expressions/operator.ts +19 -1
- package/src/parsing/functions/function_factory.ts +45 -45
- package/src/parsing/operations/create_node.ts +39 -0
- package/src/parsing/operations/create_relationship.ts +38 -0
- package/src/parsing/operations/match.ts +31 -0
- package/src/parsing/operations/operation.ts +3 -0
- package/src/parsing/operations/return.ts +11 -7
- package/src/parsing/operations/where.ts +10 -6
- package/src/parsing/parser.ts +346 -8
- package/src/parsing/token_to_node.ts +6 -0
- package/src/tokenization/keyword.ts +41 -40
- package/src/tokenization/token.ts +21 -1
- package/src/tokenization/token_type.ts +2 -1
- package/src/tokenization/tokenizer.ts +52 -31
- package/tests/compute/runner.test.ts +654 -0
- package/tests/extensibility.test.ts +97 -93
- package/tests/graph/create.test.ts +36 -0
- package/tests/graph/data.test.ts +58 -0
- package/tests/graph/match.test.ts +29 -0
- package/tests/parsing/parser.test.ts +273 -2
- package/tests/tokenization/tokenizer.test.ts +90 -0
|
@@ -0,0 +1,1011 @@
|
|
|
1
|
+
"""Main parser for FlowQuery statements."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Iterator, List, Optional
|
|
4
|
+
|
|
5
|
+
from ..tokenization.token import Token
|
|
6
|
+
from ..utils.object_utils import ObjectUtils
|
|
7
|
+
from .alias import Alias
|
|
8
|
+
from .alias_option import AliasOption
|
|
9
|
+
from .ast_node import ASTNode
|
|
10
|
+
from .base_parser import BaseParser
|
|
11
|
+
from .context import Context
|
|
12
|
+
from .components.from_ import From
|
|
13
|
+
from .components.headers import Headers
|
|
14
|
+
from .components.null import Null
|
|
15
|
+
from .components.post import Post
|
|
16
|
+
from .data_structures.associative_array import AssociativeArray
|
|
17
|
+
from .data_structures.json_array import JSONArray
|
|
18
|
+
from .data_structures.key_value_pair import KeyValuePair
|
|
19
|
+
from .data_structures.lookup import Lookup
|
|
20
|
+
from .data_structures.range_lookup import RangeLookup
|
|
21
|
+
from .expressions.expression import Expression
|
|
22
|
+
from .expressions.f_string import FString
|
|
23
|
+
from .expressions.identifier import Identifier
|
|
24
|
+
from .expressions.operator import Not
|
|
25
|
+
from .expressions.reference import Reference
|
|
26
|
+
from .expressions.string import String
|
|
27
|
+
from .functions.aggregate_function import AggregateFunction
|
|
28
|
+
from .functions.async_function import AsyncFunction
|
|
29
|
+
from .functions.function import Function
|
|
30
|
+
from .functions.function_factory import FunctionFactory
|
|
31
|
+
from .functions.predicate_function import PredicateFunction
|
|
32
|
+
from .logic.case import Case
|
|
33
|
+
from .logic.when import When
|
|
34
|
+
from .logic.then import Then
|
|
35
|
+
from .logic.else_ import Else
|
|
36
|
+
from .operations.aggregated_return import AggregatedReturn
|
|
37
|
+
from .operations.aggregated_with import AggregatedWith
|
|
38
|
+
from .operations.call import Call
|
|
39
|
+
from .operations.limit import Limit
|
|
40
|
+
from .operations.load import Load
|
|
41
|
+
from .operations.match import Match
|
|
42
|
+
from .operations.operation import Operation
|
|
43
|
+
from .operations.return_op import Return
|
|
44
|
+
from .operations.unwind import Unwind
|
|
45
|
+
from .operations.where import Where
|
|
46
|
+
from .operations.with_op import With
|
|
47
|
+
from ..graph.node import Node
|
|
48
|
+
from ..graph.node_reference import NodeReference
|
|
49
|
+
from ..graph.pattern import Pattern
|
|
50
|
+
from ..graph.pattern_expression import PatternExpression
|
|
51
|
+
from ..graph.relationship import Relationship
|
|
52
|
+
from .operations.create_node import CreateNode
|
|
53
|
+
from .operations.create_relationship import CreateRelationship
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Parser(BaseParser):
|
|
57
|
+
"""Main parser for FlowQuery statements.
|
|
58
|
+
|
|
59
|
+
Parses FlowQuery declarative query language statements into an Abstract Syntax Tree (AST).
|
|
60
|
+
Supports operations like WITH, UNWIND, RETURN, LOAD, WHERE, and LIMIT, along with
|
|
61
|
+
expressions, functions, data structures, and logical constructs.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
parser = Parser()
|
|
65
|
+
ast = parser.parse("unwind [1, 2, 3, 4, 5] as num return num")
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, tokens: Optional[List[Token]] = None):
|
|
69
|
+
super().__init__(tokens)
|
|
70
|
+
self._variables: Dict[str, ASTNode] = {}
|
|
71
|
+
self._context = Context()
|
|
72
|
+
self._returns = 0
|
|
73
|
+
|
|
74
|
+
def parse(self, statement: str) -> ASTNode:
|
|
75
|
+
"""Parses a FlowQuery statement into an Abstract Syntax Tree.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
statement: The FlowQuery statement to parse
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The root AST node containing the parsed structure
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
ValueError: If the statement is malformed or contains syntax errors
|
|
85
|
+
"""
|
|
86
|
+
self.tokenize(statement)
|
|
87
|
+
return self._parse_tokenized()
|
|
88
|
+
|
|
89
|
+
def _parse_tokenized(self, is_sub_query: bool = False) -> ASTNode:
|
|
90
|
+
root = ASTNode()
|
|
91
|
+
previous: Optional[Operation] = None
|
|
92
|
+
operation: Optional[Operation] = None
|
|
93
|
+
|
|
94
|
+
while not self.token.is_eof():
|
|
95
|
+
if root.child_count() > 0:
|
|
96
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
97
|
+
else:
|
|
98
|
+
self._skip_whitespace_and_comments()
|
|
99
|
+
|
|
100
|
+
operation = self._parse_operation()
|
|
101
|
+
if operation is None and not is_sub_query:
|
|
102
|
+
raise ValueError("Expected one of WITH, UNWIND, RETURN, LOAD, OR CALL")
|
|
103
|
+
elif operation is None and is_sub_query:
|
|
104
|
+
return root
|
|
105
|
+
|
|
106
|
+
if self._returns > 1:
|
|
107
|
+
raise ValueError("Only one RETURN statement is allowed")
|
|
108
|
+
|
|
109
|
+
if isinstance(previous, Call) and not previous.has_yield:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"CALL operations must have a YIELD clause unless they are the last operation"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if previous is not None:
|
|
115
|
+
previous.add_sibling(operation)
|
|
116
|
+
else:
|
|
117
|
+
root.add_child(operation)
|
|
118
|
+
|
|
119
|
+
where = self._parse_where()
|
|
120
|
+
if where is not None:
|
|
121
|
+
if isinstance(operation, Return):
|
|
122
|
+
operation.where = where
|
|
123
|
+
else:
|
|
124
|
+
operation.add_sibling(where)
|
|
125
|
+
operation = where
|
|
126
|
+
|
|
127
|
+
limit = self._parse_limit()
|
|
128
|
+
if limit is not None:
|
|
129
|
+
operation.add_sibling(limit)
|
|
130
|
+
operation = limit
|
|
131
|
+
|
|
132
|
+
previous = operation
|
|
133
|
+
|
|
134
|
+
if not isinstance(operation, (Return, Call, CreateNode, CreateRelationship)):
|
|
135
|
+
raise ValueError("Last statement must be a RETURN, WHERE, CALL, or CREATE statement")
|
|
136
|
+
|
|
137
|
+
return root
|
|
138
|
+
|
|
139
|
+
def _parse_operation(self) -> Optional[Operation]:
|
|
140
|
+
return (
|
|
141
|
+
self._parse_with() or
|
|
142
|
+
self._parse_unwind() or
|
|
143
|
+
self._parse_return() or
|
|
144
|
+
self._parse_load() or
|
|
145
|
+
self._parse_call() or
|
|
146
|
+
self._parse_match() or
|
|
147
|
+
self._parse_create()
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def _parse_with(self) -> Optional[With]:
|
|
151
|
+
if not self.token.is_with():
|
|
152
|
+
return None
|
|
153
|
+
self.set_next_token()
|
|
154
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
155
|
+
expressions = list(self._parse_expressions(AliasOption.REQUIRED))
|
|
156
|
+
if len(expressions) == 0:
|
|
157
|
+
raise ValueError("Expected expression")
|
|
158
|
+
if any(expr.has_reducers() for expr in expressions):
|
|
159
|
+
return AggregatedWith(expressions)
|
|
160
|
+
return With(expressions)
|
|
161
|
+
|
|
162
|
+
def _parse_unwind(self) -> Optional[Unwind]:
|
|
163
|
+
if not self.token.is_unwind():
|
|
164
|
+
return None
|
|
165
|
+
self.set_next_token()
|
|
166
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
167
|
+
expression = self._parse_expression()
|
|
168
|
+
if expression is None:
|
|
169
|
+
raise ValueError("Expected expression")
|
|
170
|
+
if not ObjectUtils.is_instance_of_any(
|
|
171
|
+
expression.first_child(),
|
|
172
|
+
[JSONArray, Function, Reference, Lookup, RangeLookup]
|
|
173
|
+
):
|
|
174
|
+
raise ValueError("Expected array, function, reference, or lookup.")
|
|
175
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
176
|
+
alias = self._parse_alias()
|
|
177
|
+
if alias is not None:
|
|
178
|
+
expression.set_alias(alias.get_alias())
|
|
179
|
+
else:
|
|
180
|
+
raise ValueError("Expected alias")
|
|
181
|
+
unwind = Unwind(expression)
|
|
182
|
+
self._variables[alias.get_alias()] = unwind
|
|
183
|
+
return unwind
|
|
184
|
+
|
|
185
|
+
def _parse_return(self) -> Optional[Return]:
|
|
186
|
+
if not self.token.is_return():
|
|
187
|
+
return None
|
|
188
|
+
self.set_next_token()
|
|
189
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
190
|
+
expressions = list(self._parse_expressions(AliasOption.OPTIONAL))
|
|
191
|
+
if len(expressions) == 0:
|
|
192
|
+
raise ValueError("Expected expression")
|
|
193
|
+
if any(expr.has_reducers() for expr in expressions):
|
|
194
|
+
return AggregatedReturn(expressions)
|
|
195
|
+
self._returns += 1
|
|
196
|
+
return Return(expressions)
|
|
197
|
+
|
|
198
|
+
def _parse_where(self) -> Optional[Where]:
|
|
199
|
+
if not self.token.is_where():
|
|
200
|
+
return None
|
|
201
|
+
self._expect_previous_token_to_be_whitespace_or_comment()
|
|
202
|
+
self.set_next_token()
|
|
203
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
204
|
+
expression = self._parse_expression()
|
|
205
|
+
if expression is None:
|
|
206
|
+
raise ValueError("Expected expression")
|
|
207
|
+
if ObjectUtils.is_instance_of_any(
|
|
208
|
+
expression.first_child(),
|
|
209
|
+
[JSONArray, AssociativeArray]
|
|
210
|
+
):
|
|
211
|
+
raise ValueError("Expected an expression which can be evaluated to a boolean")
|
|
212
|
+
return Where(expression)
|
|
213
|
+
|
|
214
|
+
def _parse_load(self) -> Optional[Load]:
|
|
215
|
+
if not self.token.is_load():
|
|
216
|
+
return None
|
|
217
|
+
load = Load()
|
|
218
|
+
self.set_next_token()
|
|
219
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
220
|
+
if not (self.token.is_json() or self.token.is_csv() or self.token.is_text()):
|
|
221
|
+
raise ValueError("Expected JSON, CSV, or TEXT")
|
|
222
|
+
load.add_child(self.token.node)
|
|
223
|
+
self.set_next_token()
|
|
224
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
225
|
+
if not self.token.is_from():
|
|
226
|
+
raise ValueError("Expected FROM")
|
|
227
|
+
self.set_next_token()
|
|
228
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
229
|
+
from_node = From()
|
|
230
|
+
load.add_child(from_node)
|
|
231
|
+
|
|
232
|
+
# Check if source is async function
|
|
233
|
+
async_func = self._parse_async_function()
|
|
234
|
+
if async_func is not None:
|
|
235
|
+
from_node.add_child(async_func)
|
|
236
|
+
else:
|
|
237
|
+
expression = self._parse_expression()
|
|
238
|
+
if expression is None:
|
|
239
|
+
raise ValueError("Expected expression or async function")
|
|
240
|
+
from_node.add_child(expression)
|
|
241
|
+
|
|
242
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
243
|
+
if self.token.is_headers():
|
|
244
|
+
headers = Headers()
|
|
245
|
+
self.set_next_token()
|
|
246
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
247
|
+
header = self._parse_expression()
|
|
248
|
+
if header is None:
|
|
249
|
+
raise ValueError("Expected expression")
|
|
250
|
+
headers.add_child(header)
|
|
251
|
+
load.add_child(headers)
|
|
252
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
253
|
+
|
|
254
|
+
if self.token.is_post():
|
|
255
|
+
post = Post()
|
|
256
|
+
self.set_next_token()
|
|
257
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
258
|
+
payload = self._parse_expression()
|
|
259
|
+
if payload is None:
|
|
260
|
+
raise ValueError("Expected expression")
|
|
261
|
+
post.add_child(payload)
|
|
262
|
+
load.add_child(post)
|
|
263
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
264
|
+
|
|
265
|
+
alias = self._parse_alias()
|
|
266
|
+
if alias is not None:
|
|
267
|
+
load.add_child(alias)
|
|
268
|
+
self._variables[alias.get_alias()] = load
|
|
269
|
+
else:
|
|
270
|
+
raise ValueError("Expected alias")
|
|
271
|
+
return load
|
|
272
|
+
|
|
273
|
+
def _parse_call(self) -> Optional[Call]:
|
|
274
|
+
if not self.token.is_call():
|
|
275
|
+
return None
|
|
276
|
+
self.set_next_token()
|
|
277
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
278
|
+
async_function = self._parse_async_function()
|
|
279
|
+
if async_function is None:
|
|
280
|
+
raise ValueError("Expected async function")
|
|
281
|
+
call = Call()
|
|
282
|
+
call.function = async_function
|
|
283
|
+
self._skip_whitespace_and_comments()
|
|
284
|
+
if self.token.is_yield():
|
|
285
|
+
self._expect_previous_token_to_be_whitespace_or_comment()
|
|
286
|
+
self.set_next_token()
|
|
287
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
288
|
+
expressions = list(self._parse_expressions(AliasOption.OPTIONAL))
|
|
289
|
+
if len(expressions) == 0:
|
|
290
|
+
raise ValueError("Expected at least one expression")
|
|
291
|
+
call.yielded = expressions
|
|
292
|
+
return call
|
|
293
|
+
|
|
294
|
+
def _parse_match(self) -> Optional[Match]:
|
|
295
|
+
if not self.token.is_match():
|
|
296
|
+
return None
|
|
297
|
+
self.set_next_token()
|
|
298
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
299
|
+
patterns = list(self._parse_patterns())
|
|
300
|
+
if len(patterns) == 0:
|
|
301
|
+
raise ValueError("Expected graph pattern")
|
|
302
|
+
return Match(patterns)
|
|
303
|
+
|
|
304
|
+
def _parse_create(self) -> Optional[Operation]:
|
|
305
|
+
"""Parse CREATE VIRTUAL statement for nodes and relationships."""
|
|
306
|
+
if not self.token.is_create():
|
|
307
|
+
return None
|
|
308
|
+
self.set_next_token()
|
|
309
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
310
|
+
if not self.token.is_virtual():
|
|
311
|
+
raise ValueError("Expected VIRTUAL")
|
|
312
|
+
self.set_next_token()
|
|
313
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
314
|
+
|
|
315
|
+
node = self._parse_node()
|
|
316
|
+
if node is None:
|
|
317
|
+
raise ValueError("Expected node definition")
|
|
318
|
+
|
|
319
|
+
relationship: Optional[Relationship] = None
|
|
320
|
+
if self.token.is_subtract() and self.peek() and self.peek().is_opening_bracket():
|
|
321
|
+
self.set_next_token() # skip -
|
|
322
|
+
self.set_next_token() # skip [
|
|
323
|
+
if not self.token.is_colon():
|
|
324
|
+
raise ValueError("Expected ':' for relationship type")
|
|
325
|
+
self.set_next_token()
|
|
326
|
+
if not self.token.is_identifier():
|
|
327
|
+
raise ValueError("Expected relationship type identifier")
|
|
328
|
+
rel_type = self.token.value or ""
|
|
329
|
+
self.set_next_token()
|
|
330
|
+
if not self.token.is_closing_bracket():
|
|
331
|
+
raise ValueError("Expected closing bracket for relationship definition")
|
|
332
|
+
self.set_next_token()
|
|
333
|
+
if not self.token.is_subtract():
|
|
334
|
+
raise ValueError("Expected '-' for relationship definition")
|
|
335
|
+
self.set_next_token()
|
|
336
|
+
# Skip optional direction indicator '>'
|
|
337
|
+
if self.token.is_greater_than():
|
|
338
|
+
self.set_next_token()
|
|
339
|
+
target = self._parse_node()
|
|
340
|
+
if target is None:
|
|
341
|
+
raise ValueError("Expected target node definition")
|
|
342
|
+
relationship = Relationship()
|
|
343
|
+
relationship.type = rel_type
|
|
344
|
+
|
|
345
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
346
|
+
if not self.token.is_as():
|
|
347
|
+
raise ValueError("Expected AS")
|
|
348
|
+
self.set_next_token()
|
|
349
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
350
|
+
|
|
351
|
+
query = self._parse_sub_query()
|
|
352
|
+
if query is None:
|
|
353
|
+
raise ValueError("Expected sub-query")
|
|
354
|
+
|
|
355
|
+
if relationship is not None:
|
|
356
|
+
return CreateRelationship(relationship, query)
|
|
357
|
+
else:
|
|
358
|
+
return CreateNode(node, query)
|
|
359
|
+
|
|
360
|
+
def _parse_sub_query(self) -> Optional[ASTNode]:
|
|
361
|
+
"""Parse a sub-query enclosed in braces."""
|
|
362
|
+
if not self.token.is_opening_brace():
|
|
363
|
+
return None
|
|
364
|
+
self.set_next_token()
|
|
365
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
366
|
+
query = self._parse_tokenized(is_sub_query=True)
|
|
367
|
+
self._skip_whitespace_and_comments()
|
|
368
|
+
if not self.token.is_closing_brace():
|
|
369
|
+
raise ValueError("Expected closing brace for sub-query")
|
|
370
|
+
self.set_next_token()
|
|
371
|
+
return query
|
|
372
|
+
|
|
373
|
+
def _parse_patterns(self) -> Iterator[Pattern]:
|
|
374
|
+
while True:
|
|
375
|
+
identifier: Optional[str] = None
|
|
376
|
+
if self.token.is_identifier():
|
|
377
|
+
identifier = self.token.value
|
|
378
|
+
self.set_next_token()
|
|
379
|
+
self._skip_whitespace_and_comments()
|
|
380
|
+
if not self.token.is_equals():
|
|
381
|
+
raise ValueError("Expected '=' for pattern assignment")
|
|
382
|
+
self.set_next_token()
|
|
383
|
+
self._skip_whitespace_and_comments()
|
|
384
|
+
pattern = self._parse_pattern()
|
|
385
|
+
if pattern is not None:
|
|
386
|
+
if identifier is not None:
|
|
387
|
+
pattern.identifier = identifier
|
|
388
|
+
self._variables[identifier] = pattern
|
|
389
|
+
yield pattern
|
|
390
|
+
else:
|
|
391
|
+
break
|
|
392
|
+
self._skip_whitespace_and_comments()
|
|
393
|
+
if not self.token.is_comma():
|
|
394
|
+
break
|
|
395
|
+
self.set_next_token()
|
|
396
|
+
self._skip_whitespace_and_comments()
|
|
397
|
+
|
|
398
|
+
def _parse_pattern(self) -> Optional[Pattern]:
|
|
399
|
+
if not self.token.is_left_parenthesis():
|
|
400
|
+
return None
|
|
401
|
+
pattern = Pattern()
|
|
402
|
+
node = self._parse_node()
|
|
403
|
+
if node is None:
|
|
404
|
+
raise ValueError("Expected node definition")
|
|
405
|
+
pattern.add_element(node)
|
|
406
|
+
while True:
|
|
407
|
+
relationship = self._parse_relationship()
|
|
408
|
+
if relationship is None:
|
|
409
|
+
break
|
|
410
|
+
pattern.add_element(relationship)
|
|
411
|
+
node = self._parse_node()
|
|
412
|
+
if node is None:
|
|
413
|
+
raise ValueError("Expected target node definition")
|
|
414
|
+
pattern.add_element(node)
|
|
415
|
+
return pattern
|
|
416
|
+
|
|
417
|
+
def _parse_pattern_expression(self) -> Optional[PatternExpression]:
|
|
418
|
+
"""Parse a pattern expression for WHERE clauses.
|
|
419
|
+
|
|
420
|
+
PatternExpression is used to test if a graph pattern exists.
|
|
421
|
+
It must start with a NodeReference (referencing an existing variable).
|
|
422
|
+
"""
|
|
423
|
+
if not self.token.is_left_parenthesis():
|
|
424
|
+
return None
|
|
425
|
+
pattern = PatternExpression()
|
|
426
|
+
node = self._parse_node()
|
|
427
|
+
if node is None:
|
|
428
|
+
raise ValueError("Expected node definition")
|
|
429
|
+
if not isinstance(node, NodeReference):
|
|
430
|
+
raise ValueError("PatternExpression must start with a NodeReference")
|
|
431
|
+
pattern.add_element(node)
|
|
432
|
+
while True:
|
|
433
|
+
relationship = self._parse_relationship()
|
|
434
|
+
if relationship is None:
|
|
435
|
+
break
|
|
436
|
+
if relationship.hops and relationship.hops.multi():
|
|
437
|
+
raise ValueError("PatternExpression does not support variable-length relationships")
|
|
438
|
+
pattern.add_element(relationship)
|
|
439
|
+
node = self._parse_node()
|
|
440
|
+
if node is None:
|
|
441
|
+
raise ValueError("Expected target node definition")
|
|
442
|
+
pattern.add_element(node)
|
|
443
|
+
return pattern
|
|
444
|
+
|
|
445
|
+
def _parse_node(self) -> Optional[Node]:
|
|
446
|
+
if not self.token.is_left_parenthesis():
|
|
447
|
+
return None
|
|
448
|
+
self.set_next_token()
|
|
449
|
+
self._skip_whitespace_and_comments()
|
|
450
|
+
identifier: Optional[str] = None
|
|
451
|
+
if self.token.is_identifier():
|
|
452
|
+
identifier = self.token.value
|
|
453
|
+
self.set_next_token()
|
|
454
|
+
self._skip_whitespace_and_comments()
|
|
455
|
+
label: Optional[str] = None
|
|
456
|
+
peek = self.peek()
|
|
457
|
+
if not self.token.is_colon() and peek is not None and peek.is_identifier():
|
|
458
|
+
raise ValueError("Expected ':' for node label")
|
|
459
|
+
if self.token.is_colon() and (peek is None or not peek.is_identifier()):
|
|
460
|
+
raise ValueError("Expected node label identifier")
|
|
461
|
+
if self.token.is_colon() and peek is not None and peek.is_identifier():
|
|
462
|
+
self.set_next_token()
|
|
463
|
+
label = self.token.value
|
|
464
|
+
self.set_next_token()
|
|
465
|
+
self._skip_whitespace_and_comments()
|
|
466
|
+
node = Node()
|
|
467
|
+
node.label = label
|
|
468
|
+
if label is not None and identifier is not None:
|
|
469
|
+
node.identifier = identifier
|
|
470
|
+
self._variables[identifier] = node
|
|
471
|
+
elif identifier is not None:
|
|
472
|
+
reference = self._variables.get(identifier)
|
|
473
|
+
from ..graph.node_reference import NodeReference
|
|
474
|
+
if reference is None or not isinstance(reference, Node):
|
|
475
|
+
raise ValueError(f"Undefined node reference: {identifier}")
|
|
476
|
+
node = NodeReference(node, reference)
|
|
477
|
+
if not self.token.is_right_parenthesis():
|
|
478
|
+
raise ValueError("Expected closing parenthesis for node definition")
|
|
479
|
+
self.set_next_token()
|
|
480
|
+
return node
|
|
481
|
+
|
|
482
|
+
def _parse_relationship(self) -> Optional[Relationship]:
|
|
483
|
+
if self.token.is_less_than() and self.peek() is not None and self.peek().is_subtract():
|
|
484
|
+
self.set_next_token()
|
|
485
|
+
self.set_next_token()
|
|
486
|
+
elif self.token.is_subtract():
|
|
487
|
+
self.set_next_token()
|
|
488
|
+
else:
|
|
489
|
+
return None
|
|
490
|
+
if not self.token.is_opening_bracket():
|
|
491
|
+
return None
|
|
492
|
+
self.set_next_token()
|
|
493
|
+
variable: Optional[str] = None
|
|
494
|
+
if self.token.is_identifier():
|
|
495
|
+
variable = self.token.value
|
|
496
|
+
self.set_next_token()
|
|
497
|
+
if not self.token.is_colon():
|
|
498
|
+
raise ValueError("Expected ':' for relationship type")
|
|
499
|
+
self.set_next_token()
|
|
500
|
+
if not self.token.is_identifier():
|
|
501
|
+
raise ValueError("Expected relationship type identifier")
|
|
502
|
+
rel_type: str = self.token.value or ""
|
|
503
|
+
self.set_next_token()
|
|
504
|
+
hops = self._parse_relationship_hops()
|
|
505
|
+
if not self.token.is_closing_bracket():
|
|
506
|
+
raise ValueError("Expected closing bracket for relationship definition")
|
|
507
|
+
self.set_next_token()
|
|
508
|
+
if not self.token.is_subtract():
|
|
509
|
+
raise ValueError("Expected '-' for relationship definition")
|
|
510
|
+
self.set_next_token()
|
|
511
|
+
if self.token.is_greater_than():
|
|
512
|
+
self.set_next_token()
|
|
513
|
+
relationship = Relationship()
|
|
514
|
+
if rel_type is not None and variable is not None:
|
|
515
|
+
relationship.identifier = variable
|
|
516
|
+
self._variables[variable] = relationship
|
|
517
|
+
elif variable is not None:
|
|
518
|
+
reference = self._variables.get(variable)
|
|
519
|
+
from ..graph.relationship_reference import RelationshipReference
|
|
520
|
+
if reference is None or not isinstance(reference, Relationship):
|
|
521
|
+
raise ValueError(f"Undefined relationship reference: {variable}")
|
|
522
|
+
relationship = RelationshipReference(relationship, reference)
|
|
523
|
+
if hops is not None:
|
|
524
|
+
relationship.hops = hops
|
|
525
|
+
relationship.type = rel_type
|
|
526
|
+
return relationship
|
|
527
|
+
|
|
528
|
+
def _parse_relationship_hops(self):
|
|
529
|
+
import sys
|
|
530
|
+
from ..graph.hops import Hops
|
|
531
|
+
if not self.token.is_multiply():
|
|
532
|
+
return None
|
|
533
|
+
hops = Hops()
|
|
534
|
+
self.set_next_token()
|
|
535
|
+
if self.token.is_number():
|
|
536
|
+
hops.min = int(self.token.value or "0")
|
|
537
|
+
self.set_next_token()
|
|
538
|
+
if self.token.is_dot():
|
|
539
|
+
self.set_next_token()
|
|
540
|
+
if not self.token.is_dot():
|
|
541
|
+
raise ValueError("Expected '..' for relationship hops")
|
|
542
|
+
self.set_next_token()
|
|
543
|
+
if not self.token.is_number():
|
|
544
|
+
raise ValueError("Expected number for relationship hops")
|
|
545
|
+
hops.max = int(self.token.value or "0")
|
|
546
|
+
self.set_next_token()
|
|
547
|
+
else:
|
|
548
|
+
# Just * without numbers means unbounded
|
|
549
|
+
hops.min = 0
|
|
550
|
+
hops.max = sys.maxsize
|
|
551
|
+
return hops
|
|
552
|
+
|
|
553
|
+
def _parse_limit(self) -> Optional[Limit]:
|
|
554
|
+
self._skip_whitespace_and_comments()
|
|
555
|
+
if not self.token.is_limit():
|
|
556
|
+
return None
|
|
557
|
+
self._expect_previous_token_to_be_whitespace_or_comment()
|
|
558
|
+
self.set_next_token()
|
|
559
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
560
|
+
if not self.token.is_number():
|
|
561
|
+
raise ValueError("Expected number")
|
|
562
|
+
limit = Limit(int(self.token.value or "0"))
|
|
563
|
+
self.set_next_token()
|
|
564
|
+
return limit
|
|
565
|
+
|
|
566
|
+
def _parse_expressions(
|
|
567
|
+
self, alias_option: AliasOption = AliasOption.NOT_ALLOWED
|
|
568
|
+
) -> Iterator[Expression]:
|
|
569
|
+
while True:
|
|
570
|
+
expression = self._parse_expression()
|
|
571
|
+
if expression is not None:
|
|
572
|
+
alias = self._parse_alias()
|
|
573
|
+
if isinstance(expression.first_child(), Reference) and alias is None:
|
|
574
|
+
reference = expression.first_child()
|
|
575
|
+
expression.set_alias(reference.identifier)
|
|
576
|
+
self._variables[reference.identifier] = expression
|
|
577
|
+
elif (alias_option == AliasOption.REQUIRED and
|
|
578
|
+
alias is None and
|
|
579
|
+
not isinstance(expression.first_child(), Reference)):
|
|
580
|
+
raise ValueError("Alias required")
|
|
581
|
+
elif alias_option == AliasOption.NOT_ALLOWED and alias is not None:
|
|
582
|
+
raise ValueError("Alias not allowed")
|
|
583
|
+
elif alias_option in (AliasOption.OPTIONAL, AliasOption.REQUIRED) and alias is not None:
|
|
584
|
+
expression.set_alias(alias.get_alias())
|
|
585
|
+
self._variables[alias.get_alias()] = expression
|
|
586
|
+
yield expression
|
|
587
|
+
else:
|
|
588
|
+
break
|
|
589
|
+
self._skip_whitespace_and_comments()
|
|
590
|
+
if not self.token.is_comma():
|
|
591
|
+
break
|
|
592
|
+
self.set_next_token()
|
|
593
|
+
|
|
594
|
+
def _parse_expression(self) -> Optional[Expression]:
|
|
595
|
+
expression = Expression()
|
|
596
|
+
while True:
|
|
597
|
+
self._skip_whitespace_and_comments()
|
|
598
|
+
if self.token.is_identifier() and (self.peek() is None or not self.peek().is_left_parenthesis()):
|
|
599
|
+
identifier = self.token.value or ""
|
|
600
|
+
reference = Reference(identifier, self._variables.get(identifier))
|
|
601
|
+
self.set_next_token()
|
|
602
|
+
lookup = self._parse_lookup(reference)
|
|
603
|
+
expression.add_node(lookup)
|
|
604
|
+
elif self.token.is_identifier() and self.peek() is not None and self.peek().is_left_parenthesis():
|
|
605
|
+
func = self._parse_predicate_function() or self._parse_function()
|
|
606
|
+
if func is not None:
|
|
607
|
+
lookup = self._parse_lookup(func)
|
|
608
|
+
expression.add_node(lookup)
|
|
609
|
+
elif self.token.is_left_parenthesis() and self.peek() is not None and self.peek().is_identifier():
|
|
610
|
+
# Possible graph pattern expression
|
|
611
|
+
pattern = self._parse_pattern_expression()
|
|
612
|
+
if pattern is not None:
|
|
613
|
+
expression.add_node(pattern)
|
|
614
|
+
elif self.token.is_operand():
|
|
615
|
+
expression.add_node(self.token.node)
|
|
616
|
+
self.set_next_token()
|
|
617
|
+
elif self.token.is_f_string():
|
|
618
|
+
f_string = self._parse_f_string()
|
|
619
|
+
if f_string is None:
|
|
620
|
+
raise ValueError("Expected f-string")
|
|
621
|
+
expression.add_node(f_string)
|
|
622
|
+
elif self.token.is_left_parenthesis():
|
|
623
|
+
self.set_next_token()
|
|
624
|
+
sub = self._parse_expression()
|
|
625
|
+
if sub is None:
|
|
626
|
+
raise ValueError("Expected expression")
|
|
627
|
+
if not self.token.is_right_parenthesis():
|
|
628
|
+
raise ValueError("Expected right parenthesis")
|
|
629
|
+
self.set_next_token()
|
|
630
|
+
lookup = self._parse_lookup(sub)
|
|
631
|
+
expression.add_node(lookup)
|
|
632
|
+
elif self.token.is_opening_brace() or self.token.is_opening_bracket():
|
|
633
|
+
json = self._parse_json()
|
|
634
|
+
if json is None:
|
|
635
|
+
raise ValueError("Expected JSON object")
|
|
636
|
+
lookup = self._parse_lookup(json)
|
|
637
|
+
expression.add_node(lookup)
|
|
638
|
+
elif self.token.is_case():
|
|
639
|
+
case = self._parse_case()
|
|
640
|
+
if case is None:
|
|
641
|
+
raise ValueError("Expected CASE statement")
|
|
642
|
+
expression.add_node(case)
|
|
643
|
+
elif self.token.is_not():
|
|
644
|
+
not_node = Not()
|
|
645
|
+
self.set_next_token()
|
|
646
|
+
sub = self._parse_expression()
|
|
647
|
+
if sub is None:
|
|
648
|
+
raise ValueError("Expected expression")
|
|
649
|
+
not_node.add_child(sub)
|
|
650
|
+
expression.add_node(not_node)
|
|
651
|
+
else:
|
|
652
|
+
if expression.nodes_added():
|
|
653
|
+
raise ValueError("Expected operand or left parenthesis")
|
|
654
|
+
else:
|
|
655
|
+
break
|
|
656
|
+
self._skip_whitespace_and_comments()
|
|
657
|
+
if self.token.is_operator():
|
|
658
|
+
expression.add_node(self.token.node)
|
|
659
|
+
else:
|
|
660
|
+
break
|
|
661
|
+
self.set_next_token()
|
|
662
|
+
|
|
663
|
+
if expression.nodes_added():
|
|
664
|
+
expression.finish()
|
|
665
|
+
return expression
|
|
666
|
+
return None
|
|
667
|
+
|
|
668
|
+
def _parse_lookup(self, node: ASTNode) -> ASTNode:
|
|
669
|
+
variable = node
|
|
670
|
+
lookup = None
|
|
671
|
+
while True:
|
|
672
|
+
if self.token.is_dot():
|
|
673
|
+
self.set_next_token()
|
|
674
|
+
if not self.token.is_identifier() and not self.token.is_keyword():
|
|
675
|
+
raise ValueError("Expected identifier")
|
|
676
|
+
lookup = Lookup()
|
|
677
|
+
lookup.index = Identifier(self.token.value or "")
|
|
678
|
+
lookup.variable = variable
|
|
679
|
+
self.set_next_token()
|
|
680
|
+
elif self.token.is_opening_bracket():
|
|
681
|
+
self.set_next_token()
|
|
682
|
+
self._skip_whitespace_and_comments()
|
|
683
|
+
index = self._parse_expression()
|
|
684
|
+
to = None
|
|
685
|
+
self._skip_whitespace_and_comments()
|
|
686
|
+
if self.token.is_colon():
|
|
687
|
+
self.set_next_token()
|
|
688
|
+
self._skip_whitespace_and_comments()
|
|
689
|
+
lookup = RangeLookup()
|
|
690
|
+
to = self._parse_expression()
|
|
691
|
+
else:
|
|
692
|
+
if index is None:
|
|
693
|
+
raise ValueError("Expected expression")
|
|
694
|
+
lookup = Lookup()
|
|
695
|
+
self._skip_whitespace_and_comments()
|
|
696
|
+
if not self.token.is_closing_bracket():
|
|
697
|
+
raise ValueError("Expected closing bracket")
|
|
698
|
+
self.set_next_token()
|
|
699
|
+
if isinstance(lookup, RangeLookup):
|
|
700
|
+
lookup.from_ = index or Null()
|
|
701
|
+
lookup.to = to or Null()
|
|
702
|
+
elif isinstance(lookup, Lookup) and index is not None:
|
|
703
|
+
lookup.index = index
|
|
704
|
+
lookup.variable = variable
|
|
705
|
+
else:
|
|
706
|
+
break
|
|
707
|
+
variable = lookup or variable
|
|
708
|
+
return variable
|
|
709
|
+
|
|
710
|
+
def _parse_case(self) -> Optional[Case]:
|
|
711
|
+
if not self.token.is_case():
|
|
712
|
+
return None
|
|
713
|
+
self.set_next_token()
|
|
714
|
+
case = Case()
|
|
715
|
+
parts = 0
|
|
716
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
717
|
+
while True:
|
|
718
|
+
when = self._parse_when()
|
|
719
|
+
if when is None and parts == 0:
|
|
720
|
+
raise ValueError("Expected WHEN")
|
|
721
|
+
elif when is None and parts > 0:
|
|
722
|
+
break
|
|
723
|
+
elif when is not None:
|
|
724
|
+
case.add_child(when)
|
|
725
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
726
|
+
then = self._parse_then()
|
|
727
|
+
if then is None:
|
|
728
|
+
raise ValueError("Expected THEN")
|
|
729
|
+
else:
|
|
730
|
+
case.add_child(then)
|
|
731
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
732
|
+
parts += 1
|
|
733
|
+
else_ = self._parse_else()
|
|
734
|
+
if else_ is None:
|
|
735
|
+
raise ValueError("Expected ELSE")
|
|
736
|
+
else:
|
|
737
|
+
case.add_child(else_)
|
|
738
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
739
|
+
if not self.token.is_end():
|
|
740
|
+
raise ValueError("Expected END")
|
|
741
|
+
self.set_next_token()
|
|
742
|
+
return case
|
|
743
|
+
|
|
744
|
+
def _parse_when(self) -> Optional[When]:
|
|
745
|
+
if not self.token.is_when():
|
|
746
|
+
return None
|
|
747
|
+
self.set_next_token()
|
|
748
|
+
when = When()
|
|
749
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
750
|
+
expression = self._parse_expression()
|
|
751
|
+
if expression is None:
|
|
752
|
+
raise ValueError("Expected expression")
|
|
753
|
+
when.add_child(expression)
|
|
754
|
+
return when
|
|
755
|
+
|
|
756
|
+
def _parse_then(self) -> Optional[Then]:
|
|
757
|
+
if not self.token.is_then():
|
|
758
|
+
return None
|
|
759
|
+
self.set_next_token()
|
|
760
|
+
then = Then()
|
|
761
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
762
|
+
expression = self._parse_expression()
|
|
763
|
+
if expression is None:
|
|
764
|
+
raise ValueError("Expected expression")
|
|
765
|
+
then.add_child(expression)
|
|
766
|
+
return then
|
|
767
|
+
|
|
768
|
+
def _parse_else(self) -> Optional[Else]:
|
|
769
|
+
if not self.token.is_else():
|
|
770
|
+
return None
|
|
771
|
+
self.set_next_token()
|
|
772
|
+
else_ = Else()
|
|
773
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
774
|
+
expression = self._parse_expression()
|
|
775
|
+
if expression is None:
|
|
776
|
+
raise ValueError("Expected expression")
|
|
777
|
+
else_.add_child(expression)
|
|
778
|
+
return else_
|
|
779
|
+
|
|
780
|
+
def _parse_alias(self) -> Optional[Alias]:
|
|
781
|
+
self._skip_whitespace_and_comments()
|
|
782
|
+
if not self.token.is_as():
|
|
783
|
+
return None
|
|
784
|
+
self._expect_previous_token_to_be_whitespace_or_comment()
|
|
785
|
+
self.set_next_token()
|
|
786
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
787
|
+
if not self.token.is_identifier():
|
|
788
|
+
raise ValueError("Expected identifier")
|
|
789
|
+
alias = Alias(self.token.value or "")
|
|
790
|
+
self.set_next_token()
|
|
791
|
+
return alias
|
|
792
|
+
|
|
793
|
+
def _parse_predicate_function(self) -> Optional[PredicateFunction]:
|
|
794
|
+
"""Parse a predicate function like sum(n in [...] | n where condition)."""
|
|
795
|
+
# Lookahead: identifier ( identifier in
|
|
796
|
+
if not self.ahead([
|
|
797
|
+
Token.IDENTIFIER(""),
|
|
798
|
+
Token.LEFT_PARENTHESIS,
|
|
799
|
+
Token.IDENTIFIER(""),
|
|
800
|
+
Token.IN,
|
|
801
|
+
]):
|
|
802
|
+
return None
|
|
803
|
+
if self.token.value is None:
|
|
804
|
+
raise ValueError("Expected identifier")
|
|
805
|
+
func = FunctionFactory.create_predicate(self.token.value)
|
|
806
|
+
self.set_next_token()
|
|
807
|
+
if not self.token.is_left_parenthesis():
|
|
808
|
+
raise ValueError("Expected left parenthesis")
|
|
809
|
+
self.set_next_token()
|
|
810
|
+
self._skip_whitespace_and_comments()
|
|
811
|
+
if not self.token.is_identifier():
|
|
812
|
+
raise ValueError("Expected identifier")
|
|
813
|
+
reference = Reference(self.token.value)
|
|
814
|
+
self._variables[reference.identifier] = reference
|
|
815
|
+
func.add_child(reference)
|
|
816
|
+
self.set_next_token()
|
|
817
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
818
|
+
if not self.token.is_in():
|
|
819
|
+
raise ValueError("Expected IN")
|
|
820
|
+
self.set_next_token()
|
|
821
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
822
|
+
expression = self._parse_expression()
|
|
823
|
+
if expression is None:
|
|
824
|
+
raise ValueError("Expected expression")
|
|
825
|
+
if not ObjectUtils.is_instance_of_any(expression.first_child(), [
|
|
826
|
+
JSONArray,
|
|
827
|
+
Reference,
|
|
828
|
+
Lookup,
|
|
829
|
+
Function,
|
|
830
|
+
]):
|
|
831
|
+
raise ValueError("Expected array or reference")
|
|
832
|
+
func.add_child(expression)
|
|
833
|
+
self._skip_whitespace_and_comments()
|
|
834
|
+
if not self.token.is_pipe():
|
|
835
|
+
raise ValueError("Expected pipe")
|
|
836
|
+
self.set_next_token()
|
|
837
|
+
return_expr = self._parse_expression()
|
|
838
|
+
if return_expr is None:
|
|
839
|
+
raise ValueError("Expected expression")
|
|
840
|
+
func.add_child(return_expr)
|
|
841
|
+
where = self._parse_where()
|
|
842
|
+
if where is not None:
|
|
843
|
+
func.add_child(where)
|
|
844
|
+
self._skip_whitespace_and_comments()
|
|
845
|
+
if not self.token.is_right_parenthesis():
|
|
846
|
+
raise ValueError("Expected right parenthesis")
|
|
847
|
+
self.set_next_token()
|
|
848
|
+
del self._variables[reference.identifier]
|
|
849
|
+
return func
|
|
850
|
+
|
|
851
|
+
def _parse_function(self) -> Optional[Function]:
|
|
852
|
+
if not self.token.is_identifier():
|
|
853
|
+
return None
|
|
854
|
+
name = self.token.value or ""
|
|
855
|
+
if not self.peek() or not self.peek().is_left_parenthesis():
|
|
856
|
+
return None
|
|
857
|
+
|
|
858
|
+
try:
|
|
859
|
+
func = FunctionFactory.create(name)
|
|
860
|
+
except ValueError:
|
|
861
|
+
raise ValueError(f"Unknown function: {name}")
|
|
862
|
+
|
|
863
|
+
# Check for nested aggregate functions
|
|
864
|
+
if isinstance(func, AggregateFunction) and self._context.contains_type(AggregateFunction):
|
|
865
|
+
raise ValueError("Aggregate functions cannot be nested")
|
|
866
|
+
|
|
867
|
+
self._context.push(func)
|
|
868
|
+
self.set_next_token() # skip function name
|
|
869
|
+
self.set_next_token() # skip left parenthesis
|
|
870
|
+
self._skip_whitespace_and_comments()
|
|
871
|
+
|
|
872
|
+
# Check for DISTINCT keyword
|
|
873
|
+
if self.token.is_distinct():
|
|
874
|
+
func.distinct = True
|
|
875
|
+
self.set_next_token()
|
|
876
|
+
self._expect_and_skip_whitespace_and_comments()
|
|
877
|
+
|
|
878
|
+
params = list(self._parse_function_parameters())
|
|
879
|
+
func.parameters = params
|
|
880
|
+
|
|
881
|
+
if not self.token.is_right_parenthesis():
|
|
882
|
+
raise ValueError("Expected right parenthesis")
|
|
883
|
+
self.set_next_token()
|
|
884
|
+
self._context.pop()
|
|
885
|
+
return func
|
|
886
|
+
|
|
887
|
+
def _parse_async_function(self) -> Optional[AsyncFunction]:
|
|
888
|
+
if not self.token.is_identifier():
|
|
889
|
+
return None
|
|
890
|
+
name = self.token.value or ""
|
|
891
|
+
if not FunctionFactory.is_async_provider(name):
|
|
892
|
+
return None
|
|
893
|
+
self.set_next_token()
|
|
894
|
+
if not self.token.is_left_parenthesis():
|
|
895
|
+
raise ValueError("Expected left parenthesis")
|
|
896
|
+
self.set_next_token()
|
|
897
|
+
|
|
898
|
+
func = FunctionFactory.create_async(name)
|
|
899
|
+
params = list(self._parse_function_parameters())
|
|
900
|
+
func.parameters = params
|
|
901
|
+
|
|
902
|
+
if not self.token.is_right_parenthesis():
|
|
903
|
+
raise ValueError("Expected right parenthesis")
|
|
904
|
+
self.set_next_token()
|
|
905
|
+
return func
|
|
906
|
+
|
|
907
|
+
def _parse_function_parameters(self) -> Iterator[ASTNode]:
|
|
908
|
+
while True:
|
|
909
|
+
self._skip_whitespace_and_comments()
|
|
910
|
+
if self.token.is_right_parenthesis():
|
|
911
|
+
break
|
|
912
|
+
expr = self._parse_expression()
|
|
913
|
+
if expr is not None:
|
|
914
|
+
yield expr
|
|
915
|
+
self._skip_whitespace_and_comments()
|
|
916
|
+
if not self.token.is_comma():
|
|
917
|
+
break
|
|
918
|
+
self.set_next_token()
|
|
919
|
+
|
|
920
|
+
def _parse_json(self) -> Optional[ASTNode]:
|
|
921
|
+
if self.token.is_opening_brace():
|
|
922
|
+
return self._parse_associative_array()
|
|
923
|
+
elif self.token.is_opening_bracket():
|
|
924
|
+
return self._parse_json_array()
|
|
925
|
+
return None
|
|
926
|
+
|
|
927
|
+
def _parse_associative_array(self) -> AssociativeArray:
|
|
928
|
+
if not self.token.is_opening_brace():
|
|
929
|
+
raise ValueError("Expected opening brace")
|
|
930
|
+
self.set_next_token()
|
|
931
|
+
array = AssociativeArray()
|
|
932
|
+
while True:
|
|
933
|
+
self._skip_whitespace_and_comments()
|
|
934
|
+
if self.token.is_closing_brace():
|
|
935
|
+
break
|
|
936
|
+
if not self.token.is_identifier() and not self.token.is_string() and not self.token.is_keyword():
|
|
937
|
+
raise ValueError("Expected key identifier or string")
|
|
938
|
+
key = self.token.value or ""
|
|
939
|
+
self.set_next_token()
|
|
940
|
+
self._skip_whitespace_and_comments()
|
|
941
|
+
if not self.token.is_colon():
|
|
942
|
+
raise ValueError("Expected colon")
|
|
943
|
+
self.set_next_token()
|
|
944
|
+
self._skip_whitespace_and_comments()
|
|
945
|
+
value = self._parse_expression()
|
|
946
|
+
if value is None:
|
|
947
|
+
raise ValueError("Expected value")
|
|
948
|
+
array.add_key_value(KeyValuePair(key, value))
|
|
949
|
+
self._skip_whitespace_and_comments()
|
|
950
|
+
if not self.token.is_comma():
|
|
951
|
+
break
|
|
952
|
+
self.set_next_token()
|
|
953
|
+
if not self.token.is_closing_brace():
|
|
954
|
+
raise ValueError("Expected closing brace")
|
|
955
|
+
self.set_next_token()
|
|
956
|
+
return array
|
|
957
|
+
|
|
958
|
+
def _parse_json_array(self) -> JSONArray:
|
|
959
|
+
if not self.token.is_opening_bracket():
|
|
960
|
+
raise ValueError("Expected opening bracket")
|
|
961
|
+
self.set_next_token()
|
|
962
|
+
array = JSONArray()
|
|
963
|
+
while True:
|
|
964
|
+
self._skip_whitespace_and_comments()
|
|
965
|
+
if self.token.is_closing_bracket():
|
|
966
|
+
break
|
|
967
|
+
value = self._parse_expression()
|
|
968
|
+
if value is None:
|
|
969
|
+
break
|
|
970
|
+
array.add_value(value)
|
|
971
|
+
self._skip_whitespace_and_comments()
|
|
972
|
+
if not self.token.is_comma():
|
|
973
|
+
break
|
|
974
|
+
self.set_next_token()
|
|
975
|
+
if not self.token.is_closing_bracket():
|
|
976
|
+
raise ValueError("Expected closing bracket")
|
|
977
|
+
self.set_next_token()
|
|
978
|
+
return array
|
|
979
|
+
|
|
980
|
+
def _parse_f_string(self) -> Optional[FString]:
|
|
981
|
+
if not self.token.is_f_string():
|
|
982
|
+
return None
|
|
983
|
+
f_string = FString()
|
|
984
|
+
while self.token.is_f_string() or self.token.is_opening_brace():
|
|
985
|
+
if self.token.is_f_string():
|
|
986
|
+
f_string.add_child(String(self.token.value or ""))
|
|
987
|
+
self.set_next_token()
|
|
988
|
+
elif self.token.is_opening_brace():
|
|
989
|
+
self.set_next_token()
|
|
990
|
+
expr = self._parse_expression()
|
|
991
|
+
if expr is not None:
|
|
992
|
+
f_string.add_child(expr)
|
|
993
|
+
if self.token.is_closing_brace():
|
|
994
|
+
self.set_next_token()
|
|
995
|
+
return f_string
|
|
996
|
+
|
|
997
|
+
def _skip_whitespace_and_comments(self) -> bool:
|
|
998
|
+
skipped: bool = self.previous_token.is_whitespace_or_comment() if self.previous_token else False
|
|
999
|
+
while self.token.is_whitespace_or_comment():
|
|
1000
|
+
self.set_next_token()
|
|
1001
|
+
skipped = True
|
|
1002
|
+
return skipped
|
|
1003
|
+
|
|
1004
|
+
def _expect_and_skip_whitespace_and_comments(self) -> None:
|
|
1005
|
+
skipped = self._skip_whitespace_and_comments()
|
|
1006
|
+
if not skipped:
|
|
1007
|
+
raise ValueError("Expected whitespace")
|
|
1008
|
+
|
|
1009
|
+
def _expect_previous_token_to_be_whitespace_or_comment(self) -> None:
|
|
1010
|
+
if not self.previous_token.is_whitespace_or_comment():
|
|
1011
|
+
raise ValueError("Expected previous token to be whitespace or comment")
|