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,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()
|