ormlambda 2.0.2__py3-none-any.whl → 2.6.1__py3-none-any.whl
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.
- ormlambda/common/abstract_classes/abstract_model.py +28 -13
- ormlambda/common/abstract_classes/decomposition_query.py +301 -0
- ormlambda/common/interfaces/IAggregate.py +10 -0
- ormlambda/common/interfaces/IDecompositionQuery.py +56 -0
- ormlambda/common/interfaces/IRepositoryBase.py +1 -10
- ormlambda/common/interfaces/IStatements.py +8 -2
- ormlambda/common/interfaces/__init__.py +2 -0
- ormlambda/databases/my_sql/clauses/__init__.py +16 -14
- ormlambda/databases/my_sql/clauses/count.py +27 -23
- ormlambda/databases/my_sql/clauses/select.py +28 -155
- ormlambda/databases/my_sql/clauses/where_condition.py +2 -2
- ormlambda/databases/my_sql/functions/__init__.py +3 -0
- ormlambda/databases/my_sql/functions/concat.py +41 -0
- ormlambda/databases/my_sql/functions/group_by.py +37 -0
- ormlambda/databases/my_sql/functions/max.py +39 -0
- ormlambda/databases/my_sql/repository.py +29 -44
- ormlambda/databases/my_sql/statements.py +18 -32
- ormlambda/model_base.py +2 -0
- ormlambda/utils/table_constructor.py +5 -4
- {ormlambda-2.0.2.dist-info → ormlambda-2.6.1.dist-info}/METADATA +1 -1
- {ormlambda-2.0.2.dist-info → ormlambda-2.6.1.dist-info}/RECORD +23 -19
- ormlambda/components/select/ISelect.py +0 -14
- ormlambda/components/select/__init__.py +0 -2
- ormlambda/components/select/table_column.py +0 -43
- {ormlambda-2.0.2.dist-info → ormlambda-2.6.1.dist-info}/LICENSE +0 -0
- {ormlambda-2.0.2.dist-info → ormlambda-2.6.1.dist-info}/WHEEL +0 -0
@@ -1,14 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import Any, Type, override, Iterable, Literal, TYPE_CHECKING
|
3
3
|
from collections import defaultdict
|
4
|
+
import abc
|
4
5
|
|
5
6
|
|
6
7
|
from ormlambda.utils import Table
|
7
8
|
from ormlambda.common.interfaces import IQuery, IRepositoryBase, IStatements_two_generic
|
9
|
+
from ormlambda.common.interfaces.IAggregate import IAggregate
|
8
10
|
|
9
11
|
if TYPE_CHECKING:
|
12
|
+
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
|
10
13
|
from ormlambda.components.select import ISelect
|
11
|
-
from ormlambda.
|
14
|
+
from ormlambda.common.abstract_classes.decomposition_query import ClauseInfo
|
12
15
|
|
13
16
|
|
14
17
|
ORDER_QUERIES = Literal["select", "join", "where", "order", "with", "group by", "limit", "offset"]
|
@@ -50,33 +53,45 @@ class AbstractSQLStatements[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
|
|
50
53
|
response_sql = self._repository.read_sql(query, flavour=dict) # store all columns of the SQL query
|
51
54
|
|
52
55
|
if isinstance(response_sql, Iterable):
|
53
|
-
return ClusterQuery(select, response_sql).clean_response()
|
56
|
+
return ClusterQuery[T](select, response_sql).clean_response()
|
54
57
|
|
55
58
|
return response_sql
|
56
59
|
|
60
|
+
@abc.abstractmethod
|
61
|
+
def _build(sef): ...
|
57
62
|
|
58
|
-
|
59
|
-
|
60
|
-
|
63
|
+
|
64
|
+
class ClusterQuery[T]:
|
65
|
+
def __init__(self, select: DecompositionQueryBase[T], response_sql: tuple[dict[str, Any]]) -> None:
|
66
|
+
self._select: DecompositionQueryBase[T] = select
|
61
67
|
self._response_sql: tuple[dict[str, Any]] = response_sql
|
62
68
|
|
63
69
|
def loop_foo(self) -> dict[Type[Table], list[Table]]:
|
64
70
|
# We must ensure to get the valid attributes for each instance
|
65
71
|
table_initialize = defaultdict(list)
|
66
72
|
|
67
|
-
|
68
|
-
for table_col in self._select.select_list:
|
69
|
-
unic_table[table_col._table].append(table_col)
|
70
|
-
|
71
|
-
for table_, table_col in unic_table.items():
|
73
|
+
for table, clauses in self._select._clauses_group_by_tables.items():
|
72
74
|
for dicc_cols in self._response_sql:
|
73
75
|
valid_attr: dict[str, Any] = {}
|
74
|
-
for
|
75
|
-
|
76
|
+
for clause in clauses:
|
77
|
+
if not hasattr(table, clause.column):
|
78
|
+
agg_methods = self.get_all_aggregate_method(clauses)
|
79
|
+
raise ValueError(f"You cannot use aggregation method like '{agg_methods}' to return model objects")
|
80
|
+
valid_attr[clause.column] = dicc_cols[clause.alias]
|
81
|
+
|
76
82
|
# COMMENT: At this point we are going to instantiate Table class with specific attributes getting directly from database
|
77
|
-
table_initialize[
|
83
|
+
table_initialize[table].append(table(**valid_attr))
|
78
84
|
return table_initialize
|
79
85
|
|
86
|
+
def get_all_aggregate_method(self, clauses: list[ClauseInfo]) -> str:
|
87
|
+
res: set[str] = set()
|
88
|
+
|
89
|
+
for clause in clauses:
|
90
|
+
row = clause._row_column
|
91
|
+
if isinstance(row, IAggregate):
|
92
|
+
res.add(row.__class__.__name__)
|
93
|
+
return ", ".join(res)
|
94
|
+
|
80
95
|
def clean_response(self) -> tuple[dict[Type[Table], tuple[Table]]]:
|
81
96
|
tbl_dicc: dict[Type[Table], list[Table]] = self.loop_foo()
|
82
97
|
|
@@ -0,0 +1,301 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from collections import defaultdict
|
3
|
+
import typing as tp
|
4
|
+
import inspect
|
5
|
+
import abc
|
6
|
+
from ormlambda import Table
|
7
|
+
|
8
|
+
from ormlambda.utils.lambda_disassembler.tree_instruction import TreeInstruction
|
9
|
+
from ormlambda.common.interfaces import IAggregate, IDecompositionQuery
|
10
|
+
from ormlambda import JoinType, ForeignKey
|
11
|
+
from ormlambda.databases.my_sql.clauses.joins import JoinSelector
|
12
|
+
from ormlambda.utils.module_tree.dfs_traversal import DFSTraversal
|
13
|
+
|
14
|
+
ClauseDataType = tp.TypeVar("ClauseDataType", bound=tp.Union[property, str])
|
15
|
+
|
16
|
+
|
17
|
+
class ClauseInfo[T: tp.Type[Table]]:
|
18
|
+
@tp.overload
|
19
|
+
def __init__(self, table: T, column: property, alias_children_resolver: tp.Callable[..., str]): ...
|
20
|
+
@tp.overload
|
21
|
+
def __init__(self, table: T, column: str, alias_children_resolver: tp.Callable[..., str]): ...
|
22
|
+
|
23
|
+
def __init__(self, table: T, column: ClauseDataType, alias_children_resolver: tp.Callable[[DecompositionQueryBase[T], str], str]):
|
24
|
+
self._table: T = table
|
25
|
+
self._row_column: ClauseDataType = column
|
26
|
+
self._column: str = self._resolve_column(column)
|
27
|
+
self._alias_children_resolver: tp.Callable[[DecompositionQueryBase[T], str], str] = alias_children_resolver
|
28
|
+
self._alias: tp.Optional[str] = self._alias_children_resolver(self)
|
29
|
+
|
30
|
+
self._query: str = self.__create_value_string(self._column)
|
31
|
+
pass
|
32
|
+
|
33
|
+
def __repr__(self) -> str:
|
34
|
+
return f"{ClauseInfo.__name__}: {self.query}"
|
35
|
+
|
36
|
+
@property
|
37
|
+
def column(self) -> ClauseDataType:
|
38
|
+
return self._column
|
39
|
+
|
40
|
+
@property
|
41
|
+
def alias(self) -> str:
|
42
|
+
return self._alias
|
43
|
+
|
44
|
+
@property
|
45
|
+
def query(self) -> str:
|
46
|
+
return self._query
|
47
|
+
|
48
|
+
def _resolve_column(self, data: ClauseDataType) -> str:
|
49
|
+
if isinstance(data, property):
|
50
|
+
return self._table.__properties_mapped__[data]
|
51
|
+
|
52
|
+
elif isinstance(data, IAggregate):
|
53
|
+
return data.alias_name
|
54
|
+
|
55
|
+
elif isinstance(data, str):
|
56
|
+
# TODOL: refactor to base the condition in dict with '*' as key. '*' must to work as special character
|
57
|
+
return f"'{data}'" if data != "*" else data
|
58
|
+
|
59
|
+
def __create_value_string(self, name: str) -> str:
|
60
|
+
if isinstance(self._row_column, property):
|
61
|
+
return self.concat_with_alias(f"{self._table.__table_name__}.{name}")
|
62
|
+
|
63
|
+
if isinstance(self._row_column, IAggregate):
|
64
|
+
return self.concat_with_alias(self._row_column.query)
|
65
|
+
|
66
|
+
return self.concat_with_alias(self.column)
|
67
|
+
|
68
|
+
def concat_with_alias(self, column_name: str) -> str:
|
69
|
+
alias: None | str = self._alias_children_resolver(self)
|
70
|
+
|
71
|
+
if not alias:
|
72
|
+
return column_name
|
73
|
+
return f"{column_name} as `{alias}`"
|
74
|
+
|
75
|
+
|
76
|
+
class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
|
77
|
+
def __init__[*Ts](
|
78
|
+
self,
|
79
|
+
table: T,
|
80
|
+
lambda_query: tp.Callable[[T], tuple[*Ts]],
|
81
|
+
*,
|
82
|
+
alias: bool = True,
|
83
|
+
alias_name: tp.Optional[str] = None,
|
84
|
+
by: JoinType = JoinType.INNER_JOIN,
|
85
|
+
replace_asterisk_char: bool = True,
|
86
|
+
) -> None:
|
87
|
+
self._table: T = table
|
88
|
+
self._lambda_query: tp.Callable[[T], tuple[Ts]] = lambda_query
|
89
|
+
self._alias: bool = alias
|
90
|
+
self._alias_name: tp.Optional[str] = alias_name
|
91
|
+
self._by: JoinType = by
|
92
|
+
|
93
|
+
self._fk_relationship: set[tuple[tp.Type[Table], tp.Type[Table]]] = set()
|
94
|
+
self._clauses_group_by_tables: dict[tp.Type[Table], list[ClauseInfo[T]]] = defaultdict(list)
|
95
|
+
self._all_clauses: list[ClauseInfo] = []
|
96
|
+
self.alias_cache: dict[str, tp.Any] = {"*": lambda x: x}
|
97
|
+
self._replace_asterisk_char: bool = replace_asterisk_char
|
98
|
+
self.__assign_lambda_variables_to_table(lambda_query)
|
99
|
+
|
100
|
+
self.__clauses_list_generetor(lambda_query)
|
101
|
+
|
102
|
+
def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
|
103
|
+
DEFAULT_ALIAS: str = f"{clause_info._table.__table_name__}_{clause_info._column}"
|
104
|
+
|
105
|
+
if isinstance(clause_info._row_column, IAggregate):
|
106
|
+
return clause_info._row_column.alias_name
|
107
|
+
return DEFAULT_ALIAS
|
108
|
+
|
109
|
+
def __assign_lambda_variables_to_table(self, _lambda: tp.Callable[[T], None]) -> None:
|
110
|
+
"""
|
111
|
+
return a dictionary with the lambda's parameters as keys and Type[Table] as the values
|
112
|
+
|
113
|
+
|
114
|
+
>>> res = _assign_lambda_variables_to_table(lambda a,ci,co: ...)
|
115
|
+
>>> print(res)
|
116
|
+
>>> # {
|
117
|
+
>>> # "a": Address,
|
118
|
+
>>> # "ci": City,
|
119
|
+
>>> # "co": Country,
|
120
|
+
>>> # }
|
121
|
+
"""
|
122
|
+
lambda_vars = tuple(inspect.signature(_lambda).parameters)
|
123
|
+
|
124
|
+
for param in lambda_vars:
|
125
|
+
self.alias_cache[param] = lambda x: self._table
|
126
|
+
return None
|
127
|
+
|
128
|
+
def __clauses_list_generetor(self, function: tp.Callable[[T], tp.Any]) -> None:
|
129
|
+
if not callable(function):
|
130
|
+
return None
|
131
|
+
|
132
|
+
resolved_function = function(self._table)
|
133
|
+
|
134
|
+
# Python treats string objects as iterable, so we need to prevent this behavior
|
135
|
+
if isinstance(resolved_function, str) or not isinstance(resolved_function, tp.Iterable):
|
136
|
+
resolved_function = (resolved_function,)
|
137
|
+
|
138
|
+
for index, value in enumerate(resolved_function):
|
139
|
+
values: ClauseInfo | list[ClauseInfo] = self._identify_value_type(index, value, function)
|
140
|
+
|
141
|
+
if isinstance(values, tp.Iterable):
|
142
|
+
[self.add_clause(x) for x in values]
|
143
|
+
else:
|
144
|
+
self.add_clause(values)
|
145
|
+
|
146
|
+
return None
|
147
|
+
|
148
|
+
def _identify_value_type[TProp](self, index: int, value: TProp, function) -> ClauseInfo[T]:
|
149
|
+
"""
|
150
|
+
A method that behaves based on the variable's type
|
151
|
+
"""
|
152
|
+
if isinstance(value, property):
|
153
|
+
if value in self._table.__properties_mapped__:
|
154
|
+
return ClauseInfo[T](self._table, value, self.alias_children_resolver)
|
155
|
+
|
156
|
+
return self._search_correct_table_for_prop(index, function, value)
|
157
|
+
|
158
|
+
elif isinstance(value, IAggregate):
|
159
|
+
return ClauseInfo[T](self._table, value, self.alias_children_resolver)
|
160
|
+
|
161
|
+
# if value is a Table instance (when you need to retrieve all columns) we'll ensure that all INNER JOINs are added
|
162
|
+
elif isinstance(value, type) and issubclass(value, Table):
|
163
|
+
if self._table != value:
|
164
|
+
self._add_necessary_fk(index, function, value)
|
165
|
+
# all columns
|
166
|
+
clauses: list[ClauseInfo] = []
|
167
|
+
for prop in value.__properties_mapped__:
|
168
|
+
if isinstance(prop, property):
|
169
|
+
clauses.append(self._identify_value_type(index, prop, function))
|
170
|
+
return clauses
|
171
|
+
|
172
|
+
elif isinstance(value, str):
|
173
|
+
# TODOM: alias_cache to replace '*' by all columns
|
174
|
+
if self._replace_asterisk_char and (replace_value := self.alias_cache.get(value, None)) is not None:
|
175
|
+
return self._identify_value_type(index, replace_value(self._table), function)
|
176
|
+
return ClauseInfo[T](self._table, value, alias_children_resolver=self.alias_children_resolver)
|
177
|
+
|
178
|
+
elif isinstance(value, bool):
|
179
|
+
...
|
180
|
+
|
181
|
+
raise NotImplementedError(f"type of value '{value}' is not implemented.")
|
182
|
+
|
183
|
+
def _search_correct_table_for_prop[TTable](self, index: int, function: tp.Callable[[T], tp.Any], prop: property) -> ClauseInfo[TTable]:
|
184
|
+
tree_list = TreeInstruction(function).to_list()
|
185
|
+
temp_table: tp.Type[Table] = self._table
|
186
|
+
|
187
|
+
table_list: list[Table] = tree_list[index].nested_element.parents[1:]
|
188
|
+
counter: int = 0
|
189
|
+
while prop not in temp_table.__properties_mapped__:
|
190
|
+
new_table: TTable = getattr(temp_table(), table_list[counter])
|
191
|
+
|
192
|
+
if not isinstance(new_table, type) or not issubclass(new_table, Table):
|
193
|
+
raise ValueError(f"new_table var must be '{Table.__class__}' type and is '{type(new_table)}'")
|
194
|
+
self.__add_fk_relationship(temp_table, new_table)
|
195
|
+
|
196
|
+
temp_table = new_table
|
197
|
+
counter += 1
|
198
|
+
|
199
|
+
if prop in new_table.__properties_mapped__:
|
200
|
+
return ClauseInfo[TTable](new_table, prop, self.alias_children_resolver)
|
201
|
+
|
202
|
+
raise ValueError(f"property '{prop}' does not exist in any inherit table.")
|
203
|
+
|
204
|
+
def add_clause[Tc: tp.Type[Table]](self, clause: ClauseInfo[Tc]) -> None:
|
205
|
+
self._all_clauses.append(clause)
|
206
|
+
self._clauses_group_by_tables[clause._table].append(clause)
|
207
|
+
|
208
|
+
return None
|
209
|
+
|
210
|
+
def _add_necessary_fk(self, index: int, function: tp.Callable[[T], tp.Any], table: tp.Type[Table]) -> None:
|
211
|
+
tree_list = TreeInstruction(function).to_list()
|
212
|
+
old_table: tp.Type[Table] = self._table
|
213
|
+
|
214
|
+
table_list: list[Table] = tree_list[index].nested_element.parents[1:]
|
215
|
+
counter: int = 0
|
216
|
+
while table not in old_table.__dict__.values():
|
217
|
+
new_table: tp.Type[Table] = getattr(old_table(), table_list[counter])
|
218
|
+
|
219
|
+
if not issubclass(new_table, Table):
|
220
|
+
raise ValueError(f"new_table var must be '{Table.__class__}' type and is '{type(new_table)}'")
|
221
|
+
|
222
|
+
self.__add_fk_relationship(old_table, new_table)
|
223
|
+
|
224
|
+
if table in new_table.__dict__.values():
|
225
|
+
self.__add_fk_relationship(new_table, table)
|
226
|
+
return None
|
227
|
+
|
228
|
+
old_table = new_table
|
229
|
+
counter += 1
|
230
|
+
|
231
|
+
self.__add_fk_relationship(old_table, table)
|
232
|
+
return None
|
233
|
+
|
234
|
+
@property
|
235
|
+
def table(self) -> T:
|
236
|
+
return self._table
|
237
|
+
|
238
|
+
@property
|
239
|
+
def lambda_query[*Ts](self) -> tp.Callable[[T], tuple[*Ts]]:
|
240
|
+
return self._lambda_query
|
241
|
+
|
242
|
+
@property
|
243
|
+
def all_clauses(self) -> list[ClauseInfo[T]]:
|
244
|
+
return self._all_clauses
|
245
|
+
|
246
|
+
@property
|
247
|
+
def clauses_group_by_tables(self) -> dict[tp.Type[Table], list[ClauseInfo[T]]]:
|
248
|
+
return self._clauses_group_by_tables
|
249
|
+
|
250
|
+
@property
|
251
|
+
def has_foreign_keys(self) -> bool:
|
252
|
+
return len(self._fk_relationship) > 0
|
253
|
+
|
254
|
+
@property
|
255
|
+
def fk_relationship(self) -> set[tuple[tp.Type[Table], tp.Type[Table]]]:
|
256
|
+
return self._fk_relationship
|
257
|
+
|
258
|
+
@property
|
259
|
+
@abc.abstractmethod
|
260
|
+
def query(self) -> str: ...
|
261
|
+
|
262
|
+
@property
|
263
|
+
def alias(self) -> str:
|
264
|
+
return self._alias
|
265
|
+
|
266
|
+
@property
|
267
|
+
def alias_name(self) -> str:
|
268
|
+
return self._alias_name
|
269
|
+
|
270
|
+
@alias_name.setter
|
271
|
+
def alias_name(self, value: tp.Optional[str]) -> None:
|
272
|
+
if value is None and self._alias:
|
273
|
+
self._alias = False
|
274
|
+
else:
|
275
|
+
self._alias = True
|
276
|
+
|
277
|
+
self._alias_name = value
|
278
|
+
|
279
|
+
def stringify_foreign_key(self, sep: str = "\n") -> str:
|
280
|
+
graph: dict[tp.Type[Table], list[tp.Type[Table]]] = defaultdict(list)
|
281
|
+
for left, right in self.fk_relationship:
|
282
|
+
graph[left].append(right)
|
283
|
+
|
284
|
+
dfs = DFSTraversal.sort(graph)[::-1]
|
285
|
+
query: list = []
|
286
|
+
for l_tbl in dfs:
|
287
|
+
list_r_tbl = graph[l_tbl]
|
288
|
+
if not list_r_tbl:
|
289
|
+
continue
|
290
|
+
|
291
|
+
for r_tbl in list_r_tbl:
|
292
|
+
lambda_relationship = ForeignKey.MAPPED[l_tbl.__table_name__].referenced_tables[r_tbl.__table_name__].relationship
|
293
|
+
|
294
|
+
join = JoinSelector(l_tbl, r_tbl, by=self._by, where=lambda_relationship)
|
295
|
+
query.append(join.query)
|
296
|
+
|
297
|
+
return f"{sep}".join(query)
|
298
|
+
|
299
|
+
def __add_fk_relationship(self, t1: Table, t2: Table) -> None:
|
300
|
+
self._fk_relationship.add((t1, t2))
|
301
|
+
return None
|
@@ -0,0 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import typing as tp
|
3
|
+
|
4
|
+
if tp.TYPE_CHECKING:
|
5
|
+
from ormlambda import Table
|
6
|
+
from .IQueryCommand import IQuery
|
7
|
+
from .IDecompositionQuery import IDecompositionQuery
|
8
|
+
|
9
|
+
|
10
|
+
class IAggregate[T: tp.Type[Table]](IDecompositionQuery[T], IQuery): ...
|
@@ -0,0 +1,56 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import abc
|
3
|
+
import typing as tp
|
4
|
+
|
5
|
+
|
6
|
+
if tp.TYPE_CHECKING:
|
7
|
+
from ormlambda import Table
|
8
|
+
|
9
|
+
# TODOH: Changed to avoid mysql dependency
|
10
|
+
from ormlambda.common.abstract_classes.decomposition_query import ClauseInfo
|
11
|
+
|
12
|
+
from .IQueryCommand import IQuery
|
13
|
+
|
14
|
+
|
15
|
+
class IDecompositionQuery[T: tp.Type[Table]](IQuery):
|
16
|
+
@property
|
17
|
+
@abc.abstractmethod
|
18
|
+
def table(self) -> T: ...
|
19
|
+
|
20
|
+
@property
|
21
|
+
@abc.abstractmethod
|
22
|
+
def lambda_query[*Ts](self) -> tp.Callable[[T], tuple[*Ts]]: ...
|
23
|
+
|
24
|
+
@property
|
25
|
+
@abc.abstractmethod
|
26
|
+
def all_clauses(self) -> list[ClauseInfo]: ...
|
27
|
+
|
28
|
+
@property
|
29
|
+
@abc.abstractmethod
|
30
|
+
def clauses_group_by_tables(self) -> dict[tp.Type[Table], list[ClauseInfo[T]]]: ...
|
31
|
+
|
32
|
+
@property
|
33
|
+
@abc.abstractmethod
|
34
|
+
def fk_relationship(self) -> tuple[tuple[tp.Type[Table], tp.Type[Table]]]: ...
|
35
|
+
|
36
|
+
@property
|
37
|
+
@abc.abstractmethod
|
38
|
+
def query(self) -> str: ...
|
39
|
+
|
40
|
+
@property
|
41
|
+
@abc.abstractmethod
|
42
|
+
def alias(self) -> bool: ...
|
43
|
+
|
44
|
+
@property
|
45
|
+
@abc.abstractmethod
|
46
|
+
def alias_name(self) -> tp.Optional[str]: ...
|
47
|
+
|
48
|
+
@property
|
49
|
+
@abc.abstractmethod
|
50
|
+
def has_foreign_keys(self) -> bool: ...
|
51
|
+
|
52
|
+
@abc.abstractmethod
|
53
|
+
def stringify_foreign_key(self, sep: str = "\n"): ...
|
54
|
+
|
55
|
+
@abc.abstractmethod
|
56
|
+
def alias_children_resolver(self) -> tp.Callable[[tp.Type[Table], str], str]: ...
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
-
from typing import
|
2
|
+
from typing import Literal, Optional, Type
|
3
3
|
|
4
4
|
TypeExists = Literal["fail", "replace", "append"]
|
5
5
|
|
@@ -8,15 +8,6 @@ class IRepositoryBase[T](ABC):
|
|
8
8
|
def __repr__(self) -> str:
|
9
9
|
return f"{IRepositoryBase.__name__}: {self.__class__.__name__}"
|
10
10
|
|
11
|
-
@abstractmethod
|
12
|
-
def is_connected(self) -> bool: ...
|
13
|
-
|
14
|
-
@abstractmethod
|
15
|
-
def connect(self, **kwargs: Any) -> None: ...
|
16
|
-
|
17
|
-
@abstractmethod
|
18
|
-
def close_connection(self) -> None: ...
|
19
|
-
|
20
11
|
@abstractmethod
|
21
12
|
def read_sql[TFlavour](self, query: str, flavour: Optional[Type[TFlavour]], **kwargs) -> tuple[TFlavour]: ...
|
22
13
|
|
@@ -91,7 +91,7 @@ class IStatements[T: Table](ABC):
|
|
91
91
|
|
92
92
|
# region count
|
93
93
|
@abstractmethod
|
94
|
-
def count(self) -> int: ...
|
94
|
+
def count(self, selection: Callable[[T], property]) -> int: ...
|
95
95
|
|
96
96
|
# endregion
|
97
97
|
|
@@ -235,8 +235,14 @@ class IStatements[T: Table](ABC):
|
|
235
235
|
|
236
236
|
# endregion
|
237
237
|
|
238
|
+
# region group_by
|
238
239
|
@abstractmethod
|
239
|
-
def
|
240
|
+
def group_by[TRepo, *Ts](self, column: Callable[[T], TRepo], select_query: Callable[[T], tuple[*Ts]]) -> tuple[tuple[*Ts]]: ...
|
241
|
+
|
242
|
+
# endregion
|
243
|
+
|
244
|
+
@abstractmethod
|
245
|
+
def _build(self) -> str: ...
|
240
246
|
|
241
247
|
|
242
248
|
class IStatements_two_generic[T: Table, TRepo](IStatements[T]):
|
@@ -2,3 +2,5 @@ from .IQueryCommand import IQuery # noqa: F401
|
|
2
2
|
from .INonQueryCommand import INonQueryCommand # noqa: F401
|
3
3
|
from .IRepositoryBase import IRepositoryBase # noqa: F401
|
4
4
|
from .IStatements import IStatements, IStatements_two_generic # noqa: F401
|
5
|
+
from .IDecompositionQuery import IDecompositionQuery # noqa: F401
|
6
|
+
from .IAggregate import IAggregate # noqa: F401
|
@@ -1,14 +1,16 @@
|
|
1
|
-
from .create_database import CreateDatabase
|
2
|
-
from .
|
3
|
-
from .
|
4
|
-
from .
|
5
|
-
from .
|
6
|
-
from .
|
7
|
-
from .
|
8
|
-
from .
|
9
|
-
from .
|
10
|
-
from .
|
11
|
-
from .
|
12
|
-
from .
|
13
|
-
from .
|
14
|
-
from .
|
1
|
+
from .create_database import CreateDatabase as CreateDatabase
|
2
|
+
from .create_database import TypeExists as TypeExists
|
3
|
+
from .delete import DeleteQuery as DeleteQuery
|
4
|
+
from .drop_database import DropDatabase as DropDatabase
|
5
|
+
from .drop_table import DropTable as DropTable
|
6
|
+
from .insert import InsertQuery as InsertQuery
|
7
|
+
from .joins import JoinSelector as JoinSelector
|
8
|
+
from .joins import JoinType as JoinType
|
9
|
+
from .limit import LimitQuery as LimitQuery
|
10
|
+
from .offset import OffsetQuery as OffsetQuery
|
11
|
+
from .order import OrderQuery as OrderQuery
|
12
|
+
from .update import UpdateQuery as UpdateQuery
|
13
|
+
from .upsert import UpsertQuery as UpsertQuery
|
14
|
+
from .where_condition import WhereCondition as WhereCondition
|
15
|
+
from .count import Count as Count
|
16
|
+
from ..functions.group_by import GroupBy as GroupBy
|
@@ -1,35 +1,39 @@
|
|
1
|
-
|
1
|
+
import typing as tp
|
2
2
|
|
3
|
-
|
4
|
-
from ormlambda
|
5
|
-
from ormlambda.
|
3
|
+
if tp.TYPE_CHECKING:
|
4
|
+
from ormlambda import Table
|
5
|
+
from ormlambda.common.interfaces import IAggregate
|
6
|
+
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase, ClauseInfo
|
7
|
+
from ormlambda import JoinType
|
6
8
|
|
7
9
|
|
8
|
-
class
|
9
|
-
|
10
|
+
class Count[T: tp.Type[Table]](DecompositionQueryBase[T], IAggregate[T]):
|
11
|
+
NAME: str = "COUNT"
|
10
12
|
|
11
13
|
def __init__(
|
12
14
|
self,
|
13
|
-
|
14
|
-
|
15
|
+
table: T,
|
16
|
+
lambda_query: str | tp.Callable[[T], tuple],
|
15
17
|
*,
|
18
|
+
alias: bool = True,
|
19
|
+
alias_name: str = "count",
|
16
20
|
by: JoinType = JoinType.INNER_JOIN,
|
17
21
|
) -> None:
|
18
|
-
super().__init__(
|
22
|
+
super().__init__(
|
23
|
+
table,
|
24
|
+
lambda_query=lambda_query,
|
25
|
+
alias=alias,
|
26
|
+
alias_name=alias_name,
|
27
|
+
by=by,
|
28
|
+
replace_asterisk_char=False,
|
29
|
+
)
|
30
|
+
|
31
|
+
def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
|
32
|
+
if isinstance(clause_info._row_column, IAggregate):
|
33
|
+
return clause_info._row_column.alias
|
34
|
+
return None
|
19
35
|
|
20
|
-
@override
|
21
36
|
@property
|
22
37
|
def query(self) -> str:
|
23
|
-
|
24
|
-
|
25
|
-
involved_tables = self.get_involved_tables()
|
26
|
-
if not involved_tables:
|
27
|
-
return query
|
28
|
-
|
29
|
-
sub_query: str = ""
|
30
|
-
for l_tbl, r_tbl in involved_tables:
|
31
|
-
join = JoinSelector(l_tbl, r_tbl, by=self._by, where=ForeignKey.MAPPED[l_tbl.__table_name__].referenced_tables[r_tbl.__table_name__].relationship)
|
32
|
-
sub_query += f" {join.query}"
|
33
|
-
|
34
|
-
query += sub_query
|
35
|
-
return query
|
38
|
+
col = ", ".join([x.query for x in self.all_clauses])
|
39
|
+
return f"{self.NAME}({col})"
|