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.
Files changed (145) hide show
  1. ormlambda/__init__.py +2 -0
  2. ormlambda/caster/__init__.py +1 -1
  3. ormlambda/caster/caster.py +29 -12
  4. ormlambda/common/abstract_classes/clause_info_converter.py +4 -12
  5. ormlambda/common/abstract_classes/decomposition_query.py +17 -2
  6. ormlambda/common/abstract_classes/non_query_base.py +9 -7
  7. ormlambda/common/abstract_classes/query_base.py +3 -1
  8. ormlambda/common/errors/__init__.py +29 -0
  9. ormlambda/common/interfaces/IQueryCommand.py +6 -2
  10. ormlambda/databases/__init__.py +0 -1
  11. ormlambda/databases/my_sql/__init__.py +0 -1
  12. ormlambda/databases/my_sql/caster/caster.py +23 -19
  13. ormlambda/databases/my_sql/caster/types/__init__.py +3 -0
  14. ormlambda/databases/my_sql/caster/types/boolean.py +35 -0
  15. ormlambda/databases/my_sql/caster/types/bytes.py +7 -7
  16. ormlambda/databases/my_sql/caster/types/date.py +34 -0
  17. ormlambda/databases/my_sql/caster/types/datetime.py +7 -7
  18. ormlambda/databases/my_sql/caster/types/decimal.py +32 -0
  19. ormlambda/databases/my_sql/caster/types/float.py +7 -7
  20. ormlambda/databases/my_sql/caster/types/int.py +7 -7
  21. ormlambda/databases/my_sql/caster/types/iterable.py +7 -7
  22. ormlambda/databases/my_sql/caster/types/none.py +8 -7
  23. ormlambda/databases/my_sql/caster/types/point.py +4 -4
  24. ormlambda/databases/my_sql/caster/types/string.py +7 -7
  25. ormlambda/databases/my_sql/clauses/ST_AsText.py +8 -7
  26. ormlambda/databases/my_sql/clauses/ST_Contains.py +10 -5
  27. ormlambda/databases/my_sql/clauses/__init__.py +4 -10
  28. ormlambda/databases/my_sql/clauses/count.py +5 -15
  29. ormlambda/databases/my_sql/clauses/delete.py +3 -50
  30. ormlambda/databases/my_sql/clauses/group_by.py +3 -16
  31. ormlambda/databases/my_sql/clauses/having.py +2 -6
  32. ormlambda/databases/my_sql/clauses/insert.py +4 -92
  33. ormlambda/databases/my_sql/clauses/joins.py +5 -140
  34. ormlambda/databases/my_sql/clauses/limit.py +4 -15
  35. ormlambda/databases/my_sql/clauses/offset.py +4 -15
  36. ormlambda/databases/my_sql/clauses/order.py +4 -61
  37. ormlambda/databases/my_sql/clauses/update.py +4 -67
  38. ormlambda/databases/my_sql/clauses/upsert.py +3 -66
  39. ormlambda/databases/my_sql/clauses/where.py +4 -42
  40. ormlambda/databases/my_sql/repository.py +217 -0
  41. ormlambda/dialects/__init__.py +39 -0
  42. ormlambda/dialects/default/__init__.py +1 -0
  43. ormlambda/dialects/default/base.py +39 -0
  44. ormlambda/dialects/interface/__init__.py +1 -0
  45. ormlambda/dialects/interface/dialect.py +78 -0
  46. ormlambda/dialects/mysql/__init__.py +8 -0
  47. ormlambda/dialects/mysql/base.py +387 -0
  48. ormlambda/dialects/mysql/mysqlconnector.py +46 -0
  49. ormlambda/dialects/mysql/types.py +732 -0
  50. ormlambda/dialects/sqlite/__init__.py +5 -0
  51. ormlambda/dialects/sqlite/base.py +47 -0
  52. ormlambda/dialects/sqlite/pysqlite.py +32 -0
  53. ormlambda/engine/__init__.py +1 -0
  54. ormlambda/engine/base.py +58 -0
  55. ormlambda/engine/create.py +9 -23
  56. ormlambda/engine/url.py +31 -19
  57. ormlambda/env.py +30 -0
  58. ormlambda/errors.py +17 -0
  59. ormlambda/model/base_model.py +7 -9
  60. ormlambda/repository/base_repository.py +36 -5
  61. ormlambda/repository/interfaces/IRepositoryBase.py +121 -7
  62. ormlambda/repository/response.py +134 -0
  63. ormlambda/sql/clause_info/aggregate_function_base.py +19 -9
  64. ormlambda/sql/clause_info/clause_info.py +34 -17
  65. ormlambda/sql/clauses/__init__.py +14 -0
  66. ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
  67. ormlambda/sql/clauses/count.py +57 -0
  68. ormlambda/sql/clauses/delete.py +71 -0
  69. ormlambda/sql/clauses/group_by.py +30 -0
  70. ormlambda/sql/clauses/having.py +21 -0
  71. ormlambda/sql/clauses/insert.py +104 -0
  72. ormlambda/sql/clauses/interfaces/__init__.py +5 -0
  73. ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
  74. ormlambda/sql/clauses/joins.py +159 -0
  75. ormlambda/sql/clauses/limit.py +15 -0
  76. ormlambda/sql/clauses/offset.py +15 -0
  77. ormlambda/sql/clauses/order.py +55 -0
  78. ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
  79. ormlambda/sql/clauses/update.py +84 -0
  80. ormlambda/sql/clauses/upsert.py +77 -0
  81. ormlambda/sql/clauses/where.py +65 -0
  82. ormlambda/sql/column/__init__.py +1 -0
  83. ormlambda/sql/{column.py → column/column.py} +82 -22
  84. ormlambda/sql/comparer.py +51 -37
  85. ormlambda/sql/compiler.py +427 -0
  86. ormlambda/sql/ddl.py +68 -0
  87. ormlambda/sql/elements.py +36 -0
  88. ormlambda/sql/foreign_key.py +43 -39
  89. ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
  90. ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
  91. ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
  92. ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
  93. ormlambda/sql/sqltypes.py +647 -0
  94. ormlambda/sql/table/__init__.py +1 -1
  95. ormlambda/sql/table/table.py +179 -0
  96. ormlambda/sql/table/table_constructor.py +1 -208
  97. ormlambda/sql/type_api.py +35 -0
  98. ormlambda/sql/types.py +3 -1
  99. ormlambda/sql/visitors.py +74 -0
  100. ormlambda/statements/__init__.py +1 -0
  101. ormlambda/statements/base_statement.py +28 -38
  102. ormlambda/statements/interfaces/IStatements.py +5 -4
  103. ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
  104. ormlambda/{databases/my_sql → statements}/statements.py +50 -60
  105. ormlambda/statements/types.py +2 -2
  106. ormlambda/types/__init__.py +24 -0
  107. ormlambda/types/metadata.py +42 -0
  108. ormlambda/util/__init__.py +88 -0
  109. ormlambda/util/load_module.py +21 -0
  110. ormlambda/util/plugin_loader.py +32 -0
  111. ormlambda/util/typing.py +6 -0
  112. ormlambda-3.34.1.dist-info/AUTHORS +32 -0
  113. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/METADATA +2 -3
  114. ormlambda-3.34.1.dist-info/RECORD +157 -0
  115. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/WHEEL +1 -1
  116. ormlambda/components/__init__.py +0 -4
  117. ormlambda/components/delete/__init__.py +0 -2
  118. ormlambda/components/delete/abstract_delete.py +0 -17
  119. ormlambda/components/insert/__init__.py +0 -2
  120. ormlambda/components/insert/abstract_insert.py +0 -25
  121. ormlambda/components/select/__init__.py +0 -1
  122. ormlambda/components/update/__init__.py +0 -2
  123. ormlambda/components/update/abstract_update.py +0 -29
  124. ormlambda/components/upsert/__init__.py +0 -2
  125. ormlambda/components/upsert/abstract_upsert.py +0 -25
  126. ormlambda/databases/my_sql/clauses/create_database.py +0 -35
  127. ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
  128. ormlambda/databases/my_sql/repository/__init__.py +0 -1
  129. ormlambda/databases/my_sql/repository/repository.py +0 -351
  130. ormlambda/engine/template.py +0 -47
  131. ormlambda/sql/dtypes.py +0 -94
  132. ormlambda/utils/__init__.py +0 -1
  133. ormlambda-3.12.2.dist-info/RECORD +0 -125
  134. /ormlambda/databases/my_sql/{types.py → pool_types.py} +0 -0
  135. /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
  136. /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
  137. /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
  138. /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
  139. /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
  140. /ormlambda/{components → sql/clauses}/join/__init__.py +0 -0
  141. /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
  142. /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
  143. /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
  144. /ormlambda/{utils → util}/module_tree/dynamic_module.py +0 -0
  145. {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 typing import Optional, override, Type, Callable, TYPE_CHECKING
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], ISelect):
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
- return ClauseInfo.join_clauses(self._all_clauses, ",", self.context)
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
- return f"{self.CLAUSE} {self.COLUMNS} FROM {FROM.query}"
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