ormlambda 3.12.2__py3-none-any.whl → 3.34.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/databases/__init__.py +0 -1
- ormlambda/databases/my_sql/__init__.py +0 -1
- ormlambda/databases/my_sql/caster/caster.py +23 -19
- ormlambda/databases/my_sql/caster/types/__init__.py +3 -0
- ormlambda/databases/my_sql/caster/types/boolean.py +35 -0
- ormlambda/databases/my_sql/caster/types/bytes.py +7 -7
- ormlambda/databases/my_sql/caster/types/date.py +34 -0
- ormlambda/databases/my_sql/caster/types/datetime.py +7 -7
- ormlambda/databases/my_sql/caster/types/decimal.py +32 -0
- ormlambda/databases/my_sql/caster/types/float.py +7 -7
- ormlambda/databases/my_sql/caster/types/int.py +7 -7
- ormlambda/databases/my_sql/caster/types/iterable.py +7 -7
- ormlambda/databases/my_sql/caster/types/none.py +8 -7
- ormlambda/databases/my_sql/caster/types/point.py +4 -4
- ormlambda/databases/my_sql/caster/types/string.py +7 -7
- ormlambda/databases/my_sql/clauses/ST_AsText.py +8 -7
- ormlambda/databases/my_sql/clauses/ST_Contains.py +10 -5
- ormlambda/databases/my_sql/clauses/__init__.py +4 -10
- ormlambda/databases/my_sql/clauses/count.py +5 -15
- ormlambda/databases/my_sql/clauses/delete.py +3 -50
- ormlambda/databases/my_sql/clauses/group_by.py +3 -16
- ormlambda/databases/my_sql/clauses/having.py +2 -6
- ormlambda/databases/my_sql/clauses/insert.py +4 -92
- ormlambda/databases/my_sql/clauses/joins.py +5 -140
- ormlambda/databases/my_sql/clauses/limit.py +4 -15
- ormlambda/databases/my_sql/clauses/offset.py +4 -15
- ormlambda/databases/my_sql/clauses/order.py +4 -61
- ormlambda/databases/my_sql/clauses/update.py +4 -67
- ormlambda/databases/my_sql/clauses/upsert.py +3 -66
- ormlambda/databases/my_sql/clauses/where.py +4 -42
- ormlambda/databases/my_sql/repository.py +217 -0
- 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 +8 -0
- ormlambda/dialects/mysql/base.py +387 -0
- ormlambda/dialects/mysql/mysqlconnector.py +46 -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 +58 -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 +121 -7
- 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/sql/clauses/count.py +57 -0
- ormlambda/sql/clauses/delete.py +71 -0
- ormlambda/sql/clauses/group_by.py +30 -0
- ormlambda/sql/clauses/having.py +21 -0
- ormlambda/sql/clauses/insert.py +104 -0
- ormlambda/sql/clauses/interfaces/__init__.py +5 -0
- ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
- ormlambda/sql/clauses/joins.py +159 -0
- ormlambda/sql/clauses/limit.py +15 -0
- ormlambda/sql/clauses/offset.py +15 -0
- ormlambda/sql/clauses/order.py +55 -0
- ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
- ormlambda/sql/clauses/update.py +84 -0
- ormlambda/sql/clauses/upsert.py +77 -0
- ormlambda/sql/clauses/where.py +65 -0
- 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 +427 -0
- ormlambda/sql/ddl.py +68 -0
- ormlambda/sql/elements.py +36 -0
- ormlambda/sql/foreign_key.py +43 -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 +179 -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 +5 -4
- ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
- ormlambda/{databases/my_sql → statements}/statements.py +50 -60
- ormlambda/statements/types.py +2 -2
- ormlambda/types/__init__.py +24 -0
- ormlambda/types/metadata.py +42 -0
- ormlambda/util/__init__.py +88 -0
- ormlambda/util/load_module.py +21 -0
- ormlambda/util/plugin_loader.py +32 -0
- ormlambda/util/typing.py +6 -0
- ormlambda-3.34.1.dist-info/AUTHORS +32 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/METADATA +2 -3
- ormlambda-3.34.1.dist-info/RECORD +157 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/WHEEL +1 -1
- 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/my_sql/clauses/create_database.py +0 -35
- ormlambda/databases/my_sql/clauses/drop_database.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/{types.py → 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/{utils → util}/module_tree/dynamic_module.py +0 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from collections import defaultdict
|
3
|
+
from typing import override, Optional, TYPE_CHECKING, Type
|
4
|
+
|
5
|
+
|
6
|
+
from ormlambda.util.module_tree.dfs_traversal import DFSTraversal
|
7
|
+
from ormlambda.common.interfaces.IJoinSelector import IJoinSelector
|
8
|
+
from ormlambda.common.interfaces.IQueryCommand import IQuery
|
9
|
+
from ormlambda import JoinType
|
10
|
+
from ormlambda.sql.clause_info import ClauseInfo
|
11
|
+
from ormlambda.sql.comparer import Comparer
|
12
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
13
|
+
from ormlambda.sql.elements import ClauseElement
|
14
|
+
|
15
|
+
|
16
|
+
# TODOL [x]: Try to import Table module without circular import Error
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from ormlambda import Table
|
19
|
+
from ormlambda.dialects import Dialect
|
20
|
+
|
21
|
+
|
22
|
+
class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight], ClauseElement):
|
23
|
+
__visit_name__ = "join"
|
24
|
+
__slots__: tuple = (
|
25
|
+
"_comparer",
|
26
|
+
"_orig_table",
|
27
|
+
"_right_table",
|
28
|
+
"_by",
|
29
|
+
"_left_col",
|
30
|
+
"_right_col",
|
31
|
+
"_compareop",
|
32
|
+
"_alias",
|
33
|
+
)
|
34
|
+
|
35
|
+
@override
|
36
|
+
def __repr__(self) -> str:
|
37
|
+
table_col_left: str = f"{self.left_table.table_alias()}.{self._left_col}"
|
38
|
+
table_col_right: str = f"{self.right_table.table_alias()}.{self._right_col}"
|
39
|
+
|
40
|
+
return f"{IQuery.__name__}: {self.__class__.__name__} ({table_col_left} == {table_col_right})"
|
41
|
+
|
42
|
+
def __init__[LProp, RProp](
|
43
|
+
self,
|
44
|
+
where: Comparer,
|
45
|
+
by: JoinType,
|
46
|
+
alias: Optional[str] = "{table}",
|
47
|
+
context: ClauseContextType = None,
|
48
|
+
*,
|
49
|
+
dialect: Dialect,
|
50
|
+
**kw,
|
51
|
+
) -> None:
|
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
|
57
|
+
self._by: JoinType = by
|
58
|
+
self._left_col: str = lcon._column.column_name
|
59
|
+
self._right_col: str = rcon._column.column_name
|
60
|
+
self._compareop = where._compare
|
61
|
+
self._context: ClauseContextType = context if context else ClauseInfoContext()
|
62
|
+
|
63
|
+
# COMMENT: When multiple columns reference the same table, we need to create an alias to maintain clear references.
|
64
|
+
self._alias: Optional[str] = alias
|
65
|
+
|
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)
|
69
|
+
|
70
|
+
def __eq__(self, __value: JoinSelector) -> bool:
|
71
|
+
return isinstance(__value, JoinSelector) and self.__hash__() == __value.__hash__()
|
72
|
+
|
73
|
+
def __hash__(self) -> int:
|
74
|
+
return hash(
|
75
|
+
(
|
76
|
+
self.left_table,
|
77
|
+
self.right_table,
|
78
|
+
self._by,
|
79
|
+
self._left_col,
|
80
|
+
self._right_col,
|
81
|
+
self._compareop,
|
82
|
+
)
|
83
|
+
)
|
84
|
+
|
85
|
+
def _create_partial_context(self) -> ClauseInfoContext:
|
86
|
+
"""
|
87
|
+
Only use table_context from global context
|
88
|
+
"""
|
89
|
+
if not self._context:
|
90
|
+
return ClauseInfoContext()
|
91
|
+
return ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
|
92
|
+
|
93
|
+
@classmethod
|
94
|
+
def join_selectors(cls, dialect: Dialect, *args: JoinSelector) -> str:
|
95
|
+
return "\n".join([x.query(dialect) for x in args])
|
96
|
+
|
97
|
+
def query(self, dialect: Dialect, **kwargs) -> str:
|
98
|
+
self._context = ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
|
99
|
+
list_ = [
|
100
|
+
self._by.value, # inner join
|
101
|
+
self._from_clause.query(dialect, **kwargs),
|
102
|
+
"ON",
|
103
|
+
self._left_table_clause.query(dialect, **kwargs),
|
104
|
+
self._compareop, # =
|
105
|
+
self._right_table_clause.query(dialect, **kwargs),
|
106
|
+
]
|
107
|
+
return " ".join([x for x in list_ if x is not None])
|
108
|
+
|
109
|
+
@property
|
110
|
+
def left_table(self) -> TLeft:
|
111
|
+
return self._orig_table
|
112
|
+
|
113
|
+
@property
|
114
|
+
def right_table(self) -> TRight:
|
115
|
+
return self._right_table
|
116
|
+
|
117
|
+
@property
|
118
|
+
def left_col(self) -> str:
|
119
|
+
return self._left_col
|
120
|
+
|
121
|
+
@property
|
122
|
+
def right_col(self) -> str:
|
123
|
+
return self._right_col
|
124
|
+
|
125
|
+
@property
|
126
|
+
def alias(self) -> str:
|
127
|
+
return self._alias
|
128
|
+
|
129
|
+
@classmethod
|
130
|
+
def sort_join_selectors(cls, joins: set[JoinSelector]) -> tuple[JoinSelector]:
|
131
|
+
# FIXME [x]: How to sort when needed because it's not necessary at this point. It is for testing purpouse
|
132
|
+
if len(joins) == 1:
|
133
|
+
return tuple(joins)
|
134
|
+
|
135
|
+
join_object_map: dict[str, list[JoinSelector]] = defaultdict(list)
|
136
|
+
|
137
|
+
for obj in joins:
|
138
|
+
join_object_map[obj.left_table].append(obj)
|
139
|
+
|
140
|
+
graph: dict[Type[Table], list[Type[Table]]] = defaultdict(list)
|
141
|
+
for join in joins:
|
142
|
+
graph[join.left_table].append(join.right_table)
|
143
|
+
|
144
|
+
sorted_graph = DFSTraversal.sort(graph)[::-1]
|
145
|
+
|
146
|
+
if not sorted_graph:
|
147
|
+
return tuple(joins)
|
148
|
+
|
149
|
+
res = []
|
150
|
+
for table in sorted_graph:
|
151
|
+
tables = join_object_map[table]
|
152
|
+
|
153
|
+
if not tables:
|
154
|
+
continue
|
155
|
+
res.extend(tables)
|
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"]
|
@@ -0,0 +1,55 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import typing as tp
|
3
|
+
|
4
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
5
|
+
from ormlambda.sql.types import ColumnType
|
6
|
+
from ormlambda.sql.clause_info import AggregateFunctionBase
|
7
|
+
|
8
|
+
from ormlambda.statements import OrderType
|
9
|
+
from ormlambda.sql.elements import ClauseElement
|
10
|
+
|
11
|
+
if tp.TYPE_CHECKING:
|
12
|
+
from ormlambda.dialects import Dialect
|
13
|
+
|
14
|
+
|
15
|
+
class Order(AggregateFunctionBase, ClauseElement):
|
16
|
+
__visit_name__ = "order"
|
17
|
+
|
18
|
+
@staticmethod
|
19
|
+
def FUNCTION_NAME() -> str:
|
20
|
+
return "ORDER BY"
|
21
|
+
|
22
|
+
def __init__[TProp](
|
23
|
+
self,
|
24
|
+
column: tuple[ColumnType[TProp], ...] | ColumnType[TProp],
|
25
|
+
order_type: tp.Iterable[OrderType],
|
26
|
+
context: ClauseContextType = None,
|
27
|
+
*,
|
28
|
+
dialect: Dialect,
|
29
|
+
**kw,
|
30
|
+
):
|
31
|
+
super().__init__(
|
32
|
+
table=None,
|
33
|
+
column=column,
|
34
|
+
context=context,
|
35
|
+
dialect=dialect,
|
36
|
+
**kw,
|
37
|
+
)
|
38
|
+
|
39
|
+
if isinstance(order_type, str) or not isinstance(order_type, tp.Iterable):
|
40
|
+
order_type = (order_type,)
|
41
|
+
|
42
|
+
self._order_type: list[OrderType] = [self.__cast_to_OrderType(x) for x in order_type]
|
43
|
+
|
44
|
+
def __cast_to_OrderType(self, _value: tp.Any) -> tp.Iterable[OrderType]:
|
45
|
+
if isinstance(_value, OrderType):
|
46
|
+
return _value
|
47
|
+
|
48
|
+
if isinstance(_value, str):
|
49
|
+
try:
|
50
|
+
return OrderType(_value)
|
51
|
+
except Exception:
|
52
|
+
pass
|
53
|
+
raise Exception(f"order_type param only can be 'ASC' or 'DESC' string or '{OrderType.__name__}' enum")
|
54
|
+
|
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"]
|
@@ -0,0 +1,84 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Type, override, Any, TYPE_CHECKING, Optional
|
3
|
+
|
4
|
+
from ormlambda import Table, Column
|
5
|
+
from ormlambda.caster.caster import Caster
|
6
|
+
from .where import Where
|
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
|
16
|
+
|
17
|
+
|
18
|
+
class UpdateKeyError(KeyError):
|
19
|
+
def __init__(self, table: Type[Table], key: str | ColumnType, *args):
|
20
|
+
super().__init__(*args)
|
21
|
+
self._table: Type[Table] = table
|
22
|
+
self._key: str | ColumnType = key
|
23
|
+
|
24
|
+
def __str__(self):
|
25
|
+
if isinstance(self._key, Column):
|
26
|
+
return f"The column '{self._key.column_name}' does not belong to the table '{self._table.__table_name__}'; it belongs to the table '{self._key.table.__table_name__}'. Please check the columns in the query."
|
27
|
+
return f"The column '{self._key}' does not belong to the table '{self._table.__table_name__}'. Please check the columns in the query."
|
28
|
+
|
29
|
+
|
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
|
36
|
+
|
37
|
+
@override
|
38
|
+
@property
|
39
|
+
def CLAUSE(self) -> str:
|
40
|
+
return "UPDATE"
|
41
|
+
|
42
|
+
@override
|
43
|
+
def execute(self) -> None:
|
44
|
+
if self._where:
|
45
|
+
for where in self._where:
|
46
|
+
query_with_table = where.query(self._engine.dialect)
|
47
|
+
for x in where._comparer:
|
48
|
+
# TODOH []: Refactor this part. We need to get only the columns withouth __table_name__ preffix
|
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)
|
51
|
+
|
52
|
+
@override
|
53
|
+
def update[TProp](self, dicc: dict[str | ColumnType[TProp], Any]) -> None:
|
54
|
+
if not isinstance(dicc, dict):
|
55
|
+
raise TypeError
|
56
|
+
|
57
|
+
col_names: list[Column] = []
|
58
|
+
CASTER = self._engine.dialect.caster()
|
59
|
+
for col, value in dicc.items():
|
60
|
+
if isinstance(col, str):
|
61
|
+
if not hasattr(self._model, col):
|
62
|
+
raise UpdateKeyError(self._model, col)
|
63
|
+
col = getattr(self._model, col)
|
64
|
+
if not isinstance(col, Column):
|
65
|
+
raise ValueError
|
66
|
+
|
67
|
+
if self.__is_valid__(col):
|
68
|
+
clean_data = CASTER.for_value(value)
|
69
|
+
col_names.append((col.column_name, clean_data.wildcard_to_insert()))
|
70
|
+
self._values.append(clean_data.to_database)
|
71
|
+
|
72
|
+
set_query: str = ",".join(["=".join(col_data) for col_data in col_names])
|
73
|
+
|
74
|
+
self._query = f"{self.CLAUSE} {self._model.__table_name__} SET {set_query}"
|
75
|
+
self._values = tuple(self._values)
|
76
|
+
return None
|
77
|
+
|
78
|
+
def __is_valid__(self, col: Column) -> bool:
|
79
|
+
if self._model is not col.table:
|
80
|
+
raise UpdateKeyError(self._model, col)
|
81
|
+
return not col.is_auto_generated
|
82
|
+
|
83
|
+
|
84
|
+
__all__ = ["Update"]
|
@@ -0,0 +1,77 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import override, TYPE_CHECKING
|
3
|
+
|
4
|
+
if TYPE_CHECKING:
|
5
|
+
from ormlambda.engine import Engine
|
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
|
13
|
+
|
14
|
+
|
15
|
+
class Upsert[T: Table, TRepo](NonQueryBase[T, TRepo], IUpsert[T], ClauseElement):
|
16
|
+
__visit_name__ = "upsert"
|
17
|
+
|
18
|
+
def __init__(self, model: T, repository: BaseRepository[TRepo], engine: Engine) -> None:
|
19
|
+
super().__init__(model, repository, engine=engine)
|
20
|
+
|
21
|
+
@override
|
22
|
+
@property
|
23
|
+
def CLAUSE(self) -> str:
|
24
|
+
return "ON DUPLICATE KEY UPDATE"
|
25
|
+
|
26
|
+
@override
|
27
|
+
def execute(self) -> None:
|
28
|
+
return self._engine.repository.executemany_with_values(self._query, self._values)
|
29
|
+
|
30
|
+
@override
|
31
|
+
def upsert(self, instances: T | list[T]) -> None:
|
32
|
+
"""
|
33
|
+
Esta funcion se enfoca para trabajar con listas, aunque el argumneto changes sea un unico diccionario.
|
34
|
+
|
35
|
+
Accedemos a la primera posicion de la lista 'changes[0]' porque en la query solo estamos poniendo marcadores de posicion, alias y nombres de columnas
|
36
|
+
|
37
|
+
EXAMPLE
|
38
|
+
------
|
39
|
+
|
40
|
+
MySQL
|
41
|
+
-----
|
42
|
+
|
43
|
+
INSERT INTO NAME_TABLE(PK_COL,COL2)
|
44
|
+
VALUES
|
45
|
+
(1,'PABLO'),
|
46
|
+
(2,'MARINA') AS _val
|
47
|
+
ON DUPLICATE KEY UPDATE
|
48
|
+
COL2 = _val.COL2;
|
49
|
+
|
50
|
+
Python
|
51
|
+
-----
|
52
|
+
|
53
|
+
INSERT INTO NAME_TABLE(PK_COL,COL2)
|
54
|
+
VALUES (%s, %s') AS _val
|
55
|
+
ON DUPLICATE KEY UPDATE
|
56
|
+
COL2 = _val.COL2;
|
57
|
+
|
58
|
+
"""
|
59
|
+
insert = Insert(self._model, self._engine.repository, self._engine.dialect)
|
60
|
+
insert.insert(instances)
|
61
|
+
|
62
|
+
if isinstance(instances, Table):
|
63
|
+
instances = tuple([instances])
|
64
|
+
ALIAS = "VALUES"
|
65
|
+
|
66
|
+
cols = instances[0].get_columns()
|
67
|
+
pk_key = instances[0].get_pk().column_name
|
68
|
+
|
69
|
+
alternative = ", ".join([f"{col}={ALIAS}({col})" for col in cols if col != pk_key])
|
70
|
+
query = f"{insert._query} {self.CLAUSE} {alternative};"
|
71
|
+
|
72
|
+
self._query = query
|
73
|
+
self._values = insert.values
|
74
|
+
return None
|
75
|
+
|
76
|
+
|
77
|
+
__all__ = ["Upsert"]
|
@@ -0,0 +1,65 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import typing as tp
|
3
|
+
from ormlambda.sql.comparer import Comparer
|
4
|
+
from ormlambda.sql.clause_info import AggregateFunctionBase
|
5
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
6
|
+
from ormlambda.sql.elements import ClauseElement
|
7
|
+
|
8
|
+
if tp.TYPE_CHECKING:
|
9
|
+
from ormlambda.dialects import Dialect
|
10
|
+
|
11
|
+
|
12
|
+
class Where(AggregateFunctionBase, ClauseElement):
|
13
|
+
"""
|
14
|
+
The purpose of this class is to create 'WHERE' condition queries properly.
|
15
|
+
"""
|
16
|
+
|
17
|
+
__visit_name__ = "where"
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
*comparer: Comparer,
|
22
|
+
restrictive: bool = True,
|
23
|
+
context: ClauseContextType = None,
|
24
|
+
) -> None:
|
25
|
+
self._comparer: tuple[Comparer] = comparer
|
26
|
+
self._restrictive: bool = restrictive
|
27
|
+
self._context: ClauseContextType = context if context else ClauseInfoContext()
|
28
|
+
|
29
|
+
@staticmethod
|
30
|
+
def FUNCTION_NAME() -> str:
|
31
|
+
return "WHERE"
|
32
|
+
|
33
|
+
def query(self, dialect: Dialect, **kwargs) -> str:
|
34
|
+
if isinstance(self._comparer, tp.Iterable):
|
35
|
+
context = ClauseInfoContext(table_context=self._context._table_context)
|
36
|
+
comparer = Comparer.join_comparers(
|
37
|
+
self._comparer,
|
38
|
+
restrictive=self._restrictive,
|
39
|
+
context=context,
|
40
|
+
dialect=dialect,
|
41
|
+
**kwargs,
|
42
|
+
)
|
43
|
+
else:
|
44
|
+
comparer = self._comparer
|
45
|
+
return f"{self.FUNCTION_NAME()} {comparer}"
|
46
|
+
|
47
|
+
@property
|
48
|
+
def alias_clause(self) -> None:
|
49
|
+
return None
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def join_condition(cls, wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext, dialect: Dialect = None) -> str:
|
53
|
+
if not isinstance(wheres, tp.Iterable):
|
54
|
+
wheres = (wheres,)
|
55
|
+
|
56
|
+
comparers: list[Comparer] = []
|
57
|
+
for where in wheres:
|
58
|
+
for c in where._comparer:
|
59
|
+
if not c._context:
|
60
|
+
c._context = context
|
61
|
+
comparers.append(c)
|
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
|