ormlambda 2.11.1__py3-none-any.whl → 3.7.0__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/__init__.py +11 -9
- ormlambda/caster/__init__.py +3 -0
- ormlambda/caster/base_caster.py +69 -0
- ormlambda/caster/caster.py +48 -0
- ormlambda/caster/interfaces/ICaster.py +26 -0
- ormlambda/caster/interfaces/__init__.py +1 -0
- ormlambda/common/__init__.py +1 -1
- ormlambda/common/abstract_classes/__init__.py +3 -3
- ormlambda/common/abstract_classes/decomposition_query.py +117 -315
- ormlambda/common/abstract_classes/non_query_base.py +1 -1
- ormlambda/common/enums/condition_types.py +2 -1
- ormlambda/common/enums/join_type.py +4 -1
- ormlambda/common/errors/__init__.py +15 -2
- ormlambda/common/global_checker.py +28 -0
- ormlambda/common/interfaces/ICustomAlias.py +4 -1
- ormlambda/common/interfaces/IDecompositionQuery.py +9 -34
- ormlambda/common/interfaces/IJoinSelector.py +21 -0
- ormlambda/common/interfaces/__init__.py +4 -6
- ormlambda/components/__init__.py +4 -0
- ormlambda/components/insert/abstract_insert.py +1 -1
- ormlambda/components/select/ISelect.py +17 -0
- ormlambda/components/select/__init__.py +1 -0
- ormlambda/components/update/abstract_update.py +4 -4
- ormlambda/components/upsert/abstract_upsert.py +1 -1
- ormlambda/databases/__init__.py +5 -0
- ormlambda/databases/my_sql/__init__.py +3 -1
- ormlambda/databases/my_sql/caster/__init__.py +1 -0
- ormlambda/databases/my_sql/caster/caster.py +38 -0
- ormlambda/databases/my_sql/caster/read.py +39 -0
- ormlambda/databases/my_sql/caster/types/__init__.py +8 -0
- ormlambda/databases/my_sql/caster/types/bytes.py +31 -0
- ormlambda/databases/my_sql/caster/types/datetime.py +34 -0
- ormlambda/databases/my_sql/caster/types/float.py +31 -0
- ormlambda/databases/my_sql/caster/types/int.py +31 -0
- ormlambda/databases/my_sql/caster/types/iterable.py +31 -0
- ormlambda/databases/my_sql/caster/types/none.py +30 -0
- ormlambda/databases/my_sql/caster/types/point.py +43 -0
- ormlambda/databases/my_sql/caster/types/string.py +31 -0
- ormlambda/databases/my_sql/caster/write.py +37 -0
- ormlambda/databases/my_sql/clauses/ST_AsText.py +36 -0
- ormlambda/databases/my_sql/clauses/ST_Contains.py +31 -0
- ormlambda/databases/my_sql/clauses/__init__.py +6 -4
- ormlambda/databases/my_sql/clauses/alias.py +24 -21
- ormlambda/databases/my_sql/clauses/count.py +32 -28
- ormlambda/databases/my_sql/clauses/create_database.py +3 -4
- ormlambda/databases/my_sql/clauses/delete.py +10 -10
- ormlambda/databases/my_sql/clauses/drop_database.py +3 -5
- ormlambda/databases/my_sql/clauses/drop_table.py +3 -3
- ormlambda/databases/my_sql/clauses/group_by.py +4 -7
- ormlambda/databases/my_sql/clauses/insert.py +33 -19
- ormlambda/databases/my_sql/clauses/joins.py +66 -59
- ormlambda/databases/my_sql/clauses/limit.py +1 -1
- ormlambda/databases/my_sql/clauses/offset.py +1 -1
- ormlambda/databases/my_sql/clauses/order.py +36 -23
- ormlambda/databases/my_sql/clauses/select.py +25 -36
- ormlambda/databases/my_sql/clauses/update.py +38 -13
- ormlambda/databases/my_sql/clauses/upsert.py +2 -2
- ormlambda/databases/my_sql/clauses/where.py +45 -0
- ormlambda/databases/my_sql/functions/concat.py +24 -27
- ormlambda/databases/my_sql/functions/max.py +32 -28
- ormlambda/databases/my_sql/functions/min.py +32 -28
- ormlambda/databases/my_sql/functions/sum.py +32 -28
- ormlambda/databases/my_sql/join_context.py +75 -0
- ormlambda/databases/my_sql/repository/__init__.py +1 -0
- ormlambda/databases/my_sql/{repository.py → repository/repository.py} +104 -73
- ormlambda/databases/my_sql/statements.py +231 -153
- ormlambda/engine/__init__.py +0 -0
- ormlambda/engine/template.py +47 -0
- ormlambda/model/__init__.py +0 -0
- ormlambda/model/base_model.py +37 -0
- ormlambda/repository/__init__.py +2 -0
- ormlambda/repository/base_repository.py +14 -0
- ormlambda/repository/interfaces/IDatabaseConnection.py +12 -0
- ormlambda/{common → repository}/interfaces/IRepositoryBase.py +6 -5
- ormlambda/repository/interfaces/__init__.py +2 -0
- ormlambda/sql/__init__.py +3 -0
- ormlambda/sql/clause_info/__init__.py +3 -0
- ormlambda/sql/clause_info/clause_info.py +434 -0
- ormlambda/sql/clause_info/clause_info_context.py +87 -0
- ormlambda/sql/clause_info/interface/IAggregate.py +10 -0
- ormlambda/sql/clause_info/interface/__init__.py +1 -0
- ormlambda/sql/column.py +126 -0
- ormlambda/sql/comparer.py +156 -0
- ormlambda/sql/foreign_key.py +115 -0
- ormlambda/sql/interfaces/__init__.py +0 -0
- ormlambda/sql/table/__init__.py +1 -0
- ormlambda/{utils → sql/table}/fields.py +6 -5
- ormlambda/{utils → sql/table}/table_constructor.py +43 -91
- ormlambda/sql/types.py +25 -0
- ormlambda/statements/__init__.py +2 -0
- ormlambda/statements/base_statement.py +129 -0
- ormlambda/statements/interfaces/IStatements.py +309 -0
- ormlambda/statements/interfaces/__init__.py +1 -0
- ormlambda/statements/types.py +51 -0
- ormlambda/utils/__init__.py +1 -3
- ormlambda/utils/module_tree/__init__.py +1 -0
- ormlambda/utils/module_tree/dynamic_module.py +20 -14
- {ormlambda-2.11.1.dist-info → ormlambda-3.7.0.dist-info}/METADATA +132 -68
- ormlambda-3.7.0.dist-info/RECORD +117 -0
- ormlambda/common/abstract_classes/abstract_model.py +0 -115
- ormlambda/common/interfaces/IAggregate.py +0 -10
- ormlambda/common/interfaces/IStatements.py +0 -348
- ormlambda/components/where/__init__.py +0 -1
- ormlambda/components/where/abstract_where.py +0 -15
- ormlambda/databases/my_sql/clauses/where_condition.py +0 -222
- ormlambda/model_base.py +0 -36
- ormlambda/utils/column.py +0 -105
- ormlambda/utils/foreign_key.py +0 -81
- ormlambda/utils/lambda_disassembler/__init__.py +0 -4
- ormlambda/utils/lambda_disassembler/dis_types.py +0 -133
- ormlambda/utils/lambda_disassembler/disassembler.py +0 -69
- ormlambda/utils/lambda_disassembler/dtypes.py +0 -103
- ormlambda/utils/lambda_disassembler/name_of.py +0 -41
- ormlambda/utils/lambda_disassembler/nested_element.py +0 -44
- ormlambda/utils/lambda_disassembler/tree_instruction.py +0 -145
- ormlambda-2.11.1.dist-info/RECORD +0 -81
- /ormlambda/{utils → sql}/dtypes.py +0 -0
- {ormlambda-2.11.1.dist-info → ormlambda-3.7.0.dist-info}/LICENSE +0 -0
- {ormlambda-2.11.1.dist-info → ormlambda-3.7.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,434 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import abc
|
3
|
+
import typing as tp
|
4
|
+
import re
|
5
|
+
|
6
|
+
from ormlambda import Table
|
7
|
+
from ormlambda import Column
|
8
|
+
from ormlambda.common.interfaces.IQueryCommand import IQuery
|
9
|
+
from ormlambda.sql.types import (
|
10
|
+
ASTERISK,
|
11
|
+
TableType,
|
12
|
+
ColumnType,
|
13
|
+
AliasType,
|
14
|
+
)
|
15
|
+
from .interface import IAggregate
|
16
|
+
from ormlambda.common.errors import NotKeysInIAggregateError
|
17
|
+
from ormlambda.sql import ForeignKey
|
18
|
+
from ormlambda.sql.table import TableMeta
|
19
|
+
from ormlambda.caster import Caster
|
20
|
+
|
21
|
+
|
22
|
+
from .clause_info_context import ClauseInfoContext, ClauseContextType
|
23
|
+
|
24
|
+
|
25
|
+
class ReplacePlaceholderError(ValueError):
|
26
|
+
def __init__(self, placeholder: str, attribute: str, *args):
|
27
|
+
super().__init__(*args)
|
28
|
+
self.placeholder: str = placeholder
|
29
|
+
self.attr: str = attribute
|
30
|
+
|
31
|
+
def __str__(self):
|
32
|
+
return "You cannot use {" + self.placeholder + "} placeholder without using '" + self.attr + "' attribute"
|
33
|
+
|
34
|
+
|
35
|
+
class IClauseInfo[T: Table](IQuery):
|
36
|
+
@property
|
37
|
+
@abc.abstractmethod
|
38
|
+
def table(self) -> TableType[T]: ...
|
39
|
+
@property
|
40
|
+
@abc.abstractmethod
|
41
|
+
def alias_clause(self) -> tp.Optional[str]: ...
|
42
|
+
@property
|
43
|
+
@abc.abstractmethod
|
44
|
+
def alias_table(self) -> tp.Optional[str]: ...
|
45
|
+
@property
|
46
|
+
@abc.abstractmethod
|
47
|
+
def column(self) -> str: ...
|
48
|
+
@property
|
49
|
+
@abc.abstractmethod
|
50
|
+
def unresolved_column(self) -> ColumnType: ...
|
51
|
+
@property
|
52
|
+
@abc.abstractmethod
|
53
|
+
def context(self) -> ClauseContextType: ...
|
54
|
+
@property
|
55
|
+
@abc.abstractmethod
|
56
|
+
def dtype[TProp](self) -> tp.Optional[tp.Type[TProp]]: ...
|
57
|
+
|
58
|
+
|
59
|
+
class ClauseInfo[T: Table](IClauseInfo[T]):
|
60
|
+
_keyRegex: re.Pattern = re.compile(r"{([^{}:]+)}")
|
61
|
+
|
62
|
+
@tp.overload
|
63
|
+
def __init__(self, table: TableType[T]): ...
|
64
|
+
@tp.overload
|
65
|
+
def __init__[TProp](self, table: TableType[T], column: ColumnType[TProp]): ...
|
66
|
+
@tp.overload
|
67
|
+
def __init__[TProp](self, table: TableType[T], column: ColumnType[TProp], alias_table: AliasType[ClauseInfo[T]] = ..., alias_clause: AliasType[ClauseInfo[T]] = ...): ...
|
68
|
+
@tp.overload
|
69
|
+
def __init__(self, table: TableType[T], alias_table: AliasType[ClauseInfo[T]] = ..., alias_clause: AliasType[ClauseInfo[T]] = ...): ...
|
70
|
+
@tp.overload
|
71
|
+
def __init__[TProp](self, table: TableType[T], column: ColumnType[TProp], context: ClauseContextType): ...
|
72
|
+
@tp.overload
|
73
|
+
def __init__(self, table: TableType[T], keep_asterisk: tp.Optional[bool] = ...): ...
|
74
|
+
@tp.overload
|
75
|
+
def __init__(self, table: TableType[T], preserve_context: tp.Optional[bool] = ...): ...
|
76
|
+
|
77
|
+
def __init__[TProp](
|
78
|
+
self,
|
79
|
+
table: TableType[T],
|
80
|
+
column: tp.Optional[ColumnType[TProp]] = None,
|
81
|
+
alias_table: tp.Optional[AliasType[ClauseInfo[T]]] = None,
|
82
|
+
alias_clause: tp.Optional[AliasType[ClauseInfo[T]]] = None,
|
83
|
+
context: ClauseContextType = None,
|
84
|
+
keep_asterisk: bool = False,
|
85
|
+
preserve_context: bool = False,
|
86
|
+
):
|
87
|
+
if not self.is_table(table):
|
88
|
+
column = table if not column else column
|
89
|
+
table = self.extract_table(table)
|
90
|
+
|
91
|
+
self._table: TableType[T] = table
|
92
|
+
self._column: TableType[T] | ColumnType[TProp] = column
|
93
|
+
self._alias_table: tp.Optional[AliasType[ClauseInfo[T]]] = alias_table
|
94
|
+
self._alias_clause: tp.Optional[AliasType[ClauseInfo[T]]] = alias_clause
|
95
|
+
self._context: ClauseContextType = context if context else ClauseInfoContext()
|
96
|
+
self._keep_asterisk: bool = keep_asterisk
|
97
|
+
self._preserve_context: bool = preserve_context
|
98
|
+
|
99
|
+
self._placeholderValues: dict[str, tp.Callable[[TProp], str]] = {
|
100
|
+
"column": self.replace_column_placeholder,
|
101
|
+
"table": self.replace_table_placeholder,
|
102
|
+
}
|
103
|
+
|
104
|
+
if not self._preserve_context and (self._context and any([alias_table, alias_clause])):
|
105
|
+
self._context.add_clause_to_context(self)
|
106
|
+
|
107
|
+
def __repr__(self) -> str:
|
108
|
+
return f"{type(self).__name__}: query -> {self.query}"
|
109
|
+
|
110
|
+
def replace_column_placeholder[TProp](self, column: ColumnType[TProp]) -> str:
|
111
|
+
if not column:
|
112
|
+
raise ReplacePlaceholderError("column", "column")
|
113
|
+
return self._column_resolver(column)
|
114
|
+
|
115
|
+
def replace_table_placeholder[TProp](self, _: ColumnType[TProp]) -> str:
|
116
|
+
if not self.table:
|
117
|
+
raise ReplacePlaceholderError("table", "table")
|
118
|
+
return self.table.__table_name__
|
119
|
+
|
120
|
+
@property
|
121
|
+
def table(self) -> TableType[T]:
|
122
|
+
if self.is_foreign_key(self._table):
|
123
|
+
return self._table.tright
|
124
|
+
return self._table
|
125
|
+
|
126
|
+
@table.setter
|
127
|
+
def table(self, value: TableType[T]) -> None:
|
128
|
+
self._table = value
|
129
|
+
|
130
|
+
@property
|
131
|
+
def alias_clause(self) -> tp.Optional[str]:
|
132
|
+
alias = self._alias_clause if not (a := self.get_clause_alias()) else a
|
133
|
+
return self._alias_resolver(alias)
|
134
|
+
|
135
|
+
# TODOL [ ]: if we using this setter, we don't update the _context with the new value. Study if it's necessary
|
136
|
+
@alias_clause.setter
|
137
|
+
def alias_clause(self, value: str) -> str:
|
138
|
+
self._alias_clause = value
|
139
|
+
|
140
|
+
@property
|
141
|
+
def alias_table(self) -> tp.Optional[str]:
|
142
|
+
alias = self._alias_table if not (a := self.get_table_alias()) else a
|
143
|
+
return self._alias_resolver(alias)
|
144
|
+
|
145
|
+
# TODOL [ ]: if we using this setter, we don't update the _context with the new value. Study if it's necessary
|
146
|
+
@alias_table.setter
|
147
|
+
def alias_table(self, value: str) -> str:
|
148
|
+
self._alias_table = value
|
149
|
+
|
150
|
+
def replaced_alias(self, value: str) -> tp.Optional[str]:
|
151
|
+
return value
|
152
|
+
|
153
|
+
@property
|
154
|
+
def column(self) -> str:
|
155
|
+
return self._column_resolver(self._column)
|
156
|
+
|
157
|
+
@property
|
158
|
+
def unresolved_column(self) -> ColumnType:
|
159
|
+
return self._column
|
160
|
+
|
161
|
+
@property
|
162
|
+
def context(self) -> ClauseContextType:
|
163
|
+
return self._context
|
164
|
+
|
165
|
+
@context.setter
|
166
|
+
def context(self, value: ClauseInfoContext) -> None:
|
167
|
+
self._context = value
|
168
|
+
|
169
|
+
@property
|
170
|
+
def dtype[TProp](self) -> tp.Optional[tp.Type[TProp]]:
|
171
|
+
if isinstance(self._column, Column):
|
172
|
+
return self._column.dtype
|
173
|
+
|
174
|
+
if isinstance(self._column, type):
|
175
|
+
return self._column
|
176
|
+
return type(self._column)
|
177
|
+
|
178
|
+
@property
|
179
|
+
def query(self) -> str:
|
180
|
+
return self._create_query()
|
181
|
+
|
182
|
+
def _create_query(self) -> str:
|
183
|
+
# when passing some value that is not a column name
|
184
|
+
if not self.table and not self._alias_clause:
|
185
|
+
return self.column
|
186
|
+
|
187
|
+
if not self.table and self._alias_clause:
|
188
|
+
# it means that we are passing an object with alias. We should delete '' around the object
|
189
|
+
alias_clause = self.alias_clause
|
190
|
+
return self._concat_alias_and_column(self._column, alias_clause)
|
191
|
+
|
192
|
+
# When passing the Table itself without 'column'
|
193
|
+
if self.table and not self._column:
|
194
|
+
if not self._alias_table:
|
195
|
+
return self.table.__table_name__
|
196
|
+
alias_table = self.alias_table
|
197
|
+
return self._concat_alias_and_column(self.table.__table_name__, alias_table)
|
198
|
+
|
199
|
+
if self._return_all_columns():
|
200
|
+
return self._get_all_columns()
|
201
|
+
return self._join_table_and_column(self._column)
|
202
|
+
|
203
|
+
def _join_table_and_column[TProp](self, column: ColumnType[TProp]) -> str:
|
204
|
+
# FIXME [ ]: Study how to deacoplate from mysql database
|
205
|
+
from ormlambda.databases.my_sql.repository import MySQLRepository
|
206
|
+
|
207
|
+
caster = Caster(MySQLRepository)
|
208
|
+
|
209
|
+
if self.alias_table:
|
210
|
+
table = self._wrapped_with_quotes(self.alias_table)
|
211
|
+
else:
|
212
|
+
table = self.table.__table_name__
|
213
|
+
|
214
|
+
column: str = self._column_resolver(column)
|
215
|
+
|
216
|
+
table_column = f"{table}.{column}"
|
217
|
+
|
218
|
+
dtype = str if self.is_table(self.dtype) else self.dtype
|
219
|
+
wrapped_column = caster.for_value(table_column, dtype).wildcard_to_select(table_column)
|
220
|
+
return self._concat_alias_and_column(wrapped_column, self.alias_clause)
|
221
|
+
|
222
|
+
def _return_all_columns(self) -> bool:
|
223
|
+
if self._keep_asterisk:
|
224
|
+
return False
|
225
|
+
if self.is_foreign_key(self._column) or self.is_table(self._column):
|
226
|
+
return True
|
227
|
+
|
228
|
+
C1 = self._column is self.table and self.is_table(self._column)
|
229
|
+
return any([self.is_asterisk(self._column), C1])
|
230
|
+
|
231
|
+
@staticmethod
|
232
|
+
def is_asterisk(value: tp.Optional[str]) -> bool:
|
233
|
+
return isinstance(value, str) and value == ASTERISK
|
234
|
+
|
235
|
+
def _get_all_columns(self) -> str:
|
236
|
+
def ClauseCreator(column: str) -> ClauseInfo:
|
237
|
+
return ClauseInfo(
|
238
|
+
table=self.table,
|
239
|
+
column=column,
|
240
|
+
alias_table=self._alias_table,
|
241
|
+
alias_clause=self._alias_clause,
|
242
|
+
context=self._context,
|
243
|
+
keep_asterisk=self._keep_asterisk,
|
244
|
+
)
|
245
|
+
|
246
|
+
if self._alias_table and self._alias_clause: # We'll add an "*" when we are certain that we have included 'alias_clause' attr
|
247
|
+
return self._join_table_and_column(ASTERISK)
|
248
|
+
|
249
|
+
columns: list[ClauseInfo] = [ClauseCreator(column).query for column in self.table.get_columns()]
|
250
|
+
|
251
|
+
return ", ".join(columns)
|
252
|
+
|
253
|
+
# FIXME [ ]: Study how to deacoplate from mysql database
|
254
|
+
def _column_resolver[TProp](self, column: ColumnType[TProp]) -> str:
|
255
|
+
from ormlambda.databases.my_sql.repository import MySQLRepository
|
256
|
+
|
257
|
+
caster = Caster(MySQLRepository)
|
258
|
+
if isinstance(column, ClauseInfo):
|
259
|
+
return column.query
|
260
|
+
|
261
|
+
if isinstance(column, tp.Iterable) and isinstance(column[0], ClauseInfo):
|
262
|
+
return self.join_clauses(column)
|
263
|
+
|
264
|
+
if isinstance(column, Column):
|
265
|
+
return column.column_name
|
266
|
+
|
267
|
+
# if we want to pass the name of a column as a string, the 'table' var must not be None
|
268
|
+
if self.table and isinstance(self._column, str):
|
269
|
+
return self._column
|
270
|
+
|
271
|
+
if self.is_asterisk(column):
|
272
|
+
return ASTERISK
|
273
|
+
|
274
|
+
if self.is_table(self._column):
|
275
|
+
return self._column.__table_name__
|
276
|
+
|
277
|
+
if self.is_foreign_key(self._column):
|
278
|
+
return self._column.tright.__table_name__
|
279
|
+
|
280
|
+
casted_value = caster.for_value(column, self.dtype)
|
281
|
+
if not self._table:
|
282
|
+
# if we haven't some table atrribute, we assume that the user want to retrieve the string_data from caster.
|
283
|
+
return casted_value.string_data
|
284
|
+
return casted_value.wildcard_to_select()
|
285
|
+
|
286
|
+
def _replace_placeholder(self, string: str) -> str:
|
287
|
+
return self._keyRegex.sub(self._replace, string)
|
288
|
+
|
289
|
+
def _replace(self, match: re.Match[str]) -> str:
|
290
|
+
key = match.group(1)
|
291
|
+
|
292
|
+
if not (func := self._placeholderValues.get(key, None)):
|
293
|
+
return match.group(0) # No placeholder / value
|
294
|
+
|
295
|
+
return func(self._column)
|
296
|
+
|
297
|
+
def _concat_alias_and_column(self, column: str, alias_clause: tp.Optional[str] = None) -> str:
|
298
|
+
if alias_clause is None:
|
299
|
+
return column
|
300
|
+
alias = f"{column} AS {self._wrapped_with_quotes(alias_clause)}"
|
301
|
+
return alias
|
302
|
+
|
303
|
+
def _alias_resolver(self, alias: AliasType[ClauseInfo[T]]) -> tp.Optional[str]:
|
304
|
+
if alias is None:
|
305
|
+
return None
|
306
|
+
|
307
|
+
if callable(alias):
|
308
|
+
return self._alias_resolver(alias(self))
|
309
|
+
|
310
|
+
return self._replace_placeholder(alias)
|
311
|
+
|
312
|
+
def get_clause_alias(self) -> tp.Optional[str]:
|
313
|
+
if not self._context:
|
314
|
+
return None
|
315
|
+
return self._context.get_clause_alias(self)
|
316
|
+
|
317
|
+
def get_table_alias(self) -> tp.Optional[str]:
|
318
|
+
if not self._context:
|
319
|
+
return None
|
320
|
+
return self._context.get_table_alias(self.table)
|
321
|
+
|
322
|
+
@staticmethod
|
323
|
+
def join_clauses(clauses: list[ClauseInfo[T]], chr: str = ",", context: tp.Optional[ClauseInfoContext] = None) -> str:
|
324
|
+
queries: list[str] = []
|
325
|
+
for c in clauses:
|
326
|
+
if context:
|
327
|
+
c.context = context
|
328
|
+
queries.append(c.query)
|
329
|
+
|
330
|
+
return f"{chr} ".join(queries)
|
331
|
+
|
332
|
+
@staticmethod
|
333
|
+
def _wrapped_with_quotes(string: str) -> str:
|
334
|
+
return f"`{string}`"
|
335
|
+
|
336
|
+
@classmethod
|
337
|
+
def extract_table(cls, element: ColumnType[T] | TableType[T]) -> tp.Optional[T]:
|
338
|
+
if element is None:
|
339
|
+
return None
|
340
|
+
|
341
|
+
if cls.is_table(element):
|
342
|
+
return element
|
343
|
+
|
344
|
+
if cls.is_foreign_key(element):
|
345
|
+
return element.tright
|
346
|
+
|
347
|
+
if isinstance(element, Column):
|
348
|
+
return element.table
|
349
|
+
return None
|
350
|
+
|
351
|
+
@staticmethod
|
352
|
+
def is_table(data: ColumnType | Table | ForeignKey) -> bool:
|
353
|
+
return isinstance(data, type) and issubclass(data, Table)
|
354
|
+
|
355
|
+
@staticmethod
|
356
|
+
def is_foreign_key(data: ColumnType | Table | ForeignKey) -> bool:
|
357
|
+
return isinstance(data, ForeignKey)
|
358
|
+
|
359
|
+
@classmethod
|
360
|
+
def is_column(cls, data: tp.Any) -> bool:
|
361
|
+
if cls.is_table(data) or cls.is_foreign_key(data) or cls.is_asterisk(data):
|
362
|
+
return False
|
363
|
+
if isinstance(data, Column):
|
364
|
+
return True
|
365
|
+
return False
|
366
|
+
|
367
|
+
|
368
|
+
class AggregateFunctionBase[T: Table](ClauseInfo[T], IAggregate):
|
369
|
+
def __init__[TProp: Column](
|
370
|
+
self,
|
371
|
+
table: TableType[T],
|
372
|
+
column: tp.Optional[ColumnType[TProp]] = None,
|
373
|
+
alias_table: tp.Optional[AliasType[ClauseInfo[T]]] = None,
|
374
|
+
alias_clause: tp.Optional[AliasType[ClauseInfo[T]]] = None,
|
375
|
+
context: ClauseContextType = None,
|
376
|
+
keep_asterisk: bool = False,
|
377
|
+
preserve_context: bool = False,
|
378
|
+
):
|
379
|
+
self._alias_aggregate = alias_clause
|
380
|
+
super().__init__(
|
381
|
+
table=table,
|
382
|
+
column=column,
|
383
|
+
alias_table=alias_table,
|
384
|
+
context=context,
|
385
|
+
keep_asterisk=keep_asterisk,
|
386
|
+
preserve_context=preserve_context,
|
387
|
+
)
|
388
|
+
|
389
|
+
@staticmethod
|
390
|
+
@abc.abstractmethod
|
391
|
+
def FUNCTION_NAME() -> str: ...
|
392
|
+
|
393
|
+
@classmethod
|
394
|
+
def _convert_into_clauseInfo[TypeColumns, TProp](cls, columns: ClauseInfo | ColumnType[TProp], context: ClauseContextType) -> list[ClauseInfo]:
|
395
|
+
type DEFAULT = tp.Literal["default"]
|
396
|
+
type ClusterType = ColumnType | ForeignKey | DEFAULT
|
397
|
+
|
398
|
+
dicc_type: dict[ClusterType, tp.Callable[[ClusterType], ClauseInfo]] = {
|
399
|
+
Column: lambda column: ClauseInfo(column.table, column, context=context),
|
400
|
+
ClauseInfo: lambda column: column,
|
401
|
+
ForeignKey: lambda tbl: ClauseInfo(tbl.tright, tbl.tright, context=context),
|
402
|
+
TableMeta: lambda tbl: ClauseInfo(tbl, tbl, context=context),
|
403
|
+
"default": lambda column: ClauseInfo(table=None, column=column, context=context),
|
404
|
+
}
|
405
|
+
all_clauses: list[ClauseInfo] = []
|
406
|
+
if isinstance(columns, str) or not isinstance(columns, tp.Iterable):
|
407
|
+
columns = (columns,)
|
408
|
+
for value in columns:
|
409
|
+
all_clauses.append(dicc_type.get(type(value), dicc_type["default"])(value))
|
410
|
+
|
411
|
+
return all_clauses
|
412
|
+
|
413
|
+
@tp.override
|
414
|
+
@property
|
415
|
+
def query(self) -> str:
|
416
|
+
wrapped_ci = self.wrapped_clause_info(self)
|
417
|
+
if not self._alias_aggregate:
|
418
|
+
return wrapped_ci
|
419
|
+
|
420
|
+
return ClauseInfo(
|
421
|
+
table=None,
|
422
|
+
column=wrapped_ci,
|
423
|
+
alias_clause=self._alias_aggregate,
|
424
|
+
context=self._context,
|
425
|
+
keep_asterisk=self._keep_asterisk,
|
426
|
+
preserve_context=self._preserve_context,
|
427
|
+
).query
|
428
|
+
|
429
|
+
def wrapped_clause_info(self, ci: ClauseInfo[T]) -> str:
|
430
|
+
# avoid use placeholder when using IAggregate because no make sense.
|
431
|
+
if self._alias_aggregate and (found := self._keyRegex.findall(self._alias_aggregate)):
|
432
|
+
raise NotKeysInIAggregateError(found)
|
433
|
+
|
434
|
+
return f"{self.FUNCTION_NAME()}({ci._create_query()})"
|
@@ -0,0 +1,87 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import abc
|
3
|
+
from typing import Literal, Optional, TYPE_CHECKING, overload
|
4
|
+
|
5
|
+
from ormlambda import Table
|
6
|
+
from ormlambda.sql.types import (
|
7
|
+
TableType,
|
8
|
+
)
|
9
|
+
|
10
|
+
from ormlambda import Column
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from .clause_info import ClauseInfo
|
14
|
+
|
15
|
+
|
16
|
+
class IClauseInfo(abc.ABC): ...
|
17
|
+
|
18
|
+
|
19
|
+
AliasChoiceType = Literal["TABLE", "CLAUSE"]
|
20
|
+
|
21
|
+
type ClauseAliasKey[T: Table, TProp] = tuple[TableType[T], Column[TProp]]
|
22
|
+
type TableAliasKey[T: Table] = T
|
23
|
+
|
24
|
+
type AliasKey[T: Table, TProp] = ClauseAliasKey[T, TProp] | TableAliasKey[T]
|
25
|
+
|
26
|
+
type ClauseContextType = Optional[ClauseInfoContext]
|
27
|
+
|
28
|
+
|
29
|
+
class ClauseInfoContext(IClauseInfo):
|
30
|
+
@overload
|
31
|
+
def __init__(self) -> None: ...
|
32
|
+
@overload
|
33
|
+
def __init__[T: Table, TProp](self, clause_context: dict[ClauseAliasKey[T, TProp], str]) -> None: ...
|
34
|
+
@overload
|
35
|
+
def __init__[T: Table](self, table_context: dict[TableAliasKey[T], str]) -> None: ...
|
36
|
+
|
37
|
+
def __init__[T: Table, TProp](
|
38
|
+
self,
|
39
|
+
clause_context: Optional[dict[ClauseAliasKey[T, TProp], str]] = None,
|
40
|
+
table_context: Optional[dict[TableAliasKey[T], str]] = None,
|
41
|
+
) -> None:
|
42
|
+
self._clause_context: dict[AliasKey[T, TProp], str] = clause_context if clause_context else {}
|
43
|
+
self._table_context: dict[AliasKey[T, TProp], str] = table_context if table_context else {}
|
44
|
+
|
45
|
+
def add_clause_to_context[T: Table](self, clause: ClauseInfo[T]) -> None:
|
46
|
+
if not clause:
|
47
|
+
return None
|
48
|
+
|
49
|
+
if t := clause.table:
|
50
|
+
self._add_table_alias(t, clause._alias_table)
|
51
|
+
if c := clause.column:
|
52
|
+
self._add_clause_alias((t, c, type(clause)), clause._alias_clause)
|
53
|
+
|
54
|
+
return None
|
55
|
+
|
56
|
+
def _add_clause_alias[T: Table, TProp](self, key: AliasKey[T, TProp], alias: str) -> None:
|
57
|
+
if not all([key, alias]):
|
58
|
+
return None
|
59
|
+
|
60
|
+
self._clause_context[key] = alias
|
61
|
+
|
62
|
+
return None
|
63
|
+
|
64
|
+
def _add_table_alias[T: Table, TProp](self, key: AliasKey[T, TProp], alias: str) -> None:
|
65
|
+
if not all([key, alias]):
|
66
|
+
return None
|
67
|
+
|
68
|
+
self._table_context[key] = alias
|
69
|
+
|
70
|
+
return None
|
71
|
+
|
72
|
+
def get_clause_alias[T: Table, TProp](self, clause: ClauseInfo[T]) -> Optional[str]:
|
73
|
+
table_col: ClauseAliasKey[T, TProp] = (clause.table, clause.column, type(clause))
|
74
|
+
return self._clause_context.get(table_col, None)
|
75
|
+
|
76
|
+
def get_table_alias[T: Table](self, table: T) -> Optional[str]:
|
77
|
+
return self._table_context.get(table, None)
|
78
|
+
|
79
|
+
def update(self, context: ClauseInfoContext) -> None:
|
80
|
+
if not context:
|
81
|
+
return None
|
82
|
+
if not isinstance(context, ClauseInfoContext):
|
83
|
+
raise ValueError(f"A '{ClauseInfoContext.__name__}' type was expected")
|
84
|
+
|
85
|
+
self._table_context.update(context._table_context)
|
86
|
+
self._clause_context.update(context._clause_context)
|
87
|
+
return None
|
@@ -0,0 +1 @@
|
|
1
|
+
from .IAggregate import IAggregate # noqa: F401
|
ormlambda/sql/column.py
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Iterable, Type, Optional, TYPE_CHECKING
|
3
|
+
import abc
|
4
|
+
from ormlambda.sql.types import TableType, ComparerType, ColumnType
|
5
|
+
from ormlambda import ConditionType
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
import re
|
9
|
+
from ormlambda import Table
|
10
|
+
from ormlambda.sql.comparer import Comparer, Regex, Like
|
11
|
+
|
12
|
+
|
13
|
+
class Column[TProp]:
|
14
|
+
PRIVATE_CHAR: str = "_"
|
15
|
+
|
16
|
+
__slots__ = (
|
17
|
+
"dtype",
|
18
|
+
"column_name",
|
19
|
+
"table",
|
20
|
+
"is_primary_key",
|
21
|
+
"is_auto_generated",
|
22
|
+
"is_auto_increment",
|
23
|
+
"is_unique",
|
24
|
+
"__private_name",
|
25
|
+
"_check",
|
26
|
+
)
|
27
|
+
|
28
|
+
def __init__[T: Table](
|
29
|
+
self,
|
30
|
+
dtype: Type[TProp],
|
31
|
+
is_primary_key: bool = False,
|
32
|
+
is_auto_generated: bool = False,
|
33
|
+
is_auto_increment: bool = False,
|
34
|
+
is_unique: bool = False,
|
35
|
+
check_types: bool = True,
|
36
|
+
) -> None:
|
37
|
+
self.dtype: Type[TProp] = dtype
|
38
|
+
self.table: Optional[TableType[T]] = None
|
39
|
+
self.column_name: Optional[str] = None
|
40
|
+
self.__private_name: Optional[str] = None
|
41
|
+
self._check = check_types
|
42
|
+
|
43
|
+
self.is_primary_key: bool = is_primary_key
|
44
|
+
self.is_auto_generated: bool = is_auto_generated
|
45
|
+
self.is_auto_increment: bool = is_auto_increment
|
46
|
+
self.is_unique: bool = is_unique
|
47
|
+
|
48
|
+
def __repr__(self) -> str:
|
49
|
+
return f"{type(self).__name__}[{self.dtype.__name__}] => {self.column_name}"
|
50
|
+
|
51
|
+
def __str__(self) -> str:
|
52
|
+
return self.table.__table_name__ + "." + self.column_name
|
53
|
+
|
54
|
+
def __set_name__[T: Table](self, owner: TableType[T], name: str) -> None:
|
55
|
+
self.table: TableType[T] = owner
|
56
|
+
self.column_name = name
|
57
|
+
self.__private_name = self.PRIVATE_CHAR + name
|
58
|
+
|
59
|
+
def __get__(self, obj, objtype=None) -> ColumnType[TProp]:
|
60
|
+
if not obj:
|
61
|
+
return self
|
62
|
+
return getattr(obj, self.__private_name)
|
63
|
+
|
64
|
+
def __set__(self, obj, value):
|
65
|
+
if self._check and value is not None:
|
66
|
+
if not isinstance(value, self.dtype):
|
67
|
+
raise ValueError(f"The '{self.column_name}' Column from '{self.table.__table_name__}' table expected '{str(self.dtype)}' type. You passed '{type(value).__name__}' type")
|
68
|
+
setattr(obj, self.__private_name, value)
|
69
|
+
|
70
|
+
def __hash__(self) -> int:
|
71
|
+
return hash(
|
72
|
+
(
|
73
|
+
self.column_name,
|
74
|
+
self.is_primary_key,
|
75
|
+
self.is_auto_generated,
|
76
|
+
self.is_auto_increment,
|
77
|
+
self.is_unique,
|
78
|
+
)
|
79
|
+
)
|
80
|
+
|
81
|
+
@abc.abstractmethod
|
82
|
+
def __comparer_creator[LTable: Table, OTherTable: Table, OTherType](self, other: ColumnType[OTherType], compare: ComparerType, *args) -> Comparer:
|
83
|
+
from ormlambda.sql.comparer import Comparer
|
84
|
+
|
85
|
+
return Comparer[LTable, TProp, OTherTable, OTherType](self, other, compare, *args)
|
86
|
+
|
87
|
+
def __eq__[LTable, OTherTable, OTherProp](self, other: ColumnType[OTherProp], *args) -> Comparer[LTable, TProp, OTherTable, OTherProp]:
|
88
|
+
return self.__comparer_creator(other, ConditionType.EQUAL.value, *args)
|
89
|
+
|
90
|
+
def __ne__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
|
91
|
+
return self.__comparer_creator(other, ConditionType.NOT_EQUAL.value, *args)
|
92
|
+
|
93
|
+
def __lt__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
|
94
|
+
return self.__comparer_creator(other, ConditionType.LESS_THAN.value, *args)
|
95
|
+
|
96
|
+
def __le__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
|
97
|
+
return self.__comparer_creator(other, ConditionType.LESS_THAN_OR_EQUAL.value, *args)
|
98
|
+
|
99
|
+
def __gt__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
|
100
|
+
return self.__comparer_creator(other, ConditionType.GREATER_THAN.value, *args)
|
101
|
+
|
102
|
+
def __ge__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
|
103
|
+
return self.__comparer_creator(other, ConditionType.GREATER_THAN_OR_EQUAL.value, *args)
|
104
|
+
|
105
|
+
def contains[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
|
106
|
+
return self.__comparer_creator(other, ConditionType.IN.value, *args)
|
107
|
+
|
108
|
+
def not_contains[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
|
109
|
+
return self.__comparer_creator(other, ConditionType.NOT_IN.value, *args)
|
110
|
+
|
111
|
+
def regex[LProp, RProp](self, pattern: str, flags: Optional[re.RegexFlag | Iterable[re.RegexFlag]] = None) -> Regex[LProp, RProp]:
|
112
|
+
from ormlambda.sql.comparer import Regex
|
113
|
+
|
114
|
+
if not isinstance(flags, Iterable):
|
115
|
+
flags = (flags,)
|
116
|
+
return Regex(
|
117
|
+
left_condition=self,
|
118
|
+
right_condition=pattern,
|
119
|
+
context=None,
|
120
|
+
flags=flags,
|
121
|
+
)
|
122
|
+
|
123
|
+
def like[LProp, RProp](self, pattern: str) -> Like[LProp, RProp]:
|
124
|
+
from ormlambda.sql.comparer import Like
|
125
|
+
|
126
|
+
return Like(self, pattern)
|