ormlambda 3.35.3__py3-none-any.whl → 4.0.4__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 (130) hide show
  1. ormlambda/__init__.py +79 -51
  2. ormlambda/caster/caster.py +6 -1
  3. ormlambda/common/abstract_classes/__init__.py +0 -2
  4. ormlambda/common/enums/__init__.py +1 -0
  5. ormlambda/common/enums/order_type.py +9 -0
  6. ormlambda/common/errors/__init__.py +13 -3
  7. ormlambda/common/global_checker.py +86 -8
  8. ormlambda/common/interfaces/IQueryCommand.py +2 -2
  9. ormlambda/common/interfaces/__init__.py +0 -2
  10. ormlambda/dialects/__init__.py +75 -3
  11. ormlambda/dialects/default/base.py +1 -1
  12. ormlambda/dialects/mysql/__init__.py +35 -78
  13. ormlambda/dialects/mysql/base.py +226 -40
  14. ormlambda/dialects/mysql/clauses/ST_AsText.py +26 -0
  15. ormlambda/dialects/mysql/clauses/ST_Contains.py +30 -0
  16. ormlambda/dialects/mysql/clauses/__init__.py +1 -0
  17. ormlambda/dialects/mysql/repository/__init__.py +1 -0
  18. ormlambda/{databases/my_sql → dialects/mysql/repository}/repository.py +0 -5
  19. ormlambda/dialects/mysql/types.py +6 -0
  20. ormlambda/engine/base.py +26 -4
  21. ormlambda/errors.py +9 -0
  22. ormlambda/model/base_model.py +3 -10
  23. ormlambda/repository/base_repository.py +1 -1
  24. ormlambda/repository/interfaces/IRepositoryBase.py +0 -7
  25. ormlambda/repository/response.py +21 -8
  26. ormlambda/sql/__init__.py +12 -3
  27. ormlambda/sql/clause_info/__init__.py +0 -2
  28. ormlambda/sql/clause_info/clause_info.py +94 -76
  29. ormlambda/sql/clause_info/interface/IAggregate.py +14 -4
  30. ormlambda/sql/clause_info/interface/IClauseInfo.py +6 -11
  31. ormlambda/sql/clauses/alias.py +6 -37
  32. ormlambda/sql/clauses/count.py +21 -36
  33. ormlambda/sql/clauses/group_by.py +13 -19
  34. ormlambda/sql/clauses/having.py +2 -6
  35. ormlambda/sql/clauses/insert.py +3 -3
  36. ormlambda/sql/clauses/interfaces/__init__.py +0 -1
  37. ormlambda/sql/clauses/join/join_context.py +5 -12
  38. ormlambda/sql/clauses/joins.py +34 -52
  39. ormlambda/sql/clauses/limit.py +1 -2
  40. ormlambda/sql/clauses/offset.py +1 -2
  41. ormlambda/sql/clauses/order.py +17 -21
  42. ormlambda/sql/clauses/select.py +56 -28
  43. ormlambda/sql/clauses/update.py +13 -10
  44. ormlambda/sql/clauses/where.py +20 -39
  45. ormlambda/sql/column/__init__.py +1 -0
  46. ormlambda/sql/column/column.py +19 -12
  47. ormlambda/sql/column/column_proxy.py +117 -0
  48. ormlambda/sql/column_table_proxy.py +23 -0
  49. ormlambda/sql/comparer.py +31 -65
  50. ormlambda/sql/compiler.py +248 -58
  51. ormlambda/sql/context/__init__.py +304 -0
  52. ormlambda/sql/ddl.py +19 -5
  53. ormlambda/sql/elements.py +3 -0
  54. ormlambda/sql/foreign_key.py +42 -64
  55. ormlambda/sql/functions/__init__.py +0 -1
  56. ormlambda/sql/functions/concat.py +35 -38
  57. ormlambda/sql/functions/max.py +12 -36
  58. ormlambda/sql/functions/min.py +13 -28
  59. ormlambda/sql/functions/sum.py +17 -33
  60. ormlambda/sql/sqltypes.py +2 -0
  61. ormlambda/sql/table/__init__.py +1 -0
  62. ormlambda/sql/table/table.py +31 -45
  63. ormlambda/sql/table/table_proxy.py +88 -0
  64. ormlambda/sql/type_api.py +4 -1
  65. ormlambda/sql/types.py +15 -12
  66. ormlambda/statements/__init__.py +0 -2
  67. ormlambda/statements/base_statement.py +53 -91
  68. ormlambda/statements/interfaces/IStatements.py +77 -123
  69. ormlambda/statements/interfaces/__init__.py +1 -1
  70. ormlambda/statements/query_builder.py +296 -128
  71. ormlambda/statements/statements.py +122 -115
  72. ormlambda/statements/types.py +5 -25
  73. ormlambda/util/__init__.py +7 -100
  74. ormlambda/util/langhelpers.py +102 -0
  75. ormlambda/util/module_tree/dynamic_module.py +1 -1
  76. ormlambda/util/preloaded.py +80 -0
  77. ormlambda/util/typing.py +12 -3
  78. {ormlambda-3.35.3.dist-info → ormlambda-4.0.4.dist-info}/METADATA +56 -79
  79. ormlambda-4.0.4.dist-info/RECORD +139 -0
  80. ormlambda/common/abstract_classes/clause_info_converter.py +0 -65
  81. ormlambda/common/abstract_classes/decomposition_query.py +0 -141
  82. ormlambda/common/abstract_classes/query_base.py +0 -15
  83. ormlambda/common/interfaces/ICustomAlias.py +0 -7
  84. ormlambda/common/interfaces/IDecompositionQuery.py +0 -33
  85. ormlambda/databases/__init__.py +0 -4
  86. ormlambda/databases/my_sql/__init__.py +0 -3
  87. ormlambda/databases/my_sql/clauses/ST_AsText.py +0 -37
  88. ormlambda/databases/my_sql/clauses/ST_Contains.py +0 -36
  89. ormlambda/databases/my_sql/clauses/__init__.py +0 -14
  90. ormlambda/databases/my_sql/clauses/count.py +0 -33
  91. ormlambda/databases/my_sql/clauses/delete.py +0 -9
  92. ormlambda/databases/my_sql/clauses/drop_table.py +0 -26
  93. ormlambda/databases/my_sql/clauses/group_by.py +0 -17
  94. ormlambda/databases/my_sql/clauses/having.py +0 -12
  95. ormlambda/databases/my_sql/clauses/insert.py +0 -9
  96. ormlambda/databases/my_sql/clauses/joins.py +0 -14
  97. ormlambda/databases/my_sql/clauses/limit.py +0 -6
  98. ormlambda/databases/my_sql/clauses/offset.py +0 -6
  99. ormlambda/databases/my_sql/clauses/order.py +0 -8
  100. ormlambda/databases/my_sql/clauses/update.py +0 -8
  101. ormlambda/databases/my_sql/clauses/upsert.py +0 -9
  102. ormlambda/databases/my_sql/clauses/where.py +0 -7
  103. ormlambda/dialects/interface/__init__.py +0 -1
  104. ormlambda/dialects/interface/dialect.py +0 -78
  105. ormlambda/sql/clause_info/aggregate_function_base.py +0 -96
  106. ormlambda/sql/clause_info/clause_info_context.py +0 -87
  107. ormlambda/sql/clauses/interfaces/ISelect.py +0 -17
  108. ormlambda/sql/clauses/new_join.py +0 -119
  109. ormlambda/util/load_module.py +0 -21
  110. ormlambda/util/plugin_loader.py +0 -32
  111. ormlambda-3.35.3.dist-info/RECORD +0 -159
  112. /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
  113. /ormlambda/{databases/my_sql → dialects/mysql}/caster/caster.py +0 -0
  114. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +0 -0
  115. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/boolean.py +0 -0
  116. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +0 -0
  117. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/date.py +0 -0
  118. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +0 -0
  119. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/decimal.py +0 -0
  120. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +0 -0
  121. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +0 -0
  122. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +0 -0
  123. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/json.py +0 -0
  124. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +0 -0
  125. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +0 -0
  126. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +0 -0
  127. /ormlambda/{databases/my_sql → dialects/mysql/repository}/pool_types.py +0 -0
  128. {ormlambda-3.35.3.dist-info → ormlambda-4.0.4.dist-info}/AUTHORS +0 -0
  129. {ormlambda-3.35.3.dist-info → ormlambda-4.0.4.dist-info}/LICENSE +0 -0
  130. {ormlambda-3.35.3.dist-info → ormlambda-4.0.4.dist-info}/WHEEL +0 -0
@@ -6,10 +6,9 @@ from ormlambda.sql.comparer import Comparer
6
6
 
7
7
 
8
8
  if TYPE_CHECKING:
9
- from ormlambda.statements.interfaces.IStatements import IStatements_two_generic
9
+ from ormlambda.statements.interfaces.IStatements import IStatements
10
10
  from ormlambda.sql.clause_info import ClauseInfo
11
11
  from ormlambda import Table
12
- from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
13
12
  from ormlambda.common.enums.join_type import JoinType
14
13
  from ormlambda.dialects import Dialect
15
14
 
@@ -17,34 +16,31 @@ if TYPE_CHECKING:
17
16
  type TupleJoinType[LTable: Table, LProp, RTable: Table, RProp] = tuple[Comparer]
18
17
 
19
18
 
20
- class JoinContext[TParent: Table, TRepo]:
19
+ class JoinContext[TParent: Table]:
21
20
  def __init__(
22
21
  self,
23
- statements: IStatements_two_generic[TParent, TRepo],
22
+ statements: IStatements[TParent],
24
23
  joins: tuple,
25
- context: ClauseContextType,
26
24
  dialect: Dialect,
27
25
  ) -> None:
28
26
  self._statements = statements
29
27
  self._parent: TParent = statements.model
30
28
  self._joins: Iterable[tuple[Comparer, JoinType]] = joins
31
- self._context: ClauseContextType = context
32
29
  self._dialect: Dialect = dialect
33
30
 
34
- def __enter__(self) -> IStatements_two_generic[TParent, TRepo]:
31
+ def __enter__(self) -> IStatements[TParent]:
35
32
  for comparer, by in self._joins:
36
33
  fk_clause, alias = self.get_fk_clause(comparer)
37
34
 
38
35
  foreign_key: ForeignKey = ForeignKey(comparer=comparer, clause_name=alias, keep_alive=True, dialect=self._dialect)
39
36
  fk_clause.alias_table = foreign_key.get_alias(self._dialect)
40
- self._context.add_clause_to_context(fk_clause)
41
37
  setattr(self._parent, alias, foreign_key)
42
38
 
43
39
  # TODOH [x]: We need to preserve the 'foreign_key' variable while inside the 'with' clause.
44
40
  # Keep in mind that 'ForeignKey.stored_calls' is cleared every time we call methods like
45
41
  # .select(), .select_one(), .insert(), .update(), or .count(). This means we only retain
46
42
  # the context from the first call of any of these methods.
47
- ForeignKey.stored_calls.add(foreign_key)
43
+ # FIXME [ ]: See how to deal with context when using JoinContext class and PATH_CONTEXT
48
44
 
49
45
  return self
50
46
 
@@ -54,10 +50,7 @@ class JoinContext[TParent: Table, TRepo]:
54
50
 
55
51
  for comparer, _ in self._joins:
56
52
  _, attribute = self.get_fk_clause(comparer)
57
- fk: ForeignKey = getattr(self._parent, attribute)
58
53
  delattr(self._parent, attribute)
59
- del self._context._table_context[fk.tright]
60
- ForeignKey.stored_calls.remove(fk)
61
54
  return None
62
55
 
63
56
  def __getattr__(self, name: str) -> TParent:
@@ -1,15 +1,13 @@
1
1
  from __future__ import annotations
2
2
  from collections import defaultdict
3
- from typing import override, Optional, TYPE_CHECKING, Type
3
+ from typing import override, Optional, TYPE_CHECKING
4
4
 
5
5
 
6
6
  from ormlambda.util.module_tree.dfs_traversal import DFSTraversal
7
7
  from ormlambda.common.interfaces.IJoinSelector import IJoinSelector
8
8
  from ormlambda.common.interfaces.IQueryCommand import IQuery
9
- from ormlambda import JoinType
10
- from ormlambda.sql.clause_info import ClauseInfo
9
+ from ormlambda import ColumnProxy, JoinType
11
10
  from ormlambda.sql.comparer import Comparer
12
- from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
13
11
  from ormlambda.sql.elements import ClauseElement
14
12
 
15
13
 
@@ -34,78 +32,41 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight], Cl
34
32
 
35
33
  @override
36
34
  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})"
35
+ return f"{IQuery.__name__}: {self.__class__.__name__} ({self.alias})"
41
36
 
42
37
  def __init__[LProp, RProp](
43
38
  self,
44
39
  where: Comparer,
45
40
  by: JoinType,
46
41
  alias: Optional[str] = "{table}",
47
- context: ClauseContextType = None,
48
42
  *,
49
43
  dialect: Dialect,
50
44
  **kw,
51
45
  ) -> None:
52
- lcon = where.left_condition(dialect)
53
- rcon = where.right_condition(dialect)
46
+ self.lcon: ColumnProxy = where.left_condition
47
+ self.rcon: ColumnProxy = where.right_condition
54
48
  self._comparer: Comparer = where
55
- self._orig_table: TLeft = lcon.table
56
- self._right_table: TRight = rcon.table
49
+ self._orig_table: TLeft = self.lcon.table
50
+ self._right_table: TRight = self.rcon.table
57
51
  self._by: JoinType = by
58
- self._left_col: str = lcon._column.column_name
59
- self._right_col: str = rcon._column.column_name
52
+ self._left_col: str = self.lcon._column.column_name
53
+ self._right_col: str = self.rcon._column.column_name
60
54
  self._compareop = where._compare
61
- self._context: ClauseContextType = context if context else ClauseInfoContext()
62
55
 
63
56
  # COMMENT: When multiple columns reference the same table, we need to create an alias to maintain clear references.
64
57
  self._alias: Optional[str] = alias
65
58
 
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
59
  def __eq__(self, __value: JoinSelector) -> bool:
71
60
  return isinstance(__value, JoinSelector) and self.__hash__() == __value.__hash__()
72
61
 
73
62
  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)
63
+ # Only can add the first instance of a JoinsSelector which has the same alias
64
+ return hash(self.alias)
92
65
 
93
66
  @classmethod
94
67
  def join_selectors(cls, dialect: Dialect, *args: JoinSelector) -> str:
95
68
  return "\n".join([x.query(dialect) for x in args])
96
69
 
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
70
  @property
110
71
  def left_table(self) -> TLeft:
111
72
  return self._orig_table
@@ -137,9 +98,9 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight], Cl
137
98
  for obj in joins:
138
99
  join_object_map[obj.left_table].append(obj)
139
100
 
140
- graph: dict[Type[Table], list[Type[Table]]] = defaultdict(list)
101
+ graph: dict[str, list[str]] = defaultdict(list)
141
102
  for join in joins:
142
- graph[join.left_table].append(join.right_table)
103
+ graph[join.alias].append(join.right_table.__table_name__)
143
104
 
144
105
  sorted_graph = DFSTraversal.sort(graph)[::-1]
145
106
 
@@ -155,5 +116,26 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight], Cl
155
116
  res.extend(tables)
156
117
  return res
157
118
 
119
+ @classmethod
120
+ def sort_joins_by_alias(cls, joins: set[JoinSelector]) -> tuple[JoinSelector]:
121
+ # FIXME [x]: How to sort when needed because it's not necessary at this point. It is for testing purpouse
122
+ if len(joins) == 1:
123
+ return tuple(joins)
124
+
125
+ join_object_map: dict[str, JoinSelector] = {}
126
+
127
+ for obj in joins:
128
+ join_object_map[obj.alias] = obj
129
+
130
+ sorted_graph = []
131
+
132
+ for alias in sorted([x.alias for x in joins]):
133
+ sorted_graph.append(join_object_map[alias])
134
+
135
+ if not sorted_graph:
136
+ return tuple(joins)
137
+
138
+ return tuple(sorted_graph)
139
+
158
140
 
159
141
  __all__ = ["JoinSelector"]
@@ -4,9 +4,8 @@ from ormlambda.sql.elements import ClauseElement
4
4
 
5
5
  class Limit(ClauseElement):
6
6
  __visit_name__ = "limit"
7
- LIMIT = "LIMIT"
8
7
 
9
- def __init__(self, number: int, **kwargs) -> None:
8
+ def __init__(self, number: int) -> None:
10
9
  if not isinstance(number, int):
11
10
  raise ValueError
12
11
  self._number: int = number
@@ -4,9 +4,8 @@ from ormlambda.sql.elements import ClauseElement
4
4
 
5
5
  class Offset(ClauseElement):
6
6
  __visit_name__ = "offset"
7
- OFFSET = "OFFSET"
8
7
 
9
- def __init__(self, number: int, **kwargs) -> None:
8
+ def __init__(self, number: int) -> None:
10
9
  if not isinstance(number, int):
11
10
  raise ValueError
12
11
  self._number: int = number
@@ -1,44 +1,33 @@
1
1
  from __future__ import annotations
2
2
  import typing as tp
3
3
 
4
- from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
4
+ from ormlambda.sql.clause_info import IAggregate
5
5
  from ormlambda.sql.types import ColumnType
6
- from ormlambda.sql.clause_info import AggregateFunctionBase
7
6
 
8
- from ormlambda.statements import OrderType
7
+ from ormlambda.common.enums import OrderType
9
8
  from ormlambda.sql.elements import ClauseElement
10
9
 
11
10
  if tp.TYPE_CHECKING:
12
- from ormlambda.dialects import Dialect
11
+ from ormlambda import ColumnProxy
13
12
 
14
13
 
15
- class Order(AggregateFunctionBase, ClauseElement):
14
+ class Order[TProp](ClauseElement, IAggregate):
16
15
  __visit_name__ = "order"
17
16
 
18
- @staticmethod
19
- def FUNCTION_NAME() -> str:
20
- return "ORDER BY"
17
+ columns: tuple[ColumnType[TProp], ...]
21
18
 
22
- def __init__[TProp](
19
+ def __init__(
23
20
  self,
24
- column: tuple[ColumnType[TProp], ...] | ColumnType[TProp],
21
+ *columns: ColumnProxy[TProp],
25
22
  order_type: tp.Iterable[OrderType],
26
- context: ClauseContextType = None,
27
- *,
28
- dialect: Dialect,
29
- **kw,
30
23
  ):
31
- super().__init__(
32
- table=None,
33
- column=column,
34
- context=context,
35
- dialect=dialect,
36
- **kw,
37
- )
24
+ if isinstance(columns, str) or not isinstance(columns, tp.Iterable):
25
+ columns = (columns,)
38
26
 
39
27
  if isinstance(order_type, str) or not isinstance(order_type, tp.Iterable):
40
28
  order_type = (order_type,)
41
29
 
30
+ self.columns = columns
42
31
  self._order_type: list[OrderType] = [self.__cast_to_OrderType(x) for x in order_type]
43
32
 
44
33
  def __cast_to_OrderType(self, _value: tp.Any) -> tp.Iterable[OrderType]:
@@ -52,4 +41,11 @@ class Order(AggregateFunctionBase, ClauseElement):
52
41
  pass
53
42
  raise Exception(f"order_type param only can be 'ASC' or 'DESC' string or '{OrderType.__name__}' enum")
54
43
 
44
+ def used_columns(self):
45
+ return self.columns
46
+
47
+ @property
48
+ def dtype(self) -> tp.Any: ...
49
+
50
+
55
51
  __all__ = ["Order"]
@@ -1,51 +1,79 @@
1
1
  from __future__ import annotations
2
+ from ormlambda import ColumnProxy
2
3
  from ormlambda.sql.elements import ClauseElement
3
- from typing import Optional, Type, Callable, TYPE_CHECKING
4
+ from typing import Any, Iterable, Optional, Type, TYPE_CHECKING
4
5
 
5
- from ormlambda.sql.clause_info import ClauseInfo
6
- from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
7
- from ormlambda.sql.types import AliasType
8
- from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
6
+ from ormlambda.sql.clause_info import ClauseInfo, IAggregate
7
+ from ormlambda.sql.types import SelectCol
8
+ from ormlambda.sql.table import TableProxy
9
9
 
10
10
  if TYPE_CHECKING:
11
+ from ormlambda.sql.types import AliasType, ColumnType
11
12
  from ormlambda import Table
12
- from ormlambda.dialects import Dialect
13
13
 
14
+ type Selectable = ColumnType | TableProxy
14
15
 
15
- class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ClauseElement):
16
- __visit_name__ = "select"
17
16
 
18
- CLAUSE: str = "SELECT"
17
+ class Select[T: Type[Table]](ClauseElement, IAggregate):
18
+ __visit_name__ = "select"
19
19
 
20
20
  def __init__(
21
21
  self,
22
- tables: tuple[T, *Ts],
23
- columns: Callable[[T], tuple] = lambda x: x,
22
+ table: T,
23
+ columns: SelectCol,
24
24
  *,
25
25
  alias_table: AliasType[ClauseInfo] = "{table}",
26
- context: Optional[ClauseInfoContext] = None,
27
- dialect: Dialect,
28
- **kwargs,
26
+ alias: Optional[AliasType[T]] = None,
27
+ avoid_duplicates: bool = False,
29
28
  ) -> None:
30
- super().__init__(
31
- tables,
32
- columns,
33
- context=context,
34
- dialect=dialect,
35
- **kwargs,
36
- )
29
+ self._table = table
30
+ self._columns = columns
37
31
  self._alias_table = alias_table
38
- # We always need to add the self alias of the Select
39
- self._context._add_table_alias(self.table, self._alias_table)
32
+ self._alias = alias
33
+ self._avoid_duplicates = avoid_duplicates
34
+
35
+ @property
36
+ def columns(self) -> tuple[SelectCol, ...]:
37
+ if not isinstance(self._columns, Iterable):
38
+ return [self._columns]
39
+ return self._columns
40
40
 
41
41
  @property
42
- def FROM(self) -> ClauseInfo[T]:
43
- return ClauseInfo(self.table, None, alias_table=self._alias_table, context=self._context, dialect=self._dialect, **self.kwargs)
42
+ def table(self) -> TableProxy[T]:
43
+ return TableProxy(self._table)
44
+
45
+ @property
46
+ def alias(self) -> str:
47
+ return self._alias
48
+
49
+ @alias.setter
50
+ def alias(self, value) -> None:
51
+ self._alias = value
44
52
 
45
53
  @property
46
- def COLUMNS(self) -> str:
47
- dialect = self.kwargs.pop("dialect", self._dialect)
48
- return ClauseInfo.join_clauses(self._all_clauses, ",", self.context, dialect=dialect)
54
+ def avoid_duplicates(self) -> bool:
55
+ return self._avoid_duplicates
56
+
57
+ def used_columns(self):
58
+ res = []
59
+
60
+ for col in self._columns:
61
+ if isinstance(col, ColumnProxy):
62
+ res.append(col)
63
+
64
+ elif isinstance(col, IAggregate):
65
+ res.extend(col.used_columns())
66
+ return res
67
+
68
+ @property
69
+ def dtype(self) -> Any: ...
70
+
71
+ def __getitem__(self, key: str) -> SelectCol:
72
+ for clause in self.columns:
73
+ if isinstance(clause, ColumnProxy) and key in (clause.column_name, clause.alias, clause.get_full_chain("_")):
74
+ return clause
75
+ if isinstance(clause, IAggregate) and key == clause.alias:
76
+ return clause
49
77
 
50
78
 
51
79
  __all__ = ["Select"]
@@ -1,8 +1,7 @@
1
1
  from __future__ import annotations
2
2
  from typing import Type, override, Any, TYPE_CHECKING, Optional
3
3
 
4
- from ormlambda import Table, Column
5
- from ormlambda.caster.caster import Caster
4
+ from ormlambda import Table, Column, ColumnProxy
6
5
  from .where import Where
7
6
  from ormlambda.common.abstract_classes import NonQueryBase
8
7
  from .interfaces import IUpdate
@@ -30,9 +29,15 @@ class UpdateKeyError(KeyError):
30
29
  class Update[T: Type[Table], TRepo](NonQueryBase[T, TRepo], IUpdate, ClauseElement):
31
30
  __visit_name__ = "update"
32
31
 
33
- def __init__(self, model: T, repository: Any, where: list[Where], engine: Engine) -> None:
32
+ def __init__(
33
+ self,
34
+ model: T,
35
+ repository: Any,
36
+ where: list[Where],
37
+ engine: Engine,
38
+ ) -> None:
34
39
  super().__init__(model, repository, engine=engine)
35
- self._where: Optional[list[Where]] = where
40
+ self._where: Optional[Where] = where
36
41
 
37
42
  @override
38
43
  @property
@@ -42,11 +47,9 @@ class Update[T: Type[Table], TRepo](NonQueryBase[T, TRepo], IUpdate, ClauseEleme
42
47
  @override
43
48
  def execute(self) -> None:
44
49
  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
+ where_string = self._where.compile(self._engine.dialect).string
51
+
52
+ self._query += " " + where_string
50
53
  return self._engine.repository.execute_with_values(self._query, self._values)
51
54
 
52
55
  @override
@@ -61,7 +64,7 @@ class Update[T: Type[Table], TRepo](NonQueryBase[T, TRepo], IUpdate, ClauseEleme
61
64
  if not hasattr(self._model, col):
62
65
  raise UpdateKeyError(self._model, col)
63
66
  col = getattr(self._model, col)
64
- if not isinstance(col, Column):
67
+ if not isinstance(col, Column | ColumnProxy):
65
68
  raise ValueError
66
69
 
67
70
  if self.__is_valid__(col):
@@ -1,15 +1,14 @@
1
1
  from __future__ import annotations
2
2
  import typing as tp
3
+ from ormlambda import ColumnProxy
3
4
  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
5
  from ormlambda.sql.elements import ClauseElement
7
6
 
8
7
  if tp.TYPE_CHECKING:
9
- from ormlambda.dialects import Dialect
8
+ from ormlambda.statements.types import WhereTypes
10
9
 
11
10
 
12
- class Where(AggregateFunctionBase, ClauseElement):
11
+ class Where[T](ClauseElement):
13
12
  """
14
13
  The purpose of this class is to create 'WHERE' condition queries properly.
15
14
  """
@@ -18,48 +17,30 @@ class Where(AggregateFunctionBase, ClauseElement):
18
17
 
19
18
  def __init__(
20
19
  self,
21
- *comparer: Comparer,
20
+ *comparer: WhereTypes,
22
21
  restrictive: bool = True,
23
- context: ClauseContextType = None,
24
22
  ) -> None:
25
- self._comparer: tuple[Comparer] = comparer
26
- self._restrictive: bool = restrictive
27
- self._context: ClauseContextType = context if context else ClauseInfoContext()
23
+ self.comparer: set[Comparer] = set(comparer)
24
+ self.restrictive: bool = restrictive
28
25
 
29
- @staticmethod
30
- def FUNCTION_NAME() -> str:
31
- return "WHERE"
26
+ def used_columns(self) -> tp.Iterable[ColumnProxy]:
27
+ res = []
32
28
 
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}"
29
+ for comparer in self.comparer:
30
+ if isinstance(comparer.left_condition, ColumnProxy):
31
+ res.append(comparer.left_condition)
46
32
 
47
- @property
48
- def alias_clause(self) -> None:
49
- return None
33
+ if isinstance(comparer.right_condition, ColumnProxy):
34
+ res.append(comparer.right_condition)
50
35
 
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,)
36
+ return res
55
37
 
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)
38
+ def add_comparers(self, comparers: tp.Iterable[Comparer]) -> None:
39
+ if not isinstance(comparers, tp.Iterable):
40
+ comparers = [comparers]
41
+
42
+ for comparer in comparers:
43
+ self.comparer.add(comparer)
63
44
 
64
45
 
65
46
  __all__ = ["Where"]
@@ -1 +1,2 @@
1
1
  from .column import Column # noqa: F401
2
+ from .column_proxy import ColumnProxy # noqa: F401
@@ -1,9 +1,9 @@
1
1
  from __future__ import annotations
2
- import abc
3
- from typing import Annotated, Any, ClassVar, Iterable, Type, Optional, TYPE_CHECKING, get_type_hints, overload, get_origin, get_args
4
- from ormlambda.sql.types import TableType, ComparerType, ColumnType
2
+ from typing import Annotated, Any, Iterable, Type, Optional, TYPE_CHECKING, get_type_hints, overload, get_origin, get_args, ClassVar
3
+ from ormlambda.sql.elements import ClauseElement
4
+ from ormlambda.sql.types import TableType, ComparerType, ColumnType, TypeEngine
5
5
  from ormlambda import ConditionType
6
- from ormlambda.sql.type_api import TypeEngine
6
+ from ormlambda import util
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  import re
@@ -23,7 +23,8 @@ from ormlambda.types import (
23
23
  )
24
24
 
25
25
 
26
- class Column[TProp]:
26
+ class Column[TProp](ClauseElement):
27
+ __visit_name__ = "column"
27
28
  PRIVATE_CHAR: ClassVar[str] = "_"
28
29
 
29
30
  _dbtype: Optional[TypeEngine]
@@ -44,6 +45,8 @@ class Column[TProp]:
44
45
  "_check",
45
46
  )
46
47
 
48
+ table: Type[Table]
49
+
47
50
  @overload
48
51
  def __init__(self, *, column_name: str): ...
49
52
 
@@ -100,8 +103,8 @@ class Column[TProp]:
100
103
  def __str__(self) -> str:
101
104
  return self.table.__table_name__ + "." + self.column_name
102
105
 
103
- def __set_name__[T: Table](self, owner: TableType[T], name: str) -> None:
104
- self.table: TableType[T] = owner
106
+ def __set_name__(self, owner: TableType[Table], name: str) -> None:
107
+ self.table: TableType[Table] = owner
105
108
  self.column_name = name
106
109
  self.__private_name = self.PRIVATE_CHAR + name
107
110
 
@@ -133,9 +136,12 @@ class Column[TProp]:
133
136
  )
134
137
  )
135
138
 
139
+ @util.preload_module("ormlambda.sql.type_api")
136
140
  def _fill_from_annotations[T: Table](self, obj: Type[T], name: str) -> None:
137
141
  """Read the metada when using Annotated typing class, and set the attributes accordingly"""
138
142
 
143
+ TypeEngine = util.preloaded.sql_type_api.TypeEngine
144
+
139
145
  annotations = get_type_hints(obj, include_extras=True)
140
146
  if name in annotations:
141
147
  annotation = annotations[name]
@@ -182,9 +188,9 @@ class Column[TProp]:
182
188
  def dbtype(self) -> TypeEngine[TProp]:
183
189
  return self._dbtype if self._dbtype else self.dtype
184
190
 
185
- @abc.abstractmethod
191
+ @util.preload_module("ormlambda.sql.comparer")
186
192
  def __comparer_creator(self, other: ColumnType, compare: ComparerType) -> Comparer:
187
- from ormlambda.sql.comparer import Comparer
193
+ Comparer = util.preloaded.sql_comparer.Comparer
188
194
 
189
195
  return Comparer(self, other, compare)
190
196
 
@@ -215,20 +221,21 @@ class Column[TProp]:
215
221
  def not_contains(self, other: ColumnType) -> Comparer:
216
222
  return self.__comparer_creator(other, ConditionType.NOT_IN.value)
217
223
 
224
+ @util.preload_module("ormlambda.sql.comparer")
218
225
  def regex(self, pattern: str, flags: Optional[re.RegexFlag | Iterable[re.RegexFlag]] = None) -> Regex:
219
- from ormlambda.sql.comparer import Regex
226
+ Regex = util.preloaded.sql_comparer.Regex
220
227
 
221
228
  if not isinstance(flags, Iterable):
222
229
  flags = (flags,)
223
230
  return Regex(
224
231
  left_condition=self,
225
232
  right_condition=pattern,
226
- context=None,
227
233
  flags=flags,
228
234
  )
229
235
 
236
+ @util.preload_module("ormlambda.sql.comparer")
230
237
  def like(self, pattern: str) -> Like:
231
- from ormlambda.sql.comparer import Like
238
+ Like = util.preloaded.sql_comparer.Like
232
239
 
233
240
  return Like(self, pattern)
234
241