Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@7741 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 588eeb356c7a88138fac53c3caeb705e16aa562c 1 parent 1834428
@malcolmt malcolmt authored
View
34 django/db/models/sql/query.py
@@ -631,8 +631,10 @@ def find_ordering_name(self, name, opts, alias=None, default_order='ASC',
# We have to do the same "final join" optimisation as in
# add_filter, since the final column might not otherwise be part of
# the select set (so we can't order on it).
- join = self.alias_map[alias]
- if col == join[RHS_JOIN_COL]:
+ while 1:
+ join = self.alias_map[alias]
+ if col != join[RHS_JOIN_COL]:
+ break
self.unref_alias(alias)
alias = join[LHS_ALIAS]
col = join[LHS_JOIN_COL]
@@ -830,6 +832,10 @@ def join(self, connection, always_create=False, exclusions=(),
if not always_create:
for alias in self.join_map.get(t_ident, ()):
if alias not in exclusions:
+ if lhs_table and not self.alias_refcount[self.alias_map[alias][LHS_ALIAS]]:
+ # The LHS of this join tuple is no longer part of the
+ # query, so skip this possibility.
+ continue
self.ref_alias(alias)
if promote:
self.promote_alias(alias)
@@ -989,20 +995,22 @@ def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
col = target.column
alias = join_list[-1]
- if final > 1:
+ while final > 1:
# An optimization: if the final join is against the same column as
# we are comparing against, we can go back one step in the join
- # chain and compare against the lhs of the join instead. The result
- # (potentially) involves one less table join.
+ # chain and compare against the lhs of the join instead (and then
+ # repeat the optimization). The result, potentially, involves less
+ # table joins.
join = self.alias_map[alias]
- if col == join[RHS_JOIN_COL]:
- self.unref_alias(alias)
- alias = join[LHS_ALIAS]
- col = join[LHS_JOIN_COL]
- join_list = join_list[:-1]
- final -= 1
- if final == penultimate:
- penultimate = last.pop()
+ if col != join[RHS_JOIN_COL]:
+ break
+ self.unref_alias(alias)
+ alias = join[LHS_ALIAS]
+ col = join[LHS_JOIN_COL]
+ join_list = join_list[:-1]
+ final -= 1
+ if final == penultimate:
+ penultimate = last.pop()
if (lookup_type == 'isnull' and value is True and not negate and
final > 1):
View
36 tests/regressiontests/queries/models.py
@@ -135,6 +135,24 @@ class ManagedModel(models.Model):
def __unicode__(self):
return self.data
+# An inter-related setup with multiple paths from Child to Detail.
+class Detail(models.Model):
+ data = models.CharField(max_length=10)
+
+class MemberManager(models.Manager):
+ def get_query_set(self):
+ return super(MemberManager, self).get_query_set().select_related("details")
+
+class Member(models.Model):
+ name = models.CharField(max_length=10)
+ details = models.OneToOneField(Detail, primary_key=True)
+
+ objects = MemberManager()
+
+class Child(models.Model):
+ person = models.OneToOneField(Member, primary_key=True)
+ parent = models.ForeignKey(Member, related_name="children")
+
__test__ = {'API_TESTS':"""
>>> t1 = Tag(name='t1')
@@ -720,5 +738,23 @@ def __unicode__(self):
>>> Report.objects.values_list("creator__extra__info", flat=True).order_by("name")
[u'e1', u'e2', None]
+Similarly for select_related(), joins beyond an initial nullable join must
+use outer joins so that all results are included.
+>>> Report.objects.select_related("creator", "creator__extra").order_by("name")
+[<Report: r1>, <Report: r2>, <Report: r3>]
+
+When there are multiple paths to a table from another table, we have to be
+careful not to accidentally reuse an inappropriate join when using
+select_related(). We used to return the parent's Detail record here by mistake.
+
+>>> d1 = Detail.objects.create(data="d1")
+>>> d2 = Detail.objects.create(data="d2")
+>>> m1 = Member.objects.create(name="m1", details=d1)
+>>> m2 = Member.objects.create(name="m2", details=d2)
+>>> c1 = Child.objects.create(person=m2, parent=m1)
+>>> obj = m1.children.select_related("person__details")[0]
+>>> obj.person.details.data
+u'd2'
+
"""}
Please sign in to comment.
Something went wrong with that request. Please try again.