v0.166.0
   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)