Removed Query.setup_joins() and join() argument outer_if_first.

Instead always create new joins as OUTER.
This commit is contained in:
Anssi Kääriäinen 2013-11-06 21:31:19 +02:00
parent e7b61e5717
commit ba6c9fae45
2 changed files with 16 additions and 37 deletions

View File

@ -453,7 +453,7 @@ class SQLCompiler(object):
def _setup_joins(self, pieces, opts, alias):
"""
A helper method for get_ordering and get_distinct. This method will
call query.setup_joins, handle refcounts and then promote the joins.
call query.setup_joins and handle refcounts.
Note that get_ordering and get_distinct must produce same target
columns on same input, as the prefixes of get_ordering and get_distinct
@ -463,20 +463,12 @@ class SQLCompiler(object):
alias = self.query.get_initial_alias()
field, targets, opts, joins, path = self.query.setup_joins(
pieces, opts, alias)
# We will later on need to promote those joins that were added to the
# query afresh above.
joins_to_promote = [j for j in joins if self.query.alias_refcount[j] < 2]
alias = joins[-1]
if not field.rel:
# To avoid inadvertent trimming of a necessary alias, use the
# refcount to show that we are referencing a non-relation field on
# the model.
self.query.ref_alias(alias)
# Must use left outer joins for nullable fields and their relations.
# Ordering or distinct must not affect the returned set, and INNER
# JOINS for nullable fields could do this.
self.query.promote_joins(joins_to_promote)
return field, targets, alias, joins, path, opts
def get_from_clause(self):
@ -589,7 +581,7 @@ class SQLCompiler(object):
return result, params
def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
requested=None, restricted=None, nullable=None):
requested=None, restricted=None):
"""
Fill in the information needed for a select_related query. The current
depth is measured as the number of connections away from the root model
@ -623,9 +615,8 @@ class SQLCompiler(object):
if not select_related_descend(f, restricted, requested,
only_load.get(field_model)):
continue
promote = nullable or f.null
_, _, _, joins, _ = self.query.setup_joins(
[f.name], opts, root_alias, outer_if_first=promote)
[f.name], opts, root_alias)
alias = joins[-1]
columns, aliases = self.get_default_columns(start_alias=alias,
opts=f.rel.to._meta, as_pairs=True)
@ -635,9 +626,8 @@ class SQLCompiler(object):
next = requested.get(f.name, {})
else:
next = False
new_nullable = f.null or promote
self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1,
next, restricted, new_nullable)
next, restricted)
if restricted:
related_fields = [
@ -651,7 +641,7 @@ class SQLCompiler(object):
continue
_, _, _, joins, _ = self.query.setup_joins(
[f.related_query_name()], opts, root_alias, outer_if_first=True)
[f.related_query_name()], opts, root_alias)
alias = joins[-1]
from_parent = (opts.model if issubclass(model, opts.model)
else None)
@ -661,11 +651,8 @@ class SQLCompiler(object):
SelectInfo(col, field) for col, field
in zip(columns, model._meta.concrete_fields))
next = requested.get(f.related_query_name(), {})
# Use True here because we are looking at the _reverse_ side of
# the relation, which is always nullable.
new_nullable = True
self.fill_related_selections(model._meta, alias, cur_depth + 1,
next, restricted, new_nullable)
next, restricted)
def deferred_to_columns(self):
"""

View File

@ -491,8 +491,7 @@ class Query(object):
lhs = change_map.get(lhs, lhs)
new_alias = self.join(
(lhs, table, join_cols), reuse=reuse,
outer_if_first=True, nullable=nullable,
join_field=join_field)
nullable=nullable, join_field=join_field)
if join_type == self.INNER:
rhs_votes.add(new_alias)
# We can't reuse the same join again in the query. If we have two
@ -854,8 +853,7 @@ class Query(object):
"""
return len([1 for count in self.alias_refcount.values() if count])
def join(self, connection, reuse=None, outer_if_first=False,
nullable=False, join_field=None):
def join(self, connection, reuse=None, nullable=False, join_field=None):
"""
Returns an alias for the join in 'connection', either reusing an
existing alias for that join or creating a new one. 'connection' is a
@ -870,11 +868,9 @@ class Query(object):
(matching the connection) are reusable, or it can be a set containing
the aliases that can be reused.
If 'outer_if_first' is True and a new join is created, it will have the
LOUTER join type.
A join is always created as LOUTER if the lhs alias is LOUTER to make
sure we do not generate chains like t1 LOUTER t2 INNER t3.
sure we do not generate chains like t1 LOUTER t2 INNER t3. All new
joins are created as LOUTER if nullable is True.
If 'nullable' is True, the join can potentially involve NULL values and
is a candidate for promotion (to "left outer") when combining querysets.
@ -904,15 +900,13 @@ class Query(object):
# Not all tables need to be joined to anything. No join type
# means the later columns are ignored.
join_type = None
elif self.alias_map[lhs].join_type == self.LOUTER:
elif self.alias_map[lhs].join_type == self.LOUTER or nullable:
join_type = self.LOUTER
else:
join_type = self.INNER
join = JoinInfo(table, alias, join_type, lhs, join_cols or ((None, None),), nullable,
join_field)
self.alias_map[alias] = join
if outer_if_first:
self.promote_joins([alias])
if connection in self.join_map:
self.join_map[connection] += (alias,)
else:
@ -1010,7 +1004,7 @@ class Query(object):
# Join promotion note - we must not remove any rows here, so use
# outer join if there isn't any existing join.
field, sources, opts, join_list, path = self.setup_joins(
field_list, opts, self.get_initial_alias(), outer_if_first=True)
field_list, opts, self.get_initial_alias())
# Process the join chain to see if it can be trimmed
targets, _, join_list = self.trim_joins(sources, join_list, path)
@ -1139,7 +1133,7 @@ class Query(object):
try:
field, sources, opts, join_list, path = self.setup_joins(
parts, opts, alias, can_reuse, allow_many, outer_if_first=True)
parts, opts, alias, can_reuse, allow_many)
except MultiJoin as e:
return self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]),
can_reuse, e.names_with_path)
@ -1343,8 +1337,7 @@ class Query(object):
raise FieldError("Join on field %r not permitted." % name)
return path, final_field, targets
def setup_joins(self, names, opts, alias, can_reuse=None, allow_many=True,
outer_if_first=False):
def setup_joins(self, names, opts, alias, can_reuse=None, allow_many=True):
"""
Compute the necessary table joins for the passage through the fields
given in 'names'. 'opts' is the Options class for the current model
@ -1385,8 +1378,7 @@ class Query(object):
connection = alias, opts.db_table, join.join_field.get_joining_columns()
reuse = can_reuse if join.m2m else None
alias = self.join(
connection, reuse=reuse, nullable=nullable, join_field=join.join_field,
outer_if_first=outer_if_first)
connection, reuse=reuse, nullable=nullable, join_field=join.join_field)
joins.append(alias)
if hasattr(final_field, 'field'):
final_field = final_field.field
@ -1561,7 +1553,7 @@ class Query(object):
# if there is no existing joins, use outer join.
field, targets, u2, joins, path = self.setup_joins(
name.split(LOOKUP_SEP), opts, alias, can_reuse=None,
allow_many=allow_m2m, outer_if_first=True)
allow_many=allow_m2m)
targets, final_alias, joins = self.trim_joins(targets, joins, path)
for target in targets:
self.select.append(SelectInfo((final_alias, target.column), target))