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
@@ -1,163 +1,331 @@
1
1
  from __future__ import annotations
2
- from typing import Iterable, TypedDict, Optional, TYPE_CHECKING
2
+ from typing import Callable, Generator, Optional, TYPE_CHECKING, Iterable, overload, Concatenate
3
+ from ormlambda.sql.clause_info import IAggregate
4
+ from ormlambda.sql.clauses import (
5
+ Select,
6
+ Where,
7
+ Having,
8
+ Order,
9
+ GroupBy,
10
+ Limit,
11
+ Offset,
12
+ JoinSelector,
13
+ )
3
14
 
4
- from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
5
- from ormlambda.sql.clauses import JoinSelector
6
- from ormlambda import ForeignKey
7
-
8
- from ormlambda.common.interfaces import IQuery
9
-
10
-
11
- from ormlambda.sql.clause_info import ClauseInfo
12
15
 
13
16
  if TYPE_CHECKING:
14
- from ..sql.clauses import Limit as Limit
15
- from ..sql.clauses import Offset as Offset
16
- from ..sql.clauses import Order as Order
17
- from ..sql.clauses import Select as Select
18
-
19
- from ..sql.clauses import GroupBy as GroupBy
20
-
21
17
  from ormlambda.dialects import Dialect
18
+ from ormlambda import Count
19
+
20
+ from ormlambda import ColumnProxy, TableProxy
22
21
 
23
- from ..sql.clauses import Where as Where
24
- from ..sql.clauses import Having as Having
25
22
  from ormlambda.common.enums import JoinType
26
23
  from ormlambda.sql.elements import ClauseElement
24
+ from ormlambda.common.interfaces import IQuery
25
+
26
+ # =============================================================================
27
+ # CLEAN QUERY COMPONENTS
28
+ # =============================================================================
29
+
30
+
31
+ class QueryComponents:
32
+ """Clean storage for all query components"""
33
+
34
+ __slots__ = (
35
+ "select",
36
+ "where",
37
+ "having",
38
+ "order",
39
+ "group_by",
40
+ "limit",
41
+ "offset",
42
+ "joins",
43
+ "count",
44
+ )
45
+
46
+ def __init__(
47
+ self,
48
+ ):
49
+ self.select: Optional[Select] = None
50
+ self.where: Optional[Where] = None
51
+ self.having: Optional[Having] = None
52
+ self.order: Optional[Order] = None
53
+ self.group_by: Optional[GroupBy] = None
54
+ self.limit: Optional[Limit] = None
55
+ self.offset: Optional[Offset] = None
56
+ self.joins: Optional[set[JoinSelector]] = {}
57
+ self.count: Optional[Count] = None
27
58
 
59
+ def clear(self) -> None:
60
+ """Reset all components"""
28
61
 
29
- class OrderType(TypedDict):
30
- Select: Select
31
- JoinSelector: JoinSelector
32
- Where: Where
33
- Order: Order
34
- GroupBy: GroupBy
35
- Having: Having
36
- Limit: Limit
37
- Offset: Offset
62
+ for att_name in self.__slots__:
63
+ # We can restore those cases that are different of None
64
+ if att_name == "joins":
65
+ setattr(self, att_name, set())
66
+ else:
67
+ setattr(self, att_name, None)
38
68
 
39
69
 
40
- class QueryBuilder(IQuery):
41
- __order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "GroupBy", "Having", "Order", "Limit", "Offset")
70
+ # =============================================================================
71
+ # QUERY COMPILER INTERFACE
72
+ # =============================================================================
42
73
 
43
- def __init__(self, dialect: Dialect, by: JoinType = JoinType.INNER_JOIN):
74
+
75
+ class StandardSQLCompiler:
76
+ """Standard SQL compiler"""
77
+
78
+ def __init__(self, dialect: Dialect):
44
79
  self.dialect = dialect
45
- self._context = ClauseInfoContext()
46
- self._query_list: OrderType = {}
47
- self._by = by
48
-
49
- self._joins: Optional[IQuery] = None
50
- self._select: Optional[IQuery] = None
51
- self._where: Optional[IQuery] = None
52
- self._order: Optional[IQuery] = None
53
- self._group_by: Optional[IQuery] = None
54
- self._limit: Optional[IQuery] = None
55
- self._offset: Optional[IQuery] = None
56
-
57
- def add_statement[T](self, clause: ClauseInfo[T]):
58
- self.update_context(clause)
59
- self._query_list[type(clause).__name__] = clause
60
80
 
61
- @property
62
- def by(self) -> JoinType:
63
- return self._by
81
+ def compile(self, components: QueryComponents, joins: set[JoinSelector]) -> str:
82
+ """Compile all components into final SQL"""
83
+ query_parts = []
64
84
 
65
- @by.setter
66
- def by(self, value: JoinType) -> None:
67
- self._by = value
85
+ # SELECT
86
+ if components.select:
87
+ query_parts.append(components.select.compile(self.dialect).string)
68
88
 
69
- @property
70
- def JOINS(self) -> Optional[set[JoinSelector]]:
71
- return self._joins
89
+ # JOINs
90
+ joins_sql = self._compile_joins(joins)
91
+ if joins_sql:
92
+ query_parts.append(joins_sql)
72
93
 
73
- @property
74
- def SELECT(self) -> ClauseElement:
75
- return self._query_list.get("Select", None)
94
+ # WHERE
95
+ if components.where:
96
+ query_parts.append(components.where.compile(self.dialect).string)
76
97
 
77
- @property
78
- def WHERE(self) -> ClauseElement:
79
- where = self._query_list.get("Where", None)
80
- if not isinstance(where, Iterable):
81
- if not where:
82
- return ()
83
- return (where,)
84
- return where
98
+ # GROUP BY
99
+ if components.group_by:
100
+ query_parts.append(components.group_by.compile(self.dialect).string)
85
101
 
86
- @property
87
- def ORDER(self) -> ClauseElement:
88
- return self._query_list.get("Order", None)
102
+ # HAVING
103
+ if components.having:
104
+ query_parts.append(components.having.compile(self.dialect).string)
89
105
 
90
- @property
91
- def GROUP_BY(self) -> ClauseElement:
92
- return self._query_list.get("GroupBy", None)
106
+ # ORDER BY
107
+ if components.order:
108
+ query_parts.append(components.order.compile(self.dialect).string)
93
109
 
94
- @property
95
- def HAVING(self) -> ClauseElement:
96
- where = self._query_list.get("Having", None)
97
- if not isinstance(where, Iterable):
98
- if not where:
99
- return ()
100
- return (where,)
101
- return where
110
+ # LIMIT
111
+ if components.limit:
112
+ query_parts.append(components.limit.compile(self.dialect).string)
102
113
 
103
- @property
104
- def LIMIT(self) -> ClauseElement:
105
- return self._query_list.get("Limit", None)
114
+ # OFFSET
115
+ if components.offset:
116
+ query_parts.append(components.offset.compile(self.dialect).string)
106
117
 
107
- @property
108
- def OFFSET(self) -> ClauseElement:
109
- return self._query_list.get("Offset", None)
110
-
111
- def query(self, dialect: Dialect, **kwargs) -> str:
112
- # COMMENT: (select.query, query)We must first create an alias for 'FROM' and then define all the remaining clauses.
113
- # This order is mandatory because it adds the clause name to the context when accessing the .query property of 'FROM'
114
-
115
- extract_joins = self.pop_tables_and_create_joins_from_ForeignKey(self._by)
116
-
117
- JOINS = self.stringify_foreign_key(extract_joins, " ")
118
- query_list: tuple[str, ...] = (
119
- self.SELECT.compile(dialect).string,
120
- JOINS,
121
- Where.join_condition(self.WHERE, True, self._context, dialect=dialect) if self.WHERE else None,
122
- self.GROUP_BY.compile(dialect).string if self.GROUP_BY else None,
123
- Having.join_condition(self.HAVING, True, self._context, dialect=dialect) if self.HAVING else None,
124
- self.ORDER.compile(dialect).string if self.ORDER else None,
125
- self.LIMIT.compile(dialect).string if self.LIMIT else None,
126
- self.OFFSET.compile(dialect).string if self.OFFSET else None,
127
- )
128
- return " ".join([x for x in query_list if x])
129
-
130
- def stringify_foreign_key(self, joins: set[JoinSelector], sep: str = "\n") -> Optional[str]:
118
+ return " ".join(query_parts)
119
+
120
+ def _compile_joins(self, joins: set[JoinSelector]) -> Optional[str]:
121
+ """Compile JOIN clauses"""
131
122
  if not joins:
132
123
  return None
133
- sorted_joins = JoinSelector.sort_join_selectors(joins)
134
- return f"{sep}".join([join.query(self.dialect) for join in sorted_joins])
135
124
 
136
- def pop_tables_and_create_joins_from_ForeignKey(self, by: JoinType = JoinType.INNER_JOIN) -> set[JoinSelector]:
137
- # When we applied filters in any table that we wont select any column, we need to add manually all neccessary joins to achieve positive result.
138
- if not ForeignKey.stored_calls:
125
+ from ormlambda.sql.clauses import JoinSelector
126
+
127
+ sorted_joins = JoinSelector.sort_joins_by_alias(joins)
128
+ return " ".join(join.compile(self.dialect).string for join in sorted_joins)
129
+
130
+ # =============================================================================
131
+ # MODERN QUERY BUILDER
132
+ # =============================================================================
133
+
134
+
135
+ class ColumnIterable[T: TableProxy | ColumnProxy]:
136
+ @overload
137
+ def __init__(self) -> None: ...
138
+ @overload
139
+ def __init__(self, iterable: Iterable[T], /) -> None: ...
140
+
141
+ def __init__(self, iterable=None):
142
+ self.iterable: list = iterable if iterable is not None else []
143
+
144
+ def __repr__(self):
145
+ return self.iterable.__repr__()
146
+
147
+ def append(self, object: T, /) -> None:
148
+ if isinstance(object, TableProxy):
149
+ self.extend(object.get_columns())
139
150
  return None
140
151
 
141
- joins = set()
142
- # Always it's gonna be a set of two
143
- # FIXME [x]: Resolved when we get Compare object instead ClauseInfo. For instance, when we have multiples condition using '&' or '|'
144
- for fk in ForeignKey.stored_calls.copy():
145
- fk = ForeignKey.stored_calls.pop(fk)
146
- fk_alias = fk.get_alias(self.dialect)
147
- self._context._add_table_alias(fk.tright, fk_alias)
148
- join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk_alias, dialect=self.dialect)
149
- joins.add(join)
152
+ return self.iterable.append(object)
150
153
 
151
- return joins
154
+ def extend(self, iterable: Iterable[T], /) -> None:
155
+ for item in iterable:
156
+ if isinstance(item, TableProxy):
157
+ self.iterable.extend(item.get_columns())
158
+ else:
159
+ self.iterable.append(item)
160
+ return None
152
161
 
153
162
  def clear(self) -> None:
154
- self.__init__(self.dialect, self.by)
163
+ return self.iterable.clear()
164
+
165
+ def __iter__(self) -> Generator[ColumnProxy, None, None]:
166
+ yield from self.iterable
167
+
168
+ def __len__(self) -> int:
169
+ return len(self.iterable)
170
+
171
+ def __getitem__(self, index: int) -> T:
172
+ return self.iterable[index]
173
+
174
+
175
+ def call_used_column[T, **P](f: Callable[Concatenate[QueryBuilder, IAggregate, P], T]) -> Callable[Concatenate[QueryBuilder, IAggregate, P], T]:
176
+ def wrapped(self: QueryBuilder, aggregate: IAggregate, *args: P.args, **kwargs: P.kwargs):
177
+ self.used_columns.extend(aggregate.used_columns())
178
+ return f(self, aggregate, *args, **kwargs)
179
+
180
+ return wrapped
181
+
182
+
183
+ class QueryBuilder(IQuery):
184
+ compiler: StandardSQLCompiler
185
+ components: QueryComponents
186
+ used_columns: ColumnIterable[ColumnProxy]
187
+ join_type: JoinType
188
+
189
+ def __init__(self):
190
+ # Clean component storage
191
+ self.components = QueryComponents()
192
+ self.join_type = JoinType.INNER_JOIN
193
+ self.used_columns = ColumnIterable()
194
+
195
+ # =============================================================================
196
+ # CLEAN CLAUSE MANAGEMENT
197
+ # =============================================================================
198
+
199
+ @call_used_column
200
+ def add_select(self, select: Select) -> QueryBuilder:
201
+ self.components.select = select
202
+ return self
203
+
204
+ @call_used_column
205
+ def add_where(self, where: Where) -> QueryBuilder:
206
+ if not self.components.where:
207
+ # we need to do that in order to initialize with our first where clause to control 'restrictive' attribute.
208
+ # Otherwise, we'd always has the same attribute
209
+ self.components.where = where
210
+ return self
211
+
212
+ comparer = where.comparer
213
+ self.components.where.add_comparers(comparer)
214
+ return self
215
+
216
+ @call_used_column
217
+ def add_having(self, having: Having) -> QueryBuilder:
218
+ if not self.components.having:
219
+ # we need to do that in order to initialize with our first having clause to control 'restrictive' attribute.
220
+ # Otherwise, we'd always has the same attribute
221
+ self.components.having = having
222
+ return self
223
+
224
+ comparer = having.comparer
225
+ self.components.having.add_comparers(comparer)
226
+ return self
227
+
228
+ @call_used_column
229
+ def add_order(self, order: Order) -> QueryBuilder:
230
+ self.components.order = order
231
+ return self
232
+
233
+ @call_used_column
234
+ def add_group_by(self, group_by: GroupBy) -> QueryBuilder:
235
+ self.components.group_by = group_by
236
+ return self
237
+
238
+ def add_limit(self, limit: Limit) -> QueryBuilder:
239
+ self.components.limit = limit
240
+ return self
241
+
242
+ def add_offset(self, offset: Offset) -> QueryBuilder:
243
+ self.components.offset = offset
244
+ return self
245
+
246
+ @call_used_column
247
+ def add_join(self, join: JoinSelector) -> QueryBuilder:
248
+ self.components.joins.add(join)
249
+ return self
250
+
251
+ @call_used_column
252
+ def add_count(self, count: Count) -> QueryBuilder:
253
+ self.components.count = count
254
+ return self
255
+
256
+ def set_join_type(self, join_type: JoinType) -> QueryBuilder:
257
+ self.join_type = join_type
258
+ return self
259
+
260
+ # =============================================================================
261
+ # GENERIC CLAUSE ADDITION (for existing code compatibility)
262
+ # =============================================================================
263
+
264
+ def add_statement(self, clause: ClauseElement) -> QueryBuilder:
265
+ """
266
+ Add any clause element - determines type automatically
267
+
268
+ This provides a single entry point for all clause types
269
+ while maintaining clean type-specific methods above
270
+ """
271
+ clause_type = type(clause).__name__
272
+
273
+ clause_selector: dict[str, Callable[[ClauseElement], QueryBuilder]] = {
274
+ "Select": self.add_select,
275
+ "Where": self.add_where,
276
+ "Having": self.add_having,
277
+ "Order": self.add_order,
278
+ "GroupBy": self.add_group_by,
279
+ "Limit": self.add_limit,
280
+ "Offset": self.add_offset,
281
+ "JoinSelector": self.add_join,
282
+ "Count": self.add_count,
283
+ }
284
+
285
+ method = clause_selector.get(clause_type, None)
286
+ if not method:
287
+ raise ValueError(f"Unknown clause type: {clause_type}")
288
+
289
+ return method(clause)
290
+
291
+ # =============================================================================
292
+ # QUERY GENERATION
293
+ # =============================================================================
294
+
295
+ def query(self, dialect: Optional[Dialect] = None) -> str:
296
+ # Detect required joins
297
+ all_joins = self.pop_tables_and_create_joins_from_ForeignKey(dialect)
298
+
299
+ # Compile to SQL
300
+ return StandardSQLCompiler(dialect).compile(self.components, all_joins)
301
+
302
+ def pop_tables_and_create_joins_from_ForeignKey(self, dialect) -> set[JoinSelector]:
303
+ # When we applied filters in any table that we wont select any column, we need to add manually all neccessary joins to achieve positive result.
304
+
305
+ joins: set[JoinSelector] = set()
306
+
307
+ join_type = self.by if self.by != JoinType.INNER_JOIN else JoinType.LEFT_INCLUSIVE
308
+ for column in self.used_columns:
309
+ if not column.number_table_in_chain():
310
+ continue # It would be the same table so we don't need any join clause
311
+
312
+ temp_joins = column.get_relations(join_type, dialect)
313
+
314
+ [joins.add(x) for x in temp_joins]
315
+
316
+ sorted_joins = JoinSelector.sort_joins_by_alias(joins)
317
+ return sorted_joins
318
+
319
+ def clear(self) -> None:
320
+ """Clear all components"""
321
+ self.components.clear()
322
+ self.used_columns.clear()
155
323
  return None
156
324
 
157
- def update_context(self, clause: ClauseInfo) -> None:
158
- if not hasattr(clause, "context"):
159
- return None
325
+ @property
326
+ def by(self) -> Optional[JoinType]:
327
+ return self.join_type
160
328
 
161
- if clause.context is not None:
162
- self._context.update(clause.context)
163
- clause.context = self._context
329
+ @by.setter
330
+ def by(self, value: JoinType) -> None:
331
+ self.join_type = value