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.
- 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/expression_map.d.ts +1 -0
- package/dist/parsing/expressions/expression_map.d.ts.map +1 -1
- package/dist/parsing/expressions/expression_map.js +3 -0
- package/dist/parsing/expressions/expression_map.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/call.d.ts.map +1 -1
- package/dist/parsing/operations/call.js +3 -1
- package/dist/parsing/operations/call.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/CatFacts.ts +21 -26
- package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +24 -25
- package/misc/apps/RAG/src/plugins/loaders/Form.ts +163 -147
- package/misc/apps/RAG/src/plugins/loaders/Llm.ts +103 -90
- package/misc/apps/RAG/src/plugins/loaders/MockData.ts +80 -130
- package/misc/apps/RAG/src/plugins/loaders/Table.ts +104 -101
- package/misc/apps/RAG/src/plugins/loaders/Weather.ts +47 -36
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +89 -78
- 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/expression_map.ts +3 -0
- package/src/parsing/expressions/operator.ts +19 -1
- package/src/parsing/functions/function_factory.ts +45 -45
- package/src/parsing/operations/call.ts +3 -1
- 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 +660 -6
- 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 +276 -8
- package/tests/tokenization/tokenizer.test.ts +107 -17
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Physical node representation for FlowQuery."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ..parsing.ast_node import ASTNode
|
|
7
|
+
|
|
8
|
+
from .node import Node
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PhysicalNode(Node):
|
|
12
|
+
"""Represents a physical node in the graph database."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, id_: Optional[str], label: str, properties: Optional[Dict[str, Any]] = None):
|
|
15
|
+
super().__init__(id_, label)
|
|
16
|
+
# Store additional physical properties in a separate dict
|
|
17
|
+
# (Node.properties is for Expression-based pattern properties)
|
|
18
|
+
self._physical_properties = properties or {}
|
|
19
|
+
self._statement: Optional["ASTNode"] = None
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def physical_properties(self) -> Dict[str, Any]:
|
|
23
|
+
"""Get the physical properties (values, not expressions)."""
|
|
24
|
+
return self._physical_properties
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def statement(self) -> Optional["ASTNode"]:
|
|
28
|
+
return self._statement
|
|
29
|
+
|
|
30
|
+
@statement.setter
|
|
31
|
+
def statement(self, value: Optional["ASTNode"]) -> None:
|
|
32
|
+
self._statement = value
|
|
33
|
+
|
|
34
|
+
async def data(self) -> List[Dict[str, Any]]:
|
|
35
|
+
if self._statement is None:
|
|
36
|
+
raise ValueError("Statement is null")
|
|
37
|
+
from ..compute.runner import Runner
|
|
38
|
+
runner = Runner(ast=self._statement)
|
|
39
|
+
await runner.run()
|
|
40
|
+
return runner.results
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Physical relationship representation for FlowQuery."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .relationship import Relationship
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..parsing.ast_node import ASTNode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PhysicalRelationship(Relationship):
|
|
13
|
+
"""Represents a physical relationship in the graph database."""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self._statement: Optional[ASTNode] = None
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def statement(self) -> Optional[ASTNode]:
|
|
21
|
+
"""Get the statement for this relationship."""
|
|
22
|
+
return self._statement
|
|
23
|
+
|
|
24
|
+
@statement.setter
|
|
25
|
+
def statement(self, value: Optional[ASTNode]) -> None:
|
|
26
|
+
"""Set the statement for this relationship."""
|
|
27
|
+
self._statement = value
|
|
28
|
+
|
|
29
|
+
async def data(self) -> List[Dict[str, Any]]:
|
|
30
|
+
"""Execute the statement and return results."""
|
|
31
|
+
if self._statement is None:
|
|
32
|
+
raise ValueError("Statement is null")
|
|
33
|
+
from ..compute.runner import Runner
|
|
34
|
+
runner = Runner(None, self._statement)
|
|
35
|
+
await runner.run()
|
|
36
|
+
return runner.results
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Graph relationship representation for FlowQuery."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union
|
|
4
|
+
|
|
5
|
+
from ..parsing.ast_node import ASTNode
|
|
6
|
+
from .hops import Hops
|
|
7
|
+
from .relationship_match_collector import RelationshipMatchCollector, RelationshipMatchRecord
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .node import Node
|
|
11
|
+
from .relationship_data import RelationshipData, RelationshipRecord
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Relationship(ASTNode):
|
|
15
|
+
"""Represents a relationship in a graph pattern."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self._identifier: Optional[str] = None
|
|
20
|
+
self._type: Optional[str] = None
|
|
21
|
+
self._hops: Hops = Hops()
|
|
22
|
+
self._source: Optional['Node'] = None
|
|
23
|
+
self._target: Optional['Node'] = None
|
|
24
|
+
self._data: Optional['RelationshipData'] = None
|
|
25
|
+
self._value: Optional[Union[RelationshipMatchRecord, List[RelationshipMatchRecord]]] = None
|
|
26
|
+
self._matches: RelationshipMatchCollector = RelationshipMatchCollector()
|
|
27
|
+
self._properties: Dict[str, Any] = {}
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def identifier(self) -> Optional[str]:
|
|
31
|
+
return self._identifier
|
|
32
|
+
|
|
33
|
+
@identifier.setter
|
|
34
|
+
def identifier(self, value: str) -> None:
|
|
35
|
+
self._identifier = value
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def type(self) -> Optional[str]:
|
|
39
|
+
return self._type
|
|
40
|
+
|
|
41
|
+
@type.setter
|
|
42
|
+
def type(self, value: str) -> None:
|
|
43
|
+
self._type = value
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def hops(self) -> Hops:
|
|
47
|
+
return self._hops
|
|
48
|
+
|
|
49
|
+
@hops.setter
|
|
50
|
+
def hops(self, value: Hops) -> None:
|
|
51
|
+
self._hops = value
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def properties(self) -> Dict[str, Any]:
|
|
55
|
+
"""Get properties from relationship data."""
|
|
56
|
+
if self._data:
|
|
57
|
+
return self._data.properties() or {}
|
|
58
|
+
return {}
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def source(self) -> Optional['Node']:
|
|
62
|
+
return self._source
|
|
63
|
+
|
|
64
|
+
@source.setter
|
|
65
|
+
def source(self, value: 'Node') -> None:
|
|
66
|
+
self._source = value
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def target(self) -> Optional['Node']:
|
|
70
|
+
return self._target
|
|
71
|
+
|
|
72
|
+
@target.setter
|
|
73
|
+
def target(self, value: 'Node') -> None:
|
|
74
|
+
self._target = value
|
|
75
|
+
|
|
76
|
+
# Keep start/end aliases for backward compatibility
|
|
77
|
+
@property
|
|
78
|
+
def start(self) -> Optional['Node']:
|
|
79
|
+
return self._source
|
|
80
|
+
|
|
81
|
+
@start.setter
|
|
82
|
+
def start(self, value: 'Node') -> None:
|
|
83
|
+
self._source = value
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def end(self) -> Optional['Node']:
|
|
87
|
+
return self._target
|
|
88
|
+
|
|
89
|
+
@end.setter
|
|
90
|
+
def end(self, value: 'Node') -> None:
|
|
91
|
+
self._target = value
|
|
92
|
+
|
|
93
|
+
def set_data(self, data: Optional['RelationshipData']) -> None:
|
|
94
|
+
self._data = data
|
|
95
|
+
|
|
96
|
+
def set_value(self, relationship: 'Relationship') -> None:
|
|
97
|
+
"""Set value by pushing match to collector."""
|
|
98
|
+
self._matches.push(relationship)
|
|
99
|
+
self._value = self._matches.value()
|
|
100
|
+
|
|
101
|
+
def value(self) -> Optional[Union[RelationshipMatchRecord, List[RelationshipMatchRecord]]]:
|
|
102
|
+
return self._value
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def matches(self) -> List[RelationshipMatchRecord]:
|
|
106
|
+
return self._matches.matches
|
|
107
|
+
|
|
108
|
+
def set_end_node(self, node: 'Node') -> None:
|
|
109
|
+
"""Set the end node for the current match."""
|
|
110
|
+
self._matches.end_node = node
|
|
111
|
+
|
|
112
|
+
async def find(self, left_id: str, hop: int = 0) -> None:
|
|
113
|
+
"""Find relationships starting from the given node ID."""
|
|
114
|
+
# Save original source node
|
|
115
|
+
original = self._source
|
|
116
|
+
if hop > 0:
|
|
117
|
+
# For hops greater than 0, the source becomes the target of the previous hop
|
|
118
|
+
self._source = self._target
|
|
119
|
+
if hop == 0:
|
|
120
|
+
self._data.reset() if self._data else None
|
|
121
|
+
|
|
122
|
+
while self._data and self._data.find(left_id, hop):
|
|
123
|
+
data = self._data.current(hop)
|
|
124
|
+
if data and self._hops and hop >= self._hops.min:
|
|
125
|
+
self.set_value(self)
|
|
126
|
+
if self._target and 'right_id' in data:
|
|
127
|
+
await self._target.find(data['right_id'], hop)
|
|
128
|
+
if self._matches.is_circular():
|
|
129
|
+
raise ValueError("Circular relationship detected")
|
|
130
|
+
if self._hops and hop + 1 < self._hops.max:
|
|
131
|
+
await self.find(data['right_id'], hop + 1)
|
|
132
|
+
self._matches.pop()
|
|
133
|
+
|
|
134
|
+
# Restore original source node
|
|
135
|
+
self._source = original
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Relationship data class for FlowQuery."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, TypedDict
|
|
4
|
+
|
|
5
|
+
from .data import Data
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RelationshipRecord(TypedDict, total=False):
|
|
9
|
+
"""Represents a relationship record from the database."""
|
|
10
|
+
left_id: str
|
|
11
|
+
right_id: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RelationshipData(Data):
|
|
15
|
+
"""Relationship data class extending Data with left_id-based indexing."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, records: Optional[List[Dict[str, Any]]] = None):
|
|
18
|
+
super().__init__(records)
|
|
19
|
+
self._build_index("left_id")
|
|
20
|
+
|
|
21
|
+
def find(self, left_id: str, hop: int = 0) -> bool:
|
|
22
|
+
"""Find a relationship by start node ID."""
|
|
23
|
+
return self._find(left_id, hop)
|
|
24
|
+
|
|
25
|
+
def properties(self) -> Optional[Dict[str, Any]]:
|
|
26
|
+
"""Get properties of current relationship, excluding left_id and right_id."""
|
|
27
|
+
current = self.current()
|
|
28
|
+
if current:
|
|
29
|
+
props = dict(current)
|
|
30
|
+
props.pop("left_id", None)
|
|
31
|
+
props.pop("right_id", None)
|
|
32
|
+
return props
|
|
33
|
+
return None
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Collector for relationship match records."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING, TypedDict, Union
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .relationship import Relationship
|
|
7
|
+
from .node import Node
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RelationshipMatchRecord(TypedDict, total=False):
|
|
11
|
+
"""Represents a matched relationship record."""
|
|
12
|
+
type: str
|
|
13
|
+
startNode: Dict[str, Any]
|
|
14
|
+
endNode: Optional[Dict[str, Any]]
|
|
15
|
+
properties: Dict[str, Any]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RelationshipMatchCollector:
|
|
19
|
+
"""Collects relationship matches during graph traversal."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self._matches: List[RelationshipMatchRecord] = []
|
|
23
|
+
self._node_ids: List[str] = []
|
|
24
|
+
|
|
25
|
+
def push(self, relationship: 'Relationship') -> RelationshipMatchRecord:
|
|
26
|
+
"""Push a new match onto the collector."""
|
|
27
|
+
match: RelationshipMatchRecord = {
|
|
28
|
+
"type": relationship.type or "",
|
|
29
|
+
"startNode": relationship.source.value() if relationship.source else {},
|
|
30
|
+
"endNode": None,
|
|
31
|
+
"properties": relationship.properties,
|
|
32
|
+
}
|
|
33
|
+
self._matches.append(match)
|
|
34
|
+
start_node_value = match.get("startNode", {})
|
|
35
|
+
if isinstance(start_node_value, dict):
|
|
36
|
+
self._node_ids.append(start_node_value.get("id", ""))
|
|
37
|
+
return match
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def end_node(self) -> Optional[Dict[str, Any]]:
|
|
41
|
+
"""Get the end node of the last match."""
|
|
42
|
+
if self._matches:
|
|
43
|
+
return self._matches[-1].get("endNode")
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
@end_node.setter
|
|
47
|
+
def end_node(self, node: 'Node') -> None:
|
|
48
|
+
"""Set the end node of the last match."""
|
|
49
|
+
if self._matches:
|
|
50
|
+
self._matches[-1]["endNode"] = node.value()
|
|
51
|
+
|
|
52
|
+
def pop(self) -> Optional[RelationshipMatchRecord]:
|
|
53
|
+
"""Pop the last match from the collector."""
|
|
54
|
+
if self._node_ids:
|
|
55
|
+
self._node_ids.pop()
|
|
56
|
+
if self._matches:
|
|
57
|
+
return self._matches.pop()
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def value(self) -> Optional[Union[RelationshipMatchRecord, List[RelationshipMatchRecord]]]:
|
|
61
|
+
"""Get the current value(s)."""
|
|
62
|
+
if len(self._matches) == 0:
|
|
63
|
+
return None
|
|
64
|
+
elif len(self._matches) == 1:
|
|
65
|
+
return self._matches[0]
|
|
66
|
+
else:
|
|
67
|
+
return self._matches
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def matches(self) -> List[RelationshipMatchRecord]:
|
|
71
|
+
"""Get all matches."""
|
|
72
|
+
return self._matches
|
|
73
|
+
|
|
74
|
+
def is_circular(self) -> bool:
|
|
75
|
+
"""Check if the collected relationships form a circular pattern."""
|
|
76
|
+
seen = set(self._node_ids)
|
|
77
|
+
return len(seen) < len(self._node_ids)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Relationship reference for FlowQuery."""
|
|
2
|
+
|
|
3
|
+
from .relationship import Relationship
|
|
4
|
+
from ..parsing.ast_node import ASTNode
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RelationshipReference(Relationship):
|
|
8
|
+
"""Represents a reference to an existing relationship variable."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, relationship: Relationship, referred: ASTNode):
|
|
11
|
+
super().__init__()
|
|
12
|
+
self._referred = referred
|
|
13
|
+
if relationship.type:
|
|
14
|
+
self.type = relationship.type
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def referred(self) -> ASTNode:
|
|
18
|
+
return self._referred
|
|
19
|
+
|
|
20
|
+
def value(self):
|
|
21
|
+
return self._referred.value() if self._referred else None
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Interactive command-line interface for FlowQuery."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from ..compute.runner import Runner
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CommandLine:
|
|
10
|
+
"""Interactive command-line interface for FlowQuery.
|
|
11
|
+
|
|
12
|
+
Provides a REPL (Read-Eval-Print Loop) for executing FlowQuery statements
|
|
13
|
+
and displaying results.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
cli = CommandLine()
|
|
17
|
+
cli.loop() # Starts interactive mode
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def loop(self) -> None:
|
|
21
|
+
"""Starts the interactive command loop.
|
|
22
|
+
|
|
23
|
+
Prompts the user for FlowQuery statements, executes them, and displays results.
|
|
24
|
+
Type "exit" to quit the loop. End multi-line queries with ";".
|
|
25
|
+
"""
|
|
26
|
+
print('Welcome to FlowQuery! Type "exit" to quit.')
|
|
27
|
+
print('End queries with ";" to execute. Multi-line input supported.')
|
|
28
|
+
|
|
29
|
+
while True:
|
|
30
|
+
try:
|
|
31
|
+
lines = []
|
|
32
|
+
prompt = "> "
|
|
33
|
+
while True:
|
|
34
|
+
line = input(prompt)
|
|
35
|
+
if line.strip() == "exit":
|
|
36
|
+
print("Exiting FlowQuery.")
|
|
37
|
+
return
|
|
38
|
+
lines.append(line)
|
|
39
|
+
user_input = "\n".join(lines)
|
|
40
|
+
if user_input.strip().endswith(";"):
|
|
41
|
+
break
|
|
42
|
+
prompt = "... "
|
|
43
|
+
except EOFError:
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
if user_input.strip() == "":
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
# Remove the termination semicolon before sending to the engine
|
|
50
|
+
user_input = user_input.strip().rstrip(";")
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
runner = Runner(user_input)
|
|
54
|
+
asyncio.run(self._execute(runner))
|
|
55
|
+
except Exception as e:
|
|
56
|
+
print(f"Error: {e}")
|
|
57
|
+
|
|
58
|
+
print("Exiting FlowQuery.")
|
|
59
|
+
|
|
60
|
+
async def _execute(self, runner: Runner) -> None:
|
|
61
|
+
await runner.run()
|
|
62
|
+
print(runner.results)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main() -> None:
|
|
66
|
+
"""Entry point for the flowquery CLI command."""
|
|
67
|
+
CommandLine().loop()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Parsing module for FlowQuery."""
|
|
2
|
+
|
|
3
|
+
from .ast_node import ASTNode
|
|
4
|
+
from .context import Context
|
|
5
|
+
from .alias import Alias
|
|
6
|
+
from .alias_option import AliasOption
|
|
7
|
+
from .base_parser import BaseParser
|
|
8
|
+
from .parser import Parser
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ASTNode",
|
|
12
|
+
"Context",
|
|
13
|
+
"Alias",
|
|
14
|
+
"AliasOption",
|
|
15
|
+
"BaseParser",
|
|
16
|
+
"Parser",
|
|
17
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Alias node for FlowQuery AST."""
|
|
2
|
+
|
|
3
|
+
from .ast_node import ASTNode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Alias(ASTNode):
|
|
7
|
+
"""Represents an alias in the FlowQuery AST."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, alias: str):
|
|
10
|
+
super().__init__()
|
|
11
|
+
self._alias = alias
|
|
12
|
+
|
|
13
|
+
def __str__(self) -> str:
|
|
14
|
+
return f"Alias ({self._alias})"
|
|
15
|
+
|
|
16
|
+
def get_alias(self) -> str:
|
|
17
|
+
return self._alias
|
|
18
|
+
|
|
19
|
+
def value(self) -> str:
|
|
20
|
+
return self._alias
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Represents a node in the Abstract Syntax Tree (AST)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import List, Any, Generator, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ASTNode:
|
|
8
|
+
"""Represents a node in the Abstract Syntax Tree (AST).
|
|
9
|
+
|
|
10
|
+
The AST is a tree representation of the parsed FlowQuery statement structure.
|
|
11
|
+
Each node can have children and maintains a reference to its parent.
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
root = ASTNode()
|
|
15
|
+
child = ASTNode()
|
|
16
|
+
root.add_child(child)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self._parent: Optional[ASTNode] = None
|
|
21
|
+
self.children: List[ASTNode] = []
|
|
22
|
+
|
|
23
|
+
def add_child(self, child: ASTNode) -> None:
|
|
24
|
+
"""Adds a child node to this node and sets the child's parent reference.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
child: The child node to add
|
|
28
|
+
"""
|
|
29
|
+
child._parent = self
|
|
30
|
+
self.children.append(child)
|
|
31
|
+
|
|
32
|
+
def first_child(self) -> ASTNode:
|
|
33
|
+
"""Returns the first child node.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The first child node
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError: If the node has no children
|
|
40
|
+
"""
|
|
41
|
+
if len(self.children) == 0:
|
|
42
|
+
raise ValueError('Expected child')
|
|
43
|
+
return self.children[0]
|
|
44
|
+
|
|
45
|
+
def last_child(self) -> ASTNode:
|
|
46
|
+
"""Returns the last child node.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The last child node
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If the node has no children
|
|
53
|
+
"""
|
|
54
|
+
if len(self.children) == 0:
|
|
55
|
+
raise ValueError('Expected child')
|
|
56
|
+
return self.children[-1]
|
|
57
|
+
|
|
58
|
+
def get_children(self) -> List[ASTNode]:
|
|
59
|
+
"""Returns all child nodes.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Array of child nodes
|
|
63
|
+
"""
|
|
64
|
+
return self.children
|
|
65
|
+
|
|
66
|
+
def child_count(self) -> int:
|
|
67
|
+
"""Returns the number of child nodes.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The count of children
|
|
71
|
+
"""
|
|
72
|
+
return len(self.children)
|
|
73
|
+
|
|
74
|
+
def value(self) -> Any:
|
|
75
|
+
"""Returns the value of this node. Override in subclasses to provide specific values.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The node's value, or None if not applicable
|
|
79
|
+
"""
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def is_operator(self) -> bool:
|
|
83
|
+
"""Checks if this node represents an operator.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if this is an operator node, False otherwise
|
|
87
|
+
"""
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def is_operand(self) -> bool:
|
|
91
|
+
"""Checks if this node represents an operand (the opposite of an operator).
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
True if this is an operand node, False otherwise
|
|
95
|
+
"""
|
|
96
|
+
return not self.is_operator()
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def precedence(self) -> int:
|
|
100
|
+
"""Gets the operator precedence for this node. Higher values indicate higher precedence.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
The precedence value (0 for non-operators)
|
|
104
|
+
"""
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def left_associative(self) -> bool:
|
|
109
|
+
"""Indicates whether this operator is left-associative.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
True if left-associative, False otherwise
|
|
113
|
+
"""
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def print(self) -> str:
|
|
117
|
+
"""Prints a string representation of the AST tree starting from this node.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A formatted string showing the tree structure
|
|
121
|
+
"""
|
|
122
|
+
return '\n'.join(self._print(0))
|
|
123
|
+
|
|
124
|
+
def _print(self, indent: int) -> Generator[str, None, None]:
|
|
125
|
+
"""Generator function for recursively printing the tree structure.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
indent: The current indentation level
|
|
129
|
+
|
|
130
|
+
Yields:
|
|
131
|
+
Lines representing each node in the tree
|
|
132
|
+
"""
|
|
133
|
+
if indent == 0:
|
|
134
|
+
yield self.__class__.__name__
|
|
135
|
+
elif indent > 0:
|
|
136
|
+
yield '-' * indent + f' {self}'
|
|
137
|
+
for child in self.children:
|
|
138
|
+
yield from child._print(indent + 1)
|
|
139
|
+
|
|
140
|
+
def __str__(self) -> str:
|
|
141
|
+
"""Returns a string representation of this node. Override in subclasses for custom formatting.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
The string representation
|
|
145
|
+
"""
|
|
146
|
+
return self.__class__.__name__
|