Commit ba6c9fae authored by Anssi Kääriäinen's avatar Anssi Kääriäinen
Browse files

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

Instead always create new joins as OUTER.
parent e7b61e57
Loading
Loading
Loading
Loading
+6 −19
Original line number Diff line number Diff line
@@ -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):
        """
+10 −18
Original line number Diff line number Diff line
@@ -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))