flowquery 1.0.15 → 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.
Files changed (333) hide show
  1. package/.github/workflows/python-publish.yml +97 -0
  2. package/dist/compute/runner.d.ts +3 -2
  3. package/dist/compute/runner.d.ts.map +1 -1
  4. package/dist/compute/runner.js +7 -7
  5. package/dist/compute/runner.js.map +1 -1
  6. package/dist/flowquery.min.js +1 -1
  7. package/dist/graph/data.d.ts +31 -0
  8. package/dist/graph/data.d.ts.map +1 -0
  9. package/dist/graph/data.js +110 -0
  10. package/dist/graph/data.js.map +1 -0
  11. package/dist/graph/database.d.ts +20 -0
  12. package/dist/graph/database.d.ts.map +1 -0
  13. package/dist/graph/database.js +77 -0
  14. package/dist/graph/database.js.map +1 -0
  15. package/dist/graph/hops.d.ts +11 -0
  16. package/dist/graph/hops.d.ts.map +1 -0
  17. package/dist/graph/hops.js +25 -0
  18. package/dist/graph/hops.js.map +1 -0
  19. package/dist/graph/node.d.ts +35 -0
  20. package/dist/graph/node.d.ts.map +1 -0
  21. package/dist/graph/node.js +113 -0
  22. package/dist/graph/node.js.map +1 -0
  23. package/dist/graph/node_data.d.ts +11 -0
  24. package/dist/graph/node_data.d.ts.map +1 -0
  25. package/dist/graph/node_data.js +20 -0
  26. package/dist/graph/node_data.js.map +1 -0
  27. package/dist/graph/node_reference.d.ts +10 -0
  28. package/dist/graph/node_reference.d.ts.map +1 -0
  29. package/dist/graph/node_reference.js +52 -0
  30. package/dist/graph/node_reference.js.map +1 -0
  31. package/dist/graph/pattern.d.ts +18 -0
  32. package/dist/graph/pattern.d.ts.map +1 -0
  33. package/dist/graph/pattern.js +114 -0
  34. package/dist/graph/pattern.js.map +1 -0
  35. package/dist/graph/pattern_expression.d.ts +14 -0
  36. package/dist/graph/pattern_expression.d.ts.map +1 -0
  37. package/dist/graph/pattern_expression.js +58 -0
  38. package/dist/graph/pattern_expression.js.map +1 -0
  39. package/dist/graph/patterns.d.ts +11 -0
  40. package/dist/graph/patterns.d.ts.map +1 -0
  41. package/dist/graph/patterns.js +49 -0
  42. package/dist/graph/patterns.js.map +1 -0
  43. package/dist/graph/physical_node.d.ts +10 -0
  44. package/dist/graph/physical_node.d.ts.map +1 -0
  45. package/dist/graph/physical_node.js +40 -0
  46. package/dist/graph/physical_node.js.map +1 -0
  47. package/dist/graph/physical_relationship.d.ts +10 -0
  48. package/dist/graph/physical_relationship.d.ts.map +1 -0
  49. package/dist/graph/physical_relationship.js +40 -0
  50. package/dist/graph/physical_relationship.js.map +1 -0
  51. package/dist/graph/relationship.d.ts +40 -0
  52. package/dist/graph/relationship.d.ts.map +1 -0
  53. package/dist/graph/relationship.js +124 -0
  54. package/dist/graph/relationship.js.map +1 -0
  55. package/dist/graph/relationship_data.d.ts +12 -0
  56. package/dist/graph/relationship_data.d.ts.map +1 -0
  57. package/dist/graph/relationship_data.js +40 -0
  58. package/dist/graph/relationship_data.js.map +1 -0
  59. package/dist/graph/relationship_match_collector.d.ts +19 -0
  60. package/dist/graph/relationship_match_collector.d.ts.map +1 -0
  61. package/dist/graph/relationship_match_collector.js +55 -0
  62. package/dist/graph/relationship_match_collector.js.map +1 -0
  63. package/dist/graph/relationship_reference.d.ts +8 -0
  64. package/dist/graph/relationship_reference.d.ts.map +1 -0
  65. package/dist/graph/relationship_reference.js +37 -0
  66. package/dist/graph/relationship_reference.js.map +1 -0
  67. package/dist/parsing/base_parser.d.ts +1 -0
  68. package/dist/parsing/base_parser.d.ts.map +1 -1
  69. package/dist/parsing/base_parser.js +4 -1
  70. package/dist/parsing/base_parser.js.map +1 -1
  71. package/dist/parsing/context.d.ts +2 -2
  72. package/dist/parsing/context.js +5 -5
  73. package/dist/parsing/expressions/boolean.d.ts +8 -0
  74. package/dist/parsing/expressions/boolean.d.ts.map +1 -0
  75. package/dist/parsing/expressions/boolean.js +26 -0
  76. package/dist/parsing/expressions/boolean.js.map +1 -0
  77. package/dist/parsing/expressions/expression.d.ts +4 -1
  78. package/dist/parsing/expressions/expression.d.ts.map +1 -1
  79. package/dist/parsing/expressions/expression.js +15 -8
  80. package/dist/parsing/expressions/expression.js.map +1 -1
  81. package/dist/parsing/expressions/expression_map.d.ts +1 -0
  82. package/dist/parsing/expressions/expression_map.d.ts.map +1 -1
  83. package/dist/parsing/expressions/expression_map.js +3 -0
  84. package/dist/parsing/expressions/expression_map.js.map +1 -1
  85. package/dist/parsing/expressions/operator.d.ts +1 -1
  86. package/dist/parsing/expressions/operator.d.ts.map +1 -1
  87. package/dist/parsing/expressions/operator.js.map +1 -1
  88. package/dist/parsing/functions/function_factory.d.ts +13 -13
  89. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  90. package/dist/parsing/functions/function_factory.js +20 -18
  91. package/dist/parsing/functions/function_factory.js.map +1 -1
  92. package/dist/parsing/operations/call.d.ts.map +1 -1
  93. package/dist/parsing/operations/call.js +3 -1
  94. package/dist/parsing/operations/call.js.map +1 -1
  95. package/dist/parsing/operations/create_node.d.ts +14 -0
  96. package/dist/parsing/operations/create_node.d.ts.map +1 -0
  97. package/dist/parsing/operations/create_node.js +51 -0
  98. package/dist/parsing/operations/create_node.js.map +1 -0
  99. package/dist/parsing/operations/create_relationship.d.ts +14 -0
  100. package/dist/parsing/operations/create_relationship.d.ts.map +1 -0
  101. package/dist/parsing/operations/create_relationship.js +51 -0
  102. package/dist/parsing/operations/create_relationship.js.map +1 -0
  103. package/dist/parsing/operations/match.d.ts +15 -0
  104. package/dist/parsing/operations/match.d.ts.map +1 -0
  105. package/dist/parsing/operations/match.js +45 -0
  106. package/dist/parsing/operations/match.js.map +1 -0
  107. package/dist/parsing/operations/operation.d.ts +1 -0
  108. package/dist/parsing/operations/operation.d.ts.map +1 -1
  109. package/dist/parsing/operations/operation.js +6 -0
  110. package/dist/parsing/operations/operation.js.map +1 -1
  111. package/dist/parsing/operations/return.d.ts +1 -0
  112. package/dist/parsing/operations/return.d.ts.map +1 -1
  113. package/dist/parsing/operations/return.js +7 -1
  114. package/dist/parsing/operations/return.js.map +1 -1
  115. package/dist/parsing/operations/where.d.ts +1 -1
  116. package/dist/parsing/operations/where.d.ts.map +1 -1
  117. package/dist/parsing/operations/where.js +4 -0
  118. package/dist/parsing/operations/where.js.map +1 -1
  119. package/dist/parsing/parser.d.ts +10 -0
  120. package/dist/parsing/parser.d.ts.map +1 -1
  121. package/dist/parsing/parser.js +344 -5
  122. package/dist/parsing/parser.js.map +1 -1
  123. package/dist/parsing/token_to_node.d.ts.map +1 -1
  124. package/dist/parsing/token_to_node.js +7 -0
  125. package/dist/parsing/token_to_node.js.map +1 -1
  126. package/dist/tokenization/keyword.d.ts +1 -0
  127. package/dist/tokenization/keyword.d.ts.map +1 -1
  128. package/dist/tokenization/keyword.js +1 -0
  129. package/dist/tokenization/keyword.js.map +1 -1
  130. package/dist/tokenization/token.d.ts +4 -0
  131. package/dist/tokenization/token.d.ts.map +1 -1
  132. package/dist/tokenization/token.js +14 -1
  133. package/dist/tokenization/token.js.map +1 -1
  134. package/dist/tokenization/token_type.d.ts +1 -0
  135. package/dist/tokenization/token_type.d.ts.map +1 -1
  136. package/dist/tokenization/token_type.js +1 -0
  137. package/dist/tokenization/token_type.js.map +1 -1
  138. package/dist/tokenization/tokenizer.d.ts +2 -1
  139. package/dist/tokenization/tokenizer.d.ts.map +1 -1
  140. package/dist/tokenization/tokenizer.js +25 -12
  141. package/dist/tokenization/tokenizer.js.map +1 -1
  142. package/docs/flowquery.min.js +1 -1
  143. package/flowquery-py/README.md +166 -0
  144. package/flowquery-py/pyproject.toml +75 -0
  145. package/flowquery-py/setup_env.ps1 +92 -0
  146. package/flowquery-py/setup_env.sh +87 -0
  147. package/flowquery-py/src/__init__.py +34 -0
  148. package/flowquery-py/src/__main__.py +10 -0
  149. package/flowquery-py/src/compute/__init__.py +5 -0
  150. package/flowquery-py/src/compute/runner.py +60 -0
  151. package/flowquery-py/src/extensibility.py +52 -0
  152. package/flowquery-py/src/graph/__init__.py +31 -0
  153. package/flowquery-py/src/graph/data.py +118 -0
  154. package/flowquery-py/src/graph/database.py +82 -0
  155. package/flowquery-py/src/graph/hops.py +43 -0
  156. package/flowquery-py/src/graph/node.py +112 -0
  157. package/flowquery-py/src/graph/node_data.py +26 -0
  158. package/flowquery-py/src/graph/node_reference.py +49 -0
  159. package/flowquery-py/src/graph/pattern.py +125 -0
  160. package/flowquery-py/src/graph/pattern_expression.py +62 -0
  161. package/flowquery-py/src/graph/patterns.py +42 -0
  162. package/flowquery-py/src/graph/physical_node.py +40 -0
  163. package/flowquery-py/src/graph/physical_relationship.py +36 -0
  164. package/flowquery-py/src/graph/relationship.py +135 -0
  165. package/flowquery-py/src/graph/relationship_data.py +33 -0
  166. package/flowquery-py/src/graph/relationship_match_collector.py +77 -0
  167. package/flowquery-py/src/graph/relationship_reference.py +21 -0
  168. package/flowquery-py/src/io/__init__.py +5 -0
  169. package/flowquery-py/src/io/command_line.py +67 -0
  170. package/flowquery-py/src/parsing/__init__.py +17 -0
  171. package/flowquery-py/src/parsing/alias.py +20 -0
  172. package/flowquery-py/src/parsing/alias_option.py +11 -0
  173. package/flowquery-py/src/parsing/ast_node.py +146 -0
  174. package/flowquery-py/src/parsing/base_parser.py +84 -0
  175. package/flowquery-py/src/parsing/components/__init__.py +19 -0
  176. package/flowquery-py/src/parsing/components/csv.py +8 -0
  177. package/flowquery-py/src/parsing/components/from_.py +10 -0
  178. package/flowquery-py/src/parsing/components/headers.py +12 -0
  179. package/flowquery-py/src/parsing/components/json.py +8 -0
  180. package/flowquery-py/src/parsing/components/null.py +10 -0
  181. package/flowquery-py/src/parsing/components/post.py +8 -0
  182. package/flowquery-py/src/parsing/components/text.py +8 -0
  183. package/flowquery-py/src/parsing/context.py +50 -0
  184. package/flowquery-py/src/parsing/data_structures/__init__.py +15 -0
  185. package/flowquery-py/src/parsing/data_structures/associative_array.py +41 -0
  186. package/flowquery-py/src/parsing/data_structures/json_array.py +30 -0
  187. package/flowquery-py/src/parsing/data_structures/key_value_pair.py +38 -0
  188. package/flowquery-py/src/parsing/data_structures/lookup.py +49 -0
  189. package/flowquery-py/src/parsing/data_structures/range_lookup.py +42 -0
  190. package/flowquery-py/src/parsing/expressions/__init__.py +57 -0
  191. package/flowquery-py/src/parsing/expressions/boolean.py +20 -0
  192. package/flowquery-py/src/parsing/expressions/expression.py +138 -0
  193. package/flowquery-py/src/parsing/expressions/expression_map.py +26 -0
  194. package/flowquery-py/src/parsing/expressions/f_string.py +27 -0
  195. package/flowquery-py/src/parsing/expressions/identifier.py +20 -0
  196. package/flowquery-py/src/parsing/expressions/number.py +32 -0
  197. package/flowquery-py/src/parsing/expressions/operator.py +169 -0
  198. package/flowquery-py/src/parsing/expressions/reference.py +47 -0
  199. package/flowquery-py/src/parsing/expressions/string.py +27 -0
  200. package/flowquery-py/src/parsing/functions/__init__.py +75 -0
  201. package/flowquery-py/src/parsing/functions/aggregate_function.py +60 -0
  202. package/flowquery-py/src/parsing/functions/async_function.py +62 -0
  203. package/flowquery-py/src/parsing/functions/avg.py +55 -0
  204. package/flowquery-py/src/parsing/functions/collect.py +75 -0
  205. package/flowquery-py/src/parsing/functions/function.py +68 -0
  206. package/flowquery-py/src/parsing/functions/function_factory.py +173 -0
  207. package/flowquery-py/src/parsing/functions/function_metadata.py +149 -0
  208. package/flowquery-py/src/parsing/functions/functions.py +59 -0
  209. package/flowquery-py/src/parsing/functions/join.py +47 -0
  210. package/flowquery-py/src/parsing/functions/keys.py +34 -0
  211. package/flowquery-py/src/parsing/functions/predicate_function.py +46 -0
  212. package/flowquery-py/src/parsing/functions/predicate_sum.py +47 -0
  213. package/flowquery-py/src/parsing/functions/rand.py +28 -0
  214. package/flowquery-py/src/parsing/functions/range_.py +34 -0
  215. package/flowquery-py/src/parsing/functions/reducer_element.py +15 -0
  216. package/flowquery-py/src/parsing/functions/replace.py +37 -0
  217. package/flowquery-py/src/parsing/functions/round_.py +32 -0
  218. package/flowquery-py/src/parsing/functions/size.py +32 -0
  219. package/flowquery-py/src/parsing/functions/split.py +47 -0
  220. package/flowquery-py/src/parsing/functions/stringify.py +47 -0
  221. package/flowquery-py/src/parsing/functions/sum.py +51 -0
  222. package/flowquery-py/src/parsing/functions/to_json.py +33 -0
  223. package/flowquery-py/src/parsing/functions/type_.py +47 -0
  224. package/flowquery-py/src/parsing/functions/value_holder.py +24 -0
  225. package/flowquery-py/src/parsing/logic/__init__.py +15 -0
  226. package/flowquery-py/src/parsing/logic/case.py +29 -0
  227. package/flowquery-py/src/parsing/logic/else_.py +12 -0
  228. package/flowquery-py/src/parsing/logic/end.py +8 -0
  229. package/flowquery-py/src/parsing/logic/then.py +12 -0
  230. package/flowquery-py/src/parsing/logic/when.py +10 -0
  231. package/flowquery-py/src/parsing/operations/__init__.py +35 -0
  232. package/flowquery-py/src/parsing/operations/aggregated_return.py +24 -0
  233. package/flowquery-py/src/parsing/operations/aggregated_with.py +22 -0
  234. package/flowquery-py/src/parsing/operations/call.py +74 -0
  235. package/flowquery-py/src/parsing/operations/create_node.py +34 -0
  236. package/flowquery-py/src/parsing/operations/create_relationship.py +34 -0
  237. package/flowquery-py/src/parsing/operations/group_by.py +130 -0
  238. package/flowquery-py/src/parsing/operations/limit.py +22 -0
  239. package/flowquery-py/src/parsing/operations/load.py +140 -0
  240. package/flowquery-py/src/parsing/operations/match.py +29 -0
  241. package/flowquery-py/src/parsing/operations/operation.py +69 -0
  242. package/flowquery-py/src/parsing/operations/projection.py +21 -0
  243. package/flowquery-py/src/parsing/operations/return_op.py +50 -0
  244. package/flowquery-py/src/parsing/operations/unwind.py +37 -0
  245. package/flowquery-py/src/parsing/operations/where.py +41 -0
  246. package/flowquery-py/src/parsing/operations/with_op.py +18 -0
  247. package/flowquery-py/src/parsing/parser.py +1011 -0
  248. package/flowquery-py/src/parsing/token_to_node.py +109 -0
  249. package/flowquery-py/src/tokenization/__init__.py +23 -0
  250. package/flowquery-py/src/tokenization/keyword.py +48 -0
  251. package/flowquery-py/src/tokenization/operator.py +29 -0
  252. package/flowquery-py/src/tokenization/string_walker.py +158 -0
  253. package/flowquery-py/src/tokenization/symbol.py +19 -0
  254. package/flowquery-py/src/tokenization/token.py +659 -0
  255. package/flowquery-py/src/tokenization/token_mapper.py +52 -0
  256. package/flowquery-py/src/tokenization/token_type.py +21 -0
  257. package/flowquery-py/src/tokenization/tokenizer.py +214 -0
  258. package/flowquery-py/src/tokenization/trie.py +124 -0
  259. package/flowquery-py/src/utils/__init__.py +6 -0
  260. package/flowquery-py/src/utils/object_utils.py +20 -0
  261. package/flowquery-py/src/utils/string_utils.py +113 -0
  262. package/flowquery-py/tests/__init__.py +1 -0
  263. package/flowquery-py/tests/compute/__init__.py +1 -0
  264. package/flowquery-py/tests/compute/test_runner.py +1335 -0
  265. package/flowquery-py/tests/graph/__init__.py +1 -0
  266. package/flowquery-py/tests/graph/test_create.py +56 -0
  267. package/flowquery-py/tests/graph/test_data.py +73 -0
  268. package/flowquery-py/tests/graph/test_match.py +40 -0
  269. package/flowquery-py/tests/parsing/__init__.py +1 -0
  270. package/flowquery-py/tests/parsing/test_context.py +34 -0
  271. package/flowquery-py/tests/parsing/test_expression.py +49 -0
  272. package/flowquery-py/tests/parsing/test_parser.py +674 -0
  273. package/flowquery-py/tests/test_extensibility.py +611 -0
  274. package/flowquery-py/tests/tokenization/__init__.py +1 -0
  275. package/flowquery-py/tests/tokenization/test_token_mapper.py +60 -0
  276. package/flowquery-py/tests/tokenization/test_tokenizer.py +164 -0
  277. package/flowquery-py/tests/tokenization/test_trie.py +30 -0
  278. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  279. package/misc/apps/RAG/package.json +1 -1
  280. package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +76 -8
  281. package/misc/apps/RAG/src/components/index.ts +19 -10
  282. package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +21 -26
  283. package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +24 -25
  284. package/misc/apps/RAG/src/plugins/loaders/Form.ts +163 -147
  285. package/misc/apps/RAG/src/plugins/loaders/Llm.ts +103 -90
  286. package/misc/apps/RAG/src/plugins/loaders/MockData.ts +80 -130
  287. package/misc/apps/RAG/src/plugins/loaders/Table.ts +104 -101
  288. package/misc/apps/RAG/src/plugins/loaders/Weather.ts +47 -36
  289. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +89 -78
  290. package/package.json +1 -1
  291. package/src/compute/runner.ts +24 -19
  292. package/src/graph/data.ts +112 -0
  293. package/src/graph/database.ts +63 -0
  294. package/src/graph/hops.ts +22 -0
  295. package/src/graph/node.ts +99 -0
  296. package/src/graph/node_data.ts +18 -0
  297. package/src/graph/node_reference.ts +33 -0
  298. package/src/graph/pattern.ts +101 -0
  299. package/src/graph/pattern_expression.ts +37 -0
  300. package/src/graph/patterns.ts +36 -0
  301. package/src/graph/physical_node.ts +23 -0
  302. package/src/graph/physical_relationship.ts +23 -0
  303. package/src/graph/relationship.ts +116 -0
  304. package/src/graph/relationship_data.ts +27 -0
  305. package/src/graph/relationship_match_collector.ts +58 -0
  306. package/src/graph/relationship_reference.ts +24 -0
  307. package/src/parsing/base_parser.ts +20 -14
  308. package/src/parsing/context.ts +14 -14
  309. package/src/parsing/expressions/boolean.ts +21 -0
  310. package/src/parsing/expressions/expression.ts +34 -26
  311. package/src/parsing/expressions/expression_map.ts +3 -0
  312. package/src/parsing/expressions/operator.ts +19 -1
  313. package/src/parsing/functions/function_factory.ts +45 -45
  314. package/src/parsing/operations/call.ts +3 -1
  315. package/src/parsing/operations/create_node.ts +39 -0
  316. package/src/parsing/operations/create_relationship.ts +38 -0
  317. package/src/parsing/operations/match.ts +31 -0
  318. package/src/parsing/operations/operation.ts +3 -0
  319. package/src/parsing/operations/return.ts +11 -7
  320. package/src/parsing/operations/where.ts +10 -6
  321. package/src/parsing/parser.ts +346 -8
  322. package/src/parsing/token_to_node.ts +6 -0
  323. package/src/tokenization/keyword.ts +41 -40
  324. package/src/tokenization/token.ts +21 -1
  325. package/src/tokenization/token_type.ts +2 -1
  326. package/src/tokenization/tokenizer.ts +52 -31
  327. package/tests/compute/runner.test.ts +660 -6
  328. package/tests/extensibility.test.ts +97 -93
  329. package/tests/graph/create.test.ts +36 -0
  330. package/tests/graph/data.test.ts +58 -0
  331. package/tests/graph/match.test.ts +29 -0
  332. package/tests/parsing/parser.test.ts +276 -8
  333. package/tests/tokenization/tokenizer.test.ts +107 -17
@@ -0,0 +1,118 @@
1
+ """Data class for graph record iteration and indexing."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+
5
+
6
+ class IndexEntry:
7
+ """Index entry for tracking positions of records with a specific key value."""
8
+
9
+ def __init__(self, positions: Optional[List[int]] = None):
10
+ self._positions: List[int] = positions if positions is not None else []
11
+ self._index: int = -1
12
+
13
+ def add(self, position: int) -> None:
14
+ """Add a position to the index entry."""
15
+ self._positions.append(position)
16
+
17
+ @property
18
+ def position(self) -> int:
19
+ """Get the current position."""
20
+ return self._positions[self._index]
21
+
22
+ def reset(self) -> None:
23
+ """Reset the index to the beginning."""
24
+ self._index = -1
25
+
26
+ def next(self) -> bool:
27
+ """Move to the next position. Returns True if successful."""
28
+ if self._index < len(self._positions) - 1:
29
+ self._index += 1
30
+ return True
31
+ return False
32
+
33
+ def clone(self) -> "IndexEntry":
34
+ """Create a copy of this index entry."""
35
+ return IndexEntry(list(self._positions))
36
+
37
+
38
+ class Layer:
39
+ """Layer for managing index state at a specific level."""
40
+
41
+ def __init__(self, index: Dict[str, IndexEntry]):
42
+ self._index: Dict[str, IndexEntry] = index
43
+ self._current: int = -1
44
+
45
+ @property
46
+ def index(self) -> Dict[str, IndexEntry]:
47
+ """Get the index dictionary."""
48
+ return self._index
49
+
50
+ @property
51
+ def current(self) -> int:
52
+ """Get the current position."""
53
+ return self._current
54
+
55
+ @current.setter
56
+ def current(self, value: int) -> None:
57
+ """Set the current position."""
58
+ self._current = value
59
+
60
+
61
+ class Data:
62
+ """Base class for graph data with record iteration and indexing."""
63
+
64
+ def __init__(self, records: Optional[List[Dict[str, Any]]] = None):
65
+ self._records: List[Dict[str, Any]] = records if records is not None else []
66
+ self._layers: Dict[int, Layer] = {0: Layer({})}
67
+
68
+ def _build_index(self, key: str, level: int = 0) -> None:
69
+ """Build an index for the given key at the specified level."""
70
+ self.layer(level).index.clear()
71
+ for idx, record in enumerate(self._records):
72
+ if key in record:
73
+ if record[key] not in self.layer(level).index:
74
+ self.layer(level).index[record[key]] = IndexEntry()
75
+ self.layer(level).index[record[key]].add(idx)
76
+
77
+ def layer(self, level: int = 0) -> Layer:
78
+ """Get or create a layer at the specified level."""
79
+ if level not in self._layers:
80
+ first = self._layers[0]
81
+ cloned = {}
82
+ for key, entry in first.index.items():
83
+ cloned[key] = entry.clone()
84
+ self._layers[level] = Layer(cloned)
85
+ return self._layers[level]
86
+
87
+ def _find(self, key: str, level: int = 0) -> bool:
88
+ """Find the next record with the given key value."""
89
+ if key not in self.layer(level).index:
90
+ self.layer(level).current = len(self._records) # Move to end
91
+ return False
92
+ else:
93
+ entry = self.layer(level).index[key]
94
+ more = entry.next()
95
+ if not more:
96
+ self.layer(level).current = len(self._records) # Move to end
97
+ return False
98
+ self.layer(level).current = entry.position
99
+ return True
100
+
101
+ def reset(self) -> None:
102
+ """Reset iteration to the beginning."""
103
+ self.layer(0).current = -1
104
+ for entry in self.layer(0).index.values():
105
+ entry.reset()
106
+
107
+ def next(self, level: int = 0) -> bool:
108
+ """Move to the next record. Returns True if successful."""
109
+ if self.layer(level).current < len(self._records) - 1:
110
+ self.layer(level).current += 1
111
+ return True
112
+ return False
113
+
114
+ def current(self, level: int = 0) -> Optional[Dict[str, Any]]:
115
+ """Get the current record."""
116
+ if self.layer(level).current < len(self._records):
117
+ return self._records[self.layer(level).current]
118
+ return None
@@ -0,0 +1,82 @@
1
+ """Graph database for FlowQuery."""
2
+
3
+ from typing import Any, Dict, Optional, Union, TYPE_CHECKING
4
+
5
+ from ..parsing.ast_node import ASTNode
6
+
7
+ if TYPE_CHECKING:
8
+ from .node import Node
9
+ from .relationship import Relationship
10
+ from .node_data import NodeData
11
+ from .relationship_data import RelationshipData
12
+
13
+
14
+ class Database:
15
+ """Singleton database for storing graph data."""
16
+
17
+ _instance: Optional['Database'] = None
18
+ _nodes: Dict[str, 'PhysicalNode'] = {}
19
+ _relationships: Dict[str, 'PhysicalRelationship'] = {}
20
+
21
+ def __init__(self):
22
+ pass
23
+
24
+ @classmethod
25
+ def get_instance(cls) -> 'Database':
26
+ if cls._instance is None:
27
+ cls._instance = Database()
28
+ return cls._instance
29
+
30
+ def add_node(self, node: 'Node', statement: ASTNode) -> None:
31
+ """Adds a node to the database."""
32
+ from .physical_node import PhysicalNode
33
+ if node.label is None:
34
+ raise ValueError("Node label is null")
35
+ physical = PhysicalNode(None, node.label)
36
+ physical.statement = statement
37
+ Database._nodes[node.label] = physical
38
+
39
+ def get_node(self, node: 'Node') -> Optional['PhysicalNode']:
40
+ """Gets a node from the database."""
41
+ return Database._nodes.get(node.label) if node.label else None
42
+
43
+ def add_relationship(self, relationship: 'Relationship', statement: ASTNode) -> None:
44
+ """Adds a relationship to the database."""
45
+ from .physical_relationship import PhysicalRelationship
46
+ if relationship.type is None:
47
+ raise ValueError("Relationship type is null")
48
+ physical = PhysicalRelationship()
49
+ physical.type = relationship.type
50
+ physical.statement = statement
51
+ Database._relationships[relationship.type] = physical
52
+
53
+ def get_relationship(self, relationship: 'Relationship') -> Optional['PhysicalRelationship']:
54
+ """Gets a relationship from the database."""
55
+ return Database._relationships.get(relationship.type) if relationship.type else None
56
+
57
+ async def get_data(self, element: Union['Node', 'Relationship']) -> Union['NodeData', 'RelationshipData']:
58
+ """Gets data for a node or relationship."""
59
+ from .node import Node
60
+ from .relationship import Relationship
61
+ from .node_data import NodeData
62
+ from .relationship_data import RelationshipData
63
+
64
+ if isinstance(element, Node):
65
+ node = self.get_node(element)
66
+ if node is None:
67
+ raise ValueError(f"Physical node not found for label {element.label}")
68
+ data = await node.data()
69
+ return NodeData(data)
70
+ elif isinstance(element, Relationship):
71
+ relationship = self.get_relationship(element)
72
+ if relationship is None:
73
+ raise ValueError(f"Physical relationship not found for type {element.type}")
74
+ data = await relationship.data()
75
+ return RelationshipData(data)
76
+ else:
77
+ raise ValueError("Element is neither Node nor Relationship")
78
+
79
+
80
+ # Import for type hints
81
+ from .physical_node import PhysicalNode
82
+ from .physical_relationship import PhysicalRelationship
@@ -0,0 +1,43 @@
1
+ """Hops specification for variable-length relationships."""
2
+
3
+ import sys
4
+ from typing import Optional
5
+
6
+
7
+ class Hops:
8
+ """Specifies the number of hops for a relationship pattern."""
9
+
10
+ def __init__(self, min_hops: Optional[int] = None, max_hops: Optional[int] = None):
11
+ # Default min=0, max=1 (matching TypeScript implementation)
12
+ if min_hops is None:
13
+ self._min: int = 0
14
+ else:
15
+ self._min = min_hops
16
+ if max_hops is None:
17
+ self._max: int = 1
18
+ else:
19
+ self._max = max_hops
20
+
21
+ @property
22
+ def min(self) -> int:
23
+ return self._min
24
+
25
+ @min.setter
26
+ def min(self, value: int) -> None:
27
+ self._min = value
28
+
29
+ @property
30
+ def max(self) -> int:
31
+ return self._max
32
+
33
+ @max.setter
34
+ def max(self, value: int) -> None:
35
+ self._max = value
36
+
37
+ def multi(self) -> bool:
38
+ """Returns True if this represents a variable-length relationship."""
39
+ return self._max > 1 or self._max == -1 or self._max == sys.maxsize
40
+
41
+ def unbounded(self) -> bool:
42
+ """Returns True if the max is unbounded."""
43
+ return self._max == sys.maxsize
@@ -0,0 +1,112 @@
1
+ """Graph node representation for FlowQuery."""
2
+
3
+ from typing import Any, Callable, Dict, Optional, TYPE_CHECKING
4
+
5
+ from ..parsing.ast_node import ASTNode
6
+ from ..parsing.expressions.expression import Expression
7
+
8
+ if TYPE_CHECKING:
9
+ from .relationship import Relationship
10
+ from .node_data import NodeData, NodeRecord
11
+
12
+
13
+ class Node(ASTNode):
14
+ """Represents a node in a graph pattern."""
15
+
16
+ def __init__(
17
+ self,
18
+ identifier: Optional[str] = None,
19
+ label: Optional[str] = None
20
+ ):
21
+ super().__init__()
22
+ self._identifier = identifier
23
+ self._label = label
24
+ self._properties: Dict[str, Expression] = {}
25
+ self._value: Optional['NodeRecord'] = None
26
+ self._incoming: Optional['Relationship'] = None
27
+ self._outgoing: Optional['Relationship'] = None
28
+ self._data: Optional['NodeData'] = None
29
+ self._todo_next: Optional[Callable[[], None]] = None
30
+
31
+ @property
32
+ def identifier(self) -> Optional[str]:
33
+ return self._identifier
34
+
35
+ @identifier.setter
36
+ def identifier(self, value: str) -> None:
37
+ self._identifier = value
38
+
39
+ @property
40
+ def label(self) -> Optional[str]:
41
+ return self._label
42
+
43
+ @label.setter
44
+ def label(self, value: str) -> None:
45
+ self._label = value
46
+
47
+ @property
48
+ def properties(self) -> Dict[str, Expression]:
49
+ return self._properties
50
+
51
+ def set_property(self, key: str, value: Expression) -> None:
52
+ self._properties[key] = value
53
+
54
+ def get_property(self, key: str) -> Optional[Expression]:
55
+ return self._properties.get(key)
56
+
57
+ def set_value(self, value: 'NodeRecord') -> None:
58
+ self._value = value
59
+
60
+ def value(self) -> Optional['NodeRecord']:
61
+ return self._value
62
+
63
+ @property
64
+ def outgoing(self) -> Optional['Relationship']:
65
+ return self._outgoing
66
+
67
+ @outgoing.setter
68
+ def outgoing(self, relationship: Optional['Relationship']) -> None:
69
+ self._outgoing = relationship
70
+
71
+ @property
72
+ def incoming(self) -> Optional['Relationship']:
73
+ return self._incoming
74
+
75
+ @incoming.setter
76
+ def incoming(self, relationship: Optional['Relationship']) -> None:
77
+ self._incoming = relationship
78
+
79
+ def set_data(self, data: Optional['NodeData']) -> None:
80
+ self._data = data
81
+
82
+ async def next(self) -> None:
83
+ if self._data:
84
+ self._data.reset()
85
+ while self._data.next():
86
+ self.set_value(self._data.current())
87
+ if self._outgoing:
88
+ await self._outgoing.find(self._value['id'])
89
+ await self.run_todo_next()
90
+
91
+ async def find(self, id_: str, hop: int = 0) -> None:
92
+ if self._data:
93
+ self._data.reset()
94
+ while self._data.find(id_, hop):
95
+ self.set_value(self._data.current(hop))
96
+ if self._incoming:
97
+ self._incoming.set_end_node(self)
98
+ if self._outgoing:
99
+ await self._outgoing.find(self._value['id'], hop)
100
+ await self.run_todo_next()
101
+
102
+ @property
103
+ def todo_next(self) -> Optional[Callable[[], None]]:
104
+ return self._todo_next
105
+
106
+ @todo_next.setter
107
+ def todo_next(self, func: Optional[Callable[[], None]]) -> None:
108
+ self._todo_next = func
109
+
110
+ async def run_todo_next(self) -> None:
111
+ if self._todo_next:
112
+ await self._todo_next()
@@ -0,0 +1,26 @@
1
+ """Node data class for FlowQuery."""
2
+
3
+ from typing import Any, Dict, List, Optional, TypedDict
4
+
5
+ from .data import Data
6
+
7
+
8
+ class NodeRecord(TypedDict, total=False):
9
+ """Represents a node record from the database."""
10
+ id: str
11
+
12
+
13
+ class NodeData(Data):
14
+ """Node data class extending Data with ID-based indexing."""
15
+
16
+ def __init__(self, records: Optional[List[Dict[str, Any]]] = None):
17
+ super().__init__(records)
18
+ self._build_index("id")
19
+
20
+ def find(self, id_: str, hop: int = 0) -> bool:
21
+ """Find a record by ID."""
22
+ return self._find(id_, hop)
23
+
24
+ def current(self, hop: int = 0) -> Optional[Dict[str, Any]]:
25
+ """Get the current record."""
26
+ return super().current(hop)
@@ -0,0 +1,49 @@
1
+ """Node reference for FlowQuery."""
2
+
3
+ from typing import Optional, TYPE_CHECKING
4
+
5
+ from .node import Node
6
+
7
+ if TYPE_CHECKING:
8
+ from ..parsing.ast_node import ASTNode
9
+
10
+
11
+ class NodeReference(Node):
12
+ """Represents a reference to an existing node variable."""
13
+
14
+ def __init__(self, base: Node, reference: Node):
15
+ super().__init__(base.identifier, base.label)
16
+ self._reference: Node = reference
17
+ # Copy properties from base
18
+ self._properties = base._properties
19
+ self._outgoing = base.outgoing
20
+ self._incoming = base.incoming
21
+
22
+ @property
23
+ def reference(self) -> Node:
24
+ return self._reference
25
+
26
+ # Keep referred as alias for backward compatibility
27
+ @property
28
+ def referred(self) -> Node:
29
+ return self._reference
30
+
31
+ def value(self):
32
+ return self._reference.value() if self._reference else None
33
+
34
+ async def next(self) -> None:
35
+ """Process next using the referenced node's value."""
36
+ self.set_value(self._reference.value())
37
+ if self._outgoing and self._value:
38
+ await self._outgoing.find(self._value['id'])
39
+ await self.run_todo_next()
40
+
41
+ async def find(self, id_: str, hop: int = 0) -> None:
42
+ """Find by ID, only matching if it equals the referenced node's ID."""
43
+ referenced = self._reference.value()
44
+ if referenced is None or id_ != referenced.get('id'):
45
+ return
46
+ self.set_value(referenced)
47
+ if self._outgoing and self._value:
48
+ await self._outgoing.find(self._value['id'], hop)
49
+ await self.run_todo_next()
@@ -0,0 +1,125 @@
1
+ """Graph pattern representation for FlowQuery."""
2
+
3
+ from typing import Any, Generator, List, Optional, TYPE_CHECKING, Union
4
+
5
+ from ..parsing.ast_node import ASTNode
6
+
7
+ if TYPE_CHECKING:
8
+ from .node import Node
9
+ from .relationship import Relationship
10
+
11
+
12
+ class Pattern(ASTNode):
13
+ """Represents a graph pattern for matching."""
14
+
15
+ def __init__(self):
16
+ super().__init__()
17
+ self._identifier: Optional[str] = None
18
+ self._chain: List[Union['Node', 'Relationship']] = []
19
+
20
+ @property
21
+ def identifier(self) -> Optional[str]:
22
+ return self._identifier
23
+
24
+ @identifier.setter
25
+ def identifier(self, value: str) -> None:
26
+ self._identifier = value
27
+
28
+ @property
29
+ def chain(self) -> List[Union['Node', 'Relationship']]:
30
+ return self._chain
31
+
32
+ @property
33
+ def elements(self) -> List[ASTNode]:
34
+ return self._chain
35
+
36
+ def add_element(self, element: Union['Node', 'Relationship']) -> None:
37
+ from .node import Node
38
+ from .relationship import Relationship
39
+
40
+ if (len(self._chain) > 0 and
41
+ type(self._chain[-1]) == type(element)):
42
+ raise ValueError("Cannot add two consecutive elements of the same type to the graph pattern")
43
+
44
+ if len(self._chain) > 0:
45
+ last = self._chain[-1]
46
+ if isinstance(last, Node) and isinstance(element, Relationship):
47
+ last.outgoing = element
48
+ element.source = last
49
+ if isinstance(last, Relationship) and isinstance(element, Node):
50
+ last.target = element
51
+ element.incoming = last
52
+
53
+ self._chain.append(element)
54
+ self.add_child(element)
55
+
56
+ @property
57
+ def start_node(self) -> 'Node':
58
+ from .node import Node
59
+ if len(self._chain) == 0:
60
+ raise ValueError("Pattern is empty")
61
+ first = self._chain[0]
62
+ if isinstance(first, Node):
63
+ return first
64
+ raise ValueError("Pattern does not start with a node")
65
+
66
+ @property
67
+ def end_node(self) -> 'Node':
68
+ from .node import Node
69
+ if len(self._chain) == 0:
70
+ raise ValueError("Pattern is empty")
71
+ last = self._chain[-1]
72
+ if isinstance(last, Node):
73
+ return last
74
+ raise ValueError("Pattern does not end with a node")
75
+
76
+ def first_node(self) -> Optional['Node']:
77
+ if len(self._chain) > 0:
78
+ return self._chain[0]
79
+ return None
80
+
81
+ def value(self) -> List[Any]:
82
+ return list(self.values())
83
+
84
+ def values(self) -> Generator[Any, None, None]:
85
+ from .node import Node
86
+ from .relationship import Relationship
87
+
88
+ for element in self._chain:
89
+ if isinstance(element, Node):
90
+ yield element.value()
91
+ elif isinstance(element, Relationship):
92
+ i = 0
93
+ for match in element.matches:
94
+ yield match
95
+ if i < len(element.matches) - 1:
96
+ yield match["endNode"]
97
+ i += 1
98
+
99
+ async def fetch_data(self) -> None:
100
+ """Loads data from the database for all elements."""
101
+ from .database import Database
102
+ from .node import Node
103
+ from .relationship import Relationship
104
+ from .node_reference import NodeReference
105
+ from .relationship_reference import RelationshipReference
106
+ from .node_data import NodeData
107
+ from .relationship_data import RelationshipData
108
+
109
+ db = Database.get_instance()
110
+ for element in self._chain:
111
+ if isinstance(element, (NodeReference, RelationshipReference)):
112
+ continue
113
+ data = await db.get_data(element)
114
+ if isinstance(element, Node):
115
+ element.set_data(data)
116
+ elif isinstance(element, Relationship):
117
+ element.set_data(data)
118
+
119
+ async def initialize(self) -> None:
120
+ await self.fetch_data()
121
+
122
+ async def traverse(self) -> None:
123
+ first = self.first_node()
124
+ if first:
125
+ await first.next()
@@ -0,0 +1,62 @@
1
+ """Pattern expression for FlowQuery."""
2
+
3
+ from typing import Any
4
+
5
+ from ..parsing.ast_node import ASTNode
6
+ from .node_reference import NodeReference
7
+ from .pattern import Pattern
8
+
9
+
10
+ class PatternExpression(Pattern):
11
+ """Represents a pattern expression that can be evaluated.
12
+
13
+ PatternExpression is used in WHERE clauses to test whether a graph pattern
14
+ exists. It evaluates to True if the pattern is matched, False otherwise.
15
+ """
16
+
17
+ def __init__(self):
18
+ super().__init__()
19
+ self._fetched: bool = False
20
+ self._evaluation: bool = False
21
+
22
+ def add_element(self, element) -> None:
23
+ """Add an element to the pattern, ensuring it starts with a NodeReference."""
24
+ if len(self._chain) == 0 and not isinstance(element, NodeReference):
25
+ raise ValueError("PatternExpression must start with a NodeReference")
26
+ super().add_element(element)
27
+
28
+ @property
29
+ def identifier(self):
30
+ return None
31
+
32
+ @identifier.setter
33
+ def identifier(self, value):
34
+ raise ValueError("Cannot set identifier on PatternExpression")
35
+
36
+ async def fetch_data(self) -> None:
37
+ """Fetches data for the pattern expression with caching."""
38
+ if self._fetched:
39
+ return
40
+ await super().fetch_data()
41
+ self._fetched = True
42
+
43
+ async def evaluate(self) -> None:
44
+ """Evaluates the pattern expression by traversing the graph.
45
+
46
+ Sets _evaluation to True if the pattern is matched, False otherwise.
47
+ """
48
+ self._evaluation = False
49
+
50
+ async def set_evaluation_true():
51
+ self._evaluation = True
52
+
53
+ self.end_node.todo_next = set_evaluation_true
54
+ await self.start_node.next()
55
+
56
+ def value(self) -> bool:
57
+ """Returns the result of the pattern evaluation."""
58
+ return self._evaluation
59
+
60
+ def is_operand(self) -> bool:
61
+ """PatternExpression is an operand in expressions."""
62
+ return True
@@ -0,0 +1,42 @@
1
+ """Collection of graph patterns for FlowQuery."""
2
+
3
+ from typing import Awaitable, Callable, List, Optional
4
+
5
+ from .pattern import Pattern
6
+
7
+
8
+ class Patterns:
9
+ """Manages a collection of graph patterns."""
10
+
11
+ def __init__(self, patterns: Optional[List[Pattern]] = None):
12
+ self._patterns = patterns or []
13
+ self._to_do_next: Optional[Callable[[], Awaitable[None]]] = None
14
+
15
+ @property
16
+ def patterns(self) -> List[Pattern]:
17
+ return self._patterns
18
+
19
+ @property
20
+ def to_do_next(self) -> Optional[Callable[[], Awaitable[None]]]:
21
+ return self._to_do_next
22
+
23
+ @to_do_next.setter
24
+ def to_do_next(self, func: Optional[Callable[[], Awaitable[None]]]) -> None:
25
+ self._to_do_next = func
26
+ if self._patterns:
27
+ self._patterns[-1].end_node.todo_next = func
28
+
29
+ async def initialize(self) -> None:
30
+ previous: Optional[Pattern] = None
31
+ for pattern in self._patterns:
32
+ await pattern.fetch_data() # Ensure data is loaded
33
+ if previous is not None:
34
+ # Chain the patterns together
35
+ async def next_pattern_start(p=pattern):
36
+ await p.start_node.next()
37
+ previous.end_node.todo_next = next_pattern_start
38
+ previous = pattern
39
+
40
+ async def traverse(self) -> None:
41
+ if self._patterns:
42
+ await self._patterns[0].start_node.next()