pydoptic-sql 0.0.1.post1.dev2__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.
@@ -0,0 +1,1144 @@
1
+
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from typing import Any, Generic, List, Sequence, Tuple, Type, TypeVar, cast
5
+ from pydoptic import PartialModel
6
+ from pydoptic.selector import PropSelect, Prop, PropOpt, Param
7
+ from pydoptic_sql import SqlTable
8
+ from pydoptic_sql.sql_constraint import A, TC, TC1, TC2, TC3, Constraint, Constraint2, Constraint3, Constraint4, _qualified_label
9
+ from pydoptic_sql.sql_order import Direction, OrderBy
10
+ from pydoptic_sql.sql_computed import AggregateFunction, Computed, ComputedResult
11
+ from pydoptic_sql.sql_having import HavingConstraint, HavingConstraint2, HavingConstraint3, HavingConstraint4
12
+ from pydoptic_sql.sql_table import (
13
+ AutoIncrement,
14
+ Check,
15
+ ColumnConstraint,
16
+ ColumnInfo,
17
+ ColumnType,
18
+ Default,
19
+ ForeignKey,
20
+ ManualColumnConstraint,
21
+ PrimaryKey,
22
+ Unique,
23
+ )
24
+
25
+ R = TypeVar('R')
26
+
27
+ class SqlQuery(Generic[R]):
28
+ # R is the result type of executing the query (e.g. PartialModel[TC], or None); table type(s) are tracked separately per subclass.
29
+ def to_sql(self) -> str:
30
+ """Render this query as a single SQL string with values interpolated -- for display/debugging only."""
31
+ raise NotImplementedError()
32
+
33
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
34
+ """Render this query as a parameterized SQL string (`%s` placeholders) plus its bound values, in order --
35
+ what actually gets executed, so data values are never interpolated into the SQL text itself."""
36
+ raise NotImplementedError()
37
+
38
+ @classmethod
39
+ def from_table(cls, table: Type[TC]) -> 'Query1[TC]':
40
+ return Query1(table)
41
+
42
+ @classmethod
43
+ def create(cls, table: Type[TC]) -> 'CreateQuery[TC]':
44
+ return CreateQuery(table)
45
+
46
+ @classmethod
47
+ def drop(cls, table: Type[TC]) -> 'DropQuery[TC]':
48
+ return DropQuery(table)
49
+
50
+ @classmethod
51
+ def insert(cls, row: TC) -> 'InsertQuery[TC]':
52
+ return InsertQuery(row)
53
+
54
+ @classmethod
55
+ def update(cls, table: Type[TC], value: Param[TC, Any], *values: Param[TC, Any]) -> 'UpdateQuery[TC]':
56
+ return UpdateQuery(table, [value, *values])
57
+
58
+ @classmethod
59
+ def delete(cls, table: Type[TC]) -> 'DeleteQuery[TC]':
60
+ return DeleteQuery(table)
61
+
62
+ @classmethod
63
+ def sum(cls, column: Prop[TC, A] | PropOpt[TC, A], alias: str | None = None) -> 'Computed[TC, A]':
64
+ return Computed(column, AggregateFunction.SUM, alias or _default_computed_alias(column, AggregateFunction.SUM), column.target)
65
+
66
+ @classmethod
67
+ def avg(cls, column: Prop[TC, Any] | PropOpt[TC, Any], alias: str | None = None) -> 'Computed[TC, float]':
68
+ return Computed(column, AggregateFunction.AVG, alias or _default_computed_alias(column, AggregateFunction.AVG), float)
69
+
70
+ @classmethod
71
+ def min(cls, column: Prop[TC, A] | PropOpt[TC, A], alias: str | None = None) -> 'Computed[TC, A]':
72
+ return Computed(column, AggregateFunction.MIN, alias or _default_computed_alias(column, AggregateFunction.MIN), column.target)
73
+
74
+ @classmethod
75
+ def max(cls, column: Prop[TC, A] | PropOpt[TC, A], alias: str | None = None) -> 'Computed[TC, A]':
76
+ return Computed(column, AggregateFunction.MAX, alias or _default_computed_alias(column, AggregateFunction.MAX), column.target)
77
+
78
+ @classmethod
79
+ def count(cls, table: Type[TC], alias: str | None = None) -> 'Computed[TC, int]':
80
+ return Computed(None, AggregateFunction.COUNT, alias or f'{table.__name__.lower()}_count', int)
81
+
82
+ @classmethod
83
+ def count_col(cls, column: Prop[TC, Any] | PropOpt[TC, Any], alias: str | None = None) -> 'Computed[TC, int]':
84
+ return Computed(column, AggregateFunction.COUNT, alias or _default_computed_alias(column, AggregateFunction.COUNT), int)
85
+
86
+
87
+ def _all_props(table: Type[SqlTable]) -> List[PropSelect[Any, Any]]:
88
+ return [prop for prop in table.properties().values() if isinstance(prop, PropSelect)]
89
+
90
+ def _resolve_selection(selection: Sequence[PropSelect[Any, Any]] | None, *tables: Type[SqlTable]) -> List[PropSelect[Any, Any]]:
91
+ """Unset selection ('you didn't call select()') defaults to every column of every joined table -- i.e. SELECT *."""
92
+ if selection is not None:
93
+ return list(selection)
94
+ result: List[PropSelect[Any, Any]] = []
95
+ for table in tables:
96
+ result.extend(_all_props(table))
97
+ return result
98
+
99
+ def _order_by_sql(order_by: Sequence[OrderBy[Any]], qualify: bool) -> str:
100
+ """Render an ' ORDER BY ...' clause (leading space included), or '' if there's nothing to order by.
101
+ Qualification (table.column vs. bare column) is decided here by the caller, not by OrderBy itself --
102
+ OrderBy has no arity variants, so it doesn't know which query arity it's being rendered for."""
103
+ if not order_by:
104
+ return ''
105
+ label = _qualified_label if qualify else (lambda p: p.label)
106
+ return ' ORDER BY ' + ', '.join(f'{label(ob.column)} {ob.direction.value}' for ob in order_by)
107
+
108
+ def _group_by_sql(group_by: Sequence[PropSelect[Any, Any]], qualify: bool) -> str:
109
+ """Render a ' GROUP BY ...' clause (leading space included), or '' if there's nothing to group by.
110
+ Same externalized-qualification approach as _order_by_sql."""
111
+ if not group_by:
112
+ return ''
113
+ label = _qualified_label if qualify else (lambda p: p.label)
114
+ return ' GROUP BY ' + ', '.join(label(p) for p in group_by)
115
+
116
+ def _default_computed_alias(column: PropSelect[Any, Any], function: AggregateFunction) -> str:
117
+ return f'{column.origin.__name__.lower()}_{column.label}_{function.value.lower()}'
118
+
119
+ def _computed_sql_parts(computed: Sequence[Computed[Any, Any]], qualify: bool) -> List[str]:
120
+ """Render each computed expression as 'FUNC(col_ref) AS alias' for inclusion in a SELECT list.
121
+ Same externalized-qualification approach as _order_by_sql/_group_by_sql -- Computed has no arity
122
+ variants either, so it doesn't know which query arity it's being rendered for."""
123
+ def render(c: Computed[Any, Any]) -> str:
124
+ col_ref = '*' if c.column is None else (_qualified_label(c.column) if qualify else c.column.label)
125
+ return f'{c.function.value}({col_ref}) AS {c.label}'
126
+ return [render(c) for c in computed]
127
+
128
+
129
+ class JoinType(Enum):
130
+ Left = 'LEFT'
131
+ Inner = 'INNER'
132
+
133
+
134
+ # --- 1-4 tables: QueryN/ComputedQueryN ---
135
+ # There used to be a separate "builder" class per arity (SelectQuery/JoinQueryN) that had no
136
+ # to_sql()/to_sql_params() of its own -- calling where() was the one-time transition into a "terminal"
137
+ # class (QueryN) that did. That split existed because a WHERE/HAVING constraint set before a later
138
+ # join_inner()/join_left() couldn't be safely re-typed for the wider arity: ConstraintN/
139
+ # HavingConstraintN are distinct, unrelated classes per arity, not one class widened via a union the
140
+ # way OrderBy/Computed are.
141
+ #
142
+ # Constraint.incr_arity()/HavingConstraint.incr_arity() (see sql_constraint.py/sql_having.py) remove
143
+ # that obstacle -- a constraint set at arity N can now be safely rewrapped into the arity-(N+1) class,
144
+ # with the exact same operands, whenever a join widens the query. So where()/having() no longer need
145
+ # to be a special one-time transition: join_inner()/join_left() just carries _where/_having across by
146
+ # calling incr_arity() on them when set, and QueryN/ComputedQueryN are directly executable
147
+ # (to_sql()/to_sql_params()) at every stage, builder and "terminal" alike -- hence one class per arity
148
+ # instead of two. select_computed(_more) still splits off into a separate ComputedQueryN from QueryN,
149
+ # since the result type R differs (PartialModel[...] vs Tuple[..., ComputedResult]) and R can't vary
150
+ # at runtime for a single dataclass.
151
+ #
152
+ # _order_by/_group_by/_computed still widen by one union member per table added (rather than gaining
153
+ # an arity variant the way Constraint/HavingConstraint do), since none of OrderBy/Computed/a plain
154
+ # group-by column ever references more than one table at a time -- an entry set before a join stays
155
+ # exactly as valid after it, with no re-wrapping needed.
156
+
157
+ @dataclass(frozen=True)
158
+ class Query1(Generic[TC], SqlQuery[PartialModel[TC]]):
159
+ table1: Type[TC]
160
+ _selection: Sequence[PropSelect[TC, Any]] | None = None
161
+ _where: Constraint[TC] | None = None
162
+ _order_by: Sequence[OrderBy[TC]] = ()
163
+ _group_by: Sequence[PropSelect[TC, Any]] = ()
164
+
165
+ def select(self, sel: PropSelect[TC, Any], *sels: PropSelect[TC, Any]) -> 'Query1[TC]':
166
+ return Query1(self.table1, [sel, *sels], self._where, self._order_by, self._group_by)
167
+
168
+ def select_more(self, sel: PropSelect[TC, Any], *sels: PropSelect[TC, Any]) -> 'Query1[TC]':
169
+ return Query1(self.table1, [*(self._selection or []), sel, *sels], self._where, self._order_by, self._group_by)
170
+
171
+ def select_computed(self, computed: Computed[TC, Any], *more: Computed[TC, Any]) -> 'ComputedQuery1[TC]':
172
+ return ComputedQuery1(self.table1, self._selection, self._where, self._order_by, self._group_by, [computed, *more])
173
+
174
+ def select_computed_more(self, computed: Computed[TC, Any], *more: Computed[TC, Any]) -> 'ComputedQuery1[TC]':
175
+ return ComputedQuery1(self.table1, self._selection, self._where, self._order_by, self._group_by, [computed, *more])
176
+
177
+ def order_by(self, *order_by: OrderBy[TC]) -> 'Query1[TC]':
178
+ return Query1(self.table1, self._selection, self._where, list(order_by), self._group_by)
179
+
180
+ def order_by_more(self, column: Prop[TC, Any] | PropOpt[TC, Any], direction: Direction = Direction.ASC) -> 'Query1[TC]':
181
+ return Query1(self.table1, self._selection, self._where, [*self._order_by, OrderBy(column, direction)], self._group_by)
182
+
183
+ def group_by(self, *group_by: PropSelect[TC, Any]) -> 'Query1[TC]':
184
+ return Query1(self.table1, self._selection, self._where, self._order_by, list(group_by))
185
+
186
+ def group_by_more(self, col: PropSelect[TC, Any], *cols: PropSelect[TC, Any]) -> 'Query1[TC]':
187
+ return Query1(self.table1, self._selection, self._where, self._order_by, [*self._group_by, col, *cols])
188
+
189
+ def join_inner(self, next: Type[TC1], on: Constraint2[TC, TC1] | None = None) -> 'Query2[TC, TC1]':
190
+ return Query2(self.table1, next, JoinType.Inner, on, self._selection, None if self._where is None else self._where.incr_arity(), self._order_by, self._group_by)
191
+
192
+ def join_left(self, next: Type[TC1], on: Constraint2[TC, TC1] | None = None) -> 'Query2[TC, TC1]':
193
+ return Query2(self.table1, next, JoinType.Left, on, self._selection, None if self._where is None else self._where.incr_arity(), self._order_by, self._group_by)
194
+
195
+ def where(self, constraint: Constraint[TC] | None = None) -> 'Query1[TC]':
196
+ return Query1(self.table1, self._selection, constraint, self._order_by, self._group_by)
197
+
198
+ def where_and(self, constraint: Constraint[TC]) -> 'Query1[TC]':
199
+ return self.where(constraint if self._where is None else self._where.AND(constraint))
200
+
201
+ def where_or(self, constraint: Constraint[TC]) -> 'Query1[TC]':
202
+ return self.where(constraint if self._where is None else self._where.OR(constraint))
203
+
204
+ def _resolved_selection(self) -> List[PropSelect[Any, Any]]:
205
+ return _resolve_selection(self._selection, self.table1)
206
+
207
+ def to_sql(self) -> str:
208
+ selection = self._resolved_selection()
209
+ assert len(selection) > 0, 'You must select at least one column'
210
+ selections = ', '.join(p.label for p in selection)
211
+ where_clause = '' if self._where is None else (' WHERE ' + self._where.to_sql())
212
+ group_by_clause = _group_by_sql(self._group_by, qualify=False)
213
+ order_by_clause = _order_by_sql(self._order_by, qualify=False)
214
+ return f'SELECT {selections} FROM {self.table1.__name__.lower()}{where_clause}{group_by_clause}{order_by_clause};'
215
+
216
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
217
+ selection = self._resolved_selection()
218
+ assert len(selection) > 0, 'You must select at least one column'
219
+ selections = ', '.join(p.label for p in selection)
220
+ group_by_clause = _group_by_sql(self._group_by, qualify=False)
221
+ order_by_clause = _order_by_sql(self._order_by, qualify=False)
222
+ if self._where is None:
223
+ return f'SELECT {selections} FROM {self.table1.__name__.lower()}{group_by_clause}{order_by_clause};', []
224
+ where_clause, params = self._where.to_sql_params()
225
+ return f'SELECT {selections} FROM {self.table1.__name__.lower()} WHERE {where_clause}{group_by_clause}{order_by_clause};', params
226
+
227
+ @dataclass(frozen=True)
228
+ class ComputedQuery1(Generic[TC], SqlQuery[Tuple[PartialModel[TC], ComputedResult]]):
229
+ table1: Type[TC]
230
+ _selection: Sequence[PropSelect[TC, Any]] | None = None
231
+ _where: Constraint[TC] | None = None
232
+ _order_by: Sequence[OrderBy[TC]] = ()
233
+ _group_by: Sequence[PropSelect[TC, Any]] = ()
234
+ _computed: Sequence[Computed[TC, Any]] = ()
235
+ _having: HavingConstraint[TC] | None = None
236
+
237
+ def select(self, sel: PropSelect[TC, Any], *sels: PropSelect[TC, Any]) -> 'ComputedQuery1[TC]':
238
+ return ComputedQuery1(self.table1, [sel, *sels], self._where, self._order_by, self._group_by, self._computed, self._having)
239
+
240
+ def select_more(self, sel: PropSelect[TC, Any], *sels: PropSelect[TC, Any]) -> 'ComputedQuery1[TC]':
241
+ return ComputedQuery1(self.table1, [*(self._selection or []), sel, *sels], self._where, self._order_by, self._group_by, self._computed, self._having)
242
+
243
+ def select_computed(self, computed: Computed[TC, Any], *more: Computed[TC, Any]) -> 'ComputedQuery1[TC]':
244
+ return ComputedQuery1(self.table1, self._selection, self._where, self._order_by, self._group_by, [computed, *more], self._having)
245
+
246
+ def select_computed_more(self, computed: Computed[TC, Any], *more: Computed[TC, Any]) -> 'ComputedQuery1[TC]':
247
+ return ComputedQuery1(self.table1, self._selection, self._where, self._order_by, self._group_by, [*self._computed, computed, *more], self._having)
248
+
249
+ def order_by(self, *order_by: OrderBy[TC]) -> 'ComputedQuery1[TC]':
250
+ return ComputedQuery1(self.table1, self._selection, self._where, list(order_by), self._group_by, self._computed, self._having)
251
+
252
+ def order_by_more(self, column: Prop[TC, Any] | PropOpt[TC, Any], direction: Direction = Direction.ASC) -> 'ComputedQuery1[TC]':
253
+ return ComputedQuery1(self.table1, self._selection, self._where, [*self._order_by, OrderBy(column, direction)], self._group_by, self._computed, self._having)
254
+
255
+ def group_by(self, *group_by: PropSelect[TC, Any]) -> 'ComputedQuery1[TC]':
256
+ return ComputedQuery1(self.table1, self._selection, self._where, self._order_by, list(group_by), self._computed, self._having)
257
+
258
+ def group_by_more(self, col: PropSelect[TC, Any], *cols: PropSelect[TC, Any]) -> 'ComputedQuery1[TC]':
259
+ return ComputedQuery1(self.table1, self._selection, self._where, self._order_by, [*self._group_by, col, *cols], self._computed, self._having)
260
+
261
+ def having(self, constraint: HavingConstraint[TC] | None = None) -> 'ComputedQuery1[TC]':
262
+ return ComputedQuery1(self.table1, self._selection, self._where, self._order_by, self._group_by, self._computed, constraint)
263
+
264
+ def having_and(self, constraint: HavingConstraint[TC]) -> 'ComputedQuery1[TC]':
265
+ return self.having(constraint if self._having is None else self._having.AND(constraint))
266
+
267
+ def having_or(self, constraint: HavingConstraint[TC]) -> 'ComputedQuery1[TC]':
268
+ return self.having(constraint if self._having is None else self._having.OR(constraint))
269
+
270
+ def where(self, constraint: Constraint[TC] | None = None) -> 'ComputedQuery1[TC]':
271
+ return ComputedQuery1(self.table1, self._selection, constraint, self._order_by, self._group_by, self._computed, self._having)
272
+
273
+ def where_and(self, constraint: Constraint[TC]) -> 'ComputedQuery1[TC]':
274
+ return self.where(constraint if self._where is None else self._where.AND(constraint))
275
+
276
+ def where_or(self, constraint: Constraint[TC]) -> 'ComputedQuery1[TC]':
277
+ return self.where(constraint if self._where is None else self._where.OR(constraint))
278
+
279
+ def join_inner(self, next: Type[TC1], on: Constraint2[TC, TC1] | None = None) -> 'ComputedQuery2[TC, TC1]':
280
+ return ComputedQuery2(
281
+ self.table1, next, JoinType.Inner, on,
282
+ self._selection, None if self._where is None else self._where.incr_arity(),
283
+ self._order_by, self._group_by, self._computed,
284
+ None if self._having is None else self._having.incr_arity(),
285
+ )
286
+
287
+ def join_left(self, next: Type[TC1], on: Constraint2[TC, TC1] | None = None) -> 'ComputedQuery2[TC, TC1]':
288
+ return ComputedQuery2(
289
+ self.table1, next, JoinType.Left, on,
290
+ self._selection, None if self._where is None else self._where.incr_arity(),
291
+ self._order_by, self._group_by, self._computed,
292
+ None if self._having is None else self._having.incr_arity(),
293
+ )
294
+
295
+ def _resolved_selection(self) -> List[PropSelect[Any, Any]]:
296
+ # Unlike the plain QueryN, an unset selection here defaults to *no* plain columns rather than
297
+ # every column -- SELECT * alongside an aggregate is almost never valid SQL (every
298
+ # unaggregated column would need to be in GROUP BY), so defaulting to "just the computed
299
+ # columns" is far more often what's actually wanted.
300
+ return list(self._selection or [])
301
+
302
+ def to_sql(self) -> str:
303
+ selection = self._resolved_selection()
304
+ assert len(selection) > 0 or len(self._computed) > 0, 'You must select at least one column or computed value'
305
+ selections = ', '.join([*(p.label for p in selection), *_computed_sql_parts(self._computed, qualify=False)])
306
+ where_clause = '' if self._where is None else (' WHERE ' + self._where.to_sql())
307
+ group_by_clause = _group_by_sql(self._group_by, qualify=False)
308
+ having_clause = '' if self._having is None else (' HAVING ' + self._having.to_sql())
309
+ order_by_clause = _order_by_sql(self._order_by, qualify=False)
310
+ return f'SELECT {selections} FROM {self.table1.__name__.lower()}{where_clause}{group_by_clause}{having_clause}{order_by_clause};'
311
+
312
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
313
+ selection = self._resolved_selection()
314
+ assert len(selection) > 0 or len(self._computed) > 0, 'You must select at least one column or computed value'
315
+ selections = ', '.join([*(p.label for p in selection), *_computed_sql_parts(self._computed, qualify=False)])
316
+ group_by_clause = _group_by_sql(self._group_by, qualify=False)
317
+ order_by_clause = _order_by_sql(self._order_by, qualify=False)
318
+ params: List[Any] = []
319
+ where_clause = ''
320
+ if self._where is not None:
321
+ where_sql, where_params = self._where.to_sql_params()
322
+ where_clause = ' WHERE ' + where_sql
323
+ params += where_params
324
+ having_clause = ''
325
+ if self._having is not None:
326
+ having_sql, having_params = self._having.to_sql_params()
327
+ having_clause = ' HAVING ' + having_sql
328
+ params += having_params
329
+ return f'SELECT {selections} FROM {self.table1.__name__.lower()}{where_clause}{group_by_clause}{having_clause}{order_by_clause};', params
330
+
331
+
332
+ # --- CREATE / DROP / INSERT / UPDATE / DELETE (always single-table) ---
333
+
334
+ @dataclass
335
+ class DropQuery(Generic[TC], SqlQuery[None]):
336
+ _model: Type[TC]
337
+
338
+ def to_sql(self) -> str:
339
+ return f'DROP TABLE {self._model.__name__.lower()};'
340
+
341
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
342
+ return self.to_sql(), []
343
+
344
+ def _sql_literal(value: Any) -> str:
345
+ if value is None:
346
+ return 'NULL'
347
+ if isinstance(value, str):
348
+ return "'" + value + "'"
349
+ return str(value)
350
+
351
+ def _column_constraint_to_sql(constraint: ColumnConstraint) -> str:
352
+ if constraint is PrimaryKey:
353
+ return 'PRIMARY KEY'
354
+ if constraint is Unique:
355
+ return 'UNIQUE'
356
+ if constraint is AutoIncrement:
357
+ return 'AUTOINCREMENT'
358
+ if isinstance(constraint, ForeignKey):
359
+ return f'REFERENCES {constraint.references.origin.__name__.lower()}({constraint.references.label})'
360
+ if isinstance(constraint, Check):
361
+ return f'CHECK ({constraint.constraint})'
362
+ if isinstance(constraint, Default):
363
+ return f'DEFAULT {_sql_literal(constraint.value)}'
364
+ if isinstance(constraint, ManualColumnConstraint):
365
+ return constraint.type
366
+ raise ValueError(f'Unknown column constraint: {constraint}')
367
+
368
+ @dataclass(frozen=True)
369
+ class CreateQuery(Generic[TC], SqlQuery[None]):
370
+ _model: Type[TC]
371
+
372
+ def to_sql(self) -> str:
373
+ header = f'CREATE TABLE {self._model.__name__.lower()} (\n'
374
+ footer = '\n);'
375
+
376
+ columns: List[str] = []
377
+
378
+ for prop in self._model.properties().values():
379
+ if isinstance(prop, PropSelect):
380
+ prop_data = cast(ColumnInfo, prop.data)
381
+ constraints = prop_data['constraints'] if 'constraints' in prop_data else []
382
+ tpe = prop_data['type'] if 'type' in prop_data else ColumnType.from_type(prop.target)
383
+ constraint_sqls = [_column_constraint_to_sql(c) for c in constraints]
384
+ column = ' '.join([f'{prop.label} {tpe.to_sql()}', *constraint_sqls])
385
+ columns.append(column)
386
+
387
+ return header + ',\n'.join(columns) + footer
388
+
389
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
390
+ # DDL: any literal values here (e.g. a Default constraint's value) come from the model
391
+ # definition in code, not runtime data, so there's nothing to parameterize.
392
+ return self.to_sql(), []
393
+
394
+
395
+ @dataclass(frozen=True)
396
+ class InsertQuery(Generic[TC], SqlQuery[None]):
397
+ row: TC
398
+
399
+ def _row_values(self) -> List[Any]:
400
+ values: List[Any] = []
401
+ for prop in self.row.__class__.properties().values():
402
+ if isinstance(prop, Prop):
403
+ values.append(prop.get_val(self.row))
404
+ elif isinstance(prop, PropOpt):
405
+ values.append(prop.get_val(self.row))
406
+ return values
407
+
408
+ def to_sql(self) -> str:
409
+ values_sql = ', '.join(_sql_literal(v) for v in self._row_values())
410
+ return f'INSERT INTO {self.row.__class__.__name__.lower()} VALUES ({values_sql});'
411
+
412
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
413
+ values = self._row_values()
414
+ placeholders = ', '.join(['%s'] * len(values))
415
+ return f'INSERT INTO {self.row.__class__.__name__.lower()} VALUES ({placeholders});', values
416
+
417
+ @dataclass(frozen=True)
418
+ class UpdateQuery(Generic[TC], SqlQuery[None]):
419
+ _model: Type[TC]
420
+ _values: List[Param[TC, Any]]
421
+ _where: Constraint[TC] | None = None
422
+
423
+ def where(self, constraint: Constraint[TC]) -> 'UpdateQuery[TC]':
424
+ return UpdateQuery(self._model, self._values, constraint)
425
+
426
+ def to_sql(self) -> str:
427
+ assert len(self._values) > 0, 'You must set at least one value'
428
+ assignments = ', '.join(f'{p.label} = {_sql_literal(p.value)}' for p in self._values)
429
+ where_clause = '' if self._where is None else (' WHERE ' + self._where.to_sql())
430
+ return f'UPDATE {self._model.__name__.lower()} SET {assignments}{where_clause};'
431
+
432
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
433
+ assert len(self._values) > 0, 'You must set at least one value'
434
+ assignments = ', '.join(f'{p.label} = %s' for p in self._values)
435
+ params: List[Any] = [p.value for p in self._values]
436
+ if self._where is None:
437
+ return f'UPDATE {self._model.__name__.lower()} SET {assignments};', params
438
+ where_clause, where_params = self._where.to_sql_params()
439
+ return f'UPDATE {self._model.__name__.lower()} SET {assignments} WHERE {where_clause};', params + where_params
440
+
441
+ @dataclass(frozen=True)
442
+ class DeleteQuery(Generic[TC], SqlQuery[None]):
443
+ _model: Type[TC]
444
+ _where: Constraint[TC] | None = None
445
+
446
+ def where(self, constraint: Constraint[TC]) -> 'DeleteQuery[TC]':
447
+ return DeleteQuery(self._model, constraint)
448
+
449
+ def to_sql(self) -> str:
450
+ where_clause = '' if self._where is None else (' WHERE ' + self._where.to_sql())
451
+ return f'DELETE FROM {self._model.__name__.lower()}{where_clause};'
452
+
453
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
454
+ if self._where is None:
455
+ return f'DELETE FROM {self._model.__name__.lower()};', []
456
+ where_clause, params = self._where.to_sql_params()
457
+ return f'DELETE FROM {self._model.__name__.lower()} WHERE {where_clause};', params
458
+
459
+
460
+ # --- 2 tables ---
461
+
462
+ @dataclass(frozen=True)
463
+ class Query2(Generic[TC, TC1], SqlQuery[Tuple[PartialModel[TC], PartialModel[TC1]]]):
464
+ table1: Type[TC]
465
+ table2: Type[TC1]
466
+ join_type_2: JoinType
467
+ on_2: Constraint2[TC, TC1] | None
468
+ _selection: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any]] | None = None
469
+ _where: Constraint2[TC, TC1] | None = None
470
+ _order_by: Sequence[OrderBy[TC] | OrderBy[TC1]] = ()
471
+ _group_by: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any]] = ()
472
+
473
+ def select(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any]) -> 'Query2[TC, TC1]':
474
+ return Query2(self.table1, self.table2, self.join_type_2, self.on_2, [sel, *sels], self._where, self._order_by, self._group_by)
475
+
476
+ def select_more(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any]) -> 'Query2[TC, TC1]':
477
+ return Query2(self.table1, self.table2, self.join_type_2, self.on_2, [*(self._selection or []), sel, *sels], self._where, self._order_by, self._group_by)
478
+
479
+ def select_computed(self, computed: Computed[TC, Any] | Computed[TC1, Any], *more: Computed[TC, Any] | Computed[TC1, Any]) -> 'ComputedQuery2[TC, TC1]':
480
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, self._order_by, self._group_by, [computed, *more])
481
+
482
+ def select_computed_more(self, computed: Computed[TC, Any] | Computed[TC1, Any], *more: Computed[TC, Any] | Computed[TC1, Any]) -> 'ComputedQuery2[TC, TC1]':
483
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, self._order_by, self._group_by, [computed, *more])
484
+
485
+ def order_by(self, *order_by: OrderBy[TC] | OrderBy[TC1]) -> 'Query2[TC, TC1]':
486
+ return Query2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, list(order_by), self._group_by)
487
+
488
+ def order_by_more(self, column: Prop[TC, Any] | PropOpt[TC, Any] | Prop[TC1, Any] | PropOpt[TC1, Any], direction: Direction = Direction.ASC) -> 'Query2[TC, TC1]':
489
+ new_entry: OrderBy[TC] | OrderBy[TC1] = OrderBy(column, direction) # type: ignore[assignment]
490
+ return Query2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, [*self._order_by, new_entry], self._group_by)
491
+
492
+ def group_by(self, *group_by: PropSelect[TC, Any] | PropSelect[TC1, Any]) -> 'Query2[TC, TC1]':
493
+ return Query2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, self._order_by, list(group_by))
494
+
495
+ def group_by_more(self, col: PropSelect[TC, Any] | PropSelect[TC1, Any], *cols: PropSelect[TC, Any] | PropSelect[TC1, Any]) -> 'Query2[TC, TC1]':
496
+ return Query2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, self._order_by, [*self._group_by, col, *cols])
497
+
498
+ def join_inner(self, next: Type[TC2], on: Constraint3[TC, TC1, TC2] | None = None) -> 'Query3[TC, TC1, TC2]':
499
+ return Query3(
500
+ self.table1, self.table2, next, self.join_type_2, self.on_2, JoinType.Inner, on,
501
+ self._selection, None if self._where is None else self._where.incr_arity(),
502
+ self._order_by, self._group_by,
503
+ )
504
+
505
+ def join_left(self, next: Type[TC2], on: Constraint3[TC, TC1, TC2] | None = None) -> 'Query3[TC, TC1, TC2]':
506
+ return Query3(
507
+ self.table1, self.table2, next, self.join_type_2, self.on_2, JoinType.Left, on,
508
+ self._selection, None if self._where is None else self._where.incr_arity(),
509
+ self._order_by, self._group_by,
510
+ )
511
+
512
+ def where(self, constraint: Constraint2[TC, TC1] | None = None) -> 'Query2[TC, TC1]':
513
+ return Query2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, constraint, self._order_by, self._group_by)
514
+
515
+ def where_and(self, constraint: Constraint2[TC, TC1]) -> 'Query2[TC, TC1]':
516
+ return self.where(constraint if self._where is None else self._where.AND(constraint))
517
+
518
+ def where_or(self, constraint: Constraint2[TC, TC1]) -> 'Query2[TC, TC1]':
519
+ return self.where(constraint if self._where is None else self._where.OR(constraint))
520
+
521
+ def _resolved_selection(self) -> List[PropSelect[Any, Any]]:
522
+ return _resolve_selection(self._selection, self.table1, self.table2)
523
+
524
+ def to_sql(self) -> str:
525
+ selection = self._resolved_selection()
526
+ assert len(selection) > 0, 'You must select at least one column'
527
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
528
+
529
+ selections = ', '.join(_qualified_label(p) for p in selection)
530
+ where_clause = '' if self._where is None else ' WHERE ' + self._where.to_sql()
531
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
532
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
533
+
534
+ from_clause = f'{self.table1.__name__.lower()}'
535
+ on_2_clause = self.on_2.to_sql()
536
+ from_clause += f' {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
537
+
538
+ return f'SELECT {selections} FROM {from_clause}{where_clause}{group_by_clause}{order_by_clause};'
539
+
540
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
541
+ selection = self._resolved_selection()
542
+ assert len(selection) > 0, 'You must select at least one column'
543
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
544
+
545
+ selections = ', '.join(_qualified_label(p) for p in selection)
546
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
547
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
548
+ params: List[Any] = []
549
+
550
+ on_2_clause, on_2_params = self.on_2.to_sql_params()
551
+ params += on_2_params
552
+ from_clause = f'{self.table1.__name__.lower()} {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
553
+
554
+ if self._where is None:
555
+ return f'SELECT {selections} FROM {from_clause}{group_by_clause}{order_by_clause};', params
556
+ where_clause, where_params = self._where.to_sql_params()
557
+ params += where_params
558
+ return f'SELECT {selections} FROM {from_clause} WHERE {where_clause}{group_by_clause}{order_by_clause};', params
559
+
560
+ @dataclass(frozen=True)
561
+ class ComputedQuery2(Generic[TC, TC1], SqlQuery[Tuple[PartialModel[TC], PartialModel[TC1], ComputedResult]]):
562
+ table1: Type[TC]
563
+ table2: Type[TC1]
564
+ join_type_2: JoinType
565
+ on_2: Constraint2[TC, TC1] | None
566
+ _selection: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any]] | None = None
567
+ _where: Constraint2[TC, TC1] | None = None
568
+ _order_by: Sequence[OrderBy[TC] | OrderBy[TC1]] = ()
569
+ _group_by: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any]] = ()
570
+ _computed: Sequence[Computed[TC, Any] | Computed[TC1, Any]] = ()
571
+ _having: HavingConstraint2[TC, TC1] | None = None
572
+
573
+ def select(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any]) -> 'ComputedQuery2[TC, TC1]':
574
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, [sel, *sels], self._where, self._order_by, self._group_by, self._computed, self._having)
575
+
576
+ def select_more(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any]) -> 'ComputedQuery2[TC, TC1]':
577
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, [*(self._selection or []), sel, *sels], self._where, self._order_by, self._group_by, self._computed, self._having)
578
+
579
+ def select_computed(self, computed: Computed[TC, Any] | Computed[TC1, Any], *more: Computed[TC, Any] | Computed[TC1, Any]) -> 'ComputedQuery2[TC, TC1]':
580
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, self._order_by, self._group_by, [computed, *more], self._having)
581
+
582
+ def select_computed_more(self, computed: Computed[TC, Any] | Computed[TC1, Any], *more: Computed[TC, Any] | Computed[TC1, Any]) -> 'ComputedQuery2[TC, TC1]':
583
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, self._order_by, self._group_by, [*self._computed, computed, *more], self._having)
584
+
585
+ def order_by(self, *order_by: OrderBy[TC] | OrderBy[TC1]) -> 'ComputedQuery2[TC, TC1]':
586
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, list(order_by), self._group_by, self._computed, self._having)
587
+
588
+ def order_by_more(self, column: Prop[TC, Any] | PropOpt[TC, Any] | Prop[TC1, Any] | PropOpt[TC1, Any], direction: Direction = Direction.ASC) -> 'ComputedQuery2[TC, TC1]':
589
+ new_entry: OrderBy[TC] | OrderBy[TC1] = OrderBy(column, direction) # type: ignore[assignment]
590
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, [*self._order_by, new_entry], self._group_by, self._computed, self._having)
591
+
592
+ def group_by(self, *group_by: PropSelect[TC, Any] | PropSelect[TC1, Any]) -> 'ComputedQuery2[TC, TC1]':
593
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, self._order_by, list(group_by), self._computed, self._having)
594
+
595
+ def group_by_more(self, col: PropSelect[TC, Any] | PropSelect[TC1, Any], *cols: PropSelect[TC, Any] | PropSelect[TC1, Any]) -> 'ComputedQuery2[TC, TC1]':
596
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, self._order_by, [*self._group_by, col, *cols], self._computed, self._having)
597
+
598
+ def having(self, constraint: HavingConstraint2[TC, TC1] | None = None) -> 'ComputedQuery2[TC, TC1]':
599
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, self._where, self._order_by, self._group_by, self._computed, constraint)
600
+
601
+ def having_and(self, constraint: HavingConstraint2[TC, TC1]) -> 'ComputedQuery2[TC, TC1]':
602
+ return self.having(constraint if self._having is None else self._having.AND(constraint))
603
+
604
+ def having_or(self, constraint: HavingConstraint2[TC, TC1]) -> 'ComputedQuery2[TC, TC1]':
605
+ return self.having(constraint if self._having is None else self._having.OR(constraint))
606
+
607
+ def where(self, constraint: Constraint2[TC, TC1] | None = None) -> 'ComputedQuery2[TC, TC1]':
608
+ return ComputedQuery2(self.table1, self.table2, self.join_type_2, self.on_2, self._selection, constraint, self._order_by, self._group_by, self._computed, self._having)
609
+
610
+ def where_and(self, constraint: Constraint2[TC, TC1]) -> 'ComputedQuery2[TC, TC1]':
611
+ return self.where(constraint if self._where is None else self._where.AND(constraint))
612
+
613
+ def where_or(self, constraint: Constraint2[TC, TC1]) -> 'ComputedQuery2[TC, TC1]':
614
+ return self.where(constraint if self._where is None else self._where.OR(constraint))
615
+
616
+ def join_inner(self, next: Type[TC2], on: Constraint3[TC, TC1, TC2] | None = None) -> 'ComputedQuery3[TC, TC1, TC2]':
617
+ return ComputedQuery3(
618
+ self.table1, self.table2, next, self.join_type_2, self.on_2, JoinType.Inner, on,
619
+ self._selection, None if self._where is None else self._where.incr_arity(),
620
+ self._order_by, self._group_by, self._computed,
621
+ None if self._having is None else self._having.incr_arity(),
622
+ )
623
+
624
+ def join_left(self, next: Type[TC2], on: Constraint3[TC, TC1, TC2] | None = None) -> 'ComputedQuery3[TC, TC1, TC2]':
625
+ return ComputedQuery3(
626
+ self.table1, self.table2, next, self.join_type_2, self.on_2, JoinType.Left, on,
627
+ self._selection, None if self._where is None else self._where.incr_arity(),
628
+ self._order_by, self._group_by, self._computed,
629
+ None if self._having is None else self._having.incr_arity(),
630
+ )
631
+
632
+ def _resolved_selection(self) -> List[PropSelect[Any, Any]]:
633
+ return list(self._selection or [])
634
+
635
+ def to_sql(self) -> str:
636
+ selection = self._resolved_selection()
637
+ assert len(selection) > 0 or len(self._computed) > 0, 'You must select at least one column or computed value'
638
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
639
+
640
+ selections = ', '.join([*(_qualified_label(p) for p in selection), *_computed_sql_parts(self._computed, qualify=True)])
641
+ where_clause = '' if self._where is None else ' WHERE ' + self._where.to_sql()
642
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
643
+ having_clause = '' if self._having is None else ' HAVING ' + self._having.to_sql()
644
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
645
+
646
+ from_clause = f'{self.table1.__name__.lower()}'
647
+ on_2_clause = self.on_2.to_sql()
648
+ from_clause += f' {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
649
+
650
+ return f'SELECT {selections} FROM {from_clause}{where_clause}{group_by_clause}{having_clause}{order_by_clause};'
651
+
652
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
653
+ selection = self._resolved_selection()
654
+ assert len(selection) > 0 or len(self._computed) > 0, 'You must select at least one column or computed value'
655
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
656
+
657
+ selections = ', '.join([*(_qualified_label(p) for p in selection), *_computed_sql_parts(self._computed, qualify=True)])
658
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
659
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
660
+ params: List[Any] = []
661
+
662
+ on_2_clause, on_2_params = self.on_2.to_sql_params()
663
+ params += on_2_params
664
+ from_clause = f'{self.table1.__name__.lower()} {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
665
+
666
+ where_clause = ''
667
+ if self._where is not None:
668
+ where_sql, where_params = self._where.to_sql_params()
669
+ where_clause = ' WHERE ' + where_sql
670
+ params += where_params
671
+ having_clause = ''
672
+ if self._having is not None:
673
+ having_sql, having_params = self._having.to_sql_params()
674
+ having_clause = ' HAVING ' + having_sql
675
+ params += having_params
676
+ return f'SELECT {selections} FROM {from_clause}{where_clause}{group_by_clause}{having_clause}{order_by_clause};', params
677
+
678
+
679
+ # --- 3 tables ---
680
+
681
+ @dataclass(frozen=True)
682
+ class Query3(Generic[TC, TC1, TC2], SqlQuery[Tuple[PartialModel[TC], PartialModel[TC1], PartialModel[TC2]]]):
683
+ table1: Type[TC]
684
+ table2: Type[TC1]
685
+ table3: Type[TC2]
686
+ join_type_2: JoinType
687
+ on_2: Constraint2[TC, TC1] | None
688
+ join_type_3: JoinType
689
+ on_3: Constraint3[TC, TC1, TC2] | None
690
+ _selection: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]] | None = None
691
+ _where: Constraint3[TC, TC1, TC2] | None = None
692
+ _order_by: Sequence[OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2]] = ()
693
+ _group_by: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]] = ()
694
+
695
+ def select(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]) -> 'Query3[TC, TC1, TC2]':
696
+ return Query3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, [sel, *sels], self._where, self._order_by, self._group_by)
697
+
698
+ def select_more(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]) -> 'Query3[TC, TC1, TC2]':
699
+ return Query3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, [*(self._selection or []), sel, *sels], self._where, self._order_by, self._group_by)
700
+
701
+ def select_computed(self, computed: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any], *more: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any]) -> 'ComputedQuery3[TC, TC1, TC2]':
702
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, self._order_by, self._group_by, [computed, *more])
703
+
704
+ def select_computed_more(self, computed: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any], *more: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any]) -> 'ComputedQuery3[TC, TC1, TC2]':
705
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, self._order_by, self._group_by, [computed, *more])
706
+
707
+ def order_by(self, *order_by: OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2]) -> 'Query3[TC, TC1, TC2]':
708
+ return Query3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, list(order_by), self._group_by)
709
+
710
+ def order_by_more(self, column: Prop[TC, Any] | PropOpt[TC, Any] | Prop[TC1, Any] | PropOpt[TC1, Any] | Prop[TC2, Any] | PropOpt[TC2, Any], direction: Direction = Direction.ASC) -> 'Query3[TC, TC1, TC2]':
711
+ new_entry: OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2] = OrderBy(column, direction) # type: ignore[assignment]
712
+ return Query3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, [*self._order_by, new_entry], self._group_by)
713
+
714
+ def group_by(self, *group_by: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]) -> 'Query3[TC, TC1, TC2]':
715
+ return Query3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, self._order_by, list(group_by))
716
+
717
+ def group_by_more(self, col: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any], *cols: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]) -> 'Query3[TC, TC1, TC2]':
718
+ return Query3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, self._order_by, [*self._group_by, col, *cols])
719
+
720
+ def join_inner(self, next: Type[TC3], on: Constraint4[TC, TC1, TC2, TC3] | None = None) -> 'Query4[TC, TC1, TC2, TC3]':
721
+ return Query4(
722
+ self.table1, self.table2, self.table3, next, self.join_type_2, self.on_2, self.join_type_3, self.on_3, JoinType.Inner, on,
723
+ self._selection, None if self._where is None else self._where.incr_arity(),
724
+ self._order_by, self._group_by,
725
+ )
726
+
727
+ def join_left(self, next: Type[TC3], on: Constraint4[TC, TC1, TC2, TC3] | None = None) -> 'Query4[TC, TC1, TC2, TC3]':
728
+ return Query4(
729
+ self.table1, self.table2, self.table3, next, self.join_type_2, self.on_2, self.join_type_3, self.on_3, JoinType.Left, on,
730
+ self._selection, None if self._where is None else self._where.incr_arity(),
731
+ self._order_by, self._group_by,
732
+ )
733
+
734
+ def where(self, constraint: Constraint3[TC, TC1, TC2] | None = None) -> 'Query3[TC, TC1, TC2]':
735
+ return Query3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, constraint, self._order_by, self._group_by)
736
+
737
+ def where_and(self, constraint: Constraint3[TC, TC1, TC2]) -> 'Query3[TC, TC1, TC2]':
738
+ return self.where(constraint if self._where is None else self._where.AND(constraint))
739
+
740
+ def where_or(self, constraint: Constraint3[TC, TC1, TC2]) -> 'Query3[TC, TC1, TC2]':
741
+ return self.where(constraint if self._where is None else self._where.OR(constraint))
742
+
743
+ def _resolved_selection(self) -> List[PropSelect[Any, Any]]:
744
+ return _resolve_selection(self._selection, self.table1, self.table2, self.table3)
745
+
746
+ def to_sql(self) -> str:
747
+ selection = self._resolved_selection()
748
+ assert len(selection) > 0, 'You must select at least one column'
749
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
750
+ assert self.on_3 is not None, 'You must specify a join condition for join 3'
751
+
752
+ selections = ', '.join(_qualified_label(p) for p in selection)
753
+ where_clause = '' if self._where is None else ' WHERE ' + self._where.to_sql()
754
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
755
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
756
+
757
+ from_clause = f'{self.table1.__name__.lower()}'
758
+ on_2_clause = self.on_2.to_sql()
759
+ from_clause += f' {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
760
+ on_3_clause = self.on_3.to_sql()
761
+ from_clause += f' {self.join_type_3.value} JOIN {self.table3.__name__.lower()} ON {on_3_clause}'
762
+
763
+ return f'SELECT {selections} FROM {from_clause}{where_clause}{group_by_clause}{order_by_clause};'
764
+
765
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
766
+ selection = self._resolved_selection()
767
+ assert len(selection) > 0, 'You must select at least one column'
768
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
769
+ assert self.on_3 is not None, 'You must specify a join condition for join 3'
770
+
771
+ selections = ', '.join(_qualified_label(p) for p in selection)
772
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
773
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
774
+ params: List[Any] = []
775
+
776
+ on_2_clause, on_2_params = self.on_2.to_sql_params()
777
+ params += on_2_params
778
+ from_clause = f'{self.table1.__name__.lower()} {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
779
+ on_3_clause, on_3_params = self.on_3.to_sql_params()
780
+ params += on_3_params
781
+ from_clause += f' {self.join_type_3.value} JOIN {self.table3.__name__.lower()} ON {on_3_clause}'
782
+
783
+ if self._where is None:
784
+ return f'SELECT {selections} FROM {from_clause}{group_by_clause}{order_by_clause};', params
785
+ where_clause, where_params = self._where.to_sql_params()
786
+ params += where_params
787
+ return f'SELECT {selections} FROM {from_clause} WHERE {where_clause}{group_by_clause}{order_by_clause};', params
788
+
789
+ @dataclass(frozen=True)
790
+ class ComputedQuery3(Generic[TC, TC1, TC2], SqlQuery[Tuple[PartialModel[TC], PartialModel[TC1], PartialModel[TC2], ComputedResult]]):
791
+ table1: Type[TC]
792
+ table2: Type[TC1]
793
+ table3: Type[TC2]
794
+ join_type_2: JoinType
795
+ on_2: Constraint2[TC, TC1] | None
796
+ join_type_3: JoinType
797
+ on_3: Constraint3[TC, TC1, TC2] | None
798
+ _selection: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]] | None = None
799
+ _where: Constraint3[TC, TC1, TC2] | None = None
800
+ _order_by: Sequence[OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2]] = ()
801
+ _group_by: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]] = ()
802
+ _computed: Sequence[Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any]] = ()
803
+ _having: HavingConstraint3[TC, TC1, TC2] | None = None
804
+
805
+ def select(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]) -> 'ComputedQuery3[TC, TC1, TC2]':
806
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, [sel, *sels], self._where, self._order_by, self._group_by, self._computed, self._having)
807
+
808
+ def select_more(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]) -> 'ComputedQuery3[TC, TC1, TC2]':
809
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, [*(self._selection or []), sel, *sels], self._where, self._order_by, self._group_by, self._computed, self._having)
810
+
811
+ def select_computed(self, computed: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any], *more: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any]) -> 'ComputedQuery3[TC, TC1, TC2]':
812
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, self._order_by, self._group_by, [computed, *more], self._having)
813
+
814
+ def select_computed_more(self, computed: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any], *more: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any]) -> 'ComputedQuery3[TC, TC1, TC2]':
815
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, self._order_by, self._group_by, [*self._computed, computed, *more], self._having)
816
+
817
+ def order_by(self, *order_by: OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2]) -> 'ComputedQuery3[TC, TC1, TC2]':
818
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, list(order_by), self._group_by, self._computed, self._having)
819
+
820
+ def order_by_more(self, column: Prop[TC, Any] | PropOpt[TC, Any] | Prop[TC1, Any] | PropOpt[TC1, Any] | Prop[TC2, Any] | PropOpt[TC2, Any], direction: Direction = Direction.ASC) -> 'ComputedQuery3[TC, TC1, TC2]':
821
+ new_entry: OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2] = OrderBy(column, direction) # type: ignore[assignment]
822
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, [*self._order_by, new_entry], self._group_by, self._computed, self._having)
823
+
824
+ def group_by(self, *group_by: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]) -> 'ComputedQuery3[TC, TC1, TC2]':
825
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, self._order_by, list(group_by), self._computed, self._having)
826
+
827
+ def group_by_more(self, col: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any], *cols: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any]) -> 'ComputedQuery3[TC, TC1, TC2]':
828
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, self._order_by, [*self._group_by, col, *cols], self._computed, self._having)
829
+
830
+ def having(self, constraint: HavingConstraint3[TC, TC1, TC2] | None = None) -> 'ComputedQuery3[TC, TC1, TC2]':
831
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, self._where, self._order_by, self._group_by, self._computed, constraint)
832
+
833
+ def having_and(self, constraint: HavingConstraint3[TC, TC1, TC2]) -> 'ComputedQuery3[TC, TC1, TC2]':
834
+ return self.having(constraint if self._having is None else self._having.AND(constraint))
835
+
836
+ def having_or(self, constraint: HavingConstraint3[TC, TC1, TC2]) -> 'ComputedQuery3[TC, TC1, TC2]':
837
+ return self.having(constraint if self._having is None else self._having.OR(constraint))
838
+
839
+ def where(self, constraint: Constraint3[TC, TC1, TC2] | None = None) -> 'ComputedQuery3[TC, TC1, TC2]':
840
+ return ComputedQuery3(self.table1, self.table2, self.table3, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self._selection, constraint, self._order_by, self._group_by, self._computed, self._having)
841
+
842
+ def where_and(self, constraint: Constraint3[TC, TC1, TC2]) -> 'ComputedQuery3[TC, TC1, TC2]':
843
+ return self.where(constraint if self._where is None else self._where.AND(constraint))
844
+
845
+ def where_or(self, constraint: Constraint3[TC, TC1, TC2]) -> 'ComputedQuery3[TC, TC1, TC2]':
846
+ return self.where(constraint if self._where is None else self._where.OR(constraint))
847
+
848
+ def join_inner(self, next: Type[TC3], on: Constraint4[TC, TC1, TC2, TC3] | None = None) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
849
+ return ComputedQuery4(
850
+ self.table1, self.table2, self.table3, next, self.join_type_2, self.on_2, self.join_type_3, self.on_3, JoinType.Inner, on,
851
+ self._selection, None if self._where is None else self._where.incr_arity(),
852
+ self._order_by, self._group_by, self._computed,
853
+ None if self._having is None else self._having.incr_arity(),
854
+ )
855
+
856
+ def join_left(self, next: Type[TC3], on: Constraint4[TC, TC1, TC2, TC3] | None = None) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
857
+ return ComputedQuery4(
858
+ self.table1, self.table2, self.table3, next, self.join_type_2, self.on_2, self.join_type_3, self.on_3, JoinType.Left, on,
859
+ self._selection, None if self._where is None else self._where.incr_arity(),
860
+ self._order_by, self._group_by, self._computed,
861
+ None if self._having is None else self._having.incr_arity(),
862
+ )
863
+
864
+ def _resolved_selection(self) -> List[PropSelect[Any, Any]]:
865
+ return list(self._selection or [])
866
+
867
+ def to_sql(self) -> str:
868
+ selection = self._resolved_selection()
869
+ assert len(selection) > 0 or len(self._computed) > 0, 'You must select at least one column or computed value'
870
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
871
+ assert self.on_3 is not None, 'You must specify a join condition for join 3'
872
+
873
+ selections = ', '.join([*(_qualified_label(p) for p in selection), *_computed_sql_parts(self._computed, qualify=True)])
874
+ where_clause = '' if self._where is None else ' WHERE ' + self._where.to_sql()
875
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
876
+ having_clause = '' if self._having is None else ' HAVING ' + self._having.to_sql()
877
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
878
+
879
+ from_clause = f'{self.table1.__name__.lower()}'
880
+ on_2_clause = self.on_2.to_sql()
881
+ from_clause += f' {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
882
+ on_3_clause = self.on_3.to_sql()
883
+ from_clause += f' {self.join_type_3.value} JOIN {self.table3.__name__.lower()} ON {on_3_clause}'
884
+
885
+ return f'SELECT {selections} FROM {from_clause}{where_clause}{group_by_clause}{having_clause}{order_by_clause};'
886
+
887
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
888
+ selection = self._resolved_selection()
889
+ assert len(selection) > 0 or len(self._computed) > 0, 'You must select at least one column or computed value'
890
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
891
+ assert self.on_3 is not None, 'You must specify a join condition for join 3'
892
+
893
+ selections = ', '.join([*(_qualified_label(p) for p in selection), *_computed_sql_parts(self._computed, qualify=True)])
894
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
895
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
896
+ params: List[Any] = []
897
+
898
+ on_2_clause, on_2_params = self.on_2.to_sql_params()
899
+ params += on_2_params
900
+ from_clause = f'{self.table1.__name__.lower()} {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
901
+ on_3_clause, on_3_params = self.on_3.to_sql_params()
902
+ params += on_3_params
903
+ from_clause += f' {self.join_type_3.value} JOIN {self.table3.__name__.lower()} ON {on_3_clause}'
904
+
905
+ where_clause = ''
906
+ if self._where is not None:
907
+ where_sql, where_params = self._where.to_sql_params()
908
+ where_clause = ' WHERE ' + where_sql
909
+ params += where_params
910
+ having_clause = ''
911
+ if self._having is not None:
912
+ having_sql, having_params = self._having.to_sql_params()
913
+ having_clause = ' HAVING ' + having_sql
914
+ params += having_params
915
+ return f'SELECT {selections} FROM {from_clause}{where_clause}{group_by_clause}{having_clause}{order_by_clause};', params
916
+
917
+
918
+ # --- 4 tables ---
919
+
920
+ @dataclass(frozen=True)
921
+ class Query4(Generic[TC, TC1, TC2, TC3], SqlQuery[Tuple[PartialModel[TC], PartialModel[TC1], PartialModel[TC2], PartialModel[TC3]]]):
922
+ table1: Type[TC]
923
+ table2: Type[TC1]
924
+ table3: Type[TC2]
925
+ table4: Type[TC3]
926
+ join_type_2: JoinType
927
+ on_2: Constraint2[TC, TC1] | None
928
+ join_type_3: JoinType
929
+ on_3: Constraint3[TC, TC1, TC2] | None
930
+ join_type_4: JoinType
931
+ on_4: Constraint4[TC, TC1, TC2, TC3] | None
932
+ _selection: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]] | None = None
933
+ _where: Constraint4[TC, TC1, TC2, TC3] | None = None
934
+ _order_by: Sequence[OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2] | OrderBy[TC3]] = ()
935
+ _group_by: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]] = ()
936
+
937
+ def select(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]) -> 'Query4[TC, TC1, TC2, TC3]':
938
+ return Query4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, [sel, *sels], self._where, self._order_by, self._group_by)
939
+
940
+ def select_more(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]) -> 'Query4[TC, TC1, TC2, TC3]':
941
+ return Query4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, [*(self._selection or []), sel, *sels], self._where, self._order_by, self._group_by)
942
+
943
+ def select_computed(self, computed: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any] | Computed[TC3, Any], *more: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any] | Computed[TC3, Any]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
944
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, self._order_by, self._group_by, [computed, *more])
945
+
946
+ def select_computed_more(self, computed: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any] | Computed[TC3, Any], *more: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any] | Computed[TC3, Any]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
947
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, self._order_by, self._group_by, [computed, *more])
948
+
949
+ def order_by(self, *order_by: OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2] | OrderBy[TC3]) -> 'Query4[TC, TC1, TC2, TC3]':
950
+ return Query4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, list(order_by), self._group_by)
951
+
952
+ def order_by_more(self, column: Prop[TC, Any] | PropOpt[TC, Any] | Prop[TC1, Any] | PropOpt[TC1, Any] | Prop[TC2, Any] | PropOpt[TC2, Any] | Prop[TC3, Any] | PropOpt[TC3, Any], direction: Direction = Direction.ASC) -> 'Query4[TC, TC1, TC2, TC3]':
953
+ new_entry: OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2] | OrderBy[TC3] = OrderBy(column, direction) # type: ignore[assignment]
954
+ return Query4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, [*self._order_by, new_entry], self._group_by)
955
+
956
+ def group_by(self, *group_by: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]) -> 'Query4[TC, TC1, TC2, TC3]':
957
+ return Query4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, self._order_by, list(group_by))
958
+
959
+ def group_by_more(self, col: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any], *cols: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]) -> 'Query4[TC, TC1, TC2, TC3]':
960
+ return Query4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, self._order_by, [*self._group_by, col, *cols])
961
+
962
+ def where(self, constraint: Constraint4[TC, TC1, TC2, TC3] | None = None) -> 'Query4[TC, TC1, TC2, TC3]':
963
+ return Query4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, constraint, self._order_by, self._group_by)
964
+
965
+ def where_and(self, constraint: Constraint4[TC, TC1, TC2, TC3]) -> 'Query4[TC, TC1, TC2, TC3]':
966
+ return self.where(constraint if self._where is None else self._where.AND(constraint))
967
+
968
+ def where_or(self, constraint: Constraint4[TC, TC1, TC2, TC3]) -> 'Query4[TC, TC1, TC2, TC3]':
969
+ return self.where(constraint if self._where is None else self._where.OR(constraint))
970
+
971
+ def _resolved_selection(self) -> List[PropSelect[Any, Any]]:
972
+ return _resolve_selection(self._selection, self.table1, self.table2, self.table3, self.table4)
973
+
974
+ def to_sql(self) -> str:
975
+ selection = self._resolved_selection()
976
+ assert len(selection) > 0, 'You must select at least one column'
977
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
978
+ assert self.on_3 is not None, 'You must specify a join condition for join 3'
979
+ assert self.on_4 is not None, 'You must specify a join condition for join 4'
980
+
981
+ selections = ', '.join(_qualified_label(p) for p in selection)
982
+ where_clause = '' if self._where is None else ' WHERE ' + self._where.to_sql()
983
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
984
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
985
+
986
+ from_clause = f'{self.table1.__name__.lower()}'
987
+ on_2_clause = self.on_2.to_sql()
988
+ from_clause += f' {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
989
+ on_3_clause = self.on_3.to_sql()
990
+ from_clause += f' {self.join_type_3.value} JOIN {self.table3.__name__.lower()} ON {on_3_clause}'
991
+ on_4_clause = self.on_4.to_sql()
992
+ from_clause += f' {self.join_type_4.value} JOIN {self.table4.__name__.lower()} ON {on_4_clause}'
993
+
994
+ return f'SELECT {selections} FROM {from_clause}{where_clause}{group_by_clause}{order_by_clause};'
995
+
996
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
997
+ selection = self._resolved_selection()
998
+ assert len(selection) > 0, 'You must select at least one column'
999
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
1000
+ assert self.on_3 is not None, 'You must specify a join condition for join 3'
1001
+ assert self.on_4 is not None, 'You must specify a join condition for join 4'
1002
+
1003
+ selections = ', '.join(_qualified_label(p) for p in selection)
1004
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
1005
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
1006
+ params: List[Any] = []
1007
+
1008
+ on_2_clause, on_2_params = self.on_2.to_sql_params()
1009
+ params += on_2_params
1010
+ from_clause = f'{self.table1.__name__.lower()} {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
1011
+ on_3_clause, on_3_params = self.on_3.to_sql_params()
1012
+ params += on_3_params
1013
+ from_clause += f' {self.join_type_3.value} JOIN {self.table3.__name__.lower()} ON {on_3_clause}'
1014
+ on_4_clause, on_4_params = self.on_4.to_sql_params()
1015
+ params += on_4_params
1016
+ from_clause += f' {self.join_type_4.value} JOIN {self.table4.__name__.lower()} ON {on_4_clause}'
1017
+
1018
+ if self._where is None:
1019
+ return f'SELECT {selections} FROM {from_clause}{group_by_clause}{order_by_clause};', params
1020
+ where_clause, where_params = self._where.to_sql_params()
1021
+ params += where_params
1022
+ return f'SELECT {selections} FROM {from_clause} WHERE {where_clause}{group_by_clause}{order_by_clause};', params
1023
+
1024
+ @dataclass(frozen=True)
1025
+ class ComputedQuery4(Generic[TC, TC1, TC2, TC3], SqlQuery[Tuple[PartialModel[TC], PartialModel[TC1], PartialModel[TC2], PartialModel[TC3], ComputedResult]]):
1026
+ table1: Type[TC]
1027
+ table2: Type[TC1]
1028
+ table3: Type[TC2]
1029
+ table4: Type[TC3]
1030
+ join_type_2: JoinType
1031
+ on_2: Constraint2[TC, TC1] | None
1032
+ join_type_3: JoinType
1033
+ on_3: Constraint3[TC, TC1, TC2] | None
1034
+ join_type_4: JoinType
1035
+ on_4: Constraint4[TC, TC1, TC2, TC3] | None
1036
+ _selection: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]] | None = None
1037
+ _where: Constraint4[TC, TC1, TC2, TC3] | None = None
1038
+ _order_by: Sequence[OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2] | OrderBy[TC3]] = ()
1039
+ _group_by: Sequence[PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]] = ()
1040
+ _computed: Sequence[Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any] | Computed[TC3, Any]] = ()
1041
+ _having: HavingConstraint4[TC, TC1, TC2, TC3] | None = None
1042
+
1043
+ def select(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1044
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, [sel, *sels], self._where, self._order_by, self._group_by, self._computed, self._having)
1045
+
1046
+ def select_more(self, sel: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any], *sels: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1047
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, [*(self._selection or []), sel, *sels], self._where, self._order_by, self._group_by, self._computed, self._having)
1048
+
1049
+ def select_computed(self, computed: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any] | Computed[TC3, Any], *more: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any] | Computed[TC3, Any]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1050
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, self._order_by, self._group_by, [computed, *more], self._having)
1051
+
1052
+ def select_computed_more(self, computed: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any] | Computed[TC3, Any], *more: Computed[TC, Any] | Computed[TC1, Any] | Computed[TC2, Any] | Computed[TC3, Any]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1053
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, self._order_by, self._group_by, [*self._computed, computed, *more], self._having)
1054
+
1055
+ def order_by(self, *order_by: OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2] | OrderBy[TC3]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1056
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, list(order_by), self._group_by, self._computed, self._having)
1057
+
1058
+ def order_by_more(self, column: Prop[TC, Any] | PropOpt[TC, Any] | Prop[TC1, Any] | PropOpt[TC1, Any] | Prop[TC2, Any] | PropOpt[TC2, Any] | Prop[TC3, Any] | PropOpt[TC3, Any], direction: Direction = Direction.ASC) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1059
+ new_entry: OrderBy[TC] | OrderBy[TC1] | OrderBy[TC2] | OrderBy[TC3] = OrderBy(column, direction) # type: ignore[assignment]
1060
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, [*self._order_by, new_entry], self._group_by, self._computed, self._having)
1061
+
1062
+ def group_by(self, *group_by: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1063
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, self._order_by, list(group_by), self._computed, self._having)
1064
+
1065
+ def group_by_more(self, col: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any], *cols: PropSelect[TC, Any] | PropSelect[TC1, Any] | PropSelect[TC2, Any] | PropSelect[TC3, Any]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1066
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, self._order_by, [*self._group_by, col, *cols], self._computed, self._having)
1067
+
1068
+ def having(self, constraint: HavingConstraint4[TC, TC1, TC2, TC3] | None = None) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1069
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, self._where, self._order_by, self._group_by, self._computed, constraint)
1070
+
1071
+ def having_and(self, constraint: HavingConstraint4[TC, TC1, TC2, TC3]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1072
+ return self.having(constraint if self._having is None else self._having.AND(constraint))
1073
+
1074
+ def having_or(self, constraint: HavingConstraint4[TC, TC1, TC2, TC3]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1075
+ return self.having(constraint if self._having is None else self._having.OR(constraint))
1076
+
1077
+ def where(self, constraint: Constraint4[TC, TC1, TC2, TC3] | None = None) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1078
+ return ComputedQuery4(self.table1, self.table2, self.table3, self.table4, self.join_type_2, self.on_2, self.join_type_3, self.on_3, self.join_type_4, self.on_4, self._selection, constraint, self._order_by, self._group_by, self._computed, self._having)
1079
+
1080
+ def where_and(self, constraint: Constraint4[TC, TC1, TC2, TC3]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1081
+ return self.where(constraint if self._where is None else self._where.AND(constraint))
1082
+
1083
+ def where_or(self, constraint: Constraint4[TC, TC1, TC2, TC3]) -> 'ComputedQuery4[TC, TC1, TC2, TC3]':
1084
+ return self.where(constraint if self._where is None else self._where.OR(constraint))
1085
+
1086
+ def _resolved_selection(self) -> List[PropSelect[Any, Any]]:
1087
+ return list(self._selection or [])
1088
+
1089
+ def to_sql(self) -> str:
1090
+ selection = self._resolved_selection()
1091
+ assert len(selection) > 0 or len(self._computed) > 0, 'You must select at least one column or computed value'
1092
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
1093
+ assert self.on_3 is not None, 'You must specify a join condition for join 3'
1094
+ assert self.on_4 is not None, 'You must specify a join condition for join 4'
1095
+
1096
+ selections = ', '.join([*(_qualified_label(p) for p in selection), *_computed_sql_parts(self._computed, qualify=True)])
1097
+ where_clause = '' if self._where is None else ' WHERE ' + self._where.to_sql()
1098
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
1099
+ having_clause = '' if self._having is None else ' HAVING ' + self._having.to_sql()
1100
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
1101
+
1102
+ from_clause = f'{self.table1.__name__.lower()}'
1103
+ on_2_clause = self.on_2.to_sql()
1104
+ from_clause += f' {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
1105
+ on_3_clause = self.on_3.to_sql()
1106
+ from_clause += f' {self.join_type_3.value} JOIN {self.table3.__name__.lower()} ON {on_3_clause}'
1107
+ on_4_clause = self.on_4.to_sql()
1108
+ from_clause += f' {self.join_type_4.value} JOIN {self.table4.__name__.lower()} ON {on_4_clause}'
1109
+
1110
+ return f'SELECT {selections} FROM {from_clause}{where_clause}{group_by_clause}{having_clause}{order_by_clause};'
1111
+
1112
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
1113
+ selection = self._resolved_selection()
1114
+ assert len(selection) > 0 or len(self._computed) > 0, 'You must select at least one column or computed value'
1115
+ assert self.on_2 is not None, 'You must specify a join condition for join 2'
1116
+ assert self.on_3 is not None, 'You must specify a join condition for join 3'
1117
+ assert self.on_4 is not None, 'You must specify a join condition for join 4'
1118
+
1119
+ selections = ', '.join([*(_qualified_label(p) for p in selection), *_computed_sql_parts(self._computed, qualify=True)])
1120
+ group_by_clause = _group_by_sql(self._group_by, qualify=True)
1121
+ order_by_clause = _order_by_sql(self._order_by, qualify=True)
1122
+ params: List[Any] = []
1123
+
1124
+ on_2_clause, on_2_params = self.on_2.to_sql_params()
1125
+ params += on_2_params
1126
+ from_clause = f'{self.table1.__name__.lower()} {self.join_type_2.value} JOIN {self.table2.__name__.lower()} ON {on_2_clause}'
1127
+ on_3_clause, on_3_params = self.on_3.to_sql_params()
1128
+ params += on_3_params
1129
+ from_clause += f' {self.join_type_3.value} JOIN {self.table3.__name__.lower()} ON {on_3_clause}'
1130
+ on_4_clause, on_4_params = self.on_4.to_sql_params()
1131
+ params += on_4_params
1132
+ from_clause += f' {self.join_type_4.value} JOIN {self.table4.__name__.lower()} ON {on_4_clause}'
1133
+
1134
+ where_clause = ''
1135
+ if self._where is not None:
1136
+ where_sql, where_params = self._where.to_sql_params()
1137
+ where_clause = ' WHERE ' + where_sql
1138
+ params += where_params
1139
+ having_clause = ''
1140
+ if self._having is not None:
1141
+ having_sql, having_params = self._having.to_sql_params()
1142
+ having_clause = ' HAVING ' + having_sql
1143
+ params += having_params
1144
+ return f'SELECT {selections} FROM {from_clause}{where_clause}{group_by_clause}{having_clause}{order_by_clause};', params