ormlambda 3.12.2__py3-none-any.whl → 3.34.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/caster/__init__.py +1 -1
- ormlambda/caster/caster.py +29 -12
- ormlambda/common/abstract_classes/clause_info_converter.py +4 -12
- ormlambda/common/abstract_classes/decomposition_query.py +17 -2
- ormlambda/common/abstract_classes/non_query_base.py +9 -7
- ormlambda/common/abstract_classes/query_base.py +3 -1
- ormlambda/common/errors/__init__.py +29 -0
- ormlambda/common/interfaces/IQueryCommand.py +6 -2
- ormlambda/dialects/__init__.py +39 -0
- ormlambda/dialects/default/__init__.py +1 -0
- ormlambda/dialects/default/base.py +39 -0
- ormlambda/dialects/interface/__init__.py +1 -0
- ormlambda/dialects/interface/dialect.py +78 -0
- ormlambda/dialects/mysql/__init__.py +38 -0
- ormlambda/dialects/mysql/base.py +388 -0
- ormlambda/dialects/mysql/caster/caster.py +39 -0
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +1 -0
- ormlambda/dialects/mysql/caster/types/boolean.py +35 -0
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +8 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +4 -4
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_AsText.py +8 -7
- ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_Contains.py +10 -5
- ormlambda/dialects/mysql/clauses/__init__.py +13 -0
- ormlambda/dialects/mysql/clauses/count.py +33 -0
- ormlambda/dialects/mysql/clauses/delete.py +9 -0
- ormlambda/dialects/mysql/clauses/group_by.py +17 -0
- ormlambda/dialects/mysql/clauses/having.py +12 -0
- ormlambda/dialects/mysql/clauses/insert.py +9 -0
- ormlambda/dialects/mysql/clauses/joins.py +14 -0
- ormlambda/dialects/mysql/clauses/limit.py +6 -0
- ormlambda/dialects/mysql/clauses/offset.py +6 -0
- ormlambda/dialects/mysql/clauses/order.py +8 -0
- ormlambda/dialects/mysql/clauses/update.py +8 -0
- ormlambda/dialects/mysql/clauses/upsert.py +9 -0
- ormlambda/dialects/mysql/clauses/where.py +7 -0
- ormlambda/dialects/mysql/mysqlconnector.py +46 -0
- ormlambda/dialects/mysql/repository/__init__.py +1 -0
- ormlambda/dialects/mysql/repository/repository.py +212 -0
- ormlambda/dialects/mysql/types.py +732 -0
- ormlambda/dialects/sqlite/__init__.py +5 -0
- ormlambda/dialects/sqlite/base.py +47 -0
- ormlambda/dialects/sqlite/pysqlite.py +32 -0
- ormlambda/engine/__init__.py +1 -0
- ormlambda/engine/base.py +77 -0
- ormlambda/engine/create.py +9 -23
- ormlambda/engine/url.py +31 -19
- ormlambda/env.py +30 -0
- ormlambda/errors.py +17 -0
- ormlambda/model/base_model.py +7 -9
- ormlambda/repository/base_repository.py +36 -5
- ormlambda/repository/interfaces/IRepositoryBase.py +119 -12
- ormlambda/repository/response.py +134 -0
- ormlambda/sql/clause_info/aggregate_function_base.py +19 -9
- ormlambda/sql/clause_info/clause_info.py +34 -17
- ormlambda/sql/clauses/__init__.py +14 -0
- ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
- ormlambda/{databases/my_sql → sql}/clauses/count.py +15 -1
- ormlambda/{databases/my_sql → sql}/clauses/delete.py +22 -7
- ormlambda/sql/clauses/group_by.py +30 -0
- ormlambda/{databases/my_sql → sql}/clauses/having.py +7 -2
- ormlambda/{databases/my_sql → sql}/clauses/insert.py +16 -9
- ormlambda/sql/clauses/interfaces/__init__.py +5 -0
- ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
- ormlambda/{databases/my_sql → sql}/clauses/joins.py +29 -19
- ormlambda/sql/clauses/limit.py +15 -0
- ormlambda/sql/clauses/offset.py +15 -0
- ormlambda/{databases/my_sql → sql}/clauses/order.py +14 -24
- ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
- ormlambda/{databases/my_sql → sql}/clauses/update.py +24 -11
- ormlambda/{databases/my_sql → sql}/clauses/upsert.py +17 -12
- ormlambda/{databases/my_sql → sql}/clauses/where.py +28 -8
- ormlambda/sql/column/__init__.py +1 -0
- ormlambda/sql/{column.py → column/column.py} +82 -22
- ormlambda/sql/comparer.py +51 -37
- ormlambda/sql/compiler.py +668 -0
- ormlambda/sql/ddl.py +82 -0
- ormlambda/sql/elements.py +36 -0
- ormlambda/sql/foreign_key.py +61 -39
- ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
- ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
- ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
- ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
- ormlambda/sql/sqltypes.py +647 -0
- ormlambda/sql/table/__init__.py +1 -1
- ormlambda/sql/table/table.py +175 -0
- ormlambda/sql/table/table_constructor.py +1 -208
- ormlambda/sql/type_api.py +35 -0
- ormlambda/sql/types.py +3 -1
- ormlambda/sql/visitors.py +74 -0
- ormlambda/statements/__init__.py +1 -0
- ormlambda/statements/base_statement.py +28 -38
- ormlambda/statements/interfaces/IStatements.py +8 -4
- ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
- ormlambda/{databases/my_sql → statements}/statements.py +57 -61
- ormlambda/statements/types.py +2 -2
- ormlambda/types/__init__.py +24 -0
- ormlambda/types/metadata.py +42 -0
- ormlambda/util/__init__.py +87 -0
- ormlambda/{utils → util}/module_tree/dynamic_module.py +1 -1
- ormlambda/util/plugin_loader.py +32 -0
- ormlambda/util/typing.py +6 -0
- ormlambda-3.34.0.dist-info/AUTHORS +32 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.0.dist-info}/METADATA +1 -1
- ormlambda-3.34.0.dist-info/RECORD +152 -0
- ormlambda/components/__init__.py +0 -4
- ormlambda/components/delete/__init__.py +0 -2
- ormlambda/components/delete/abstract_delete.py +0 -17
- ormlambda/components/insert/__init__.py +0 -2
- ormlambda/components/insert/abstract_insert.py +0 -25
- ormlambda/components/select/__init__.py +0 -1
- ormlambda/components/update/__init__.py +0 -2
- ormlambda/components/update/abstract_update.py +0 -29
- ormlambda/components/upsert/__init__.py +0 -2
- ormlambda/components/upsert/abstract_upsert.py +0 -25
- ormlambda/databases/__init__.py +0 -5
- ormlambda/databases/my_sql/__init__.py +0 -4
- ormlambda/databases/my_sql/caster/caster.py +0 -39
- ormlambda/databases/my_sql/clauses/__init__.py +0 -20
- ormlambda/databases/my_sql/clauses/create_database.py +0 -35
- ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
- ormlambda/databases/my_sql/clauses/drop_table.py +0 -26
- ormlambda/databases/my_sql/clauses/group_by.py +0 -30
- ormlambda/databases/my_sql/clauses/limit.py +0 -17
- ormlambda/databases/my_sql/clauses/offset.py +0 -17
- ormlambda/databases/my_sql/repository/__init__.py +0 -1
- ormlambda/databases/my_sql/repository/repository.py +0 -351
- ormlambda/engine/template.py +0 -47
- ormlambda/sql/dtypes.py +0 -94
- ormlambda/utils/__init__.py +0 -1
- ormlambda-3.12.2.dist-info/RECORD +0 -125
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
- /ormlambda/{databases/my_sql/types.py → dialects/mysql/repository/pool_types.py} +0 -0
- /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
- /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
- /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
- /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
- /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
- /ormlambda/{components → sql/clauses}/join/__init__.py +0 -0
- /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.0.dist-info}/LICENSE +0 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.0.dist-info}/WHEEL +0 -0
@@ -3,20 +3,24 @@ from collections import defaultdict
|
|
3
3
|
from typing import override, Optional, TYPE_CHECKING, Type
|
4
4
|
|
5
5
|
|
6
|
-
from ormlambda.
|
6
|
+
from ormlambda.util.module_tree.dfs_traversal import DFSTraversal
|
7
7
|
from ormlambda.common.interfaces.IJoinSelector import IJoinSelector
|
8
8
|
from ormlambda.common.interfaces.IQueryCommand import IQuery
|
9
9
|
from ormlambda import JoinType
|
10
10
|
from ormlambda.sql.clause_info import ClauseInfo
|
11
11
|
from ormlambda.sql.comparer import Comparer
|
12
12
|
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
13
|
+
from ormlambda.sql.elements import ClauseElement
|
14
|
+
|
13
15
|
|
14
16
|
# TODOL [x]: Try to import Table module without circular import Error
|
15
17
|
if TYPE_CHECKING:
|
16
18
|
from ormlambda import Table
|
19
|
+
from ormlambda.dialects import Dialect
|
17
20
|
|
18
21
|
|
19
|
-
class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight]):
|
22
|
+
class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight], ClauseElement):
|
23
|
+
__visit_name__ = "join"
|
20
24
|
__slots__: tuple = (
|
21
25
|
"_comparer",
|
22
26
|
"_orig_table",
|
@@ -37,26 +41,31 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight]):
|
|
37
41
|
|
38
42
|
def __init__[LProp, RProp](
|
39
43
|
self,
|
40
|
-
where: Comparer
|
44
|
+
where: Comparer,
|
41
45
|
by: JoinType,
|
42
46
|
alias: Optional[str] = "{table}",
|
43
47
|
context: ClauseContextType = None,
|
48
|
+
*,
|
49
|
+
dialect: Dialect,
|
50
|
+
**kw,
|
44
51
|
) -> None:
|
45
|
-
|
46
|
-
|
47
|
-
self.
|
52
|
+
lcon = where.left_condition(dialect)
|
53
|
+
rcon = where.right_condition(dialect)
|
54
|
+
self._comparer: Comparer = where
|
55
|
+
self._orig_table: TLeft = lcon.table
|
56
|
+
self._right_table: TRight = rcon.table
|
48
57
|
self._by: JoinType = by
|
49
|
-
self._left_col: str =
|
50
|
-
self._right_col: str =
|
58
|
+
self._left_col: str = lcon._column.column_name
|
59
|
+
self._right_col: str = rcon._column.column_name
|
51
60
|
self._compareop = where._compare
|
52
61
|
self._context: ClauseContextType = context if context else ClauseInfoContext()
|
53
62
|
|
54
63
|
# COMMENT: When multiple columns reference the same table, we need to create an alias to maintain clear references.
|
55
64
|
self._alias: Optional[str] = alias
|
56
65
|
|
57
|
-
self._from_clause = ClauseInfo(self.right_table, alias_table=alias, context=self._context)
|
58
|
-
self._left_table_clause = ClauseInfo(self.left_table, column=self.left_col, alias_clause=None, context=self._create_partial_context())
|
59
|
-
self._right_table_clause = ClauseInfo(self.right_table, column=self.right_col, alias_clause=None, context=self._create_partial_context())
|
66
|
+
self._from_clause = ClauseInfo(self.right_table, alias_table=alias, context=self._context, dialect=dialect, **kw)
|
67
|
+
self._left_table_clause = ClauseInfo(self.left_table, column=self.left_col, alias_clause=None, context=self._create_partial_context(), dialect=dialect, **kw)
|
68
|
+
self._right_table_clause = ClauseInfo(self.right_table, column=self.right_col, alias_clause=None, context=self._create_partial_context(), dialect=dialect, **kw)
|
60
69
|
|
61
70
|
def __eq__(self, __value: JoinSelector) -> bool:
|
62
71
|
return isinstance(__value, JoinSelector) and self.__hash__() == __value.__hash__()
|
@@ -82,20 +91,18 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight]):
|
|
82
91
|
return ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
|
83
92
|
|
84
93
|
@classmethod
|
85
|
-
def join_selectors(cls, *args: JoinSelector) -> str:
|
86
|
-
return "\n".join([x.query for x in args])
|
94
|
+
def join_selectors(cls, dialect: Dialect, *args: JoinSelector) -> str:
|
95
|
+
return "\n".join([x.query(dialect) for x in args])
|
87
96
|
|
88
|
-
|
89
|
-
@override
|
90
|
-
def query(self) -> str:
|
97
|
+
def query(self, dialect: Dialect, **kwargs) -> str:
|
91
98
|
self._context = ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
|
92
99
|
list_ = [
|
93
100
|
self._by.value, # inner join
|
94
|
-
self._from_clause.query,
|
101
|
+
self._from_clause.query(dialect, **kwargs),
|
95
102
|
"ON",
|
96
|
-
self._left_table_clause.query,
|
103
|
+
self._left_table_clause.query(dialect, **kwargs),
|
97
104
|
self._compareop, # =
|
98
|
-
self._right_table_clause.query,
|
105
|
+
self._right_table_clause.query(dialect, **kwargs),
|
99
106
|
]
|
100
107
|
return " ".join([x for x in list_ if x is not None])
|
101
108
|
|
@@ -147,3 +154,6 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight]):
|
|
147
154
|
continue
|
148
155
|
res.extend(tables)
|
149
156
|
return res
|
157
|
+
|
158
|
+
|
159
|
+
__all__ = ["JoinSelector"]
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from ormlambda.sql.elements import ClauseElement
|
3
|
+
|
4
|
+
|
5
|
+
class Limit(ClauseElement):
|
6
|
+
__visit_name__ = "limit"
|
7
|
+
LIMIT = "LIMIT"
|
8
|
+
|
9
|
+
def __init__(self, number: int, **kwargs) -> None:
|
10
|
+
if not isinstance(number, int):
|
11
|
+
raise ValueError
|
12
|
+
self._number: int = number
|
13
|
+
|
14
|
+
|
15
|
+
__all__ = ["Limit"]
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from ormlambda.sql.elements import ClauseElement
|
3
|
+
|
4
|
+
|
5
|
+
class Offset(ClauseElement):
|
6
|
+
__visit_name__ = "offset"
|
7
|
+
OFFSET = "OFFSET"
|
8
|
+
|
9
|
+
def __init__(self, number: int, **kwargs) -> None:
|
10
|
+
if not isinstance(number, int):
|
11
|
+
raise ValueError
|
12
|
+
self._number: int = number
|
13
|
+
|
14
|
+
|
15
|
+
__all__ = ["Offset"]
|
@@ -1,14 +1,20 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import typing as tp
|
3
3
|
|
4
|
-
from ormlambda.sql.clause_info.clause_info_context import
|
4
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
5
5
|
from ormlambda.sql.types import ColumnType
|
6
6
|
from ormlambda.sql.clause_info import AggregateFunctionBase
|
7
7
|
|
8
8
|
from ormlambda.statements import OrderType
|
9
|
+
from ormlambda.sql.elements import ClauseElement
|
9
10
|
|
11
|
+
if tp.TYPE_CHECKING:
|
12
|
+
from ormlambda.dialects import Dialect
|
13
|
+
|
14
|
+
|
15
|
+
class Order(AggregateFunctionBase, ClauseElement):
|
16
|
+
__visit_name__ = "order"
|
10
17
|
|
11
|
-
class Order(AggregateFunctionBase):
|
12
18
|
@staticmethod
|
13
19
|
def FUNCTION_NAME() -> str:
|
14
20
|
return "ORDER BY"
|
@@ -18,11 +24,16 @@ class Order(AggregateFunctionBase):
|
|
18
24
|
column: tuple[ColumnType[TProp], ...] | ColumnType[TProp],
|
19
25
|
order_type: tp.Iterable[OrderType],
|
20
26
|
context: ClauseContextType = None,
|
27
|
+
*,
|
28
|
+
dialect: Dialect,
|
29
|
+
**kw,
|
21
30
|
):
|
22
31
|
super().__init__(
|
23
32
|
table=None,
|
24
33
|
column=column,
|
25
34
|
context=context,
|
35
|
+
dialect=dialect,
|
36
|
+
**kw,
|
26
37
|
)
|
27
38
|
|
28
39
|
if isinstance(order_type, str) or not isinstance(order_type, tp.Iterable):
|
@@ -41,25 +52,4 @@ class Order(AggregateFunctionBase):
|
|
41
52
|
pass
|
42
53
|
raise Exception(f"order_type param only can be 'ASC' or 'DESC' string or '{OrderType.__name__}' enum")
|
43
54
|
|
44
|
-
|
45
|
-
@property
|
46
|
-
def query(self) -> str:
|
47
|
-
string_columns: list[str] = []
|
48
|
-
columns = self.unresolved_column
|
49
|
-
|
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
|
-
|
55
|
-
if not isinstance(columns, tp.Iterable):
|
56
|
-
columns = (columns,)
|
57
|
-
|
58
|
-
assert len(columns) == len(self._order_type)
|
59
|
-
|
60
|
-
context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
|
61
|
-
for index, clause in enumerate(self._convert_into_clauseInfo(columns, context)):
|
62
|
-
clause.alias_clause = None
|
63
|
-
string_columns.append(f"{clause.query} {str(self._order_type[index])}")
|
64
|
-
|
65
|
-
return f"{self.FUNCTION_NAME()} {', '.join(string_columns)}"
|
55
|
+
__all__ = ["Order"]
|
@@ -1,17 +1,20 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from
|
2
|
+
from ormlambda.sql.elements import ClauseElement
|
3
|
+
from typing import Optional, Type, Callable, TYPE_CHECKING
|
3
4
|
|
4
5
|
from ormlambda.sql.clause_info import ClauseInfo
|
5
6
|
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
|
6
7
|
from ormlambda.sql.types import AliasType
|
7
8
|
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
|
8
|
-
from ormlambda.components import ISelect
|
9
9
|
|
10
10
|
if TYPE_CHECKING:
|
11
11
|
from ormlambda import Table
|
12
|
+
from ormlambda.dialects import Dialect
|
12
13
|
|
13
14
|
|
14
|
-
class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts],
|
15
|
+
class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ClauseElement):
|
16
|
+
__visit_name__ = "select"
|
17
|
+
|
15
18
|
CLAUSE: str = "SELECT"
|
16
19
|
|
17
20
|
def __init__(
|
@@ -21,12 +24,14 @@ class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ISelect):
|
|
21
24
|
*,
|
22
25
|
alias_table: AliasType[ClauseInfo] = "{table}",
|
23
26
|
context: Optional[ClauseInfoContext] = None,
|
27
|
+
dialect: Dialect,
|
24
28
|
**kwargs,
|
25
29
|
) -> None:
|
26
30
|
super().__init__(
|
27
31
|
tables,
|
28
32
|
columns,
|
29
33
|
context=context,
|
34
|
+
dialect=dialect,
|
30
35
|
**kwargs,
|
31
36
|
)
|
32
37
|
self._alias_table = alias_table
|
@@ -35,18 +40,12 @@ class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ISelect):
|
|
35
40
|
|
36
41
|
@property
|
37
42
|
def FROM(self) -> ClauseInfo[T]:
|
38
|
-
return ClauseInfo(self.table, None, alias_table=self._alias_table, context=self._context)
|
43
|
+
return ClauseInfo(self.table, None, alias_table=self._alias_table, context=self._context, dialect=self._dialect, **self.kwargs)
|
39
44
|
|
40
45
|
@property
|
41
46
|
def COLUMNS(self) -> str:
|
42
|
-
|
47
|
+
dialect = self.kwargs.pop("dialect", self._dialect)
|
48
|
+
return ClauseInfo.join_clauses(self._all_clauses, ",", self.context, dialect=dialect)
|
43
49
|
|
44
|
-
# TODOL: see who to deal when we will have to add more mysql methods
|
45
|
-
@override
|
46
|
-
@property
|
47
|
-
def query(self) -> str:
|
48
|
-
# COMMENT: (select.query, query)We must first create an alias for 'FROM' and then define all the remaining clauses.
|
49
|
-
# This order is mandatory because it adds the clause name to the context when accessing the .query property of 'FROM'
|
50
|
-
FROM = self.FROM
|
51
50
|
|
52
|
-
|
51
|
+
__all__ = ["Select"]
|
@@ -1,11 +1,18 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Type, override, Any, TYPE_CHECKING, Optional
|
2
3
|
|
3
|
-
from ormlambda.components.update import UpdateQueryBase
|
4
4
|
from ormlambda import Table, Column
|
5
|
-
from ormlambda.repository import IRepositoryBase
|
6
5
|
from ormlambda.caster.caster import Caster
|
7
6
|
from .where import Where
|
8
|
-
from ormlambda.
|
7
|
+
from ormlambda.common.abstract_classes import NonQueryBase
|
8
|
+
from .interfaces import IUpdate
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from ormlambda.sql.types import ColumnType
|
12
|
+
from .where import Where
|
13
|
+
from ormlambda.engine import Engine
|
14
|
+
|
15
|
+
from ormlambda.sql.elements import ClauseElement
|
9
16
|
|
10
17
|
|
11
18
|
class UpdateKeyError(KeyError):
|
@@ -20,9 +27,12 @@ class UpdateKeyError(KeyError):
|
|
20
27
|
return f"The column '{self._key}' does not belong to the table '{self._table.__table_name__}'. Please check the columns in the query."
|
21
28
|
|
22
29
|
|
23
|
-
class
|
24
|
-
|
25
|
-
|
30
|
+
class Update[T: Type[Table], TRepo](NonQueryBase[T, TRepo], IUpdate, ClauseElement):
|
31
|
+
__visit_name__ = "update"
|
32
|
+
|
33
|
+
def __init__(self, model: T, repository: Any, where: list[Where], engine: Engine) -> None:
|
34
|
+
super().__init__(model, repository, engine=engine)
|
35
|
+
self._where: Optional[list[Where]] = where
|
26
36
|
|
27
37
|
@override
|
28
38
|
@property
|
@@ -33,11 +43,11 @@ class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase]):
|
|
33
43
|
def execute(self) -> None:
|
34
44
|
if self._where:
|
35
45
|
for where in self._where:
|
36
|
-
query_with_table = where.query
|
46
|
+
query_with_table = where.query(self._engine.dialect)
|
37
47
|
for x in where._comparer:
|
38
48
|
# TODOH []: Refactor this part. We need to get only the columns withouth __table_name__ preffix
|
39
|
-
self._query += " " + query_with_table.replace(x.left_condition.table.__table_name__ + ".", "")
|
40
|
-
return self.
|
49
|
+
self._query += " " + query_with_table.replace(x.left_condition(self._dialect).table.__table_name__ + ".", "")
|
50
|
+
return self._engine.repository.execute_with_values(self._query, self._values)
|
41
51
|
|
42
52
|
@override
|
43
53
|
def update[TProp](self, dicc: dict[str | ColumnType[TProp], Any]) -> None:
|
@@ -45,7 +55,7 @@ class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase]):
|
|
45
55
|
raise TypeError
|
46
56
|
|
47
57
|
col_names: list[Column] = []
|
48
|
-
CASTER =
|
58
|
+
CASTER = self._engine.dialect.caster()
|
49
59
|
for col, value in dicc.items():
|
50
60
|
if isinstance(col, str):
|
51
61
|
if not hasattr(self._model, col):
|
@@ -69,3 +79,6 @@ class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase]):
|
|
69
79
|
if self._model is not col.table:
|
70
80
|
raise UpdateKeyError(self._model, col)
|
71
81
|
return not col.is_auto_generated
|
82
|
+
|
83
|
+
|
84
|
+
__all__ = ["Update"]
|
@@ -1,20 +1,22 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import override, TYPE_CHECKING
|
3
3
|
|
4
|
-
|
5
|
-
from ormlambda.
|
6
|
-
from ormlambda.repository import IRepositoryBase, BaseRepository
|
4
|
+
if TYPE_CHECKING:
|
5
|
+
from ormlambda.engine import Engine
|
7
6
|
|
7
|
+
from ormlambda import Table
|
8
|
+
from ormlambda.repository import BaseRepository
|
9
|
+
from ormlambda.sql.clauses.interfaces import IUpsert
|
10
|
+
from ormlambda.common.abstract_classes import NonQueryBase
|
11
|
+
from .insert import Insert
|
12
|
+
from ormlambda.sql.elements import ClauseElement
|
8
13
|
|
9
|
-
from .insert import InsertQuery
|
10
14
|
|
11
|
-
|
12
|
-
|
15
|
+
class Upsert[T: Table, TRepo](NonQueryBase[T, TRepo], IUpsert[T], ClauseElement):
|
16
|
+
__visit_name__ = "upsert"
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
def __init__(self, model: T, repository: BaseRepository[MySQLConnection]) -> None:
|
17
|
-
super().__init__(model, repository)
|
18
|
+
def __init__(self, model: T, repository: BaseRepository[TRepo], engine: Engine) -> None:
|
19
|
+
super().__init__(model, repository, engine=engine)
|
18
20
|
|
19
21
|
@override
|
20
22
|
@property
|
@@ -23,7 +25,7 @@ class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
|
|
23
25
|
|
24
26
|
@override
|
25
27
|
def execute(self) -> None:
|
26
|
-
return self.
|
28
|
+
return self._engine.repository.executemany_with_values(self._query, self._values)
|
27
29
|
|
28
30
|
@override
|
29
31
|
def upsert(self, instances: T | list[T]) -> None:
|
@@ -54,7 +56,7 @@ class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
|
|
54
56
|
COL2 = _val.COL2;
|
55
57
|
|
56
58
|
"""
|
57
|
-
insert =
|
59
|
+
insert = Insert(self._model, self._engine.repository, self._engine.dialect)
|
58
60
|
insert.insert(instances)
|
59
61
|
|
60
62
|
if isinstance(instances, Table):
|
@@ -70,3 +72,6 @@ class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
|
|
70
72
|
self._query = query
|
71
73
|
self._values = insert.values
|
72
74
|
return None
|
75
|
+
|
76
|
+
|
77
|
+
__all__ = ["Upsert"]
|
@@ -3,14 +3,25 @@ import typing as tp
|
|
3
3
|
from ormlambda.sql.comparer import Comparer
|
4
4
|
from ormlambda.sql.clause_info import AggregateFunctionBase
|
5
5
|
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
6
|
+
from ormlambda.sql.elements import ClauseElement
|
6
7
|
|
8
|
+
if tp.TYPE_CHECKING:
|
9
|
+
from ormlambda.dialects import Dialect
|
7
10
|
|
8
|
-
|
11
|
+
|
12
|
+
class Where(AggregateFunctionBase, ClauseElement):
|
9
13
|
"""
|
10
14
|
The purpose of this class is to create 'WHERE' condition queries properly.
|
11
15
|
"""
|
12
16
|
|
13
|
-
|
17
|
+
__visit_name__ = "where"
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
*comparer: Comparer,
|
22
|
+
restrictive: bool = True,
|
23
|
+
context: ClauseContextType = None,
|
24
|
+
) -> None:
|
14
25
|
self._comparer: tuple[Comparer] = comparer
|
15
26
|
self._restrictive: bool = restrictive
|
16
27
|
self._context: ClauseContextType = context if context else ClauseInfoContext()
|
@@ -19,11 +30,16 @@ class Where(AggregateFunctionBase):
|
|
19
30
|
def FUNCTION_NAME() -> str:
|
20
31
|
return "WHERE"
|
21
32
|
|
22
|
-
|
23
|
-
def query(self) -> str:
|
33
|
+
def query(self, dialect: Dialect, **kwargs) -> str:
|
24
34
|
if isinstance(self._comparer, tp.Iterable):
|
25
35
|
context = ClauseInfoContext(table_context=self._context._table_context)
|
26
|
-
comparer = Comparer.join_comparers(
|
36
|
+
comparer = Comparer.join_comparers(
|
37
|
+
self._comparer,
|
38
|
+
restrictive=self._restrictive,
|
39
|
+
context=context,
|
40
|
+
dialect=dialect,
|
41
|
+
**kwargs,
|
42
|
+
)
|
27
43
|
else:
|
28
44
|
comparer = self._comparer
|
29
45
|
return f"{self.FUNCTION_NAME()} {comparer}"
|
@@ -33,13 +49,17 @@ class Where(AggregateFunctionBase):
|
|
33
49
|
return None
|
34
50
|
|
35
51
|
@classmethod
|
36
|
-
def join_condition(cls, wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext) -> str:
|
52
|
+
def join_condition(cls, wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext, dialect: Dialect = None) -> str:
|
37
53
|
if not isinstance(wheres, tp.Iterable):
|
38
54
|
wheres = (wheres,)
|
39
55
|
|
40
56
|
comparers: list[Comparer] = []
|
41
57
|
for where in wheres:
|
42
58
|
for c in where._comparer:
|
43
|
-
c.
|
59
|
+
if not c._context:
|
60
|
+
c._context = context
|
44
61
|
comparers.append(c)
|
45
|
-
return cls(*comparers, restrictive=restrictive, context=context).query
|
62
|
+
return cls(*comparers, restrictive=restrictive, context=context).query(dialect=dialect)
|
63
|
+
|
64
|
+
|
65
|
+
__all__ = ["Where"]
|
@@ -0,0 +1 @@
|
|
1
|
+
from .column import Column # noqa: F401
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import Iterable, Type, Optional, TYPE_CHECKING, overload
|
3
2
|
import abc
|
3
|
+
from typing import Annotated, Any, Iterable, Type, Optional, TYPE_CHECKING, get_type_hints, overload, get_origin, get_args
|
4
4
|
from ormlambda.sql.types import TableType, ComparerType, ColumnType
|
5
5
|
from ormlambda import ConditionType
|
6
6
|
|
@@ -8,6 +8,19 @@ if TYPE_CHECKING:
|
|
8
8
|
import re
|
9
9
|
from ormlambda import Table
|
10
10
|
from ormlambda.sql.comparer import Comparer, Regex, Like
|
11
|
+
from ormlambda.sql.type_api import TypeEngine
|
12
|
+
|
13
|
+
|
14
|
+
from ormlambda.types import (
|
15
|
+
# metadata
|
16
|
+
PrimaryKey,
|
17
|
+
AutoGenerated,
|
18
|
+
AutoIncrement,
|
19
|
+
Unique,
|
20
|
+
CheckTypes,
|
21
|
+
Default,
|
22
|
+
NotNull,
|
23
|
+
)
|
11
24
|
|
12
25
|
|
13
26
|
class Column[TProp]:
|
@@ -21,6 +34,9 @@ class Column[TProp]:
|
|
21
34
|
"is_auto_generated",
|
22
35
|
"is_auto_increment",
|
23
36
|
"is_unique",
|
37
|
+
"is_not_null",
|
38
|
+
"default_value",
|
39
|
+
"sql_type",
|
24
40
|
"__private_name",
|
25
41
|
"_check",
|
26
42
|
)
|
@@ -37,7 +53,9 @@ class Column[TProp]:
|
|
37
53
|
is_auto_generated: bool = False,
|
38
54
|
is_auto_increment: bool = False,
|
39
55
|
is_unique: bool = False,
|
56
|
+
is_not_null: bool = False,
|
40
57
|
check_types: bool = True,
|
58
|
+
default: Optional[Any] = None,
|
41
59
|
) -> None: ...
|
42
60
|
|
43
61
|
def __init__[T: Table](
|
@@ -48,8 +66,10 @@ class Column[TProp]:
|
|
48
66
|
is_auto_generated: bool = False,
|
49
67
|
is_auto_increment: bool = False,
|
50
68
|
is_unique: bool = False,
|
69
|
+
is_not_null: bool = False,
|
51
70
|
check_types: bool = True,
|
52
71
|
column_name: Optional[str] = None,
|
72
|
+
default: Optional[Any] = None,
|
53
73
|
) -> None:
|
54
74
|
if dtype is None and column_name is None:
|
55
75
|
raise AttributeError("You must specify either the 'dtype' or 'column_name' attribute.")
|
@@ -62,14 +82,17 @@ class Column[TProp]:
|
|
62
82
|
self.column_name: Optional[str] = column_name
|
63
83
|
self.__private_name: Optional[str] = None
|
64
84
|
self._check = check_types
|
85
|
+
self.default_value: TProp = default
|
86
|
+
self.sql_type: TypeEngine[TProp] = None
|
65
87
|
|
66
88
|
self.is_primary_key: bool = is_primary_key
|
67
89
|
self.is_auto_generated: bool = is_auto_generated
|
68
90
|
self.is_auto_increment: bool = is_auto_increment
|
69
91
|
self.is_unique: bool = is_unique
|
92
|
+
self.is_not_null: bool = is_not_null
|
70
93
|
|
71
94
|
def __repr__(self) -> str:
|
72
|
-
return f"{type(self).__name__}[{self.dtype.__name__}] => {self.column_name}"
|
95
|
+
return f"{type(self).__name__}[{self.dtype.__class__.__name__}] => {self.column_name}"
|
73
96
|
|
74
97
|
def __str__(self) -> str:
|
75
98
|
return self.table.__table_name__ + "." + self.column_name
|
@@ -79,6 +102,8 @@ class Column[TProp]:
|
|
79
102
|
self.column_name = name
|
80
103
|
self.__private_name = self.PRIVATE_CHAR + name
|
81
104
|
|
105
|
+
self._fill_from_annotations(owner, name)
|
106
|
+
|
82
107
|
def __get__(self, obj, objtype=None) -> ColumnType[TProp]:
|
83
108
|
if not obj:
|
84
109
|
return self
|
@@ -100,43 +125,78 @@ class Column[TProp]:
|
|
100
125
|
self.is_auto_generated,
|
101
126
|
self.is_auto_increment,
|
102
127
|
self.is_unique,
|
128
|
+
self.is_not_null,
|
103
129
|
)
|
104
130
|
)
|
105
131
|
|
132
|
+
def _fill_from_annotations[T: Table](self, obj: Type[T], name: str) -> None:
|
133
|
+
"""Read the metada when using Annotated typing class, and set the attributes accordingly"""
|
134
|
+
|
135
|
+
from ormlambda.sql.type_api import TypeEngine
|
136
|
+
|
137
|
+
annotations = get_type_hints(obj, include_extras=True)
|
138
|
+
if name in annotations:
|
139
|
+
annotation = annotations[name]
|
140
|
+
if get_origin(annotation) is Annotated:
|
141
|
+
dtype, *metadata = get_args(annotation)
|
142
|
+
|
143
|
+
if not self.dtype:
|
144
|
+
self.dtype = dtype
|
145
|
+
|
146
|
+
for meta in metadata:
|
147
|
+
if isinstance(meta, TypeEngine):
|
148
|
+
self.sql_type = meta
|
149
|
+
elif isinstance(meta, PrimaryKey):
|
150
|
+
self.is_primary_key = True
|
151
|
+
elif isinstance(meta, AutoGenerated):
|
152
|
+
self.is_auto_generated = True
|
153
|
+
elif isinstance(meta, AutoIncrement):
|
154
|
+
self.is_auto_increment = True
|
155
|
+
elif isinstance(meta, Unique):
|
156
|
+
self.is_unique = True
|
157
|
+
elif isinstance(meta, CheckTypes):
|
158
|
+
self._check = True
|
159
|
+
elif isinstance(meta, Default):
|
160
|
+
self.default_value = meta.value
|
161
|
+
self.is_auto_generated = True
|
162
|
+
elif isinstance(meta, NotNull):
|
163
|
+
self.is_not_null = True
|
164
|
+
return None
|
165
|
+
|
106
166
|
@abc.abstractmethod
|
107
|
-
def __comparer_creator
|
167
|
+
def __comparer_creator(self, other: ColumnType, compare: ComparerType) -> Comparer:
|
108
168
|
from ormlambda.sql.comparer import Comparer
|
109
169
|
|
110
|
-
return Comparer
|
170
|
+
return Comparer(self, other, compare)
|
111
171
|
|
112
|
-
def __eq__
|
113
|
-
return self.__comparer_creator(other, ConditionType.EQUAL.value
|
172
|
+
def __eq__(self, other: ColumnType) -> Comparer:
|
173
|
+
return self.__comparer_creator(other, ConditionType.EQUAL.value)
|
114
174
|
|
115
|
-
def __ne__
|
116
|
-
return self.__comparer_creator(other, ConditionType.NOT_EQUAL.value
|
175
|
+
def __ne__(self, other: ColumnType) -> Comparer:
|
176
|
+
return self.__comparer_creator(other, ConditionType.NOT_EQUAL.value)
|
117
177
|
|
118
|
-
def __lt__
|
119
|
-
return self.__comparer_creator(other, ConditionType.LESS_THAN.value
|
178
|
+
def __lt__(self, other: ColumnType) -> Comparer:
|
179
|
+
return self.__comparer_creator(other, ConditionType.LESS_THAN.value)
|
120
180
|
|
121
|
-
def __le__
|
122
|
-
return self.__comparer_creator(other, ConditionType.LESS_THAN_OR_EQUAL.value
|
181
|
+
def __le__(self, other: ColumnType) -> Comparer:
|
182
|
+
return self.__comparer_creator(other, ConditionType.LESS_THAN_OR_EQUAL.value)
|
123
183
|
|
124
|
-
def __gt__
|
125
|
-
return self.__comparer_creator(other, ConditionType.GREATER_THAN.value
|
184
|
+
def __gt__(self, other: ColumnType) -> Comparer:
|
185
|
+
return self.__comparer_creator(other, ConditionType.GREATER_THAN.value)
|
126
186
|
|
127
|
-
def __ge__
|
128
|
-
return self.__comparer_creator(other, ConditionType.GREATER_THAN_OR_EQUAL.value
|
187
|
+
def __ge__(self, other: ColumnType) -> Comparer:
|
188
|
+
return self.__comparer_creator(other, ConditionType.GREATER_THAN_OR_EQUAL.value)
|
129
189
|
|
130
|
-
def contains
|
190
|
+
def contains(self, other: ColumnType) -> Comparer:
|
131
191
|
if not isinstance(other, tuple) and isinstance(other, Iterable):
|
132
192
|
other = tuple(other)
|
133
193
|
|
134
|
-
return self.__comparer_creator(other, ConditionType.IN.value
|
194
|
+
return self.__comparer_creator(other, ConditionType.IN.value)
|
135
195
|
|
136
|
-
def not_contains
|
137
|
-
return self.__comparer_creator(other, ConditionType.NOT_IN.value
|
196
|
+
def not_contains(self, other: ColumnType) -> Comparer:
|
197
|
+
return self.__comparer_creator(other, ConditionType.NOT_IN.value)
|
138
198
|
|
139
|
-
def regex
|
199
|
+
def regex(self, pattern: str, flags: Optional[re.RegexFlag | Iterable[re.RegexFlag]] = None) -> Regex:
|
140
200
|
from ormlambda.sql.comparer import Regex
|
141
201
|
|
142
202
|
if not isinstance(flags, Iterable):
|
@@ -148,7 +208,7 @@ class Column[TProp]:
|
|
148
208
|
flags=flags,
|
149
209
|
)
|
150
210
|
|
151
|
-
def like
|
211
|
+
def like(self, pattern: str) -> Like:
|
152
212
|
from ormlambda.sql.comparer import Like
|
153
213
|
|
154
214
|
return Like(self, pattern)
|