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
@@ -16,6 +16,9 @@ from ormlambda.sql.table import TableMeta
16
16
  from .clause_info import ClauseInfo
17
17
  from .clause_info_context import ClauseContextType
18
18
 
19
+ if tp.TYPE_CHECKING:
20
+ from ormlambda.dialects import Dialect
21
+
19
22
 
20
23
  class AggregateFunctionBase[T: Table](ClauseInfo[T], IAggregate):
21
24
  def __init__[TProp: Column](
@@ -27,6 +30,10 @@ class AggregateFunctionBase[T: Table](ClauseInfo[T], IAggregate):
27
30
  context: ClauseContextType = None,
28
31
  keep_asterisk: bool = False,
29
32
  preserve_context: bool = False,
33
+ dtype: TProp = None,
34
+ *,
35
+ dialect: Dialect,
36
+ **kw,
30
37
  ):
31
38
  self._alias_aggregate = alias_clause
32
39
  super().__init__(
@@ -36,6 +43,9 @@ class AggregateFunctionBase[T: Table](ClauseInfo[T], IAggregate):
36
43
  context=context,
37
44
  keep_asterisk=keep_asterisk,
38
45
  preserve_context=preserve_context,
46
+ dtype=dtype,
47
+ dialect=dialect,
48
+ **kw,
39
49
  )
40
50
 
41
51
  @staticmethod
@@ -43,16 +53,16 @@ class AggregateFunctionBase[T: Table](ClauseInfo[T], IAggregate):
43
53
  def FUNCTION_NAME() -> str: ...
44
54
 
45
55
  @classmethod
46
- def _convert_into_clauseInfo[TypeColumns, TProp](cls, columns: ClauseInfo | ColumnType[TProp], context: ClauseContextType) -> list[ClauseInfo]:
56
+ def _convert_into_clauseInfo[TypeColumns, TProp](cls, columns: ClauseInfo | ColumnType[TProp], context: ClauseContextType, dialect: Dialect) -> list[ClauseInfo]:
47
57
  type DEFAULT = tp.Literal["default"]
48
58
  type ClusterType = ColumnType | ForeignKey | DEFAULT
49
59
 
50
60
  dicc_type: dict[ClusterType, tp.Callable[[ClusterType], ClauseInfo]] = {
51
- Column: lambda column: ClauseInfo(column.table, column, context=context),
61
+ Column: lambda column: ClauseInfo(column.table, column, context=context, dialect=dialect),
52
62
  ClauseInfo: lambda column: column,
53
- ForeignKey: lambda tbl: ClauseInfo(tbl.tright, tbl.tright, context=context),
54
- TableMeta: lambda tbl: ClauseInfo(tbl, tbl, context=context),
55
- "default": lambda column: ClauseInfo(table=None, column=column, context=context),
63
+ ForeignKey: lambda tbl: ClauseInfo(tbl.tright, tbl.tright, context=context, dialect=dialect),
64
+ TableMeta: lambda tbl: ClauseInfo(tbl, tbl, context=context, dialect=dialect),
65
+ "default": lambda column: ClauseInfo(table=None, column=column, context=context, dialect=dialect),
56
66
  }
57
67
  all_clauses: list[ClauseInfo] = []
58
68
  if isinstance(columns, str) or not isinstance(columns, tp.Iterable):
@@ -63,8 +73,7 @@ class AggregateFunctionBase[T: Table](ClauseInfo[T], IAggregate):
63
73
  return all_clauses
64
74
 
65
75
  @tp.override
66
- @property
67
- def query(self) -> str:
76
+ def query(self, dialect: Dialect, **kwargs) -> str:
68
77
  wrapped_ci = self.wrapped_clause_info(self)
69
78
  if not self._alias_aggregate:
70
79
  return wrapped_ci
@@ -76,11 +85,12 @@ class AggregateFunctionBase[T: Table](ClauseInfo[T], IAggregate):
76
85
  context=self._context,
77
86
  keep_asterisk=self._keep_asterisk,
78
87
  preserve_context=self._preserve_context,
79
- ).query
88
+ dialect=self._dialect,
89
+ ).query(dialect, **kwargs)
80
90
 
81
91
  def wrapped_clause_info(self, ci: ClauseInfo[T]) -> str:
82
92
  # avoid use placeholder when using IAggregate because no make sense.
83
93
  if self._alias_aggregate and (found := self._keyRegex.findall(self._alias_aggregate)):
84
94
  raise NotKeysInIAggregateError(found)
85
95
 
86
- return f"{self.FUNCTION_NAME()}({ci._create_query()})"
96
+ return f"{self.FUNCTION_NAME()}({ci._create_query(self._dialect)})"
@@ -12,11 +12,13 @@ from ormlambda.sql.types import (
12
12
  )
13
13
  from .interface import IClauseInfo
14
14
  from ormlambda.sql import ForeignKey
15
- from ormlambda.caster import Caster
16
15
 
17
16
 
18
17
  from .clause_info_context import ClauseInfoContext, ClauseContextType
19
18
 
19
+ if tp.TYPE_CHECKING:
20
+ from ormlambda.dialects import Dialect
21
+
20
22
 
21
23
  class ReplacePlaceholderError(ValueError):
22
24
  def __init__(self, placeholder: str, attribute: str, *args):
@@ -45,6 +47,11 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
45
47
  def __init__(self, table: TableType[T], keep_asterisk: tp.Optional[bool] = ...): ...
46
48
  @tp.overload
47
49
  def __init__(self, table: TableType[T], preserve_context: tp.Optional[bool] = ...): ...
50
+ @tp.overload
51
+ def __init__[TProp](self, table: TableType[T], dtype: tp.Optional[TProp] = ...): ...
52
+
53
+ @tp.overload
54
+ def __init__(self, dialect: Dialect, *args, **kwargs): ...
48
55
 
49
56
  def __init__[TProp](
50
57
  self,
@@ -55,6 +62,10 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
55
62
  context: ClauseContextType = None,
56
63
  keep_asterisk: bool = False,
57
64
  preserve_context: bool = False,
65
+ dtype: tp.Optional[TProp] = None,
66
+ *,
67
+ dialect: Dialect,
68
+ **kw,
58
69
  ):
59
70
  if not self.is_table(table):
60
71
  column = table if not column else column
@@ -67,6 +78,9 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
67
78
  self._context: ClauseContextType = context if context else ClauseInfoContext()
68
79
  self._keep_asterisk: bool = keep_asterisk
69
80
  self._preserve_context: bool = preserve_context
81
+ self._dtype = dtype
82
+
83
+ self._dialect: Dialect = dialect
70
84
 
71
85
  self._placeholderValues: dict[str, tp.Callable[[TProp], str]] = {
72
86
  "column": self.replace_column_placeholder,
@@ -76,8 +90,10 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
76
90
  if not self._preserve_context and (self._context and any([alias_table, alias_clause])):
77
91
  self._context.add_clause_to_context(self)
78
92
 
93
+ super().__init__(**kw)
94
+
79
95
  def __repr__(self) -> str:
80
- return f"{type(self).__name__}: query -> {self.query}"
96
+ return f"{type(self).__name__}: query -> {self.query(self._dialect)}"
81
97
 
82
98
  def replace_column_placeholder[TProp](self, column: ColumnType[TProp]) -> str:
83
99
  if not column:
@@ -140,6 +156,9 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
140
156
 
141
157
  @property
142
158
  def dtype[TProp](self) -> tp.Optional[tp.Type[TProp]]:
159
+ if self._dtype is not None:
160
+ return self._dtype
161
+
143
162
  if isinstance(self._column, Column):
144
163
  return self._column.dtype
145
164
 
@@ -147,11 +166,10 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
147
166
  return self._column
148
167
  return type(self._column)
149
168
 
150
- @property
151
- def query(self) -> str:
152
- return self._create_query()
169
+ def query(self, dialect: Dialect, **kwargs) -> str:
170
+ return self._create_query(dialect, **kwargs)
153
171
 
154
- def _create_query(self) -> str:
172
+ def _create_query(self, dialect: Dialect, **kwargs) -> str:
155
173
  # when passing some value that is not a column name
156
174
  if not self.table and not self._alias_clause:
157
175
  return self.column
@@ -173,10 +191,9 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
173
191
  return self._join_table_and_column(self._column)
174
192
 
175
193
  def _join_table_and_column[TProp](self, column: ColumnType[TProp]) -> str:
176
- # FIXME [ ]: Study how to deacoplate from mysql database
177
- from ormlambda.databases.my_sql.repository import MySQLRepository
194
+ # FIXME [x]: Study how to deacoplate from mysql database
178
195
 
179
- caster = Caster(MySQLRepository)
196
+ caster = self._dialect.caster()
180
197
 
181
198
  if self.alias_table:
182
199
  table = self._wrapped_with_quotes(self.alias_table)
@@ -213,22 +230,20 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
213
230
  alias_clause=self._alias_clause,
214
231
  context=self._context,
215
232
  keep_asterisk=self._keep_asterisk,
233
+ dialect=self._dialect,
216
234
  )
217
235
 
218
236
  if self._alias_table and self._alias_clause: # We'll add an "*" when we are certain that we have included 'alias_clause' attr
219
237
  return self._join_table_and_column(ASTERISK)
220
238
 
221
- columns: list[ClauseInfo] = [ClauseCreator(column).query for column in self.table.get_columns()]
239
+ columns: list[ClauseInfo] = [ClauseCreator(column).query(self._dialect) for column in self.table.get_columns()]
222
240
 
223
241
  return ", ".join(columns)
224
242
 
225
- # FIXME [ ]: Study how to deacoplate from mysql database
243
+ # FIXME [x]: Study how to deacoplate from mysql database
226
244
  def _column_resolver[TProp](self, column: ColumnType[TProp]) -> str:
227
- from ormlambda.databases.my_sql.repository import MySQLRepository
228
-
229
- caster = Caster(MySQLRepository)
230
245
  if isinstance(column, ClauseInfo):
231
- return column.query
246
+ return column.query(self._dialect)
232
247
 
233
248
  if isinstance(column, tp.Iterable) and isinstance(column[0], ClauseInfo):
234
249
  return self.join_clauses(column)
@@ -249,6 +264,7 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
249
264
  if self.is_foreign_key(self._column):
250
265
  return self._column.tright.__table_name__
251
266
 
267
+ caster = self._dialect.caster()
252
268
  casted_value = caster.for_value(column, self.dtype)
253
269
  if not self._table:
254
270
  # if we haven't some table atrribute, we assume that the user want to retrieve the string_data from caster.
@@ -292,12 +308,13 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
292
308
  return self._context.get_table_alias(self.table)
293
309
 
294
310
  @staticmethod
295
- def join_clauses(clauses: list[ClauseInfo[T]], chr: str = ",", context: tp.Optional[ClauseInfoContext] = None) -> str:
311
+ def join_clauses(clauses: list[ClauseInfo[T]], chr: str = ",", context: tp.Optional[ClauseInfoContext] = None, *, dialect: Dialect) -> str:
296
312
  queries: list[str] = []
297
313
  for c in clauses:
298
314
  if context:
299
315
  c.context = context
300
- queries.append(c.query)
316
+ c._dialect = dialect
317
+ queries.append(c.query(dialect))
301
318
 
302
319
  return f"{chr} ".join(queries)
303
320
 
@@ -0,0 +1,14 @@
1
+ from .alias import Alias # noqa: F401
2
+ from .count import Count # noqa: F401
3
+ from .delete import Delete # noqa: F401
4
+ from .group_by import GroupBy # noqa: F401
5
+ from .insert import Insert # noqa: F401
6
+ from .joins import JoinSelector # noqa: F401
7
+ from .limit import Limit # noqa: F401
8
+ from .offset import Offset # noqa: F401
9
+ from .order import Order # noqa: F401
10
+ from .select import Select # noqa: F401
11
+ from .where import Where # noqa: F401
12
+ from .having import Having # noqa: F401
13
+ from .update import Update # noqa: F401
14
+ from .upsert import Upsert # noqa: F401
@@ -1,18 +1,21 @@
1
1
  from __future__ import annotations
2
2
  import typing as tp
3
3
 
4
- from ormlambda import Table
5
4
  from ormlambda.sql.clause_info import ClauseInfo
6
- from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
7
- from ormlambda.sql.types import TableType
5
+ from ormlambda.sql.elements import ClauseElement
6
+
8
7
 
9
8
  if tp.TYPE_CHECKING:
10
- from ormlambda.sql.types import ColumnType
11
9
  from ormlambda import Table
10
+ from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
11
+ from ormlambda.sql.types import TableType
12
+ from ormlambda.sql.types import ColumnType
12
13
  from ormlambda.sql.types import AliasType
13
14
 
14
15
 
15
- class Alias[T: Table](ClauseInfo[T]):
16
+ class Alias[T: Table](ClauseInfo[T], ClauseElement):
17
+ __visit_name__ = "alias"
18
+
16
19
  def __init__[TProp](
17
20
  self,
18
21
  table: TableType[T],
@@ -22,7 +25,21 @@ class Alias[T: Table](ClauseInfo[T]):
22
25
  context: ClauseContextType = None,
23
26
  keep_asterisk: bool = False,
24
27
  preserve_context: bool = False,
28
+ **kw,
25
29
  ):
26
30
  if not alias_clause:
27
31
  raise TypeError
28
- super().__init__(table, column, alias_table, alias_clause, context, keep_asterisk, preserve_context)
32
+ super().__init__(
33
+ table,
34
+ column,
35
+ alias_table=alias_table,
36
+ alias_clause=alias_clause,
37
+ context=context,
38
+ keep_asterisk=keep_asterisk,
39
+ preserve_context=preserve_context,
40
+ dtype=None,
41
+ **kw,
42
+ )
43
+
44
+
45
+ __all__ = ["Alias"]
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+ from ormlambda.sql.clause_info import AggregateFunctionBase
3
+ from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
4
+
5
+ from ormlambda.sql.types import AliasType, ColumnType
6
+
7
+ from ormlambda import Table
8
+
9
+ import typing as tp
10
+
11
+ from ormlambda.sql.types import ASTERISK
12
+ from ormlambda.sql.elements import ClauseElement
13
+
14
+
15
+ if tp.TYPE_CHECKING:
16
+ from ormlambda import Table
17
+ from ormlambda.sql.types import ColumnType, AliasType, TableType
18
+ from ormlambda.dialects import Dialect
19
+
20
+
21
+ class Count[T: Table](AggregateFunctionBase[T], ClauseElement):
22
+ __visit_name__ = "count"
23
+
24
+ @staticmethod
25
+ def FUNCTION_NAME() -> str:
26
+ return "COUNT"
27
+
28
+ def __init__[TProp: Table](
29
+ self,
30
+ element: ColumnType[T] | TableType[TProp],
31
+ alias_table: AliasType[ColumnType[TProp]] = None,
32
+ alias_clause: AliasType[ColumnType[TProp]] = "count",
33
+ context: ClauseContextType = None,
34
+ keep_asterisk: bool = True,
35
+ preserve_context: bool = True,
36
+ *,
37
+ dialect: Dialect,
38
+ **kw,
39
+ ) -> None:
40
+ table = self.extract_table(element)
41
+ column = element if self.is_column(element) else ASTERISK
42
+
43
+ super().__init__(
44
+ table=table if (alias_table or (context and table in context._table_context)) else None,
45
+ column=column,
46
+ alias_table=alias_table,
47
+ alias_clause=alias_clause,
48
+ context=context,
49
+ keep_asterisk=keep_asterisk,
50
+ preserve_context=preserve_context,
51
+ dtype=int,
52
+ dialect=dialect,
53
+ **kw,
54
+ )
55
+
56
+
57
+ __all__ = ["Count"]
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+ from typing import Any, Optional, override, Iterable, TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from ormlambda import Column
6
+ from ormlambda.engine import Engine
7
+
8
+ from ormlambda import Table
9
+ from ormlambda import BaseRepository
10
+ from ormlambda.sql.clauses.interfaces import IDelete
11
+ from ormlambda.common.abstract_classes import NonQueryBase
12
+ from ormlambda.sql.elements import ClauseElement
13
+
14
+
15
+ class Delete[T: Table, TRepo](NonQueryBase[T, TRepo], IDelete[T], ClauseElement):
16
+ __visit_name__ = "delete"
17
+
18
+ def __init__(self, model: T, repository: BaseRepository[TRepo], engine: Engine) -> None:
19
+ super().__init__(model, repository, engine=engine)
20
+
21
+ @property
22
+ def CLAUSE(self) -> str:
23
+ return "DELETE"
24
+
25
+ @override
26
+ def delete(self, instances: T | list[T]) -> None:
27
+ col: str = ""
28
+ if isinstance(instances, Table):
29
+ pk: Optional[Column] = instances.get_pk()
30
+ if pk is None:
31
+ raise Exception(f"You cannot use 'DELETE' query without set primary key in '{instances.__table_name__}'")
32
+ col = pk.column_name
33
+
34
+ pk_value = instances[pk]
35
+
36
+ if not pk_value:
37
+ raise ValueError(f"primary key value '{pk_value}' must not be empty.")
38
+
39
+ value = str(pk_value)
40
+
41
+ elif isinstance(instances, Iterable):
42
+ value: list[Any] = []
43
+ for ins in instances:
44
+ pk = type(ins).get_pk()
45
+ value.append(ins[pk])
46
+ col = pk.column_name
47
+
48
+ query: str = f"{self.CLAUSE} FROM {self._model.__table_name__} WHERE {col}"
49
+ if isinstance(value, str):
50
+ query += "= %s"
51
+ self._query = query
52
+ self._values = [value]
53
+ return None
54
+
55
+ elif isinstance(value, Iterable):
56
+ params = ", ".join(["%s"] * len(value))
57
+ query += f" IN ({params})"
58
+ self._query = query
59
+ self._values = value
60
+ return None
61
+ else:
62
+ raise Exception(f"'{type(value)}' no esperado")
63
+
64
+ @override
65
+ def execute(self) -> None:
66
+ if not self._query:
67
+ raise ValueError
68
+ return self._engine.repository.execute_with_values(self._query, self._values)
69
+
70
+
71
+ __all__ = ["Delete"]
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from ormlambda.dialects import Dialect
6
+ from ormlambda.sql.clause_info import AggregateFunctionBase, ClauseInfoContext
7
+ from ormlambda.sql.types import ColumnType
8
+ from ormlambda.sql.elements import ClauseElement
9
+
10
+
11
+ class GroupBy(AggregateFunctionBase, ClauseElement):
12
+ __visit_name__ = "group_by"
13
+
14
+ @classmethod
15
+ def FUNCTION_NAME(self) -> str:
16
+ return "GROUP BY"
17
+
18
+ def __init__(self, column: ColumnType, context: ClauseInfoContext, dialect: Dialect, **kwargs):
19
+ super().__init__(
20
+ table=column.table,
21
+ column=column,
22
+ alias_table=None,
23
+ alias_clause=None,
24
+ context=context,
25
+ dialect=dialect,
26
+ **kwargs,
27
+ )
28
+
29
+
30
+ __all__ = ["GroupBy"]
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from .where import Where
4
+
5
+
6
+ class Having(Where):
7
+ """
8
+ The purpose of this class is to create 'WHERE' condition queries properly.
9
+ """
10
+
11
+ __visit_name__ = "having"
12
+
13
+ def __init__(self, *comparer, restrictive=True, context=None, **kw):
14
+ super().__init__(*comparer, restrictive=restrictive, context=context, **kw)
15
+
16
+ @staticmethod
17
+ def FUNCTION_NAME() -> str:
18
+ return "HAVING"
19
+
20
+
21
+ __all__ = ["Having"]
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import override, Iterable, TYPE_CHECKING
4
+
5
+ from ormlambda import Table
6
+ from ormlambda import Column
7
+
8
+ from ormlambda.sql.clauses.interfaces import IInsert
9
+ from ormlambda.common.abstract_classes import NonQueryBase
10
+ from ormlambda.sql.elements import ClauseElement
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from ormlambda.dialects import Dialect
15
+
16
+
17
+ class Insert[T: Table, TRepo](NonQueryBase[T, TRepo], IInsert[T], ClauseElement):
18
+ __visit_name__ = "insert"
19
+
20
+ def __init__(self, model: T, repository: TRepo, dialect: Dialect) -> None:
21
+ super().__init__(model, repository, dialect=dialect)
22
+
23
+ @override
24
+ @property
25
+ def CLAUSE(self) -> str:
26
+ return "INSERT INTO"
27
+
28
+ @override
29
+ def execute(self) -> None:
30
+ if not self._query:
31
+ raise ValueError
32
+ return self._repository.executemany_with_values(self.query(self._dialect), self._values)
33
+
34
+ @override
35
+ def insert[TProp](self, instances: T | list[T]) -> None:
36
+ if not isinstance(instances, Iterable):
37
+ instances = (instances,)
38
+ valid_cols: list[list[Column[TProp]]] = []
39
+ self.__fill_dict_list(valid_cols, instances)
40
+
41
+ col_names: list[str] = []
42
+ wildcards: list[str] = []
43
+ col_values: list[list[str]] = []
44
+ for i, cols in enumerate(valid_cols):
45
+ col_values.append([])
46
+ CASTER = self._dialect.caster()
47
+ for col in cols:
48
+ clean_data = CASTER.for_column(col, instances[i]) # .resolve(instances[i][col])
49
+ if i == 0:
50
+ col_names.append(col.column_name)
51
+ wildcards.append(clean_data.wildcard_to_insert())
52
+ # COMMENT: avoid MySQLWriteCastBase.resolve when using PLACEHOLDERs
53
+ col_values[-1].append(clean_data.to_database)
54
+
55
+ join_cols = ", ".join(col_names)
56
+ unknown_rows = f'({", ".join(wildcards)})' # The number of "%s" must match the dict 'dicc_0' length
57
+
58
+ self._values = [tuple(x) for x in col_values]
59
+ self._query = f"{self.CLAUSE} {self._model.__table_name__} {f'({join_cols})'} VALUES {unknown_rows}"
60
+ return None
61
+
62
+ @staticmethod
63
+ def __is_valid[TProp](column: Column[TProp], value: TProp) -> bool:
64
+ """
65
+ We want to delete the column from table when it's specified with an 'AUTO_INCREMENT' or 'AUTO GENERATED ALWAYS AS (__) STORED' statement.
66
+
67
+ if the column is auto-generated, it means the database creates the value for that column, so we must deleted it.
68
+ if the column is primary key and auto-increment, we should be able to create an object with specific pk value.
69
+
70
+ RETURN
71
+ -----
72
+
73
+ - True -> Do not delete the column from dict query
74
+ - False -> Delete the column from dict query
75
+ """
76
+
77
+ is_pk_none_and_auto_increment: bool = all([value is None, column.is_primary_key, column.is_auto_increment])
78
+
79
+ if is_pk_none_and_auto_increment or column.is_auto_generated:
80
+ return False
81
+ return True
82
+
83
+ def __fill_dict_list[TProp](self, list_dict: list[str, TProp], values: list[T]) -> list[Column]:
84
+ if isinstance(values, Iterable):
85
+ for x in values:
86
+ self.__fill_dict_list(list_dict, x)
87
+
88
+ elif issubclass(values.__class__, Table):
89
+ new_list = []
90
+ for prop in type(values).__dict__.values():
91
+ if not isinstance(prop, Column):
92
+ continue
93
+
94
+ value = getattr(values, prop.column_name)
95
+ if self.__is_valid(prop, value):
96
+ new_list.append(prop)
97
+ list_dict.append(new_list)
98
+
99
+ else:
100
+ raise Exception(f"Tipo de dato'{type(values)}' no esperado")
101
+ return None
102
+
103
+
104
+ __all__ = ["Insert"]
@@ -0,0 +1,5 @@
1
+ from .IDelete import IDelete # noqa: F401
2
+ from .IInsert import IInsert # noqa: F401
3
+ from .ISelect import ISelect # noqa: F401
4
+ from .IUpdate import IUpdate # noqa: F401
5
+ from .IUpsert import IUpsert # noqa: F401
@@ -11,24 +11,32 @@ if TYPE_CHECKING:
11
11
  from ormlambda import Table
12
12
  from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
13
13
  from ormlambda.common.enums.join_type import JoinType
14
+ from ormlambda.dialects import Dialect
14
15
 
15
16
 
16
- type TupleJoinType[LTable: Table, LProp, RTable: Table, RProp] = tuple[Comparer[LTable, LProp, RTable, RProp], JoinType]
17
+ type TupleJoinType[LTable: Table, LProp, RTable: Table, RProp] = tuple[Comparer]
17
18
 
18
19
 
19
20
  class JoinContext[TParent: Table, TRepo]:
20
- def __init__(self, statements: IStatements_two_generic[TParent, TRepo], joins: tuple, context: ClauseContextType) -> None:
21
+ def __init__(
22
+ self,
23
+ statements: IStatements_two_generic[TParent, TRepo],
24
+ joins: tuple,
25
+ context: ClauseContextType,
26
+ dialect: Dialect,
27
+ ) -> None:
21
28
  self._statements = statements
22
29
  self._parent: TParent = statements.model
23
30
  self._joins: Iterable[tuple[Comparer, JoinType]] = joins
24
31
  self._context: ClauseContextType = context
32
+ self._dialect: Dialect = dialect
25
33
 
26
34
  def __enter__(self) -> IStatements_two_generic[TParent, TRepo]:
27
35
  for comparer, by in self._joins:
28
36
  fk_clause, alias = self.get_fk_clause(comparer)
29
37
 
30
- foreign_key: ForeignKey = ForeignKey(comparer=comparer, clause_name=alias, keep_alive=True)
31
- fk_clause.alias_table = foreign_key.alias
38
+ foreign_key: ForeignKey = ForeignKey(comparer=comparer, clause_name=alias, keep_alive=True, dialect=self._dialect)
39
+ fk_clause.alias_table = foreign_key.get_alias(self._dialect)
32
40
  self._context.add_clause_to_context(fk_clause)
33
41
  setattr(self._parent, alias, foreign_key)
34
42
 
@@ -64,10 +72,10 @@ class JoinContext[TParent: Table, TRepo]:
64
72
  >>> (A.fk_b == B.pk_b) & (B.fk_c == C.pk_c) # Incorrect
65
73
  """
66
74
  clause_dicc: dict[Table, ClauseInfo] = {
67
- comparer.left_condition.table: comparer.left_condition,
68
- comparer.right_condition.table: comparer.right_condition,
75
+ comparer.left_condition(self._dialect).table: comparer.left_condition(self._dialect),
76
+ comparer.right_condition(self._dialect).table: comparer.right_condition(self._dialect),
69
77
  }
70
- conditions = set([comparer.left_condition.table, comparer.right_condition.table])
78
+ conditions = set([comparer.left_condition(self._dialect).table, comparer.right_condition(self._dialect).table])
71
79
  model = set([self._statements.model])
72
80
 
73
81
  parent_table = conditions.difference(model).pop()