ormlambda 3.35.3__py3-none-any.whl → 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ormlambda/__init__.py +79 -51
- ormlambda/caster/caster.py +6 -1
- ormlambda/common/abstract_classes/__init__.py +0 -2
- ormlambda/common/enums/__init__.py +1 -0
- ormlambda/common/enums/order_type.py +9 -0
- ormlambda/common/errors/__init__.py +13 -3
- ormlambda/common/global_checker.py +86 -8
- ormlambda/common/interfaces/IQueryCommand.py +2 -2
- ormlambda/common/interfaces/__init__.py +0 -2
- ormlambda/dialects/__init__.py +75 -3
- ormlambda/dialects/default/base.py +1 -1
- ormlambda/dialects/mysql/__init__.py +35 -78
- ormlambda/dialects/mysql/base.py +226 -40
- ormlambda/dialects/mysql/clauses/ST_AsText.py +26 -0
- ormlambda/dialects/mysql/clauses/ST_Contains.py +30 -0
- ormlambda/dialects/mysql/clauses/__init__.py +1 -0
- ormlambda/dialects/mysql/repository/__init__.py +1 -0
- ormlambda/{databases/my_sql → dialects/mysql/repository}/repository.py +0 -5
- ormlambda/dialects/mysql/types.py +6 -0
- ormlambda/engine/base.py +26 -4
- ormlambda/errors.py +9 -0
- ormlambda/model/base_model.py +3 -10
- ormlambda/repository/base_repository.py +1 -1
- ormlambda/repository/interfaces/IRepositoryBase.py +0 -7
- ormlambda/repository/response.py +12 -7
- ormlambda/sql/__init__.py +12 -3
- ormlambda/sql/clause_info/__init__.py +0 -2
- ormlambda/sql/clause_info/clause_info.py +94 -76
- ormlambda/sql/clause_info/interface/IAggregate.py +14 -4
- ormlambda/sql/clause_info/interface/IClauseInfo.py +6 -11
- ormlambda/sql/clauses/alias.py +6 -37
- ormlambda/sql/clauses/count.py +21 -36
- ormlambda/sql/clauses/group_by.py +13 -19
- ormlambda/sql/clauses/having.py +2 -6
- ormlambda/sql/clauses/insert.py +3 -3
- ormlambda/sql/clauses/interfaces/__init__.py +0 -1
- ormlambda/sql/clauses/join/join_context.py +5 -12
- ormlambda/sql/clauses/joins.py +34 -52
- ormlambda/sql/clauses/limit.py +1 -2
- ormlambda/sql/clauses/offset.py +1 -2
- ormlambda/sql/clauses/order.py +17 -21
- ormlambda/sql/clauses/select.py +56 -28
- ormlambda/sql/clauses/update.py +13 -10
- ormlambda/sql/clauses/where.py +20 -39
- ormlambda/sql/column/__init__.py +1 -0
- ormlambda/sql/column/column.py +19 -12
- ormlambda/sql/column/column_proxy.py +117 -0
- ormlambda/sql/column_table_proxy.py +23 -0
- ormlambda/sql/comparer.py +31 -65
- ormlambda/sql/compiler.py +248 -58
- ormlambda/sql/context/__init__.py +304 -0
- ormlambda/sql/ddl.py +19 -5
- ormlambda/sql/elements.py +3 -0
- ormlambda/sql/foreign_key.py +42 -64
- ormlambda/sql/functions/__init__.py +0 -1
- ormlambda/sql/functions/concat.py +35 -38
- ormlambda/sql/functions/max.py +12 -36
- ormlambda/sql/functions/min.py +13 -28
- ormlambda/sql/functions/sum.py +17 -33
- ormlambda/sql/sqltypes.py +2 -0
- ormlambda/sql/table/__init__.py +1 -0
- ormlambda/sql/table/table.py +31 -45
- ormlambda/sql/table/table_proxy.py +88 -0
- ormlambda/sql/type_api.py +4 -1
- ormlambda/sql/types.py +15 -12
- ormlambda/statements/__init__.py +0 -2
- ormlambda/statements/base_statement.py +51 -84
- ormlambda/statements/interfaces/IStatements.py +77 -123
- ormlambda/statements/interfaces/__init__.py +1 -1
- ormlambda/statements/query_builder.py +296 -128
- ormlambda/statements/statements.py +120 -110
- ormlambda/statements/types.py +5 -25
- ormlambda/util/__init__.py +7 -100
- ormlambda/util/langhelpers.py +102 -0
- ormlambda/util/module_tree/dynamic_module.py +1 -1
- ormlambda/util/preloaded.py +80 -0
- ormlambda/util/typing.py +12 -3
- {ormlambda-3.35.3.dist-info → ormlambda-4.0.0.dist-info}/METADATA +29 -31
- ormlambda-4.0.0.dist-info/RECORD +139 -0
- ormlambda/common/abstract_classes/clause_info_converter.py +0 -65
- ormlambda/common/abstract_classes/decomposition_query.py +0 -141
- ormlambda/common/abstract_classes/query_base.py +0 -15
- ormlambda/common/interfaces/ICustomAlias.py +0 -7
- ormlambda/common/interfaces/IDecompositionQuery.py +0 -33
- ormlambda/databases/__init__.py +0 -4
- ormlambda/databases/my_sql/__init__.py +0 -3
- ormlambda/databases/my_sql/clauses/ST_AsText.py +0 -37
- ormlambda/databases/my_sql/clauses/ST_Contains.py +0 -36
- ormlambda/databases/my_sql/clauses/__init__.py +0 -14
- ormlambda/databases/my_sql/clauses/count.py +0 -33
- ormlambda/databases/my_sql/clauses/delete.py +0 -9
- ormlambda/databases/my_sql/clauses/drop_table.py +0 -26
- ormlambda/databases/my_sql/clauses/group_by.py +0 -17
- ormlambda/databases/my_sql/clauses/having.py +0 -12
- ormlambda/databases/my_sql/clauses/insert.py +0 -9
- ormlambda/databases/my_sql/clauses/joins.py +0 -14
- ormlambda/databases/my_sql/clauses/limit.py +0 -6
- ormlambda/databases/my_sql/clauses/offset.py +0 -6
- ormlambda/databases/my_sql/clauses/order.py +0 -8
- ormlambda/databases/my_sql/clauses/update.py +0 -8
- ormlambda/databases/my_sql/clauses/upsert.py +0 -9
- ormlambda/databases/my_sql/clauses/where.py +0 -7
- ormlambda/dialects/interface/__init__.py +0 -1
- ormlambda/dialects/interface/dialect.py +0 -78
- ormlambda/sql/clause_info/aggregate_function_base.py +0 -96
- ormlambda/sql/clause_info/clause_info_context.py +0 -87
- ormlambda/sql/clauses/interfaces/ISelect.py +0 -17
- ormlambda/sql/clauses/new_join.py +0 -119
- ormlambda/util/load_module.py +0 -21
- ormlambda/util/plugin_loader.py +0 -32
- ormlambda-3.35.3.dist-info/RECORD +0 -159
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/caster.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/boolean.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/date.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/decimal.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/json.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql/repository}/pool_types.py +0 -0
- {ormlambda-3.35.3.dist-info → ormlambda-4.0.0.dist-info}/AUTHORS +0 -0
- {ormlambda-3.35.3.dist-info → ormlambda-4.0.0.dist-info}/LICENSE +0 -0
- {ormlambda-3.35.3.dist-info → ormlambda-4.0.0.dist-info}/WHEEL +0 -0
@@ -1,163 +1,331 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
41
|
-
|
70
|
+
# =============================================================================
|
71
|
+
# QUERY COMPILER INTERFACE
|
72
|
+
# =============================================================================
|
42
73
|
|
43
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
81
|
+
def compile(self, components: QueryComponents, joins: set[JoinSelector]) -> str:
|
82
|
+
"""Compile all components into final SQL"""
|
83
|
+
query_parts = []
|
64
84
|
|
65
|
-
|
66
|
-
|
67
|
-
|
85
|
+
# SELECT
|
86
|
+
if components.select:
|
87
|
+
query_parts.append(components.select.compile(self.dialect).string)
|
68
88
|
|
69
|
-
|
70
|
-
|
71
|
-
|
89
|
+
# JOINs
|
90
|
+
joins_sql = self._compile_joins(joins)
|
91
|
+
if joins_sql:
|
92
|
+
query_parts.append(joins_sql)
|
72
93
|
|
73
|
-
|
74
|
-
|
75
|
-
|
94
|
+
# WHERE
|
95
|
+
if components.where:
|
96
|
+
query_parts.append(components.where.compile(self.dialect).string)
|
76
97
|
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
102
|
+
# HAVING
|
103
|
+
if components.having:
|
104
|
+
query_parts.append(components.having.compile(self.dialect).string)
|
89
105
|
|
90
|
-
|
91
|
-
|
92
|
-
|
106
|
+
# ORDER BY
|
107
|
+
if components.order:
|
108
|
+
query_parts.append(components.order.compile(self.dialect).string)
|
93
109
|
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
114
|
+
# OFFSET
|
115
|
+
if components.offset:
|
116
|
+
query_parts.append(components.offset.compile(self.dialect).string)
|
106
117
|
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
158
|
-
|
159
|
-
|
325
|
+
@property
|
326
|
+
def by(self) -> Optional[JoinType]:
|
327
|
+
return self.join_type
|
160
328
|
|
161
|
-
|
162
|
-
|
163
|
-
|
329
|
+
@by.setter
|
330
|
+
def by(self, value: JoinType) -> None:
|
331
|
+
self.join_type = value
|