Removed Query.setup_joins() and join() argument outer_if_first.
Instead always create new joins as OUTER.
This commit is contained in:
parent
e7b61e5717
commit
ba6c9fae45
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue