ormlambda 3.7.2__py3-none-any.whl → 3.11.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 +2 -0
- ormlambda/databases/my_sql/caster/caster.py +1 -0
- ormlambda/databases/my_sql/clauses/__init__.py +1 -0
- ormlambda/databases/my_sql/clauses/alias.py +15 -21
- ormlambda/databases/my_sql/clauses/group_by.py +19 -20
- ormlambda/databases/my_sql/clauses/having.py +16 -0
- ormlambda/databases/my_sql/clauses/order.py +6 -1
- ormlambda/databases/my_sql/clauses/where.py +3 -3
- ormlambda/databases/my_sql/functions/concat.py +8 -6
- ormlambda/databases/my_sql/join_context.py +3 -2
- ormlambda/databases/my_sql/repository/repository.py +57 -11
- ormlambda/databases/my_sql/statements.py +72 -21
- ormlambda/engine/__init__.py +2 -0
- ormlambda/engine/create.py +35 -0
- ormlambda/engine/url.py +744 -0
- ormlambda/engine/utils.py +17 -0
- ormlambda/repository/base_repository.py +0 -1
- ormlambda/sql/column.py +27 -2
- ormlambda/sql/foreign_key.py +36 -4
- ormlambda/statements/interfaces/IStatements.py +37 -25
- ormlambda/statements/types.py +4 -1
- {ormlambda-3.7.2.dist-info → ormlambda-3.11.0.dist-info}/METADATA +107 -8
- {ormlambda-3.7.2.dist-info → ormlambda-3.11.0.dist-info}/RECORD +25 -21
- {ormlambda-3.7.2.dist-info → ormlambda-3.11.0.dist-info}/LICENSE +0 -0
- {ormlambda-3.7.2.dist-info → ormlambda-3.11.0.dist-info}/WHEEL +0 -0
ormlambda/__init__.py
CHANGED
@@ -20,3 +20,5 @@ from .model.base_model import (
|
|
20
20
|
BaseModel as BaseModel,
|
21
21
|
ORM as ORM,
|
22
22
|
) # COMMENT: to avoid relative import we need to import BaseModel after import Table,Column, ForeignKey, IRepositoryBase and Disassembler
|
23
|
+
|
24
|
+
from .engine import create_engine, URL # noqa: F401
|
@@ -12,6 +12,7 @@ from .order import Order as Order
|
|
12
12
|
from .update import UpdateQuery as UpdateQuery
|
13
13
|
from .upsert import UpsertQuery as UpsertQuery
|
14
14
|
from .where import Where as Where
|
15
|
+
from .having import Having as Having
|
15
16
|
from .count import Count as Count
|
16
17
|
from .group_by import GroupBy as GroupBy
|
17
18
|
from .alias import Alias as Alias
|
@@ -1,10 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import typing as tp
|
3
3
|
|
4
|
-
from ormlambda import Table
|
4
|
+
from ormlambda import Table
|
5
5
|
from ormlambda.sql.clause_info import ClauseInfo
|
6
|
-
from ormlambda.
|
7
|
-
from ormlambda.sql.
|
6
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
7
|
+
from ormlambda.sql.types import TableType
|
8
8
|
|
9
9
|
if tp.TYPE_CHECKING:
|
10
10
|
from ormlambda.sql.types import ColumnType
|
@@ -12,23 +12,17 @@ if tp.TYPE_CHECKING:
|
|
12
12
|
from ormlambda.sql.types import AliasType
|
13
13
|
|
14
14
|
|
15
|
-
class Alias[T: Table
|
16
|
-
def __init__(
|
15
|
+
class Alias[T: Table](ClauseInfo[T]):
|
16
|
+
def __init__[TProp](
|
17
17
|
self,
|
18
|
-
|
19
|
-
|
18
|
+
table: TableType[T],
|
19
|
+
column: tp.Optional[ColumnType[TProp]] = None,
|
20
|
+
alias_table: tp.Optional[AliasType[ClauseInfo[T]]] = None,
|
21
|
+
alias_clause: tp.Optional[AliasType[ClauseInfo[T]]] = None,
|
22
|
+
context: ClauseContextType = None,
|
23
|
+
keep_asterisk: bool = False,
|
24
|
+
preserve_context: bool = False,
|
20
25
|
):
|
21
|
-
if
|
22
|
-
|
23
|
-
|
24
|
-
else:
|
25
|
-
context = ClauseInfoContext(table_context=element.context._table_context, clause_context={})
|
26
|
-
element.context = context
|
27
|
-
element._alias_clause = alias_clause
|
28
|
-
|
29
|
-
self._element = element
|
30
|
-
|
31
|
-
@tp.override
|
32
|
-
@property
|
33
|
-
def query(self) -> str:
|
34
|
-
return self._element.query
|
26
|
+
if not alias_clause:
|
27
|
+
raise TypeError
|
28
|
+
super().__init__(table, column, alias_table, alias_clause, context, keep_asterisk, preserve_context)
|
@@ -1,32 +1,31 @@
|
|
1
1
|
import typing as tp
|
2
|
-
from ormlambda.common.enums.join_type import JoinType
|
3
|
-
from ormlambda.sql.clause_info import IAggregate
|
4
2
|
from ormlambda import Table
|
5
|
-
from ormlambda.
|
3
|
+
from ormlambda.sql.clause_info.clause_info import AggregateFunctionBase
|
4
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
|
5
|
+
from ormlambda.sql.types import ColumnType
|
6
6
|
|
7
7
|
|
8
|
-
class GroupBy[T: tp.Type[Table], *Ts, TProp](
|
9
|
-
|
8
|
+
class GroupBy[T: tp.Type[Table], *Ts, TProp](AggregateFunctionBase):
|
9
|
+
@classmethod
|
10
|
+
def FUNCTION_NAME(self) -> str:
|
11
|
+
return "GROUP BY"
|
10
12
|
|
11
13
|
def __init__(
|
12
14
|
self,
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
alias_name: str | None = None,
|
18
|
-
by: JoinType = JoinType.INNER_JOIN,
|
19
|
-
) -> None:
|
15
|
+
column: ColumnType,
|
16
|
+
context: ClauseInfoContext,
|
17
|
+
**kwargs,
|
18
|
+
):
|
20
19
|
super().__init__(
|
21
|
-
table,
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
table=column.table,
|
21
|
+
column=column,
|
22
|
+
alias_table=None,
|
23
|
+
alias_clause=None,
|
24
|
+
context=context,
|
25
|
+
**kwargs,
|
26
26
|
)
|
27
27
|
|
28
28
|
@property
|
29
29
|
def query(self) -> str:
|
30
|
-
|
31
|
-
|
32
|
-
return f"{self.CLAUSE} {col}"
|
30
|
+
column = self._create_query()
|
31
|
+
return f"{self.FUNCTION_NAME()} {column}"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from .where import Where
|
4
|
+
|
5
|
+
|
6
|
+
class Having(Where):
|
7
|
+
"""
|
8
|
+
The purpose of this class is to create 'WHERE' condition queries properly.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(self, *comparer, restrictive=True, context=None):
|
12
|
+
super().__init__(*comparer, restrictive=restrictive, context=context)
|
13
|
+
|
14
|
+
@staticmethod
|
15
|
+
def FUNCTION_NAME() -> str:
|
16
|
+
return "HAVING"
|
@@ -48,13 +48,18 @@ class Order(AggregateFunctionBase):
|
|
48
48
|
columns = self.unresolved_column
|
49
49
|
|
50
50
|
# if this attr is not iterable means that we only pass one column without wrapped in a list or tuple
|
51
|
+
if isinstance(columns, str):
|
52
|
+
string_columns = f"{columns} {str(self._order_type[0])}"
|
53
|
+
return f"{self.FUNCTION_NAME()} {string_columns}"
|
54
|
+
|
51
55
|
if not isinstance(columns, tp.Iterable):
|
52
56
|
columns = (columns,)
|
57
|
+
|
53
58
|
assert len(columns) == len(self._order_type)
|
54
59
|
|
55
60
|
context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
|
56
61
|
for index, clause in enumerate(self._convert_into_clauseInfo(columns, context)):
|
57
62
|
clause.alias_clause = None
|
58
|
-
string_columns.append(f"{clause.query} {self._order_type[index]
|
63
|
+
string_columns.append(f"{clause.query} {str(self._order_type[index])}")
|
59
64
|
|
60
65
|
return f"{self.FUNCTION_NAME()} {', '.join(string_columns)}"
|
@@ -32,8 +32,8 @@ class Where(AggregateFunctionBase):
|
|
32
32
|
def alias_clause(self) -> None:
|
33
33
|
return None
|
34
34
|
|
35
|
-
@
|
36
|
-
def join_condition(wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext) -> str:
|
35
|
+
@classmethod
|
36
|
+
def join_condition(cls, wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext) -> str:
|
37
37
|
if not isinstance(wheres, tp.Iterable):
|
38
38
|
wheres = (wheres,)
|
39
39
|
|
@@ -42,4 +42,4 @@ class Where(AggregateFunctionBase):
|
|
42
42
|
for c in where._comparer:
|
43
43
|
c.set_context(context)
|
44
44
|
comparers.append(c)
|
45
|
-
return
|
45
|
+
return cls(*comparers, restrictive=restrictive, context=context).query
|
@@ -1,20 +1,22 @@
|
|
1
|
+
import typing as tp
|
2
|
+
|
1
3
|
from ormlambda.sql.clause_info import AggregateFunctionBase
|
2
4
|
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
3
|
-
|
4
|
-
|
5
|
-
import typing as tp
|
6
5
|
from ormlambda.sql.types import ColumnType, AliasType
|
7
6
|
from ormlambda.sql.clause_info import ClauseInfo
|
8
7
|
|
9
8
|
|
10
|
-
|
9
|
+
type ConcatResponse[TProp] = tuple[str | ColumnType[TProp]]
|
10
|
+
|
11
|
+
|
12
|
+
class Concat[T](AggregateFunctionBase):
|
11
13
|
@staticmethod
|
12
14
|
def FUNCTION_NAME() -> str:
|
13
15
|
return "CONCAT"
|
14
16
|
|
15
17
|
def __init__[TProp](
|
16
18
|
self,
|
17
|
-
values:
|
19
|
+
values: ConcatResponse[TProp],
|
18
20
|
alias_clause: AliasType[ColumnType[TProp]] = "concat",
|
19
21
|
context: ClauseContextType = None,
|
20
22
|
) -> None:
|
@@ -33,6 +35,6 @@ class Concat[*Ts](AggregateFunctionBase):
|
|
33
35
|
context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
|
34
36
|
|
35
37
|
for clause in self._convert_into_clauseInfo(self.unresolved_column, context=context):
|
36
|
-
clause.alias_clause =
|
38
|
+
clause.alias_clause = self.alias_clause
|
37
39
|
columns.append(clause)
|
38
40
|
return self._concat_alias_and_column(f"{self.FUNCTION_NAME()}({ClauseInfo.join_clauses(columns)})", self._alias_aggregate)
|
@@ -27,12 +27,12 @@ class JoinContext[TParent: Table, TRepo]:
|
|
27
27
|
for comparer, by in self._joins:
|
28
28
|
fk_clause, alias = self.get_fk_clause(comparer)
|
29
29
|
|
30
|
-
foreign_key: ForeignKey = ForeignKey(comparer=comparer, clause_name=alias)
|
30
|
+
foreign_key: ForeignKey = ForeignKey(comparer=comparer, clause_name=alias, keep_alive=True)
|
31
31
|
fk_clause.alias_table = foreign_key.alias
|
32
32
|
self._context.add_clause_to_context(fk_clause)
|
33
33
|
setattr(self._parent, alias, foreign_key)
|
34
34
|
|
35
|
-
# TODOH []: We need to preserve the 'foreign_key' variable while inside the 'with' clause.
|
35
|
+
# TODOH [x]: We need to preserve the 'foreign_key' variable while inside the 'with' clause.
|
36
36
|
# Keep in mind that 'ForeignKey.stored_calls' is cleared every time we call methods like
|
37
37
|
# .select(), .select_one(), .insert(), .update(), or .count(). This means we only retain
|
38
38
|
# the context from the first call of any of these methods.
|
@@ -49,6 +49,7 @@ class JoinContext[TParent: Table, TRepo]:
|
|
49
49
|
fk: ForeignKey = getattr(self._parent, attribute)
|
50
50
|
delattr(self._parent, attribute)
|
51
51
|
del self._context._table_context[fk.tright]
|
52
|
+
ForeignKey.stored_calls.remove(fk)
|
52
53
|
return None
|
53
54
|
|
54
55
|
def __getattr__(self, name: str) -> TParent:
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
import contextlib
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Any, Generator, Iterable, Optional, Type, override, TYPE_CHECKING, Unpack
|
5
|
+
import uuid
|
5
6
|
import shapely as shp
|
6
7
|
|
7
8
|
# from mysql.connector.pooling import MySQLConnectionPool
|
@@ -16,6 +17,7 @@ from ormlambda.caster import Caster
|
|
16
17
|
from ..clauses import CreateDatabase, TypeExists
|
17
18
|
from ..clauses import DropDatabase
|
18
19
|
from ..clauses import DropTable
|
20
|
+
from ..clauses import Alias
|
19
21
|
|
20
22
|
|
21
23
|
if TYPE_CHECKING:
|
@@ -68,12 +70,15 @@ class Response[TFlavour, *Ts]:
|
|
68
70
|
|
69
71
|
def _cast_to_flavour(self, data: list[tuple[*Ts]], **kwargs) -> list[dict[str, tuple[*Ts]]] | list[tuple[*Ts]] | list[TFlavour]:
|
70
72
|
def _dict(**kwargs) -> list[dict[str, tuple[*Ts]]]:
|
73
|
+
nonlocal data
|
71
74
|
return [dict(zip(self._columns, x)) for x in data]
|
72
75
|
|
73
76
|
def _tuple(**kwargs) -> list[tuple[*Ts]]:
|
77
|
+
nonlocal data
|
74
78
|
return data
|
75
79
|
|
76
80
|
def _set(**kwargs) -> list[set]:
|
81
|
+
nonlocal data
|
77
82
|
for d in data:
|
78
83
|
n = len(d)
|
79
84
|
for i in range(n):
|
@@ -84,12 +89,19 @@ class Response[TFlavour, *Ts]:
|
|
84
89
|
return [set(x) for x in data]
|
85
90
|
|
86
91
|
def _list(**kwargs) -> list[list]:
|
92
|
+
nonlocal data
|
87
93
|
return [list(x) for x in data]
|
88
94
|
|
89
95
|
def _default(**kwargs) -> list[TFlavour]:
|
90
|
-
|
96
|
+
nonlocal data
|
97
|
+
replacer_dicc: dict[str, str] = {}
|
91
98
|
|
92
|
-
|
99
|
+
for col in self._select.all_clauses:
|
100
|
+
if hasattr(col, "_alias_aggregate") or col.alias_clause is None or isinstance(col, Alias):
|
101
|
+
continue
|
102
|
+
replacer_dicc[col.alias_clause] = col.column
|
103
|
+
|
104
|
+
cleaned_column_names = [replacer_dicc.get(col, col) for col in self._columns]
|
93
105
|
|
94
106
|
result = []
|
95
107
|
for attr in data:
|
@@ -157,7 +169,34 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
157
169
|
#
|
158
170
|
|
159
171
|
def __init__(self, **kwargs: Unpack[MySQLArgs]):
|
160
|
-
|
172
|
+
timeout = self.__add_connection_timeout(kwargs)
|
173
|
+
name = self.__add_pool_name(kwargs)
|
174
|
+
size = self.__add_pool_size(kwargs)
|
175
|
+
attr = kwargs.copy()
|
176
|
+
attr["connection_timeout"] = timeout
|
177
|
+
attr["pool_name"] = name
|
178
|
+
attr["pool_size"] = size
|
179
|
+
|
180
|
+
super().__init__(MySQLConnectionPool, **attr)
|
181
|
+
|
182
|
+
@staticmethod
|
183
|
+
def __add_connection_timeout(kwargs: MySQLArgs) -> int:
|
184
|
+
if "connection_timeout" not in kwargs.keys():
|
185
|
+
return 60
|
186
|
+
return int(kwargs.pop("connection_timeout"))
|
187
|
+
|
188
|
+
@staticmethod
|
189
|
+
def __add_pool_name(kwargs: MySQLArgs) -> str:
|
190
|
+
if "pool_name" not in kwargs.keys():
|
191
|
+
return str(uuid.uuid4())
|
192
|
+
|
193
|
+
return kwargs.pop("pool_name")
|
194
|
+
|
195
|
+
@staticmethod
|
196
|
+
def __add_pool_size(kwargs: MySQLArgs) -> int:
|
197
|
+
if "pool_size" not in kwargs.keys():
|
198
|
+
return 5
|
199
|
+
return int(kwargs.pop("pool_size"))
|
161
200
|
|
162
201
|
@contextlib.contextmanager
|
163
202
|
def get_connection(self) -> Generator[MySQLConnection, None, None]:
|
@@ -254,7 +293,6 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
254
293
|
|
255
294
|
@override
|
256
295
|
def database_exists(self, name: str) -> bool:
|
257
|
-
query = "SHOW DATABASES LIKE %s;"
|
258
296
|
temp_config = self._pool._cnx_config
|
259
297
|
|
260
298
|
config_without_db = temp_config.copy()
|
@@ -262,10 +300,11 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
262
300
|
if "database" in config_without_db:
|
263
301
|
config_without_db.pop("database")
|
264
302
|
self._pool.set_config(**config_without_db)
|
303
|
+
|
265
304
|
with self.get_connection() as cnx:
|
266
305
|
with cnx.cursor(buffered=True) as cursor:
|
267
|
-
cursor.execute(
|
268
|
-
|
306
|
+
cursor.execute("SHOW DATABASES LIKE %s;", (name,))
|
307
|
+
res = cursor.fetchmany(1)
|
269
308
|
|
270
309
|
self._pool.set_config(**temp_config)
|
271
310
|
return len(res) > 0
|
@@ -276,12 +315,11 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
276
315
|
|
277
316
|
@override
|
278
317
|
def table_exists(self, name: str) -> bool:
|
279
|
-
query = "SHOW TABLES LIKE %s;"
|
280
318
|
with self.get_connection() as cnx:
|
281
319
|
if not cnx.database:
|
282
320
|
raise Exception("No database selected")
|
283
321
|
with cnx.cursor(buffered=True) as cursor:
|
284
|
-
cursor.execute(
|
322
|
+
cursor.execute("SHOW TABLES LIKE %s;", (name,))
|
285
323
|
res = cursor.fetchmany(1)
|
286
324
|
return len(res) > 0
|
287
325
|
|
@@ -297,9 +335,17 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
297
335
|
|
298
336
|
@property
|
299
337
|
def database(self) -> Optional[str]:
|
300
|
-
return self.
|
338
|
+
return self._pool._cnx_config.get("database", None)
|
301
339
|
|
302
340
|
@database.setter
|
303
341
|
def database(self, value: str) -> None:
|
304
|
-
|
305
|
-
|
342
|
+
"""Change the current database using USE statement"""
|
343
|
+
|
344
|
+
if not self.database_exists(value):
|
345
|
+
raise ValueError(f"You cannot set the non-existent '{value}' database.")
|
346
|
+
|
347
|
+
old_config: MySQLArgs = self._pool._cnx_config.copy()
|
348
|
+
old_config["database"] = value
|
349
|
+
|
350
|
+
self._pool._remove_connections()
|
351
|
+
self._pool = type(self)(**old_config)._pool
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import Concatenate, Iterable, override, Type, TYPE_CHECKING, Any, Callable, Optional
|
2
|
+
from typing import Concatenate, Iterable, TypedDict, override, Type, TYPE_CHECKING, Any, Callable, Optional
|
3
3
|
from mysql.connector import errors, errorcode
|
4
4
|
|
5
5
|
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
|
@@ -28,11 +28,12 @@ from .clauses import InsertQuery
|
|
28
28
|
from .clauses import Limit
|
29
29
|
from .clauses import Offset
|
30
30
|
from .clauses import Order
|
31
|
-
from .clauses
|
31
|
+
from .clauses import Select
|
32
32
|
|
33
33
|
from .clauses import UpsertQuery
|
34
34
|
from .clauses import UpdateQuery
|
35
35
|
from .clauses import Where
|
36
|
+
from .clauses import Having
|
36
37
|
from .clauses import Count
|
37
38
|
from .clauses import GroupBy
|
38
39
|
from .clauses import Alias
|
@@ -60,12 +61,23 @@ def clear_list[T, **P](f: Callable[Concatenate[MySQLStatements, P], T]) -> Calla
|
|
60
61
|
return wrapper
|
61
62
|
|
62
63
|
|
64
|
+
class OrderType(TypedDict):
|
65
|
+
Select: Select
|
66
|
+
JoinSelector: JoinSelector
|
67
|
+
Where: Where
|
68
|
+
Order: Order
|
69
|
+
GroupBy: GroupBy
|
70
|
+
Having: Having
|
71
|
+
Limit: Limit
|
72
|
+
Offset: Offset
|
73
|
+
|
74
|
+
|
63
75
|
class QueryBuilder(IQuery):
|
64
|
-
__order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "
|
76
|
+
__order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "GroupBy", "Having", "Order", "Limit", "Offset")
|
65
77
|
|
66
78
|
def __init__(self, by: JoinType = JoinType.INNER_JOIN):
|
67
79
|
self._context = ClauseInfoContext()
|
68
|
-
self._query_list:
|
80
|
+
self._query_list: OrderType = {}
|
69
81
|
self._by = by
|
70
82
|
|
71
83
|
self._joins: Optional[IQuery] = None
|
@@ -113,6 +125,15 @@ class QueryBuilder(IQuery):
|
|
113
125
|
def GROUP_BY(self) -> IQuery:
|
114
126
|
return self._query_list.get("GroupBy", None)
|
115
127
|
|
128
|
+
@property
|
129
|
+
def HAVING(self) -> IQuery:
|
130
|
+
where = self._query_list.get("Having", None)
|
131
|
+
if not isinstance(where, Iterable):
|
132
|
+
if not where:
|
133
|
+
return ()
|
134
|
+
return (where,)
|
135
|
+
return where
|
136
|
+
|
116
137
|
@property
|
117
138
|
def LIMIT(self) -> IQuery:
|
118
139
|
return self._query_list.get("Limit", None)
|
@@ -133,8 +154,9 @@ class QueryBuilder(IQuery):
|
|
133
154
|
self.SELECT.query,
|
134
155
|
JOINS,
|
135
156
|
Where.join_condition(self.WHERE, True, self._context) if self.WHERE else None,
|
136
|
-
self.ORDER.query if self.ORDER else None,
|
137
157
|
self.GROUP_BY.query if self.GROUP_BY else None,
|
158
|
+
Having.join_condition(self.HAVING, True, self._context) if self.HAVING else None,
|
159
|
+
self.ORDER.query if self.ORDER else None,
|
138
160
|
self.LIMIT.query if self.LIMIT else None,
|
139
161
|
self.OFFSET.query if self.OFFSET else None,
|
140
162
|
)
|
@@ -154,8 +176,8 @@ class QueryBuilder(IQuery):
|
|
154
176
|
joins = set()
|
155
177
|
# Always it's gonna be a set of two
|
156
178
|
# FIXME [x]: Resolved when we get Compare object instead ClauseInfo. For instance, when we have multiples condition using '&' or '|'
|
157
|
-
for
|
158
|
-
fk = ForeignKey.stored_calls.pop()
|
179
|
+
for fk in ForeignKey.stored_calls.copy():
|
180
|
+
fk = ForeignKey.stored_calls.pop(fk)
|
159
181
|
self._context._add_table_alias(fk.tright, fk.alias)
|
160
182
|
join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk.alias)
|
161
183
|
joins.add(join)
|
@@ -278,7 +300,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
278
300
|
|
279
301
|
if GlobalChecker.is_lambda_function(selection):
|
280
302
|
selection = selection(*self.models)
|
281
|
-
return Count
|
303
|
+
return Count(element=selection, alias_clause=alias_clause, context=self._query_builder._context)
|
282
304
|
|
283
305
|
@override
|
284
306
|
def where(self, conditions: WhereTypes) -> IStatements_two_generic[T, MySQLConnection]:
|
@@ -291,6 +313,15 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
291
313
|
self._query_builder.add_statement(Where(*conditions))
|
292
314
|
return self
|
293
315
|
|
316
|
+
@override
|
317
|
+
def having(self, conditions: WhereTypes) -> IStatements_two_generic[T, MySQLConnection]:
|
318
|
+
if GlobalChecker.is_lambda_function(conditions):
|
319
|
+
conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
|
320
|
+
if not isinstance(conditions, Iterable):
|
321
|
+
conditions = (conditions,)
|
322
|
+
self._query_builder.add_statement(Having(*conditions))
|
323
|
+
return self
|
324
|
+
|
294
325
|
@override
|
295
326
|
def order[TValue](self, columns: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, MySQLConnection]:
|
296
327
|
query = GlobalChecker.resolved_callback_object(columns, self._models) if GlobalChecker.is_lambda_function(columns) else columns
|
@@ -299,23 +330,42 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
299
330
|
return self
|
300
331
|
|
301
332
|
@override
|
302
|
-
def concat
|
303
|
-
return func.Concat[T](
|
333
|
+
def concat(self, selector: str | Callable[[T], str], alias: str = "CONCAT") -> IAggregate:
|
334
|
+
return func.Concat[T](
|
335
|
+
values=selector,
|
336
|
+
alias_clause=alias,
|
337
|
+
context=self._query_builder._context,
|
338
|
+
)
|
304
339
|
|
305
340
|
@override
|
306
|
-
def max[TProp](
|
341
|
+
def max[TProp](
|
342
|
+
self,
|
343
|
+
column: Callable[[T], TProp],
|
344
|
+
alias: str = "max",
|
345
|
+
execute: bool = False,
|
346
|
+
) -> TProp:
|
307
347
|
if execute is True:
|
308
348
|
return self.select_one(self.max(column, alias, execute=False), flavour=dict)[alias]
|
309
349
|
return func.Max(elements=column, alias_clause=alias, context=self._query_builder._context)
|
310
350
|
|
311
351
|
@override
|
312
|
-
def min[TProp](
|
352
|
+
def min[TProp](
|
353
|
+
self,
|
354
|
+
column: Callable[[T], TProp],
|
355
|
+
alias: str = "min",
|
356
|
+
execute: bool = False,
|
357
|
+
) -> TProp:
|
313
358
|
if execute is True:
|
314
359
|
return self.select_one(self.min(column, alias, execute=False), flavour=dict)[alias]
|
315
360
|
return func.Min(elements=column, alias_clause=alias, context=self._query_builder._context)
|
316
361
|
|
317
362
|
@override
|
318
|
-
def sum[TProp](
|
363
|
+
def sum[TProp](
|
364
|
+
self,
|
365
|
+
column: Callable[[T], TProp],
|
366
|
+
alias: str = "sum",
|
367
|
+
execute: bool = False,
|
368
|
+
) -> TProp:
|
319
369
|
if execute is True:
|
320
370
|
return self.select_one(self.sum(column, alias, execute=False), flavour=dict)[alias]
|
321
371
|
return func.Sum(elements=column, alias_clause=alias, context=self._query_builder._context)
|
@@ -406,16 +456,17 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
406
456
|
)
|
407
457
|
|
408
458
|
@override
|
409
|
-
def
|
410
|
-
|
411
|
-
groupby = GroupBy[T, tuple[*Ts]](self._models, lambda x: column)
|
412
|
-
else:
|
413
|
-
groupby = GroupBy[T, tuple[*Ts]](self._models, column)
|
459
|
+
def groupby[TProp](self, column: ColumnType[TProp] | Callable[[T, *Ts], Any]):
|
460
|
+
groupby = GroupBy(column=column, context=self._query_builder._context)
|
414
461
|
# Only can be one LIMIT SQL parameter. We only use the last LimitQuery
|
415
462
|
self._query_builder.add_statement(groupby)
|
416
463
|
return self
|
417
464
|
|
418
465
|
@override
|
419
|
-
def alias[TProp](self, column: ColumnType[TProp], alias: str) ->
|
420
|
-
|
421
|
-
|
466
|
+
def alias[TProp](self, column: ColumnType[TProp], alias: str) -> ClauseInfo[T, TProp]:
|
467
|
+
return Alias(
|
468
|
+
table=column.table,
|
469
|
+
column=column,
|
470
|
+
alias_clause=alias,
|
471
|
+
context=self._query_builder._context,
|
472
|
+
)
|
ormlambda/engine/__init__.py
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from ormlambda.engine.url import URL, make_url
|
4
|
+
from ormlambda import BaseRepository
|
5
|
+
|
6
|
+
|
7
|
+
def create_engine(url: URL | str, **kwargs: Any) -> BaseRepository:
|
8
|
+
from ormlambda.databases import MySQLRepository
|
9
|
+
|
10
|
+
# create url.URL object
|
11
|
+
u = make_url(url)
|
12
|
+
url, kwargs = u._instantiate_plugins(kwargs)
|
13
|
+
|
14
|
+
repo_selector = {
|
15
|
+
"mysql": MySQLRepository,
|
16
|
+
}
|
17
|
+
|
18
|
+
if url.drivername not in repo_selector:
|
19
|
+
raise ValueError(f"drivername '{url.drivername}' not expected to load Repository class")
|
20
|
+
|
21
|
+
default_config = {
|
22
|
+
"user": url.username,
|
23
|
+
"password": url.password,
|
24
|
+
"host": url.host,
|
25
|
+
"database": url.database,
|
26
|
+
**kwargs,
|
27
|
+
}
|
28
|
+
|
29
|
+
if url.port:
|
30
|
+
try:
|
31
|
+
default_config["port"] = int(url.port)
|
32
|
+
except ValueError:
|
33
|
+
raise ValueError(f"The port must be an int. '{url.port}' passed.")
|
34
|
+
|
35
|
+
return repo_selector[url.drivername](**default_config)
|