diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index fc5a4285a1e..4244e3d854d 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -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): """ diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 2ca482de148..662aede6952 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -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))