ormlambda 3.35.2__py3-none-any.whl → 4.0.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 +79 -51
- ormlambda/caster/caster.py +6 -1
- ormlambda/common/abstract_classes/__init__.py +0 -2
- ormlambda/common/enums/__init__.py +1 -0
- ormlambda/common/enums/order_type.py +9 -0
- ormlambda/common/errors/__init__.py +13 -3
- ormlambda/common/global_checker.py +86 -8
- ormlambda/common/interfaces/IQueryCommand.py +2 -2
- ormlambda/common/interfaces/__init__.py +0 -2
- ormlambda/dialects/__init__.py +75 -3
- ormlambda/dialects/default/base.py +1 -1
- ormlambda/dialects/mysql/__init__.py +35 -78
- ormlambda/dialects/mysql/base.py +226 -40
- ormlambda/dialects/mysql/clauses/ST_AsText.py +26 -0
- ormlambda/dialects/mysql/clauses/ST_Contains.py +30 -0
- ormlambda/dialects/mysql/clauses/__init__.py +1 -0
- ormlambda/dialects/mysql/repository/__init__.py +1 -0
- ormlambda/{databases/my_sql → dialects/mysql/repository}/repository.py +0 -5
- ormlambda/dialects/mysql/types.py +6 -0
- ormlambda/engine/base.py +26 -4
- ormlambda/errors.py +9 -0
- ormlambda/model/base_model.py +3 -10
- ormlambda/repository/base_repository.py +1 -1
- ormlambda/repository/interfaces/IRepositoryBase.py +0 -7
- ormlambda/repository/response.py +12 -7
- ormlambda/sql/__init__.py +12 -3
- ormlambda/sql/clause_info/__init__.py +0 -2
- ormlambda/sql/clause_info/clause_info.py +94 -76
- ormlambda/sql/clause_info/interface/IAggregate.py +14 -4
- ormlambda/sql/clause_info/interface/IClauseInfo.py +6 -11
- ormlambda/sql/clauses/alias.py +6 -37
- ormlambda/sql/clauses/count.py +21 -36
- ormlambda/sql/clauses/group_by.py +13 -19
- ormlambda/sql/clauses/having.py +2 -6
- ormlambda/sql/clauses/insert.py +3 -3
- ormlambda/sql/clauses/interfaces/__init__.py +0 -1
- ormlambda/sql/clauses/join/join_context.py +5 -12
- ormlambda/sql/clauses/joins.py +34 -52
- ormlambda/sql/clauses/limit.py +1 -2
- ormlambda/sql/clauses/offset.py +1 -2
- ormlambda/sql/clauses/order.py +17 -21
- ormlambda/sql/clauses/select.py +56 -28
- ormlambda/sql/clauses/update.py +13 -10
- ormlambda/sql/clauses/where.py +20 -39
- ormlambda/sql/column/__init__.py +1 -0
- ormlambda/sql/column/column.py +19 -12
- ormlambda/sql/column/column_proxy.py +117 -0
- ormlambda/sql/column_table_proxy.py +23 -0
- ormlambda/sql/comparer.py +31 -65
- ormlambda/sql/compiler.py +248 -58
- ormlambda/sql/context/__init__.py +304 -0
- ormlambda/sql/ddl.py +19 -5
- ormlambda/sql/elements.py +3 -0
- ormlambda/sql/foreign_key.py +42 -64
- ormlambda/sql/functions/__init__.py +0 -1
- ormlambda/sql/functions/concat.py +35 -38
- ormlambda/sql/functions/max.py +12 -36
- ormlambda/sql/functions/min.py +13 -28
- ormlambda/sql/functions/sum.py +17 -33
- ormlambda/sql/sqltypes.py +2 -0
- ormlambda/sql/table/__init__.py +1 -0
- ormlambda/sql/table/table.py +32 -49
- ormlambda/sql/table/table_proxy.py +88 -0
- ormlambda/sql/type_api.py +4 -1
- ormlambda/sql/types.py +15 -12
- ormlambda/statements/__init__.py +0 -2
- ormlambda/statements/base_statement.py +51 -84
- ormlambda/statements/interfaces/IStatements.py +77 -123
- ormlambda/statements/interfaces/__init__.py +1 -1
- ormlambda/statements/query_builder.py +296 -128
- ormlambda/statements/statements.py +120 -110
- ormlambda/statements/types.py +5 -25
- ormlambda/util/__init__.py +7 -86
- ormlambda/util/langhelpers.py +102 -0
- ormlambda/util/module_tree/dynamic_module.py +1 -1
- ormlambda/util/preloaded.py +80 -0
- ormlambda/util/typing.py +12 -3
- {ormlambda-3.35.2.dist-info → ormlambda-4.0.0.dist-info}/METADATA +29 -31
- ormlambda-4.0.0.dist-info/RECORD +139 -0
- ormlambda/common/abstract_classes/clause_info_converter.py +0 -65
- ormlambda/common/abstract_classes/decomposition_query.py +0 -141
- ormlambda/common/abstract_classes/query_base.py +0 -15
- ormlambda/common/interfaces/ICustomAlias.py +0 -7
- ormlambda/common/interfaces/IDecompositionQuery.py +0 -33
- ormlambda/databases/__init__.py +0 -4
- ormlambda/databases/my_sql/__init__.py +0 -3
- ormlambda/databases/my_sql/clauses/ST_AsText.py +0 -37
- ormlambda/databases/my_sql/clauses/ST_Contains.py +0 -36
- ormlambda/databases/my_sql/clauses/__init__.py +0 -14
- ormlambda/databases/my_sql/clauses/count.py +0 -33
- ormlambda/databases/my_sql/clauses/delete.py +0 -9
- ormlambda/databases/my_sql/clauses/drop_table.py +0 -26
- ormlambda/databases/my_sql/clauses/group_by.py +0 -17
- ormlambda/databases/my_sql/clauses/having.py +0 -12
- ormlambda/databases/my_sql/clauses/insert.py +0 -9
- ormlambda/databases/my_sql/clauses/joins.py +0 -14
- ormlambda/databases/my_sql/clauses/limit.py +0 -6
- ormlambda/databases/my_sql/clauses/offset.py +0 -6
- ormlambda/databases/my_sql/clauses/order.py +0 -8
- ormlambda/databases/my_sql/clauses/update.py +0 -8
- ormlambda/databases/my_sql/clauses/upsert.py +0 -9
- ormlambda/databases/my_sql/clauses/where.py +0 -7
- ormlambda/dialects/interface/__init__.py +0 -1
- ormlambda/dialects/interface/dialect.py +0 -78
- ormlambda/sql/clause_info/aggregate_function_base.py +0 -96
- ormlambda/sql/clause_info/clause_info_context.py +0 -87
- ormlambda/sql/clauses/interfaces/ISelect.py +0 -17
- ormlambda/sql/clauses/new_join.py +0 -119
- ormlambda/util/load_module.py +0 -21
- ormlambda/util/plugin_loader.py +0 -32
- ormlambda-3.35.2.dist-info/RECORD +0 -159
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/caster.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/boolean.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/date.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/decimal.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/json.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql/repository}/pool_types.py +0 -0
- {ormlambda-3.35.2.dist-info → ormlambda-4.0.0.dist-info}/AUTHORS +0 -0
- {ormlambda-3.35.2.dist-info → ormlambda-4.0.0.dist-info}/LICENSE +0 -0
- {ormlambda-3.35.2.dist-info → ormlambda-4.0.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,304 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import threading
|
3
|
+
from contextlib import contextmanager
|
4
|
+
from typing import TYPE_CHECKING, Generator, Literal, Optional, Any, TypedDict, Protocol
|
5
|
+
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from ormlambda import Table
|
9
|
+
from ormlambda.sql import ForeignKey
|
10
|
+
from ormlambda.sql.context import FKChain
|
11
|
+
from ormlambda.sql.clauses import JoinSelector
|
12
|
+
|
13
|
+
type CurrentPathType = FKChain
|
14
|
+
type ForeignKeyRegistryType = dict[str, ForeignKey]
|
15
|
+
type QueryMetadataType = dict[str, Any]
|
16
|
+
type TableAliasType = dict[str, str] # table_name -> alias
|
17
|
+
type ClauseAliasType = dict[str, str] # clause_key -> alias
|
18
|
+
type JoinSelectorType = dict[str, str] # clause_key -> alias
|
19
|
+
|
20
|
+
# Separate persistent and transient data types
|
21
|
+
type PersistentDataKeys = Literal["foreign_key_registry"]
|
22
|
+
type ContextDictKeys = Literal[
|
23
|
+
"current_path",
|
24
|
+
"foreign_key_registry",
|
25
|
+
"query_metadata",
|
26
|
+
"table_aliases",
|
27
|
+
"join_selector",
|
28
|
+
]
|
29
|
+
|
30
|
+
|
31
|
+
class ContextDict(TypedDict):
|
32
|
+
current_path: CurrentPathType
|
33
|
+
foreign_key_registry: ForeignKeyRegistryType # PERSISTENT: Survives across queries
|
34
|
+
query_metadata: QueryMetadataType # TRANSIENT: Reset per query
|
35
|
+
table_aliases: TableAliasType # TRANSIENT: Reset per query
|
36
|
+
join_selector: JoinSelectorType # TRANSIENT: Reset per query
|
37
|
+
|
38
|
+
|
39
|
+
class LocalContext(Protocol):
|
40
|
+
context: ContextDict
|
41
|
+
|
42
|
+
|
43
|
+
class PathContext:
|
44
|
+
_local: LocalContext
|
45
|
+
|
46
|
+
def __init__(self):
|
47
|
+
self._local = threading.local()
|
48
|
+
|
49
|
+
def __repr__(self):
|
50
|
+
return f"{PathContext.__name__}"
|
51
|
+
|
52
|
+
@staticmethod
|
53
|
+
def _initialize_context() -> ContextDict:
|
54
|
+
return {
|
55
|
+
"current_path": NO_CURRENT_PATH,
|
56
|
+
"foreign_key_registry": {},
|
57
|
+
"query_metadata": {},
|
58
|
+
"table_aliases": {},
|
59
|
+
"join_selector": {},
|
60
|
+
}
|
61
|
+
|
62
|
+
def _get_context(self, key: Optional[ContextDictKeys] = None) -> ContextDict:
|
63
|
+
"""
|
64
|
+
we Always need to return the context variable itself to has a references to the context variable.
|
65
|
+
An approach like return self._initialize_context() won't work as expected
|
66
|
+
"""
|
67
|
+
if not self.has_context():
|
68
|
+
self._set_context(self._initialize_context())
|
69
|
+
|
70
|
+
return self._local.context if not key else self._local.context.get(key, None)
|
71
|
+
|
72
|
+
def _set_context(self, context: ContextDict) -> None:
|
73
|
+
self._local.context = context
|
74
|
+
return None
|
75
|
+
|
76
|
+
def get_current_path(self) -> Optional[FKChain]:
|
77
|
+
return self._get_context("current_path")
|
78
|
+
|
79
|
+
def set_current_path(self, path: FKChain) -> None:
|
80
|
+
context = self._get_context()
|
81
|
+
context["current_path"] = path
|
82
|
+
return None
|
83
|
+
|
84
|
+
def add_foreign_key_access(self, fk: ForeignKey, path: FKChain) -> None:
|
85
|
+
context = self._get_context()
|
86
|
+
|
87
|
+
# Store in registry
|
88
|
+
if fk not in context["foreign_key_registry"]:
|
89
|
+
context["foreign_key_registry"][f"{fk.tleft.__table_name__}.{fk.clause_name}"] = fk
|
90
|
+
|
91
|
+
return None
|
92
|
+
|
93
|
+
def get_all_foreign_key_accesses(self) -> ForeignKeyRegistryType:
|
94
|
+
return self._get_context("foreign_key_registry").copy()
|
95
|
+
|
96
|
+
def get_foreign_key_from_registry(self, key: str) -> Optional[ForeignKey]:
|
97
|
+
"""Get a specific ForeignKey from the registry by key"""
|
98
|
+
return self._get_context("foreign_key_registry").get(key, None)
|
99
|
+
|
100
|
+
def remove_foreign_key_from_registry(self, key: str) -> bool:
|
101
|
+
"""Remove a ForeignKey from the registry. Returns True if removed, False if not found"""
|
102
|
+
context = self._get_context()
|
103
|
+
if key in context["foreign_key_registry"]:
|
104
|
+
del context["foreign_key_registry"][key]
|
105
|
+
return True
|
106
|
+
return False
|
107
|
+
|
108
|
+
def clear_foreign_key_registry(self) -> None:
|
109
|
+
"""Clear only the ForeignKey registry while preserving other context"""
|
110
|
+
context = self._get_context()
|
111
|
+
context["foreign_key_registry"].clear()
|
112
|
+
return None
|
113
|
+
|
114
|
+
def get_foreign_key_registry_size(self) -> int:
|
115
|
+
"""Get the number of ForeignKeys in the registry"""
|
116
|
+
return len(self._get_context("foreign_key_registry"))
|
117
|
+
|
118
|
+
def clear_context(self):
|
119
|
+
"""Clear all context data (legacy method - use clear_all_context for clarity)"""
|
120
|
+
self.clear_all_context()
|
121
|
+
|
122
|
+
def clear_all_context(self) -> None:
|
123
|
+
"""Clear all context data including persistent ForeignKey registry"""
|
124
|
+
if self.has_context():
|
125
|
+
delattr(self._local, "context")
|
126
|
+
|
127
|
+
def clear_transient_context(self) -> None:
|
128
|
+
"""Clear only transient context data, preserving ForeignKey registry"""
|
129
|
+
if not self.has_context():
|
130
|
+
return None
|
131
|
+
|
132
|
+
context = self._get_context()
|
133
|
+
# Preserve foreign_key_registry, clear everything else
|
134
|
+
preserved_registry = context["foreign_key_registry"].copy()
|
135
|
+
|
136
|
+
context["current_path"] = NO_CURRENT_PATH
|
137
|
+
context["query_metadata"].clear()
|
138
|
+
context["table_aliases"].clear()
|
139
|
+
context["join_selector"].clear()
|
140
|
+
# Restore preserved registry
|
141
|
+
context["foreign_key_registry"] = preserved_registry
|
142
|
+
|
143
|
+
return None
|
144
|
+
|
145
|
+
def reset_query_context(self) -> None:
|
146
|
+
"""Reset context for new query while preserving persistent data"""
|
147
|
+
self.clear_transient_context()
|
148
|
+
|
149
|
+
def has_context(self) -> bool:
|
150
|
+
return hasattr(self._local, "context")
|
151
|
+
|
152
|
+
@contextmanager
|
153
|
+
def query_context(self, table: Optional[Table] = None) -> Generator[PathContext, None, None]:
|
154
|
+
"""Context manager for query execution with proper cleanup"""
|
155
|
+
# Store the old context if it exists
|
156
|
+
old_context = None
|
157
|
+
if self.has_context():
|
158
|
+
old_context = self._get_context()
|
159
|
+
|
160
|
+
try:
|
161
|
+
self.initialize_context_with_table(table)
|
162
|
+
yield self
|
163
|
+
|
164
|
+
finally:
|
165
|
+
# Restore old context or use selective clearing to preserve FK registry
|
166
|
+
if old_context is not None:
|
167
|
+
self._set_context(old_context)
|
168
|
+
else:
|
169
|
+
# Use selective clearing to preserve foreign_key_registry
|
170
|
+
self.reset_query_context()
|
171
|
+
|
172
|
+
def initialize_context_with_table(self, table: Optional[Table] = None) -> None:
|
173
|
+
# Preserve foreign_key_registry if context already exists
|
174
|
+
preserved_registry = {}
|
175
|
+
if self.has_context():
|
176
|
+
preserved_registry = self._get_context("foreign_key_registry").copy()
|
177
|
+
|
178
|
+
self._set_context(self._initialize_context())
|
179
|
+
|
180
|
+
# Restore preserved registry
|
181
|
+
if preserved_registry:
|
182
|
+
context = self._get_context()
|
183
|
+
context["foreign_key_registry"] = preserved_registry
|
184
|
+
|
185
|
+
if not table:
|
186
|
+
return None
|
187
|
+
|
188
|
+
initial_path = FKChain(table, [])
|
189
|
+
self.set_current_path(initial_path)
|
190
|
+
return None
|
191
|
+
|
192
|
+
def reset_current_path(self, table):
|
193
|
+
initial_path = FKChain(table, [])
|
194
|
+
self.set_current_path(initial_path)
|
195
|
+
return None
|
196
|
+
|
197
|
+
# FIXME [x]: Alias Management Methods (replacing ClauseInfoContext functionality)
|
198
|
+
def add_table_alias(self, table: Table, alias: str) -> None:
|
199
|
+
"""Add a table alias to the context"""
|
200
|
+
if not table or not alias:
|
201
|
+
return None
|
202
|
+
|
203
|
+
context = self._get_context()
|
204
|
+
table_key = table.__table_name__ if hasattr(table, "__table_name__") else str(table)
|
205
|
+
context["table_aliases"][table_key] = alias
|
206
|
+
return None
|
207
|
+
|
208
|
+
def get_table_alias(self, table: Table) -> Optional[str]:
|
209
|
+
"""Get a table alias from the context"""
|
210
|
+
if not table:
|
211
|
+
return None
|
212
|
+
|
213
|
+
context = self._get_context()
|
214
|
+
table_key = table.__table_name__ if hasattr(table, "__table_name__") else str(table)
|
215
|
+
return context["table_aliases"].get(table_key, None)
|
216
|
+
|
217
|
+
def add_join(self, join: JoinSelector, alias: str) -> None:
|
218
|
+
"""Add a table alias to the context"""
|
219
|
+
if not join or not alias:
|
220
|
+
return None
|
221
|
+
|
222
|
+
context = self._get_context()
|
223
|
+
context["join_selector"][alias] = join
|
224
|
+
return None
|
225
|
+
|
226
|
+
def get_join(self, alias: str) -> Optional[str]:
|
227
|
+
"""Get a table alias from the context"""
|
228
|
+
if not alias:
|
229
|
+
return None
|
230
|
+
|
231
|
+
context = self._get_context("join_selector")
|
232
|
+
return context.get(alias, None)
|
233
|
+
|
234
|
+
|
235
|
+
class FKChain:
|
236
|
+
base: Optional[Table]
|
237
|
+
steps: list[ForeignKey]
|
238
|
+
|
239
|
+
def __init__(
|
240
|
+
self,
|
241
|
+
base: Optional[Table] = None,
|
242
|
+
steps: Optional[list[Table]] = None,
|
243
|
+
):
|
244
|
+
self.base = base if base else None
|
245
|
+
self.steps = steps if steps else []
|
246
|
+
|
247
|
+
def __repr__(self):
|
248
|
+
return f"{FKChain.__name__}: {self.get_path_key()}"
|
249
|
+
|
250
|
+
@property
|
251
|
+
def parent(self) -> FKChain:
|
252
|
+
# FIXME [ ]: what if we reach the top parent? we need to return None in some point
|
253
|
+
if len(self.steps) <= 1:
|
254
|
+
steps = []
|
255
|
+
else:
|
256
|
+
steps = self.steps[:-1].copy()
|
257
|
+
|
258
|
+
return FKChain(self.base, steps)
|
259
|
+
|
260
|
+
def add_step(self, step: Table | ForeignKey):
|
261
|
+
"""Add a step to the path"""
|
262
|
+
if not self.base:
|
263
|
+
self.base = step
|
264
|
+
return None
|
265
|
+
|
266
|
+
self.steps.append(step)
|
267
|
+
return None
|
268
|
+
|
269
|
+
def copy(self) -> FKChain:
|
270
|
+
return FKChain(base=self.base, steps=self.steps.copy())
|
271
|
+
|
272
|
+
def get_path_key(self) -> str:
|
273
|
+
return self._generate_chain(".")
|
274
|
+
|
275
|
+
def get_alias(self) -> str:
|
276
|
+
"""Generate table alias from the path"""
|
277
|
+
if not self.base:
|
278
|
+
return ""
|
279
|
+
return self._generate_chain("_") if self.steps else self.base.__table_name__
|
280
|
+
|
281
|
+
def _generate_chain(self, char: str) -> str:
|
282
|
+
if not self.base:
|
283
|
+
return ""
|
284
|
+
result: list[Table] = [self.base.__table_name__]
|
285
|
+
for step in self.steps:
|
286
|
+
# For foreign keys, use their clause name or a descriptive name
|
287
|
+
data = getattr(step, "clause_name", f"fk_{step.__class__.__name__}")
|
288
|
+
result.append(data)
|
289
|
+
return char.join(result)
|
290
|
+
|
291
|
+
def get_depth(self) -> int:
|
292
|
+
"""Get the depth of this path (number of foreign key steps)"""
|
293
|
+
return len(self.steps)
|
294
|
+
|
295
|
+
def clear(self) -> None:
|
296
|
+
self.steps.clear()
|
297
|
+
|
298
|
+
def __getitem__(self, number: int):
|
299
|
+
return FKChain(self.base, self.steps[number])
|
300
|
+
|
301
|
+
|
302
|
+
NO_CURRENT_PATH = FKChain(None, [])
|
303
|
+
|
304
|
+
PATH_CONTEXT = PathContext()
|
ormlambda/sql/ddl.py
CHANGED
@@ -3,7 +3,8 @@ from typing import TYPE_CHECKING
|
|
3
3
|
from .elements import ClauseElement
|
4
4
|
|
5
5
|
if TYPE_CHECKING:
|
6
|
-
from ormlambda
|
6
|
+
from ormlambda import URL
|
7
|
+
from ormlambda.dialects import Dialect
|
7
8
|
from ormlambda import Column
|
8
9
|
from ormlambda import Table
|
9
10
|
|
@@ -22,16 +23,22 @@ class BaseDDLElement(ClauseElement):
|
|
22
23
|
return dialect.ddl_compiler(dialect, self, **kw)
|
23
24
|
|
24
25
|
|
25
|
-
class
|
26
|
+
class CreateDropTable:
|
27
|
+
def __init__(self, element: Table):
|
28
|
+
self.element = element
|
29
|
+
self.columns = [CreateColumn(c) for c in element.get_columns()]
|
30
|
+
|
31
|
+
|
32
|
+
class CreateTable(CreateDropTable, BaseDDLElement):
|
26
33
|
"""
|
27
34
|
Class representing a CREATE TABLE statement.
|
28
35
|
"""
|
29
36
|
|
30
37
|
__visit_name__ = "create_table"
|
31
38
|
|
32
|
-
|
33
|
-
|
34
|
-
|
39
|
+
|
40
|
+
class DropTable(CreateDropTable, BaseDDLElement):
|
41
|
+
__visit_name__ = "drop_table"
|
35
42
|
|
36
43
|
|
37
44
|
class CreateColumn[T](BaseDDLElement):
|
@@ -66,3 +73,10 @@ class SchemaExists(BaseDDLElement):
|
|
66
73
|
|
67
74
|
def __init__(self, schema: str):
|
68
75
|
self.schema = schema
|
76
|
+
|
77
|
+
|
78
|
+
class CreateBackup(BaseDDLElement):
|
79
|
+
__visit_name__ = "create_backup"
|
80
|
+
|
81
|
+
def __init__(self, url: URL):
|
82
|
+
self.url = url
|
ormlambda/sql/elements.py
CHANGED
ormlambda/sql/foreign_key.py
CHANGED
@@ -1,42 +1,28 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
import logging
|
3
|
+
from typing import Callable, TYPE_CHECKING, Optional, Any, Type, overload
|
3
4
|
|
4
|
-
from ormlambda.
|
5
|
-
from ormlambda.sql.elements import Element
|
5
|
+
from ormlambda.sql.ddl import BaseDDLElement
|
6
6
|
|
7
7
|
if TYPE_CHECKING:
|
8
8
|
from ormlambda.sql.comparer import Comparer
|
9
9
|
from ormlambda import Table
|
10
|
-
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
11
10
|
from ormlambda.dialects import Dialect
|
12
11
|
|
12
|
+
from ormlambda.util import preloaded as _preloaded
|
13
13
|
|
14
|
-
|
15
|
-
def clear(self):
|
16
|
-
to_remove = {x for x in self if not cast(ForeignKey, x)._keep_alive}
|
17
|
-
for el in to_remove:
|
18
|
-
self.remove(el)
|
14
|
+
log = logging.getLogger(__name__)
|
19
15
|
|
20
|
-
def remove(self, element):
|
21
|
-
return super().remove(element)
|
22
16
|
|
23
|
-
|
24
|
-
for el in self:
|
25
|
-
if el != item:
|
26
|
-
continue
|
27
|
-
|
28
|
-
if not cast(ForeignKey, el)._keep_alive:
|
29
|
-
super().remove(el)
|
30
|
-
return el
|
31
|
-
|
32
|
-
def add(self, element):
|
33
|
-
return super().add(element)
|
34
|
-
|
35
|
-
|
36
|
-
class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
|
17
|
+
class ForeignKey[TLeft: Table, TRight: Table](BaseDDLElement):
|
37
18
|
__visit_name__ = "foreign_key"
|
38
19
|
|
39
|
-
|
20
|
+
__slots__ = (
|
21
|
+
"_tright",
|
22
|
+
"_relationship",
|
23
|
+
"_comparer",
|
24
|
+
"_clause_name",
|
25
|
+
)
|
40
26
|
|
41
27
|
@overload
|
42
28
|
def __new__(self, comparer: Comparer, clause_name: str) -> None: ...
|
@@ -59,11 +45,9 @@ class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
|
|
59
45
|
*,
|
60
46
|
comparer: Optional[Comparer] = None,
|
61
47
|
clause_name: Optional[str] = None,
|
62
|
-
keep_alive: bool = False,
|
63
48
|
**kwargs: Any,
|
64
49
|
) -> None:
|
65
50
|
self.kwargs = kwargs
|
66
|
-
self._keep_alive = keep_alive
|
67
51
|
if comparer is not None and clause_name is not None:
|
68
52
|
self.__init__with_comparer(comparer, clause_name, **kwargs)
|
69
53
|
else:
|
@@ -71,8 +55,8 @@ class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
|
|
71
55
|
|
72
56
|
def __init__with_comparer(self, comparer: Comparer, clause_name: str, **kwargs) -> None:
|
73
57
|
self._relationship = None
|
74
|
-
self._tleft: TLeft = comparer.left_condition
|
75
|
-
self._tright: TRight = comparer.right_condition
|
58
|
+
self._tleft: TLeft = comparer.left_condition.table
|
59
|
+
self._tright: TRight = comparer.right_condition.table
|
76
60
|
self._clause_name: str = clause_name
|
77
61
|
self._comparer: Comparer = comparer
|
78
62
|
|
@@ -88,10 +72,9 @@ class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
|
|
88
72
|
self._clause_name: str = name
|
89
73
|
|
90
74
|
def __get__(self, obj: Optional[TRight], objtype=None) -> ForeignKey[TLeft, TRight] | TRight:
|
91
|
-
if
|
92
|
-
|
93
|
-
|
94
|
-
return self._tright
|
75
|
+
if obj:
|
76
|
+
return self.tright
|
77
|
+
return self
|
95
78
|
|
96
79
|
def __set__(self, obj, value):
|
97
80
|
raise AttributeError(f"The {ForeignKey.__name__} '{self._clause_name}' in the '{self._tleft.__table_name__}' table cannot be overwritten.")
|
@@ -102,50 +85,45 @@ class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
|
|
102
85
|
return getattr(self._tright, name)
|
103
86
|
|
104
87
|
def __repr__(self) -> str:
|
105
|
-
return f"{self.__class__.__name__}(
|
88
|
+
return f"{self.__class__.__name__}(left={self._tleft.__name__ if self._tleft else 'None'}, right={self._tright.__name__ if self._tright else 'None'}, name={self._clause_name})"
|
106
89
|
|
107
90
|
@property
|
108
|
-
def tleft(self) ->
|
91
|
+
def tleft(self) -> Table:
|
109
92
|
return self._tleft
|
110
93
|
|
111
94
|
@property
|
112
|
-
def tright(self) ->
|
95
|
+
def tright(self) -> Table:
|
113
96
|
return self._tright
|
114
97
|
|
115
98
|
@property
|
116
99
|
def clause_name(self) -> str:
|
117
100
|
return self._clause_name
|
118
101
|
|
119
|
-
def query(self, dialect: Dialect, **kwargs) -> str:
|
120
|
-
compare = self.resolved_function(dialect)
|
121
|
-
left_col = compare.left_condition(dialect).column
|
122
|
-
rcon = alias if (alias := compare.right_condition(dialect).alias_table) else compare.right_condition(dialect).table.__table_name__
|
123
|
-
return f"FOREIGN KEY ({left_col}) REFERENCES {rcon}({compare.right_condition(dialect).column})"
|
124
|
-
|
125
102
|
def get_alias(self, dialect: Dialect) -> str:
|
126
|
-
self._comparer = self.resolved_function(
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
clauses: list[str] = []
|
135
|
-
|
136
|
-
for attr in orig_table.__dict__.values():
|
137
|
-
if isinstance(attr, ForeignKey):
|
138
|
-
clauses.append(attr.query(dialect))
|
139
|
-
return clauses
|
140
|
-
|
141
|
-
def resolved_function[LProp: Any, RProp: Any](self, dialect: Dialect, context: Optional[ClauseContextType] = None) -> Comparer:
|
142
|
-
""" """
|
103
|
+
self._comparer = self.resolved_function()
|
104
|
+
# TODOH []: look into why i dropped 'lcol' variable
|
105
|
+
return f"{self.tleft.__table_name__}_{self.clause_name}"
|
106
|
+
|
107
|
+
@_preloaded.preload_module("ormlambda.sql.table")
|
108
|
+
def resolved_function(self) -> Comparer:
|
109
|
+
util = _preloaded.sql_table
|
110
|
+
|
143
111
|
if self._comparer is not None:
|
144
112
|
return self._comparer
|
145
113
|
|
146
|
-
left = self._tleft
|
147
|
-
right = self._tright
|
114
|
+
left = util.TableProxy(self._tleft)
|
115
|
+
right = util.TableProxy(self._tright)
|
148
116
|
comparer = self._relationship(left, right)
|
149
|
-
comparer.set_context(context)
|
150
|
-
comparer._dialect = dialect
|
151
117
|
return comparer
|
118
|
+
|
119
|
+
def __hash__(self):
|
120
|
+
return hash(
|
121
|
+
(
|
122
|
+
self._tleft,
|
123
|
+
self._tright,
|
124
|
+
self._clause_name,
|
125
|
+
)
|
126
|
+
)
|
127
|
+
|
128
|
+
def __eq__(self, other: ForeignKey):
|
129
|
+
return hash(other) == hash(self)
|
@@ -1,48 +1,45 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
from typing import Any, Iterable
|
3
3
|
|
4
|
-
from ormlambda.sql.clause_info import AggregateFunctionBase
|
5
|
-
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
6
|
-
from ormlambda.sql.types import ColumnType, AliasType
|
7
|
-
from ormlambda.sql.clause_info import ClauseInfo
|
8
|
-
|
9
|
-
|
10
|
-
type ConcatResponse[TProp] = tuple[str | ColumnType[TProp]]
|
11
4
|
|
5
|
+
from ormlambda.sql.clause_info import IAggregate
|
6
|
+
from ormlambda.sql.elements import ClauseElement
|
7
|
+
from ormlambda.sql.types import ColumnType, AliasType
|
8
|
+
from ormlambda import ColumnProxy, TableProxy
|
9
|
+
from ormlambda.common import GlobalChecker
|
12
10
|
|
13
|
-
|
14
|
-
from ormlambda.dialects import Dialect
|
11
|
+
VALID_CONCAT_TYPES = ColumnProxy | str | TableProxy
|
15
12
|
|
16
13
|
|
17
|
-
class Concat[T](
|
18
|
-
|
19
|
-
def FUNCTION_NAME() -> str:
|
20
|
-
return "CONCAT"
|
14
|
+
class Concat[T](ClauseElement, IAggregate):
|
15
|
+
__visit_name__ = "concat"
|
21
16
|
|
22
17
|
def __init__[TProp](
|
23
18
|
self,
|
24
|
-
values:
|
25
|
-
|
26
|
-
context: ClauseContextType = None,
|
27
|
-
*,
|
28
|
-
dialect: Dialect,
|
19
|
+
values: tuple[str | ColumnType[TProp] | TableProxy[T], ...],
|
20
|
+
alias: AliasType[ColumnType[TProp]] = "concat",
|
29
21
|
) -> None:
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
22
|
+
if isinstance(values, TableProxy):
|
23
|
+
values = GlobalChecker.parser_object(values, values._table_class)
|
24
|
+
if not self.is_valid(values):
|
25
|
+
raise ValueError(values)
|
26
|
+
|
27
|
+
self.values = values
|
28
|
+
self.alias = alias
|
29
|
+
|
30
|
+
def used_columns(self):
|
31
|
+
return [x for x in self.values if not isinstance(x, str)]
|
32
|
+
|
33
|
+
@property
|
34
|
+
def dtype(self) -> str:
|
35
|
+
return str
|
36
|
+
|
37
|
+
@staticmethod
|
38
|
+
def is_valid(values: Any) -> bool:
|
39
|
+
if not isinstance(values, Iterable):
|
40
|
+
return False
|
41
|
+
for val in values:
|
42
|
+
if not isinstance(val, VALID_CONCAT_TYPES):
|
43
|
+
return False
|
44
|
+
|
45
|
+
return True
|
ormlambda/sql/functions/max.py
CHANGED
@@ -1,48 +1,24 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
import typing as tp
|
3
2
|
|
4
|
-
from ormlambda.sql.
|
5
|
-
from ormlambda.sql.clause_info import ClauseInfo
|
3
|
+
from ormlambda.sql.elements import ClauseElement
|
6
4
|
from ormlambda.sql.types import ColumnType, AliasType
|
7
|
-
from ormlambda.sql.clause_info import
|
5
|
+
from ormlambda.sql.clause_info import IAggregate
|
8
6
|
|
9
|
-
if tp.TYPE_CHECKING:
|
10
|
-
from ormlambda.dialects import Dialect
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
@staticmethod
|
15
|
-
def FUNCTION_NAME() -> str:
|
16
|
-
return "MAX"
|
8
|
+
class Max(ClauseElement, IAggregate):
|
9
|
+
__visit_name__ = "max"
|
17
10
|
|
18
11
|
def __init__[TProp](
|
19
12
|
self,
|
20
13
|
elements: ColumnType[TProp],
|
21
|
-
|
22
|
-
context: ClauseContextType = None,
|
23
|
-
*,
|
24
|
-
dialect: Dialect,
|
14
|
+
alias: AliasType[ColumnType[TProp]] = "max",
|
25
15
|
):
|
26
|
-
|
27
|
-
|
28
|
-
column=elements,
|
29
|
-
alias_table=None,
|
30
|
-
alias_clause=alias_clause,
|
31
|
-
context=context,
|
32
|
-
keep_asterisk=False,
|
33
|
-
preserve_context=False,
|
34
|
-
dialect=dialect,
|
35
|
-
)
|
36
|
-
|
37
|
-
@tp.override
|
38
|
-
def query(self, dialect: Dialect, **kwargs) -> str:
|
39
|
-
columns: list[str] = []
|
16
|
+
self.column = elements
|
17
|
+
self.alias = alias
|
40
18
|
|
41
|
-
|
42
|
-
|
43
|
-
new_clause = clause
|
44
|
-
new_clause.alias_clause = None
|
45
|
-
columns.append(new_clause)
|
19
|
+
def used_columns(self):
|
20
|
+
return [self.column]
|
46
21
|
|
47
|
-
|
48
|
-
|
22
|
+
@property
|
23
|
+
def dtype(self) -> int:
|
24
|
+
return int
|