Skip to content

Commit 028a64e

Browse files
author
oleksandr.volha
committed
improvements
1 parent 8c2e9fa commit 028a64e

File tree

15 files changed

+172
-137
lines changed

15 files changed

+172
-137
lines changed

uncoder-core/app/translator/core/custom_types/functions.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,20 @@ class FunctionType(CustomEnum):
99
min = "min"
1010
sum = "sum"
1111

12-
divide = "divide"
12+
values = "values"
1313

1414
earliest = "earliest"
1515
latest = "latest"
1616

17+
divide = "divide"
18+
1719
lower = "lower"
20+
split = "split"
1821
upper = "upper"
1922

20-
compare_boolean = "compare_boolean"
21-
23+
array_length = "array_length"
24+
compare = "compare"
25+
extract_time = "extract_time"
2226
ipv4_is_in_range = "ipv4_is_in_range"
2327

2428
bin = "bin"
@@ -30,4 +34,6 @@ class FunctionType(CustomEnum):
3034
stats = "stats"
3135
table = "table"
3236
timeframe = "timeframe"
33-
values = "values"
37+
union = "union"
38+
39+
aggregation_data_function = "aggregation_data_function"

uncoder-core/app/translator/core/functions.py

Lines changed: 75 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
import re
2222
from abc import ABC, abstractmethod
2323
from dataclasses import dataclass
24-
from functools import cached_property
25-
from typing import TYPE_CHECKING, Any, Optional
24+
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union
2625

27-
from app.translator.core.exceptions.functions import InvalidFunctionSignature, NotSupportedFunctionException
26+
from app.translator.core.exceptions.functions import NotSupportedFunctionException
2827
from app.translator.core.mapping import SourceMapping
29-
from app.translator.core.models.field import Field
28+
from app.translator.core.models.field import Alias, Field
3029
from app.translator.core.models.functions.base import Function, ParsedFunctions, RenderedFunctions
30+
from app.translator.tools.utils import execute_module
3131
from settings import INIT_FUNCTIONS
3232

3333
if TYPE_CHECKING:
@@ -41,6 +41,14 @@ class FunctionMatchContainer:
4141

4242

4343
class BaseFunctionParser(ABC):
44+
function_names_map: ClassVar[dict[str, str]] = {}
45+
functions_group_name: str = None
46+
manager: PlatformFunctionsManager = None
47+
48+
def set_functions_manager(self, manager: PlatformFunctionsManager) -> BaseFunctionParser:
49+
self.manager = manager
50+
return self
51+
4452
@abstractmethod
4553
def parse(self, *args, **kwargs) -> Function:
4654
raise NotImplementedError
@@ -73,21 +81,25 @@ def parse(self, func_body: str, raw: str) -> Function:
7381

7482

7583
class FunctionRender(ABC):
84+
function_names_map: ClassVar[dict[str, str]] = {}
85+
order_to_render: int = 0
86+
in_query_render: bool = False
87+
render_to_prefix: bool = False
88+
manager: PlatformFunctionsManager = None
89+
90+
def set_functions_manager(self, manager: PlatformFunctionsManager) -> FunctionRender:
91+
self.manager = manager
92+
return self
93+
7694
@abstractmethod
7795
def render(self, function: Function, source_mapping: SourceMapping) -> str:
7896
raise NotImplementedError
7997

8098
@staticmethod
81-
def concat_kwargs(kwargs: dict[str, str]) -> str:
82-
result = ""
83-
for key, value in kwargs.items():
84-
if value:
85-
result = f"{result}, {key}={value}" if result else f"{key}={value}"
99+
def map_field(field: Union[Alias, Field], source_mapping: SourceMapping) -> str:
100+
if isinstance(field, Alias):
101+
return field.name
86102

87-
return result
88-
89-
@staticmethod
90-
def map_field(field: Field, source_mapping: SourceMapping) -> str:
91103
generic_field_name = field.get_generic_field_name(source_mapping.source_id)
92104
mapped_field = source_mapping.fields_mapping.get_platform_field_name(generic_field_name=generic_field_name)
93105
if isinstance(mapped_field, list):
@@ -97,23 +109,48 @@ def map_field(field: Field, source_mapping: SourceMapping) -> str:
97109

98110

99111
class PlatformFunctionsManager:
112+
platform_functions: PlatformFunctions = None
113+
100114
def __init__(self):
101-
self._parsers_map: dict[str, HigherOrderFunctionParser] = {}
102-
self._renders_map: dict[str, FunctionRender] = {}
103-
self._in_query_renders_map: dict[str, FunctionRender] = {}
104-
self._names_map: dict[str, str] = {}
105-
self._order_to_render: dict[str, int] = {}
106-
self._render_to_prefix_functions: list[str] = []
107-
108-
def post_init_configure(self, platform_render: PlatformQueryRender) -> None:
109-
raise NotImplementedError
115+
# {platform_func_name: HigherOrderFunctionParser}
116+
self._hof_parsers_map: dict[str, HigherOrderFunctionParser] = {}
117+
self._parsers_map: dict[str, FunctionParser] = {} # {platform_func_name: FunctionParser}
118+
119+
self._renders_map: dict[str, FunctionRender] = {} # {generic_func_name: FunctionRender}
120+
self._in_query_renders_map: dict[str, FunctionRender] = {} # {generic_func_name: FunctionRender}
121+
self._order_to_render: dict[str, int] = {} # {generic_func_name: int}
122+
123+
def register_render(self, render_class: type[FunctionRender]) -> type[FunctionRender]:
124+
render = render_class()
125+
render.manager = self
126+
for generic_function_name in render.function_names_map:
127+
self._renders_map[generic_function_name] = render
128+
self._order_to_render[generic_function_name] = render.order_to_render
129+
if render.in_query_render:
130+
self._in_query_renders_map[generic_function_name] = render
131+
132+
return render_class
133+
134+
def register_parser(self, parser_class: type[BaseFunctionParser]) -> type[BaseFunctionParser]:
135+
parser = parser_class()
136+
parser.manager = self
137+
parsers_map = self._hof_parsers_map if isinstance(parser, HigherOrderFunctionParser) else self._parsers_map
138+
for platform_function_name in parser.function_names_map:
139+
parsers_map[platform_function_name] = parser
140+
141+
if parser.functions_group_name:
142+
parsers_map[parser.functions_group_name] = parser
143+
144+
return parser_class
145+
146+
def get_hof_parser(self, platform_func_name: str) -> HigherOrderFunctionParser:
147+
if INIT_FUNCTIONS and (parser := self._hof_parsers_map.get(platform_func_name)):
148+
return parser
110149

111-
@cached_property
112-
def _inverted_names_map(self) -> dict[str, str]:
113-
return {value: key for key, value in self._names_map.items()}
150+
raise NotSupportedFunctionException
114151

115-
def get_parser(self, generic_func_name: str) -> HigherOrderFunctionParser:
116-
if INIT_FUNCTIONS and (parser := self._parsers_map.get(generic_func_name)):
152+
def get_parser(self, platform_func_name: str) -> FunctionParser:
153+
if INIT_FUNCTIONS and (parser := self._parsers_map.get(platform_func_name)):
117154
return parser
118155

119156
raise NotSupportedFunctionException
@@ -130,56 +167,29 @@ def get_in_query_render(self, generic_func_name: str) -> FunctionRender:
130167

131168
raise NotSupportedFunctionException
132169

133-
def get_generic_func_name(self, platform_func_name: str) -> Optional[str]:
134-
if INIT_FUNCTIONS and (generic_func_name := self._names_map.get(platform_func_name)):
135-
return generic_func_name
136-
137-
raise NotSupportedFunctionException
138-
139-
def get_platform_func_name(self, generic_func_name: str) -> Optional[str]:
140-
if INIT_FUNCTIONS:
141-
return self._inverted_names_map.get(generic_func_name)
142-
143170
@property
144171
def order_to_render(self) -> dict[str, int]:
145172
if INIT_FUNCTIONS:
146173
return self._order_to_render
147174

148175
return {}
149176

150-
@property
151-
def render_to_prefix_functions(self) -> list[str]:
152-
if INIT_FUNCTIONS:
153-
return self._render_to_prefix_functions
154-
155-
return []
156-
157177

158178
class PlatformFunctions:
179+
dir_path: str = None
180+
platform_query_render: PlatformQueryRender = None
159181
manager: PlatformFunctionsManager = PlatformFunctionsManager()
182+
160183
function_delimiter = "|"
161184

162-
def parse(self, query: str) -> ParsedFunctions:
163-
parsed = []
164-
not_supported = []
165-
invalid = []
166-
functions = query.split(self.function_delimiter)
167-
for func in functions:
168-
split_func = func.strip().split(" ")
169-
func_name, func_body = split_func[0], " ".join(split_func[1:])
170-
try:
171-
func_parser = self.manager.get_parser(self.manager.get_generic_func_name(func_name))
172-
parsed.append(func_parser.parse(func_body, func))
173-
except NotSupportedFunctionException:
174-
not_supported.append(func)
175-
except InvalidFunctionSignature:
176-
invalid.append(func)
185+
def __init__(self):
186+
self.manager.platform_functions = self
187+
if self.dir_path:
188+
execute_module(f"{self.dir_path}/parsers/__init__.py")
189+
execute_module(f"{self.dir_path}/renders/__init__.py")
177190

178-
return ParsedFunctions(
179-
functions=parsed,
180-
not_supported=[self.wrap_function_with_delimiter(func) for func in not_supported],
181-
invalid=invalid,
182-
)
191+
def parse(self, query: str) -> ParsedFunctions: # noqa: ARG002
192+
return ParsedFunctions()
183193

184194
def _sort_functions_to_render(self, functions: list[Function]) -> list[Function]:
185195
return sorted(functions, key=lambda func: self.manager.order_to_render.get(func.name, 0))
@@ -193,7 +203,7 @@ def render(self, functions: list[Function], source_mapping: SourceMapping) -> Re
193203
try:
194204
func_render = self.manager.get_render(func.name)
195205
_rendered = func_render.render(func, source_mapping)
196-
if func.name in self.manager.render_to_prefix_functions:
206+
if func_render.render_to_prefix:
197207
rendered_prefix += _rendered
198208
else:
199209
rendered += self.wrap_function_with_delimiter(_rendered)

uncoder-core/app/translator/platforms/base/aql/functions/__init__.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
-----------------------------------------------------------------
1717
"""
1818

19+
import os.path
1920
import re
2021
from typing import Optional, Union
2122

@@ -26,15 +27,20 @@
2627
from app.translator.core.models.functions.base import Function, ParsedFunctions
2728
from app.translator.core.models.functions.sort import SortLimitFunction
2829
from app.translator.platforms.base.aql.const import TABLE_PATTERN
29-
from app.translator.platforms.base.aql.functions.const import func_aliases_ctx_var, AGGREGATION_FUNCTIONS_MAP
30-
from app.translator.platforms.base.aql.functions.const import AQLFunctionType
31-
from app.translator.platforms.base.aql.functions.manager import AQLFunctionsManager
30+
from app.translator.platforms.base.aql.functions.const import (
31+
AGGREGATION_FUNCTIONS_MAP,
32+
AQLFunctionType,
33+
func_aliases_ctx_var,
34+
)
35+
from app.translator.platforms.base.aql.functions.manager import AQLFunctionsManager, aql_functions_manager
3236

3337

3438
class AQLFunctions(PlatformFunctions):
39+
dir_path: str = os.path.abspath(os.path.dirname(__file__))
40+
3541
function_delimiter = ""
3642
functions_pattern = r"\s(?P<function_name>(group by|order by|last))"
37-
manager = AQLFunctionsManager()
43+
manager: AQLFunctionsManager = aql_functions_manager
3844

3945
def parse(self, query: str) -> tuple[str, ParsedFunctions]:
4046
parsed = []
@@ -56,7 +62,7 @@ def parse(self, query: str) -> tuple[str, ParsedFunctions]:
5662
agg_functions = query[search.start() :]
5763
query = query[: search.start()]
5864
self._parse_function(
59-
function_name=AQLFunctionType.aggregation_data_parser,
65+
function_name=AQLFunctionType.aggregation_data_function,
6066
function=agg_functions,
6167
parsed=parsed,
6268
not_supported=not_supported,
@@ -108,6 +114,7 @@ def __merge_sort_limit_functions(functions: list[Function]) -> list[Function]:
108114
if len(funcs) == 2: # noqa: PLR2004
109115
funcs[1].args = funcs[1].args or funcs[0].args
110116
funcs[1].limit = funcs[1].limit or funcs[0].limit
117+
funcs[1].raw = f"{funcs[1].raw} {funcs[0].raw}"
111118
functions.pop(indices[0])
112119

113120
return functions
@@ -116,7 +123,7 @@ def _parse_function(
116123
self, function: str, function_name: str, parsed: list[Function], not_supported: list[str], invalid: list[str]
117124
) -> None:
118125
try:
119-
function_parser = self.manager.get_parser(function_name)
126+
function_parser = self.manager.get_hof_parser(function_name)
120127
function_token = function_parser.parse(func_body=function, raw=function)
121128
if isinstance(function_token, list):
122129
parsed.extend(function_token)

uncoder-core/app/translator/platforms/base/aql/functions/const.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ class AQLFunctionType(CustomEnum):
1313
avg: str = "AVG"
1414
count: str = "COUNT"
1515
distinct_count: str = "DISTINCTCOUNT"
16+
group_by: str = "GROUP BY"
1617
last: str = "LAST"
17-
fields: str = "fields"
18-
aggregation_data_parser: str = "aggregation_data_parser"
18+
fields: str = "SELECT"
19+
limit: str = "LIMIT"
20+
order_by: str = "ORDER BY"
21+
22+
aggregation_data_function: str = "aggregation_data_function"
23+
24+
25+
class AQLFunctionGroupType(CustomEnum):
26+
agg = "agg"
1927

2028

2129
class AQLSortOrderType(CustomEnum):
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from __future__ import annotations
2-
31
from app.translator.core.functions import PlatformFunctionsManager
42

53

6-
class AQLFunctionsManager(PlatformFunctionsManager):
7-
...
4+
class AQLFunctionsManager(PlatformFunctionsManager): ...
5+
6+
7+
aql_functions_manager = AQLFunctionsManager()

uncoder-core/app/translator/platforms/base/spl/functions/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
import os.path
12
import re
23

34
from app.translator.core.exceptions.functions import InvalidFunctionSignature, NotSupportedFunctionException
45
from app.translator.core.functions import PlatformFunctions
56
from app.translator.core.models.functions.base import ParsedFunctions
67
from app.translator.core.models.query_container import RawQueryContainer, TokenizedQueryContainer
78
from app.translator.platforms.base.spl.functions.const import SplFunctionType
8-
from app.translator.platforms.base.spl.functions.manager import SplFunctionsManager
9+
from app.translator.platforms.base.spl.functions.manager import SplFunctionsManager, spl_functions_manager
910

1011

1112
class SplFunctions(PlatformFunctions):
12-
manager = SplFunctionsManager()
13+
dir_path: str = os.path.abspath(os.path.dirname(__file__))
14+
manager: SplFunctionsManager = spl_functions_manager
1315

1416
@staticmethod
1517
def prepare_query(query: str) -> str:
@@ -27,7 +29,7 @@ def parse(self, query: str) -> tuple[str, ParsedFunctions]:
2729
split_func = func.strip().split(" ")
2830
func_name, func_body = split_func[0], " ".join(split_func[1:])
2931
try:
30-
func_parser = self.manager.get_parser(self.manager.get_generic_func_name(func_name))
32+
func_parser = self.manager.get_hof_parser(func_name)
3133
parsed.append(func_parser.parse(func_body, func))
3234
except NotSupportedFunctionException:
3335
not_supported.append(func)

uncoder-core/app/translator/platforms/base/spl/functions/const.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ class SplFunctionType(CustomEnum):
2121
where = "where"
2222

2323

24+
class SplFunctionGroupType(CustomEnum):
25+
agg = "agg"
26+
time = "time"
27+
28+
2429
class SplSortOrderType(CustomEnum):
2530
asc = "+"
2631
desc = "-"
Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
from __future__ import annotations
2-
3-
from typing import TYPE_CHECKING
4-
51
from app.translator.core.functions import PlatformFunctionsManager
62

7-
if TYPE_CHECKING:
8-
from app.translator.platforms.base.spl.renders.spl import SplQueryRender
3+
4+
class SplFunctionsManager(PlatformFunctionsManager): ...
95

106

11-
class SplFunctionsManager(PlatformFunctionsManager):
12-
def post_init_configure(self, platform_render: SplQueryRender) -> None:
13-
pass
7+
spl_functions_manager = SplFunctionsManager()

0 commit comments

Comments
 (0)