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,611 @@
|
|
|
1
|
+
"""Tests for the FlowQuery extensibility API."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from typing import TypedDict, Optional, List, Any
|
|
5
|
+
from flowquery.parsing.functions.function import Function
|
|
6
|
+
from flowquery.parsing.functions.aggregate_function import AggregateFunction
|
|
7
|
+
from flowquery.parsing.functions.async_function import AsyncFunction
|
|
8
|
+
from flowquery.parsing.functions.predicate_function import PredicateFunction
|
|
9
|
+
from flowquery.parsing.functions.reducer_element import ReducerElement
|
|
10
|
+
from flowquery.parsing.functions.function_metadata import (
|
|
11
|
+
FunctionDef,
|
|
12
|
+
FunctionMetadata,
|
|
13
|
+
FunctionCategory,
|
|
14
|
+
ParameterSchema,
|
|
15
|
+
OutputSchema,
|
|
16
|
+
FunctionDefOptions,
|
|
17
|
+
get_function_metadata,
|
|
18
|
+
get_registered_function_factory,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestExtensibilityExports:
|
|
23
|
+
"""Test cases for the extensibility API."""
|
|
24
|
+
|
|
25
|
+
def test_function_class_can_be_extended(self):
|
|
26
|
+
"""Function class is exported and can be extended."""
|
|
27
|
+
class CustomFunction(Function):
|
|
28
|
+
def __init__(self):
|
|
29
|
+
super().__init__("customFunc")
|
|
30
|
+
self._expected_parameter_count = 1
|
|
31
|
+
|
|
32
|
+
def value(self) -> str:
|
|
33
|
+
return "custom value"
|
|
34
|
+
|
|
35
|
+
func = CustomFunction()
|
|
36
|
+
assert func.name == "customFunc"
|
|
37
|
+
assert str(func) == "Function (customFunc)"
|
|
38
|
+
assert func.value() == "custom value"
|
|
39
|
+
|
|
40
|
+
def test_function_validates_parameter_count(self):
|
|
41
|
+
"""Function validates parameter count when set."""
|
|
42
|
+
class TwoParamFunction(Function):
|
|
43
|
+
def __init__(self):
|
|
44
|
+
super().__init__("twoParam")
|
|
45
|
+
self._expected_parameter_count = 2
|
|
46
|
+
|
|
47
|
+
func = TwoParamFunction()
|
|
48
|
+
|
|
49
|
+
# Should throw when wrong number of parameters
|
|
50
|
+
with pytest.raises(ValueError, match="Function twoParam expected 2 parameters, but got 0"):
|
|
51
|
+
func.parameters = []
|
|
52
|
+
|
|
53
|
+
def test_function_without_expected_count_accepts_any(self):
|
|
54
|
+
"""Function without expected parameter count accepts any number."""
|
|
55
|
+
class FlexibleFunction(Function):
|
|
56
|
+
def __init__(self):
|
|
57
|
+
super().__init__("flexible")
|
|
58
|
+
# _expected_parameter_count is None by default
|
|
59
|
+
|
|
60
|
+
func = FlexibleFunction()
|
|
61
|
+
# Should not throw
|
|
62
|
+
func.parameters = []
|
|
63
|
+
assert len(func.get_children()) == 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestAggregateFunctionExtension:
|
|
67
|
+
"""Test cases for AggregateFunction extension."""
|
|
68
|
+
|
|
69
|
+
def test_aggregate_function_can_be_extended(self):
|
|
70
|
+
"""AggregateFunction class is exported and can be extended."""
|
|
71
|
+
class SumElement(ReducerElement):
|
|
72
|
+
def __init__(self):
|
|
73
|
+
self._value: float = 0
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def value(self) -> float:
|
|
77
|
+
return self._value
|
|
78
|
+
|
|
79
|
+
@value.setter
|
|
80
|
+
def value(self, v: float) -> None:
|
|
81
|
+
self._value = v
|
|
82
|
+
|
|
83
|
+
class CustomSum(AggregateFunction):
|
|
84
|
+
def __init__(self):
|
|
85
|
+
super().__init__("customSum")
|
|
86
|
+
self._total: float = 0
|
|
87
|
+
|
|
88
|
+
def reduce(self, element: ReducerElement) -> None:
|
|
89
|
+
self._total += element.value
|
|
90
|
+
|
|
91
|
+
def element(self) -> ReducerElement:
|
|
92
|
+
el = SumElement()
|
|
93
|
+
el.value = self._total
|
|
94
|
+
return el
|
|
95
|
+
|
|
96
|
+
def value(self) -> float:
|
|
97
|
+
return self._total
|
|
98
|
+
|
|
99
|
+
func = CustomSum()
|
|
100
|
+
assert func.name == "customSum"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestFunctionDefDecorator:
|
|
104
|
+
"""Test cases for the FunctionDef decorator."""
|
|
105
|
+
|
|
106
|
+
def test_function_def_decorator_registers_metadata(self):
|
|
107
|
+
"""FunctionDef decorator registers function metadata."""
|
|
108
|
+
@FunctionDef({
|
|
109
|
+
"description": "Test function for unit testing",
|
|
110
|
+
"category": "scalar",
|
|
111
|
+
"parameters": [
|
|
112
|
+
{"name": "value", "description": "Input value", "type": "any"}
|
|
113
|
+
],
|
|
114
|
+
"output": {"description": "Result", "type": "any"},
|
|
115
|
+
"examples": ["WITH test(1) AS x RETURN x"]
|
|
116
|
+
})
|
|
117
|
+
class TestFunction(Function):
|
|
118
|
+
def __init__(self):
|
|
119
|
+
super().__init__("testFunc")
|
|
120
|
+
self._expected_parameter_count = 1
|
|
121
|
+
|
|
122
|
+
def value(self):
|
|
123
|
+
return self.get_children()[0].value()
|
|
124
|
+
|
|
125
|
+
# Get the registered metadata using the function name (as registered by @FunctionDef)
|
|
126
|
+
metadata = get_function_metadata("testFunc", "scalar")
|
|
127
|
+
assert metadata is not None
|
|
128
|
+
assert metadata.description == "Test function for unit testing"
|
|
129
|
+
assert metadata.category == "scalar"
|
|
130
|
+
assert len(metadata.parameters) == 1
|
|
131
|
+
assert metadata.parameters[0]["name"] == "value"
|
|
132
|
+
|
|
133
|
+
def test_function_def_decorator_for_aggregate_function(self):
|
|
134
|
+
"""FunctionDef decorator can be applied to an aggregate function."""
|
|
135
|
+
@FunctionDef({
|
|
136
|
+
"description": "Test aggregate function",
|
|
137
|
+
"category": "aggregate",
|
|
138
|
+
"parameters": [{"name": "value", "description": "Numeric value", "type": "number"}],
|
|
139
|
+
"output": {"description": "Aggregated result", "type": "number"},
|
|
140
|
+
})
|
|
141
|
+
class TestAggExt(AggregateFunction):
|
|
142
|
+
def __init__(self):
|
|
143
|
+
super().__init__("testAggExt")
|
|
144
|
+
self._sum = 0
|
|
145
|
+
|
|
146
|
+
def value(self):
|
|
147
|
+
return self._sum
|
|
148
|
+
|
|
149
|
+
instance = TestAggExt()
|
|
150
|
+
assert instance.name == "testAggExt"
|
|
151
|
+
assert instance.value() == 0
|
|
152
|
+
|
|
153
|
+
def test_function_def_decorator_for_predicate_function(self):
|
|
154
|
+
"""FunctionDef decorator can be applied to a predicate function."""
|
|
155
|
+
@FunctionDef({
|
|
156
|
+
"description": "Test predicate function",
|
|
157
|
+
"category": "predicate",
|
|
158
|
+
"parameters": [{"name": "list", "description": "List to check", "type": "array"}],
|
|
159
|
+
"output": {"description": "Boolean result", "type": "boolean"},
|
|
160
|
+
})
|
|
161
|
+
class TestPredExt(PredicateFunction):
|
|
162
|
+
def __init__(self):
|
|
163
|
+
super().__init__("testPredExt")
|
|
164
|
+
|
|
165
|
+
def value(self):
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
instance = TestPredExt()
|
|
169
|
+
assert instance.name == "testPredExt"
|
|
170
|
+
assert instance.value() is True
|
|
171
|
+
|
|
172
|
+
@pytest.mark.asyncio
|
|
173
|
+
async def test_function_def_decorator_for_async_provider(self):
|
|
174
|
+
"""FunctionDef decorator can be applied to an async provider."""
|
|
175
|
+
from flowquery.parsing.functions.function_metadata import (
|
|
176
|
+
get_function_metadata,
|
|
177
|
+
get_registered_function_factory,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
@FunctionDef({
|
|
181
|
+
"description": "Test async provider for extensibility",
|
|
182
|
+
"category": "async",
|
|
183
|
+
"parameters": [
|
|
184
|
+
{
|
|
185
|
+
"name": "count",
|
|
186
|
+
"description": "Number of items",
|
|
187
|
+
"type": "number",
|
|
188
|
+
"required": False,
|
|
189
|
+
"default": 1,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
"output": {"description": "Data object", "type": "object"},
|
|
193
|
+
})
|
|
194
|
+
class Simple(AsyncFunction):
|
|
195
|
+
async def generate(self, count: int = 1):
|
|
196
|
+
for i in range(count):
|
|
197
|
+
yield {"id": i, "data": f"item{i}"}
|
|
198
|
+
|
|
199
|
+
# Verify the decorated class still works correctly
|
|
200
|
+
loader = Simple("simple")
|
|
201
|
+
results = []
|
|
202
|
+
async for item in loader.generate(2):
|
|
203
|
+
results.append(item)
|
|
204
|
+
assert len(results) == 2
|
|
205
|
+
assert results[0] == {"id": 0, "data": "item0"}
|
|
206
|
+
assert results[1] == {"id": 1, "data": "item1"}
|
|
207
|
+
|
|
208
|
+
# Verify the async provider was registered (using class name)
|
|
209
|
+
provider = get_registered_function_factory("simple", "async")
|
|
210
|
+
assert provider is not None
|
|
211
|
+
assert callable(provider)
|
|
212
|
+
|
|
213
|
+
# Verify the metadata was registered
|
|
214
|
+
metadata = get_function_metadata("simple", "async")
|
|
215
|
+
assert metadata is not None
|
|
216
|
+
assert metadata.name == "simple"
|
|
217
|
+
assert metadata.category == "async"
|
|
218
|
+
assert metadata.description == "Test async provider for extensibility"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class TestPredicateFunctionExtension:
|
|
222
|
+
"""Test cases for PredicateFunction extension."""
|
|
223
|
+
|
|
224
|
+
def test_predicate_function_can_be_extended(self):
|
|
225
|
+
"""PredicateFunction class is exported and can be extended."""
|
|
226
|
+
class CustomPredicate(PredicateFunction):
|
|
227
|
+
def __init__(self):
|
|
228
|
+
super().__init__("customPredicate")
|
|
229
|
+
|
|
230
|
+
def value(self):
|
|
231
|
+
return True
|
|
232
|
+
|
|
233
|
+
pred = CustomPredicate()
|
|
234
|
+
assert pred.name == "customPredicate"
|
|
235
|
+
assert str(pred) == "PredicateFunction (customPredicate)"
|
|
236
|
+
assert pred.value() is True
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class TestAsyncFunctionExtension:
|
|
240
|
+
"""Test cases for AsyncFunction extension."""
|
|
241
|
+
|
|
242
|
+
def test_async_function_can_be_instantiated(self):
|
|
243
|
+
"""AsyncFunction class is exported and can be instantiated."""
|
|
244
|
+
async_func = AsyncFunction("testAsync")
|
|
245
|
+
assert async_func.name == "testAsync"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class TestReducerElementExtension:
|
|
249
|
+
"""Test cases for ReducerElement extension."""
|
|
250
|
+
|
|
251
|
+
def test_reducer_element_can_be_extended(self):
|
|
252
|
+
"""ReducerElement class is exported and can be extended."""
|
|
253
|
+
class NumberElement(ReducerElement):
|
|
254
|
+
def __init__(self):
|
|
255
|
+
self._num = 0
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def value(self):
|
|
259
|
+
return self._num
|
|
260
|
+
|
|
261
|
+
@value.setter
|
|
262
|
+
def value(self, v):
|
|
263
|
+
self._num = v
|
|
264
|
+
|
|
265
|
+
elem = NumberElement()
|
|
266
|
+
elem.value = 42
|
|
267
|
+
assert elem.value == 42
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class TestTypeExports:
|
|
271
|
+
"""Test cases for type exports."""
|
|
272
|
+
|
|
273
|
+
def test_function_metadata_type(self):
|
|
274
|
+
"""FunctionMetadata type can be used."""
|
|
275
|
+
meta = FunctionMetadata(
|
|
276
|
+
name="typeTest",
|
|
277
|
+
description="Testing type exports",
|
|
278
|
+
category="scalar",
|
|
279
|
+
parameters=[],
|
|
280
|
+
output={"description": "Output", "type": "string"},
|
|
281
|
+
)
|
|
282
|
+
assert meta.name == "typeTest"
|
|
283
|
+
assert meta.description == "Testing type exports"
|
|
284
|
+
|
|
285
|
+
def test_function_category_accepts_standard_and_custom(self):
|
|
286
|
+
"""FunctionCategory type accepts standard and custom categories."""
|
|
287
|
+
scalar: FunctionCategory = "scalar"
|
|
288
|
+
aggregate: FunctionCategory = "aggregate"
|
|
289
|
+
predicate: FunctionCategory = "predicate"
|
|
290
|
+
async_cat: FunctionCategory = "async"
|
|
291
|
+
custom: FunctionCategory = "myCustomCategory"
|
|
292
|
+
|
|
293
|
+
assert scalar == "scalar"
|
|
294
|
+
assert aggregate == "aggregate"
|
|
295
|
+
assert predicate == "predicate"
|
|
296
|
+
assert async_cat == "async"
|
|
297
|
+
assert custom == "myCustomCategory"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class TestPluginFunctionsIntegration:
|
|
301
|
+
"""Test cases for plugin functions integration with FlowQuery."""
|
|
302
|
+
|
|
303
|
+
@pytest.mark.asyncio
|
|
304
|
+
async def test_custom_scalar_function_in_query(self):
|
|
305
|
+
"""Custom scalar function can be used in a FlowQuery statement."""
|
|
306
|
+
from flowquery.compute.runner import Runner
|
|
307
|
+
|
|
308
|
+
@FunctionDef({
|
|
309
|
+
"description": "Doubles a number",
|
|
310
|
+
"category": "scalar",
|
|
311
|
+
"parameters": [{"name": "value", "description": "Number to double", "type": "number"}],
|
|
312
|
+
"output": {"description": "Doubled value", "type": "number"},
|
|
313
|
+
})
|
|
314
|
+
class Double(Function):
|
|
315
|
+
def __init__(self):
|
|
316
|
+
super().__init__("double")
|
|
317
|
+
self._expected_parameter_count = 1
|
|
318
|
+
|
|
319
|
+
def value(self):
|
|
320
|
+
return self.get_children()[0].value() * 2
|
|
321
|
+
|
|
322
|
+
runner = Runner("WITH 5 AS num RETURN double(num) AS result")
|
|
323
|
+
await runner.run()
|
|
324
|
+
|
|
325
|
+
assert len(runner.results) == 1
|
|
326
|
+
assert runner.results[0] == {"result": 10}
|
|
327
|
+
|
|
328
|
+
@pytest.mark.asyncio
|
|
329
|
+
async def test_custom_string_function_in_query(self):
|
|
330
|
+
"""Custom string function can be used in a FlowQuery statement."""
|
|
331
|
+
from flowquery.compute.runner import Runner
|
|
332
|
+
|
|
333
|
+
@FunctionDef({
|
|
334
|
+
"description": "Reverses a string",
|
|
335
|
+
"category": "scalar",
|
|
336
|
+
"parameters": [{"name": "text", "description": "String to reverse", "type": "string"}],
|
|
337
|
+
"output": {"description": "Reversed string", "type": "string"},
|
|
338
|
+
})
|
|
339
|
+
class StrReverse(Function):
|
|
340
|
+
def __init__(self):
|
|
341
|
+
super().__init__("strreverse")
|
|
342
|
+
self._expected_parameter_count = 1
|
|
343
|
+
|
|
344
|
+
def value(self):
|
|
345
|
+
input_str = str(self.get_children()[0].value())
|
|
346
|
+
return input_str[::-1]
|
|
347
|
+
|
|
348
|
+
runner = Runner("WITH 'hello' AS s RETURN strreverse(s) AS reversed")
|
|
349
|
+
await runner.run()
|
|
350
|
+
|
|
351
|
+
assert len(runner.results) == 1
|
|
352
|
+
assert runner.results[0] == {"reversed": "olleh"}
|
|
353
|
+
|
|
354
|
+
@pytest.mark.asyncio
|
|
355
|
+
async def test_custom_function_with_expressions(self):
|
|
356
|
+
"""Custom function works with expressions and other functions."""
|
|
357
|
+
from flowquery.compute.runner import Runner
|
|
358
|
+
|
|
359
|
+
@FunctionDef({
|
|
360
|
+
"description": "Adds 100 to a number",
|
|
361
|
+
"category": "scalar",
|
|
362
|
+
"parameters": [{"name": "value", "description": "Number", "type": "number"}],
|
|
363
|
+
"output": {"description": "Number plus 100", "type": "number"},
|
|
364
|
+
})
|
|
365
|
+
class AddHundred(Function):
|
|
366
|
+
def __init__(self):
|
|
367
|
+
super().__init__("addhundred")
|
|
368
|
+
self._expected_parameter_count = 1
|
|
369
|
+
|
|
370
|
+
def value(self):
|
|
371
|
+
return self.get_children()[0].value() + 100
|
|
372
|
+
|
|
373
|
+
runner = Runner("WITH 5 * 3 AS num RETURN addhundred(num) + 1 AS result")
|
|
374
|
+
await runner.run()
|
|
375
|
+
|
|
376
|
+
assert len(runner.results) == 1
|
|
377
|
+
assert runner.results[0] == {"result": 116} # (5*3) + 100 + 1 = 116
|
|
378
|
+
|
|
379
|
+
@pytest.mark.asyncio
|
|
380
|
+
async def test_multiple_custom_functions_together(self):
|
|
381
|
+
"""Multiple custom functions can be used together."""
|
|
382
|
+
from flowquery.compute.runner import Runner
|
|
383
|
+
|
|
384
|
+
@FunctionDef({
|
|
385
|
+
"description": "Triples a number",
|
|
386
|
+
"category": "scalar",
|
|
387
|
+
"parameters": [{"name": "value", "description": "Number to triple", "type": "number"}],
|
|
388
|
+
"output": {"description": "Tripled value", "type": "number"},
|
|
389
|
+
})
|
|
390
|
+
class Triple(Function):
|
|
391
|
+
def __init__(self):
|
|
392
|
+
super().__init__("triple")
|
|
393
|
+
self._expected_parameter_count = 1
|
|
394
|
+
|
|
395
|
+
def value(self):
|
|
396
|
+
return self.get_children()[0].value() * 3
|
|
397
|
+
|
|
398
|
+
@FunctionDef({
|
|
399
|
+
"description": "Squares a number",
|
|
400
|
+
"category": "scalar",
|
|
401
|
+
"parameters": [{"name": "value", "description": "Number to square", "type": "number"}],
|
|
402
|
+
"output": {"description": "Squared value", "type": "number"},
|
|
403
|
+
})
|
|
404
|
+
class Square(Function):
|
|
405
|
+
def __init__(self):
|
|
406
|
+
super().__init__("square")
|
|
407
|
+
self._expected_parameter_count = 1
|
|
408
|
+
|
|
409
|
+
def value(self):
|
|
410
|
+
v = self.get_children()[0].value()
|
|
411
|
+
return v * v
|
|
412
|
+
|
|
413
|
+
runner = Runner("WITH 2 AS num RETURN triple(num) AS tripled, square(num) AS squared")
|
|
414
|
+
await runner.run()
|
|
415
|
+
|
|
416
|
+
assert len(runner.results) == 1
|
|
417
|
+
assert runner.results[0] == {"tripled": 6, "squared": 4}
|
|
418
|
+
|
|
419
|
+
@pytest.mark.asyncio
|
|
420
|
+
async def test_custom_aggregate_function_in_query(self):
|
|
421
|
+
"""Custom aggregate function can be used in a FlowQuery statement."""
|
|
422
|
+
from flowquery.compute.runner import Runner
|
|
423
|
+
|
|
424
|
+
# Custom reducer element for MinValue
|
|
425
|
+
class MinReducerElement(ReducerElement):
|
|
426
|
+
def __init__(self):
|
|
427
|
+
self._value = None
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def value(self):
|
|
431
|
+
return self._value
|
|
432
|
+
|
|
433
|
+
@value.setter
|
|
434
|
+
def value(self, val):
|
|
435
|
+
self._value = val
|
|
436
|
+
|
|
437
|
+
@FunctionDef({
|
|
438
|
+
"description": "Collects the minimum value",
|
|
439
|
+
"category": "aggregate",
|
|
440
|
+
"parameters": [{"name": "value", "description": "Value to compare", "type": "number"}],
|
|
441
|
+
"output": {"description": "Minimum value", "type": "number"},
|
|
442
|
+
})
|
|
443
|
+
class MinValue(AggregateFunction):
|
|
444
|
+
def __init__(self):
|
|
445
|
+
super().__init__("minvalue")
|
|
446
|
+
self._expected_parameter_count = 1
|
|
447
|
+
|
|
448
|
+
def reduce(self, element):
|
|
449
|
+
current = self.first_child().value()
|
|
450
|
+
if element.value is None or current < element.value:
|
|
451
|
+
element.value = current
|
|
452
|
+
|
|
453
|
+
def element(self):
|
|
454
|
+
return MinReducerElement()
|
|
455
|
+
|
|
456
|
+
runner = Runner("unwind [5, 2, 8, 1, 9] AS num RETURN minvalue(num) AS min")
|
|
457
|
+
await runner.run()
|
|
458
|
+
|
|
459
|
+
assert len(runner.results) == 1
|
|
460
|
+
assert runner.results[0] == {"min": 1}
|
|
461
|
+
|
|
462
|
+
@pytest.mark.asyncio
|
|
463
|
+
async def test_custom_async_provider_in_load_json_from_statement(self):
|
|
464
|
+
"""Custom async provider can be used in LOAD JSON FROM statement."""
|
|
465
|
+
from flowquery.compute.runner import Runner
|
|
466
|
+
|
|
467
|
+
@FunctionDef({
|
|
468
|
+
"description": "Provides example data for testing",
|
|
469
|
+
"category": "async",
|
|
470
|
+
"parameters": [],
|
|
471
|
+
"output": {"description": "Example data object", "type": "object"},
|
|
472
|
+
})
|
|
473
|
+
class _GetExampleData(AsyncFunction):
|
|
474
|
+
def __init__(self):
|
|
475
|
+
super().__init__("getexampledata")
|
|
476
|
+
self._expected_parameter_count = 0
|
|
477
|
+
|
|
478
|
+
async def generate(self):
|
|
479
|
+
yield {"id": 1, "name": "Alice"}
|
|
480
|
+
yield {"id": 2, "name": "Bob"}
|
|
481
|
+
|
|
482
|
+
runner = Runner("LOAD JSON FROM getexampledata() AS data RETURN data.id AS id, data.name AS name")
|
|
483
|
+
await runner.run()
|
|
484
|
+
|
|
485
|
+
assert len(runner.results) == 2
|
|
486
|
+
assert runner.results[0] == {"id": 1, "name": "Alice"}
|
|
487
|
+
assert runner.results[1] == {"id": 2, "name": "Bob"}
|
|
488
|
+
|
|
489
|
+
@pytest.mark.asyncio
|
|
490
|
+
async def test_function_names_are_case_insensitive(self):
|
|
491
|
+
"""Function names are case-insensitive."""
|
|
492
|
+
from flowquery.compute.runner import Runner
|
|
493
|
+
|
|
494
|
+
@FunctionDef({
|
|
495
|
+
"description": "Test function for case insensitivity",
|
|
496
|
+
"category": "async",
|
|
497
|
+
"parameters": [],
|
|
498
|
+
"output": {"description": "Test data", "type": "object"},
|
|
499
|
+
})
|
|
500
|
+
class _MixedCaseFunc(AsyncFunction):
|
|
501
|
+
def __init__(self):
|
|
502
|
+
super().__init__("mixedcasefunc")
|
|
503
|
+
self._expected_parameter_count = 0
|
|
504
|
+
|
|
505
|
+
async def generate(self):
|
|
506
|
+
yield {"value": 42}
|
|
507
|
+
|
|
508
|
+
# Test using different casings in FlowQuery statements
|
|
509
|
+
runner1 = Runner("LOAD JSON FROM mixedcasefunc() AS d RETURN d.value AS v")
|
|
510
|
+
await runner1.run()
|
|
511
|
+
assert runner1.results[0] == {"v": 42}
|
|
512
|
+
|
|
513
|
+
runner2 = Runner("LOAD JSON FROM MIXEDCASEFUNC() AS d RETURN d.value AS v")
|
|
514
|
+
await runner2.run()
|
|
515
|
+
assert runner2.results[0] == {"v": 42}
|
|
516
|
+
|
|
517
|
+
def test_parameter_schema_type_can_be_used(self):
|
|
518
|
+
"""ParameterSchema type can be used."""
|
|
519
|
+
param: ParameterSchema = {
|
|
520
|
+
"name": "testParam",
|
|
521
|
+
"description": "A test parameter",
|
|
522
|
+
"type": "string",
|
|
523
|
+
"required": True,
|
|
524
|
+
"default": "default value",
|
|
525
|
+
"example": "example value",
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
assert param["name"] == "testParam"
|
|
529
|
+
assert param["required"] is True
|
|
530
|
+
|
|
531
|
+
def test_parameter_schema_with_nested_types(self):
|
|
532
|
+
"""ParameterSchema with nested types."""
|
|
533
|
+
array_param: ParameterSchema = {
|
|
534
|
+
"name": "items",
|
|
535
|
+
"description": "Array of items",
|
|
536
|
+
"type": "array",
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
object_param: ParameterSchema = {
|
|
540
|
+
"name": "config",
|
|
541
|
+
"description": "Configuration object",
|
|
542
|
+
"type": "object",
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
assert array_param["type"] == "array"
|
|
546
|
+
assert object_param["type"] == "object"
|
|
547
|
+
|
|
548
|
+
def test_output_schema_type_can_be_used(self):
|
|
549
|
+
"""OutputSchema type can be used."""
|
|
550
|
+
output: OutputSchema = {
|
|
551
|
+
"description": "Result output",
|
|
552
|
+
"type": "object",
|
|
553
|
+
"example": {"success": True, "data": []},
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
assert output["type"] == "object"
|
|
557
|
+
assert output["example"]["success"] is True
|
|
558
|
+
|
|
559
|
+
def test_function_def_options_type_can_be_used(self):
|
|
560
|
+
"""FunctionDefOptions type can be used."""
|
|
561
|
+
options: FunctionDefOptions = {
|
|
562
|
+
"description": "Function options test",
|
|
563
|
+
"category": "scalar",
|
|
564
|
+
"parameters": [],
|
|
565
|
+
"output": {"description": "Output", "type": "string"},
|
|
566
|
+
"notes": "Some additional notes",
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
assert options["description"] == "Function options test"
|
|
570
|
+
assert options["notes"] == "Some additional notes"
|
|
571
|
+
|
|
572
|
+
@pytest.mark.asyncio
|
|
573
|
+
async def test_custom_function_retrieved_via_functions(self):
|
|
574
|
+
"""Custom function can be retrieved via functions() in a FlowQuery statement."""
|
|
575
|
+
from flowquery.extensibility import FunctionDef
|
|
576
|
+
from flowquery.parsing.functions.function import Function
|
|
577
|
+
from flowquery.parsing.functions.function_metadata import get_function_metadata
|
|
578
|
+
from flowquery.compute.runner import Runner
|
|
579
|
+
|
|
580
|
+
@FunctionDef({
|
|
581
|
+
"description": "A unique test function for introspection",
|
|
582
|
+
"category": "scalar",
|
|
583
|
+
"parameters": [{"name": "x", "description": "Input value", "type": "number"}],
|
|
584
|
+
"output": {"description": "Output value", "type": "number"},
|
|
585
|
+
})
|
|
586
|
+
class IntrospectTestFunc(Function):
|
|
587
|
+
def __init__(self):
|
|
588
|
+
super().__init__("introspectTestFunc")
|
|
589
|
+
self._expected_parameter_count = 1
|
|
590
|
+
|
|
591
|
+
def value(self):
|
|
592
|
+
return self.get_children()[0].value() + 42
|
|
593
|
+
|
|
594
|
+
# First verify the function is registered
|
|
595
|
+
metadata = get_function_metadata("introspectTestFunc")
|
|
596
|
+
assert metadata is not None
|
|
597
|
+
assert metadata.name == "introspecttestfunc"
|
|
598
|
+
|
|
599
|
+
# Use functions() with UNWIND to find the registered function
|
|
600
|
+
runner = Runner("""
|
|
601
|
+
WITH functions() AS funcs
|
|
602
|
+
UNWIND funcs AS f
|
|
603
|
+
WITH f WHERE f.name = 'introspecttestfunc'
|
|
604
|
+
RETURN f.name AS name, f.description AS description, f.category AS category
|
|
605
|
+
""")
|
|
606
|
+
await runner.run()
|
|
607
|
+
|
|
608
|
+
assert len(runner.results) == 1
|
|
609
|
+
assert runner.results[0]["name"] == "introspecttestfunc"
|
|
610
|
+
assert runner.results[0]["description"] == "A unique test function for introspection"
|
|
611
|
+
assert runner.results[0]["category"] == "scalar"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tokenization tests package."""
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Tests for the TokenMapper class."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from flowquery.tokenization.token_mapper import TokenMapper
|
|
5
|
+
from flowquery.tokenization.symbol import Symbol
|
|
6
|
+
from flowquery.tokenization.keyword import Keyword
|
|
7
|
+
from flowquery.tokenization.operator import Operator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestTokenMapper:
|
|
11
|
+
"""Test cases for the TokenMapper class."""
|
|
12
|
+
|
|
13
|
+
def test_mapper_with_symbols(self):
|
|
14
|
+
"""Test mapper with Symbol enum."""
|
|
15
|
+
mapper = TokenMapper(Symbol)
|
|
16
|
+
|
|
17
|
+
assert mapper.map(Symbol.LEFT_PARENTHESIS.value) is not None
|
|
18
|
+
assert mapper.map(Symbol.RIGHT_PARENTHESIS.value) is not None
|
|
19
|
+
assert mapper.map(Symbol.COMMA.value) is not None
|
|
20
|
+
assert mapper.map(Symbol.DOT.value) is not None
|
|
21
|
+
assert mapper.map(Symbol.COLON.value) is not None
|
|
22
|
+
|
|
23
|
+
# Operator should not be found in symbol mapper
|
|
24
|
+
assert mapper.map(Operator.ADD.value) is None
|
|
25
|
+
|
|
26
|
+
def test_mapper_with_keywords(self):
|
|
27
|
+
"""Test mapper with Keyword enum."""
|
|
28
|
+
mapper = TokenMapper(Keyword)
|
|
29
|
+
|
|
30
|
+
assert mapper.map(Keyword.MATCH.value) is not None
|
|
31
|
+
assert mapper.map(Keyword.RETURN.value) is not None
|
|
32
|
+
assert mapper.map(Keyword.WHERE.value) is not None
|
|
33
|
+
|
|
34
|
+
assert mapper.map("not_a_keyword") is None
|
|
35
|
+
|
|
36
|
+
def test_mapper_with_operators(self):
|
|
37
|
+
"""Test mapper with Operator enum."""
|
|
38
|
+
mapper = TokenMapper(Operator)
|
|
39
|
+
|
|
40
|
+
assert mapper.map(Operator.GREATER_THAN_OR_EQUAL.value) is not None
|
|
41
|
+
assert mapper.map(Operator.ADD.value) is not None
|
|
42
|
+
assert mapper.map(Operator.SUBTRACT.value) is not None
|
|
43
|
+
assert mapper.map(Operator.NOT.value) is not None
|
|
44
|
+
assert mapper.map(Operator.EQUALS.value) is not None
|
|
45
|
+
assert mapper.map(Operator.NOT_EQUALS.value) is not None
|
|
46
|
+
assert mapper.map(Operator.LESS_THAN.value) is not None
|
|
47
|
+
assert mapper.map(Operator.LESS_THAN_OR_EQUAL.value) is not None
|
|
48
|
+
|
|
49
|
+
# Partial match should still work
|
|
50
|
+
assert mapper.map(Operator.GREATER_THAN_OR_EQUAL.value + "1") is not None
|
|
51
|
+
|
|
52
|
+
assert mapper.map("i_s_n_o_t_an_operator") is None
|
|
53
|
+
|
|
54
|
+
def test_mapper_with_mixed_types(self):
|
|
55
|
+
"""Test mapper with mixed types."""
|
|
56
|
+
mapper = TokenMapper(Symbol)
|
|
57
|
+
|
|
58
|
+
assert mapper.map(Symbol.LEFT_PARENTHESIS.value) is not None
|
|
59
|
+
assert mapper.map(Symbol.RIGHT_PARENTHESIS.value) is not None
|
|
60
|
+
assert mapper.map(Symbol.COMMA.value) is not None
|