ormlambda 0.1.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 +4 -0
- ormlambda/common/__init__.py +2 -0
- ormlambda/common/abstract_classes/__init__.py +3 -0
- ormlambda/common/abstract_classes/abstract_model.py +302 -0
- ormlambda/common/abstract_classes/non_query_base.py +33 -0
- ormlambda/common/abstract_classes/query_base.py +10 -0
- ormlambda/common/enums/__init__.py +2 -0
- ormlambda/common/enums/condition_types.py +16 -0
- ormlambda/common/enums/join_type.py +10 -0
- ormlambda/common/interfaces/INonQueryCommand.py +9 -0
- ormlambda/common/interfaces/IQueryCommand.py +11 -0
- ormlambda/common/interfaces/IRepositoryBase.py +67 -0
- ormlambda/common/interfaces/IStatements.py +227 -0
- ormlambda/common/interfaces/__init__.py +4 -0
- ormlambda/components/__init__.py +0 -0
- ormlambda/components/delete/IDelete.py +6 -0
- ormlambda/components/delete/__init__.py +2 -0
- ormlambda/components/delete/abstract_delete.py +14 -0
- ormlambda/components/insert/IInsert.py +6 -0
- ormlambda/components/insert/__init__.py +2 -0
- ormlambda/components/insert/abstract_insert.py +21 -0
- ormlambda/components/select/ISelect.py +14 -0
- ormlambda/components/select/__init__.py +2 -0
- ormlambda/components/select/table_column.py +39 -0
- ormlambda/components/update/IUpdate.py +7 -0
- ormlambda/components/update/__init__.py +2 -0
- ormlambda/components/update/abstract_update.py +25 -0
- ormlambda/components/upsert/IUpsert.py +6 -0
- ormlambda/components/upsert/__init__.py +2 -0
- ormlambda/components/upsert/abstract_upsert.py +21 -0
- ormlambda/components/where/__init__.py +1 -0
- ormlambda/components/where/abstract_where.py +11 -0
- ormlambda/databases/__init__.py +0 -0
- ormlambda/databases/my_sql/__init__.py +2 -0
- ormlambda/databases/my_sql/clauses/__init__.py +13 -0
- ormlambda/databases/my_sql/clauses/create_database.py +29 -0
- ormlambda/databases/my_sql/clauses/delete.py +54 -0
- ormlambda/databases/my_sql/clauses/drop_database.py +19 -0
- ormlambda/databases/my_sql/clauses/drop_table.py +23 -0
- ormlambda/databases/my_sql/clauses/insert.py +70 -0
- ormlambda/databases/my_sql/clauses/joins.py +103 -0
- ormlambda/databases/my_sql/clauses/limit.py +17 -0
- ormlambda/databases/my_sql/clauses/offset.py +17 -0
- ormlambda/databases/my_sql/clauses/order.py +29 -0
- ormlambda/databases/my_sql/clauses/select.py +172 -0
- ormlambda/databases/my_sql/clauses/update.py +52 -0
- ormlambda/databases/my_sql/clauses/upsert.py +68 -0
- ormlambda/databases/my_sql/clauses/where_condition.py +219 -0
- ormlambda/databases/my_sql/repository.py +192 -0
- ormlambda/databases/my_sql/statements.py +86 -0
- ormlambda/model_base.py +36 -0
- ormlambda/utils/__init__.py +3 -0
- ormlambda/utils/column.py +65 -0
- ormlambda/utils/dtypes.py +104 -0
- ormlambda/utils/foreign_key.py +36 -0
- ormlambda/utils/lambda_disassembler/__init__.py +4 -0
- ormlambda/utils/lambda_disassembler/dis_types.py +136 -0
- ormlambda/utils/lambda_disassembler/disassembler.py +69 -0
- ormlambda/utils/lambda_disassembler/dtypes.py +103 -0
- ormlambda/utils/lambda_disassembler/name_of.py +41 -0
- ormlambda/utils/lambda_disassembler/nested_element.py +44 -0
- ormlambda/utils/lambda_disassembler/tree_instruction.py +145 -0
- ormlambda/utils/module_tree/__init__.py +0 -0
- ormlambda/utils/module_tree/dfs_traversal.py +60 -0
- ormlambda/utils/module_tree/dynamic_module.py +237 -0
- ormlambda/utils/table_constructor.py +308 -0
- ormlambda-0.1.0.dist-info/LICENSE +21 -0
- ormlambda-0.1.0.dist-info/METADATA +268 -0
- ormlambda-0.1.0.dist-info/RECORD +70 -0
- ormlambda-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
from typing import override, Callable, overload, Optional, TypeVar
|
2
|
+
|
3
|
+
# from ..table import Table
|
4
|
+
|
5
|
+
from ....common.interfaces.IQueryCommand import IQuery
|
6
|
+
from ....utils.lambda_disassembler import Disassembler
|
7
|
+
from ....common.enums import JoinType
|
8
|
+
|
9
|
+
# TODOL: Try to import Table module without circular import Error
|
10
|
+
Table = TypeVar("Table")
|
11
|
+
|
12
|
+
|
13
|
+
class JoinSelector[TLeft, TRight](IQuery):
|
14
|
+
__slots__: tuple = (
|
15
|
+
"_orig_table",
|
16
|
+
"_table_right",
|
17
|
+
"_by",
|
18
|
+
"_left_col",
|
19
|
+
"_right_col",
|
20
|
+
"_compareop",
|
21
|
+
)
|
22
|
+
|
23
|
+
@overload
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
table_left: TLeft,
|
27
|
+
table_right: TRight,
|
28
|
+
col_left: str,
|
29
|
+
col_right: str,
|
30
|
+
by: JoinType,
|
31
|
+
) -> None: ...
|
32
|
+
|
33
|
+
@overload
|
34
|
+
def __init__(
|
35
|
+
self,
|
36
|
+
table_left: TLeft,
|
37
|
+
table_right: TRight,
|
38
|
+
by: JoinType,
|
39
|
+
where: Callable[[TLeft, TRight], bool],
|
40
|
+
) -> None: ...
|
41
|
+
|
42
|
+
def __init__(
|
43
|
+
self,
|
44
|
+
table_left: Table,
|
45
|
+
table_right: Table,
|
46
|
+
by: JoinType,
|
47
|
+
col_left: Optional[str] = None,
|
48
|
+
col_right: Optional[str] = None,
|
49
|
+
where: Optional[Callable[[TLeft, TRight], bool]] = None,
|
50
|
+
) -> None:
|
51
|
+
self._orig_table: Table = table_left
|
52
|
+
self._table_right: Table = table_right
|
53
|
+
self._by: JoinType = by
|
54
|
+
|
55
|
+
if all(x is None for x in (col_left, col_right, where)):
|
56
|
+
raise ValueError("You must specify at least 'where' clausule or ('_left_col',_right_col')")
|
57
|
+
|
58
|
+
if where is None:
|
59
|
+
self._left_col: str = col_left
|
60
|
+
self._right_col: str = col_right
|
61
|
+
self._compareop: str = "="
|
62
|
+
else:
|
63
|
+
_dis: Disassembler[TLeft, TRight] = Disassembler[TLeft, TRight](where)
|
64
|
+
self._left_col: str = _dis.cond_1.name
|
65
|
+
self._right_col: str = _dis.cond_2.name
|
66
|
+
self._compareop: str = _dis.compare_op
|
67
|
+
|
68
|
+
def __eq__(self, __value: "JoinSelector") -> bool:
|
69
|
+
return isinstance(__value, JoinSelector) and self.__hash__() == __value.__hash__()
|
70
|
+
|
71
|
+
def __hash__(self) -> int:
|
72
|
+
return hash(
|
73
|
+
(
|
74
|
+
self._orig_table,
|
75
|
+
self._table_right,
|
76
|
+
self._by,
|
77
|
+
self._left_col,
|
78
|
+
self._right_col,
|
79
|
+
self._compareop,
|
80
|
+
)
|
81
|
+
)
|
82
|
+
|
83
|
+
@classmethod
|
84
|
+
def join_selectors(cls, *args: "JoinSelector") -> str:
|
85
|
+
return "\n".join([x.query for x in args])
|
86
|
+
|
87
|
+
@property
|
88
|
+
@override
|
89
|
+
def query(self) -> str:
|
90
|
+
# {inner join} table_name on
|
91
|
+
# table_name.first col = table_name.second_col
|
92
|
+
|
93
|
+
left_col = f"{self._orig_table.__table_name__}.{self._left_col}"
|
94
|
+
right_col = f"{self._table_right.__table_name__}.{self._right_col}"
|
95
|
+
list_ = [
|
96
|
+
self._by.value, # inner join
|
97
|
+
self._table_right.__table_name__, # table_name
|
98
|
+
"ON",
|
99
|
+
left_col, # first_col
|
100
|
+
self._compareop, # =
|
101
|
+
right_col, # second_col
|
102
|
+
]
|
103
|
+
return " ".join(list_)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from ....common.interfaces.IQueryCommand import IQuery
|
4
|
+
|
5
|
+
|
6
|
+
class LimitQuery(IQuery):
|
7
|
+
LIMIT = "LIMIT"
|
8
|
+
|
9
|
+
def __init__(self, number: int) -> None:
|
10
|
+
if not isinstance(number, int):
|
11
|
+
raise ValueError
|
12
|
+
self._number: int = number
|
13
|
+
|
14
|
+
@override
|
15
|
+
@property
|
16
|
+
def query(self) -> str:
|
17
|
+
return f"{self.LIMIT} {self._number}"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from ....common.interfaces.IQueryCommand import IQuery
|
4
|
+
|
5
|
+
|
6
|
+
class OffsetQuery(IQuery):
|
7
|
+
OFFSET = "OFFSET"
|
8
|
+
|
9
|
+
def __init__(self, number: int) -> None:
|
10
|
+
if not isinstance(number, int):
|
11
|
+
raise ValueError
|
12
|
+
self._number: int = number
|
13
|
+
|
14
|
+
@override
|
15
|
+
@property
|
16
|
+
def query(self) -> str:
|
17
|
+
return f"{self.OFFSET} {self._number}"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
from typing import override, Callable
|
2
|
+
|
3
|
+
from ....utils.lambda_disassembler.tree_instruction import TreeInstruction
|
4
|
+
from ....common.interfaces.IQueryCommand import IQuery
|
5
|
+
from ....common.interfaces.IStatements import OrderType
|
6
|
+
|
7
|
+
ASC = "ASC"
|
8
|
+
DESC = "DESC"
|
9
|
+
|
10
|
+
|
11
|
+
class OrderQuery[T](IQuery):
|
12
|
+
ORDER = "ORDER BY"
|
13
|
+
|
14
|
+
def __init__(self, instance: T, order_lambda: Callable[[T], None], order_type: OrderType) -> None:
|
15
|
+
if not self._valid_order_type(order_type):
|
16
|
+
raise Exception("order_type only can be 'ASC' or 'DESC'")
|
17
|
+
|
18
|
+
self._instance: T = instance
|
19
|
+
self._order_lambda: Callable[[T], None] = order_lambda
|
20
|
+
self._order_type: str = order_type
|
21
|
+
self._column: str = TreeInstruction(order_lambda).to_list()[0].nested_element.name
|
22
|
+
|
23
|
+
def _valid_order_type(self, _value: str) -> bool:
|
24
|
+
return _value in (ASC, DESC)
|
25
|
+
|
26
|
+
@override
|
27
|
+
@property
|
28
|
+
def query(self) -> str:
|
29
|
+
return f"{self.ORDER} {self._instance.__table_name__}.{self._column} {self._order_type}"
|
@@ -0,0 +1,172 @@
|
|
1
|
+
from typing import Callable, Optional, Type, override
|
2
|
+
import inspect
|
3
|
+
|
4
|
+
from ....utils.lambda_disassembler import TreeInstruction, TupleInstruction, NestedElement
|
5
|
+
from ....components.select import ISelect, TableColumn
|
6
|
+
from ....utils import Table, ForeignKey
|
7
|
+
from ....utils.table_constructor import TableMeta
|
8
|
+
|
9
|
+
from . import JoinSelector, JoinType
|
10
|
+
|
11
|
+
|
12
|
+
class SelectQuery[T: Table, *Ts](ISelect):
|
13
|
+
SELECT = "SELECT"
|
14
|
+
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
tables: T | tuple[T, *Ts] = (),
|
18
|
+
select_lambda: Optional[Callable[[T, *Ts], None]] = lambda: None,
|
19
|
+
*,
|
20
|
+
by: JoinType = JoinType.INNER_JOIN,
|
21
|
+
) -> None:
|
22
|
+
if not isinstance(tables, tuple):
|
23
|
+
tables = tuple([tables])
|
24
|
+
|
25
|
+
self._first_table: T = tables[0]
|
26
|
+
self._tables: tuple[T, *Ts] = tables
|
27
|
+
self._select_lambda: Optional[Callable[[T, *Ts], None]] = select_lambda
|
28
|
+
self._by: JoinType = by
|
29
|
+
|
30
|
+
self._tables_heritage: list[tuple[Table, Table]] = []
|
31
|
+
self._lambda_var_to_table_dicc: dict[str, Table] = self._assign_lambda_variables_to_table(select_lambda)
|
32
|
+
|
33
|
+
self._select_list: list[TableColumn] = self._rename_recursive_column_list(select_lambda)
|
34
|
+
|
35
|
+
def _rename_recursive_column_list(self, _lambda: Optional[Callable[[T], None]]) -> list[TableColumn]:
|
36
|
+
"""
|
37
|
+
Recursive function tu replace variable names by Select Query
|
38
|
+
|
39
|
+
lambda a: (a.pk_address, a.city.pk_city, a.city.country.pk_country)
|
40
|
+
|
41
|
+
>>> # convert lambda expression into list of values
|
42
|
+
>>> select_list = [
|
43
|
+
>>> "a.pk_address",
|
44
|
+
>>> "a.city",
|
45
|
+
>>> "a.city.pk_city",
|
46
|
+
>>> "a.city.country",
|
47
|
+
>>> "a.city.country.pk_country",
|
48
|
+
>>> ]
|
49
|
+
>>> result = _rename_recursive_column_list(select_list)
|
50
|
+
>>> print(result)
|
51
|
+
>>> # result = [
|
52
|
+
>>> # "address.pk_address"
|
53
|
+
>>> # "city.*"
|
54
|
+
>>> # "city.pk_city"
|
55
|
+
>>> # "country.*"
|
56
|
+
>>> # "country.pk_country"
|
57
|
+
]
|
58
|
+
"""
|
59
|
+
instruction_list: list[TupleInstruction] = TreeInstruction(_lambda).to_list()
|
60
|
+
column_list: list[TableColumn] = []
|
61
|
+
|
62
|
+
for ti in instruction_list:
|
63
|
+
obj = self._lambda_var_to_table_dicc[ti.var]
|
64
|
+
|
65
|
+
var = obj.__table_name__
|
66
|
+
new_nested = ti.nested_element.parents
|
67
|
+
new_nested[0] = var
|
68
|
+
ti = TupleInstruction(var, NestedElement(new_nested))
|
69
|
+
self._get_parents(obj, ti, column_list)
|
70
|
+
return column_list
|
71
|
+
|
72
|
+
def _get_parents(self, tbl_obj: Table, tuple_inst: TupleInstruction, column_list: list[TableColumn]) -> None:
|
73
|
+
if self._user_want_all_col(tbl_obj, tuple_inst):
|
74
|
+
column_list.extend(list(TableColumn.all_columns(tbl_obj)))
|
75
|
+
return None
|
76
|
+
|
77
|
+
# if the 'last_el' var is a property, we'll know the user will want retrieve a column of the same instance of the 'tbl_obj'. Otherwise the user will want to get a column of the other instance
|
78
|
+
last_el: str = tuple_inst.nested_element.name
|
79
|
+
if self._user_want_column_of_the_same_table(tbl_obj, tuple_inst):
|
80
|
+
return column_list.append(TableColumn(tbl_obj, last_el))
|
81
|
+
|
82
|
+
parents: list[str] = tuple_inst.nested_element.parents
|
83
|
+
first_el = parents[1]
|
84
|
+
new_ti = TupleInstruction(first_el, NestedElement[str](parents[1:])) # create new TupleInstruction from the second parent to the top
|
85
|
+
new_attr = self.get_attribute_of(tbl_obj, first_el) # could be Table or property
|
86
|
+
|
87
|
+
self._add_fk_relationship(tbl_obj, new_attr)
|
88
|
+
return self._get_parents(new_attr, new_ti, column_list)
|
89
|
+
|
90
|
+
def _add_fk_relationship(self, t1: Table, t2: Table) -> None:
|
91
|
+
tuple_ = tuple([t1, t2])
|
92
|
+
if tuple_ not in self._tables_heritage:
|
93
|
+
self._tables_heritage.append(tuple_)
|
94
|
+
return None
|
95
|
+
|
96
|
+
@staticmethod
|
97
|
+
def _user_want_all_col(tbl: Table, ti: TupleInstruction) -> bool:
|
98
|
+
"""
|
99
|
+
if ti.nested_element.parents length is 1 says that the element is the table itself (table.*)
|
100
|
+
"""
|
101
|
+
return issubclass(tbl.__class__, Table | TableMeta) and len(ti.nested_element.parents) == 1
|
102
|
+
|
103
|
+
def _user_want_column_of_the_same_table(self, table: Table, ti: TupleInstruction) -> bool:
|
104
|
+
last_el: str = ti.nested_element.name
|
105
|
+
first_el = ti.nested_element.parents[1]
|
106
|
+
|
107
|
+
table_attr = self.get_attribute_of(table, first_el)
|
108
|
+
|
109
|
+
return last_el in table.__dict__ and isinstance(table_attr, property)
|
110
|
+
|
111
|
+
@staticmethod
|
112
|
+
def get_attribute_of[TProp: Table](table: TProp, _value: str) -> Optional[TProp | property]:
|
113
|
+
try:
|
114
|
+
return getattr(table.__class__, _value)
|
115
|
+
except Exception:
|
116
|
+
return getattr(table, _value, None)
|
117
|
+
|
118
|
+
def _assign_lambda_variables_to_table(self, _lambda: Callable[[T], None]) -> dict[str, Type[Table]]:
|
119
|
+
"""
|
120
|
+
return a dictionary with the lambda's parameters as keys and Type[Table] as the values
|
121
|
+
|
122
|
+
|
123
|
+
>>> res = _assign_lambda_variables_to_table(lambda a,ci,co: ...)
|
124
|
+
>>> print(res)
|
125
|
+
>>> # {
|
126
|
+
>>> # "a": Address,
|
127
|
+
>>> # "ci": City,
|
128
|
+
>>> # "co": Country,
|
129
|
+
>>> # }
|
130
|
+
"""
|
131
|
+
lambda_vars = tuple(inspect.signature(_lambda).parameters)
|
132
|
+
|
133
|
+
dicc: dict[str, Table] = {}
|
134
|
+
for i in range(len(lambda_vars)):
|
135
|
+
dicc[lambda_vars[i]] = self._tables[i]
|
136
|
+
return dicc
|
137
|
+
|
138
|
+
def _convert_select_list(self) -> str:
|
139
|
+
self._select_list = self._select_list if self._select_list else tuple(TableColumn.all_columns(self._first_table))
|
140
|
+
|
141
|
+
return ", ".join(col.column for col in self._select_list)
|
142
|
+
|
143
|
+
@override
|
144
|
+
@property
|
145
|
+
def query(self) -> str:
|
146
|
+
select_str = self._convert_select_list()
|
147
|
+
query: str = f"{self.SELECT} {select_str} FROM {self._first_table.__table_name__}"
|
148
|
+
|
149
|
+
involved_tables = self.get_involved_tables()
|
150
|
+
if not involved_tables:
|
151
|
+
return query
|
152
|
+
|
153
|
+
sub_query: str = ""
|
154
|
+
for l_tbl, r_tbl in involved_tables:
|
155
|
+
join = JoinSelector(l_tbl, r_tbl, by=self._by, where=ForeignKey.MAPPED[l_tbl][r_tbl])
|
156
|
+
sub_query += f" {join.query}"
|
157
|
+
|
158
|
+
query += sub_query
|
159
|
+
return query
|
160
|
+
|
161
|
+
@override
|
162
|
+
@property
|
163
|
+
def select_list(self) -> list[TableColumn]:
|
164
|
+
return self._select_list
|
165
|
+
|
166
|
+
@override
|
167
|
+
@property
|
168
|
+
def tables_heritage(self) -> list[tuple[Table, Table]]:
|
169
|
+
return self._tables_heritage
|
170
|
+
|
171
|
+
def get_involved_tables(self) -> tuple[tuple[Table, Table]]:
|
172
|
+
return tuple(self._tables_heritage)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from typing import Type, override, Any
|
2
|
+
from mysql.connector import MySQLConnection
|
3
|
+
|
4
|
+
from ....components.update import UpdateQueryBase
|
5
|
+
from ....utils import Table, Column
|
6
|
+
from ....common.interfaces import IRepositoryBase
|
7
|
+
from .where_condition import WhereCondition
|
8
|
+
|
9
|
+
|
10
|
+
class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase[MySQLConnection]]):
|
11
|
+
def __init__(self, model: T, repository: Any, where: list[WhereCondition]) -> None:
|
12
|
+
super().__init__(model, repository, where)
|
13
|
+
|
14
|
+
@override
|
15
|
+
@property
|
16
|
+
def CLAUSE(self) -> str:
|
17
|
+
return "UPDATE"
|
18
|
+
|
19
|
+
@override
|
20
|
+
def execute(self) -> None:
|
21
|
+
if self._where:
|
22
|
+
self._query += " " + WhereCondition.join_condition(*self._where)
|
23
|
+
return self._repository.execute_with_values(self._query, self._values)
|
24
|
+
|
25
|
+
@override
|
26
|
+
def update(self, dicc: Any | dict[str | property, Any]) -> None:
|
27
|
+
if not isinstance(dicc, dict):
|
28
|
+
raise TypeError
|
29
|
+
|
30
|
+
name_cols: list[str] = []
|
31
|
+
|
32
|
+
for col, value in dicc.items():
|
33
|
+
if isinstance(col, str):
|
34
|
+
string_col = col
|
35
|
+
else:
|
36
|
+
string_col = self._model.__properties_mapped__.get(col, None)
|
37
|
+
if not string_col:
|
38
|
+
raise KeyError(f"Class '{self._model.__name__}' has not {col} mapped.")
|
39
|
+
if self.__is_valid__(string_col, value):
|
40
|
+
name_cols.append(string_col)
|
41
|
+
self._values.append(value)
|
42
|
+
|
43
|
+
set_query: str = ",".join(["=".join([col, "%s"]) for col in name_cols])
|
44
|
+
|
45
|
+
self._query = f"{self.CLAUSE} {self._model.__table_name__} SET {set_query}"
|
46
|
+
return None
|
47
|
+
|
48
|
+
def __is_valid__(self, col: str, value: Any) -> bool:
|
49
|
+
instance_table: Table = self._model(**{col: value})
|
50
|
+
|
51
|
+
column: Column = getattr(instance_table, f"_{col}")
|
52
|
+
return not column.is_auto_generated
|
@@ -0,0 +1,68 @@
|
|
1
|
+
from typing import override, Any
|
2
|
+
|
3
|
+
from ....utils import Table
|
4
|
+
from ....components.upsert import UpsertQueryBase
|
5
|
+
from ....common.interfaces import IRepositoryBase
|
6
|
+
from mysql.connector import MySQLConnection
|
7
|
+
|
8
|
+
from .insert import InsertQuery
|
9
|
+
|
10
|
+
|
11
|
+
class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase[MySQLConnection]]):
|
12
|
+
def __init__(self, model: T, repository: Any) -> None:
|
13
|
+
super().__init__(model, repository)
|
14
|
+
|
15
|
+
@override
|
16
|
+
@property
|
17
|
+
def CLAUSE(self) -> str:
|
18
|
+
return "ON DUPLICATE KEY UPDATE"
|
19
|
+
|
20
|
+
@override
|
21
|
+
def execute(self) -> None:
|
22
|
+
return self._repository.executemany_with_values(self._query, self._values)
|
23
|
+
|
24
|
+
@override
|
25
|
+
def upsert(self, instances: T | list[T]) -> None:
|
26
|
+
"""
|
27
|
+
Esta funcion se enfoca para trabajar con listas, aunque el argumneto changes sea un unico diccionario.
|
28
|
+
|
29
|
+
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
|
30
|
+
|
31
|
+
EXAMPLE
|
32
|
+
------
|
33
|
+
|
34
|
+
MySQL
|
35
|
+
-----
|
36
|
+
|
37
|
+
INSERT INTO NAME_TABLE(PK_COL,COL2)
|
38
|
+
VALUES
|
39
|
+
(1,'PABLO'),
|
40
|
+
(2,'MARINA') AS _val
|
41
|
+
ON DUPLICATE KEY UPDATE
|
42
|
+
COL2 = _val.COL2;
|
43
|
+
|
44
|
+
Python
|
45
|
+
-----
|
46
|
+
|
47
|
+
INSERT INTO NAME_TABLE(PK_COL,COL2)
|
48
|
+
VALUES (%s, %s') AS _val
|
49
|
+
ON DUPLICATE KEY UPDATE
|
50
|
+
COL2 = _val.COL2;
|
51
|
+
|
52
|
+
"""
|
53
|
+
insert = InsertQuery[T](self._model, self._repository)
|
54
|
+
insert.insert(instances)
|
55
|
+
|
56
|
+
if isinstance(instances, Table):
|
57
|
+
instances = tuple([instances])
|
58
|
+
ALIAS = "VALUES"
|
59
|
+
|
60
|
+
cols = instances[0].get_columns()
|
61
|
+
pk_key = instances[0].get_pk().column_name
|
62
|
+
|
63
|
+
alternative = ", ".join([f"{col}={ALIAS}({col})" for col in cols if col != pk_key])
|
64
|
+
query = f"{insert._query} {self.CLAUSE} {alternative};"
|
65
|
+
|
66
|
+
self._query = query
|
67
|
+
self._values = insert.values
|
68
|
+
return None
|
@@ -0,0 +1,219 @@
|
|
1
|
+
from typing import Any, Callable, Optional, override
|
2
|
+
import inspect
|
3
|
+
|
4
|
+
from ....common.enums import ConditionType
|
5
|
+
from ....utils.lambda_disassembler.tree_instruction import TreeInstruction, TupleInstruction
|
6
|
+
from ....common.interfaces.IQueryCommand import IQuery
|
7
|
+
from ....components.where.abstract_where import AbstractWhere
|
8
|
+
from ....utils import Table
|
9
|
+
|
10
|
+
|
11
|
+
class WhereConditionByArg[TProp1, TProp2](IQuery):
|
12
|
+
def __init__(self, cond1: TProp1, cond2: TProp2, symbol: ConditionType) -> None:
|
13
|
+
self.cond1: TProp1 = cond1
|
14
|
+
self.cond2: TProp2 = cond2
|
15
|
+
self.symbol: ConditionType = symbol
|
16
|
+
|
17
|
+
@property
|
18
|
+
def query(self) -> str:
|
19
|
+
return f"WHERE {self.cond1} {self.symbol.value} {self.cond2}"
|
20
|
+
|
21
|
+
|
22
|
+
class WhereCondition[*Inst](AbstractWhere):
|
23
|
+
"""
|
24
|
+
The purpose of this class is to create 'WHERE' condition queries properly.
|
25
|
+
|
26
|
+
Args.
|
27
|
+
- instances: tuple[*Inst],
|
28
|
+
- passed all instance that we are going to use inside of `function` arg
|
29
|
+
|
30
|
+
- function: Callable[[*Inst], bool] = lambda: None,
|
31
|
+
- lambda function to create condition between instance variables
|
32
|
+
- **kwargs: Any,
|
33
|
+
- We use this clause by passing all the variables that we want to replace inside the lambda function.
|
34
|
+
When we try to disassemble the lambda function, we see that the variables were not replaced by their values.
|
35
|
+
Instead, we only got the variable names, not the values.
|
36
|
+
Due to this problem, we need to specify the correct dictionary to map variable names to their values.
|
37
|
+
|
38
|
+
>>> var = 100
|
39
|
+
>>> _lambda = lambda a: a.city_id <= var
|
40
|
+
>>> ... #Dissamble _lambda method
|
41
|
+
>>> parts_of_lambda = [
|
42
|
+
>>> "city_id"
|
43
|
+
>>> "<="
|
44
|
+
>>> "var" <-------- We excepted 100
|
45
|
+
>>> ]
|
46
|
+
"""
|
47
|
+
|
48
|
+
__slots__ = [
|
49
|
+
"_instances",
|
50
|
+
"_function",
|
51
|
+
"_tree",
|
52
|
+
"_kwargs",
|
53
|
+
"_lambda_param_map",
|
54
|
+
]
|
55
|
+
|
56
|
+
def __init__(
|
57
|
+
self,
|
58
|
+
instances: tuple[*Inst],
|
59
|
+
function: Callable[[*Inst], bool] = lambda: None,
|
60
|
+
**kwargs: Any,
|
61
|
+
) -> None:
|
62
|
+
self._instances: tuple[Table] = instances
|
63
|
+
self._function: Callable[[*Inst], bool] = function
|
64
|
+
self._kwargs: dict[str, tuple[*Inst]] = kwargs
|
65
|
+
self._tree: TreeInstruction = TreeInstruction(function)
|
66
|
+
self._lambda_param_map: dict[str, Table] = self._create_lambda_param_map()
|
67
|
+
|
68
|
+
def _create_lambda_param_map(self) -> dict[str, Table]:
|
69
|
+
"""
|
70
|
+
The method is responsible for mapping the variables present in the lambda function so that they are replaced with the instance of the model Table.
|
71
|
+
"""
|
72
|
+
assert len(lamda_param := inspect.signature(self._function).parameters) == len(self._instances)
|
73
|
+
|
74
|
+
_temp_instances = list(self._instances)[::-1] # we copied and translated tuple instance due to pop each value in order to param
|
75
|
+
new_dicc: dict[str, Table] = {}
|
76
|
+
for param in lamda_param.keys():
|
77
|
+
new_dicc[param] = _temp_instances.pop()
|
78
|
+
|
79
|
+
return new_dicc
|
80
|
+
|
81
|
+
@override
|
82
|
+
@property
|
83
|
+
def query(self) -> str:
|
84
|
+
if len(self._tree.compare_op) == 0:
|
85
|
+
return self._build_with_lambda_as_column_name()
|
86
|
+
return self._build_with_lambda_as_condition()
|
87
|
+
|
88
|
+
def _build_with_lambda_as_column_name(self) -> str:
|
89
|
+
conditions, compare_sign = self.create_conditions_list_and_compare_sign()
|
90
|
+
c1, c2 = conditions
|
91
|
+
return f"{self.WHERE} {c1} {compare_sign[0]} {c2}"
|
92
|
+
|
93
|
+
def _replace_values(self, ti: TupleInstruction) -> str:
|
94
|
+
instance: Any = self._kwargs[ti.var]
|
95
|
+
if isinstance(instance, Table):
|
96
|
+
data = getattr(instance, ti.nested_element.name)
|
97
|
+
else:
|
98
|
+
data = instance
|
99
|
+
|
100
|
+
return f"'{data}'" if isinstance(data, str) else data
|
101
|
+
|
102
|
+
def _build_with_lambda_as_condition(self) -> Callable[[], Any]:
|
103
|
+
n: int = len(self._tree.compare_op)
|
104
|
+
dicc_selector: dict[int, Callable[[], str]] = {
|
105
|
+
1: self.__one_sign,
|
106
|
+
2: self.__two_sign,
|
107
|
+
}
|
108
|
+
return dicc_selector[n]()
|
109
|
+
|
110
|
+
def __one_sign(self) -> str:
|
111
|
+
"""lambda x: x <= 10"""
|
112
|
+
(c1, c2), _ = self.create_conditions_list_and_compare_sign()
|
113
|
+
|
114
|
+
return f"{self.WHERE} {c1} {self._tree.compare_op[0]} {c2}"
|
115
|
+
|
116
|
+
def _get_table_for_tuple_instruction(self, ti: TupleInstruction) -> Optional[Table]:
|
117
|
+
if ti.var not in self._lambda_param_map:
|
118
|
+
return None
|
119
|
+
|
120
|
+
involved_tables: list[Table] = [self._lambda_param_map[ti.var]]
|
121
|
+
|
122
|
+
def get_attr_tbl(tbl: Table, class_var: str) -> Optional[Table]:
|
123
|
+
tbl_attrs = (tbl, class_var)
|
124
|
+
if hasattr(*tbl_attrs):
|
125
|
+
attr = getattr(*tbl_attrs)
|
126
|
+
|
127
|
+
if not isinstance(attr, property) and issubclass(attr, Table):
|
128
|
+
return attr
|
129
|
+
return None
|
130
|
+
|
131
|
+
for name in ti.nested_element.parents[1:]:
|
132
|
+
attr = get_attr_tbl(involved_tables[-1], name)
|
133
|
+
if attr is not None:
|
134
|
+
involved_tables.append(attr)
|
135
|
+
|
136
|
+
return involved_tables[-1].__table_name__
|
137
|
+
|
138
|
+
def __two_sign(self) -> str:
|
139
|
+
"""lambda x: 100 <= x <= 500"""
|
140
|
+
self.__valid_between_comparable_sign()
|
141
|
+
conds, _ = self.create_conditions_list_and_compare_sign()
|
142
|
+
c1, c2, c3 = conds
|
143
|
+
cond1 = WhereConditionByArg[str, str](c1, c2, ConditionType(self._tree.compare_op[0]))
|
144
|
+
cond2 = WhereConditionByArg[str, str](c2, c3, ConditionType(self._tree.compare_op[1]))
|
145
|
+
|
146
|
+
return WhereCondition.join_condition(cond1, cond2, restrictive=True)
|
147
|
+
|
148
|
+
def __valid_between_comparable_sign(self) -> bool:
|
149
|
+
if not len(self._tree.compare_op) == 2:
|
150
|
+
raise Exception("Number of comparable signs distinct from 2.")
|
151
|
+
return True
|
152
|
+
|
153
|
+
@classmethod
|
154
|
+
def join_condition(cls, *args: WhereConditionByArg, restrictive=False) -> str:
|
155
|
+
BY: str = "AND" if restrictive else "OR"
|
156
|
+
query: str = f"{cls.WHERE} "
|
157
|
+
|
158
|
+
n = len(args)
|
159
|
+
for i in range(n):
|
160
|
+
condition: IQuery = args[i]
|
161
|
+
query += "(" + condition.query.removeprefix(f"{cls.WHERE} ") + ")"
|
162
|
+
if i != n - 1:
|
163
|
+
query += f" {BY} "
|
164
|
+
|
165
|
+
return query
|
166
|
+
|
167
|
+
@override
|
168
|
+
def get_involved_tables(self) -> tuple[tuple[Table, Table]]:
|
169
|
+
return_involved_tables: list[tuple[Table, Table]] = []
|
170
|
+
involved_tables: list[Table] = [self._instances[0]]
|
171
|
+
|
172
|
+
def get_attr_tbl(instance: Table, tbl_name: str) -> Optional[Table]:
|
173
|
+
inst_tbl_name = (instance, tbl_name)
|
174
|
+
if hasattr(*inst_tbl_name):
|
175
|
+
attr = getattr(*inst_tbl_name)
|
176
|
+
|
177
|
+
if not isinstance(attr, property) and issubclass(attr, Table):
|
178
|
+
return attr
|
179
|
+
return None
|
180
|
+
|
181
|
+
tables: list[str] = self._tree.to_list()[0].nested_element.parents[1:] # Avoid lambda variable
|
182
|
+
for tbl_name in tables:
|
183
|
+
tbl = involved_tables[-1]
|
184
|
+
attr = get_attr_tbl(tbl, tbl_name)
|
185
|
+
if attr is not None:
|
186
|
+
involved_tables.append(attr)
|
187
|
+
return_involved_tables.append(tuple([tbl, attr]))
|
188
|
+
return tuple(return_involved_tables)
|
189
|
+
|
190
|
+
def create_conditions_list_and_compare_sign(self) -> tuple[list[str], list[str]]:
|
191
|
+
compare_sign: list[str] = []
|
192
|
+
conds: list[str] = []
|
193
|
+
for ti in self._tree.to_list():
|
194
|
+
key = ti.var
|
195
|
+
ne = ti.nested_element
|
196
|
+
|
197
|
+
if hasattr(ConditionType, str(ne.name)):
|
198
|
+
cond_type: ConditionType = getattr(ConditionType, ne.name)
|
199
|
+
compare_sign.append(cond_type.value)
|
200
|
+
|
201
|
+
elif key in self._kwargs:
|
202
|
+
conds.append(self._replace_values(ti))
|
203
|
+
else:
|
204
|
+
_name_table = self._get_table_for_tuple_instruction(ti)
|
205
|
+
_name_table_str = f"{_name_table}." if _name_table else ""
|
206
|
+
|
207
|
+
_name = ne.name
|
208
|
+
if not _name_table:
|
209
|
+
_name = self._wrapp_condition_id_str(ne.name)
|
210
|
+
|
211
|
+
conds.append(f"{_name_table_str}{_name}")
|
212
|
+
return conds, compare_sign
|
213
|
+
|
214
|
+
def _wrapp_condition_id_str(self, name: Any):
|
215
|
+
if not name:
|
216
|
+
return "NULL"
|
217
|
+
if not isinstance(name, str):
|
218
|
+
return name
|
219
|
+
return f"'{name}'"
|