plain.models 0.49.2__py3-none-any.whl → 0.51.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain/models/CHANGELOG.md +27 -0
- plain/models/README.md +26 -42
- plain/models/__init__.py +2 -0
- plain/models/aggregates.py +42 -19
- plain/models/backends/base/base.py +125 -105
- plain/models/backends/base/client.py +11 -3
- plain/models/backends/base/creation.py +24 -14
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +37 -20
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +338 -218
- plain/models/backends/base/validation.py +13 -4
- plain/models/backends/ddl_references.py +85 -43
- plain/models/backends/mysql/base.py +29 -26
- plain/models/backends/mysql/client.py +7 -2
- plain/models/backends/mysql/compiler.py +13 -4
- plain/models/backends/mysql/creation.py +5 -2
- plain/models/backends/mysql/features.py +24 -22
- plain/models/backends/mysql/introspection.py +22 -13
- plain/models/backends/mysql/operations.py +107 -40
- plain/models/backends/mysql/schema.py +52 -28
- plain/models/backends/mysql/validation.py +13 -6
- plain/models/backends/postgresql/base.py +41 -34
- plain/models/backends/postgresql/client.py +7 -2
- plain/models/backends/postgresql/creation.py +10 -5
- plain/models/backends/postgresql/introspection.py +15 -8
- plain/models/backends/postgresql/operations.py +110 -43
- plain/models/backends/postgresql/schema.py +88 -49
- plain/models/backends/sqlite3/_functions.py +151 -115
- plain/models/backends/sqlite3/base.py +37 -23
- plain/models/backends/sqlite3/client.py +7 -1
- plain/models/backends/sqlite3/creation.py +9 -5
- plain/models/backends/sqlite3/features.py +5 -3
- plain/models/backends/sqlite3/introspection.py +32 -16
- plain/models/backends/sqlite3/operations.py +126 -43
- plain/models/backends/sqlite3/schema.py +127 -92
- plain/models/backends/utils.py +52 -29
- plain/models/backups/cli.py +8 -6
- plain/models/backups/clients.py +16 -7
- plain/models/backups/core.py +24 -13
- plain/models/base.py +221 -229
- plain/models/cli.py +98 -67
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +79 -56
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +80 -56
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +441 -258
- plain/models/fields/__init__.py +272 -217
- plain/models/fields/json.py +123 -57
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +324 -290
- plain/models/fields/related_descriptors.py +33 -24
- plain/models/fields/related_lookups.py +24 -12
- plain/models/fields/related_managers.py +102 -79
- plain/models/fields/reverse_related.py +66 -63
- plain/models/forms.py +101 -75
- plain/models/functions/comparison.py +71 -18
- plain/models/functions/datetime.py +79 -29
- plain/models/functions/math.py +43 -10
- plain/models/functions/mixins.py +24 -7
- plain/models/functions/text.py +104 -25
- plain/models/functions/window.py +12 -6
- plain/models/indexes.py +57 -32
- plain/models/lookups.py +228 -153
- plain/models/meta.py +505 -0
- plain/models/migrations/autodetector.py +86 -43
- plain/models/migrations/exceptions.py +7 -3
- plain/models/migrations/executor.py +33 -7
- plain/models/migrations/graph.py +79 -50
- plain/models/migrations/loader.py +45 -22
- plain/models/migrations/migration.py +23 -18
- plain/models/migrations/operations/base.py +38 -20
- plain/models/migrations/operations/fields.py +95 -48
- plain/models/migrations/operations/models.py +246 -142
- plain/models/migrations/operations/special.py +82 -25
- plain/models/migrations/optimizer.py +7 -2
- plain/models/migrations/questioner.py +58 -31
- plain/models/migrations/recorder.py +27 -16
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +232 -156
- plain/models/migrations/utils.py +30 -14
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +189 -518
- plain/models/otel.py +16 -6
- plain/models/preflight.py +42 -17
- plain/models/query.py +400 -251
- plain/models/query_utils.py +109 -69
- plain/models/registry.py +40 -21
- plain/models/sql/compiler.py +190 -127
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +320 -225
- plain/models/sql/subqueries.py +36 -25
- plain/models/sql/where.py +54 -29
- plain/models/test/pytest.py +15 -11
- plain/models/test/utils.py +4 -2
- plain/models/transaction.py +20 -7
- plain/models/utils.py +17 -6
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
- plain_models-0.51.0.dist-info/RECORD +123 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
plain/models/sql/query.py
CHANGED
@@ -7,15 +7,19 @@ databases). The abstraction barrier only works one way: this module has to know
|
|
7
7
|
all about the internals of models in order to get the information it needs.
|
8
8
|
"""
|
9
9
|
|
10
|
+
from __future__ import annotations
|
11
|
+
|
10
12
|
import copy
|
11
13
|
import difflib
|
12
14
|
import functools
|
13
15
|
import sys
|
14
16
|
from collections import Counter, namedtuple
|
15
17
|
from collections.abc import Iterator, Mapping
|
18
|
+
from collections.abc import Iterator as TypingIterator
|
16
19
|
from functools import cached_property
|
17
20
|
from itertools import chain, count, product
|
18
21
|
from string import ascii_uppercase
|
22
|
+
from typing import TYPE_CHECKING, Any
|
19
23
|
|
20
24
|
from plain.models.aggregates import Count
|
21
25
|
from plain.models.constants import LOOKUP_SEP
|
@@ -45,6 +49,13 @@ from plain.models.sql.where import AND, OR, ExtraWhere, NothingNode, WhereNode
|
|
45
49
|
from plain.utils.regex_helper import _lazy_re_compile
|
46
50
|
from plain.utils.tree import Node
|
47
51
|
|
52
|
+
if TYPE_CHECKING:
|
53
|
+
from plain.models import Model
|
54
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
55
|
+
from plain.models.meta import Meta
|
56
|
+
from plain.models.sql.compiler import SQLCompiler
|
57
|
+
|
58
|
+
|
48
59
|
__all__ = ["Query", "RawQuery"]
|
49
60
|
|
50
61
|
# Quotation marks ('"`[]), whitespace characters, semicolons, or inline
|
@@ -56,17 +67,17 @@ FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|--|/\*|\*/")
|
|
56
67
|
EXPLAIN_OPTIONS_PATTERN = _lazy_re_compile(r"[\w\-]+")
|
57
68
|
|
58
69
|
|
59
|
-
def get_field_names_from_opts(
|
60
|
-
if
|
70
|
+
def get_field_names_from_opts(meta: Meta | None) -> set[str]:
|
71
|
+
if meta is None:
|
61
72
|
return set()
|
62
73
|
return set(
|
63
74
|
chain.from_iterable(
|
64
|
-
(f.name, f.attname) if f.concrete else (f.name,) for f in
|
75
|
+
(f.name, f.attname) if f.concrete else (f.name,) for f in meta.get_fields()
|
65
76
|
)
|
66
77
|
)
|
67
78
|
|
68
79
|
|
69
|
-
def get_children_from_q(q):
|
80
|
+
def get_children_from_q(q: Q) -> TypingIterator[tuple[str, Any]]:
|
70
81
|
for child in q.children:
|
71
82
|
if isinstance(child, Node):
|
72
83
|
yield from get_children_from_q(child)
|
@@ -76,14 +87,14 @@ def get_children_from_q(q):
|
|
76
87
|
|
77
88
|
JoinInfo = namedtuple(
|
78
89
|
"JoinInfo",
|
79
|
-
("final_field", "targets", "
|
90
|
+
("final_field", "targets", "meta", "joins", "path", "transform_function"),
|
80
91
|
)
|
81
92
|
|
82
93
|
|
83
94
|
class RawQuery:
|
84
95
|
"""A single raw SQL query."""
|
85
96
|
|
86
|
-
def __init__(self, sql, params=()):
|
97
|
+
def __init__(self, sql: str, params: tuple[Any, ...] | dict[str, Any] = ()):
|
87
98
|
self.params = params
|
88
99
|
self.sql = sql
|
89
100
|
self.cursor = None
|
@@ -94,19 +105,22 @@ class RawQuery:
|
|
94
105
|
self.extra_select = {}
|
95
106
|
self.annotation_select = {}
|
96
107
|
|
97
|
-
def chain(self):
|
108
|
+
def chain(self) -> RawQuery:
|
98
109
|
return self.clone()
|
99
110
|
|
100
|
-
def clone(self):
|
111
|
+
def clone(self) -> RawQuery:
|
101
112
|
return RawQuery(self.sql, params=self.params)
|
102
113
|
|
103
|
-
def get_columns(self):
|
114
|
+
def get_columns(self) -> list[str]:
|
104
115
|
if self.cursor is None:
|
105
116
|
self._execute_query()
|
106
117
|
converter = db_connection.introspection.identifier_converter
|
107
|
-
return [
|
118
|
+
return [
|
119
|
+
converter(column_model_meta[0])
|
120
|
+
for column_model_meta in self.cursor.description
|
121
|
+
]
|
108
122
|
|
109
|
-
def __iter__(self):
|
123
|
+
def __iter__(self) -> TypingIterator[Any]:
|
110
124
|
# Always execute a new query for a new iterator.
|
111
125
|
# This could be optimized with a cache at the expense of RAM.
|
112
126
|
self._execute_query()
|
@@ -118,21 +132,21 @@ class RawQuery:
|
|
118
132
|
result = self.cursor
|
119
133
|
return iter(result)
|
120
134
|
|
121
|
-
def __repr__(self):
|
135
|
+
def __repr__(self) -> str:
|
122
136
|
return f"<{self.__class__.__name__}: {self}>"
|
123
137
|
|
124
138
|
@property
|
125
|
-
def params_type(self):
|
139
|
+
def params_type(self) -> type[dict] | type[tuple] | None:
|
126
140
|
if self.params is None:
|
127
141
|
return None
|
128
142
|
return dict if isinstance(self.params, Mapping) else tuple
|
129
143
|
|
130
|
-
def __str__(self):
|
144
|
+
def __str__(self) -> str:
|
131
145
|
if self.params_type is None:
|
132
146
|
return self.sql
|
133
147
|
return self.sql % self.params_type(self.params)
|
134
148
|
|
135
|
-
def _execute_query(self):
|
149
|
+
def _execute_query(self) -> None:
|
136
150
|
# Adapt parameters to the database, as much as possible considering
|
137
151
|
# that the target type isn't known. See #17755.
|
138
152
|
params_type = self.params_type
|
@@ -226,7 +240,7 @@ class Query(BaseExpression):
|
|
226
240
|
|
227
241
|
explain_info = None
|
228
242
|
|
229
|
-
def __init__(self, model, alias_cols=True):
|
243
|
+
def __init__(self, model: type[Model], alias_cols: bool = True):
|
230
244
|
self.model = model
|
231
245
|
self.alias_refcount = {}
|
232
246
|
# alias_map is the most important data structure regarding joins.
|
@@ -255,7 +269,7 @@ class Query(BaseExpression):
|
|
255
269
|
self._filtered_relations = {}
|
256
270
|
|
257
271
|
@property
|
258
|
-
def output_field(self):
|
272
|
+
def output_field(self) -> Field | None: # type: ignore[return]
|
259
273
|
if len(self.select) == 1:
|
260
274
|
select = self.select[0]
|
261
275
|
return getattr(select, "target", None) or select.field
|
@@ -263,11 +277,11 @@ class Query(BaseExpression):
|
|
263
277
|
return next(iter(self.annotation_select.values())).output_field
|
264
278
|
|
265
279
|
@cached_property
|
266
|
-
def base_table(self):
|
280
|
+
def base_table(self) -> str | None:
|
267
281
|
for alias in self.alias_map:
|
268
282
|
return alias
|
269
283
|
|
270
|
-
def __str__(self):
|
284
|
+
def __str__(self) -> str:
|
271
285
|
"""
|
272
286
|
Return the query as a string of SQL with the parameter values
|
273
287
|
substituted in (use sql_with_params() to see the unsubstituted string).
|
@@ -278,53 +292,52 @@ class Query(BaseExpression):
|
|
278
292
|
sql, params = self.sql_with_params()
|
279
293
|
return sql % params
|
280
294
|
|
281
|
-
def sql_with_params(self):
|
295
|
+
def sql_with_params(self) -> tuple[str, tuple[Any, ...]]:
|
282
296
|
"""
|
283
297
|
Return the query as an SQL string and the parameters that will be
|
284
298
|
substituted into the query.
|
285
299
|
"""
|
286
300
|
return self.get_compiler().as_sql()
|
287
301
|
|
288
|
-
def __deepcopy__(self, memo):
|
302
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> Query:
|
289
303
|
"""Limit the amount of work when a Query is deepcopied."""
|
290
304
|
result = self.clone()
|
291
305
|
memo[id(self)] = result
|
292
306
|
return result
|
293
307
|
|
294
|
-
def get_compiler(self, *, elide_empty=True):
|
308
|
+
def get_compiler(self, *, elide_empty: bool = True) -> Any:
|
295
309
|
return db_connection.ops.compiler(self.compiler)(
|
296
310
|
self, db_connection, elide_empty
|
297
311
|
)
|
298
312
|
|
299
|
-
def
|
313
|
+
def get_model_meta(self) -> Meta:
|
300
314
|
"""
|
301
|
-
Return the
|
302
|
-
processing. Normally, this is self.model.
|
315
|
+
Return the Meta instance (the model._model_meta) from which to start
|
316
|
+
processing. Normally, this is self.model._model_meta, but it can be changed
|
303
317
|
by subclasses.
|
304
318
|
"""
|
305
|
-
|
306
|
-
return self.model._meta
|
319
|
+
return self.model._model_meta
|
307
320
|
|
308
|
-
def clone(self):
|
321
|
+
def clone(self) -> Query:
|
309
322
|
"""
|
310
323
|
Return a copy of the current Query. A lightweight alternative to
|
311
324
|
deepcopy().
|
312
325
|
"""
|
313
|
-
obj = Empty()
|
314
|
-
obj.__class__ = self.__class__
|
326
|
+
obj = Empty() # type: ignore[misc]
|
327
|
+
obj.__class__ = self.__class__ # type: ignore[misc]
|
315
328
|
# Copy references to everything.
|
316
|
-
obj.__dict__ = self.__dict__.copy()
|
329
|
+
obj.__dict__ = self.__dict__.copy() # type: ignore[attr-defined]
|
317
330
|
# Clone attributes that can't use shallow copy.
|
318
|
-
obj.alias_refcount = self.alias_refcount.copy()
|
319
|
-
obj.alias_map = self.alias_map.copy()
|
320
|
-
obj.external_aliases = self.external_aliases.copy()
|
321
|
-
obj.table_map = self.table_map.copy()
|
322
|
-
obj.where = self.where.clone()
|
323
|
-
obj.annotations = self.annotations.copy()
|
331
|
+
obj.alias_refcount = self.alias_refcount.copy() # type: ignore[attr-defined]
|
332
|
+
obj.alias_map = self.alias_map.copy() # type: ignore[attr-defined]
|
333
|
+
obj.external_aliases = self.external_aliases.copy() # type: ignore[attr-defined]
|
334
|
+
obj.table_map = self.table_map.copy() # type: ignore[attr-defined]
|
335
|
+
obj.where = self.where.clone() # type: ignore[attr-defined]
|
336
|
+
obj.annotations = self.annotations.copy() # type: ignore[attr-defined]
|
324
337
|
if self.annotation_select_mask is not None:
|
325
|
-
obj.annotation_select_mask = self.annotation_select_mask.copy()
|
338
|
+
obj.annotation_select_mask = self.annotation_select_mask.copy() # type: ignore[attr-defined]
|
326
339
|
if self.combined_queries:
|
327
|
-
obj.combined_queries = tuple(
|
340
|
+
obj.combined_queries = tuple( # type: ignore[attr-defined]
|
328
341
|
[query.clone() for query in self.combined_queries]
|
329
342
|
)
|
330
343
|
# _annotation_select_cache cannot be copied, as doing so breaks the
|
@@ -332,50 +345,50 @@ class Query(BaseExpression):
|
|
332
345
|
# _annotation_select_cache point to the same underlying objects.
|
333
346
|
# It will get re-populated in the cloned queryset the next time it's
|
334
347
|
# used.
|
335
|
-
obj._annotation_select_cache = None
|
336
|
-
obj.extra = self.extra.copy()
|
348
|
+
obj._annotation_select_cache = None # type: ignore[attr-defined]
|
349
|
+
obj.extra = self.extra.copy() # type: ignore[attr-defined]
|
337
350
|
if self.extra_select_mask is not None:
|
338
|
-
obj.extra_select_mask = self.extra_select_mask.copy()
|
351
|
+
obj.extra_select_mask = self.extra_select_mask.copy() # type: ignore[attr-defined]
|
339
352
|
if self._extra_select_cache is not None:
|
340
|
-
obj._extra_select_cache = self._extra_select_cache.copy()
|
353
|
+
obj._extra_select_cache = self._extra_select_cache.copy() # type: ignore[attr-defined]
|
341
354
|
if self.select_related is not False:
|
342
355
|
# Use deepcopy because select_related stores fields in nested
|
343
356
|
# dicts.
|
344
|
-
obj.select_related = copy.deepcopy(obj.select_related)
|
357
|
+
obj.select_related = copy.deepcopy(obj.select_related) # type: ignore[attr-defined]
|
345
358
|
if "subq_aliases" in self.__dict__:
|
346
|
-
obj.subq_aliases = self.subq_aliases.copy()
|
347
|
-
obj.used_aliases = self.used_aliases.copy()
|
348
|
-
obj._filtered_relations = self._filtered_relations.copy()
|
359
|
+
obj.subq_aliases = self.subq_aliases.copy() # type: ignore[attr-defined]
|
360
|
+
obj.used_aliases = self.used_aliases.copy() # type: ignore[attr-defined]
|
361
|
+
obj._filtered_relations = self._filtered_relations.copy() # type: ignore[attr-defined]
|
349
362
|
# Clear the cached_property, if it exists.
|
350
363
|
obj.__dict__.pop("base_table", None)
|
351
|
-
return obj
|
364
|
+
return obj # type: ignore[return-value]
|
352
365
|
|
353
|
-
def chain(self, klass=None):
|
366
|
+
def chain(self, klass: type[Query] | None = None) -> Query:
|
354
367
|
"""
|
355
368
|
Return a copy of the current Query that's ready for another operation.
|
356
369
|
The klass argument changes the type of the Query, e.g. UpdateQuery.
|
357
370
|
"""
|
358
371
|
obj = self.clone()
|
359
372
|
if klass and obj.__class__ != klass:
|
360
|
-
obj.__class__ = klass
|
373
|
+
obj.__class__ = klass # type: ignore[misc]
|
361
374
|
if not obj.filter_is_sticky:
|
362
|
-
obj.used_aliases = set()
|
375
|
+
obj.used_aliases = set() # type: ignore[attr-defined]
|
363
376
|
obj.filter_is_sticky = False
|
364
377
|
if hasattr(obj, "_setup_query"):
|
365
|
-
obj._setup_query()
|
366
|
-
return obj
|
378
|
+
obj._setup_query() # type: ignore[attr-defined]
|
379
|
+
return obj # type: ignore[return-value]
|
367
380
|
|
368
|
-
def relabeled_clone(self, change_map):
|
381
|
+
def relabeled_clone(self, change_map: dict[str, str]) -> Query:
|
369
382
|
clone = self.clone()
|
370
383
|
clone.change_aliases(change_map)
|
371
384
|
return clone
|
372
385
|
|
373
|
-
def _get_col(self, target, field, alias):
|
386
|
+
def _get_col(self, target: Any, field: Field, alias: str | None) -> Col:
|
374
387
|
if not self.alias_cols:
|
375
388
|
alias = None
|
376
389
|
return target.get_col(alias, field)
|
377
390
|
|
378
|
-
def get_aggregation(self, aggregate_exprs):
|
391
|
+
def get_aggregation(self, aggregate_exprs: dict[str, Any]) -> dict[str, Any]:
|
379
392
|
"""
|
380
393
|
Return the dictionary with the values of the existing aggregations.
|
381
394
|
"""
|
@@ -438,7 +451,7 @@ class Query(BaseExpression):
|
|
438
451
|
# used.
|
439
452
|
if inner_query.default_cols and has_existing_aggregation:
|
440
453
|
inner_query.group_by = (
|
441
|
-
self.model.
|
454
|
+
self.model._model_meta.get_field("id").get_col(
|
442
455
|
inner_query.get_initial_alias()
|
443
456
|
),
|
444
457
|
)
|
@@ -482,7 +495,7 @@ class Query(BaseExpression):
|
|
482
495
|
# field selected in the inner query, yet we must use a subquery.
|
483
496
|
# So, make sure at least one field is selected.
|
484
497
|
inner_query.select = (
|
485
|
-
self.model.
|
498
|
+
self.model._model_meta.get_field("id").get_col(
|
486
499
|
inner_query.get_initial_alias()
|
487
500
|
),
|
488
501
|
)
|
@@ -526,22 +539,22 @@ class Query(BaseExpression):
|
|
526
539
|
|
527
540
|
return dict(zip(outer_query.annotation_select, result))
|
528
541
|
|
529
|
-
def get_count(self):
|
542
|
+
def get_count(self) -> int:
|
530
543
|
"""
|
531
544
|
Perform a COUNT() query using the current filter constraints.
|
532
545
|
"""
|
533
546
|
obj = self.clone()
|
534
547
|
return obj.get_aggregation({"__count": Count("*")})["__count"]
|
535
548
|
|
536
|
-
def has_filters(self):
|
549
|
+
def has_filters(self) -> bool:
|
537
550
|
return self.where
|
538
551
|
|
539
|
-
def exists(self, limit=True):
|
552
|
+
def exists(self, limit: bool = True) -> Query:
|
540
553
|
q = self.clone()
|
541
554
|
if not (q.distinct and q.is_sliced):
|
542
555
|
if q.group_by is True:
|
543
556
|
q.add_fields(
|
544
|
-
(f.attname for f in self.model.
|
557
|
+
(f.attname for f in self.model._model_meta.concrete_fields), False
|
545
558
|
)
|
546
559
|
# Disable GROUP BY aliases to avoid orphaning references to the
|
547
560
|
# SELECT clause which is about to be cleared.
|
@@ -558,12 +571,12 @@ class Query(BaseExpression):
|
|
558
571
|
q.add_annotation(Value(1), "a")
|
559
572
|
return q
|
560
573
|
|
561
|
-
def has_results(self):
|
574
|
+
def has_results(self) -> bool:
|
562
575
|
q = self.exists()
|
563
576
|
compiler = q.get_compiler()
|
564
577
|
return compiler.has_results()
|
565
578
|
|
566
|
-
def explain(self, format=None, **options):
|
579
|
+
def explain(self, format: str | None = None, **options: Any) -> str:
|
567
580
|
q = self.clone()
|
568
581
|
for option_name in options:
|
569
582
|
if (
|
@@ -575,7 +588,7 @@ class Query(BaseExpression):
|
|
575
588
|
compiler = q.get_compiler()
|
576
589
|
return "\n".join(compiler.explain_query())
|
577
590
|
|
578
|
-
def combine(self, rhs, connector):
|
591
|
+
def combine(self, rhs: Query, connector: str) -> None:
|
579
592
|
"""
|
580
593
|
Merge the 'rhs' query into the current one (with any 'rhs' effects
|
581
594
|
being applied *after* (that is, "to the right of") anything in the
|
@@ -690,15 +703,20 @@ class Query(BaseExpression):
|
|
690
703
|
self.order_by = rhs.order_by or self.order_by
|
691
704
|
self.extra_order_by = rhs.extra_order_by or self.extra_order_by
|
692
705
|
|
693
|
-
def _get_defer_select_mask(
|
706
|
+
def _get_defer_select_mask(
|
707
|
+
self,
|
708
|
+
meta: Meta,
|
709
|
+
mask: dict[str, Any],
|
710
|
+
select_mask: dict[Any, Any] | None = None,
|
711
|
+
) -> dict[Any, Any]:
|
694
712
|
if select_mask is None:
|
695
713
|
select_mask = {}
|
696
|
-
select_mask[
|
714
|
+
select_mask[meta.get_field("id")] = {}
|
697
715
|
# All concrete fields that are not part of the defer mask must be
|
698
716
|
# loaded. If a relational field is encountered it gets added to the
|
699
717
|
# mask for it be considered if `select_related` and the cycle continues
|
700
718
|
# by recursively calling this function.
|
701
|
-
for field in
|
719
|
+
for field in meta.concrete_fields:
|
702
720
|
field_mask = mask.pop(field.name, None)
|
703
721
|
if field_mask is None:
|
704
722
|
select_mask.setdefault(field, {})
|
@@ -708,43 +726,48 @@ class Query(BaseExpression):
|
|
708
726
|
field_select_mask = select_mask.setdefault(field, {})
|
709
727
|
related_model = field.remote_field.model
|
710
728
|
self._get_defer_select_mask(
|
711
|
-
related_model.
|
729
|
+
related_model._model_meta, field_mask, field_select_mask
|
712
730
|
)
|
713
731
|
# Remaining defer entries must be references to reverse relationships.
|
714
732
|
# The following code is expected to raise FieldError if it encounters
|
715
733
|
# a malformed defer entry.
|
716
734
|
for field_name, field_mask in mask.items():
|
717
735
|
if filtered_relation := self._filtered_relations.get(field_name):
|
718
|
-
relation =
|
736
|
+
relation = meta.get_field(filtered_relation.relation_name)
|
719
737
|
field_select_mask = select_mask.setdefault((field_name, relation), {})
|
720
738
|
field = relation.field
|
721
739
|
else:
|
722
|
-
field =
|
740
|
+
field = meta.get_field(field_name).field
|
723
741
|
field_select_mask = select_mask.setdefault(field, {})
|
724
742
|
related_model = field.model
|
725
743
|
self._get_defer_select_mask(
|
726
|
-
related_model.
|
744
|
+
related_model._model_meta, field_mask, field_select_mask
|
727
745
|
)
|
728
746
|
return select_mask
|
729
747
|
|
730
|
-
def _get_only_select_mask(
|
748
|
+
def _get_only_select_mask(
|
749
|
+
self,
|
750
|
+
meta: Meta,
|
751
|
+
mask: dict[str, Any],
|
752
|
+
select_mask: dict[Any, Any] | None = None,
|
753
|
+
) -> dict[Any, Any]:
|
731
754
|
if select_mask is None:
|
732
755
|
select_mask = {}
|
733
|
-
select_mask[
|
756
|
+
select_mask[meta.get_field("id")] = {}
|
734
757
|
# Only include fields mentioned in the mask.
|
735
758
|
for field_name, field_mask in mask.items():
|
736
|
-
field =
|
759
|
+
field = meta.get_field(field_name)
|
737
760
|
field_select_mask = select_mask.setdefault(field, {})
|
738
761
|
if field_mask:
|
739
762
|
if not field.is_relation:
|
740
763
|
raise FieldError(next(iter(field_mask)))
|
741
764
|
related_model = field.remote_field.model
|
742
765
|
self._get_only_select_mask(
|
743
|
-
related_model.
|
766
|
+
related_model._model_meta, field_mask, field_select_mask
|
744
767
|
)
|
745
768
|
return select_mask
|
746
769
|
|
747
|
-
def get_select_mask(self):
|
770
|
+
def get_select_mask(self) -> dict[Any, Any]:
|
748
771
|
"""
|
749
772
|
Convert the self.deferred_loading data structure to an alternate data
|
750
773
|
structure, describing the field that *will* be loaded. This is used to
|
@@ -761,12 +784,14 @@ class Query(BaseExpression):
|
|
761
784
|
part_mask = mask
|
762
785
|
for part in field_name.split(LOOKUP_SEP):
|
763
786
|
part_mask = part_mask.setdefault(part, {})
|
764
|
-
|
787
|
+
meta = self.get_model_meta()
|
765
788
|
if defer:
|
766
|
-
return self._get_defer_select_mask(
|
767
|
-
return self._get_only_select_mask(
|
789
|
+
return self._get_defer_select_mask(meta, mask)
|
790
|
+
return self._get_only_select_mask(meta, mask)
|
768
791
|
|
769
|
-
def table_alias(
|
792
|
+
def table_alias(
|
793
|
+
self, table_name: str, create: bool = False, filtered_relation: Any = None
|
794
|
+
) -> tuple[str, bool]:
|
770
795
|
"""
|
771
796
|
Return a table alias for the given table_name and whether this is a
|
772
797
|
new alias or not.
|
@@ -793,15 +818,15 @@ class Query(BaseExpression):
|
|
793
818
|
self.alias_refcount[alias] = 1
|
794
819
|
return alias, True
|
795
820
|
|
796
|
-
def ref_alias(self, alias):
|
821
|
+
def ref_alias(self, alias: str) -> None:
|
797
822
|
"""Increases the reference count for this alias."""
|
798
823
|
self.alias_refcount[alias] += 1
|
799
824
|
|
800
|
-
def unref_alias(self, alias, amount=1):
|
825
|
+
def unref_alias(self, alias: str, amount: int = 1) -> None:
|
801
826
|
"""Decreases the reference count for this alias."""
|
802
827
|
self.alias_refcount[alias] -= amount
|
803
828
|
|
804
|
-
def promote_joins(self, aliases):
|
829
|
+
def promote_joins(self, aliases: set[str] | list[str]) -> None:
|
805
830
|
"""
|
806
831
|
Promote recursively the join type of given aliases and its children to
|
807
832
|
an outer join. If 'unconditional' is False, only promote the join if
|
@@ -838,7 +863,7 @@ class Query(BaseExpression):
|
|
838
863
|
and join not in aliases
|
839
864
|
)
|
840
865
|
|
841
|
-
def demote_joins(self, aliases):
|
866
|
+
def demote_joins(self, aliases: set[str] | list[str]) -> None:
|
842
867
|
"""
|
843
868
|
Change join type from LOUTER to INNER for all joins in aliases.
|
844
869
|
|
@@ -857,7 +882,7 @@ class Query(BaseExpression):
|
|
857
882
|
if self.alias_map[parent_alias].join_type == INNER:
|
858
883
|
aliases.append(parent_alias)
|
859
884
|
|
860
|
-
def reset_refcounts(self, to_counts):
|
885
|
+
def reset_refcounts(self, to_counts: dict[str, int]) -> None:
|
861
886
|
"""
|
862
887
|
Reset reference counts for aliases so that they match the value passed
|
863
888
|
in `to_counts`.
|
@@ -866,7 +891,7 @@ class Query(BaseExpression):
|
|
866
891
|
unref_amount = cur_refcount - to_counts.get(alias, 0)
|
867
892
|
self.unref_alias(alias, unref_amount)
|
868
893
|
|
869
|
-
def change_aliases(self, change_map):
|
894
|
+
def change_aliases(self, change_map: dict[str, str]) -> None:
|
870
895
|
"""
|
871
896
|
Change the aliases in change_map (which maps old-alias -> new-alias),
|
872
897
|
relabelling any references to them in select columns and the where
|
@@ -911,7 +936,9 @@ class Query(BaseExpression):
|
|
911
936
|
for alias, aliased in self.external_aliases.items()
|
912
937
|
}
|
913
938
|
|
914
|
-
def bump_prefix(
|
939
|
+
def bump_prefix(
|
940
|
+
self, other_query: Query, exclude: set[str] | dict[str, str] | None = None
|
941
|
+
) -> None:
|
915
942
|
"""
|
916
943
|
Change the alias prefix to the next letter in the alphabet in a way
|
917
944
|
that the other query's aliases and this query's aliases will not
|
@@ -919,7 +946,7 @@ class Query(BaseExpression):
|
|
919
946
|
after this call. To prevent changing aliases use the exclude parameter.
|
920
947
|
"""
|
921
948
|
|
922
|
-
def prefix_gen():
|
949
|
+
def prefix_gen() -> TypingIterator[str]:
|
923
950
|
"""
|
924
951
|
Generate a sequence of characters in alphabetical order:
|
925
952
|
-> 'A', 'B', 'C', ...
|
@@ -966,7 +993,7 @@ class Query(BaseExpression):
|
|
966
993
|
}
|
967
994
|
)
|
968
995
|
|
969
|
-
def get_initial_alias(self):
|
996
|
+
def get_initial_alias(self) -> str | None:
|
970
997
|
"""
|
971
998
|
Return the first alias for this query, after increasing its reference
|
972
999
|
count.
|
@@ -975,12 +1002,14 @@ class Query(BaseExpression):
|
|
975
1002
|
alias = self.base_table
|
976
1003
|
self.ref_alias(alias)
|
977
1004
|
elif self.model:
|
978
|
-
alias = self.join(
|
1005
|
+
alias = self.join(
|
1006
|
+
self.base_table_class(self.model.model_options.db_table, None)
|
1007
|
+
)
|
979
1008
|
else:
|
980
1009
|
alias = None
|
981
1010
|
return alias
|
982
1011
|
|
983
|
-
def count_active_tables(self):
|
1012
|
+
def count_active_tables(self) -> int:
|
984
1013
|
"""
|
985
1014
|
Return the number of tables in this query with a non-zero reference
|
986
1015
|
count. After execution, the reference counts are zeroed, so tables
|
@@ -988,7 +1017,12 @@ class Query(BaseExpression):
|
|
988
1017
|
"""
|
989
1018
|
return len([1 for count in self.alias_refcount.values() if count])
|
990
1019
|
|
991
|
-
def join(
|
1020
|
+
def join(
|
1021
|
+
self,
|
1022
|
+
join: BaseTable | Join,
|
1023
|
+
reuse: set[str] | None = None,
|
1024
|
+
reuse_with_filtered_relation: bool = False,
|
1025
|
+
) -> str:
|
992
1026
|
"""
|
993
1027
|
Return an alias for the 'join', either reusing an existing alias for
|
994
1028
|
that join or creating a new one. 'join' is either a base_table_class or
|
@@ -1029,7 +1063,7 @@ class Query(BaseExpression):
|
|
1029
1063
|
join.table_name, create=True, filtered_relation=join.filtered_relation
|
1030
1064
|
)
|
1031
1065
|
if join.join_type:
|
1032
|
-
if self.alias_map[join.parent_alias].join_type == LOUTER or join.nullable:
|
1066
|
+
if self.alias_map[join.parent_alias].join_type == LOUTER or join.nullable: # type: ignore[attr-defined]
|
1033
1067
|
join_type = LOUTER
|
1034
1068
|
else:
|
1035
1069
|
join_type = INNER
|
@@ -1038,14 +1072,16 @@ class Query(BaseExpression):
|
|
1038
1072
|
self.alias_map[alias] = join
|
1039
1073
|
return alias
|
1040
1074
|
|
1041
|
-
def check_alias(self, alias):
|
1075
|
+
def check_alias(self, alias: str) -> None:
|
1042
1076
|
if FORBIDDEN_ALIAS_PATTERN.search(alias):
|
1043
1077
|
raise ValueError(
|
1044
1078
|
"Column aliases cannot contain whitespace characters, quotation marks, "
|
1045
1079
|
"semicolons, or SQL comments."
|
1046
1080
|
)
|
1047
1081
|
|
1048
|
-
def add_annotation(
|
1082
|
+
def add_annotation(
|
1083
|
+
self, annotation: BaseExpression, alias: str, select: bool = True
|
1084
|
+
) -> None:
|
1049
1085
|
"""Add a single annotation expression to the Query."""
|
1050
1086
|
self.check_alias(alias)
|
1051
1087
|
annotation = annotation.resolve_expression(self, allow_joins=True, reuse=None)
|
@@ -1055,7 +1091,7 @@ class Query(BaseExpression):
|
|
1055
1091
|
self.set_annotation_mask(set(self.annotation_select).difference({alias}))
|
1056
1092
|
self.annotations[alias] = annotation
|
1057
1093
|
|
1058
|
-
def resolve_expression(self, query, *args, **kwargs):
|
1094
|
+
def resolve_expression(self, query: Query, *args: Any, **kwargs: Any) -> Query:
|
1059
1095
|
clone = self.clone()
|
1060
1096
|
# Subqueries need to use a different set of aliases than the outer query.
|
1061
1097
|
clone.bump_prefix(query)
|
@@ -1078,13 +1114,13 @@ class Query(BaseExpression):
|
|
1078
1114
|
for alias, table in query.alias_map.items():
|
1079
1115
|
clone.external_aliases[alias] = (
|
1080
1116
|
isinstance(table, Join)
|
1081
|
-
and table.join_field.related_model.
|
1117
|
+
and table.join_field.related_model.model_options.db_table != alias
|
1082
1118
|
) or (
|
1083
1119
|
isinstance(table, BaseTable) and table.table_name != table.table_alias
|
1084
1120
|
)
|
1085
1121
|
return clone
|
1086
1122
|
|
1087
|
-
def get_external_cols(self):
|
1123
|
+
def get_external_cols(self) -> list[Col]:
|
1088
1124
|
exprs = chain(self.annotations.values(), self.where.children)
|
1089
1125
|
return [
|
1090
1126
|
col
|
@@ -1092,7 +1128,9 @@ class Query(BaseExpression):
|
|
1092
1128
|
if col.alias in self.external_aliases
|
1093
1129
|
]
|
1094
1130
|
|
1095
|
-
def get_group_by_cols(
|
1131
|
+
def get_group_by_cols(
|
1132
|
+
self, wrapper: BaseExpression | None = None
|
1133
|
+
) -> list[Col] | list[BaseExpression]:
|
1096
1134
|
# If wrapper is referenced by an alias for an explicit GROUP BY through
|
1097
1135
|
# values() a reference to this expression and not the self must be
|
1098
1136
|
# returned to ensure external column references are not grouped against
|
@@ -1102,7 +1140,9 @@ class Query(BaseExpression):
|
|
1102
1140
|
return [wrapper or self]
|
1103
1141
|
return external_cols
|
1104
1142
|
|
1105
|
-
def as_sql(
|
1143
|
+
def as_sql(
|
1144
|
+
self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
|
1145
|
+
) -> tuple[str, tuple[Any, ...]]:
|
1106
1146
|
# Some backends (e.g. Oracle) raise an error when a subquery contains
|
1107
1147
|
# unnecessary ORDER BY clause.
|
1108
1148
|
if (
|
@@ -1117,7 +1157,9 @@ class Query(BaseExpression):
|
|
1117
1157
|
sql = f"({sql})"
|
1118
1158
|
return sql, params
|
1119
1159
|
|
1120
|
-
def resolve_lookup_value(
|
1160
|
+
def resolve_lookup_value(
|
1161
|
+
self, value: Any, can_reuse: set[str] | None, allow_joins: bool
|
1162
|
+
) -> Any:
|
1121
1163
|
if hasattr(value, "resolve_expression"):
|
1122
1164
|
value = value.resolve_expression(
|
1123
1165
|
self,
|
@@ -1137,7 +1179,9 @@ class Query(BaseExpression):
|
|
1137
1179
|
return type_(values)
|
1138
1180
|
return value
|
1139
1181
|
|
1140
|
-
def solve_lookup_type(
|
1182
|
+
def solve_lookup_type(
|
1183
|
+
self, lookup: str, summarize: bool = False
|
1184
|
+
) -> tuple[list[str] | tuple[str, ...], tuple[str, ...], BaseExpression | bool]:
|
1141
1185
|
"""
|
1142
1186
|
Solve the lookup type from the lookup (e.g.: 'foobar__id__icontains').
|
1143
1187
|
"""
|
@@ -1151,48 +1195,50 @@ class Query(BaseExpression):
|
|
1151
1195
|
if summarize:
|
1152
1196
|
expression = Ref(annotation, expression)
|
1153
1197
|
return expression_lookups, (), expression
|
1154
|
-
_, field, _, lookup_parts = self.names_to_path(
|
1198
|
+
_, field, _, lookup_parts = self.names_to_path(
|
1199
|
+
lookup_splitted, self.get_model_meta()
|
1200
|
+
)
|
1155
1201
|
field_parts = lookup_splitted[0 : len(lookup_splitted) - len(lookup_parts)]
|
1156
1202
|
if len(lookup_parts) > 1 and not field_parts:
|
1157
1203
|
raise FieldError(
|
1158
|
-
f'Invalid lookup "{lookup}" for model {self.
|
1204
|
+
f'Invalid lookup "{lookup}" for model {self.get_model_meta().model.__name__}".'
|
1159
1205
|
)
|
1160
|
-
return lookup_parts, field_parts, False
|
1206
|
+
return lookup_parts, field_parts, False # type: ignore[return-value]
|
1161
1207
|
|
1162
|
-
def check_query_object_type(self, value,
|
1208
|
+
def check_query_object_type(self, value: Any, meta: Meta, field: Field) -> None:
|
1163
1209
|
"""
|
1164
1210
|
Check whether the object passed while querying is of the correct type.
|
1165
1211
|
If not, raise a ValueError specifying the wrong object.
|
1166
1212
|
"""
|
1167
|
-
if hasattr(value, "
|
1168
|
-
if not check_rel_lookup_compatibility(value.
|
1213
|
+
if hasattr(value, "_model_meta"):
|
1214
|
+
if not check_rel_lookup_compatibility(value._model_meta.model, meta, field):
|
1169
1215
|
raise ValueError(
|
1170
|
-
f'Cannot query "{value}": Must be "{
|
1216
|
+
f'Cannot query "{value}": Must be "{meta.model.model_options.object_name}" instance.'
|
1171
1217
|
)
|
1172
1218
|
|
1173
|
-
def check_related_objects(self, field, value,
|
1219
|
+
def check_related_objects(self, field: Field, value: Any, meta: Meta) -> None:
|
1174
1220
|
"""Check the type of object passed to query relations."""
|
1175
1221
|
if field.is_relation:
|
1176
1222
|
# Check that the field and the queryset use the same model in a
|
1177
1223
|
# query like .filter(author=Author.query.all()). For example, the
|
1178
|
-
#
|
1224
|
+
# meta would be Author's (from the author field) and value.model
|
1179
1225
|
# would be Author.query.all() queryset's .model (Author also).
|
1180
1226
|
# The field is the related field on the lhs side.
|
1181
1227
|
if (
|
1182
1228
|
isinstance(value, Query)
|
1183
1229
|
and not value.has_select_fields
|
1184
|
-
and not check_rel_lookup_compatibility(value.model,
|
1230
|
+
and not check_rel_lookup_compatibility(value.model, meta, field)
|
1185
1231
|
):
|
1186
1232
|
raise ValueError(
|
1187
|
-
f'Cannot use QuerySet for "{value.model.
|
1233
|
+
f'Cannot use QuerySet for "{value.model.model_options.object_name}": Use a QuerySet for "{meta.model.model_options.object_name}".'
|
1188
1234
|
)
|
1189
|
-
elif hasattr(value, "
|
1190
|
-
self.check_query_object_type(value,
|
1235
|
+
elif hasattr(value, "_model_meta"):
|
1236
|
+
self.check_query_object_type(value, meta, field)
|
1191
1237
|
elif hasattr(value, "__iter__"):
|
1192
1238
|
for v in value:
|
1193
|
-
self.check_query_object_type(v,
|
1239
|
+
self.check_query_object_type(v, meta, field)
|
1194
1240
|
|
1195
|
-
def check_filterable(self, expression):
|
1241
|
+
def check_filterable(self, expression: Any) -> None:
|
1196
1242
|
"""Raise an error if expression cannot be used in a WHERE clause."""
|
1197
1243
|
if hasattr(expression, "resolve_expression") and not getattr(
|
1198
1244
|
expression, "filterable", True
|
@@ -1204,7 +1250,9 @@ class Query(BaseExpression):
|
|
1204
1250
|
for expr in expression.get_source_expressions():
|
1205
1251
|
self.check_filterable(expr)
|
1206
1252
|
|
1207
|
-
def build_lookup(
|
1253
|
+
def build_lookup(
|
1254
|
+
self, lookups: list[str], lhs: BaseExpression, rhs: Any
|
1255
|
+
) -> Lookup | None:
|
1208
1256
|
"""
|
1209
1257
|
Try to extract transforms and lookup from given lhs.
|
1210
1258
|
|
@@ -1235,11 +1283,11 @@ class Query(BaseExpression):
|
|
1235
1283
|
if lookup.rhs is None and not lookup.can_use_none_as_rhs:
|
1236
1284
|
if lookup_name not in ("exact", "iexact"):
|
1237
1285
|
raise ValueError("Cannot use None as a query value")
|
1238
|
-
return lhs.get_lookup("isnull")(lhs, True)
|
1286
|
+
return lhs.get_lookup("isnull")(lhs, True) # type: ignore[return-value]
|
1239
1287
|
|
1240
1288
|
return lookup
|
1241
1289
|
|
1242
|
-
def try_transform(self, lhs, name):
|
1290
|
+
def try_transform(self, lhs: BaseExpression, name: str) -> BaseExpression:
|
1243
1291
|
"""
|
1244
1292
|
Helper method for build_lookup(). Try to fetch and initialize
|
1245
1293
|
a transform for name parameter from lhs.
|
@@ -1265,16 +1313,16 @@ class Query(BaseExpression):
|
|
1265
1313
|
|
1266
1314
|
def build_filter(
|
1267
1315
|
self,
|
1268
|
-
filter_expr,
|
1269
|
-
branch_negated=False,
|
1270
|
-
current_negated=False,
|
1271
|
-
can_reuse=None,
|
1272
|
-
allow_joins=True,
|
1273
|
-
split_subq=True,
|
1274
|
-
reuse_with_filtered_relation=False,
|
1275
|
-
check_filterable=True,
|
1276
|
-
summarize=False,
|
1277
|
-
):
|
1316
|
+
filter_expr: tuple[str, Any] | Q | BaseExpression,
|
1317
|
+
branch_negated: bool = False,
|
1318
|
+
current_negated: bool = False,
|
1319
|
+
can_reuse: set[str] | None = None,
|
1320
|
+
allow_joins: bool = True,
|
1321
|
+
split_subq: bool = True,
|
1322
|
+
reuse_with_filtered_relation: bool = False,
|
1323
|
+
check_filterable: bool = True,
|
1324
|
+
summarize: bool = False,
|
1325
|
+
) -> tuple[WhereNode, set[str] | tuple[()]]:
|
1278
1326
|
"""
|
1279
1327
|
Build a WhereNode for a single filter clause but don't add it
|
1280
1328
|
to this Query. Query.add_q() will then add this filter to the where
|
@@ -1321,10 +1369,10 @@ class Query(BaseExpression):
|
|
1321
1369
|
raise TypeError("Cannot filter against a non-conditional expression.")
|
1322
1370
|
condition = filter_expr.resolve_expression(
|
1323
1371
|
self, allow_joins=allow_joins, summarize=summarize
|
1324
|
-
)
|
1372
|
+
) # type: ignore[attr-defined]
|
1325
1373
|
if not isinstance(condition, Lookup):
|
1326
1374
|
condition = self.build_lookup(["exact"], condition, True)
|
1327
|
-
return WhereNode([condition], connector=AND), []
|
1375
|
+
return WhereNode([condition], connector=AND), [] # type: ignore[return-value]
|
1328
1376
|
arg, value = filter_expr
|
1329
1377
|
if not arg:
|
1330
1378
|
raise FieldError(f"Cannot parse keyword query {arg!r}")
|
@@ -1347,16 +1395,16 @@ class Query(BaseExpression):
|
|
1347
1395
|
|
1348
1396
|
if reffed_expression:
|
1349
1397
|
condition = self.build_lookup(lookups, reffed_expression, value)
|
1350
|
-
return WhereNode([condition], connector=AND), []
|
1398
|
+
return WhereNode([condition], connector=AND), [] # type: ignore[return-value]
|
1351
1399
|
|
1352
|
-
|
1400
|
+
meta = self.get_model_meta()
|
1353
1401
|
alias = self.get_initial_alias()
|
1354
1402
|
allow_many = not branch_negated or not split_subq
|
1355
1403
|
|
1356
1404
|
try:
|
1357
1405
|
join_info = self.setup_joins(
|
1358
1406
|
parts,
|
1359
|
-
|
1407
|
+
meta,
|
1360
1408
|
alias,
|
1361
1409
|
can_reuse=can_reuse,
|
1362
1410
|
allow_many=allow_many,
|
@@ -1366,7 +1414,7 @@ class Query(BaseExpression):
|
|
1366
1414
|
# Prevent iterator from being consumed by check_related_objects()
|
1367
1415
|
if isinstance(value, Iterator):
|
1368
1416
|
value = list(value)
|
1369
|
-
self.check_related_objects(join_info.final_field, value, join_info.
|
1417
|
+
self.check_related_objects(join_info.final_field, value, join_info.meta)
|
1370
1418
|
|
1371
1419
|
# split_exclude() needs to know which joins were generated for the
|
1372
1420
|
# lookup parts
|
@@ -1430,10 +1478,10 @@ class Query(BaseExpression):
|
|
1430
1478
|
clause.add(lookup_class(value, False), AND)
|
1431
1479
|
return clause, used_joins if not require_outer else ()
|
1432
1480
|
|
1433
|
-
def add_filter(self, filter_lhs, filter_rhs):
|
1481
|
+
def add_filter(self, filter_lhs: str, filter_rhs: Any) -> None:
|
1434
1482
|
self.add_q(Q((filter_lhs, filter_rhs)))
|
1435
1483
|
|
1436
|
-
def add_q(self, q_object):
|
1484
|
+
def add_q(self, q_object: Q) -> None: # type: ignore[unsupported-operator]
|
1437
1485
|
"""
|
1438
1486
|
A preprocessor for the internal _add_q(). Responsible for doing final
|
1439
1487
|
join promotion.
|
@@ -1452,23 +1500,23 @@ class Query(BaseExpression):
|
|
1452
1500
|
self.where.add(clause, AND)
|
1453
1501
|
self.demote_joins(existing_inner)
|
1454
1502
|
|
1455
|
-
def build_where(self, filter_expr):
|
1503
|
+
def build_where(self, filter_expr: tuple[str, Any]) -> WhereNode:
|
1456
1504
|
return self.build_filter(filter_expr, allow_joins=False)[0]
|
1457
1505
|
|
1458
|
-
def clear_where(self):
|
1506
|
+
def clear_where(self) -> None:
|
1459
1507
|
self.where = WhereNode()
|
1460
1508
|
|
1461
1509
|
def _add_q(
|
1462
1510
|
self,
|
1463
|
-
q_object,
|
1464
|
-
used_aliases,
|
1465
|
-
branch_negated=False,
|
1466
|
-
current_negated=False,
|
1467
|
-
allow_joins=True,
|
1468
|
-
split_subq=True,
|
1469
|
-
check_filterable=True,
|
1470
|
-
summarize=False,
|
1471
|
-
):
|
1511
|
+
q_object: Q,
|
1512
|
+
used_aliases: set[str] | None,
|
1513
|
+
branch_negated: bool = False,
|
1514
|
+
current_negated: bool = False,
|
1515
|
+
allow_joins: bool = True,
|
1516
|
+
split_subq: bool = True,
|
1517
|
+
check_filterable: bool = True,
|
1518
|
+
summarize: bool = False,
|
1519
|
+
) -> tuple[WhereNode, set[str] | tuple[()]]: # type: ignore[unsupported-operator]
|
1472
1520
|
"""Add a Q-object to the current filter."""
|
1473
1521
|
connector = q_object.connector
|
1474
1522
|
current_negated ^= q_object.negated
|
@@ -1495,8 +1543,12 @@ class Query(BaseExpression):
|
|
1495
1543
|
return target_clause, needed_inner
|
1496
1544
|
|
1497
1545
|
def build_filtered_relation_q(
|
1498
|
-
self,
|
1499
|
-
|
1546
|
+
self,
|
1547
|
+
q_object: Q,
|
1548
|
+
reuse: set[str],
|
1549
|
+
branch_negated: bool = False,
|
1550
|
+
current_negated: bool = False,
|
1551
|
+
) -> WhereNode: # type: ignore[unsupported-operator]
|
1500
1552
|
"""Add a FilteredRelation object to the current filter."""
|
1501
1553
|
connector = q_object.connector
|
1502
1554
|
current_negated ^= q_object.negated
|
@@ -1523,7 +1575,7 @@ class Query(BaseExpression):
|
|
1523
1575
|
target_clause.add(child_clause, connector)
|
1524
1576
|
return target_clause
|
1525
1577
|
|
1526
|
-
def add_filtered_relation(self, filtered_relation, alias):
|
1578
|
+
def add_filtered_relation(self, filtered_relation: Any, alias: str) -> None:
|
1527
1579
|
filtered_relation.alias = alias
|
1528
1580
|
lookups = dict(get_children_from_q(filtered_relation.condition))
|
1529
1581
|
relation_lookup_parts, relation_field_parts, _ = self.solve_lookup_type(
|
@@ -1553,12 +1605,18 @@ class Query(BaseExpression):
|
|
1553
1605
|
)
|
1554
1606
|
self._filtered_relations[filtered_relation.alias] = filtered_relation
|
1555
1607
|
|
1556
|
-
def names_to_path(
|
1608
|
+
def names_to_path(
|
1609
|
+
self,
|
1610
|
+
names: list[str],
|
1611
|
+
meta: Meta,
|
1612
|
+
allow_many: bool = True,
|
1613
|
+
fail_on_missing: bool = False,
|
1614
|
+
) -> tuple[list[Any], Field, tuple[Field, ...], list[str]]:
|
1557
1615
|
"""
|
1558
1616
|
Walk the list of names and turns them into PathInfo tuples. A single
|
1559
1617
|
name in 'names' can generate multiple PathInfos (m2m, for example).
|
1560
1618
|
|
1561
|
-
'names' is the path of names to travel, '
|
1619
|
+
'names' is the path of names to travel, 'meta' is the Meta we
|
1562
1620
|
start the name resolving from, 'allow_many' is as for setup_joins().
|
1563
1621
|
If fail_on_missing is set to True, then a name that can't be resolved
|
1564
1622
|
will generate a FieldError.
|
@@ -1575,9 +1633,9 @@ class Query(BaseExpression):
|
|
1575
1633
|
field = None
|
1576
1634
|
filtered_relation = None
|
1577
1635
|
try:
|
1578
|
-
if
|
1636
|
+
if meta is None:
|
1579
1637
|
raise FieldDoesNotExist
|
1580
|
-
field =
|
1638
|
+
field = meta.get_field(name)
|
1581
1639
|
except FieldDoesNotExist:
|
1582
1640
|
if name in self.annotation_select:
|
1583
1641
|
field = self.annotation_select[name].output_field
|
@@ -1587,13 +1645,13 @@ class Query(BaseExpression):
|
|
1587
1645
|
parts = filtered_relation.relation_name.split(LOOKUP_SEP)
|
1588
1646
|
filtered_relation_path, field, _, _ = self.names_to_path(
|
1589
1647
|
parts,
|
1590
|
-
|
1648
|
+
meta,
|
1591
1649
|
allow_many,
|
1592
1650
|
fail_on_missing,
|
1593
1651
|
)
|
1594
1652
|
path.extend(filtered_relation_path[:-1])
|
1595
1653
|
else:
|
1596
|
-
field =
|
1654
|
+
field = meta.get_field(filtered_relation.relation_name) # type: ignore[attr-defined]
|
1597
1655
|
if field is not None:
|
1598
1656
|
# Fields that contain one-to-many relations with a generic
|
1599
1657
|
# model (like a GenericForeignKey) cannot generate reverse
|
@@ -1612,7 +1670,7 @@ class Query(BaseExpression):
|
|
1612
1670
|
if pos == -1 or fail_on_missing:
|
1613
1671
|
available = sorted(
|
1614
1672
|
[
|
1615
|
-
*get_field_names_from_opts(
|
1673
|
+
*get_field_names_from_opts(meta),
|
1616
1674
|
*self.annotation_select,
|
1617
1675
|
*self._filtered_relations,
|
1618
1676
|
]
|
@@ -1637,7 +1695,7 @@ class Query(BaseExpression):
|
|
1637
1695
|
last = pathinfos[-1]
|
1638
1696
|
path.extend(pathinfos)
|
1639
1697
|
final_field = last.join_field
|
1640
|
-
|
1698
|
+
meta = last.to_meta
|
1641
1699
|
targets = last.target_fields
|
1642
1700
|
cur_names_with_path[1].extend(pathinfos)
|
1643
1701
|
names_with_path.append(cur_names_with_path)
|
@@ -1655,16 +1713,16 @@ class Query(BaseExpression):
|
|
1655
1713
|
|
1656
1714
|
def setup_joins(
|
1657
1715
|
self,
|
1658
|
-
names,
|
1659
|
-
|
1660
|
-
alias,
|
1661
|
-
can_reuse=None,
|
1662
|
-
allow_many=True,
|
1663
|
-
reuse_with_filtered_relation=False,
|
1664
|
-
):
|
1716
|
+
names: list[str],
|
1717
|
+
meta: Meta,
|
1718
|
+
alias: str,
|
1719
|
+
can_reuse: set[str] | None = None,
|
1720
|
+
allow_many: bool = True,
|
1721
|
+
reuse_with_filtered_relation: bool = False,
|
1722
|
+
) -> JoinInfo:
|
1665
1723
|
"""
|
1666
1724
|
Compute the necessary table joins for the passage through the fields
|
1667
|
-
given in 'names'. '
|
1725
|
+
given in 'names'. 'meta' is the Meta for the current model
|
1668
1726
|
(which gives the table we are starting from), 'alias' is the alias for
|
1669
1727
|
the table to start the joining from.
|
1670
1728
|
|
@@ -1698,10 +1756,10 @@ class Query(BaseExpression):
|
|
1698
1756
|
# directly, compute transforms here and create a partial that converts
|
1699
1757
|
# fields to the appropriate wrapped version.
|
1700
1758
|
|
1701
|
-
def final_transformer(field, alias):
|
1759
|
+
def final_transformer(field: Field, alias: str | None) -> Col:
|
1702
1760
|
if not self.alias_cols:
|
1703
1761
|
alias = None
|
1704
|
-
return field.get_col(alias)
|
1762
|
+
return field.get_col(alias) # type: ignore[arg-type]
|
1705
1763
|
|
1706
1764
|
# Try resolving all the names as fields first. If there's an error,
|
1707
1765
|
# treat trailing names as lookups until a field can be resolved.
|
@@ -1710,7 +1768,7 @@ class Query(BaseExpression):
|
|
1710
1768
|
try:
|
1711
1769
|
path, final_field, targets, rest = self.names_to_path(
|
1712
1770
|
names[:pivot],
|
1713
|
-
|
1771
|
+
meta,
|
1714
1772
|
allow_many,
|
1715
1773
|
fail_on_missing=True,
|
1716
1774
|
)
|
@@ -1728,7 +1786,9 @@ class Query(BaseExpression):
|
|
1728
1786
|
break
|
1729
1787
|
for name in transforms:
|
1730
1788
|
|
1731
|
-
def transform(
|
1789
|
+
def transform(
|
1790
|
+
field: Field, alias: str | None, *, name: str, previous: Any
|
1791
|
+
) -> BaseExpression:
|
1732
1792
|
try:
|
1733
1793
|
wrapped = previous(field, alias)
|
1734
1794
|
return self.try_transform(wrapped, name)
|
@@ -1739,10 +1799,10 @@ class Query(BaseExpression):
|
|
1739
1799
|
else:
|
1740
1800
|
raise
|
1741
1801
|
|
1742
|
-
final_transformer = functools.partial(
|
1802
|
+
final_transformer = functools.partial( # type: ignore[misc]
|
1743
1803
|
transform, name=name, previous=final_transformer
|
1744
1804
|
)
|
1745
|
-
final_transformer.has_transforms = True
|
1805
|
+
final_transformer.has_transforms = True # type: ignore[attr-defined]
|
1746
1806
|
# Then, add the path to the query's joins. Note that we can't trim
|
1747
1807
|
# joins at this stage - we will need the information about join type
|
1748
1808
|
# of the trimmed joins.
|
@@ -1753,13 +1813,13 @@ class Query(BaseExpression):
|
|
1753
1813
|
else:
|
1754
1814
|
filtered_relation = None
|
1755
1815
|
table_alias = None
|
1756
|
-
|
1816
|
+
meta = join.to_meta
|
1757
1817
|
if join.direct:
|
1758
1818
|
nullable = self.is_nullable(join.join_field)
|
1759
1819
|
else:
|
1760
1820
|
nullable = True
|
1761
1821
|
connection = self.join_class(
|
1762
|
-
|
1822
|
+
meta.model.model_options.db_table,
|
1763
1823
|
alias,
|
1764
1824
|
table_alias,
|
1765
1825
|
INNER,
|
@@ -1776,9 +1836,11 @@ class Query(BaseExpression):
|
|
1776
1836
|
joins.append(alias)
|
1777
1837
|
if filtered_relation:
|
1778
1838
|
filtered_relation.path = joins[:]
|
1779
|
-
return JoinInfo(final_field, targets,
|
1839
|
+
return JoinInfo(final_field, targets, meta, joins, path, final_transformer)
|
1780
1840
|
|
1781
|
-
def trim_joins(
|
1841
|
+
def trim_joins(
|
1842
|
+
self, targets: tuple[Field, ...], joins: list[str], path: list[Any]
|
1843
|
+
) -> tuple[tuple[Field, ...], str, list[str]]:
|
1782
1844
|
"""
|
1783
1845
|
The 'target' parameter is the final field being joined to, 'joins'
|
1784
1846
|
is the full list of join aliases. The 'path' contain the PathInfos
|
@@ -1811,7 +1873,12 @@ class Query(BaseExpression):
|
|
1811
1873
|
return targets, joins[-1], joins
|
1812
1874
|
|
1813
1875
|
@classmethod
|
1814
|
-
def _gen_cols(
|
1876
|
+
def _gen_cols(
|
1877
|
+
cls,
|
1878
|
+
exprs: TypingIterator[Any],
|
1879
|
+
include_external: bool = False,
|
1880
|
+
resolve_refs: bool = True,
|
1881
|
+
) -> TypingIterator[Col]:
|
1815
1882
|
for expr in exprs:
|
1816
1883
|
if isinstance(expr, Col):
|
1817
1884
|
yield expr
|
@@ -1829,10 +1896,16 @@ class Query(BaseExpression):
|
|
1829
1896
|
)
|
1830
1897
|
|
1831
1898
|
@classmethod
|
1832
|
-
def _gen_col_aliases(cls, exprs):
|
1899
|
+
def _gen_col_aliases(cls, exprs: TypingIterator[Any]) -> TypingIterator[str]:
|
1833
1900
|
yield from (expr.alias for expr in cls._gen_cols(exprs))
|
1834
1901
|
|
1835
|
-
def resolve_ref(
|
1902
|
+
def resolve_ref(
|
1903
|
+
self,
|
1904
|
+
name: str,
|
1905
|
+
allow_joins: bool = True,
|
1906
|
+
reuse: set[str] | None = None,
|
1907
|
+
summarize: bool = False,
|
1908
|
+
) -> BaseExpression:
|
1836
1909
|
annotation = self.annotations.get(name)
|
1837
1910
|
if annotation is not None:
|
1838
1911
|
if not allow_joins:
|
@@ -1862,7 +1935,10 @@ class Query(BaseExpression):
|
|
1862
1935
|
annotation = self.try_transform(annotation, transform)
|
1863
1936
|
return annotation
|
1864
1937
|
join_info = self.setup_joins(
|
1865
|
-
field_list,
|
1938
|
+
field_list,
|
1939
|
+
self.get_model_meta(),
|
1940
|
+
self.get_initial_alias(),
|
1941
|
+
can_reuse=reuse,
|
1866
1942
|
)
|
1867
1943
|
targets, final_alias, join_list = self.trim_joins(
|
1868
1944
|
join_info.targets, join_info.joins, join_info.path
|
@@ -1882,7 +1958,12 @@ class Query(BaseExpression):
|
|
1882
1958
|
reuse.update(join_list)
|
1883
1959
|
return transform
|
1884
1960
|
|
1885
|
-
def split_exclude(
|
1961
|
+
def split_exclude(
|
1962
|
+
self,
|
1963
|
+
filter_expr: tuple[str, Any],
|
1964
|
+
can_reuse: set[str],
|
1965
|
+
names_with_path: list[tuple[str, list[Any]]],
|
1966
|
+
) -> tuple[WhereNode, set[str] | tuple[()]]:
|
1886
1967
|
"""
|
1887
1968
|
When doing an exclude against any kind of N-to-many relation, we need
|
1888
1969
|
to use a subquery. This method constructs the nested query, given the
|
@@ -1919,7 +2000,7 @@ class Query(BaseExpression):
|
|
1919
2000
|
select_field = col.target
|
1920
2001
|
alias = col.alias
|
1921
2002
|
if alias in can_reuse:
|
1922
|
-
id_field = select_field.model.
|
2003
|
+
id_field = select_field.model._model_meta.get_field("id")
|
1923
2004
|
# Need to add a restriction so that outer query's filters are in effect for
|
1924
2005
|
# the subquery, too.
|
1925
2006
|
query.bump_prefix(self)
|
@@ -1953,15 +2034,15 @@ class Query(BaseExpression):
|
|
1953
2034
|
# outercol IS NULL we will not match the row.
|
1954
2035
|
return condition, needed_inner
|
1955
2036
|
|
1956
|
-
def set_empty(self):
|
2037
|
+
def set_empty(self) -> None:
|
1957
2038
|
self.where.add(NothingNode(), AND)
|
1958
2039
|
for query in self.combined_queries:
|
1959
2040
|
query.set_empty()
|
1960
2041
|
|
1961
|
-
def is_empty(self):
|
2042
|
+
def is_empty(self) -> bool:
|
1962
2043
|
return any(isinstance(c, NothingNode) for c in self.where.children)
|
1963
2044
|
|
1964
|
-
def set_limits(self, low=None, high=None):
|
2045
|
+
def set_limits(self, low: int | None = None, high: int | None = None) -> None:
|
1965
2046
|
"""
|
1966
2047
|
Adjust the limits on the rows retrieved. Use low/high to set these,
|
1967
2048
|
as it makes it more Pythonic to read and write. When the SQL query is
|
@@ -1984,18 +2065,18 @@ class Query(BaseExpression):
|
|
1984
2065
|
if self.low_mark == self.high_mark:
|
1985
2066
|
self.set_empty()
|
1986
2067
|
|
1987
|
-
def clear_limits(self):
|
2068
|
+
def clear_limits(self) -> None:
|
1988
2069
|
"""Clear any existing limits."""
|
1989
2070
|
self.low_mark, self.high_mark = 0, None
|
1990
2071
|
|
1991
2072
|
@property
|
1992
|
-
def is_sliced(self):
|
2073
|
+
def is_sliced(self) -> bool:
|
1993
2074
|
return self.low_mark != 0 or self.high_mark is not None
|
1994
2075
|
|
1995
|
-
def has_limit_one(self):
|
2076
|
+
def has_limit_one(self) -> bool:
|
1996
2077
|
return self.high_mark is not None and (self.high_mark - self.low_mark) == 1
|
1997
2078
|
|
1998
|
-
def can_filter(self):
|
2079
|
+
def can_filter(self) -> bool:
|
1999
2080
|
"""
|
2000
2081
|
Return True if adding filters to this instance is still possible.
|
2001
2082
|
|
@@ -2003,7 +2084,7 @@ class Query(BaseExpression):
|
|
2003
2084
|
"""
|
2004
2085
|
return not self.is_sliced
|
2005
2086
|
|
2006
|
-
def clear_select_clause(self):
|
2087
|
+
def clear_select_clause(self) -> None:
|
2007
2088
|
"""Remove all fields from SELECT clause."""
|
2008
2089
|
self.select = ()
|
2009
2090
|
self.default_cols = False
|
@@ -2011,7 +2092,7 @@ class Query(BaseExpression):
|
|
2011
2092
|
self.set_extra_mask(())
|
2012
2093
|
self.set_annotation_mask(())
|
2013
2094
|
|
2014
|
-
def clear_select_fields(self):
|
2095
|
+
def clear_select_fields(self) -> None:
|
2015
2096
|
"""
|
2016
2097
|
Clear the list of fields to select (but not extra_select columns).
|
2017
2098
|
Some queryset types completely replace any existing list of select
|
@@ -2020,28 +2101,30 @@ class Query(BaseExpression):
|
|
2020
2101
|
self.select = ()
|
2021
2102
|
self.values_select = ()
|
2022
2103
|
|
2023
|
-
def add_select_col(self, col, name):
|
2104
|
+
def add_select_col(self, col: Col, name: str) -> None:
|
2024
2105
|
self.select += (col,)
|
2025
2106
|
self.values_select += (name,)
|
2026
2107
|
|
2027
|
-
def set_select(self, cols):
|
2108
|
+
def set_select(self, cols: list[Col] | tuple[Col, ...]) -> None:
|
2028
2109
|
self.default_cols = False
|
2029
2110
|
self.select = tuple(cols)
|
2030
2111
|
|
2031
|
-
def add_distinct_fields(self, *field_names):
|
2112
|
+
def add_distinct_fields(self, *field_names: str) -> None:
|
2032
2113
|
"""
|
2033
2114
|
Add and resolve the given fields to the query's "distinct on" clause.
|
2034
2115
|
"""
|
2035
2116
|
self.distinct_fields = field_names
|
2036
2117
|
self.distinct = True
|
2037
2118
|
|
2038
|
-
def add_fields(
|
2119
|
+
def add_fields(
|
2120
|
+
self, field_names: list[str] | TypingIterator[str], allow_m2m: bool = True
|
2121
|
+
) -> None:
|
2039
2122
|
"""
|
2040
2123
|
Add the given (model) fields to the select set. Add the field names in
|
2041
2124
|
the order specified.
|
2042
2125
|
"""
|
2043
2126
|
alias = self.get_initial_alias()
|
2044
|
-
|
2127
|
+
meta = self.get_model_meta()
|
2045
2128
|
|
2046
2129
|
try:
|
2047
2130
|
cols = []
|
@@ -2049,7 +2132,7 @@ class Query(BaseExpression):
|
|
2049
2132
|
# Join promotion note - we must not remove any rows here, so
|
2050
2133
|
# if there is no existing joins, use outer join.
|
2051
2134
|
join_info = self.setup_joins(
|
2052
|
-
name.split(LOOKUP_SEP),
|
2135
|
+
name.split(LOOKUP_SEP), meta, alias, allow_many=allow_m2m
|
2053
2136
|
)
|
2054
2137
|
targets, final_alias, joins = self.trim_joins(
|
2055
2138
|
join_info.targets,
|
@@ -2074,7 +2157,7 @@ class Query(BaseExpression):
|
|
2074
2157
|
else:
|
2075
2158
|
names = sorted(
|
2076
2159
|
[
|
2077
|
-
*get_field_names_from_opts(
|
2160
|
+
*get_field_names_from_opts(meta),
|
2078
2161
|
*self.extra,
|
2079
2162
|
*self.annotation_select,
|
2080
2163
|
*self._filtered_relations,
|
@@ -2086,7 +2169,7 @@ class Query(BaseExpression):
|
|
2086
2169
|
)
|
2087
2170
|
)
|
2088
2171
|
|
2089
|
-
def add_ordering(self, *ordering):
|
2172
|
+
def add_ordering(self, *ordering: str | BaseExpression) -> None:
|
2090
2173
|
"""
|
2091
2174
|
Add items from the 'ordering' sequence to the query's "order by"
|
2092
2175
|
clause. These items are either field names (not column names) --
|
@@ -2107,7 +2190,7 @@ class Query(BaseExpression):
|
|
2107
2190
|
continue
|
2108
2191
|
# names_to_path() validates the lookup. A descriptive
|
2109
2192
|
# FieldError will be raise if it's not.
|
2110
|
-
self.names_to_path(item.split(LOOKUP_SEP), self.model.
|
2193
|
+
self.names_to_path(item.split(LOOKUP_SEP), self.model._model_meta)
|
2111
2194
|
elif not hasattr(item, "resolve_expression"):
|
2112
2195
|
errors.append(item)
|
2113
2196
|
if getattr(item, "contains_aggregate", False):
|
@@ -2122,7 +2205,7 @@ class Query(BaseExpression):
|
|
2122
2205
|
else:
|
2123
2206
|
self.default_ordering = False
|
2124
2207
|
|
2125
|
-
def clear_ordering(self, force=False, clear_default=True):
|
2208
|
+
def clear_ordering(self, force: bool = False, clear_default: bool = True) -> None:
|
2126
2209
|
"""
|
2127
2210
|
Remove any ordering settings if the current query allows it without
|
2128
2211
|
side effects, set 'force' to True to clear the ordering regardless.
|
@@ -2138,7 +2221,7 @@ class Query(BaseExpression):
|
|
2138
2221
|
if clear_default:
|
2139
2222
|
self.default_ordering = False
|
2140
2223
|
|
2141
|
-
def set_group_by(self, allow_aliases=True):
|
2224
|
+
def set_group_by(self, allow_aliases: bool = True) -> None:
|
2142
2225
|
"""
|
2143
2226
|
Expand the GROUP BY clause required by the query.
|
2144
2227
|
|
@@ -2171,7 +2254,7 @@ class Query(BaseExpression):
|
|
2171
2254
|
group_by.extend(group_by_cols)
|
2172
2255
|
self.group_by = tuple(group_by)
|
2173
2256
|
|
2174
|
-
def add_select_related(self, fields):
|
2257
|
+
def add_select_related(self, fields: list[str]) -> None:
|
2175
2258
|
"""
|
2176
2259
|
Set up the select_related data structure so that we only select
|
2177
2260
|
certain related models (as opposed to all models, when
|
@@ -2187,7 +2270,15 @@ class Query(BaseExpression):
|
|
2187
2270
|
d = d.setdefault(part, {})
|
2188
2271
|
self.select_related = field_dict
|
2189
2272
|
|
2190
|
-
def add_extra(
|
2273
|
+
def add_extra(
|
2274
|
+
self,
|
2275
|
+
select: dict[str, str],
|
2276
|
+
select_params: list[Any] | None,
|
2277
|
+
where: list[str],
|
2278
|
+
params: list[Any],
|
2279
|
+
tables: list[str],
|
2280
|
+
order_by: tuple[str, ...],
|
2281
|
+
) -> None:
|
2191
2282
|
"""
|
2192
2283
|
Add data to the various extra_* attributes for user-created additions
|
2193
2284
|
to the query.
|
@@ -2220,11 +2311,11 @@ class Query(BaseExpression):
|
|
2220
2311
|
if order_by:
|
2221
2312
|
self.extra_order_by = order_by
|
2222
2313
|
|
2223
|
-
def clear_deferred_loading(self):
|
2314
|
+
def clear_deferred_loading(self) -> None:
|
2224
2315
|
"""Remove any fields from the deferred loading set."""
|
2225
2316
|
self.deferred_loading = (frozenset(), True)
|
2226
2317
|
|
2227
|
-
def add_deferred_loading(self, field_names):
|
2318
|
+
def add_deferred_loading(self, field_names: frozenset[str]) -> None:
|
2228
2319
|
"""
|
2229
2320
|
Add the given list of model field names to the set of fields to
|
2230
2321
|
exclude from loading from the database when automatic column selection
|
@@ -2249,7 +2340,7 @@ class Query(BaseExpression):
|
|
2249
2340
|
if new_only := set(field_names).difference(existing):
|
2250
2341
|
self.deferred_loading = new_only, True
|
2251
2342
|
|
2252
|
-
def add_immediate_loading(self, field_names):
|
2343
|
+
def add_immediate_loading(self, field_names: list[str] | set[str]) -> None:
|
2253
2344
|
"""
|
2254
2345
|
Add the given list of model field names to the set of fields to
|
2255
2346
|
retrieve when the SQL is executed ("immediate loading" fields). The
|
@@ -2270,7 +2361,9 @@ class Query(BaseExpression):
|
|
2270
2361
|
# Replace any existing "immediate load" field names.
|
2271
2362
|
self.deferred_loading = frozenset(field_names), False
|
2272
2363
|
|
2273
|
-
def set_annotation_mask(
|
2364
|
+
def set_annotation_mask(
|
2365
|
+
self, names: set[str] | frozenset[str] | list[str] | dict[str, Any] | None
|
2366
|
+
) -> None: # type: ignore[misc]
|
2274
2367
|
"""Set the mask of annotations that will be returned by the SELECT."""
|
2275
2368
|
if names is None:
|
2276
2369
|
self.annotation_select_mask = None
|
@@ -2278,11 +2371,11 @@ class Query(BaseExpression):
|
|
2278
2371
|
self.annotation_select_mask = set(names)
|
2279
2372
|
self._annotation_select_cache = None
|
2280
2373
|
|
2281
|
-
def append_annotation_mask(self, names):
|
2374
|
+
def append_annotation_mask(self, names: list[str] | dict[str, Any]) -> None:
|
2282
2375
|
if self.annotation_select_mask is not None:
|
2283
2376
|
self.set_annotation_mask(self.annotation_select_mask.union(names))
|
2284
2377
|
|
2285
|
-
def set_extra_mask(self, names):
|
2378
|
+
def set_extra_mask(self, names: set[str] | tuple[str, ...] | None) -> None:
|
2286
2379
|
"""
|
2287
2380
|
Set the mask of extra select items that will be returned by SELECT.
|
2288
2381
|
Don't remove them from the Query since they might be used later.
|
@@ -2293,7 +2386,7 @@ class Query(BaseExpression):
|
|
2293
2386
|
self.extra_select_mask = set(names)
|
2294
2387
|
self._extra_select_cache = None
|
2295
2388
|
|
2296
|
-
def set_values(self, fields):
|
2389
|
+
def set_values(self, fields: list[str]) -> None:
|
2297
2390
|
self.select_related = False
|
2298
2391
|
self.clear_deferred_loading()
|
2299
2392
|
self.clear_select_fields()
|
@@ -2320,13 +2413,13 @@ class Query(BaseExpression):
|
|
2320
2413
|
self.set_annotation_mask(annotation_names)
|
2321
2414
|
selected = frozenset(field_names + extra_names + annotation_names)
|
2322
2415
|
else:
|
2323
|
-
field_names = [f.attname for f in self.model.
|
2416
|
+
field_names = [f.attname for f in self.model._model_meta.concrete_fields]
|
2324
2417
|
selected = frozenset(field_names)
|
2325
2418
|
# Selected annotations must be known before setting the GROUP BY
|
2326
2419
|
# clause.
|
2327
2420
|
if self.group_by is True:
|
2328
2421
|
self.add_fields(
|
2329
|
-
(f.attname for f in self.model.
|
2422
|
+
(f.attname for f in self.model._model_meta.concrete_fields), False
|
2330
2423
|
)
|
2331
2424
|
# Disable GROUP BY aliases to avoid orphaning references to the
|
2332
2425
|
# SELECT clause which is about to be cleared.
|
@@ -2346,7 +2439,7 @@ class Query(BaseExpression):
|
|
2346
2439
|
self.add_fields(field_names, True)
|
2347
2440
|
|
2348
2441
|
@property
|
2349
|
-
def annotation_select(self):
|
2442
|
+
def annotation_select(self) -> dict[str, BaseExpression]:
|
2350
2443
|
"""
|
2351
2444
|
Return the dictionary of aggregate columns that are not masked and
|
2352
2445
|
should be used in the SELECT clause. Cache this result for performance.
|
@@ -2366,7 +2459,7 @@ class Query(BaseExpression):
|
|
2366
2459
|
return self.annotations
|
2367
2460
|
|
2368
2461
|
@property
|
2369
|
-
def extra_select(self):
|
2462
|
+
def extra_select(self) -> dict[str, tuple[str, list[Any]]]:
|
2370
2463
|
if self._extra_select_cache is not None:
|
2371
2464
|
return self._extra_select_cache
|
2372
2465
|
if not self.extra:
|
@@ -2379,7 +2472,9 @@ class Query(BaseExpression):
|
|
2379
2472
|
else:
|
2380
2473
|
return self.extra
|
2381
2474
|
|
2382
|
-
def trim_start(
|
2475
|
+
def trim_start(
|
2476
|
+
self, names_with_path: list[tuple[str, list[Any]]]
|
2477
|
+
) -> tuple[str, bool]:
|
2383
2478
|
"""
|
2384
2479
|
Trim joins from the start of the join path. The candidates for trim
|
2385
2480
|
are the PathInfos in names_with_path structure that are m2m joins.
|
@@ -2456,7 +2551,7 @@ class Query(BaseExpression):
|
|
2456
2551
|
self.set_select([f.get_col(select_alias) for f in select_fields])
|
2457
2552
|
return trimmed_prefix, contains_louter
|
2458
2553
|
|
2459
|
-
def is_nullable(self, field):
|
2554
|
+
def is_nullable(self, field: Field) -> bool:
|
2460
2555
|
"""Check if the given field should be treated as nullable."""
|
2461
2556
|
# QuerySet does not have knowledge of which connection is going to be
|
2462
2557
|
# used. For the single-database setup we always reference the default
|
@@ -2464,7 +2559,7 @@ class Query(BaseExpression):
|
|
2464
2559
|
return field.allow_null
|
2465
2560
|
|
2466
2561
|
|
2467
|
-
def get_order_dir(field, default="ASC"):
|
2562
|
+
def get_order_dir(field: str, default: str = "ASC") -> tuple[str, str]:
|
2468
2563
|
"""
|
2469
2564
|
Return the field name and direction for an order specification. For
|
2470
2565
|
example, '-foo' is returned as ('foo', 'DESC').
|
@@ -2484,7 +2579,7 @@ class JoinPromoter:
|
|
2484
2579
|
conditions.
|
2485
2580
|
"""
|
2486
2581
|
|
2487
|
-
def __init__(self, connector, num_children, negated):
|
2582
|
+
def __init__(self, connector: str, num_children: int, negated: bool):
|
2488
2583
|
self.connector = connector
|
2489
2584
|
self.negated = negated
|
2490
2585
|
if self.negated:
|
@@ -2499,20 +2594,20 @@ class JoinPromoter:
|
|
2499
2594
|
# inner and/or outer joins.
|
2500
2595
|
self.votes = Counter()
|
2501
2596
|
|
2502
|
-
def __repr__(self):
|
2597
|
+
def __repr__(self) -> str:
|
2503
2598
|
return (
|
2504
2599
|
f"{self.__class__.__qualname__}(connector={self.connector!r}, "
|
2505
2600
|
f"num_children={self.num_children!r}, negated={self.negated!r})"
|
2506
2601
|
)
|
2507
2602
|
|
2508
|
-
def add_votes(self, votes):
|
2603
|
+
def add_votes(self, votes: Any) -> None:
|
2509
2604
|
"""
|
2510
2605
|
Add single vote per item to self.votes. Parameter can be any
|
2511
2606
|
iterable.
|
2512
2607
|
"""
|
2513
2608
|
self.votes.update(votes)
|
2514
2609
|
|
2515
|
-
def update_join_types(self, query):
|
2610
|
+
def update_join_types(self, query: Query) -> set[str]:
|
2516
2611
|
"""
|
2517
2612
|
Change join types so that the generated query is as efficient as
|
2518
2613
|
possible, but still correct. So, change as many joins as possible
|