Chapter 10 · From QuerySet to SQL
10.4 Tables and joins: the alias map and join promotion
Query.alias_map is the query’s record of every table its statement joins, each under an alias and each but the first a Join from a table already there; Query.join adds a join to it or reuses one, Query names it, and Query counts what refers to it. Whether a join is written "INNER JOIN" or "LEFT OUTER JOIN" is decided by the conditions together, through JoinPromoter, Query and Query; Query and Query rename the aliases of a query that is to stand inside another.
A statement reads one table and joins others to it, and the query has to keep that list as conditions, selected columns and an ordering add to it. A table may be joined more than once, so a table is known by an alias, and the first alias of a table is its own name, unless the join carries a filtered relation. Each join hangs from a table already in the list, along a field, so that the compiler can write what the join’s "ON" compares. A join made for a condition that is later dropped, or made by the compiler for an ordering, must not be written and must not be taken out either, since the query is read again at the next evaluation, so each alias carries a count of what refers to it. Whether a join is inner or outer cannot be settled when the join is made: the ships with no captain must survive Q(captain__name="Cora") | Q(tonnage__lt=500) and need not survive captain__name="Cora" alone, and the same join serves both. And a query put inside another must share no alias with it. The alias map, the counts and the promoter are the query’s answers; the compiler reads the map once, when it writes the "FROM" clause.
Where a join comes from, the walk of a name into a path and a Join for each step of it, is From a name to a column: names_to_path, setup_joins and build_lookup; what a condition does with a join’s alias, and the subquery an exclude across a multi-valued relation becomes, is The where clause: build_filter, add_q and WhereNode.
The alias map
Query.alias_map is a dictionary from each alias to its entry, in the order the entries were made, except that a join whose filtered relation joined tables of its own is moved after them, and that renaming through change_aliases moves the renamed entries after any that were excluded; the first entry is the table the statement selects from. That entry is a BaseTable; every later one is a Join, which hangs from an earlier alias. Query.base_table is the first key, kept as a cached property that clone drops so that a clone works it out again, and Query is how a method that is about to join gets it: the first key, with its count raised by one, or, on a query that has no entry yet, a BaseTable of the model’s table put in through join. Beside the map the query keeps Query.table_map, from each table’s name to the list of its aliases, which is how table_alias knows whether a table is already in the map; Query, from each alias to the count of what refers to it; and Query, the aliases that the current call of filter has made, which Query.chain empties between one call and the next unless the filter is sticky (The query: what a Query holds, and how it is copied, Clones: how a queryset is built).
Recorded from a running Django on SQLite: in the lines below, once is a queryset of voyages with two conditions on their ports of call in one call of filter, calls__name="Leith" and calls__country="Scotland"; an alias_map line writes out one entry of the map, in the recording’s words for a join, with the count of references to its alias and with the join type as the filter’s conditions have left it, the work of JoinPromoter; and a line that asks a question gives its answer after ->, tail being the statement from its "FROM" on.
alias_map['fleet_voyage']: BaseTable(fleet_voyage), referenced 2
alias_map['fleet_voyage_calls']: Join(fleet_voyage_calls, from fleet_voyage via the rel of Voyage_calls.voyage, INNER JOIN, nullable), referenced 2
alias_map['fleet_port']: Join(fleet_port, from fleet_voyage_calls via Voyage_calls.port, INNER JOIN, not nullable), referenced 2
tail(once) -> ('FROM "fleet_voyage" INNER JOIN "fleet_voyage_calls" ON ("fleet_voyage"."id" = "fleet_voyage_calls"."voyage_id") INNER JOIN "fleet_port" ON ("fleet_voyage_calls"."port_id" = "fleet_port"."id") WHERE ("fleet_port"."country" = %s AND "fleet_port"."name" = %s)', ('Scotland', 'Leith'))
The many-to-many field made two joins of one word, the table between and the ports’ table, and the second condition reused both, so that each of the two conditions is on the same joined port: a voyage that called at Leith and at some Scottish port other than Leith does not match. The joins hang one from the other, and the chain is what the "FROM" clause writes.
Join and BaseTable
A Join holds what the compiler needs to write one "JOIN" clause: the name of the table joined, the alias it is joined under, the alias of the table it hangs from, the join type, the field or rel the join is made along, whether the join is nullable, and the filtered relation it carries, where it carries one. From the field it takes, as it is made, the pairs of fields the "ON" clause compares, through the field’s get_joining_fields, a pair for each column of the key, and keeps their column names as Join.join_cols; a rel answers with its field’s pairs the other way round. The field’s side of the pairs is in Relations: a field, its rel and the class at the other end.
Join.as_sql writes the clause. For each pair it asks the backend’s prepare_join_on_clause for the two sides as expressions, a Col of each alias and field in the base implementation, compiles them and writes them with = between. After the pairs it adds, in parentheses, whatever the field’s get_extra_restriction returns, a hook that returns None on ForeignObject and that a relation of django uses to restrict a join to one content type, and then the filtered relation’s condition, compiled, unless the condition matches every row. An "ON" clause with nothing in it is refused with ValueError. The clause is the join type, the table’s name quoted, the alias quoted where it differs from the table’s name, and the conditions joined by "AND" in parentheses.
Two joins are the same join when their Join.identity agrees: the class, the table’s name, the alias they hang from, the field they are made along, and the filtered relation, compared through FilteredRelation. The alias of the join itself is not in it, nor the join type, nor whether it is nullable, so a Join made afresh for a second condition equals the one already in the map, which is what lets join reuse it. Join returns a copy with the aliases renamed through a mapping, and Join.promote and Join.demote return a copy made outer and a copy made inner: a join’s type and aliases are never changed in place once it is in the map, and the entry is replaced by a copy instead, which is why a clone of a query can share the Join objects of its original (The query: what a Query holds, and how it is copied); the one thing written on an entry after it is in the map is the filtered relation join resolves at once.
A Join and a BaseTable: what each holds, and what makes two joins equal
j = twice.query.alias_map['T4']
j.table_name, j.parent_alias, j.table_alias, j.join_type, j.join_cols, j.nullable, j.filtered_relation, brief(j.join_field) -> ('fleet_voyage_calls', 'fleet_voyage', 'T4', 'INNER JOIN', (('id', 'voyage_id'),), True, None, 'the rel of Voyage_calls.voyage')
j.identity == twice.query.alias_map['fleet_voyage_calls'].identity, j == twice.query.alias_map['fleet_voyage_calls'], j.relabeled_clone({'T4': 'T9'}).table_alias, j.promote().join_type, j.demote().join_type -> (True, True, 'T9', 'LEFT OUTER JOIN', 'INNER JOIN')
twice.query.alias_map['fleet_voyage'].identity, twice.query.alias_map['fleet_voyage'].join_type, twice.query.alias_map['fleet_voyage'].parent_alias -> ((<class 'django.db.models.sql.datastructures.BaseTable'>, 'fleet_voyage', 'fleet_voyage'), None, None)
twice.query.alias_refcount['fleet_voyage'], twice.query.base_table, twice.query.get_initial_alias(), twice.query.alias_refcount['fleet_voyage'] -> (2, 'fleet_voyage', 'fleet_voyage', 3)
twice.query.count_active_tables() -> 5
Here j is the entry under "T4" of twice, a queryset of voyages filtered across their ports of call in two calls of filter, which joined the same two tables twice. The first questions ask what it holds, and whether it equals the first join of its table: it does, the alias apart; the relabelled clone carries the new alias, the promoted copy is outer and the demoted copy inner, as j was already, and each is a new object. A BaseTable holds only a table’s name and its alias, and its identity is those two with the class; it writes the name, and the alias after it where the two differ. The question that reads the base table’s count twice reads it before and after its own get_initial_alias, which raised it by one. Query is the number of aliases whose count is not zero, which the update compiler asks before it decides whether an update can be written against one table (INSERT, UPDATE and DELETE: the insert, update and delete compilers).
table_alias and join: an alias, and when a join is reused
Query gives a table an alias. Asked to create one, it gives a table that is not yet in table_map the table’s own name, or the alias of the filtered relation where the join carries one; a table that is already there gets the query’s prefix, "T" unless bump_prefix has changed it, followed by a number one more than the entries the map has, which is why the recording’s second join of the table between is "T4" in a map of three entries. Either way the new alias starts with a count of one. Asked without creating, it returns the table’s first alias and raises its count where the table has one, and makes one otherwise; that is what get_from_clause does for an extra table.
Query.join is given a Join or a BaseTable and returns its alias, after either reusing an entry of the map or adding one. It looks for entries that equal the join, among all of them where it is given no set of reusable aliases and among the set’s where it is given one; where there are any, it takes the one under the join’s own alias where the join came with one, as a join made for a filtered relation does, and the most recent otherwise, since, the comment says, a many-to-many relation may be joined several times, raises that alias’s count, and returns it. Otherwise it has table_alias make an alias, settles the join’s first type, puts the join in the map under the alias and, where the join carries a filtered relation, has the relation resolve its condition against the query, with the new alias among what the resolving may reuse; where resolving added joins of its own, the entry is moved to the end of the map, so that it is written after the joins it depends on. What the first type is, and what changes it, is the subject of the section on promotion.
Which joins may be reused is decided by the caller, and setup_joins is the caller that matters: for a step of a path that can reach several rows for one row of the model it leaves, a reverse foreign key or a many-to-many relation, it passes the set it was given, and for any other step it passes None, so that an equal join is always reused. The set, for a filter, is used_aliases, and build_filter adds to it the joins each condition settled, so that the second condition of one filter call reuses the first’s join across a many-valued relation, as above, and a condition in a later call does not: chain emptied the set between the calls. Here twice is the same two conditions as once, in two calls of filter:
alias_map['fleet_voyage_calls']: Join(fleet_voyage_calls, from fleet_voyage via the rel of Voyage_calls.voyage, INNER JOIN, nullable), referenced 1
alias_map['fleet_port']: Join(fleet_port, from fleet_voyage_calls via Voyage_calls.port, INNER JOIN, not nullable), referenced 1
alias_map['T4']: Join(fleet_voyage_calls, from fleet_voyage via the rel of Voyage_calls.voyage, INNER JOIN, nullable), referenced 1
alias_map['T5']: Join(fleet_port, from T4 via Voyage_calls.port, INNER JOIN, not nullable), referenced 1
twice.query.table_map, sorted(twice.query.used_aliases) -> ({'fleet_voyage': ['fleet_voyage'], 'fleet_voyage_calls': ['fleet_voyage_calls', 'T4'], 'fleet_port': ['fleet_port', 'T5']}, ['T4', 'T5', 'fleet_voyage'])
tail(twice) -> ('FROM "fleet_voyage" INNER JOIN "fleet_voyage_calls" ON ("fleet_voyage"."id" = "fleet_voyage_calls"."voyage_id") INNER JOIN "fleet_port" ON ("fleet_voyage_calls"."port_id" = "fleet_port"."id") INNER JOIN "fleet_voyage_calls" "T4" ON ("fleet_voyage"."id" = "T4"."voyage_id") INNER JOIN "fleet_port" "T5" ON ("T4"."port_id" = "T5"."id") WHERE ("fleet_port"."name" = %s AND "T5"."country" = %s)', ('Leith', 'Scotland'))
The second call joined the table between and the ports’ table again, under "T4" and "T5", and its condition stands on "T5": a voyage that called at Leith and at any Scottish port matches. table_map lists both aliases of each table, and used_aliases holds what the second call made, the base table and its two joins. Where a many-to-many manager’s queryset is filtered, the set is carried over one call so that the filter shares the manager’s own join, and that is the sticky filter of Clones: how a queryset is built. A join along a foreign key, which reaches one row, is reused across calls as within one: a condition on a sailor’s ship’s name and, in a second call of filter, one on the ship’s tonnage share one join of the ships’ table.
alias_map['crew_sailor']: BaseTable(crew_sailor), referenced 2
alias_map['fleet_ship']: Join(fleet_ship, from crew_sailor via Sailor.ship, INNER JOIN, not nullable), referenced 2
Reference counts
Each alias has a count in Query, raised by Query.ref_alias and lowered by Query, and the count says how many things stand on the alias: join raises the count of an alias it reuses, table_alias starts a new one at one, and get_initial_alias raises the base table’s each time a caller starts a walk from it: once for each condition of a filter, once for each F that names a path, once for all the names of a values. A count, not a flag: an alias is referred to by several things, and let go of at several times. trim_joins lowers the count of a join it takes back, and the join stays in the map, with a count of zero where nothing else referred to it (From a name to a column: names_to_path, setup_joins and build_lookup). build_filter makes its joins before it knows whether the condition will be kept, and its docstring says that a caller which does not add the clause it returns is responsible for unreferencing the joins it used. And the compiler joins tables of its own for an ordering or a selected relation when the statement is written, and puts every count back to what it was, through Query, before as_sql returns, so that the query is as it was for the next evaluation (Query, The compiler: as_sql, execute_sql and results_iter). reset_refcounts is given the counts to go back to and lowers each alias’s count by the difference. A join whose count is zero is left out of the statement by SQLCompiler.
Join promotion
A join starts with a type and the conditions change it. The rule the compiler needs is this: a row of the base table that has no row on the far side of a join is lost under an inner join and kept, with nulls, under an outer one; a condition on the far side’s columns cannot be true of nulls, so a join such a condition stands on may be inner where the condition must hold, and must be outer where the row may match some other way. Three methods carry it.
The first type, at join. A new join is outer where the join it hangs from is outer or the join is nullable, and inner otherwise; the docstring of join says that a join is always made outer when its left side is outer, to make sure chains like an outer join followed by an inner one are not made. Nullable is what setup_joins decided from the path, a reverse step or a step along a key that Query says may be null (From a name to a column: names_to_path, setup_joins and build_lookup). So the join a ship makes to its captain, a one-to-one that may be null, and the join a ship makes to its crew, a reverse relation, both start outer, and the join a sailor makes to its ship, a key that may not be null, starts inner. A join that no condition stands on keeps the type it started with: an annotation of F("captain__name") leaves the join outer, and the ships with no captain are in the result with a null for the name.
promote_joins and demote_joins. Query is given aliases and makes each outer, where the join is nullable or the join it hangs from is outer and it is not outer already, skipping the base table; when it has promoted one it adds to its list every join that hangs from it, so that no inner join is left under an outer one, since, the docstring says, promoting the first without the second would change nothing in the result. Query makes each outer join among its aliases inner. Its docstring says the join a demoted join hangs from must be demoted too, so that no outer join is left above an inner one; what the method does is look at that join and add it to its list only where it is inner already, so an outer join above is not demoted here. A demotion by votes reaches it all the same, since a condition’s vote names every join on its path.
JoinPromoter: the votes. Query._add_q makes a JoinPromoter for each node of the Q it walks, with the node’s connector, the number of its children, and whether the node stands under an odd number of negations, its own counted, and after it has built the children’s clauses it has the promoter settle the joins, unless the caller asked it not to, as a filtered relation’s condition does. Each child’s clause comes back from build_filter with a vote: the aliases the condition needs a row at, which are every alias on its name’s path, the base table and the joins that were trimmed included, and those a value such as an F joined; or no aliases at all where the condition must be allowed a missing row: an "isnull" of True that is not negated, or, under a negation, any comparison but an "isnull" of True, to which build_filter, unless the comparison is itself an "isnull", adds an "IS NOT NULL" where the column may be null or the join it stands on is outer (The where clause: build_filter, add_q and WhereNode). A child that is a Q of its own votes with what its own promoter settled as inner. JoinPromoter counts each vote in a collections by alias.
JoinPromoter then reads the counts against the node’s effective connector: the node’s own, or, where the node is negated, "OR" for an "AND" and "AND" for anything else, so that NOT (a AND b) is treated as a OR b and NOT (a OR b) as a AND b. Under "OR", an alias that fewer than all the children voted for is promoted, and the comment gives the reason: if one relation produces no row and another child matches, an inner join to the first would remove a valid match. Under "AND", every alias voted for is demoted, and so, under "OR", is one that every child voted for: if the relation produces no row, no child can match, and the comment says an inner join is then safe. Under "XOR" neither test is met and the joins keep their types. The method calls promote_joins and then demote_joins, and returns what it demoted, which is the node’s vote to the node above it. Last, Query.add_q, which called _add_q on the whole tree, demotes every join that was inner before the tree was added: a comment there says the new tree is joined to the existing conditions by "AND", so an existing inner join must stay inner, and an existing outer one may be demoted.
Three promoters asked for their effective connector, a negated "OR", a negated "AND" and an "OR" that is not negated:
JoinPromoter('OR', 2, True).effective_connector, JoinPromoter('AND', 2, True).effective_connector, JoinPromoter('OR', 2, False).effective_connector -> ('AND', 'OR', 'OR')
The recording has the three cases the comments give. First an "OR" whose two children do not share a join; the lines are calls nested as they were made, with only the calls this passage is about written down, and -> is what a call returned:
either = Ship.objects.filter(Q(captain__name='Cora') | Q(tonnage__lt=500))
Query.add_q(q_object=<Q: (AND: (OR: ('captain__name', 'Cora'), ('tonnage__lt', 500)))>)
Query._add_q(<Q: (AND: (OR: ('captain__name', 'Cora'), ('tonnage__lt', 500)))>)
JoinPromoter.__init__(connector='AND', num_children=1, negated=False)
Query._add_q(<Q: (OR: ('captain__name', 'Cora'), ('tonnage__lt', 500))>)
JoinPromoter.__init__(connector='OR', num_children=2, negated=False)
Query.join(BaseTable(fleet_ship)) -> 'fleet_ship'
Query.join(Join(crew_sailor, from fleet_ship via Ship.captain, INNER JOIN, nullable)) -> 'crew_sailor'
JoinPromoter.add_votes({'crew_sailor', 'fleet_ship'})
JoinPromoter.add_votes({'fleet_ship'})
JoinPromoter.update_join_types(a Query of Ship)
Query.promote_joins({'crew_sailor'})
Query.demote_joins({'fleet_ship'})
-> {'fleet_ship'}
JoinPromoter.add_votes({'fleet_ship'})
JoinPromoter.update_join_types(a Query of Ship)
Query.promote_joins(set())
Query.demote_joins({'fleet_ship'})
-> {'fleet_ship'}
Query.demote_joins(set())
alias_map['fleet_ship']: BaseTable(fleet_ship), referenced 2
alias_map['crew_sailor']: Join(crew_sailor, from fleet_ship via Ship.captain, LEFT OUTER JOIN, nullable), referenced 1
The captain’s join was made outer, nullable as it is. The condition on the captain voted for the base table and the join, the condition on the tonnage for the base table alone, so the "OR" promoted the join, which was outer already, and demoted the base table, which promote_joins and demote_joins leave as it is; it returned the base table as its vote to the "AND" above it, which demoted that alone. Then a second filter, an "AND" of one child that stands on the same join:
narrowed = either.filter(captain__signed_on__year=2011)
Query.add_q(q_object=<Q: (AND: ('captain__signed_on__year', 2011))>)
Query._add_q(<Q: (AND: ('captain__signed_on__year', 2011))>)
JoinPromoter.__init__(connector='AND', num_children=1, negated=False)
Query.join(Join(crew_sailor, from fleet_ship via Ship.captain, INNER JOIN, nullable)) -> 'crew_sailor'
JoinPromoter.add_votes({'crew_sailor', 'fleet_ship'})
JoinPromoter.update_join_types(a Query of Ship)
Query.promote_joins(set())
Query.demote_joins({'crew_sailor', 'fleet_ship'})
-> {'crew_sailor', 'fleet_ship'}
Query.demote_joins(set())
alias_map['fleet_ship']: BaseTable(fleet_ship), referenced 3
alias_map['crew_sailor']: Join(crew_sailor, from fleet_ship via Ship.captain, INNER JOIN, nullable), referenced 2
join reused the entry, and the one condition voted for it; under "AND" a vote is enough, and the join was demoted to inner. The comment in update_join_types gives this case, an "OR" followed by an "AND" on one of its relations, and says the demotion is right: if the relation produces no row, the second condition cannot be true, and the whole must be false. Last, an "OR" of two conditions on the captain’s name, negated:
neither = Ship.objects.filter(~(Q(captain__name='Cora') | Q(captain__name='Dag')))
Query.add_q(q_object=<Q: (AND: (NOT (OR: ('captain__name', 'Cora'), ('captain__name', 'Dag'))))>)
Query._add_q(<Q: (AND: (NOT (OR: ('captain__name', 'Cora'), ('captain__name', 'Dag'))))>)
JoinPromoter.__init__(connector='AND', num_children=1, negated=False)
Query._add_q(<Q: (NOT (OR: ('captain__name', 'Cora'), ('captain__name', 'Dag')))>)
JoinPromoter.__init__(connector='OR', num_children=2, negated=True)
Query.join(BaseTable(fleet_ship)) -> 'fleet_ship'
Query.join(Join(crew_sailor, from fleet_ship via Ship.captain, INNER JOIN, nullable)) -> 'crew_sailor'
JoinPromoter.add_votes(())
Query.join(Join(crew_sailor, from fleet_ship via Ship.captain, INNER JOIN, nullable)) -> 'crew_sailor'
JoinPromoter.add_votes(())
JoinPromoter.update_join_types(a Query of Ship)
Query.promote_joins(set())
Query.demote_joins(set())
-> set()
-> ((NOT (OR: (AND: Exact(Col(crew_sailor, crew.Sailor.name), 'Cora'), IsNull(Col(crew_sailor, crew.Sailor.name), False)), (AND: Exact(Col(crew_sailor, crew.Sailor.name), 'Dag'), IsNull(Col(crew_sailor, crew.Sailor.name), False)))), set())
JoinPromoter.add_votes(set())
JoinPromoter.update_join_types(a Query of Ship)
Query.promote_joins(set())
Query.demote_joins(set())
-> set()
Query.demote_joins(set())
alias_map['fleet_ship']: BaseTable(fleet_ship), referenced 2
alias_map['crew_sailor']: Join(crew_sailor, from fleet_ship via Ship.captain, LEFT OUTER JOIN, nullable), referenced 2
The promoter of the negated "OR" has "AND" as its effective connector, and each child, being under a negation, voted for nothing and had an "IS NOT NULL" added to its clause; with no votes nothing was promoted or demoted, and the join stays outer, which is what the condition needs: a ship with no captain is neither Cora’s nor Dag’s, and must be kept.
"OR" promotes a join that not every child needs; the "AND" that follows demotes it, since its one condition cannot be true of a ship with no captain.The rule to take away. A join along a key that may not be null is never promoted on its own account: promote_joins makes a join outer only where it is nullable or the join it hangs from is outer. So an "OR" whose children do not share a join leaves such a join inner, and a sailor always has a ship; it does promote one that hangs from an outer join, since a row with no captain has no captain’s ship either; and a join over a reverse relation, nullable, is left outer under such an "OR". Asked of the statements:
tail(Sailor.objects.filter(Q(ship__home__name='Bergen') | Q(name='Dag'))) -> ('FROM "crew_sailor" INNER JOIN "fleet_ship" ON ("crew_sailor"."ship_id" = "fleet_ship"."id") INNER JOIN "fleet_port" ON ("fleet_ship"."home_id" = "fleet_port"."id") WHERE ("fleet_port"."name" = %s OR "crew_sailor"."name" = %s)', ('Bergen', 'Dag'))
tail(Ship.objects.filter(Q(captain__ship__name='Petrel') | Q(name='Gannet'))) -> ('FROM "fleet_ship" LEFT OUTER JOIN "crew_sailor" ON ("fleet_ship"."captain_id" = "crew_sailor"."id") LEFT OUTER JOIN "fleet_ship" "T3" ON ("crew_sailor"."ship_id" = "T3"."id") WHERE ("T3"."name" = %s OR "fleet_ship"."name" = %s) ORDER BY "fleet_ship"."name" ASC', ('Petrel', 'Gannet'))
...
tail(Ship.objects.filter(Q(crew__name='Ada') | Q(tonnage__lt=500))) -> ('FROM "fleet_ship" LEFT OUTER JOIN "crew_sailor" ON ("fleet_ship"."id" = "crew_sailor"."ship_id") WHERE ("crew_sailor"."name" = %s OR "fleet_ship"."tonnage" < %s) ORDER BY "fleet_ship"."name" ASC', ('Ada', 500))
Renaming aliases: change_aliases and bump_prefix
A query that is to stand inside another, as a subquery or as the right side of combine, must share no alias with it, and the two name their tables the same way. Query is the letter new aliases take, "T" on the class, and Query the set of letters in use between a query and those inside it, {"T"} to begin with. Query is given the outer query and changes the inner query’s prefix to a letter not in its own subq_aliases, and tells the outer query of it, so that each query in a nest has a prefix none of the others has. Where the two prefixes already differ it does nothing. Otherwise it takes the first prefix after its own letter that is not in that set, from a generator defined inside the method that yields the letters after its own, then pairs of letters, and so on; past a sixteenth of Python’s recursion limit it raises RecursionError, the comment saying the divider is how much depth a nested subquery adds to the stack. The letter is added to both queries’ subq_aliases, and every alias of the map, except those the caller excludes, is renamed to the letter and the alias’s position in the map. Here left and right are queries of voyages with one condition on their ports of call each, and bumped is a clone of right bumped against left with the base table excluded:
bump_prefix and change_aliases: the aliases of a query that is to stand inside another are renamed to a prefix the outer query does not use
list(left.alias_map), list(right.alias_map), right.alias_prefix, sorted(right.subq_aliases) -> (['fleet_voyage', 'fleet_voyage_calls', 'fleet_port'], ['fleet_voyage', 'fleet_voyage_calls', 'fleet_port'], 'T', ['T'])
list(bumped.alias_map), bumped.alias_prefix, sorted(bumped.subq_aliases), sorted(left.subq_aliases) -> (['fleet_voyage', 'U1', 'U2'], 'U', ['T', 'U'], ['T', 'U'])
bumped.table_map, bumped.alias_refcount, said(bumped.where) -> ({'fleet_voyage': ['fleet_voyage'], 'fleet_voyage_calls': ['U1'], 'fleet_port': ['U2']}, {'fleet_voyage': 1, 'U1': 1, 'U2': 1}, (AND: Exact(Col(U2, fleet.Port.name), 'Lisbon')))
list(right.relabeled_clone({'fleet_voyage_calls': 'C1', 'fleet_port': 'C2'}).alias_map), str(right.relabeled_clone({'fleet_voyage_calls': 'C1', 'fleet_port': 'C2'}).where) -> (['fleet_voyage', 'C1', 'C2'], "(AND: Exact(Col(C2, fleet.Port.name), 'Lisbon'))")
What is excluded is the caller’s choice: combine excludes the base table, and the comment there says it must be present in the query on both sides; a query resolved as a subquery excludes nothing (Two queries into one: combine and get_combinator_sql, Subqueries: Subquery, Exists, OuterRef and a query inside another).
Query does the renaming, given a mapping from old alias to new. It asserts that no alias is both a key and a value of the mapping, since an alias might otherwise be renamed twice, as the comment says. Then it relabels every place an alias is written: the where tree, through WhereNode, which has each lookup make a relabelled clone of itself; the grouping, where it is a tuple; the select; and the annotations. The lookups go through copies, and the clones of the query, which share its lookups, are left as they were (The where clause: build_filter, add_q and WhereNode). Then the map itself: each entry under an old alias is replaced by its relabeled_clone under the new, with its count moved and table_map corrected. Last it renames Query, the aliases of an outer query that a subquery refers to, marking each renamed one as aliased, and passes the part of the mapping that each combined query refers to down to it (Subqueries: Subquery, Exists, OuterRef and a query inside another). Query is a clone with the renaming applied, as the last line of the recording above shows, and is what an expression’s relabeled_clone does to a query it holds (The query: what a Query holds, and how it is copied).
get_from_clause: the map written out
SQLCompiler reads the map when the statement is written and returns the pieces of the "FROM" clause with their parameters: for each entry whose count is not zero, in the map’s order, what BaseTable.as_sql or Join.as_sql writes. Its docstring says it must be called after everything that may add a table, the select, the ordering and the distinct, and SQLCompiler calls it after those (The compiler: as_sql, execute_sql and results_iter). After the entries come the tables of Query, those a queryset’s extra named: each is given an alias by table_alias without creating one, and appended after a comma, quoted, unless the table is already joined and written, which the method tells by a count above one; a join of the table that is in the map with a count of zero is written as a bare table this way.
sql_of(Sailor.objects.extra(tables=['fleet_ship'], where=['fleet_ship.id = crew_sailor.ship_id'])) -> ('SELECT "crew_sailor"."id", "crew_sailor"."name", "crew_sailor"."ship_id", "crew_sailor"."signed_on" FROM "crew_sailor" , "fleet_ship" WHERE (fleet_ship.id = crew_sailor.ship_id)', ())
The extra table is written after a comma, with no "ON", and the condition the program wrote against it is taken as it is (The where clause: build_filter, add_q and WhereNode).