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.
@@ -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.components.select import TableColumn
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
- class ClusterQuery:
59
- def __init__(self, select: ISelect, response_sql: tuple[dict[str, Any]]) -> None:
60
- self._select: ISelect = select
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
- unic_table: dict[Table, list[TableColumn]] = defaultdict(list)
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 col in table_col:
75
- valid_attr[col.real_column] = dicc_cols[col.alias]
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[table_].append(table_(**valid_attr))
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 Any, Literal, Optional, Type
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 build(self) -> str: ...
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, TypeExists # noqa: F401
2
- from .delete import DeleteQuery # noqa: F401
3
- from .drop_database import DropDatabase # noqa: F401
4
- from .drop_table import DropTable # noqa: F401
5
- from .insert import InsertQuery # noqa: F401
6
- from .joins import JoinSelector, JoinType # noqa: F401
7
- from .limit import LimitQuery # noqa: F401
8
- from .offset import OffsetQuery # noqa: F401
9
- from .order import OrderQuery # noqa: F401
10
- from .select import SelectQuery # noqa: F401
11
- from .update import UpdateQuery # noqa: F401
12
- from .upsert import UpsertQuery # noqa: F401
13
- from .where_condition import WhereCondition # noqa: F401
14
- from .count import CountQuery # noqa: F401
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
- from typing import Callable, Type, override
1
+ import typing as tp
2
2
 
3
- from ormlambda import Table, JoinType, ForeignKey
4
- from ormlambda.databases.my_sql.clauses.joins import JoinSelector
5
- from ormlambda.databases.my_sql.clauses.select import SelectQuery
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 CountQuery[T: Type[Table]](SelectQuery[T]):
9
- CLAUSE: str = "COUNT"
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
- tables: T | tuple[T] = (),
14
- select_lambda: Callable[[T], None] | None = lambda: None,
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__(tables, select_lambda, by=by)
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
- query: str = f"{self.SELECT} {self.CLAUSE}(*) FROM {self._first_table.__table_name__}"
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})"