flowquery 1.0.18 → 1.0.21
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/.gitattributes +3 -0
- package/.github/workflows/python-publish.yml +56 -4
- package/.github/workflows/release.yml +26 -19
- package/.husky/pre-commit +26 -0
- package/README.md +37 -32
- package/dist/flowquery.min.js +1 -1
- package/dist/graph/data.d.ts +5 -4
- package/dist/graph/data.d.ts.map +1 -1
- package/dist/graph/data.js +38 -20
- package/dist/graph/data.js.map +1 -1
- package/dist/graph/node.d.ts +2 -0
- package/dist/graph/node.d.ts.map +1 -1
- package/dist/graph/node.js +23 -0
- package/dist/graph/node.js.map +1 -1
- package/dist/graph/node_data.js +1 -1
- package/dist/graph/node_data.js.map +1 -1
- package/dist/graph/pattern.d.ts.map +1 -1
- package/dist/graph/pattern.js +11 -4
- package/dist/graph/pattern.js.map +1 -1
- package/dist/graph/relationship.d.ts +6 -1
- package/dist/graph/relationship.d.ts.map +1 -1
- package/dist/graph/relationship.js +43 -5
- package/dist/graph/relationship.js.map +1 -1
- package/dist/graph/relationship_data.d.ts +2 -0
- package/dist/graph/relationship_data.d.ts.map +1 -1
- package/dist/graph/relationship_data.js +8 -1
- package/dist/graph/relationship_data.js.map +1 -1
- package/dist/graph/relationship_match_collector.js +2 -2
- package/dist/graph/relationship_match_collector.js.map +1 -1
- package/dist/graph/relationship_reference.d.ts.map +1 -1
- package/dist/graph/relationship_reference.js +2 -1
- package/dist/graph/relationship_reference.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/parsing/parser.d.ts +6 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +139 -72
- package/dist/parsing/parser.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/misc/data/test.json +10 -0
- package/flowquery-py/misc/data/users.json +242 -0
- package/flowquery-py/notebooks/TestFlowQuery.ipynb +440 -0
- package/flowquery-py/pyproject.toml +48 -2
- package/flowquery-py/src/__init__.py +7 -5
- package/flowquery-py/src/compute/runner.py +14 -10
- package/flowquery-py/src/extensibility.py +8 -8
- package/flowquery-py/src/graph/__init__.py +7 -7
- package/flowquery-py/src/graph/data.py +38 -20
- package/flowquery-py/src/graph/database.py +10 -20
- package/flowquery-py/src/graph/node.py +50 -19
- package/flowquery-py/src/graph/node_data.py +1 -1
- package/flowquery-py/src/graph/node_reference.py +10 -11
- package/flowquery-py/src/graph/pattern.py +27 -37
- package/flowquery-py/src/graph/pattern_expression.py +13 -11
- package/flowquery-py/src/graph/patterns.py +2 -2
- package/flowquery-py/src/graph/physical_node.py +4 -3
- package/flowquery-py/src/graph/physical_relationship.py +5 -5
- package/flowquery-py/src/graph/relationship.py +62 -14
- package/flowquery-py/src/graph/relationship_data.py +7 -2
- package/flowquery-py/src/graph/relationship_match_collector.py +15 -10
- package/flowquery-py/src/graph/relationship_reference.py +4 -4
- package/flowquery-py/src/io/command_line.py +13 -14
- package/flowquery-py/src/parsing/__init__.py +2 -2
- package/flowquery-py/src/parsing/alias_option.py +1 -1
- package/flowquery-py/src/parsing/ast_node.py +21 -20
- package/flowquery-py/src/parsing/base_parser.py +7 -7
- package/flowquery-py/src/parsing/components/__init__.py +3 -3
- package/flowquery-py/src/parsing/components/from_.py +3 -1
- package/flowquery-py/src/parsing/components/headers.py +2 -2
- package/flowquery-py/src/parsing/components/null.py +2 -2
- package/flowquery-py/src/parsing/context.py +7 -7
- package/flowquery-py/src/parsing/data_structures/associative_array.py +7 -7
- package/flowquery-py/src/parsing/data_structures/json_array.py +3 -3
- package/flowquery-py/src/parsing/data_structures/key_value_pair.py +4 -4
- package/flowquery-py/src/parsing/data_structures/lookup.py +2 -2
- package/flowquery-py/src/parsing/data_structures/range_lookup.py +2 -2
- package/flowquery-py/src/parsing/expressions/__init__.py +16 -16
- package/flowquery-py/src/parsing/expressions/expression.py +16 -13
- package/flowquery-py/src/parsing/expressions/expression_map.py +9 -9
- package/flowquery-py/src/parsing/expressions/f_string.py +3 -3
- package/flowquery-py/src/parsing/expressions/identifier.py +4 -3
- package/flowquery-py/src/parsing/expressions/number.py +3 -3
- package/flowquery-py/src/parsing/expressions/operator.py +16 -16
- package/flowquery-py/src/parsing/expressions/reference.py +3 -3
- package/flowquery-py/src/parsing/expressions/string.py +2 -2
- package/flowquery-py/src/parsing/functions/__init__.py +17 -17
- package/flowquery-py/src/parsing/functions/aggregate_function.py +8 -8
- package/flowquery-py/src/parsing/functions/async_function.py +12 -9
- package/flowquery-py/src/parsing/functions/avg.py +4 -4
- package/flowquery-py/src/parsing/functions/collect.py +6 -6
- package/flowquery-py/src/parsing/functions/function.py +6 -6
- package/flowquery-py/src/parsing/functions/function_factory.py +31 -34
- package/flowquery-py/src/parsing/functions/function_metadata.py +10 -11
- package/flowquery-py/src/parsing/functions/functions.py +14 -6
- package/flowquery-py/src/parsing/functions/join.py +3 -3
- package/flowquery-py/src/parsing/functions/keys.py +3 -3
- package/flowquery-py/src/parsing/functions/predicate_function.py +8 -7
- package/flowquery-py/src/parsing/functions/predicate_sum.py +12 -7
- package/flowquery-py/src/parsing/functions/rand.py +2 -2
- package/flowquery-py/src/parsing/functions/range_.py +9 -4
- package/flowquery-py/src/parsing/functions/replace.py +2 -2
- package/flowquery-py/src/parsing/functions/round_.py +2 -2
- package/flowquery-py/src/parsing/functions/size.py +2 -2
- package/flowquery-py/src/parsing/functions/split.py +9 -4
- package/flowquery-py/src/parsing/functions/stringify.py +3 -3
- package/flowquery-py/src/parsing/functions/sum.py +4 -4
- package/flowquery-py/src/parsing/functions/to_json.py +2 -2
- package/flowquery-py/src/parsing/functions/type_.py +3 -3
- package/flowquery-py/src/parsing/functions/value_holder.py +1 -1
- package/flowquery-py/src/parsing/logic/__init__.py +2 -2
- package/flowquery-py/src/parsing/logic/case.py +0 -1
- package/flowquery-py/src/parsing/logic/when.py +3 -1
- package/flowquery-py/src/parsing/operations/__init__.py +10 -10
- package/flowquery-py/src/parsing/operations/aggregated_return.py +3 -5
- package/flowquery-py/src/parsing/operations/aggregated_with.py +4 -4
- package/flowquery-py/src/parsing/operations/call.py +6 -7
- package/flowquery-py/src/parsing/operations/create_node.py +5 -4
- package/flowquery-py/src/parsing/operations/create_relationship.py +5 -4
- package/flowquery-py/src/parsing/operations/group_by.py +18 -16
- package/flowquery-py/src/parsing/operations/load.py +21 -19
- package/flowquery-py/src/parsing/operations/match.py +8 -7
- package/flowquery-py/src/parsing/operations/operation.py +3 -3
- package/flowquery-py/src/parsing/operations/projection.py +6 -6
- package/flowquery-py/src/parsing/operations/return_op.py +9 -5
- package/flowquery-py/src/parsing/operations/unwind.py +3 -2
- package/flowquery-py/src/parsing/operations/where.py +9 -7
- package/flowquery-py/src/parsing/operations/with_op.py +2 -2
- package/flowquery-py/src/parsing/parser.py +178 -114
- package/flowquery-py/src/parsing/token_to_node.py +2 -2
- package/flowquery-py/src/tokenization/__init__.py +4 -4
- package/flowquery-py/src/tokenization/keyword.py +1 -1
- package/flowquery-py/src/tokenization/operator.py +1 -1
- package/flowquery-py/src/tokenization/string_walker.py +4 -4
- package/flowquery-py/src/tokenization/symbol.py +1 -1
- package/flowquery-py/src/tokenization/token.py +11 -11
- package/flowquery-py/src/tokenization/token_mapper.py +10 -9
- package/flowquery-py/src/tokenization/token_type.py +1 -1
- package/flowquery-py/src/tokenization/tokenizer.py +19 -19
- package/flowquery-py/src/tokenization/trie.py +18 -17
- package/flowquery-py/src/utils/__init__.py +1 -1
- package/flowquery-py/src/utils/object_utils.py +3 -3
- package/flowquery-py/src/utils/string_utils.py +12 -12
- package/flowquery-py/tests/compute/test_runner.py +214 -7
- package/flowquery-py/tests/parsing/test_parser.py +41 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/graph/data.ts +38 -20
- package/src/graph/node.ts +23 -0
- package/src/graph/node_data.ts +1 -1
- package/src/graph/pattern.ts +13 -4
- package/src/graph/relationship.ts +45 -5
- package/src/graph/relationship_data.ts +8 -1
- package/src/graph/relationship_match_collector.ts +1 -1
- package/src/graph/relationship_reference.ts +2 -1
- package/src/index.ts +5 -5
- package/src/parsing/parser.ts +139 -71
- package/tests/compute/runner.test.ts +249 -79
- package/tests/parsing/parser.test.ts +32 -0
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, Dict, List
|
|
4
4
|
|
|
5
|
-
from .
|
|
5
|
+
from ...graph.database import Database
|
|
6
|
+
from ...graph.relationship import Relationship
|
|
6
7
|
from ..ast_node import ASTNode
|
|
8
|
+
from .operation import Operation
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class CreateRelationship(Operation):
|
|
10
12
|
"""Represents a CREATE operation for creating virtual relationships."""
|
|
11
13
|
|
|
12
|
-
def __init__(self, relationship, statement: ASTNode):
|
|
14
|
+
def __init__(self, relationship: Relationship, statement: ASTNode) -> None:
|
|
13
15
|
super().__init__()
|
|
14
16
|
self._relationship = relationship
|
|
15
17
|
self._statement = statement
|
|
16
18
|
|
|
17
19
|
@property
|
|
18
|
-
def relationship(self):
|
|
20
|
+
def relationship(self) -> Relationship:
|
|
19
21
|
return self._relationship
|
|
20
22
|
|
|
21
23
|
@property
|
|
@@ -25,7 +27,6 @@ class CreateRelationship(Operation):
|
|
|
25
27
|
async def run(self) -> None:
|
|
26
28
|
if self._relationship is None:
|
|
27
29
|
raise ValueError("Relationship is null")
|
|
28
|
-
from ...graph.database import Database
|
|
29
30
|
db = Database.get_instance()
|
|
30
31
|
db.add_relationship(self._relationship, self._statement)
|
|
31
32
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, Dict, Generator, List, Optional
|
|
4
4
|
|
|
5
|
-
from ..
|
|
5
|
+
from ..ast_node import ASTNode
|
|
6
6
|
from ..functions.aggregate_function import AggregateFunction
|
|
7
7
|
from ..functions.reducer_element import ReducerElement
|
|
8
8
|
from .projection import Projection
|
|
@@ -36,13 +36,13 @@ class GroupByNode:
|
|
|
36
36
|
class GroupBy(Projection):
|
|
37
37
|
"""Implements grouping and aggregation for FlowQuery operations."""
|
|
38
38
|
|
|
39
|
-
def __init__(self, expressions: List[
|
|
39
|
+
def __init__(self, expressions: List[ASTNode]) -> None:
|
|
40
40
|
super().__init__(expressions)
|
|
41
41
|
self._root = GroupByNode()
|
|
42
42
|
self._current = self._root
|
|
43
|
-
self._mappers: Optional[List[
|
|
43
|
+
self._mappers: Optional[List[Any]] = None
|
|
44
44
|
self._reducers: Optional[List[AggregateFunction]] = None
|
|
45
|
-
self._where = None
|
|
45
|
+
self._where: Optional[ASTNode] = None
|
|
46
46
|
|
|
47
47
|
async def run(self) -> None:
|
|
48
48
|
self._reset_tree()
|
|
@@ -71,18 +71,19 @@ class GroupBy(Projection):
|
|
|
71
71
|
if self._current.elements is None:
|
|
72
72
|
self._current.elements = [reducer.element() for reducer in self.reducers]
|
|
73
73
|
elements = self._current.elements
|
|
74
|
-
|
|
75
|
-
reducer.
|
|
74
|
+
if elements:
|
|
75
|
+
for i, reducer in enumerate(self.reducers):
|
|
76
|
+
reducer.reduce(elements[i])
|
|
76
77
|
|
|
77
78
|
@property
|
|
78
|
-
def mappers(self) -> List[
|
|
79
|
+
def mappers(self) -> List[Any]:
|
|
79
80
|
if self._mappers is None:
|
|
80
81
|
self._mappers = list(self._generate_mappers())
|
|
81
82
|
return self._mappers
|
|
82
83
|
|
|
83
|
-
def _generate_mappers(self) -> Generator[
|
|
84
|
+
def _generate_mappers(self) -> Generator[Any, None, None]:
|
|
84
85
|
for expression, _ in self.expressions():
|
|
85
|
-
if expression.mappable():
|
|
86
|
+
if hasattr(expression, 'mappable') and expression.mappable():
|
|
86
87
|
yield expression
|
|
87
88
|
|
|
88
89
|
@property
|
|
@@ -90,17 +91,18 @@ class GroupBy(Projection):
|
|
|
90
91
|
if self._reducers is None:
|
|
91
92
|
self._reducers = []
|
|
92
93
|
for child in self.children:
|
|
93
|
-
|
|
94
|
+
if hasattr(child, 'reducers'):
|
|
95
|
+
self._reducers.extend(child.reducers())
|
|
94
96
|
return self._reducers
|
|
95
97
|
|
|
96
98
|
def generate_results(
|
|
97
|
-
self,
|
|
98
|
-
mapper_index: int = 0,
|
|
99
|
+
self,
|
|
100
|
+
mapper_index: int = 0,
|
|
99
101
|
node: Optional[GroupByNode] = None
|
|
100
102
|
) -> Generator[Dict[str, Any], None, None]:
|
|
101
103
|
if node is None:
|
|
102
104
|
node = self._root
|
|
103
|
-
|
|
105
|
+
|
|
104
106
|
if len(node.children) > 0:
|
|
105
107
|
for child in node.children.values():
|
|
106
108
|
self.mappers[mapper_index].overridden = child.value
|
|
@@ -116,15 +118,15 @@ class GroupBy(Projection):
|
|
|
116
118
|
yield record
|
|
117
119
|
|
|
118
120
|
@property
|
|
119
|
-
def where(self):
|
|
121
|
+
def where(self) -> Optional[ASTNode]:
|
|
120
122
|
return self._where
|
|
121
123
|
|
|
122
124
|
@where.setter
|
|
123
|
-
def where(self, where) -> None:
|
|
125
|
+
def where(self, where: Optional[ASTNode]) -> None:
|
|
124
126
|
self._where = where
|
|
125
127
|
|
|
126
128
|
@property
|
|
127
|
-
def where_condition(self) ->
|
|
129
|
+
def where_condition(self) -> Any:
|
|
128
130
|
if self._where is None:
|
|
129
131
|
return True
|
|
130
132
|
return self._where.value()
|
|
@@ -3,24 +3,31 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from typing import Any, Dict, Optional
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import aiohttp
|
|
7
|
+
|
|
8
|
+
from ..ast_node import ASTNode
|
|
9
|
+
from ..components.headers import Headers
|
|
10
|
+
from ..components.json import JSON as JSONComponent
|
|
11
|
+
from ..components.post import Post
|
|
12
|
+
from ..components.text import Text
|
|
7
13
|
from ..functions.async_function import AsyncFunction
|
|
14
|
+
from .operation import Operation
|
|
8
15
|
|
|
9
16
|
|
|
10
17
|
class Load(Operation):
|
|
11
18
|
"""Represents a LOAD operation that fetches data from external sources."""
|
|
12
19
|
|
|
13
|
-
def __init__(self):
|
|
20
|
+
def __init__(self) -> None:
|
|
14
21
|
super().__init__()
|
|
15
22
|
self._value: Any = None
|
|
16
23
|
|
|
17
24
|
@property
|
|
18
|
-
def type(self):
|
|
25
|
+
def type(self) -> ASTNode:
|
|
19
26
|
"""Gets the data type (JSON, CSV, or Text)."""
|
|
20
27
|
return self.children[0]
|
|
21
28
|
|
|
22
29
|
@property
|
|
23
|
-
def from_component(self):
|
|
30
|
+
def from_component(self) -> ASTNode:
|
|
24
31
|
"""Gets the From component which contains either a URL expression or an AsyncFunction."""
|
|
25
32
|
return self.children[1]
|
|
26
33
|
|
|
@@ -36,19 +43,17 @@ class Load(Operation):
|
|
|
36
43
|
return child if isinstance(child, AsyncFunction) else None
|
|
37
44
|
|
|
38
45
|
@property
|
|
39
|
-
def from_(self) ->
|
|
46
|
+
def from_(self) -> Any:
|
|
40
47
|
return self.children[1].value()
|
|
41
48
|
|
|
42
49
|
@property
|
|
43
50
|
def headers(self) -> Dict[str, str]:
|
|
44
|
-
from ..components.headers import Headers
|
|
45
51
|
if self.child_count() > 2 and isinstance(self.children[2], Headers):
|
|
46
52
|
return self.children[2].value() or {}
|
|
47
53
|
return {}
|
|
48
54
|
|
|
49
55
|
@property
|
|
50
|
-
def payload(self):
|
|
51
|
-
from ..components.post import Post
|
|
56
|
+
def payload(self) -> Optional[ASTNode]:
|
|
52
57
|
post = None
|
|
53
58
|
if self.child_count() > 2 and isinstance(self.children[2], Post):
|
|
54
59
|
post = self.children[2]
|
|
@@ -86,26 +91,22 @@ class Load(Operation):
|
|
|
86
91
|
|
|
87
92
|
async def _load_from_url(self) -> None:
|
|
88
93
|
"""Loads data from a URL source."""
|
|
89
|
-
import aiohttp
|
|
90
|
-
from ..components.json import JSON as JSONComponent
|
|
91
|
-
from ..components.text import Text
|
|
92
|
-
|
|
93
94
|
async with aiohttp.ClientSession() as session:
|
|
94
95
|
options = self._options()
|
|
95
96
|
method = options.pop("method")
|
|
96
97
|
headers = options.pop("headers", {})
|
|
97
98
|
body = options.pop("body", None)
|
|
98
|
-
|
|
99
|
+
|
|
99
100
|
# Set Accept-Encoding to support common compression formats
|
|
100
101
|
# Note: brotli (br) is excluded due to API incompatibility between
|
|
101
102
|
# aiohttp 3.13+ and the brotli package's Decompressor.decompress() method
|
|
102
103
|
if "Accept-Encoding" not in headers:
|
|
103
104
|
headers["Accept-Encoding"] = "gzip, deflate"
|
|
104
|
-
|
|
105
|
+
|
|
105
106
|
async with session.request(
|
|
106
|
-
method,
|
|
107
|
-
self.from_,
|
|
108
|
-
headers=headers,
|
|
107
|
+
method,
|
|
108
|
+
self.from_,
|
|
109
|
+
headers=headers,
|
|
109
110
|
data=body
|
|
110
111
|
) as response:
|
|
111
112
|
if isinstance(self.type, JSONComponent):
|
|
@@ -114,7 +115,7 @@ class Load(Operation):
|
|
|
114
115
|
data = await response.text()
|
|
115
116
|
else:
|
|
116
117
|
data = await response.text()
|
|
117
|
-
|
|
118
|
+
|
|
118
119
|
if isinstance(data, list):
|
|
119
120
|
for item in data:
|
|
120
121
|
self._value = item
|
|
@@ -139,7 +140,8 @@ class Load(Operation):
|
|
|
139
140
|
try:
|
|
140
141
|
await self.load()
|
|
141
142
|
except Exception as e:
|
|
142
|
-
|
|
143
|
+
async_func = self.async_function
|
|
144
|
+
source = async_func.name if async_func else self.from_
|
|
143
145
|
raise RuntimeError(f"Failed to load data from {source}. Error: {e}")
|
|
144
146
|
|
|
145
147
|
def value(self) -> Any:
|
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
"""Represents a MATCH operation for graph pattern matching."""
|
|
2
2
|
|
|
3
|
-
from typing import List
|
|
3
|
+
from typing import List, Optional
|
|
4
4
|
|
|
5
|
+
from ...graph.pattern import Pattern
|
|
6
|
+
from ...graph.patterns import Patterns
|
|
5
7
|
from .operation import Operation
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class Match(Operation):
|
|
9
11
|
"""Represents a MATCH operation for graph pattern matching."""
|
|
10
12
|
|
|
11
|
-
def __init__(self, patterns=None):
|
|
13
|
+
def __init__(self, patterns: Optional[List[Pattern]] = None) -> None:
|
|
12
14
|
super().__init__()
|
|
13
|
-
from ...graph.patterns import Patterns
|
|
14
15
|
self._patterns = Patterns(patterns or [])
|
|
15
16
|
|
|
16
17
|
@property
|
|
17
|
-
def patterns(self):
|
|
18
|
+
def patterns(self) -> List[Pattern]:
|
|
18
19
|
return self._patterns.patterns if self._patterns else []
|
|
19
20
|
|
|
20
21
|
async def run(self) -> None:
|
|
21
22
|
"""Executes the match operation by chaining the patterns together."""
|
|
22
23
|
await self._patterns.initialize()
|
|
23
|
-
|
|
24
|
-
async def to_do_next():
|
|
24
|
+
|
|
25
|
+
async def to_do_next() -> None:
|
|
25
26
|
if self.next:
|
|
26
27
|
await self.next.run()
|
|
27
|
-
|
|
28
|
+
|
|
28
29
|
self._patterns.to_do_next = to_do_next
|
|
29
30
|
await self._patterns.traverse()
|
|
@@ -8,12 +8,12 @@ from ..ast_node import ASTNode
|
|
|
8
8
|
|
|
9
9
|
class Operation(ASTNode, ABC):
|
|
10
10
|
"""Base class for all FlowQuery operations.
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
Operations represent the main statements in FlowQuery (WITH, UNWIND, RETURN, LOAD, WHERE).
|
|
13
13
|
They form a linked list structure and can be executed sequentially.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
def __init__(self):
|
|
16
|
+
def __init__(self) -> None:
|
|
17
17
|
super().__init__()
|
|
18
18
|
self._previous: Optional[Operation] = None
|
|
19
19
|
self._next: Optional[Operation] = None
|
|
@@ -46,7 +46,7 @@ class Operation(ASTNode, ABC):
|
|
|
46
46
|
|
|
47
47
|
async def run(self) -> None:
|
|
48
48
|
"""Executes this operation. Must be implemented by subclasses.
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
Raises:
|
|
51
51
|
NotImplementedError: If not implemented by subclass
|
|
52
52
|
"""
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
"""Base class for projection operations."""
|
|
2
2
|
|
|
3
|
-
from typing import Generator, List, Tuple
|
|
3
|
+
from typing import Any, Generator, List, Tuple
|
|
4
4
|
|
|
5
|
-
from ..
|
|
5
|
+
from ..ast_node import ASTNode
|
|
6
6
|
from .operation import Operation
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class Projection(Operation):
|
|
10
10
|
"""Base class for operations that project expressions."""
|
|
11
11
|
|
|
12
|
-
def __init__(self, expressions: List[
|
|
12
|
+
def __init__(self, expressions: List[ASTNode]):
|
|
13
13
|
super().__init__()
|
|
14
14
|
self.children = expressions
|
|
15
15
|
|
|
16
|
-
def expressions(self) -> Generator[Tuple[
|
|
16
|
+
def expressions(self) -> Generator[Tuple[Any, str], None, None]:
|
|
17
17
|
"""Yields tuples of (expression, alias) for all child expressions."""
|
|
18
18
|
for i, child in enumerate(self.children):
|
|
19
|
-
expression
|
|
20
|
-
alias = expression
|
|
19
|
+
expression = child
|
|
20
|
+
alias = getattr(expression, 'alias', None) or f"expr{i}"
|
|
21
21
|
yield (expression, alias)
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
"""Represents a RETURN operation that produces the final query results."""
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
|
-
from typing import Any, Dict, List, Optional
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
5
5
|
|
|
6
|
+
from ..ast_node import ASTNode
|
|
6
7
|
from .projection import Projection
|
|
7
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .where import Where
|
|
11
|
+
|
|
8
12
|
|
|
9
13
|
class Return(Projection):
|
|
10
14
|
"""Represents a RETURN operation that produces the final query results.
|
|
11
|
-
|
|
15
|
+
|
|
12
16
|
The RETURN operation evaluates expressions and collects them into result records.
|
|
13
17
|
It can optionally have a WHERE clause to filter results.
|
|
14
|
-
|
|
18
|
+
|
|
15
19
|
Example:
|
|
16
20
|
# RETURN x, y WHERE x > 0
|
|
17
21
|
"""
|
|
18
22
|
|
|
19
|
-
def __init__(self, expressions):
|
|
23
|
+
def __init__(self, expressions: List[ASTNode]) -> None:
|
|
20
24
|
super().__init__(expressions)
|
|
21
25
|
self._where: Optional['Where'] = None
|
|
22
26
|
self._results: List[Dict[str, Any]] = []
|
|
23
27
|
|
|
24
28
|
@property
|
|
25
|
-
def where(self) ->
|
|
29
|
+
def where(self) -> Any:
|
|
26
30
|
if self._where is None:
|
|
27
31
|
return True
|
|
28
32
|
return self._where.value()
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
+
from ..ast_node import ASTNode
|
|
5
6
|
from ..expressions.expression import Expression
|
|
6
7
|
from .operation import Operation
|
|
7
8
|
|
|
@@ -15,11 +16,11 @@ class Unwind(Operation):
|
|
|
15
16
|
self.add_child(expression)
|
|
16
17
|
|
|
17
18
|
@property
|
|
18
|
-
def expression(self) ->
|
|
19
|
+
def expression(self) -> ASTNode:
|
|
19
20
|
return self.children[0]
|
|
20
21
|
|
|
21
22
|
@property
|
|
22
|
-
def as_(self) ->
|
|
23
|
+
def as_(self) -> Any:
|
|
23
24
|
return self.children[1].value()
|
|
24
25
|
|
|
25
26
|
async def run(self) -> None:
|
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
+
from ..ast_node import ASTNode
|
|
5
6
|
from ..expressions.expression import Expression
|
|
6
7
|
from .operation import Operation
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Where(Operation):
|
|
10
11
|
"""Represents a WHERE operation that filters data based on a condition.
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
The WHERE operation evaluates a boolean expression and only continues
|
|
13
14
|
execution to the next operation if the condition is true.
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
Example:
|
|
16
17
|
# RETURN x WHERE x > 0
|
|
17
18
|
"""
|
|
18
19
|
|
|
19
20
|
def __init__(self, expression: Expression):
|
|
20
21
|
"""Creates a new WHERE operation with the given condition.
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
Args:
|
|
23
24
|
expression: The boolean expression to evaluate
|
|
24
25
|
"""
|
|
@@ -26,13 +27,14 @@ class Where(Operation):
|
|
|
26
27
|
self.add_child(expression)
|
|
27
28
|
|
|
28
29
|
@property
|
|
29
|
-
def expression(self) ->
|
|
30
|
+
def expression(self) -> ASTNode:
|
|
30
31
|
return self.children[0]
|
|
31
32
|
|
|
32
33
|
async def run(self) -> None:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
if hasattr(self.expression, 'patterns'):
|
|
35
|
+
for pattern in self.expression.patterns():
|
|
36
|
+
await pattern.fetch_data()
|
|
37
|
+
await pattern.evaluate()
|
|
36
38
|
if self.expression.value():
|
|
37
39
|
if self.next:
|
|
38
40
|
await self.next.run()
|
|
@@ -5,10 +5,10 @@ from .projection import Projection
|
|
|
5
5
|
|
|
6
6
|
class With(Projection):
|
|
7
7
|
"""Represents a WITH operation that defines variables or intermediate results.
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
The WITH operation creates named expressions that can be referenced later in the query.
|
|
10
10
|
It passes control to the next operation in the chain.
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
Example:
|
|
13
13
|
# WITH x = 1, y = 2 RETURN x + y
|
|
14
14
|
"""
|