Django

Code

Changeset 7741

Show
Ignore:
Timestamp:
06/25/08 20:02:11 (5 months ago)
Author:
mtredinnick
Message:

Fixed a problem when constructing complex select_related() calls.

Avoids joining with the wrong tables when connecting select_related() tables to
the main query. This also leads to slightly more efficient (meaning less tables
are joined) SQL queries in some other cases, too. Some unnecessary tables are
now trimmed that were not previously.

Files:

Legend:

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

    r7740 r7741  
    632632            # add_filter, since the final column might not otherwise be part of 
    633633            # the select set (so we can't order on it). 
    634             join = self.alias_map[alias] 
    635             if col == join[RHS_JOIN_COL]: 
     634            while 1: 
     635                join = self.alias_map[alias] 
     636                if col != join[RHS_JOIN_COL]: 
     637                    break 
    636638                self.unref_alias(alias) 
    637639                alias = join[LHS_ALIAS] 
     
    831833            for alias in self.join_map.get(t_ident, ()): 
    832834                if alias not in exclusions: 
     835                    if lhs_table and not self.alias_refcount[self.alias_map[alias][LHS_ALIAS]]: 
     836                        # The LHS of this join tuple is no longer part of the 
     837                        # query, so skip this possibility. 
     838                        continue 
    833839                    self.ref_alias(alias) 
    834840                    if promote: 
     
    990996        alias = join_list[-1] 
    991997 
    992         if final > 1: 
     998        while final > 1: 
    993999            # An optimization: if the final join is against the same column as 
    9941000            # we are comparing against, we can go back one step in the join 
    995             # chain and compare against the lhs of the join instead. The result 
    996             # (potentially) involves one less table join. 
     1001            # chain and compare against the lhs of the join instead (and then 
     1002            # repeat the optimization). The result, potentially, involves less 
     1003            # table joins. 
    9971004            join = self.alias_map[alias] 
    998             if col == join[RHS_JOIN_COL]: 
    999                 self.unref_alias(alias) 
    1000                 alias = join[LHS_ALIAS] 
    1001                 col = join[LHS_JOIN_COL] 
    1002                 join_list = join_list[:-1] 
    1003                 final -= 1 
    1004                 if final == penultimate: 
    1005                     penultimate = last.pop() 
     1005            if col != join[RHS_JOIN_COL]: 
     1006                break 
     1007            self.unref_alias(alias) 
     1008            alias = join[LHS_ALIAS] 
     1009            col = join[LHS_JOIN_COL] 
     1010            join_list = join_list[:-1] 
     1011            final -= 1 
     1012            if final == penultimate: 
     1013                penultimate = last.pop() 
    10061014 
    10071015        if (lookup_type == 'isnull' and value is True and not negate and 
  • django/trunk/tests/regressiontests/queries/models.py

    r7740 r7741  
    135135    def __unicode__(self): 
    136136        return self.data 
     137 
     138# An inter-related setup with multiple paths from Child to Detail. 
     139class Detail(models.Model): 
     140    data = models.CharField(max_length=10) 
     141 
     142class MemberManager(models.Manager): 
     143    def get_query_set(self): 
     144        return super(MemberManager, self).get_query_set().select_related("details") 
     145 
     146class Member(models.Model): 
     147    name = models.CharField(max_length=10) 
     148    details = models.OneToOneField(Detail, primary_key=True) 
     149 
     150    objects = MemberManager() 
     151 
     152class Child(models.Model): 
     153    person = models.OneToOneField(Member, primary_key=True) 
     154    parent = models.ForeignKey(Member, related_name="children") 
    137155 
    138156 
     
    721739[u'e1', u'e2', None] 
    722740 
     741Similarly for select_related(), joins beyond an initial nullable join must 
     742use outer joins so that all results are included. 
     743>>> Report.objects.select_related("creator", "creator__extra").order_by("name") 
     744[<Report: r1>, <Report: r2>, <Report: r3>] 
     745 
     746When there are multiple paths to a table from another table, we have to be 
     747careful not to accidentally reuse an inappropriate join when using 
     748select_related(). We used to return the parent's Detail record here by mistake. 
     749 
     750>>> d1 = Detail.objects.create(data="d1") 
     751>>> d2 = Detail.objects.create(data="d2") 
     752>>> m1 = Member.objects.create(name="m1", details=d1) 
     753>>> m2 = Member.objects.create(name="m2", details=d2) 
     754>>> c1 = Child.objects.create(person=m2, parent=m1) 
     755>>> obj = m1.children.select_related("person__details")[0] 
     756>>> obj.person.details.data 
     757u'd2' 
     758 
    723759"""} 
    724760