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,782 @@
1
+
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from typing import Any, Generic, List, Sequence, Tuple, TypeVar
5
+
6
+ from pydoptic.selector import PropSelect, Prop, PropOpt
7
+ from pydoptic_sql import SqlTable
8
+
9
+ A = TypeVar('A')
10
+ TC = TypeVar('TC', bound=SqlTable, contravariant=True)
11
+ TC1 = TypeVar('TC1', bound=SqlTable, contravariant=True)
12
+ TC2 = TypeVar('TC2', bound=SqlTable, contravariant=True)
13
+ TC3 = TypeVar('TC3', bound=SqlTable, contravariant=True)
14
+
15
+ def _qualified_label(prop: PropSelect[Any, Any]) -> str:
16
+ return f'{prop.origin.__name__.lower()}.{prop.label}'
17
+
18
+ def _render_value(value: Any, qualify: bool) -> Tuple[str, List[Any]]:
19
+ """
20
+ Render a constraint operand: a `PropSelect` becomes a (possibly table-qualified) column
21
+ reference with no params, anything else becomes a `%s` placeholder bound to that value --
22
+ letting psycopg's own adapters handle quoting/escaping instead of interpolating it as text.
23
+ """
24
+ if isinstance(value, PropSelect):
25
+ return (_qualified_label(value) if qualify else value.label), []
26
+ return '%s', [value]
27
+
28
+ class Comparison(Enum):
29
+ EQ = 1
30
+ GT = 2
31
+ GTE = 3
32
+ LT = 4
33
+ LTE = 5
34
+ NE = 6
35
+ LIKE = 7
36
+
37
+ def _comparison_symbol(comp: Comparison) -> str:
38
+ match comp:
39
+ case Comparison.EQ:
40
+ return '='
41
+ case Comparison.LT:
42
+ return '<'
43
+ case Comparison.LTE:
44
+ return '<='
45
+ case Comparison.GT:
46
+ return '>'
47
+ case Comparison.GTE:
48
+ return '>='
49
+ case Comparison.LIKE:
50
+ return 'LIKE'
51
+ case Comparison.NE:
52
+ return '<>'
53
+
54
+
55
+ # --- arity 1: constraints over a single table ---
56
+
57
+ class Constraint(Generic[TC]):
58
+ def to_sql(self) -> str:
59
+ raise NotImplementedError()
60
+
61
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
62
+ raise NotImplementedError()
63
+
64
+ def incr_arity(self) -> 'Constraint2[TC, TC1]':
65
+ raise NotImplementedError()
66
+
67
+ @classmethod
68
+ def any(cls, *constraints: 'Constraint[TC]') -> 'OrConstraint[TC]':
69
+ return OrConstraint(list(constraints))
70
+
71
+ @classmethod
72
+ def all(cls, *constraints: 'Constraint[TC]') -> 'AndConstraint[TC]':
73
+ return AndConstraint(list(constraints))
74
+
75
+ @classmethod
76
+ def eq(cls, prop: Prop[TC, A] | PropOpt[TC, A], other: Prop[TC, A] | PropOpt[TC, A] | A) -> 'CompConstraint[TC, A]':
77
+ return CompConstraint(prop, other, Comparison.EQ)
78
+
79
+ @classmethod
80
+ def gt(cls, prop: Prop[TC, A] | PropOpt[TC, A], other: Prop[TC, A] | PropOpt[TC, A] | A) -> 'CompConstraint[TC, A]':
81
+ return CompConstraint(prop, other, Comparison.GT)
82
+
83
+ @classmethod
84
+ def gte(cls, prop: Prop[TC, A] | PropOpt[TC, A], other: Prop[TC, A] | PropOpt[TC, A] | A) -> 'CompConstraint[TC, A]':
85
+ return CompConstraint(prop, other, Comparison.GTE)
86
+
87
+ @classmethod
88
+ def lt(cls, prop: Prop[TC, A] | PropOpt[TC, A], other: Prop[TC, A] | PropOpt[TC, A] | A) -> 'CompConstraint[TC, A]':
89
+ return CompConstraint(prop, other, Comparison.LT)
90
+
91
+ @classmethod
92
+ def lte(cls, prop: Prop[TC, A] | PropOpt[TC, A], other: Prop[TC, A] | PropOpt[TC, A] | A) -> 'CompConstraint[TC, A]':
93
+ return CompConstraint(prop, other, Comparison.LTE)
94
+
95
+ @classmethod
96
+ def ne(cls, prop: Prop[TC, A] | PropOpt[TC, A], other: Prop[TC, A] | PropOpt[TC, A] | A) -> 'CompConstraint[TC, A]':
97
+ return CompConstraint(prop, other, Comparison.NE)
98
+
99
+ @classmethod
100
+ def like(cls, prop: Prop[TC, A] | PropOpt[TC, A], other: Prop[TC, A] | PropOpt[TC, A] | A) -> 'CompConstraint[TC, A]':
101
+ return CompConstraint(prop, other, Comparison.LIKE)
102
+
103
+ @classmethod
104
+ def between(cls, prop: Prop[TC, A] | PropOpt[TC, A], lower: Prop[TC, A] | PropOpt[TC, A] | A, upper: Prop[TC, A] | PropOpt[TC, A] | A) -> 'BetweenConstraint[TC, A]':
105
+ return BetweenConstraint(prop, lower, upper)
106
+
107
+ @classmethod
108
+ def in_(cls, prop: Prop[TC, A] | PropOpt[TC, A], values: Sequence[Prop[TC, A] | PropOpt[TC, A] | A]) -> 'InConstraint[TC, A]':
109
+ return InConstraint(prop, values)
110
+
111
+ def AND(self, other: 'Constraint[TC]') -> 'AndConstraint[TC]':
112
+ return AndConstraint([self, other])
113
+
114
+ def OR(self, other: 'Constraint[TC]') -> 'OrConstraint[TC]':
115
+ return OrConstraint([self, other])
116
+
117
+ @property
118
+ def NOT(self) -> 'NotConstraint[TC]':
119
+ return NotConstraint(self)
120
+
121
+ @dataclass(frozen=True)
122
+ class OrConstraint(Constraint[TC]):
123
+ constraints: List[Constraint[TC]]
124
+
125
+ def to_sql(self) -> str:
126
+ return '(' + ' OR '.join(c.to_sql() for c in self.constraints) + ')'
127
+
128
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
129
+ parts_params = [c.to_sql_params() for c in self.constraints]
130
+ sql = '(' + ' OR '.join(part for part, _ in parts_params) + ')'
131
+ params = [p for _, params in parts_params for p in params]
132
+ return sql, params
133
+
134
+ def incr_arity(self) -> 'OrConstraint2[TC, TC1]':
135
+ return OrConstraint2([c.incr_arity() for c in self.constraints])
136
+
137
+ @dataclass(frozen=True)
138
+ class AndConstraint(Constraint[TC]):
139
+ constraints: List[Constraint[TC]]
140
+
141
+ def to_sql(self) -> str:
142
+ return '(' + ' AND '.join(c.to_sql() for c in self.constraints) + ')'
143
+
144
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
145
+ parts_params = [c.to_sql_params() for c in self.constraints]
146
+ sql = '(' + ' AND '.join(part for part, _ in parts_params) + ')'
147
+ params = [p for _, params in parts_params for p in params]
148
+ return sql, params
149
+
150
+ def incr_arity(self) -> 'AndConstraint2[TC, TC1]':
151
+ return AndConstraint2([c.incr_arity() for c in self.constraints])
152
+
153
+ @dataclass(frozen=True)
154
+ class NotConstraint(Constraint[TC]):
155
+ constraint: Constraint[TC]
156
+
157
+ def to_sql(self) -> str:
158
+ return 'NOT (' + self.constraint.to_sql() + ')'
159
+
160
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
161
+ sql, params = self.constraint.to_sql_params()
162
+ return 'NOT (' + sql + ')', params
163
+
164
+ def incr_arity(self) -> 'NotConstraint2[TC, TC1]':
165
+ return NotConstraint2(self.constraint.incr_arity())
166
+
167
+ @dataclass(frozen=True)
168
+ class CompConstraint(Generic[TC, A], Constraint[TC]):
169
+ value_1: Prop[TC, A] | PropOpt[TC, A]
170
+ value_2: Prop[TC, A] | PropOpt[TC, A] | A
171
+ comp: Comparison
172
+
173
+ def to_sql(self) -> str:
174
+ if isinstance(self.value_2, PropSelect):
175
+ value_2 = self.value_2.label
176
+ else:
177
+ value_2 = str(self.value_2)
178
+ if self.value_1.target is str:
179
+ value_2 = "'" + value_2 + "'"
180
+ return self.value_1.label + ' ' + _comparison_symbol(self.comp) + ' ' + value_2
181
+
182
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
183
+ value_2, params = _render_value(self.value_2, qualify=False)
184
+ return self.value_1.label + ' ' + _comparison_symbol(self.comp) + ' ' + value_2, params
185
+
186
+ def incr_arity(self) -> 'CompConstraint2[TC, TC1, A]':
187
+ return CompConstraint2(self.value_1, self.value_2, self.comp)
188
+
189
+ @dataclass(frozen=True)
190
+ class BetweenConstraint(Generic[TC, A], Constraint[TC]):
191
+ value: Prop[TC, A] | PropOpt[TC, A]
192
+ lower: Prop[TC, A] | PropOpt[TC, A] | A
193
+ upper: Prop[TC, A] | PropOpt[TC, A] | A
194
+
195
+ def to_sql(self) -> str:
196
+ lower = self.lower.label if isinstance(self.lower, PropSelect) else str(self.lower)
197
+ upper = self.upper.label if isinstance(self.upper, PropSelect) else str(self.upper)
198
+ return f'{self.value.label} BETWEEN {lower} AND {upper}'
199
+
200
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
201
+ lower, lower_params = _render_value(self.lower, qualify=False)
202
+ upper, upper_params = _render_value(self.upper, qualify=False)
203
+ return f'{self.value.label} BETWEEN {lower} AND {upper}', lower_params + upper_params
204
+
205
+ def incr_arity(self) -> 'BetweenConstraint2[TC, TC1, A]':
206
+ return BetweenConstraint2(self.value, self.lower, self.upper)
207
+
208
+ @dataclass(frozen=True)
209
+ class InConstraint(Generic[TC, A], Constraint[TC]):
210
+ value: Prop[TC, A] | PropOpt[TC, A]
211
+ values: Sequence[Prop[TC, A] | PropOpt[TC, A] | A]
212
+
213
+ def to_sql(self) -> str:
214
+ def render(c: Prop[TC, A] | PropOpt[TC, A] | A) -> str:
215
+ if isinstance(c, PropSelect):
216
+ return c.label
217
+ value = str(c)
218
+ if self.value.target is str:
219
+ value = "'" + value + "'"
220
+ return value
221
+
222
+ values = [render(c) for c in self.values]
223
+ return f'{self.value.label} IN ({", ".join(values)})'
224
+
225
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
226
+ rendered = [_render_value(c, qualify=False) for c in self.values]
227
+ sql = f'{self.value.label} IN ({", ".join(part for part, _ in rendered)})'
228
+ params = [p for _, params in rendered for p in params]
229
+ return sql, params
230
+
231
+ def incr_arity(self) -> 'InConstraint2[TC, TC1, A]':
232
+ return InConstraint2(self.value, self.values)
233
+
234
+
235
+ # --- arity 2: constraints over a pair of joined tables ---
236
+ # Column references always render table-qualified ("table.column"), since a constraint here may
237
+ # reference either table and there's no other way to disambiguate.
238
+
239
+ class Constraint2(Generic[TC, TC1]):
240
+ def to_sql(self) -> str:
241
+ raise NotImplementedError()
242
+
243
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
244
+ raise NotImplementedError()
245
+
246
+ def incr_arity(self) -> 'Constraint3[TC, TC1, TC2]':
247
+ raise NotImplementedError()
248
+
249
+ @classmethod
250
+ def any(cls, *constraints: 'Constraint2[TC, TC1]') -> 'OrConstraint2[TC, TC1]':
251
+ return OrConstraint2(list(constraints))
252
+
253
+ @classmethod
254
+ def all(cls, *constraints: 'Constraint2[TC, TC1]') -> 'AndConstraint2[TC, TC1]':
255
+ return AndConstraint2(list(constraints))
256
+
257
+ @classmethod
258
+ def eq(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A) -> 'CompConstraint2[TC, TC1, A]':
259
+ return CompConstraint2(prop, other, Comparison.EQ)
260
+
261
+ @classmethod
262
+ def gt(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A) -> 'CompConstraint2[TC, TC1, A]':
263
+ return CompConstraint2(prop, other, Comparison.GT)
264
+
265
+ @classmethod
266
+ def gte(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A) -> 'CompConstraint2[TC, TC1, A]':
267
+ return CompConstraint2(prop, other, Comparison.GTE)
268
+
269
+ @classmethod
270
+ def lt(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A) -> 'CompConstraint2[TC, TC1, A]':
271
+ return CompConstraint2(prop, other, Comparison.LT)
272
+
273
+ @classmethod
274
+ def lte(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A) -> 'CompConstraint2[TC, TC1, A]':
275
+ return CompConstraint2(prop, other, Comparison.LTE)
276
+
277
+ @classmethod
278
+ def ne(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A) -> 'CompConstraint2[TC, TC1, A]':
279
+ return CompConstraint2(prop, other, Comparison.NE)
280
+
281
+ @classmethod
282
+ def like(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A) -> 'CompConstraint2[TC, TC1, A]':
283
+ return CompConstraint2(prop, other, Comparison.LIKE)
284
+
285
+ @classmethod
286
+ def between(
287
+ cls,
288
+ prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A],
289
+ lower: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A,
290
+ upper: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A,
291
+ ) -> 'BetweenConstraint2[TC, TC1, A]':
292
+ return BetweenConstraint2(prop, lower, upper)
293
+
294
+ @classmethod
295
+ def in_(
296
+ cls,
297
+ prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A],
298
+ values: Sequence[Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A],
299
+ ) -> 'InConstraint2[TC, TC1, A]':
300
+ return InConstraint2(prop, values)
301
+
302
+ def AND(self, other: 'Constraint2[TC, TC1]') -> 'AndConstraint2[TC, TC1]':
303
+ return AndConstraint2([self, other])
304
+
305
+ def OR(self, other: 'Constraint2[TC, TC1]') -> 'OrConstraint2[TC, TC1]':
306
+ return OrConstraint2([self, other])
307
+
308
+ @property
309
+ def NOT(self) -> 'NotConstraint2[TC, TC1]':
310
+ return NotConstraint2(self)
311
+
312
+ @dataclass(frozen=True)
313
+ class OrConstraint2(Constraint2[TC, TC1]):
314
+ constraints: List[Constraint2[TC, TC1]]
315
+
316
+ def to_sql(self) -> str:
317
+ return '(' + ' OR '.join(c.to_sql() for c in self.constraints) + ')'
318
+
319
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
320
+ parts_params = [c.to_sql_params() for c in self.constraints]
321
+ sql = '(' + ' OR '.join(part for part, _ in parts_params) + ')'
322
+ params = [p for _, params in parts_params for p in params]
323
+ return sql, params
324
+
325
+ def incr_arity(self) -> 'OrConstraint3[TC, TC1, TC2]':
326
+ return OrConstraint3([c.incr_arity() for c in self.constraints])
327
+
328
+ @dataclass(frozen=True)
329
+ class AndConstraint2(Constraint2[TC, TC1]):
330
+ constraints: List[Constraint2[TC, TC1]]
331
+
332
+ def to_sql(self) -> str:
333
+ return '(' + ' AND '.join(c.to_sql() for c in self.constraints) + ')'
334
+
335
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
336
+ parts_params = [c.to_sql_params() for c in self.constraints]
337
+ sql = '(' + ' AND '.join(part for part, _ in parts_params) + ')'
338
+ params = [p for _, params in parts_params for p in params]
339
+ return sql, params
340
+
341
+ def incr_arity(self) -> 'AndConstraint3[TC, TC1, TC2]':
342
+ return AndConstraint3([c.incr_arity() for c in self.constraints])
343
+
344
+ @dataclass(frozen=True)
345
+ class NotConstraint2(Constraint2[TC, TC1]):
346
+ constraint: Constraint2[TC, TC1]
347
+
348
+ def to_sql(self) -> str:
349
+ return 'NOT (' + self.constraint.to_sql() + ')'
350
+
351
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
352
+ sql, params = self.constraint.to_sql_params()
353
+ return 'NOT (' + sql + ')', params
354
+
355
+ def incr_arity(self) -> 'NotConstraint3[TC, TC1, TC2]':
356
+ return NotConstraint3(self.constraint.incr_arity())
357
+
358
+ @dataclass(frozen=True)
359
+ class CompConstraint2(Generic[TC, TC1, A], Constraint2[TC, TC1]):
360
+ value_1: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A]
361
+ value_2: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A
362
+ comp: Comparison
363
+
364
+ def to_sql(self) -> str:
365
+ if isinstance(self.value_2, PropSelect):
366
+ value_2 = _qualified_label(self.value_2)
367
+ else:
368
+ value_2 = str(self.value_2)
369
+ if self.value_1.target is str:
370
+ value_2 = "'" + value_2 + "'"
371
+ return _qualified_label(self.value_1) + ' ' + _comparison_symbol(self.comp) + ' ' + value_2
372
+
373
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
374
+ value_2, params = _render_value(self.value_2, qualify=True)
375
+ return _qualified_label(self.value_1) + ' ' + _comparison_symbol(self.comp) + ' ' + value_2, params
376
+
377
+ def incr_arity(self) -> 'CompConstraint3[TC, TC1, TC2, A]':
378
+ return CompConstraint3(self.value_1, self.value_2, self.comp)
379
+
380
+ @dataclass(frozen=True)
381
+ class BetweenConstraint2(Generic[TC, TC1, A], Constraint2[TC, TC1]):
382
+ value: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A]
383
+ lower: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A
384
+ upper: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A
385
+
386
+ def to_sql(self) -> str:
387
+ lower = _qualified_label(self.lower) if isinstance(self.lower, PropSelect) else str(self.lower)
388
+ upper = _qualified_label(self.upper) if isinstance(self.upper, PropSelect) else str(self.upper)
389
+ return f'{_qualified_label(self.value)} BETWEEN {lower} AND {upper}'
390
+
391
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
392
+ lower, lower_params = _render_value(self.lower, qualify=True)
393
+ upper, upper_params = _render_value(self.upper, qualify=True)
394
+ return f'{_qualified_label(self.value)} BETWEEN {lower} AND {upper}', lower_params + upper_params
395
+
396
+ def incr_arity(self) -> 'BetweenConstraint3[TC, TC1, TC2, A]':
397
+ return BetweenConstraint3(self.value, self.lower, self.upper)
398
+
399
+ @dataclass(frozen=True)
400
+ class InConstraint2(Generic[TC, TC1, A], Constraint2[TC, TC1]):
401
+ value: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A]
402
+ values: Sequence[Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A]
403
+
404
+ def to_sql(self) -> str:
405
+ def render(c: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | A) -> str:
406
+ if isinstance(c, PropSelect):
407
+ return _qualified_label(c)
408
+ value = str(c)
409
+ if self.value.target is str:
410
+ value = "'" + value + "'"
411
+ return value
412
+
413
+ values = [render(c) for c in self.values]
414
+ return f'{_qualified_label(self.value)} IN ({", ".join(values)})'
415
+
416
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
417
+ rendered = [_render_value(c, qualify=True) for c in self.values]
418
+ sql = f'{_qualified_label(self.value)} IN ({", ".join(part for part, _ in rendered)})'
419
+ params = [p for _, params in rendered for p in params]
420
+ return sql, params
421
+
422
+ def incr_arity(self) -> 'InConstraint3[TC, TC1, TC2, A]':
423
+ return InConstraint3(self.value, self.values)
424
+
425
+ # --- arity 3: constraints over 3 joined tables ---
426
+ # Column references always render table-qualified ("table.column"), since a constraint here may
427
+ # reference any of the joined tables and there's no other way to disambiguate.
428
+
429
+ class Constraint3(Generic[TC, TC1, TC2]):
430
+ def to_sql(self) -> str:
431
+ raise NotImplementedError()
432
+
433
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
434
+ raise NotImplementedError()
435
+
436
+ def incr_arity(self) -> 'Constraint4[TC, TC1, TC2, TC3]':
437
+ raise NotImplementedError()
438
+
439
+ @classmethod
440
+ def any(cls, *constraints: 'Constraint3[TC, TC1, TC2]') -> 'OrConstraint3[TC, TC1, TC2]':
441
+ return OrConstraint3(list(constraints))
442
+
443
+ @classmethod
444
+ def all(cls, *constraints: 'Constraint3[TC, TC1, TC2]') -> 'AndConstraint3[TC, TC1, TC2]':
445
+ return AndConstraint3(list(constraints))
446
+
447
+ @classmethod
448
+ def eq(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A) -> 'CompConstraint3[TC, TC1, TC2, A]':
449
+ return CompConstraint3(prop, other, Comparison.EQ)
450
+
451
+ @classmethod
452
+ def gt(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A) -> 'CompConstraint3[TC, TC1, TC2, A]':
453
+ return CompConstraint3(prop, other, Comparison.GT)
454
+
455
+ @classmethod
456
+ def gte(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A) -> 'CompConstraint3[TC, TC1, TC2, A]':
457
+ return CompConstraint3(prop, other, Comparison.GTE)
458
+
459
+ @classmethod
460
+ def lt(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A) -> 'CompConstraint3[TC, TC1, TC2, A]':
461
+ return CompConstraint3(prop, other, Comparison.LT)
462
+
463
+ @classmethod
464
+ def lte(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A) -> 'CompConstraint3[TC, TC1, TC2, A]':
465
+ return CompConstraint3(prop, other, Comparison.LTE)
466
+
467
+ @classmethod
468
+ def ne(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A) -> 'CompConstraint3[TC, TC1, TC2, A]':
469
+ return CompConstraint3(prop, other, Comparison.NE)
470
+
471
+ @classmethod
472
+ def like(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A) -> 'CompConstraint3[TC, TC1, TC2, A]':
473
+ return CompConstraint3(prop, other, Comparison.LIKE)
474
+
475
+ @classmethod
476
+ def between(
477
+ cls,
478
+ prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A],
479
+ lower: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A,
480
+ upper: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A,
481
+ ) -> 'BetweenConstraint3[TC, TC1, TC2, A]':
482
+ return BetweenConstraint3(prop, lower, upper)
483
+
484
+ @classmethod
485
+ def in_(
486
+ cls,
487
+ prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A],
488
+ values: Sequence[Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A],
489
+ ) -> 'InConstraint3[TC, TC1, TC2, A]':
490
+ return InConstraint3(prop, values)
491
+
492
+ def AND(self, other: 'Constraint3[TC, TC1, TC2]') -> 'AndConstraint3[TC, TC1, TC2]':
493
+ return AndConstraint3([self, other])
494
+
495
+ def OR(self, other: 'Constraint3[TC, TC1, TC2]') -> 'OrConstraint3[TC, TC1, TC2]':
496
+ return OrConstraint3([self, other])
497
+
498
+ @property
499
+ def NOT(self) -> 'NotConstraint3[TC, TC1, TC2]':
500
+ return NotConstraint3(self)
501
+
502
+ @dataclass(frozen=True)
503
+ class OrConstraint3(Constraint3[TC, TC1, TC2]):
504
+ constraints: List[Constraint3[TC, TC1, TC2]]
505
+
506
+ def to_sql(self) -> str:
507
+ return '(' + ' OR '.join(c.to_sql() for c in self.constraints) + ')'
508
+
509
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
510
+ parts_params = [c.to_sql_params() for c in self.constraints]
511
+ sql = '(' + ' OR '.join(part for part, _ in parts_params) + ')'
512
+ params = [p for _, params in parts_params for p in params]
513
+ return sql, params
514
+
515
+ def incr_arity(self) -> 'OrConstraint4[TC, TC1, TC2, TC3]':
516
+ return OrConstraint4([c.incr_arity() for c in self.constraints])
517
+
518
+ @dataclass(frozen=True)
519
+ class AndConstraint3(Constraint3[TC, TC1, TC2]):
520
+ constraints: List[Constraint3[TC, TC1, TC2]]
521
+
522
+ def to_sql(self) -> str:
523
+ return '(' + ' AND '.join(c.to_sql() for c in self.constraints) + ')'
524
+
525
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
526
+ parts_params = [c.to_sql_params() for c in self.constraints]
527
+ sql = '(' + ' AND '.join(part for part, _ in parts_params) + ')'
528
+ params = [p for _, params in parts_params for p in params]
529
+ return sql, params
530
+
531
+ def incr_arity(self) -> 'AndConstraint4[TC, TC1, TC2, TC3]':
532
+ return AndConstraint4([c.incr_arity() for c in self.constraints])
533
+
534
+ @dataclass(frozen=True)
535
+ class NotConstraint3(Constraint3[TC, TC1, TC2]):
536
+ constraint: Constraint3[TC, TC1, TC2]
537
+
538
+ def to_sql(self) -> str:
539
+ return 'NOT (' + self.constraint.to_sql() + ')'
540
+
541
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
542
+ sql, params = self.constraint.to_sql_params()
543
+ return 'NOT (' + sql + ')', params
544
+
545
+ def incr_arity(self) -> 'NotConstraint4[TC, TC1, TC2, TC3]':
546
+ return NotConstraint4(self.constraint.incr_arity())
547
+
548
+ @dataclass(frozen=True)
549
+ class CompConstraint3(Generic[TC, TC1, TC2, A], Constraint3[TC, TC1, TC2]):
550
+ value_1: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A]
551
+ value_2: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A
552
+ comp: Comparison
553
+
554
+ def to_sql(self) -> str:
555
+ if isinstance(self.value_2, PropSelect):
556
+ value_2 = _qualified_label(self.value_2)
557
+ else:
558
+ value_2 = str(self.value_2)
559
+ if self.value_1.target is str:
560
+ value_2 = "'" + value_2 + "'"
561
+ return _qualified_label(self.value_1) + ' ' + _comparison_symbol(self.comp) + ' ' + value_2
562
+
563
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
564
+ value_2, params = _render_value(self.value_2, qualify=True)
565
+ return _qualified_label(self.value_1) + ' ' + _comparison_symbol(self.comp) + ' ' + value_2, params
566
+
567
+ def incr_arity(self) -> 'CompConstraint4[TC, TC1, TC2, TC3, A]':
568
+ return CompConstraint4(self.value_1, self.value_2, self.comp)
569
+
570
+ @dataclass(frozen=True)
571
+ class BetweenConstraint3(Generic[TC, TC1, TC2, A], Constraint3[TC, TC1, TC2]):
572
+ value: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A]
573
+ lower: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A
574
+ upper: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A
575
+
576
+ def to_sql(self) -> str:
577
+ lower = _qualified_label(self.lower) if isinstance(self.lower, PropSelect) else str(self.lower)
578
+ upper = _qualified_label(self.upper) if isinstance(self.upper, PropSelect) else str(self.upper)
579
+ return f'{_qualified_label(self.value)} BETWEEN {lower} AND {upper}'
580
+
581
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
582
+ lower, lower_params = _render_value(self.lower, qualify=True)
583
+ upper, upper_params = _render_value(self.upper, qualify=True)
584
+ return f'{_qualified_label(self.value)} BETWEEN {lower} AND {upper}', lower_params + upper_params
585
+
586
+ def incr_arity(self) -> 'BetweenConstraint4[TC, TC1, TC2, TC3, A]':
587
+ return BetweenConstraint4(self.value, self.lower, self.upper)
588
+
589
+ @dataclass(frozen=True)
590
+ class InConstraint3(Generic[TC, TC1, TC2, A], Constraint3[TC, TC1, TC2]):
591
+ value: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A]
592
+ values: Sequence[Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A]
593
+
594
+ def to_sql(self) -> str:
595
+ def render(c: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | A) -> str:
596
+ if isinstance(c, PropSelect):
597
+ return _qualified_label(c)
598
+ value = str(c)
599
+ if self.value.target is str:
600
+ value = "'" + value + "'"
601
+ return value
602
+
603
+ values = [render(c) for c in self.values]
604
+ return f'{_qualified_label(self.value)} IN ({", ".join(values)})'
605
+
606
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
607
+ rendered = [_render_value(c, qualify=True) for c in self.values]
608
+ sql = f'{_qualified_label(self.value)} IN ({", ".join(part for part, _ in rendered)})'
609
+ params = [p for _, params in rendered for p in params]
610
+ return sql, params
611
+
612
+ def incr_arity(self) -> 'InConstraint4[TC, TC1, TC2, TC3, A]':
613
+ return InConstraint4(self.value, self.values)
614
+
615
+ # --- arity 4: constraints over 4 joined tables ---
616
+ # Column references always render table-qualified ("table.column"), since a constraint here may
617
+ # reference any of the joined tables and there's no other way to disambiguate.
618
+
619
+ class Constraint4(Generic[TC, TC1, TC2, TC3]):
620
+ def to_sql(self) -> str:
621
+ raise NotImplementedError()
622
+
623
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
624
+ raise NotImplementedError()
625
+
626
+ @classmethod
627
+ def any(cls, *constraints: 'Constraint4[TC, TC1, TC2, TC3]') -> 'OrConstraint4[TC, TC1, TC2, TC3]':
628
+ return OrConstraint4(list(constraints))
629
+
630
+ @classmethod
631
+ def all(cls, *constraints: 'Constraint4[TC, TC1, TC2, TC3]') -> 'AndConstraint4[TC, TC1, TC2, TC3]':
632
+ return AndConstraint4(list(constraints))
633
+
634
+ @classmethod
635
+ def eq(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A) -> 'CompConstraint4[TC, TC1, TC2, TC3, A]':
636
+ return CompConstraint4(prop, other, Comparison.EQ)
637
+
638
+ @classmethod
639
+ def gt(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A) -> 'CompConstraint4[TC, TC1, TC2, TC3, A]':
640
+ return CompConstraint4(prop, other, Comparison.GT)
641
+
642
+ @classmethod
643
+ def gte(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A) -> 'CompConstraint4[TC, TC1, TC2, TC3, A]':
644
+ return CompConstraint4(prop, other, Comparison.GTE)
645
+
646
+ @classmethod
647
+ def lt(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A) -> 'CompConstraint4[TC, TC1, TC2, TC3, A]':
648
+ return CompConstraint4(prop, other, Comparison.LT)
649
+
650
+ @classmethod
651
+ def lte(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A) -> 'CompConstraint4[TC, TC1, TC2, TC3, A]':
652
+ return CompConstraint4(prop, other, Comparison.LTE)
653
+
654
+ @classmethod
655
+ def ne(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A) -> 'CompConstraint4[TC, TC1, TC2, TC3, A]':
656
+ return CompConstraint4(prop, other, Comparison.NE)
657
+
658
+ @classmethod
659
+ def like(cls, prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A], other: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A) -> 'CompConstraint4[TC, TC1, TC2, TC3, A]':
660
+ return CompConstraint4(prop, other, Comparison.LIKE)
661
+
662
+ @classmethod
663
+ def between(
664
+ cls,
665
+ prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A],
666
+ lower: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A,
667
+ upper: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A,
668
+ ) -> 'BetweenConstraint4[TC, TC1, TC2, TC3, A]':
669
+ return BetweenConstraint4(prop, lower, upper)
670
+
671
+ @classmethod
672
+ def in_(
673
+ cls,
674
+ prop: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A],
675
+ values: Sequence[Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A],
676
+ ) -> 'InConstraint4[TC, TC1, TC2, TC3, A]':
677
+ return InConstraint4(prop, values)
678
+
679
+ def AND(self, other: 'Constraint4[TC, TC1, TC2, TC3]') -> 'AndConstraint4[TC, TC1, TC2, TC3]':
680
+ return AndConstraint4([self, other])
681
+
682
+ def OR(self, other: 'Constraint4[TC, TC1, TC2, TC3]') -> 'OrConstraint4[TC, TC1, TC2, TC3]':
683
+ return OrConstraint4([self, other])
684
+
685
+ @property
686
+ def NOT(self) -> 'NotConstraint4[TC, TC1, TC2, TC3]':
687
+ return NotConstraint4(self)
688
+
689
+ @dataclass(frozen=True)
690
+ class OrConstraint4(Constraint4[TC, TC1, TC2, TC3]):
691
+ constraints: List[Constraint4[TC, TC1, TC2, TC3]]
692
+
693
+ def to_sql(self) -> str:
694
+ return '(' + ' OR '.join(c.to_sql() for c in self.constraints) + ')'
695
+
696
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
697
+ parts_params = [c.to_sql_params() for c in self.constraints]
698
+ sql = '(' + ' OR '.join(part for part, _ in parts_params) + ')'
699
+ params = [p for _, params in parts_params for p in params]
700
+ return sql, params
701
+
702
+ @dataclass(frozen=True)
703
+ class AndConstraint4(Constraint4[TC, TC1, TC2, TC3]):
704
+ constraints: List[Constraint4[TC, TC1, TC2, TC3]]
705
+
706
+ def to_sql(self) -> str:
707
+ return '(' + ' AND '.join(c.to_sql() for c in self.constraints) + ')'
708
+
709
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
710
+ parts_params = [c.to_sql_params() for c in self.constraints]
711
+ sql = '(' + ' AND '.join(part for part, _ in parts_params) + ')'
712
+ params = [p for _, params in parts_params for p in params]
713
+ return sql, params
714
+
715
+ @dataclass(frozen=True)
716
+ class NotConstraint4(Constraint4[TC, TC1, TC2, TC3]):
717
+ constraint: Constraint4[TC, TC1, TC2, TC3]
718
+
719
+ def to_sql(self) -> str:
720
+ return 'NOT (' + self.constraint.to_sql() + ')'
721
+
722
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
723
+ sql, params = self.constraint.to_sql_params()
724
+ return 'NOT (' + sql + ')', params
725
+
726
+ @dataclass(frozen=True)
727
+ class CompConstraint4(Generic[TC, TC1, TC2, TC3, A], Constraint4[TC, TC1, TC2, TC3]):
728
+ value_1: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A]
729
+ value_2: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A
730
+ comp: Comparison
731
+
732
+ def to_sql(self) -> str:
733
+ if isinstance(self.value_2, PropSelect):
734
+ value_2 = _qualified_label(self.value_2)
735
+ else:
736
+ value_2 = str(self.value_2)
737
+ if self.value_1.target is str:
738
+ value_2 = "'" + value_2 + "'"
739
+ return _qualified_label(self.value_1) + ' ' + _comparison_symbol(self.comp) + ' ' + value_2
740
+
741
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
742
+ value_2, params = _render_value(self.value_2, qualify=True)
743
+ return _qualified_label(self.value_1) + ' ' + _comparison_symbol(self.comp) + ' ' + value_2, params
744
+
745
+ @dataclass(frozen=True)
746
+ class BetweenConstraint4(Generic[TC, TC1, TC2, TC3, A], Constraint4[TC, TC1, TC2, TC3]):
747
+ value: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A]
748
+ lower: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A
749
+ upper: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A
750
+
751
+ def to_sql(self) -> str:
752
+ lower = _qualified_label(self.lower) if isinstance(self.lower, PropSelect) else str(self.lower)
753
+ upper = _qualified_label(self.upper) if isinstance(self.upper, PropSelect) else str(self.upper)
754
+ return f'{_qualified_label(self.value)} BETWEEN {lower} AND {upper}'
755
+
756
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
757
+ lower, lower_params = _render_value(self.lower, qualify=True)
758
+ upper, upper_params = _render_value(self.upper, qualify=True)
759
+ return f'{_qualified_label(self.value)} BETWEEN {lower} AND {upper}', lower_params + upper_params
760
+
761
+ @dataclass(frozen=True)
762
+ class InConstraint4(Generic[TC, TC1, TC2, TC3, A], Constraint4[TC, TC1, TC2, TC3]):
763
+ value: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A]
764
+ values: Sequence[Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A]
765
+
766
+ def to_sql(self) -> str:
767
+ def render(c: Prop[TC, A] | PropOpt[TC, A] | Prop[TC1, A] | PropOpt[TC1, A] | Prop[TC2, A] | PropOpt[TC2, A] | Prop[TC3, A] | PropOpt[TC3, A] | A) -> str:
768
+ if isinstance(c, PropSelect):
769
+ return _qualified_label(c)
770
+ value = str(c)
771
+ if self.value.target is str:
772
+ value = "'" + value + "'"
773
+ return value
774
+
775
+ values = [render(c) for c in self.values]
776
+ return f'{_qualified_label(self.value)} IN ({", ".join(values)})'
777
+
778
+ def to_sql_params(self) -> Tuple[str, List[Any]]:
779
+ rendered = [_render_value(c, qualify=True) for c in self.values]
780
+ sql = f'{_qualified_label(self.value)} IN ({", ".join(part for part, _ in rendered)})'
781
+ params = [p for _, params in rendered for p in params]
782
+ return sql, params