Loading django/db/models/sql/compiler.py +6 −19 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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): Loading Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 = [ Loading @@ -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) Loading @@ -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): """ Loading django/db/models/sql/query.py +10 −18 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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. Loading Loading @@ -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: Loading Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 Loading
django/db/models/sql/compiler.py +6 −19 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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): Loading Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 = [ Loading @@ -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) Loading @@ -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): """ Loading
django/db/models/sql/query.py +10 −18 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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. Loading Loading @@ -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: Loading Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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