diff options
Diffstat (limited to 'django/db/models/sql')
| -rw-r--r-- | django/db/models/sql/query.py | 50 |
1 files changed, 36 insertions, 14 deletions
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 110e3179d5..d906cb1132 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -445,8 +445,6 @@ class Query(object): "Cannot combine a unique query with a non-unique query." self.remove_inherited_models() - l_tables = set([a for a in self.tables if self.alias_refcount[a]]) - r_tables = set([a for a in rhs.tables if rhs.alias_refcount[a]]) # Work out how to relabel the rhs aliases, if necessary. change_map = {} used = set() @@ -471,16 +469,27 @@ class Query(object): # all joins exclusive to either the lhs or the rhs must be converted # to an outer join. if not conjunction: + l_tables = set(self.tables) + r_tables = set(rhs.tables) # Update r_tables aliases. for alias in change_map: if alias in r_tables: - r_tables.remove(alias) - r_tables.add(change_map[alias]) + # r_tables may contain entries that have a refcount of 0 + # if the query has references to a table that can be + # trimmed because only the foreign key is used. + # We only need to fix the aliases for the tables that + # actually have aliases. + if rhs.alias_refcount[alias]: + r_tables.remove(alias) + r_tables.add(change_map[alias]) # Find aliases that are exclusive to rhs or lhs. # These are promoted to outer joins. - outer_aliases = (l_tables | r_tables) - (l_tables & r_tables) - for alias in outer_aliases: - self.promote_alias(alias, True) + outer_tables = (l_tables | r_tables) - (l_tables & r_tables) + for alias in outer_tables: + # Again, some of the tables won't have aliases due to + # the trimming of unnecessary tables. + if self.alias_refcount.get(alias) or rhs.alias_refcount.get(alias): + self.promote_alias(alias, True) # Now relabel a copy of the rhs where-clause and add it to the current # one. @@ -668,7 +677,7 @@ class Query(object): False, the join is only promoted if it is nullable, otherwise it is always promoted. - Returns True if the join was promoted. + Returns True if the join was promoted by this call. """ if ((unconditional or self.alias_map[alias][NULLABLE]) and self.alias_map[alias][JOIN_TYPE] != self.LOUTER): @@ -1076,17 +1085,20 @@ class Query(object): can_reuse) return + table_promote = False + join_promote = False + if (lookup_type == 'isnull' and value is True and not negate and len(join_list) > 1): # If the comparison is against NULL, we may need to use some left # outer joins when creating the join chain. This is only done when # needed, as it's less efficient at the database level. self.promote_alias_chain(join_list) + join_promote = True # Process the join list to see if we can remove any inner joins from # the far end (fewer tables in a query is better). col, alias, join_list = self.trim_joins(target, join_list, last, trim) - if connector == OR: # Some joins may need to be promoted when adding a new filter to a # disjunction. We walk the list of new joins and where it diverges @@ -1096,19 +1108,29 @@ class Query(object): join_it = iter(join_list) table_it = iter(self.tables) join_it.next(), table_it.next() - table_promote = False - join_promote = False + unconditional = False for join in join_it: table = table_it.next() + # Once we hit an outer join, all subsequent joins must + # also be promoted, regardless of whether they have been + # promoted as a result of this pass through the tables. + unconditional = (unconditional or + self.alias_map[join][JOIN_TYPE] == self.LOUTER) if join == table and self.alias_refcount[join] > 1: + # We have more than one reference to this join table. + # This means that we are dealing with two different query + # subtrees, so we don't need to do any join promotion. continue - join_promote = self.promote_alias(join) + join_promote = join_promote or self.promote_alias(join, unconditional) if table != join: table_promote = self.promote_alias(table) + # We only get here if we have found a table that exists + # in the join list, but isn't on the original tables list. + # This means we've reached the point where we only have + # new tables, so we can break out of this promotion loop. break self.promote_alias_chain(join_it, join_promote) - self.promote_alias_chain(table_it, table_promote) - + self.promote_alias_chain(table_it, table_promote or join_promote) if having_clause or force_having: if (alias, col) not in self.group_by: |
