Django

Code

Changeset 8853

Show
Ignore:
Timestamp:
09/02/08 08:52:07 (3 months ago)
Author:
mtredinnick
Message:

Fixed #8790 -- Multi-branch join trees that shared tables of the same name were
sometimes also sharing aliases, instead of creating their own. This was
generating incorrect SQL.

No representative test for this fix yet because I haven't had time to write one
that fits in nicely with the test suite. But it works for the monstrous example
in #8790 and a bunch of other complex examples I've created locally. Will write
a test later.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/db/models/sql/query.py

    r8832 r8853  
    659659 
    660660        # Must use left outer joins for nullable fields. 
    661         must_promote = False 
    662         for join in joins: 
    663             if self.promote_alias(join, must_promote): 
    664                 must_promote = True 
     661        self.promote_alias_chain(joins) 
    665662 
    666663        # If we get to this point and the field is a relation to another model, 
     
    745742        return False 
    746743 
     744    def promote_alias_chain(self, chain, must_promote=False): 
     745        """ 
     746        Walks along a chain of aliases, promoting the first nullable join and 
     747        any joins following that. If 'must_promote' is True, all the aliases in 
     748        the chain are promoted. 
     749        """ 
     750        for alias in chain: 
     751            if self.promote_alias(alias, must_promote): 
     752                must_promote = True 
     753 
     754    def promote_unused_aliases(self, initial_refcounts, used_aliases): 
     755        """ 
     756        Given a "before" copy of the alias_refcounts dictionary (as 
     757        'initial_refcounts') and a collection of aliases that may have been 
     758        changed or created, works out which aliases have been created since 
     759        then and which ones haven't been used and promotes all of those 
     760        aliases, plus any children of theirs in the alias tree, to outer joins. 
     761        """ 
     762        # FIXME: There's some (a lot of!) overlap with the similar OR promotion 
     763        # in add_filter(). It's not quite identical, but is very similar. So 
     764        # pulling out the common bits is something for later. 
     765        considered = {} 
     766        for alias in self.tables: 
     767            if alias not in used_aliases: 
     768                continue 
     769            if (alias not in initial_refcounts or 
     770                    self.alias_refcount[alias] == initial_refcounts[alias]): 
     771                parent = self.alias_map[alias][LHS_ALIAS] 
     772                must_promote = considered.get(parent, False) 
     773                promoted = self.promote_alias(alias, must_promote) 
     774                considered[alias] = must_promote or promoted 
     775 
    747776    def change_aliases(self, change_map): 
    748777        """ 
     
    808837        should not be changed. 
    809838        """ 
    810         assert ord(self.alias_prefix) < ord('Z') 
    811         self.alias_prefix = chr(ord(self.alias_prefix) + 1) 
     839        current = ord(self.alias_prefix) 
     840        assert current < ord('Z') 
     841        prefix = chr(current + 1) 
     842        self.alias_prefix = prefix 
    812843        change_map = {} 
    813         prefix = self.alias_prefix 
    814844        for pos, alias in enumerate(self.tables): 
    815845            if alias in exceptions: 
     
    888918                        # The LHS of this join tuple is no longer part of the 
    889919                        # query, so skip this possibility. 
     920                        continue 
     921                    if self.alias_map[alias][LHS_ALIAS] != lhs: 
    890922                        continue 
    891923                    self.ref_alias(alias) 
     
    11211153            join_it.next(), table_it.next() 
    11221154            table_promote = False 
     1155            join_promote = False 
    11231156            for join in join_it: 
    11241157                table = table_it.next() 
     
    11291162                    table_promote = self.promote_alias(table) 
    11301163                break 
    1131             for join in join_it: 
    1132                 if self.promote_alias(join, join_promote): 
    1133                     join_promote = True 
    1134             for table in table_it: 
    1135                 # Some of these will have been promoted from the join_list, but 
    1136                 # that's harmless. 
    1137                 if self.promote_alias(table, table_promote): 
    1138                     table_promote = True 
     1164            self.promote_alias_chain(join_it, join_promote) 
     1165            self.promote_alias_chain(table_it, table_promote) 
    11391166 
    11401167        self.where.add((alias, col, field, lookup_type, value), connector) 
    11411168 
    11421169        if negate: 
    1143             for alias in join_list: 
    1144                 self.promote_alias(alias) 
     1170            self.promote_alias_chain(join_list) 
    11451171            if lookup_type != 'isnull': 
    11461172                if final > 1: 
     
    12021228                    # (they shouldn't turn the whole conditional into the empty 
    12031229                    # set just because they don't match anything). 
    1204                     # FIXME: There's some (a lot of!) overlap with the similar 
    1205                     # OR promotion in add_filter(). It's not quite identical, 
    1206                     # but is very similar. So pulling out the common bits is 
    1207                     # something for later (code smell: too much indentation 
    1208                     # here) 
    1209                     considered = {} 
    1210                     for alias in self.tables: 
    1211                         if alias not in used_aliases: 
    1212                             continue 
    1213                         if (alias not in refcounts_before or 
    1214                                 self.alias_refcount[alias] == 
    1215                                 refcounts_before[alias]): 
    1216                             parent = self.alias_map[alias][LHS_ALIAS] 
    1217                             must_promote = considered.get(parent, False) 
    1218                             promoted = self.promote_alias(alias, must_promote) 
    1219                             considered[alias] = must_promote or promoted 
     1230                    self.promote_unused_aliases(refcounts_before, used_aliases) 
    12201231                connector = q_object.connector 
    12211232            if q_object.negated: 
     
    14401451        query = Query(self.model, self.connection) 
    14411452        query.add_filter(filter_expr, can_reuse=can_reuse) 
     1453        query.bump_prefix() 
    14421454        query.set_start(prefix) 
    14431455        query.clear_ordering(True) 
     
    15011513                        col = join[LHS_JOIN_COL] 
    15021514                        joins = joins[:-1] 
    1503                 promote = False 
    1504                 for join in joins[1:]: 
    1505                     # Only nullable aliases are promoted, so we don't end up 
    1506                     # doing unnecessary left outer joins here. 
    1507                     if self.promote_alias(join, promote): 
    1508                         promote = True 
     1515                self.promote_alias_chain(joins[1:]) 
    15091516                self.select.append((final_alias, col)) 
    15101517                self.select_fields.append(field)