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.
Files changed (158) hide show
  1. package/.gitattributes +3 -0
  2. package/.github/workflows/python-publish.yml +56 -4
  3. package/.github/workflows/release.yml +26 -19
  4. package/.husky/pre-commit +26 -0
  5. package/README.md +37 -32
  6. package/dist/flowquery.min.js +1 -1
  7. package/dist/graph/data.d.ts +5 -4
  8. package/dist/graph/data.d.ts.map +1 -1
  9. package/dist/graph/data.js +38 -20
  10. package/dist/graph/data.js.map +1 -1
  11. package/dist/graph/node.d.ts +2 -0
  12. package/dist/graph/node.d.ts.map +1 -1
  13. package/dist/graph/node.js +23 -0
  14. package/dist/graph/node.js.map +1 -1
  15. package/dist/graph/node_data.js +1 -1
  16. package/dist/graph/node_data.js.map +1 -1
  17. package/dist/graph/pattern.d.ts.map +1 -1
  18. package/dist/graph/pattern.js +11 -4
  19. package/dist/graph/pattern.js.map +1 -1
  20. package/dist/graph/relationship.d.ts +6 -1
  21. package/dist/graph/relationship.d.ts.map +1 -1
  22. package/dist/graph/relationship.js +43 -5
  23. package/dist/graph/relationship.js.map +1 -1
  24. package/dist/graph/relationship_data.d.ts +2 -0
  25. package/dist/graph/relationship_data.d.ts.map +1 -1
  26. package/dist/graph/relationship_data.js +8 -1
  27. package/dist/graph/relationship_data.js.map +1 -1
  28. package/dist/graph/relationship_match_collector.js +2 -2
  29. package/dist/graph/relationship_match_collector.js.map +1 -1
  30. package/dist/graph/relationship_reference.d.ts.map +1 -1
  31. package/dist/graph/relationship_reference.js +2 -1
  32. package/dist/graph/relationship_reference.js.map +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/parsing/parser.d.ts +6 -0
  36. package/dist/parsing/parser.d.ts.map +1 -1
  37. package/dist/parsing/parser.js +139 -72
  38. package/dist/parsing/parser.js.map +1 -1
  39. package/docs/flowquery.min.js +1 -1
  40. package/flowquery-py/misc/data/test.json +10 -0
  41. package/flowquery-py/misc/data/users.json +242 -0
  42. package/flowquery-py/notebooks/TestFlowQuery.ipynb +440 -0
  43. package/flowquery-py/pyproject.toml +48 -2
  44. package/flowquery-py/src/__init__.py +7 -5
  45. package/flowquery-py/src/compute/runner.py +14 -10
  46. package/flowquery-py/src/extensibility.py +8 -8
  47. package/flowquery-py/src/graph/__init__.py +7 -7
  48. package/flowquery-py/src/graph/data.py +38 -20
  49. package/flowquery-py/src/graph/database.py +10 -20
  50. package/flowquery-py/src/graph/node.py +50 -19
  51. package/flowquery-py/src/graph/node_data.py +1 -1
  52. package/flowquery-py/src/graph/node_reference.py +10 -11
  53. package/flowquery-py/src/graph/pattern.py +27 -37
  54. package/flowquery-py/src/graph/pattern_expression.py +13 -11
  55. package/flowquery-py/src/graph/patterns.py +2 -2
  56. package/flowquery-py/src/graph/physical_node.py +4 -3
  57. package/flowquery-py/src/graph/physical_relationship.py +5 -5
  58. package/flowquery-py/src/graph/relationship.py +62 -14
  59. package/flowquery-py/src/graph/relationship_data.py +7 -2
  60. package/flowquery-py/src/graph/relationship_match_collector.py +15 -10
  61. package/flowquery-py/src/graph/relationship_reference.py +4 -4
  62. package/flowquery-py/src/io/command_line.py +13 -14
  63. package/flowquery-py/src/parsing/__init__.py +2 -2
  64. package/flowquery-py/src/parsing/alias_option.py +1 -1
  65. package/flowquery-py/src/parsing/ast_node.py +21 -20
  66. package/flowquery-py/src/parsing/base_parser.py +7 -7
  67. package/flowquery-py/src/parsing/components/__init__.py +3 -3
  68. package/flowquery-py/src/parsing/components/from_.py +3 -1
  69. package/flowquery-py/src/parsing/components/headers.py +2 -2
  70. package/flowquery-py/src/parsing/components/null.py +2 -2
  71. package/flowquery-py/src/parsing/context.py +7 -7
  72. package/flowquery-py/src/parsing/data_structures/associative_array.py +7 -7
  73. package/flowquery-py/src/parsing/data_structures/json_array.py +3 -3
  74. package/flowquery-py/src/parsing/data_structures/key_value_pair.py +4 -4
  75. package/flowquery-py/src/parsing/data_structures/lookup.py +2 -2
  76. package/flowquery-py/src/parsing/data_structures/range_lookup.py +2 -2
  77. package/flowquery-py/src/parsing/expressions/__init__.py +16 -16
  78. package/flowquery-py/src/parsing/expressions/expression.py +16 -13
  79. package/flowquery-py/src/parsing/expressions/expression_map.py +9 -9
  80. package/flowquery-py/src/parsing/expressions/f_string.py +3 -3
  81. package/flowquery-py/src/parsing/expressions/identifier.py +4 -3
  82. package/flowquery-py/src/parsing/expressions/number.py +3 -3
  83. package/flowquery-py/src/parsing/expressions/operator.py +16 -16
  84. package/flowquery-py/src/parsing/expressions/reference.py +3 -3
  85. package/flowquery-py/src/parsing/expressions/string.py +2 -2
  86. package/flowquery-py/src/parsing/functions/__init__.py +17 -17
  87. package/flowquery-py/src/parsing/functions/aggregate_function.py +8 -8
  88. package/flowquery-py/src/parsing/functions/async_function.py +12 -9
  89. package/flowquery-py/src/parsing/functions/avg.py +4 -4
  90. package/flowquery-py/src/parsing/functions/collect.py +6 -6
  91. package/flowquery-py/src/parsing/functions/function.py +6 -6
  92. package/flowquery-py/src/parsing/functions/function_factory.py +31 -34
  93. package/flowquery-py/src/parsing/functions/function_metadata.py +10 -11
  94. package/flowquery-py/src/parsing/functions/functions.py +14 -6
  95. package/flowquery-py/src/parsing/functions/join.py +3 -3
  96. package/flowquery-py/src/parsing/functions/keys.py +3 -3
  97. package/flowquery-py/src/parsing/functions/predicate_function.py +8 -7
  98. package/flowquery-py/src/parsing/functions/predicate_sum.py +12 -7
  99. package/flowquery-py/src/parsing/functions/rand.py +2 -2
  100. package/flowquery-py/src/parsing/functions/range_.py +9 -4
  101. package/flowquery-py/src/parsing/functions/replace.py +2 -2
  102. package/flowquery-py/src/parsing/functions/round_.py +2 -2
  103. package/flowquery-py/src/parsing/functions/size.py +2 -2
  104. package/flowquery-py/src/parsing/functions/split.py +9 -4
  105. package/flowquery-py/src/parsing/functions/stringify.py +3 -3
  106. package/flowquery-py/src/parsing/functions/sum.py +4 -4
  107. package/flowquery-py/src/parsing/functions/to_json.py +2 -2
  108. package/flowquery-py/src/parsing/functions/type_.py +3 -3
  109. package/flowquery-py/src/parsing/functions/value_holder.py +1 -1
  110. package/flowquery-py/src/parsing/logic/__init__.py +2 -2
  111. package/flowquery-py/src/parsing/logic/case.py +0 -1
  112. package/flowquery-py/src/parsing/logic/when.py +3 -1
  113. package/flowquery-py/src/parsing/operations/__init__.py +10 -10
  114. package/flowquery-py/src/parsing/operations/aggregated_return.py +3 -5
  115. package/flowquery-py/src/parsing/operations/aggregated_with.py +4 -4
  116. package/flowquery-py/src/parsing/operations/call.py +6 -7
  117. package/flowquery-py/src/parsing/operations/create_node.py +5 -4
  118. package/flowquery-py/src/parsing/operations/create_relationship.py +5 -4
  119. package/flowquery-py/src/parsing/operations/group_by.py +18 -16
  120. package/flowquery-py/src/parsing/operations/load.py +21 -19
  121. package/flowquery-py/src/parsing/operations/match.py +8 -7
  122. package/flowquery-py/src/parsing/operations/operation.py +3 -3
  123. package/flowquery-py/src/parsing/operations/projection.py +6 -6
  124. package/flowquery-py/src/parsing/operations/return_op.py +9 -5
  125. package/flowquery-py/src/parsing/operations/unwind.py +3 -2
  126. package/flowquery-py/src/parsing/operations/where.py +9 -7
  127. package/flowquery-py/src/parsing/operations/with_op.py +2 -2
  128. package/flowquery-py/src/parsing/parser.py +178 -114
  129. package/flowquery-py/src/parsing/token_to_node.py +2 -2
  130. package/flowquery-py/src/tokenization/__init__.py +4 -4
  131. package/flowquery-py/src/tokenization/keyword.py +1 -1
  132. package/flowquery-py/src/tokenization/operator.py +1 -1
  133. package/flowquery-py/src/tokenization/string_walker.py +4 -4
  134. package/flowquery-py/src/tokenization/symbol.py +1 -1
  135. package/flowquery-py/src/tokenization/token.py +11 -11
  136. package/flowquery-py/src/tokenization/token_mapper.py +10 -9
  137. package/flowquery-py/src/tokenization/token_type.py +1 -1
  138. package/flowquery-py/src/tokenization/tokenizer.py +19 -19
  139. package/flowquery-py/src/tokenization/trie.py +18 -17
  140. package/flowquery-py/src/utils/__init__.py +1 -1
  141. package/flowquery-py/src/utils/object_utils.py +3 -3
  142. package/flowquery-py/src/utils/string_utils.py +12 -12
  143. package/flowquery-py/tests/compute/test_runner.py +214 -7
  144. package/flowquery-py/tests/parsing/test_parser.py +41 -0
  145. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  146. package/package.json +1 -1
  147. package/src/graph/data.ts +38 -20
  148. package/src/graph/node.ts +23 -0
  149. package/src/graph/node_data.ts +1 -1
  150. package/src/graph/pattern.ts +13 -4
  151. package/src/graph/relationship.ts +45 -5
  152. package/src/graph/relationship_data.ts +8 -1
  153. package/src/graph/relationship_match_collector.ts +1 -1
  154. package/src/graph/relationship_reference.ts +2 -1
  155. package/src/index.ts +5 -5
  156. package/src/parsing/parser.ts +139 -71
  157. package/tests/compute/runner.test.ts +249 -79
  158. 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 .operation import Operation
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 ..expressions.expression import Expression
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[Expression]):
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[Expression]] = None
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
- for i, reducer in enumerate(self.reducers):
75
- reducer.reduce(elements[i])
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[Expression]:
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[Expression, None, None]:
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
- self._reducers.extend(child.reducers())
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) -> bool:
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
- from .operation import Operation
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) -> str:
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
- source = self.async_function.name if self.is_async_function else self.from_
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, Optional
3
+ from typing import Any, Generator, List, Tuple
4
4
 
5
- from ..expressions.expression import Expression
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[Expression]):
12
+ def __init__(self, expressions: List[ASTNode]):
13
13
  super().__init__()
14
14
  self.children = expressions
15
15
 
16
- def expressions(self) -> Generator[Tuple[Expression, str], None, None]:
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: Expression = child
20
- alias = expression.alias or f"expr{i}"
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) -> bool:
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) -> Expression:
19
+ def expression(self) -> ASTNode:
19
20
  return self.children[0]
20
21
 
21
22
  @property
22
- def as_(self) -> str:
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) -> Expression:
30
+ def expression(self) -> ASTNode:
30
31
  return self.children[0]
31
32
 
32
33
  async def run(self) -> None:
33
- for pattern in self.expression.patterns():
34
- await pattern.fetch_data()
35
- await pattern.evaluate()
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
  """