Django

Code

Changeset 6958

Show
Ignore:
Timestamp:
12/19/07 04:58:26 (9 months ago)
Author:
mtredinnick
Message:

queryset-refactor: Fixed the way join promotions are done when joining queries (particularly the disjunctive -- 'OR' -- case). This fixes a FIXME and produces better queries.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/queryset-refactor/django/db/models/sql/query.py

    r6957 r6958  
    5353ALIAS_REFCOUNT = 1 
    5454ALIAS_JOIN = 2 
     55ALIAS_NULLABLE=3 
    5556 
    5657# How many results to expect from a cursor.execute call 
     
    511512        if not alias: 
    512513            alias = self.join((None, opts.db_table, None, None)) 
    513         field, target, opts, joins, unused2 = self.setup_joins(pieces, opts, 
    514                 alias, False) 
     514        field, target, opts, joins = self.setup_joins(pieces, opts, alias, 
     515                False) 
    515516        alias = joins[-1][-1] 
    516517        col = target.column 
     
    569570 
    570571    def promote_alias(self, alias): 
    571         """ Promotes the join type of an alias to an outer join. """ 
    572         self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER 
     572        """ 
     573        Promotes the join type of an alias to an outer join if it's possible 
     574        for the join to contain NULL values on the left. 
     575 
     576        Returns True if the aliased join was promoted. 
     577        """ 
     578        if self.alias_map[alias][ALIAS_NULLABLE]: 
     579            self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER 
     580            return True 
     581        return False 
    573582 
    574583    def join(self, (lhs, table, lhs_col, col), always_create=False, 
    575             exclusions=(), promote=False, outer_if_first=False): 
     584            exclusions=(), promote=False, outer_if_first=False, nullable=False): 
    576585        """ 
    577586        Returns an alias for a join between 'table' and 'lhs' on the given 
     
    594603        LOUTER join type. This is used when joining certain types of querysets 
    595604        and Q-objects together. 
     605 
     606        If 'nullable' is True, the join can potentially involve NULL values and 
     607        is a candidate for promotion (to "left outer") when combining querysets. 
    596608        """ 
    597609        if lhs is None: 
     
    611623                    if alias not in exclusions: 
    612624                        self.ref_alias(alias) 
    613                         if promote
     625                        if promote and self.alias_map[alias][ALIAS_NULLABLE]
    614626                            self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = \ 
    615627                                    self.LOUTER 
     
    632644            join[JOIN_TYPE] = None 
    633645        self.alias_map[alias][ALIAS_JOIN] = join 
     646        self.alias_map[alias][ALIAS_NULLABLE] = nullable 
    634647        self.join_map.setdefault(t_ident, []).append(alias) 
    635648        self.rev_join_map[alias] = t_ident 
     
    709722 
    710723        try: 
    711             field, target, unused, join_list, nullable = self.setup_joins(parts, 
    712                     opts, alias, (connector == AND)) 
     724            field, target, unused, join_list = self.setup_joins(parts, opts, 
     725                    alias, (connector == AND)) 
    713726        except TypeError, e: 
    714727            if len(parts) != 1 or parts[0] not in self.extra_select: 
     
    753766                if join == table and self.alias_map[join][ALIAS_REFCOUNT] > 1: 
    754767                    continue 
    755                 # FIXME: Don't have to promote here (and in the other places in 
    756                 # this block) if the join isn't nullable. So I should be 
    757                 # checking this before promoting (avoiding left outer joins is 
    758                 # important). 
    759768                self.promote_alias(join) 
    760769                if table != join: 
     
    772781        if negate: 
    773782            flag = False 
    774             for pos, null in enumerate(nullable): 
    775                 if not null: 
    776                     continue 
    777                 flag = True 
    778                 for join in join_list[pos]: 
    779                     self.promote_alias(join) 
     783            for seq in join_list: 
     784                for join in seq: 
     785                    if self.promote_alias(join): 
     786                        flag = True 
    780787            self.where.negate() 
    781788            if flag: 
     
    798805        else: 
    799806            subtree = False 
     807        connector = AND 
    800808        for child in q_object.children: 
    801809            if isinstance(child, Node): 
    802                 self.where.start_subtree(q_object.connector) 
     810                self.where.start_subtree(connector) 
    803811                self.add_q(child) 
    804812                self.where.end_subtree() 
    805813            else: 
    806                 self.add_filter(child, q_object.connector, q_object.negated) 
     814                self.add_filter(child, connector, q_object.negated) 
     815            connector = q_object.connector 
    807816        if subtree: 
    808817            self.where.end_subtree() 
     
    823832        """ 
    824833        joins = [[alias]] 
    825         nullable = [False] 
    826834        for pos, name in enumerate(names): 
    827835            if name == 'pk': 
     
    857865 
    858866                    int_alias = self.join((alias, table1, from_col1, to_col1), 
    859                             dupe_multis
     867                            dupe_multis, nullable=True
    860868                    alias = self.join((int_alias, table2, from_col2, to_col2), 
    861                             dupe_multis
     869                            dupe_multis, nullable=True
    862870                    joins.append([int_alias, alias]) 
    863                     nullable.append(field.null) 
    864871                elif field.rel: 
    865872                    # One-to-one or many-to-one field 
     
    875882                                opts, target) 
    876883 
    877                     alias = self.join((alias, table, from_col, to_col)) 
     884                    alias = self.join((alias, table, from_col, to_col), 
     885                            nullable=field.null) 
    878886                    joins.append([alias]) 
    879                     nullable.append(field.null) 
    880887                else: 
    881888                    target = field 
     
    884891                orig_field = field 
    885892                field = field.field 
    886                 nullable.append(True) 
    887893                if m2m: 
    888894                    # Many-to-many field defined on the target model. 
     
    904910 
    905911                    int_alias = self.join((alias, table1, from_col1, to_col1), 
    906                             dupe_multis
     912                            dupe_multis, nullable=True
    907913                    alias = self.join((int_alias, table2, from_col2, to_col2), 
    908                             dupe_multis
     914                            dupe_multis, nullable=True
    909915                    joins.append([int_alias, alias]) 
    910916                else: 
     
    924930 
    925931                    alias = self.join((alias, table, from_col, to_col), 
    926                             dupe_multis
     932                            dupe_multis, nullable=True
    927933                    joins.append([alias]) 
    928934 
     
    930936            raise TypeError("Join on field %r not permitted." % name) 
    931937 
    932         return field, target, opts, joins, nullable 
     938        return field, target, opts, joins 
    933939 
    934940    def set_limits(self, low=None, high=None): 
  • django/branches/queryset-refactor/tests/regressiontests/queries/models.py

    r6957 r6958  
    3093093 
    310310 
     311Similarly, when one of the joins cannot possibly, ever, involve NULL values (Author -> ExtraInfo, in the following), it should never be promoted to a left outer join. So hte following query should only involve one "left outer" join (Author -> Item is 0-to-many). 
     312>>> qs = Author.objects.filter(id=a1.id).filter(Q(extra__note=n1)|Q(item__note=n3)) 
     313>>> len([x[2][2] for x in qs.query.alias_map.values() if x[2][2] == query.LOUTER]) 
     3141 
     315 
    311316Bug #2091 
    312317>>> t = Tag.objects.get(name='t4')