1"""
2Create SQL statements for QuerySets.
3
4The code in here encapsulates all of the SQL construction so that QuerySets
5themselves do not have to. This module has to know all about the internals of
6models in order to get the information it needs.
7"""
8
9import copy
10import difflib
11import functools
12import sys
13from collections import Counter
14from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
15from collections.abc import Iterator as TypingIterator
16from functools import cached_property
17from itertools import chain, count, product
18from string import ascii_uppercase
19from typing import (
20 TYPE_CHECKING,
21 Any,
22 Literal,
23 NamedTuple,
24 Self,
25 TypeVar,
26 cast,
27 overload,
28)
29
30import psycopg
31from plain.postgres.aggregates import Count
32from plain.postgres.constants import LOOKUP_SEP, OnConflict
33from plain.postgres.db import get_connection
34from plain.postgres.exceptions import FieldDoesNotExist, FieldError
35from plain.postgres.expressions import (
36 BaseExpression,
37 Col,
38 Exists,
39 F,
40 OuterRef,
41 Ref,
42 ResolvableExpression,
43 ResolvedOuterRef,
44 Value,
45)
46from plain.postgres.fields import Field
47from plain.postgres.fields.base import ColumnField
48from plain.postgres.lookups import Lookup
49from plain.postgres.query_utils import (
50 PathInfo,
51 Q,
52 check_rel_lookup_compatibility,
53 refs_expression,
54)
55from plain.postgres.sql.constants import INNER, LOUTER, ORDER_DIR, SINGLE
56from plain.postgres.sql.datastructures import BaseTable, Join, MultiJoin
57from plain.postgres.sql.where import AND, OR, NothingNode, WhereNode
58from plain.utils.regex_helper import _lazy_re_compile
59
60if TYPE_CHECKING:
61 from plain.postgres import Model
62 from plain.postgres.connection import DatabaseConnection
63 from plain.postgres.fields.related import RelatedField
64 from plain.postgres.fields.reverse_related import ForeignObjectRel
65 from plain.postgres.meta import Meta
66 from plain.postgres.sql.compiler import (
67 SQLAggregateCompiler,
68 SQLCompiler,
69 SQLDeleteCompiler,
70 SQLInsertCompiler,
71 SQLUpdateCompiler,
72 SqlWithParams,
73 )
74
75__all__ = [
76 "AggregateQuery",
77 "DeleteQuery",
78 "InsertQuery",
79 "Query",
80 "RawQuery",
81 "UpdateQuery",
82]
83
84
85# Quotation marks ('"`[]), whitespace characters, semicolons, or inline
86# SQL comments are forbidden in column aliases.
87FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|--|/\*|\*/")
88
89# Inspired from
90# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
91EXPLAIN_OPTIONS_PATTERN = _lazy_re_compile(r"[\w\-]+")
92
93
94def get_field_names_from_opts(meta: Meta | None) -> set[str]:
95 if meta is None:
96 return set()
97 return {f.name for f in meta.get_fields()}
98
99
100class JoinInfo(NamedTuple):
101 """Information about a join operation in a query."""
102
103 final_field: Field[Any]
104 targets: tuple[Field[Any], ...]
105 meta: Meta
106 joins: list[str]
107 path: list[PathInfo]
108 transform_function: Callable[[Field[Any], str | None], BaseExpression]
109
110
111class RawQuery:
112 """A single raw SQL query."""
113
114 def __init__(self, sql: str, params: tuple[Any, ...] | dict[str, Any] = ()):
115 self.params = params
116 self.sql = sql
117 self.cursor: Any = None
118
119 # Mirror some properties of a normal query so that
120 # the compiler can be used to process results.
121 self.low_mark, self.high_mark = 0, None # Used for offset/limit
122 self.annotation_select = {}
123
124 def chain(self) -> RawQuery:
125 return self.clone()
126
127 def clone(self) -> RawQuery:
128 return RawQuery(self.sql, params=self.params)
129
130 def get_columns(self) -> list[str]:
131 if self.cursor is None:
132 self._execute_query()
133 return [column_meta[0] for column_meta in self.cursor.description]
134
135 def __iter__(self) -> TypingIterator[Any]:
136 # Always execute a new query for a new iterator.
137 # This could be optimized with a cache at the expense of RAM.
138 self._execute_query()
139 return iter(self.cursor)
140
141 def __repr__(self) -> str:
142 return f"<{self.__class__.__name__}: {self}>"
143
144 @property
145 def params_type(self) -> type[dict | tuple] | None:
146 if self.params is None:
147 return None
148 return dict if isinstance(self.params, Mapping) else tuple
149
150 def __str__(self) -> str:
151 if self.params_type is None:
152 return self.sql
153 return self.sql % self.params_type(self.params)
154
155 def _execute_query(self) -> None:
156 self.cursor = get_connection().cursor()
157 self.cursor.execute(self.sql, self.params)
158
159
160class ExplainInfo(NamedTuple):
161 """Information about an EXPLAIN query."""
162
163 format: str | None
164 options: dict[str, Any]
165
166
167class TransformWrapper:
168 """Wrapper for transform functions that supports the has_transforms attribute.
169
170 This replaces functools.partial for transform functions, allowing proper
171 type checking while supporting dynamic attribute assignment.
172 """
173
174 def __init__(
175 self,
176 func: Callable[..., BaseExpression],
177 **kwargs: Any,
178 ):
179 self._partial = functools.partial(func, **kwargs)
180 self.has_transforms: bool = False
181
182 def __call__(self, field: Field[Any], alias: str | None) -> BaseExpression:
183 return self._partial(field, alias)
184
185
186QueryType = TypeVar("QueryType", bound="Query")
187
188# Row-level locking mode requested via QuerySet.for_update() and friends.
189# dialect.lock_sql() maps each token to its actual SQL keywords.
190LockMode = Literal["update", "no_key_update", "share", "key_share"]
191
192
193class Query(BaseExpression):
194 """A single SQL query."""
195
196 alias_prefix = "T"
197 empty_result_set_value = None
198 subq_aliases = frozenset([alias_prefix])
199
200 base_table_class = BaseTable
201 join_class = Join
202
203 default_cols = True
204 default_ordering = True
205 standard_ordering = True
206
207 filter_is_sticky = False
208 subquery = False
209
210 # SQL-related attributes.
211 # Select and related select clauses are expressions to use in the SELECT
212 # clause of the query. The select is used for cases where we want to set up
213 # the select clause to contain other than default fields (values(),
214 # subqueries...). Note that annotations go to annotations dictionary.
215 select: tuple[BaseExpression, ...] = ()
216 # The group_by attribute can have one of the following forms:
217 # - None: no group by at all in the query
218 # - A tuple of expressions: group by (at least) those expressions.
219 # String refs are also allowed for now.
220 # - True: group by all select fields of the model
221 # See compiler.get_group_by() for details.
222 group_by = None
223 # Holds field-name strings and order expressions; the compiler duck-types
224 # each entry, so the element type is intentionally loose.
225 order_by: tuple[Any, ...] = ()
226 low_mark = 0 # Used for offset/limit.
227 high_mark = None # Used for offset/limit.
228 distinct = False
229 distinct_fields: tuple[str, ...] = ()
230 lock_mode: LockMode | None = None # See LockMode.
231 lock_nowait = False
232 lock_skip_locked = False
233 lock_of: tuple[str, ...] = ()
234 joined_relations: bool | dict[str, Any] = False
235 has_select_fields = False
236 # Arbitrary limit for joined_relations to prevents infinite recursion.
237 max_depth = 5
238 # Holds the selects defined by a call to values() or values_list()
239 # excluding annotation_select.
240 values_select: tuple[str, ...] = ()
241
242 # SQL annotation-related attributes.
243 annotation_select_mask = None
244 _annotation_select_cache = None
245
246 # A tuple that is a set of model field names and either True, if these are
247 # the fields to defer, or False if these are the only fields to load.
248 deferred_loading = (frozenset(), True)
249
250 explain_info = None
251
252 def __init__(self, model: type[Model] | None, alias_cols: bool = True):
253 self.model = model
254 self.alias_refcount = {}
255 # alias_map is the most important data structure regarding joins.
256 # It's used for recording which joins exist in the query and what
257 # types they are. The key is the alias of the joined table (possibly
258 # the table name) and the value is a Join-like object (see
259 # sql.datastructures.Join for more information).
260 self.alias_map = {}
261 # Whether to provide alias to columns during reference resolving.
262 self.alias_cols = alias_cols
263 # Sometimes the query contains references to aliases in outer queries (as
264 # a result of split_exclude). Correct alias quoting needs to know these
265 # aliases too.
266 # Map external tables to whether they are aliased.
267 self.external_aliases = {}
268 self.table_map = {} # Maps table names to list of aliases.
269 self.used_aliases = set()
270
271 self.where = WhereNode()
272 # Maps alias -> Annotation Expression.
273 self.annotations = {}
274
275 @property
276 def output_field(self) -> Field | None:
277 if len(self.select) == 1:
278 select = self.select[0]
279 return getattr(select, "target", None) or select.field
280 elif len(self.annotation_select) == 1:
281 return next(iter(self.annotation_select.values())).output_field
282
283 @cached_property
284 def base_table(self) -> str | None:
285 for alias in self.alias_map:
286 return alias
287
288 def __str__(self) -> str:
289 """
290 Return the query as a string of SQL with the parameter values
291 substituted in (use sql_with_params() to see the unsubstituted string).
292
293 Parameter values won't necessarily be quoted correctly, since that is
294 done by the database interface at execution time.
295 """
296 sql, params = self.sql_with_params()
297 return sql % params
298
299 def sql_with_params(self) -> SqlWithParams:
300 """
301 Return the query as an SQL string and the parameters that will be
302 substituted into the query.
303 """
304 return self.get_compiler().as_sql()
305
306 def __deepcopy__(self, memo: dict[int, Any]) -> Self:
307 """Limit the amount of work when a Query is deepcopied."""
308 result = self.clone()
309 memo[id(self)] = result
310 return result
311
312 def get_compiler(self, *, elide_empty: bool = True) -> SQLCompiler:
313 """Return a compiler instance for this query."""
314 # Import compilers here to avoid circular imports at module load time
315 from plain.postgres.sql.compiler import SQLCompiler as Compiler
316
317 return Compiler(self, get_connection(), elide_empty)
318
319 def clone(self) -> Self:
320 """
321 Return a copy of the current Query. A lightweight alternative to
322 deepcopy().
323 """
324 obj = object.__new__(self.__class__)
325 # Copy references to everything.
326 obj.__dict__ = self.__dict__.copy()
327 # Clone attributes that can't use shallow copy.
328 obj.alias_refcount = self.alias_refcount.copy()
329 obj.alias_map = self.alias_map.copy()
330 obj.external_aliases = self.external_aliases.copy()
331 obj.table_map = self.table_map.copy()
332 obj.where = self.where.clone()
333 obj.annotations = self.annotations.copy()
334 if self.annotation_select_mask is not None:
335 obj.annotation_select_mask = self.annotation_select_mask.copy()
336 # _annotation_select_cache cannot be copied, as doing so breaks the
337 # (necessary) state in which both annotations and
338 # _annotation_select_cache point to the same underlying objects.
339 # It will get re-populated in the cloned queryset the next time it's
340 # used.
341 obj._annotation_select_cache = None
342 if self.joined_relations is not False:
343 # Use deepcopy because joined_relations stores fields in nested
344 # dicts.
345 obj.joined_relations = copy.deepcopy(obj.joined_relations)
346 if "subq_aliases" in self.__dict__:
347 obj.subq_aliases = self.subq_aliases.copy()
348 obj.used_aliases = self.used_aliases.copy()
349 # Clear the cached_property, if it exists.
350 obj.__dict__.pop("base_table", None)
351 return obj
352
353 @overload
354 def chain(self, klass: None = None) -> Self: ...
355
356 @overload
357 def chain(self, klass: type[QueryType]) -> QueryType: ...
358
359 def chain(self, klass: type[Query] | None = None) -> Query:
360 """
361 Return a copy of the current Query that's ready for another operation.
362 The klass argument changes the type of the Query, e.g. UpdateQuery.
363 """
364 obj = self.clone()
365 if klass and obj.__class__ != klass:
366 obj.__class__ = klass
367 if not obj.filter_is_sticky:
368 obj.used_aliases = set()
369 obj.filter_is_sticky = False
370 if hasattr(obj, "_setup_query"):
371 obj._setup_query() # ty: ignore[call-non-callable]
372 return obj
373
374 def relabeled_clone(self, change_map: dict[str, str]) -> Self:
375 clone = self.clone()
376 clone.change_aliases(change_map)
377 return clone
378
379 def _get_col(self, target: Any, field: Field, alias: str | None) -> Col:
380 if not self.alias_cols:
381 alias = None
382 return target.get_col(alias, field)
383
384 def get_aggregation(self, aggregate_exprs: dict[str, Any]) -> dict[str, Any]:
385 """
386 Return the dictionary with the values of the existing aggregations.
387 """
388 if not aggregate_exprs:
389 return {}
390 aggregates = {}
391 for alias, aggregate_expr in aggregate_exprs.items():
392 self.check_alias(alias)
393 aggregate = aggregate_expr.resolve_expression(
394 self, allow_joins=True, reuse=None, summarize=True
395 )
396 if not aggregate.contains_aggregate:
397 raise TypeError(f"{alias} is not an aggregate expression")
398 aggregates[alias] = aggregate
399 # Existing usage of aggregation can be determined by the presence of
400 # selected aggregates but also by filters against aliased aggregates.
401 _, having, qualify = self.where.split_having_qualify()
402 has_existing_aggregation = (
403 any(
404 getattr(annotation, "contains_aggregate", True)
405 for annotation in self.annotations.values()
406 )
407 or having
408 )
409 # Decide if we need to use a subquery.
410 #
411 # Existing aggregations would cause incorrect results as
412 # get_aggregation() must produce just one result and thus must not use
413 # GROUP BY.
414 #
415 # If the query has limit or distinct, or uses set operations, then
416 # those operations must be done in a subquery so that the query
417 # aggregates on the limit and/or distinct results instead of applying
418 # the distinct and limit after the aggregation.
419 if (
420 isinstance(self.group_by, tuple)
421 or self.is_sliced
422 or has_existing_aggregation
423 or qualify
424 or self.distinct
425 ):
426 inner_query = self.clone()
427 inner_query.subquery = True
428 outer_query = AggregateQuery(self.model, inner_query)
429 inner_query.lock_mode = None
430 inner_query.joined_relations = False
431 inner_query.set_annotation_mask(self.annotation_select)
432 # Queries with distinct_fields need ordering and when a limit is
433 # applied we must take the slice from the ordered query. Otherwise
434 # no need for ordering.
435 inner_query.clear_ordering(force=False)
436 if not inner_query.distinct:
437 # If the inner query uses default select and it has some
438 # aggregate annotations, then we must make sure the inner
439 # query is grouped by the main model's primary key. However,
440 # clearing the select clause can alter results if distinct is
441 # used.
442 if inner_query.default_cols and has_existing_aggregation:
443 assert self.model is not None, "Aggregation requires a model"
444 inner_query.group_by = (
445 self.model._model_meta.get_forward_field("id").get_col(
446 inner_query.get_initial_alias()
447 ),
448 )
449 inner_query.default_cols = False
450 if not qualify:
451 # Mask existing annotations that are not referenced by
452 # aggregates to be pushed to the outer query unless
453 # filtering against window functions is involved as it
454 # requires complex realising.
455 annotation_mask = set()
456 for aggregate in aggregates.values():
457 annotation_mask |= aggregate.get_refs()
458 inner_query.set_annotation_mask(annotation_mask)
459
460 # Add aggregates to the outer AggregateQuery. This requires making
461 # sure all columns referenced by the aggregates are selected in the
462 # inner query. It is achieved by retrieving all column references
463 # by the aggregates, explicitly selecting them in the inner query,
464 # and making sure the aggregates are repointed to them.
465 col_refs = {}
466 for alias, aggregate in aggregates.items():
467 replacements = {}
468 for col in self._gen_cols([aggregate], resolve_refs=False):
469 if not (col_ref := col_refs.get(col)):
470 index = len(col_refs) + 1
471 col_alias = f"__col{index}"
472 col_ref = Ref(col_alias, col)
473 col_refs[col] = col_ref
474 inner_query.annotations[col_alias] = col
475 inner_query.append_annotation_mask([col_alias])
476 replacements[col] = col_ref
477 outer_query.annotations[alias] = aggregate.replace_expressions(
478 replacements
479 )
480 if (
481 inner_query.select == ()
482 and not inner_query.default_cols
483 and not inner_query.annotation_select_mask
484 ):
485 # In case of Model.objects[0:3].count(), there would be no
486 # field selected in the inner query, yet we must use a subquery.
487 # So, make sure at least one field is selected.
488 assert self.model is not None, "Count with slicing requires a model"
489 inner_query.select = (
490 self.model._model_meta.get_forward_field("id").get_col(
491 inner_query.get_initial_alias()
492 ),
493 )
494 else:
495 outer_query = self
496 self.select = ()
497 self.default_cols = False
498 if self.annotations:
499 # Inline reference to existing annotations and mask them as
500 # they are unnecessary given only the summarized aggregations
501 # are requested.
502 replacements = {
503 Ref(alias, annotation): annotation
504 for alias, annotation in self.annotations.items()
505 }
506 self.annotations = {
507 alias: aggregate.replace_expressions(replacements)
508 for alias, aggregate in aggregates.items()
509 }
510 else:
511 self.annotations = aggregates
512 self.set_annotation_mask(aggregates)
513
514 empty_set_result = [
515 expression.empty_result_set_value
516 for expression in outer_query.annotation_select.values()
517 ]
518 elide_empty = not any(result is NotImplemented for result in empty_set_result)
519 outer_query.clear_ordering(force=True)
520 outer_query.clear_limits()
521 outer_query.lock_mode = None
522 outer_query.joined_relations = False
523 compiler = outer_query.get_compiler(elide_empty=elide_empty)
524 result = compiler.execute_sql(SINGLE)
525 if result is None:
526 result = empty_set_result
527 else:
528 from plain.postgres.sql.compiler import apply_converters, get_converters
529
530 converters = get_converters(
531 outer_query.annotation_select.values(), compiler.connection
532 )
533 result = next(apply_converters((result,), converters, compiler.connection))
534
535 return dict(zip(outer_query.annotation_select, result))
536
537 def get_count(self) -> int:
538 """
539 Perform a COUNT() query using the current filter constraints.
540 """
541 obj = self.clone()
542 return obj.get_aggregation({"__count": Count("*")})["__count"]
543
544 def has_filters(self) -> bool:
545 return bool(self.where)
546
547 def exists(self, limit: bool = True) -> Self:
548 q = self.clone()
549 if not (q.distinct and q.is_sliced):
550 if q.group_by is True:
551 assert self.model is not None, "GROUP BY requires a model"
552 q.add_fields((f.name for f in self.model._model_meta.fields), False)
553 # Disable GROUP BY aliases to avoid orphaning references to the
554 # SELECT clause which is about to be cleared.
555 q.set_group_by(allow_aliases=False)
556 q.clear_select_clause()
557 q.clear_ordering(force=True)
558 if limit:
559 q.set_limits(high=1)
560 q.add_annotation(Value(1), "a")
561 return q
562
563 def has_results(self) -> bool:
564 q = self.exists()
565 compiler = q.get_compiler()
566 return compiler.has_results()
567
568 def explain(self, format: str | None = None, **options: Any) -> str:
569 q = self.clone()
570 for option_name in options:
571 if (
572 not EXPLAIN_OPTIONS_PATTERN.fullmatch(option_name)
573 or "--" in option_name
574 ):
575 raise ValueError(f"Invalid option name: {option_name!r}.")
576 q.explain_info = ExplainInfo(format, options)
577 compiler = q.get_compiler()
578 return "\n".join(compiler.explain_query())
579
580 def combine(self, rhs: Query, connector: str) -> None:
581 """
582 Merge the 'rhs' query into the current one (with any 'rhs' effects
583 being applied *after* (that is, "to the right of") anything in the
584 current query. 'rhs' is not modified during a call to this function.
585
586 The 'connector' parameter describes how to connect filters from the
587 'rhs' query.
588 """
589 if self.model != rhs.model:
590 raise TypeError("Cannot combine queries on two different base models.")
591 if self.is_sliced:
592 raise TypeError("Cannot combine queries once a slice has been taken.")
593 if self.distinct != rhs.distinct:
594 raise TypeError("Cannot combine a unique query with a non-unique query.")
595 if self.distinct_fields != rhs.distinct_fields:
596 raise TypeError("Cannot combine queries with different distinct fields.")
597
598 # If lhs and rhs shares the same alias prefix, it is possible to have
599 # conflicting alias changes like T4 -> T5, T5 -> T6, which might end up
600 # as T4 -> T6 while combining two querysets. To prevent this, change an
601 # alias prefix of the rhs and update current aliases accordingly,
602 # except if the alias is the base table since it must be present in the
603 # query on both sides.
604 initial_alias = self.get_initial_alias()
605 assert initial_alias is not None
606 rhs.bump_prefix(self, exclude={initial_alias})
607
608 # Work out how to relabel the rhs aliases, if necessary.
609 change_map = {}
610 conjunction = connector == AND
611
612 # Determine which existing joins can be reused. When combining the
613 # query with AND we must recreate all joins for m2m filters. When
614 # combining with OR we can reuse joins. The reason is that in AND
615 # case a single row can't fulfill a condition like:
616 # revrel__col=1 & revrel__col=2
617 # But, there might be two different related rows matching this
618 # condition. In OR case a single True is enough, so single row is
619 # enough, too.
620 #
621 # Note that we will be creating duplicate joins for non-m2m joins in
622 # the AND case. The results will be correct but this creates too many
623 # joins. This is something that could be fixed later on.
624 reuse = set() if conjunction else set(self.alias_map)
625 joinpromoter = JoinPromoter(connector, 2, False)
626 joinpromoter.add_votes(
627 j for j in self.alias_map if self.alias_map[j].join_type == INNER
628 )
629 rhs_votes = set()
630 # Now, add the joins from rhs query into the new query (skipping base
631 # table).
632 rhs_tables = list(rhs.alias_map)[1:]
633 for alias in rhs_tables:
634 join = rhs.alias_map[alias]
635 # If the left side of the join was already relabeled, use the
636 # updated alias.
637 join = join.relabeled_clone(change_map)
638 new_alias = self.join(join, reuse=reuse)
639 if join.join_type == INNER:
640 rhs_votes.add(new_alias)
641 # We can't reuse the same join again in the query. If we have two
642 # distinct joins for the same connection in rhs query, then the
643 # combined query must have two joins, too.
644 reuse.discard(new_alias)
645 if alias != new_alias:
646 change_map[alias] = new_alias
647 if not rhs.alias_refcount[alias]:
648 # The alias was unused in the rhs query. Unref it so that it
649 # will be unused in the new query, too. We have to add and
650 # unref the alias so that join promotion has information of
651 # the join type for the unused alias.
652 self.unref_alias(new_alias)
653 joinpromoter.add_votes(rhs_votes)
654 joinpromoter.update_join_types(self)
655
656 # Combine subqueries aliases to ensure aliases relabelling properly
657 # handle subqueries when combining where and select clauses.
658 self.subq_aliases |= rhs.subq_aliases
659
660 # Now relabel a copy of the rhs where-clause and add it to the current
661 # one.
662 w = rhs.where.clone()
663 w.relabel_aliases(change_map)
664 self.where.add(w, connector)
665
666 # Selection columns are those provided by 'rhs'.
667 if rhs.select:
668 self.set_select([col.relabeled_clone(change_map) for col in rhs.select])
669 else:
670 self.select = ()
671
672 # Ordering uses the 'rhs' ordering, unless it has none, in which case
673 # the current ordering is used.
674 self.order_by = rhs.order_by or self.order_by
675
676 def _get_defer_select_mask(
677 self,
678 meta: Meta,
679 mask: dict[str, Any],
680 select_mask: dict[Any, Any] | None = None,
681 ) -> dict[Any, Any]:
682 from plain.postgres.fields.related import RelatedField
683
684 if select_mask is None:
685 select_mask = {}
686 select_mask[meta.get_forward_field("id")] = {}
687 # All concrete fields that are not part of the defer mask must be
688 # loaded. If a relational field is encountered it gets added to the
689 # mask for it be considered if `joined_relations` and the cycle continues
690 # by recursively calling this function.
691 for field in meta.fields:
692 field_mask = mask.pop(field.name, None)
693 if field_mask is None:
694 select_mask.setdefault(field, {})
695 elif field_mask:
696 if not isinstance(field, RelatedField):
697 raise FieldError(next(iter(field_mask)))
698 field_select_mask = select_mask.setdefault(field, {})
699 related_model = field.remote_field.model
700 self._get_defer_select_mask(
701 related_model._model_meta, field_mask, field_select_mask
702 )
703 # Remaining defer entries must be references to reverse relationships.
704 # The following code is expected to raise FieldError if it encounters
705 # a malformed defer entry.
706 for field_name, field_mask in mask.items():
707 field = meta.get_reverse_relation(field_name).field
708 field_select_mask = select_mask.setdefault(field, {})
709 related_model = field.model
710 self._get_defer_select_mask(
711 related_model._model_meta, field_mask, field_select_mask
712 )
713 return select_mask
714
715 def _get_only_select_mask(
716 self,
717 meta: Meta,
718 mask: dict[str, Any],
719 select_mask: dict[Any, Any] | None = None,
720 ) -> dict[Any, Any]:
721 from plain.postgres.fields.related import RelatedField
722
723 if select_mask is None:
724 select_mask = {}
725 select_mask[meta.get_forward_field("id")] = {}
726 # Only include fields mentioned in the mask.
727 for field_name, field_mask in mask.items():
728 field = meta.get_field(field_name)
729 field_select_mask = select_mask.setdefault(field, {})
730 if field_mask:
731 if not isinstance(field, RelatedField):
732 raise FieldError(next(iter(field_mask)))
733 related_model = field.remote_field.model
734 self._get_only_select_mask(
735 related_model._model_meta, field_mask, field_select_mask
736 )
737 return select_mask
738
739 def get_select_mask(self) -> dict[Any, Any]:
740 """
741 Convert the self.deferred_loading data structure to an alternate data
742 structure, describing the field that *will* be loaded. This is used to
743 compute the columns to select from the database and also by the
744 QuerySet class to work out which fields are being initialized on each
745 model. Models that have all their fields included aren't mentioned in
746 the result, only those that have field restrictions in place.
747 """
748 field_names, defer = self.deferred_loading
749 if not field_names:
750 return {}
751 mask = {}
752 for field_name in field_names:
753 part_mask = mask
754 for part in field_name.split(LOOKUP_SEP):
755 part_mask = part_mask.setdefault(part, {})
756 assert self.model is not None, "Deferred/only field loading requires a model"
757 meta = self.model._model_meta
758 if defer:
759 return self._get_defer_select_mask(meta, mask)
760 return self._get_only_select_mask(meta, mask)
761
762 def table_alias(self, table_name: str, create: bool = False) -> tuple[str, bool]:
763 """
764 Return a table alias for the given table_name and whether this is a
765 new alias or not.
766
767 If 'create' is true, a new alias is always created. Otherwise, the
768 most recently created alias for the table (if one exists) is reused.
769 """
770 alias_list = self.table_map.get(table_name)
771 if not create and alias_list:
772 alias = alias_list[0]
773 self.alias_refcount[alias] += 1
774 return alias, False
775
776 # Create a new alias for this table.
777 if alias_list:
778 alias = "%s%d" % (self.alias_prefix, len(self.alias_map) + 1) # noqa: UP031
779 alias_list.append(alias)
780 else:
781 # The first occurrence of a table uses the table name directly.
782 alias = table_name
783 self.table_map[table_name] = [alias]
784 self.alias_refcount[alias] = 1
785 return alias, True
786
787 def ref_alias(self, alias: str) -> None:
788 """Increases the reference count for this alias."""
789 self.alias_refcount[alias] += 1
790
791 def unref_alias(self, alias: str, amount: int = 1) -> None:
792 """Decreases the reference count for this alias."""
793 self.alias_refcount[alias] -= amount
794
795 def promote_joins(self, aliases: set[str] | list[str]) -> None:
796 """
797 Promote recursively the join type of given aliases and its children to
798 an outer join. If 'unconditional' is False, only promote the join if
799 it is nullable or the parent join is an outer join.
800
801 The children promotion is done to avoid join chains that contain a LOUTER
802 b INNER c. So, if we have currently a INNER b INNER c and a->b is promoted,
803 then we must also promote b->c automatically, or otherwise the promotion
804 of a->b doesn't actually change anything in the query results.
805 """
806 aliases = list(aliases)
807 while aliases:
808 alias = aliases.pop(0)
809 if self.alias_map[alias].join_type is None:
810 # This is the base table (first FROM entry) - this table
811 # isn't really joined at all in the query, so we should not
812 # alter its join type.
813 continue
814 # Only the first alias (skipped above) should have None join_type
815 assert self.alias_map[alias].join_type is not None
816 parent_alias = self.alias_map[alias].parent_alias
817 parent_louter = (
818 parent_alias and self.alias_map[parent_alias].join_type == LOUTER
819 )
820 already_louter = self.alias_map[alias].join_type == LOUTER
821 if (self.alias_map[alias].nullable or parent_louter) and not already_louter:
822 self.alias_map[alias] = self.alias_map[alias].promote()
823 # Join type of 'alias' changed, so re-examine all aliases that
824 # refer to this one.
825 aliases.extend(
826 join
827 for join in self.alias_map
828 if self.alias_map[join].parent_alias == alias
829 and join not in aliases
830 )
831
832 def demote_joins(self, aliases: set[str] | list[str]) -> None:
833 """
834 Change join type from LOUTER to INNER for all joins in aliases.
835
836 Similarly to promote_joins(), this method must ensure no join chains
837 containing first an outer, then an inner join are generated. If we
838 are demoting b->c join in chain a LOUTER b LOUTER c then we must
839 demote a->b automatically, or otherwise the demotion of b->c doesn't
840 actually change anything in the query results. .
841 """
842 aliases = list(aliases)
843 while aliases:
844 alias = aliases.pop(0)
845 if self.alias_map[alias].join_type == LOUTER:
846 self.alias_map[alias] = self.alias_map[alias].demote()
847 parent_alias = self.alias_map[alias].parent_alias
848 if self.alias_map[parent_alias].join_type == INNER:
849 aliases.append(parent_alias)
850
851 def reset_refcounts(self, to_counts: dict[str, int]) -> None:
852 """
853 Reset reference counts for aliases so that they match the value passed
854 in `to_counts`.
855 """
856 for alias, cur_refcount in self.alias_refcount.copy().items():
857 unref_amount = cur_refcount - to_counts.get(alias, 0)
858 self.unref_alias(alias, unref_amount)
859
860 def change_aliases(self, change_map: dict[str, str]) -> None:
861 """
862 Change the aliases in change_map (which maps old-alias -> new-alias),
863 relabelling any references to them in select columns and the where
864 clause.
865 """
866 # If keys and values of change_map were to intersect, an alias might be
867 # updated twice (e.g. T4 -> T5, T5 -> T6, so also T4 -> T6) depending
868 # on their order in change_map.
869 assert set(change_map).isdisjoint(change_map.values())
870
871 # 1. Update references in "select" (normal columns plus aliases),
872 # "group by" and "where".
873 self.where.relabel_aliases(change_map)
874 if isinstance(self.group_by, tuple):
875 self.group_by = tuple(
876 [col.relabeled_clone(change_map) for col in self.group_by]
877 )
878 self.select = tuple([col.relabeled_clone(change_map) for col in self.select])
879 self.annotations = self.annotations and {
880 key: col.relabeled_clone(change_map)
881 for key, col in self.annotations.items()
882 }
883
884 # 2. Rename the alias in the internal table/alias datastructures.
885 for old_alias, new_alias in change_map.items():
886 if old_alias not in self.alias_map:
887 continue
888 alias_data = self.alias_map[old_alias].relabeled_clone(change_map)
889 self.alias_map[new_alias] = alias_data
890 self.alias_refcount[new_alias] = self.alias_refcount[old_alias]
891 del self.alias_refcount[old_alias]
892 del self.alias_map[old_alias]
893
894 table_aliases = self.table_map[alias_data.table_name]
895 for pos, alias in enumerate(table_aliases):
896 if alias == old_alias:
897 table_aliases[pos] = new_alias
898 break
899 self.external_aliases = {
900 # Table is aliased or it's being changed and thus is aliased.
901 change_map.get(alias, alias): (aliased or alias in change_map)
902 for alias, aliased in self.external_aliases.items()
903 }
904
905 def bump_prefix(
906 self, other_query: Query, exclude: set[str] | dict[str, str] | None = None
907 ) -> None:
908 """
909 Change the alias prefix to the next letter in the alphabet in a way
910 that the other query's aliases and this query's aliases will not
911 conflict. Even tables that previously had no alias will get an alias
912 after this call. To prevent changing aliases use the exclude parameter.
913 """
914
915 def prefix_gen() -> TypingIterator[str]:
916 """
917 Generate a sequence of characters in alphabetical order:
918 -> 'A', 'B', 'C', ...
919
920 When the alphabet is finished, the sequence will continue with the
921 Cartesian product:
922 -> 'AA', 'AB', 'AC', ...
923 """
924 alphabet = ascii_uppercase
925 prefix = chr(ord(self.alias_prefix) + 1)
926 yield prefix
927 for n in count(1):
928 seq = alphabet[alphabet.index(prefix) :] if prefix else alphabet
929 for s in product(seq, repeat=n):
930 yield "".join(s)
931 prefix = None
932
933 if self.alias_prefix != other_query.alias_prefix:
934 # No clashes between self and outer query should be possible.
935 return
936
937 # Explicitly avoid infinite loop. The constant divider is based on how
938 # much depth recursive subquery references add to the stack. This value
939 # might need to be adjusted when adding or removing function calls from
940 # the code path in charge of performing these operations.
941 local_recursion_limit = sys.getrecursionlimit() // 16
942 for pos, prefix in enumerate(prefix_gen()):
943 if prefix not in self.subq_aliases:
944 self.alias_prefix = prefix
945 break
946 if pos > local_recursion_limit:
947 raise RecursionError(
948 "Maximum recursion depth exceeded: too many subqueries."
949 )
950 self.subq_aliases = self.subq_aliases.union([self.alias_prefix])
951 other_query.subq_aliases = other_query.subq_aliases.union(self.subq_aliases)
952 if exclude is None:
953 exclude = {}
954 self.change_aliases(
955 {
956 alias: "%s%d" % (self.alias_prefix, pos) # noqa: UP031
957 for pos, alias in enumerate(self.alias_map)
958 if alias not in exclude
959 }
960 )
961
962 def get_initial_alias(self) -> str | None:
963 """
964 Return the first alias for this query, after increasing its reference
965 count.
966 """
967 if self.alias_map:
968 alias = self.base_table
969 self.ref_alias(alias) # ty: ignore[invalid-argument-type]
970 elif self.model:
971 alias = self.join(
972 self.base_table_class(self.model.model_options.db_table, None) # ty: ignore[invalid-argument-type]
973 )
974 else:
975 alias = None
976 return alias
977
978 def count_active_tables(self) -> int:
979 """
980 Return the number of tables in this query with a non-zero reference
981 count. After execution, the reference counts are zeroed, so tables
982 added in compiler will not be seen by this method.
983 """
984 return len([1 for count in self.alias_refcount.values() if count])
985
986 def join(
987 self,
988 join: BaseTable | Join,
989 reuse: set[str] | None = None,
990 ) -> str:
991 """
992 Return an alias for the 'join', either reusing an existing alias for
993 that join or creating a new one. 'join' is either a base_table_class or
994 join_class.
995
996 The 'reuse' parameter can be either None which means all joins are
997 reusable, or it can be a set containing the aliases that can be reused.
998
999 A join is always created as LOUTER if the lhs alias is LOUTER to make
1000 sure chains like t1 LOUTER t2 INNER t3 aren't generated. All new
1001 joins are created as LOUTER if the join is nullable.
1002 """
1003 reuse_aliases = [
1004 a
1005 for a, j in self.alias_map.items()
1006 if (reuse is None or a in reuse) and j == join
1007 ]
1008 if reuse_aliases:
1009 if join.table_alias in reuse_aliases:
1010 reuse_alias = join.table_alias
1011 else:
1012 # Reuse the most recent alias of the joined table
1013 # (a many-to-many relation may be joined multiple times).
1014 reuse_alias = reuse_aliases[-1]
1015 self.ref_alias(reuse_alias)
1016 return reuse_alias
1017
1018 # No reuse is possible, so we need a new alias.
1019 alias, _ = self.table_alias(join.table_name, create=True)
1020 if isinstance(join, Join):
1021 if self.alias_map[join.parent_alias].join_type == LOUTER or join.nullable:
1022 join_type = LOUTER
1023 else:
1024 join_type = INNER
1025 join.join_type = join_type
1026 join.table_alias = alias
1027 self.alias_map[alias] = join
1028 return alias
1029
1030 def check_alias(self, alias: str) -> None:
1031 if FORBIDDEN_ALIAS_PATTERN.search(alias):
1032 raise ValueError(
1033 "Column aliases cannot contain whitespace characters, quotation marks, "
1034 "semicolons, or SQL comments."
1035 )
1036
1037 def add_annotation(
1038 self, annotation: BaseExpression, alias: str, select: bool = True
1039 ) -> None:
1040 """Add a single annotation expression to the Query."""
1041 self.check_alias(alias)
1042 annotation = annotation.resolve_expression(self, allow_joins=True, reuse=None)
1043 if select:
1044 self.append_annotation_mask([alias])
1045 else:
1046 self.set_annotation_mask(set(self.annotation_select).difference({alias}))
1047 self.annotations[alias] = annotation
1048
1049 def resolve_expression(
1050 self,
1051 query: Any = None,
1052 allow_joins: bool = True,
1053 reuse: Any = None,
1054 summarize: bool = False,
1055 for_save: bool = False,
1056 ) -> Self:
1057 clone = self.clone()
1058 # Subqueries need to use a different set of aliases than the outer query.
1059 clone.bump_prefix(query)
1060 clone.subquery = True
1061 clone.where.resolve_expression(query, allow_joins, reuse, summarize, for_save)
1062 for key, value in clone.annotations.items():
1063 resolved = value.resolve_expression(
1064 query, allow_joins, reuse, summarize, for_save
1065 )
1066 if hasattr(resolved, "external_aliases"):
1067 resolved.external_aliases.update(clone.external_aliases)
1068 clone.annotations[key] = resolved
1069 # Outer query's aliases are considered external.
1070 for alias, table in query.alias_map.items():
1071 clone.external_aliases[alias] = (
1072 isinstance(table, Join)
1073 and table.join_field.related_model.model_options.db_table != alias
1074 ) or (
1075 isinstance(table, BaseTable) and table.table_name != table.table_alias
1076 )
1077 return clone
1078
1079 def get_external_cols(self) -> list[Col]:
1080 exprs = chain(self.annotations.values(), self.where.children)
1081 return [
1082 col
1083 for col in self._gen_cols(exprs, include_external=True)
1084 if col.alias in self.external_aliases
1085 ]
1086
1087 def get_group_by_cols(
1088 self, wrapper: BaseExpression | None = None
1089 ) -> list[BaseExpression]:
1090 # If wrapper is referenced by an alias for an explicit GROUP BY through
1091 # values() a reference to this expression and not the self must be
1092 # returned to ensure external column references are not grouped against
1093 # as well.
1094 external_cols = self.get_external_cols()
1095 if any(col.possibly_multivalued for col in external_cols):
1096 return [wrapper or self]
1097 # Cast needed because list is invariant: list[Col] is not list[BaseExpression]
1098 return cast(list[BaseExpression], external_cols)
1099
1100 def as_sql(
1101 self, compiler: SQLCompiler, connection: DatabaseConnection
1102 ) -> SqlWithParams:
1103 sql, params = self.get_compiler().as_sql()
1104 if self.subquery:
1105 sql = f"({sql})"
1106 return sql, params
1107
1108 def resolve_lookup_value(
1109 self, value: Any, can_reuse: set[str] | None, allow_joins: bool
1110 ) -> Any:
1111 if isinstance(value, ResolvableExpression):
1112 value = value.resolve_expression(
1113 self,
1114 reuse=can_reuse,
1115 allow_joins=allow_joins,
1116 )
1117 elif isinstance(value, list | tuple):
1118 # The items of the iterable may be expressions and therefore need
1119 # to be resolved independently.
1120 values = (
1121 self.resolve_lookup_value(sub_value, can_reuse, allow_joins)
1122 for sub_value in value
1123 )
1124 type_ = type(value)
1125 if hasattr(type_, "_make"): # namedtuple
1126 return type_(*values)
1127 return type_(values)
1128 return value
1129
1130 def solve_lookup_type(
1131 self, lookup: str, summarize: bool = False
1132 ) -> tuple[
1133 list[str] | tuple[str, ...], tuple[str, ...], BaseExpression | Literal[False]
1134 ]:
1135 """
1136 Solve the lookup type from the lookup (e.g.: 'foobar__id__icontains').
1137 """
1138 lookup_splitted = lookup.split(LOOKUP_SEP)
1139 if self.annotations:
1140 annotation, expression_lookups = refs_expression(
1141 lookup_splitted, self.annotations
1142 )
1143 if annotation:
1144 expression = self.annotations[annotation]
1145 if summarize:
1146 expression = Ref(annotation, expression)
1147 return expression_lookups, (), expression
1148 assert self.model is not None, "Field lookups require a model"
1149 meta = self.model._model_meta
1150 _, _field, _, lookup_parts = self.names_to_path(lookup_splitted, meta)
1151 field_parts = lookup_splitted[0 : len(lookup_splitted) - len(lookup_parts)]
1152 if len(lookup_parts) > 1 and not field_parts:
1153 raise FieldError(
1154 f'Invalid lookup "{lookup}" for model {meta.model.__name__}".'
1155 )
1156 return lookup_parts, tuple(field_parts), False
1157
1158 def check_query_object_type(
1159 self, value: Any, meta: Meta, field: Field | ForeignObjectRel
1160 ) -> None:
1161 """
1162 Check whether the object passed while querying is of the correct type.
1163 If not, raise a ValueError specifying the wrong object.
1164 """
1165 from plain.postgres import Model
1166
1167 if isinstance(value, Model) and not check_rel_lookup_compatibility(
1168 value._model_meta.model, meta, field
1169 ):
1170 raise ValueError(
1171 f'Cannot query "{value}": Must be "{meta.model.model_options.object_name}" instance.'
1172 )
1173
1174 def check_related_objects(
1175 self, field: RelatedField | ForeignObjectRel, value: Any, meta: Meta
1176 ) -> None:
1177 """Check the type of object passed to query relations."""
1178 from plain.postgres import Model
1179
1180 # Check that the field and the queryset use the same model in a
1181 # query like .filter(author=Author.query.all()). For example, the
1182 # meta would be Author's (from the author field) and value.model
1183 # would be Author.query.all() queryset's .model (Author also).
1184 # The field is the related field on the lhs side.
1185 if (
1186 isinstance(value, Query)
1187 and not value.has_select_fields
1188 and not check_rel_lookup_compatibility(value.model, meta, field)
1189 ):
1190 raise ValueError(
1191 f'Cannot use QuerySet for "{value.model.model_options.object_name}": Use a QuerySet for "{meta.model.model_options.object_name}".'
1192 )
1193 elif isinstance(value, Model):
1194 self.check_query_object_type(value, meta, field)
1195 elif isinstance(value, Iterable):
1196 for v in value:
1197 self.check_query_object_type(v, meta, field)
1198
1199 def check_filterable(self, expression: Any) -> None:
1200 """Raise an error if expression cannot be used in a WHERE clause."""
1201 if isinstance(expression, ResolvableExpression) and not getattr(
1202 expression, "filterable", True
1203 ):
1204 raise psycopg.NotSupportedError(
1205 expression.__class__.__name__ + " is disallowed in the filter clause."
1206 )
1207 if hasattr(expression, "get_source_expressions"):
1208 for expr in expression.get_source_expressions():
1209 self.check_filterable(expr)
1210
1211 def build_lookup(
1212 self, lookups: list[str], lhs: BaseExpression, rhs: Any
1213 ) -> Lookup | None:
1214 """
1215 Try to extract transforms and lookup from given lhs.
1216
1217 The lhs value is something that works like SQLExpression.
1218 The rhs value is what the lookup is going to compare against.
1219 The lookups is a list of names to extract using get_lookup()
1220 and get_transform().
1221 """
1222 # __exact is the default lookup if one isn't given.
1223 *transforms, lookup_name = lookups or ["exact"]
1224 for name in transforms:
1225 lhs = self.try_transform(lhs, name)
1226 # First try get_lookup() so that the lookup takes precedence if the lhs
1227 # supports both transform and lookup for the name.
1228 lookup_class = lhs.get_lookup(lookup_name)
1229 if not lookup_class:
1230 # A lookup wasn't found. Try to interpret the name as a transform
1231 # and do an Exact lookup against it.
1232 lhs = self.try_transform(lhs, lookup_name)
1233 lookup_name = "exact"
1234 lookup_class = lhs.get_lookup(lookup_name)
1235 if not lookup_class:
1236 return
1237
1238 lookup = lookup_class(lhs, rhs)
1239 # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
1240 # uses of None as a query value unless the lookup supports it.
1241 if lookup.rhs is None and not lookup.can_use_none_as_rhs:
1242 if lookup_name not in ("exact", "iexact"):
1243 raise ValueError("Cannot use None as a query value")
1244 isnull_lookup = lhs.get_lookup("isnull")
1245 assert isnull_lookup is not None
1246 return isnull_lookup(lhs, True)
1247
1248 return lookup
1249
1250 def try_transform(self, lhs: BaseExpression, name: str) -> BaseExpression:
1251 """
1252 Helper method for build_lookup(). Try to fetch and initialize
1253 a transform for name parameter from lhs.
1254 """
1255 transform_class = lhs.get_transform(name)
1256 if transform_class:
1257 return transform_class(lhs)
1258 else:
1259 output_field = lhs.output_field.__class__
1260 suggested_lookups = difflib.get_close_matches(
1261 name, output_field.get_lookups()
1262 )
1263 if suggested_lookups:
1264 suggestion = ", perhaps you meant {}?".format(
1265 " or ".join(suggested_lookups)
1266 )
1267 else:
1268 suggestion = "."
1269 raise FieldError(
1270 f"Unsupported lookup '{name}' for {output_field.__name__} or join on the field not "
1271 f"permitted{suggestion}"
1272 )
1273
1274 def build_filter(
1275 self,
1276 filter_expr: tuple[str, Any] | Q | BaseExpression,
1277 branch_negated: bool = False,
1278 current_negated: bool = False,
1279 can_reuse: set[str] | None = None,
1280 allow_joins: bool = True,
1281 split_subq: bool = True,
1282 check_filterable: bool = True,
1283 summarize: bool = False,
1284 ) -> tuple[WhereNode, set[str] | tuple[()]]:
1285 from plain.postgres.fields.related import RelatedField
1286
1287 """
1288 Build a WhereNode for a single filter clause but don't add it
1289 to this Query. Query.add_q() will then add this filter to the where
1290 Node.
1291
1292 The 'branch_negated' tells us if the current branch contains any
1293 negations. This will be used to determine if subqueries are needed.
1294
1295 The 'current_negated' is used to determine if the current filter is
1296 negated or not and this will be used to determine if IS NULL filtering
1297 is needed.
1298
1299 The difference between current_negated and branch_negated is that
1300 branch_negated is set on first negation, but current_negated is
1301 flipped for each negation.
1302
1303 Note that add_filter will not do any negating itself, that is done
1304 upper in the code by add_q().
1305
1306 The 'can_reuse' is a set of reusable joins for multijoins.
1307
1308 The method will create a filter clause that can be added to the current
1309 query. However, if the filter isn't added to the query then the caller
1310 is responsible for unreffing the joins used.
1311 """
1312 if isinstance(filter_expr, dict):
1313 raise FieldError("Cannot parse keyword query as dict")
1314 if isinstance(filter_expr, Q):
1315 return self._add_q(
1316 filter_expr,
1317 branch_negated=branch_negated,
1318 current_negated=current_negated,
1319 used_aliases=can_reuse,
1320 allow_joins=allow_joins,
1321 split_subq=split_subq,
1322 check_filterable=check_filterable,
1323 summarize=summarize,
1324 )
1325 if isinstance(filter_expr, ResolvableExpression):
1326 if not getattr(filter_expr, "conditional", False):
1327 raise TypeError("Cannot filter against a non-conditional expression.")
1328 condition = filter_expr.resolve_expression(
1329 self, allow_joins=allow_joins, summarize=summarize
1330 )
1331 if not isinstance(condition, Lookup):
1332 condition = self.build_lookup(["exact"], condition, True)
1333 return WhereNode([condition], connector=AND), set()
1334 if isinstance(filter_expr, BaseExpression):
1335 raise TypeError(f"Unexpected BaseExpression type: {type(filter_expr)}")
1336 arg, value = filter_expr
1337 if not arg:
1338 raise FieldError(f"Cannot parse keyword query {arg!r}")
1339 lookups, parts, reffed_expression = self.solve_lookup_type(arg, summarize)
1340
1341 if check_filterable:
1342 self.check_filterable(reffed_expression)
1343
1344 if not allow_joins and len(parts) > 1:
1345 raise FieldError("Joined field references are not permitted in this query")
1346
1347 pre_joins = self.alias_refcount.copy()
1348 value = self.resolve_lookup_value(value, can_reuse, allow_joins)
1349 used_joins = {
1350 k for k, v in self.alias_refcount.items() if v > pre_joins.get(k, 0)
1351 }
1352
1353 if check_filterable:
1354 self.check_filterable(value)
1355
1356 if reffed_expression:
1357 condition = self.build_lookup(list(lookups), reffed_expression, value)
1358 return WhereNode([condition], connector=AND), set()
1359
1360 assert self.model is not None, "Building filters requires a model"
1361 meta = self.model._model_meta
1362 alias = self.get_initial_alias()
1363 assert alias is not None
1364 allow_many = not branch_negated or not split_subq
1365
1366 try:
1367 join_info = self.setup_joins(
1368 list(parts),
1369 meta,
1370 alias,
1371 can_reuse=can_reuse,
1372 allow_many=allow_many,
1373 )
1374
1375 # Prevent iterator from being consumed by check_related_objects()
1376 if isinstance(value, Iterator):
1377 value = list(value)
1378 from plain.postgres.fields.related import RelatedField
1379 from plain.postgres.fields.reverse_related import ForeignObjectRel
1380
1381 if isinstance(join_info.final_field, RelatedField | ForeignObjectRel):
1382 self.check_related_objects(join_info.final_field, value, join_info.meta)
1383
1384 # split_exclude() needs to know which joins were generated for the
1385 # lookup parts
1386 self._lookup_joins = join_info.joins
1387 except MultiJoin as e:
1388 return self.split_exclude(
1389 filter_expr,
1390 can_reuse or set(),
1391 e.names_with_path,
1392 )
1393
1394 # Update used_joins before trimming since they are reused to determine
1395 # which joins could be later promoted to INNER.
1396 used_joins.update(join_info.joins)
1397 targets, alias, join_list = self.trim_joins(
1398 join_info.targets, join_info.joins, join_info.path
1399 )
1400 if can_reuse is not None:
1401 can_reuse.update(join_list)
1402
1403 col = self._get_col(targets[0], join_info.final_field, alias)
1404
1405 condition = self.build_lookup(list(lookups), col, value)
1406 assert condition is not None
1407 lookup_type = condition.lookup_name
1408 clause = WhereNode([condition], connector=AND)
1409
1410 require_outer = (
1411 lookup_type == "isnull" and condition.rhs is True and not current_negated
1412 )
1413 if (
1414 current_negated
1415 and (lookup_type != "isnull" or condition.rhs is False)
1416 and condition.rhs is not None
1417 ):
1418 require_outer = True
1419 if lookup_type != "isnull":
1420 # The condition added here will be SQL like this:
1421 # NOT (col IS NOT NULL), where the first NOT is added in
1422 # upper layers of code. The reason for addition is that if col
1423 # is null, then col != someval will result in SQL "unknown"
1424 # which isn't the same as in Python. The Python None handling
1425 # is wanted, and it can be gotten by
1426 # (col IS NULL OR col != someval)
1427 # <=>
1428 # NOT (col IS NOT NULL AND col = someval).
1429 if (
1430 self.is_nullable(targets[0])
1431 or self.alias_map[join_list[-1]].join_type == LOUTER
1432 ):
1433 lookup_class = targets[0].get_lookup("isnull")
1434 assert lookup_class is not None
1435 col = self._get_col(targets[0], join_info.targets[0], alias)
1436 clause.add(lookup_class(col, False), AND)
1437 # If someval is a nullable column, someval IS NOT NULL is
1438 # added.
1439 if isinstance(value, Col) and self.is_nullable(value.target):
1440 lookup_class = value.target.get_lookup("isnull")
1441 assert lookup_class is not None
1442 clause.add(lookup_class(value, False), AND)
1443 return clause, used_joins if not require_outer else ()
1444
1445 def add_filter(self, filter_lhs: str, filter_rhs: Any) -> None:
1446 self.add_q(Q((filter_lhs, filter_rhs)))
1447
1448 def add_q(self, q_object: Q) -> None:
1449 """
1450 A preprocessor for the internal _add_q(). Responsible for doing final
1451 join promotion.
1452 """
1453 # For join promotion this case is doing an AND for the added q_object
1454 # and existing conditions. So, any existing inner join forces the join
1455 # type to remain inner. Existing outer joins can however be demoted.
1456 # (Consider case where rel_a is LOUTER and rel_a__col=1 is added - if
1457 # rel_a doesn't produce any rows, then the whole condition must fail.
1458 # So, demotion is OK.
1459 existing_inner = {
1460 a for a in self.alias_map if self.alias_map[a].join_type == INNER
1461 }
1462 clause, _ = self._add_q(q_object, self.used_aliases)
1463 if clause:
1464 self.where.add(clause, AND)
1465 self.demote_joins(existing_inner)
1466
1467 def build_where(
1468 self, filter_expr: tuple[str, Any] | Q | BaseExpression
1469 ) -> WhereNode:
1470 return self.build_filter(filter_expr, allow_joins=False)[0]
1471
1472 def clear_where(self) -> None:
1473 self.where = WhereNode()
1474
1475 def _add_q(
1476 self,
1477 q_object: Q,
1478 used_aliases: set[str] | None,
1479 branch_negated: bool = False,
1480 current_negated: bool = False,
1481 allow_joins: bool = True,
1482 split_subq: bool = True,
1483 check_filterable: bool = True,
1484 summarize: bool = False,
1485 ) -> tuple[WhereNode, set[str] | tuple[()]]:
1486 """Add a Q-object to the current filter."""
1487 connector = q_object.connector
1488 current_negated ^= q_object.negated
1489 branch_negated = branch_negated or q_object.negated
1490 target_clause = WhereNode(connector=connector, negated=q_object.negated)
1491 joinpromoter = JoinPromoter(
1492 q_object.connector, len(q_object.children), current_negated
1493 )
1494 for child in q_object.children:
1495 child_clause, needed_inner = self.build_filter(
1496 child,
1497 can_reuse=used_aliases,
1498 branch_negated=branch_negated,
1499 current_negated=current_negated,
1500 allow_joins=allow_joins,
1501 split_subq=split_subq,
1502 check_filterable=check_filterable,
1503 summarize=summarize,
1504 )
1505 joinpromoter.add_votes(needed_inner)
1506 if child_clause:
1507 target_clause.add(child_clause, connector)
1508 needed_inner = joinpromoter.update_join_types(self)
1509 return target_clause, needed_inner
1510
1511 def names_to_path(
1512 self,
1513 names: list[str],
1514 meta: Meta,
1515 allow_many: bool = True,
1516 fail_on_missing: bool = False,
1517 ) -> tuple[list[Any], Field | ForeignObjectRel, tuple[Field, ...], list[str]]:
1518 """
1519 Walk the list of names and turns them into PathInfo tuples. A single
1520 name in 'names' can generate multiple PathInfos (m2m, for example).
1521
1522 'names' is the path of names to travel, 'meta' is the Meta we
1523 start the name resolving from, 'allow_many' is as for setup_joins().
1524 If fail_on_missing is set to True, then a name that can't be resolved
1525 will generate a FieldError.
1526
1527 Return a list of PathInfo tuples. In addition return the final field
1528 (the last used join field) and target (which is a field guaranteed to
1529 contain the same value as the final field). Finally, return those names
1530 that weren't found (which are likely transforms and the final lookup).
1531 """
1532 path, names_with_path = [], []
1533 for pos, name in enumerate(names):
1534 cur_names_with_path = (name, [])
1535
1536 field = None
1537 try:
1538 if meta is None:
1539 raise FieldDoesNotExist
1540 field = meta.get_field(name)
1541 except FieldDoesNotExist:
1542 if name in self.annotation_select:
1543 field = self.annotation_select[name].output_field
1544 if field is None:
1545 # We didn't find the current field, so move position back
1546 # one step.
1547 pos -= 1
1548 if pos == -1 or fail_on_missing:
1549 available = sorted(
1550 [
1551 *get_field_names_from_opts(meta),
1552 *self.annotation_select,
1553 ]
1554 )
1555 raise FieldError(
1556 "Cannot resolve keyword '{}' into field. "
1557 "Choices are: {}".format(name, ", ".join(available))
1558 )
1559 break
1560
1561 # Lazy import to avoid circular dependency
1562 from plain.postgres.fields.related import ForeignKeyField as FK
1563 from plain.postgres.fields.related import ManyToManyField as M2M
1564 from plain.postgres.fields.reverse_related import ForeignObjectRel as FORel
1565
1566 if isinstance(field, FK | M2M | FORel):
1567 pathinfos: list[PathInfo] = field.path_infos
1568 if not allow_many:
1569 for inner_pos, p in enumerate(pathinfos):
1570 if p.m2m:
1571 cur_names_with_path[1].extend(pathinfos[0 : inner_pos + 1])
1572 names_with_path.append(cur_names_with_path)
1573 raise MultiJoin(pos + 1, names_with_path)
1574 last = pathinfos[-1]
1575 path.extend(pathinfos)
1576 final_field = last.join_field
1577 meta = last.to_meta
1578 targets = (last.target_field,)
1579 cur_names_with_path[1].extend(pathinfos)
1580 names_with_path.append(cur_names_with_path)
1581 else:
1582 # Local non-relational field.
1583 final_field = field
1584 targets = (field,)
1585 if fail_on_missing and pos + 1 != len(names):
1586 raise FieldError(
1587 f"Cannot resolve keyword {names[pos + 1]!r} into field. Join on '{name}'"
1588 " not permitted."
1589 )
1590 break
1591 return path, final_field, targets, names[pos + 1 :]
1592
1593 def setup_joins(
1594 self,
1595 names: list[str],
1596 meta: Meta,
1597 alias: str,
1598 can_reuse: set[str] | None = None,
1599 allow_many: bool = True,
1600 ) -> JoinInfo:
1601 """
1602 Compute the necessary table joins for the passage through the fields
1603 given in 'names'. 'meta' is the Meta for the current model
1604 (which gives the table we are starting from), 'alias' is the alias for
1605 the table to start the joining from.
1606
1607 The 'can_reuse' defines the reverse foreign key joins we can reuse. It
1608 can be None in which case all joins are reusable or a set of aliases
1609 that can be reused. Note that non-reverse foreign keys are always
1610 reusable when using setup_joins().
1611
1612 If 'allow_many' is False, then any reverse foreign key seen will
1613 generate a MultiJoin exception.
1614
1615 Return the final field involved in the joins, the target field (used
1616 for any 'where' constraint), the final 'opts' value, the joins, the
1617 field path traveled to generate the joins, and a transform function
1618 that takes a field and alias and is equivalent to `field.get_col(alias)`
1619 in the simple case but wraps field transforms if they were included in
1620 names.
1621
1622 The target field is the field containing the concrete value. Final
1623 field can be something different, for example foreign key pointing to
1624 that value. Final field is needed for example in some value
1625 conversions (convert 'obj' in fk__id=obj to pk val using the foreign
1626 key field for example).
1627 """
1628 joins = [alias]
1629 # The transform can't be applied yet, as joins must be trimmed later.
1630 # To avoid making every caller of this method look up transforms
1631 # directly, compute transforms here and create a partial that converts
1632 # fields to the appropriate wrapped version.
1633
1634 def _base_transformer(field: Field, alias: str | None) -> Col:
1635 if not self.alias_cols:
1636 alias = None
1637 return field.get_col(alias)
1638
1639 final_transformer: TransformWrapper | Callable[[Field, str | None], Col] = (
1640 _base_transformer
1641 )
1642
1643 # Try resolving all the names as fields first. If there's an error,
1644 # treat trailing names as lookups until a field can be resolved.
1645 last_field_exception = None
1646 for pivot in range(len(names), 0, -1):
1647 try:
1648 path, final_field, targets, _rest = self.names_to_path(
1649 names[:pivot],
1650 meta,
1651 allow_many,
1652 fail_on_missing=True,
1653 )
1654 except FieldError as exc:
1655 if pivot == 1:
1656 # The first item cannot be a lookup, so it's safe
1657 # to raise the field error here.
1658 raise
1659 else:
1660 last_field_exception = exc
1661 else:
1662 # The transforms are the remaining items that couldn't be
1663 # resolved into fields.
1664 transforms = names[pivot:]
1665 break
1666 for name in transforms:
1667
1668 def transform(
1669 field: Field, alias: str | None, *, name: str, previous: Any
1670 ) -> BaseExpression:
1671 try:
1672 wrapped = previous(field, alias)
1673 return self.try_transform(wrapped, name)
1674 except FieldError:
1675 # FieldError is raised if the transform doesn't exist.
1676 if isinstance(final_field, Field) and last_field_exception:
1677 raise last_field_exception
1678 else:
1679 raise
1680
1681 final_transformer = TransformWrapper(
1682 transform, name=name, previous=final_transformer
1683 )
1684 final_transformer.has_transforms = True
1685 # Then, add the path to the query's joins. Note that we can't trim
1686 # joins at this stage - we will need the information about join type
1687 # of the trimmed joins.
1688 for join in path:
1689 meta = join.to_meta
1690 if join.direct:
1691 nullable = self.is_nullable(join.join_field)
1692 else:
1693 nullable = True
1694 connection = self.join_class(
1695 meta.model.model_options.db_table,
1696 alias,
1697 None, # ty: ignore[invalid-argument-type]
1698 INNER,
1699 join.join_field,
1700 nullable,
1701 )
1702 reuse = can_reuse if join.m2m else None
1703 alias = self.join(connection, reuse=reuse)
1704 joins.append(alias)
1705 return JoinInfo(final_field, targets, meta, joins, path, final_transformer) # ty: ignore[invalid-argument-type]
1706
1707 def trim_joins(
1708 self, targets: tuple[Field, ...], joins: list[str], path: list[Any]
1709 ) -> tuple[tuple[Field, ...], str, list[str]]:
1710 """
1711 The 'target' parameter is the final field being joined to, 'joins'
1712 is the full list of join aliases. The 'path' contain the PathInfos
1713 used to create the joins.
1714
1715 Return the final target field and table alias and the new active
1716 joins.
1717
1718 Always trim any direct join if the target column is already in the
1719 previous table. Can't trim reverse joins as it's unknown if there's
1720 anything on the other side of the join.
1721 """
1722 joins = joins[:]
1723 for pos, info in enumerate(reversed(path)):
1724 if len(joins) == 1 or not info.direct:
1725 break
1726 # A direct join is a single-column foreign key; if its target is
1727 # this join's foreign column, drop the join and continue from the
1728 # local foreign key column instead.
1729 join_field = info.join_field
1730 if targets[0].column != join_field.target_field.column:
1731 break
1732 targets = (join_field,)
1733 self.unref_alias(joins.pop())
1734 return targets, joins[-1], joins
1735
1736 @classmethod
1737 def _gen_cols(
1738 cls,
1739 exprs: Iterable[Any],
1740 include_external: bool = False,
1741 resolve_refs: bool = True,
1742 ) -> TypingIterator[Col]:
1743 for expr in exprs:
1744 if isinstance(expr, Col):
1745 yield expr
1746 elif include_external and callable(
1747 getattr(expr, "get_external_cols", None)
1748 ):
1749 yield from expr.get_external_cols()
1750 elif hasattr(expr, "get_source_expressions"):
1751 if not resolve_refs and isinstance(expr, Ref):
1752 continue
1753 yield from cls._gen_cols(
1754 expr.get_source_expressions(),
1755 include_external=include_external,
1756 resolve_refs=resolve_refs,
1757 )
1758
1759 @classmethod
1760 def _gen_col_aliases(cls, exprs: Iterable[Any]) -> TypingIterator[str | None]:
1761 yield from (expr.alias for expr in cls._gen_cols(exprs))
1762
1763 def resolve_ref(
1764 self,
1765 name: str,
1766 allow_joins: bool = True,
1767 reuse: set[str] | None = None,
1768 summarize: bool = False,
1769 ) -> BaseExpression:
1770 annotation = self.annotations.get(name)
1771 if annotation is not None:
1772 if not allow_joins:
1773 for alias in self._gen_col_aliases([annotation]):
1774 if isinstance(self.alias_map[alias], Join):
1775 raise FieldError(
1776 "Joined field references are not permitted in this query"
1777 )
1778 if summarize:
1779 # Summarize currently means we are doing an aggregate() query
1780 # which is executed as a wrapped subquery if any of the
1781 # aggregate() elements reference an existing annotation. In
1782 # that case we need to return a Ref to the subquery's annotation.
1783 if name not in self.annotation_select:
1784 raise FieldError(
1785 f"Cannot aggregate over the '{name}' alias. Use annotate() "
1786 "to promote it."
1787 )
1788 return Ref(name, self.annotation_select[name])
1789 else:
1790 return annotation
1791 else:
1792 field_list = name.split(LOOKUP_SEP)
1793 annotation = self.annotations.get(field_list[0])
1794 if annotation is not None:
1795 for transform in field_list[1:]:
1796 annotation = self.try_transform(annotation, transform)
1797 return annotation
1798 initial_alias = self.get_initial_alias()
1799 assert initial_alias is not None
1800 assert self.model is not None, "Resolving field references requires a model"
1801 meta = self.model._model_meta
1802 join_info = self.setup_joins(
1803 field_list,
1804 meta,
1805 initial_alias,
1806 can_reuse=reuse,
1807 )
1808 targets, final_alias, join_list = self.trim_joins(
1809 join_info.targets, join_info.joins, join_info.path
1810 )
1811 if not allow_joins and len(join_list) > 1:
1812 raise FieldError(
1813 "Joined field references are not permitted in this query"
1814 )
1815 # Verify that the last lookup in name is a field or a transform:
1816 # transform_function() raises FieldError if not.
1817 transform = join_info.transform_function(targets[0], final_alias)
1818 if reuse is not None:
1819 reuse.update(join_list)
1820 return transform
1821
1822 def split_exclude(
1823 self,
1824 filter_expr: tuple[str, Any],
1825 can_reuse: set[str],
1826 names_with_path: list[tuple[str, list[Any]]],
1827 ) -> tuple[WhereNode, set[str] | tuple[()]]:
1828 """
1829 When doing an exclude against any kind of N-to-many relation, we need
1830 to use a subquery. This method constructs the nested query, given the
1831 original exclude filter (filter_expr) and the portion up to the first
1832 N-to-many relation field.
1833
1834 For example, if the origin filter is ~Q(child__name='foo'), filter_expr
1835 is ('child__name', 'foo') and can_reuse is a set of joins usable for
1836 filters in the original query.
1837
1838 We will turn this into equivalent of:
1839 WHERE NOT EXISTS(
1840 SELECT 1
1841 FROM child
1842 WHERE name = 'foo' AND child.parent_id = parent.id
1843 LIMIT 1
1844 )
1845 """
1846 # Generate the inner query.
1847 query = self.__class__(self.model)
1848 filter_lhs, filter_rhs = filter_expr
1849 if isinstance(filter_rhs, OuterRef):
1850 filter_rhs = OuterRef(filter_rhs)
1851 elif isinstance(filter_rhs, F):
1852 filter_rhs = OuterRef(filter_rhs.name)
1853 query.add_filter(filter_lhs, filter_rhs)
1854 query.clear_ordering(force=True)
1855 # Try to have as simple as possible subquery -> trim leading joins from
1856 # the subquery.
1857 trimmed_prefix, contains_louter = query.trim_start(names_with_path)
1858
1859 # trim_start() seeds query.select with Col instances built from the
1860 # join field, so .target/.alias access below is sound.
1861 col = query.select[0]
1862 assert isinstance(col, Col)
1863 select_field = col.target
1864 alias = col.alias
1865 if alias in can_reuse:
1866 id_field = select_field.model._model_meta.get_forward_field("id")
1867 # Need to add a restriction so that outer query's filters are in effect for
1868 # the subquery, too.
1869 query.bump_prefix(self)
1870 lookup_class = select_field.get_lookup("exact")
1871 # Note that the query.select[0].alias is different from alias
1872 # due to bump_prefix above.
1873 bumped_col = query.select[0]
1874 assert isinstance(bumped_col, Col)
1875 lookup = lookup_class(
1876 id_field.get_col(bumped_col.alias), id_field.get_col(alias)
1877 )
1878 query.where.add(lookup, AND)
1879 query.external_aliases[alias] = True
1880
1881 lookup_class = select_field.get_lookup("exact")
1882 lookup = lookup_class(col, ResolvedOuterRef(trimmed_prefix))
1883 query.where.add(lookup, AND)
1884 condition, needed_inner = self.build_filter(Exists(query))
1885
1886 if contains_louter:
1887 or_null_condition, _ = self.build_filter(
1888 (f"{trimmed_prefix}__isnull", True),
1889 current_negated=True,
1890 branch_negated=True,
1891 can_reuse=can_reuse,
1892 )
1893 condition.add(or_null_condition, OR)
1894 # Note that the end result will be:
1895 # (outercol NOT IN innerq AND outercol IS NOT NULL) OR outercol IS NULL.
1896 # This might look crazy but due to how IN works, this seems to be
1897 # correct. If the IS NOT NULL check is removed then outercol NOT
1898 # IN will return UNKNOWN. If the IS NULL check is removed, then if
1899 # outercol IS NULL we will not match the row.
1900 return condition, needed_inner
1901
1902 def set_empty(self) -> None:
1903 self.where.add(NothingNode(), AND)
1904
1905 def is_empty(self) -> bool:
1906 return any(isinstance(c, NothingNode) for c in self.where.children)
1907
1908 def set_limits(self, low: int | None = None, high: int | None = None) -> None:
1909 """
1910 Adjust the limits on the rows retrieved. Use low/high to set these,
1911 as it makes it more Pythonic to read and write. When the SQL query is
1912 created, convert them to the appropriate offset and limit values.
1913
1914 Apply any limits passed in here to the existing constraints. Add low
1915 to the current low value and clamp both to any existing high value.
1916 """
1917 if high is not None:
1918 if self.high_mark is not None:
1919 self.high_mark = min(self.high_mark, self.low_mark + high)
1920 else:
1921 self.high_mark = self.low_mark + high
1922 if low is not None:
1923 if self.high_mark is not None:
1924 self.low_mark = min(self.high_mark, self.low_mark + low)
1925 else:
1926 self.low_mark = self.low_mark + low
1927
1928 if self.low_mark == self.high_mark:
1929 self.set_empty()
1930
1931 def clear_limits(self) -> None:
1932 """Clear any existing limits."""
1933 self.low_mark, self.high_mark = 0, None
1934
1935 @property
1936 def is_sliced(self) -> bool:
1937 return self.low_mark != 0 or self.high_mark is not None
1938
1939 def has_limit_one(self) -> bool:
1940 return self.high_mark is not None and (self.high_mark - self.low_mark) == 1
1941
1942 def can_filter(self) -> bool:
1943 """
1944 Return True if adding filters to this instance is still possible.
1945
1946 Typically, this means no limits or offsets have been put on the results.
1947 """
1948 return not self.is_sliced
1949
1950 def clear_select_clause(self) -> None:
1951 """Remove all fields from SELECT clause."""
1952 self.select = ()
1953 self.default_cols = False
1954 self.joined_relations = False
1955 self.set_annotation_mask(())
1956
1957 def clear_select_fields(self) -> None:
1958 """
1959 Clear the list of fields to select. Some queryset types completely
1960 replace any existing list of select columns.
1961 """
1962 self.select = ()
1963 self.values_select = ()
1964
1965 def add_select_col(self, col: BaseExpression, name: str) -> None:
1966 self.select += (col,)
1967 self.values_select += (name,)
1968
1969 def set_select(
1970 self, cols: list[BaseExpression] | tuple[BaseExpression, ...]
1971 ) -> None:
1972 self.default_cols = False
1973 self.select = tuple(cols)
1974
1975 def add_distinct_fields(self, *field_names: str) -> None:
1976 """
1977 Add and resolve the given fields to the query's "distinct on" clause.
1978 """
1979 self.distinct_fields = field_names
1980 self.distinct = True
1981
1982 def add_fields(
1983 self, field_names: list[str] | TypingIterator[str], allow_m2m: bool = True
1984 ) -> None:
1985 """
1986 Add the given (model) fields to the select set. Add the field names in
1987 the order specified.
1988 """
1989 alias = self.get_initial_alias()
1990 assert alias is not None
1991 assert self.model is not None, "add_fields() requires a model"
1992 meta = self.model._model_meta
1993
1994 try:
1995 cols = []
1996 for name in field_names:
1997 # Join promotion note - we must not remove any rows here, so
1998 # if there is no existing joins, use outer join.
1999 join_info = self.setup_joins(
2000 name.split(LOOKUP_SEP), meta, alias, allow_many=allow_m2m
2001 )
2002 targets, final_alias, _joins = self.trim_joins(
2003 join_info.targets,
2004 join_info.joins,
2005 join_info.path,
2006 )
2007 for target in targets:
2008 cols.append(join_info.transform_function(target, final_alias))
2009 if cols:
2010 self.set_select(cols)
2011 except MultiJoin:
2012 raise FieldError(f"Invalid field name: '{name}'")
2013 except FieldError:
2014 if LOOKUP_SEP in name:
2015 # For lookups spanning over relationships, show the error
2016 # from the model on which the lookup failed.
2017 raise
2018 elif name in self.annotations:
2019 raise FieldError(
2020 f"Cannot select the '{name}' alias. Use annotate() to promote it."
2021 )
2022 else:
2023 names = sorted(
2024 [
2025 *get_field_names_from_opts(meta),
2026 *self.annotation_select,
2027 ]
2028 )
2029 raise FieldError(
2030 "Cannot resolve keyword {!r} into field. Choices are: {}".format(
2031 name, ", ".join(names)
2032 )
2033 )
2034
2035 def add_ordering(self, *ordering: str | ResolvableExpression) -> None:
2036 """
2037 Add items from the 'ordering' sequence to the query's "order by"
2038 clause. These items are either field names (not column names) --
2039 possibly with a direction prefix ('-' or '?') -- or OrderBy
2040 expressions.
2041
2042 If 'ordering' is empty, clear all ordering from the query.
2043 """
2044 errors = []
2045 for item in ordering:
2046 if isinstance(item, str):
2047 if item == "?":
2048 continue
2049 item = item.removeprefix("-")
2050 if item in self.annotations:
2051 continue
2052 # names_to_path() validates the lookup. A descriptive
2053 # FieldError will be raise if it's not.
2054 assert self.model is not None, "ORDER BY field names require a model"
2055 self.names_to_path(item.split(LOOKUP_SEP), self.model._model_meta)
2056 elif not isinstance(item, ResolvableExpression):
2057 errors.append(item)
2058 if getattr(item, "contains_aggregate", False):
2059 raise FieldError(
2060 "Using an aggregate in order_by() without also including "
2061 f"it in annotate() is not allowed: {item}"
2062 )
2063 if errors:
2064 raise FieldError(f"Invalid order_by arguments: {errors}")
2065 if ordering:
2066 self.order_by += ordering
2067 else:
2068 self.default_ordering = False
2069
2070 def clear_ordering(self, force: bool = False, clear_default: bool = True) -> None:
2071 """
2072 Remove any ordering settings if the current query allows it without
2073 side effects, set 'force' to True to clear the ordering regardless.
2074 If 'clear_default' is True, there will be no ordering in the resulting
2075 query (not even the model's default).
2076 """
2077 if not force and (self.is_sliced or self.distinct_fields or self.lock_mode):
2078 return
2079 self.order_by = ()
2080 if clear_default:
2081 self.default_ordering = False
2082
2083 def set_group_by(self, allow_aliases: bool = True) -> None:
2084 """
2085 Expand the GROUP BY clause required by the query.
2086
2087 This will usually be the set of all non-aggregate fields in the
2088 return data. If the database backend supports grouping by the
2089 primary key, and the query would be equivalent, the optimization
2090 will be made automatically.
2091 """
2092 if allow_aliases and self.values_select:
2093 # If grouping by aliases is allowed assign selected value aliases
2094 # by moving them to annotations.
2095 group_by_annotations = {}
2096 values_select = {}
2097 for alias, expr in zip(self.values_select, self.select):
2098 if isinstance(expr, Col):
2099 values_select[alias] = expr
2100 else:
2101 group_by_annotations[alias] = expr
2102 self.annotations = {**group_by_annotations, **self.annotations}
2103 self.append_annotation_mask(group_by_annotations)
2104 self.select = tuple(values_select.values())
2105 self.values_select = tuple(values_select)
2106 group_by = list(self.select)
2107 for alias, annotation in self.annotation_select.items():
2108 if not (group_by_cols := annotation.get_group_by_cols()):
2109 continue
2110 if allow_aliases and not annotation.contains_aggregate:
2111 group_by.append(Ref(alias, annotation))
2112 else:
2113 group_by.extend(group_by_cols)
2114 self.group_by = tuple(group_by)
2115
2116 def add_joined_relations(self, fields: list[str]) -> None:
2117 """
2118 Set up the joined_relations data structure so that we only select
2119 certain related models (as opposed to all models, when
2120 self.joined_relations=True).
2121 """
2122 if isinstance(self.joined_relations, bool):
2123 field_dict: dict[str, Any] = {}
2124 else:
2125 field_dict = self.joined_relations
2126 for field in fields:
2127 d = field_dict
2128 for part in field.split(LOOKUP_SEP):
2129 d = d.setdefault(part, {})
2130 self.joined_relations = field_dict
2131
2132 def clear_deferred_loading(self) -> None:
2133 """Remove any fields from the deferred loading set."""
2134 self.deferred_loading = (frozenset(), True)
2135
2136 def add_deferred_loading(self, field_names: frozenset[str]) -> None:
2137 """
2138 Add the given list of model field names to the set of fields to
2139 exclude from loading from the database when automatic column selection
2140 is done. Add the new field names to any existing field names that
2141 are deferred (or removed from any existing field names that are marked
2142 as the only ones for immediate loading).
2143 """
2144 # Fields on related models are stored in the literal double-underscore
2145 # format, so that we can use a set datastructure. We do the foo__bar
2146 # splitting and handling when computing the SQL column names (as part of
2147 # get_columns()).
2148 existing, defer = self.deferred_loading
2149 existing_set = set(existing)
2150 if defer:
2151 # Add to existing deferred names.
2152 self.deferred_loading = frozenset(existing_set.union(field_names)), True
2153 else:
2154 # Remove names from the set of any existing "immediate load" names.
2155 if new_existing := existing_set.difference(field_names):
2156 self.deferred_loading = frozenset(new_existing), False
2157 else:
2158 self.clear_deferred_loading()
2159 if new_only := set(field_names).difference(existing_set):
2160 self.deferred_loading = frozenset(new_only), True
2161
2162 def add_immediate_loading(self, field_names: list[str] | set[str]) -> None:
2163 """
2164 Add the given list of model field names to the set of fields to
2165 retrieve when the SQL is executed ("immediate loading" fields). The
2166 field names replace any existing immediate loading field names. If
2167 there are field names already specified for deferred loading, remove
2168 those names from the new field_names before storing the new names
2169 for immediate loading. (That is, immediate loading overrides any
2170 existing immediate values, but respects existing deferrals.)
2171 """
2172 existing, defer = self.deferred_loading
2173 field_names_set = set(field_names)
2174
2175 if defer:
2176 # Remove any existing deferred names from the current set before
2177 # setting the new names.
2178 self.deferred_loading = (
2179 frozenset(field_names_set.difference(existing)),
2180 False,
2181 )
2182 else:
2183 # Replace any existing "immediate load" field names.
2184 self.deferred_loading = frozenset(field_names_set), False
2185
2186 def set_annotation_mask(
2187 self,
2188 names: set[str]
2189 | frozenset[str]
2190 | list[str]
2191 | tuple[str, ...]
2192 | dict[str, Any]
2193 | None,
2194 ) -> None:
2195 """Set the mask of annotations that will be returned by the SELECT."""
2196 if names is None:
2197 self.annotation_select_mask = None
2198 else:
2199 self.annotation_select_mask = set(names)
2200 self._annotation_select_cache = None
2201
2202 def append_annotation_mask(self, names: list[str] | dict[str, Any]) -> None:
2203 if self.annotation_select_mask is not None:
2204 self.set_annotation_mask(self.annotation_select_mask.union(names))
2205
2206 def set_values(self, fields: list[str]) -> None:
2207 self.joined_relations = False
2208 self.clear_deferred_loading()
2209 self.clear_select_fields()
2210 self.has_select_fields = True
2211
2212 if fields:
2213 field_names = []
2214 annotation_names = []
2215 if not self.annotations:
2216 # Shortcut - if there are no annotations, then the values()
2217 # clause must be just field names.
2218 field_names = list(fields)
2219 else:
2220 self.default_cols = False
2221 for f in fields:
2222 if f in self.annotation_select:
2223 annotation_names.append(f)
2224 else:
2225 field_names.append(f)
2226 self.set_annotation_mask(annotation_names)
2227 selected = frozenset(field_names + annotation_names)
2228 else:
2229 assert self.model is not None, "Default values query requires a model"
2230 field_names = [f.name for f in self.model._model_meta.fields]
2231 selected = frozenset(field_names)
2232 # Selected annotations must be known before setting the GROUP BY
2233 # clause.
2234 if self.group_by is True:
2235 assert self.model is not None, "GROUP BY True requires a model"
2236 self.add_fields((f.name for f in self.model._model_meta.fields), False)
2237 # Disable GROUP BY aliases to avoid orphaning references to the
2238 # SELECT clause which is about to be cleared.
2239 self.set_group_by(allow_aliases=False)
2240 self.clear_select_fields()
2241 elif self.group_by:
2242 # Resolve GROUP BY annotation references if they are not part of
2243 # the selected fields anymore.
2244 group_by = []
2245 for expr in self.group_by:
2246 if isinstance(expr, Ref) and expr.refs not in selected:
2247 expr = self.annotations[expr.refs]
2248 group_by.append(expr)
2249 self.group_by = tuple(group_by)
2250
2251 self.values_select = tuple(field_names)
2252 self.add_fields(field_names, True)
2253
2254 @property
2255 def annotation_select(self) -> dict[str, BaseExpression]:
2256 """
2257 Return the dictionary of aggregate columns that are not masked and
2258 should be used in the SELECT clause. Cache this result for performance.
2259 """
2260 if self._annotation_select_cache is not None:
2261 return self._annotation_select_cache
2262 elif not self.annotations:
2263 return {}
2264 elif self.annotation_select_mask is not None:
2265 self._annotation_select_cache = {
2266 k: v
2267 for k, v in self.annotations.items()
2268 if k in self.annotation_select_mask
2269 }
2270 return self._annotation_select_cache
2271 else:
2272 return self.annotations
2273
2274 def trim_start(
2275 self, names_with_path: list[tuple[str, list[Any]]]
2276 ) -> tuple[str, bool]:
2277 """
2278 Trim joins from the start of the join path. The candidates for trim
2279 are the PathInfos in names_with_path structure that are m2m joins.
2280
2281 Also set the select column so the start matches the join.
2282
2283 This method is meant to be used for generating the subquery joins &
2284 cols in split_exclude().
2285
2286 Return a lookup usable for doing outerq.filter(lookup=self) and a
2287 boolean indicating if the joins in the prefix contain a LEFT OUTER join.
2288 _"""
2289 all_paths = []
2290 for _, paths in names_with_path:
2291 all_paths.extend(paths)
2292 contains_louter = False
2293 # Trim and operate only on tables that were generated for
2294 # the lookup part of the query. That is, avoid trimming
2295 # joins generated for F() expressions.
2296 lookup_tables = [
2297 t for t in self.alias_map if t in self._lookup_joins or t == self.base_table
2298 ]
2299 for trimmed_paths, path in enumerate(all_paths):
2300 if path.m2m:
2301 break
2302 if self.alias_map[lookup_tables[trimmed_paths + 1]].join_type == LOUTER:
2303 contains_louter = True
2304 alias = lookup_tables[trimmed_paths]
2305 self.unref_alias(alias)
2306 # The path.join_field is a Rel, lets get the other side's field
2307 join_field = path.join_field.field
2308 # Build the filter prefix.
2309 paths_in_prefix = trimmed_paths
2310 trimmed_prefix = []
2311 for name, path in names_with_path:
2312 if paths_in_prefix - len(path) < 0:
2313 break
2314 trimmed_prefix.append(name)
2315 paths_in_prefix -= len(path)
2316 trimmed_prefix.append(join_field.target_field.name)
2317 trimmed_prefix = LOOKUP_SEP.join(trimmed_prefix)
2318 # Lets still see if we can trim the first join from the inner query
2319 # (that is, self). We can't do this for:
2320 # - LEFT JOINs because we would miss those rows that have nothing on
2321 # the outer side.
2322 first_join = self.alias_map[lookup_tables[trimmed_paths + 1]]
2323 if first_join.join_type != LOUTER:
2324 select_fields = [join_field]
2325 select_alias = lookup_tables[trimmed_paths + 1]
2326 self.unref_alias(lookup_tables[trimmed_paths])
2327 else:
2328 # TODO: It might be possible to trim more joins from the start of the
2329 # inner query if it happens to have a longer join chain containing the
2330 # values in select_fields. Lets punt this one for now.
2331 select_fields = [join_field.target_field]
2332 select_alias = lookup_tables[trimmed_paths]
2333 # The found starting point is likely a join_class instead of a
2334 # base_table_class reference. But the first entry in the query's FROM
2335 # clause must not be a JOIN.
2336 for table in self.alias_map:
2337 if self.alias_refcount[table] > 0:
2338 self.alias_map[table] = self.base_table_class(
2339 self.alias_map[table].table_name,
2340 table,
2341 )
2342 break
2343 self.set_select([f.get_col(select_alias) for f in select_fields])
2344 return trimmed_prefix, contains_louter
2345
2346 def is_nullable(self, field: Field) -> bool:
2347 """Check if the given field should be treated as nullable."""
2348 # QuerySet does not have knowledge of which connection is going to be
2349 # used. For the single-database setup we always reference the default
2350 # connection here.
2351 if not isinstance(field, ColumnField):
2352 return False
2353 return field.allow_null
2354
2355
2356def get_order_dir(field: str, default: str = "ASC") -> tuple[str, str]:
2357 """
2358 Return the field name and direction for an order specification. For
2359 example, '-foo' is returned as ('foo', 'DESC').
2360
2361 The 'default' param is used to indicate which way no prefix (or a '+'
2362 prefix) should sort. The '-' prefix always sorts the opposite way.
2363 """
2364 dirn = ORDER_DIR[default]
2365 if field[0] == "-":
2366 return field[1:], dirn[1]
2367 return field, dirn[0]
2368
2369
2370class JoinPromoter:
2371 """
2372 A class to abstract away join promotion problems for complex filter
2373 conditions.
2374 """
2375
2376 def __init__(self, connector: str, num_children: int, negated: bool):
2377 self.connector = connector
2378 self.negated = negated
2379 if self.negated:
2380 if connector == AND:
2381 self.effective_connector = OR
2382 else:
2383 self.effective_connector = AND
2384 else:
2385 self.effective_connector = self.connector
2386 self.num_children = num_children
2387 # Maps of table alias to how many times it is seen as required for
2388 # inner and/or outer joins.
2389 self.votes = Counter()
2390
2391 def __repr__(self) -> str:
2392 return (
2393 f"{self.__class__.__qualname__}(connector={self.connector!r}, "
2394 f"num_children={self.num_children!r}, negated={self.negated!r})"
2395 )
2396
2397 def add_votes(self, votes: Any) -> None:
2398 """
2399 Add single vote per item to self.votes. Parameter can be any
2400 iterable.
2401 """
2402 self.votes.update(votes)
2403
2404 def update_join_types(self, query: Query) -> set[str]:
2405 """
2406 Change join types so that the generated query is as efficient as
2407 possible, but still correct. So, change as many joins as possible
2408 to INNER, but don't make OUTER joins INNER if that could remove
2409 results from the query.
2410 """
2411 to_promote = set()
2412 to_demote = set()
2413 # The effective_connector is used so that NOT (a AND b) is treated
2414 # similarly to (a OR b) for join promotion.
2415 for table, votes in self.votes.items():
2416 # We must use outer joins in OR case when the join isn't contained
2417 # in all of the joins. Otherwise the INNER JOIN itself could remove
2418 # valid results. Consider the case where a model with rel_a and
2419 # rel_b relations is queried with rel_a__col=1 | rel_b__col=2. Now,
2420 # if rel_a join doesn't produce any results is null (for example
2421 # reverse foreign key or null value in direct foreign key), and
2422 # there is a matching row in rel_b with col=2, then an INNER join
2423 # to rel_a would remove a valid match from the query. So, we need
2424 # to promote any existing INNER to LOUTER (it is possible this
2425 # promotion in turn will be demoted later on).
2426 if self.effective_connector == OR and votes < self.num_children:
2427 to_promote.add(table)
2428 # If connector is AND and there is a filter that can match only
2429 # when there is a joinable row, then use INNER. For example, in
2430 # rel_a__col=1 & rel_b__col=2, if either of the rels produce NULL
2431 # as join output, then the col=1 or col=2 can't match (as
2432 # NULL=anything is always false).
2433 # For the OR case, if all children voted for a join to be inner,
2434 # then we can use INNER for the join. For example:
2435 # (rel_a__col__icontains=Alex | rel_a__col__icontains=Russell)
2436 # then if rel_a doesn't produce any rows, the whole condition
2437 # can't match. Hence we can safely use INNER join.
2438 if self.effective_connector == AND or (
2439 self.effective_connector == OR and votes == self.num_children
2440 ):
2441 to_demote.add(table)
2442 # Finally, what happens in cases where we have:
2443 # (rel_a__col=1|rel_b__col=2) & rel_a__col__gte=0
2444 # Now, we first generate the OR clause, and promote joins for it
2445 # in the first if branch above. Both rel_a and rel_b are promoted
2446 # to LOUTER joins. After that we do the AND case. The OR case
2447 # voted no inner joins but the rel_a__col__gte=0 votes inner join
2448 # for rel_a. We demote it back to INNER join (in AND case a single
2449 # vote is enough). The demotion is OK, if rel_a doesn't produce
2450 # rows, then the rel_a__col__gte=0 clause can't be true, and thus
2451 # the whole clause must be false. So, it is safe to use INNER
2452 # join.
2453 # Note that in this example we could just as well have the __gte
2454 # clause and the OR clause swapped. Or we could replace the __gte
2455 # clause with an OR clause containing rel_a__col=1|rel_a__col=2,
2456 # and again we could safely demote to INNER.
2457 query.promote_joins(to_promote)
2458 query.demote_joins(to_demote)
2459 return to_demote
2460
2461
2462# ##### Query subclasses (merged from subqueries.py) #####
2463
2464
2465class DeleteQuery(Query):
2466 """A DELETE SQL query."""
2467
2468 # Concrete fields to emit in a RETURNING clause, or None for a plain DELETE.
2469 returning_fields: list[Field] | None = None
2470
2471 def get_compiler(self, *, elide_empty: bool = True) -> SQLDeleteCompiler:
2472 from plain.postgres.sql.compiler import SQLDeleteCompiler
2473
2474 return SQLDeleteCompiler(self, get_connection(), elide_empty)
2475
2476 def do_query(self, table: str, where: Any) -> int:
2477 from plain.postgres.sql.constants import CURSOR
2478
2479 self.alias_map = {table: self.alias_map[table]}
2480 self.where = where
2481 # The compiler returns the deleted row count directly (this query never
2482 # carries returning_fields).
2483 return self.get_compiler().execute_sql(CURSOR)
2484
2485 def delete_batch(self, id_list: list[Any]) -> int:
2486 """
2487 Set up and execute delete queries for all the objects in id_list.
2488
2489 More than one physical query may be executed if there are a
2490 lot of values in id_list.
2491 """
2492 from plain.postgres.sql.constants import GET_ITERATOR_CHUNK_SIZE
2493
2494 # number of objects deleted
2495 num_deleted = 0
2496 assert self.model is not None, "DELETE requires a model"
2497 meta = self.model._model_meta
2498 field = meta.get_forward_field("id")
2499 for offset in range(0, len(id_list), GET_ITERATOR_CHUNK_SIZE):
2500 self.clear_where()
2501 self.add_filter(
2502 f"{field.name}__in",
2503 id_list[offset : offset + GET_ITERATOR_CHUNK_SIZE],
2504 )
2505 num_deleted += self.do_query(self.model.model_options.db_table, self.where)
2506 return num_deleted
2507
2508
2509class UpdateQuery(Query):
2510 """An UPDATE SQL query."""
2511
2512 # Concrete fields to emit in a RETURNING clause, or None for a plain UPDATE.
2513 returning_fields: list[Field] | None = None
2514
2515 def get_compiler(self, *, elide_empty: bool = True) -> SQLUpdateCompiler:
2516 from plain.postgres.sql.compiler import SQLUpdateCompiler
2517
2518 return SQLUpdateCompiler(self, get_connection(), elide_empty)
2519
2520 def __init__(self, *args: Any, **kwargs: Any) -> None:
2521 super().__init__(*args, **kwargs)
2522 self._setup_query()
2523
2524 def _setup_query(self) -> None:
2525 """
2526 Run on initialization and at the end of chaining. Any attributes that
2527 would normally be set in __init__() should go here instead.
2528 """
2529 self.values: list[tuple[Any, Any]] = []
2530
2531 def update_batch(self, id_list: list[Any], values: dict[str, Any]) -> None:
2532 from plain.postgres.sql.constants import GET_ITERATOR_CHUNK_SIZE, NO_RESULTS
2533
2534 self.add_update_values(values)
2535 for offset in range(0, len(id_list), GET_ITERATOR_CHUNK_SIZE):
2536 self.clear_where()
2537 self.add_filter(
2538 "id__in", id_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]
2539 )
2540 self.get_compiler().execute_sql(NO_RESULTS)
2541
2542 def add_update_values(self, values: dict[str, Any]) -> None:
2543 """
2544 Convert a dictionary of field name to value mappings into an update
2545 query. This is the entry point for the public update() method on
2546 querysets.
2547 """
2548
2549 assert self.model is not None, "UPDATE requires model metadata"
2550 meta = self.model._model_meta
2551 values_seq = []
2552 for name, val in values.items():
2553 field = meta.get_field(name)
2554 from plain.postgres.fields.related import ManyToManyField
2555
2556 if isinstance(field, ManyToManyField):
2557 raise FieldError(
2558 f"Cannot update model field {field!r} (only non-relations and "
2559 "foreign keys permitted)."
2560 )
2561 values_seq.append((field, val))
2562 return self.add_update_fields(values_seq)
2563
2564 def add_update_fields(self, values_seq: Sequence[tuple[Any, Any]]) -> None:
2565 """
2566 Append a sequence of (field, value) pairs to the internal list that
2567 will be used to generate the UPDATE query.
2568 """
2569 for field, val in values_seq:
2570 if isinstance(val, ResolvableExpression):
2571 # Resolve expressions here so that annotations are no longer needed
2572 val = val.resolve_expression(self, allow_joins=False, for_save=True)
2573 self.values.append((field, val))
2574
2575
2576class InsertQuery(Query):
2577 def get_compiler(self, *, elide_empty: bool = True) -> SQLInsertCompiler:
2578 from plain.postgres.sql.compiler import SQLInsertCompiler
2579
2580 return SQLInsertCompiler(self, get_connection(), elide_empty)
2581
2582 def __str__(self) -> str:
2583 raise NotImplementedError(
2584 "InsertQuery does not support __str__(). "
2585 "Use get_compiler().as_sql() which returns a list of SQL statements."
2586 )
2587
2588 def sql_with_params(self) -> Any:
2589 raise NotImplementedError(
2590 "InsertQuery does not support sql_with_params(). "
2591 "Use get_compiler().as_sql() which returns a list of SQL statements."
2592 )
2593
2594 def __init__(
2595 self,
2596 *args: Any,
2597 on_conflict: OnConflict | None = None,
2598 update_fields: list[Field] | None = None,
2599 unique_fields: list[Field] | None = None,
2600 conflict_defaults: dict[Field, Any] | None = None,
2601 returning_created: bool = False,
2602 **kwargs: Any,
2603 ) -> None:
2604 super().__init__(*args, **kwargs)
2605 self.fields: Sequence[Field] = []
2606 self.objs: list[Any] = []
2607 self.on_conflict = on_conflict
2608 self.update_fields: list[Field] = update_fields or []
2609 self.unique_fields: list[Field] = unique_fields or []
2610 # Per-column DO UPDATE SET overrides (upsert conflict_defaults) and the
2611 # flag for the trailing "(xmax = 0)" created column in RETURNING.
2612 self.conflict_defaults: dict[Field, Any] = conflict_defaults or {}
2613 self.returning_created: bool = returning_created
2614 # Raised by the compiler only while it compiles the DO UPDATE SET
2615 # assignments, which is the one place EXCLUDED means anything.
2616 self.compiling_conflict_assignment: bool = False
2617
2618 def insert_values(self, fields: Sequence[Any], objs: list[Any]) -> None:
2619 self.fields = fields
2620 self.objs = objs
2621
2622
2623class AggregateQuery(Query):
2624 """
2625 Take another query as a parameter to the FROM clause and only select the
2626 elements in the provided list.
2627 """
2628
2629 def get_compiler(self, *, elide_empty: bool = True) -> SQLAggregateCompiler:
2630 from plain.postgres.sql.compiler import SQLAggregateCompiler
2631
2632 return SQLAggregateCompiler(self, get_connection(), elide_empty)
2633
2634 def __init__(self, model: Any, inner_query: Any) -> None:
2635 self.inner_query = inner_query
2636 super().__init__(model)