From 2fba099779324dd460184849ba221d233c1a6751 Mon Sep 17 00:00:00 2001
From: Malcolm Tredinnick <malcolm.tredinnick@gmail.com>
Date: Tue, 24 Mar 2009 05:54:35 +0000
Subject: [PATCH] [1.0.X] Fixed #9926 -- Fixes for some select_related()
 situations.

Using select_related(...) across a nullable relation to a multi-table
model inheritance situation no longer excludes results. Thanks to AdamG
for a test demonstrating part of the problem.

Backport of r10136 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.0.X@10138 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/db/models/sql/query.py                 | 14 ++++++++---
 tests/regressiontests/queries/models.py       | 18 +++++++++++--
 .../select_related_regress/models.py          | 25 +++++++++++++++++++
 3 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index e6d653a46e..a25ed52564 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -510,6 +510,7 @@ class BaseQuery(object):
                 alias = self.included_inherited_models[model]
             if as_pairs:
                 result.append((alias, field.column))
+                aliases.add(alias)
                 continue
             if with_aliases and field.column in col_aliases:
                 c_alias = 'Col%d' % len(col_aliases)
@@ -524,7 +525,7 @@ class BaseQuery(object):
                 if with_aliases:
                     col_aliases.add(field.column)
         if as_pairs:
-            return result, None
+            return result, aliases
         return result, aliases
 
     def get_from_clause(self):
@@ -1076,6 +1077,7 @@ class BaseQuery(object):
             if model:
                 int_opts = opts
                 alias = root_alias
+                alias_chain = []
                 for int_model in opts.get_base_chain(model):
                     lhs_col = int_opts.parents[int_model].column
                     dedupe = lhs_col in opts.duplicate_targets
@@ -1087,8 +1089,11 @@ class BaseQuery(object):
                     alias = self.join((alias, int_opts.db_table, lhs_col,
                             int_opts.pk.column), exclusions=used,
                             promote=promote)
+                    alias_chain.append(alias)
                     for (dupe_opts, dupe_col) in dupe_set:
                         self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
+                if self.alias_map[root_alias][JOIN_TYPE] == self.LOUTER:
+                    self.promote_alias_chain(alias_chain, True)
             else:
                 alias = root_alias
 
@@ -1102,8 +1107,11 @@ class BaseQuery(object):
                     f.rel.get_related_field().column),
                     exclusions=used.union(avoid), promote=promote)
             used.add(alias)
-            self.related_select_cols.extend(self.get_default_columns(
-                start_alias=alias, opts=f.rel.to._meta, as_pairs=True)[0])
+            columns, aliases = self.get_default_columns(start_alias=alias,
+                    opts=f.rel.to._meta, as_pairs=True)
+            self.related_select_cols.extend(columns)
+            if self.alias_map[alias][JOIN_TYPE] == self.LOUTER:
+                self.promote_alias_chain(aliases, True)
             self.related_select_fields.extend(f.rel.to._meta.fields)
             if restricted:
                 next = requested.get(f.name, {})
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index e16a6c2e92..3f8c9e6a07 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -16,10 +16,17 @@ try:
 except NameError:
     from django.utils.itercompat import sorted
 
+class DumbCategory(models.Model):
+    pass
+
+class NamedCategory(DumbCategory):
+    name = models.CharField(max_length=10)
+
 class Tag(models.Model):
     name = models.CharField(max_length=10)
     parent = models.ForeignKey('self', blank=True, null=True,
             related_name='children')
+    category = models.ForeignKey(NamedCategory, null=True, default=None)
 
     class Meta:
         ordering = ['name']
@@ -266,8 +273,9 @@ class Plaything(models.Model):
 
 
 __test__ = {'API_TESTS':"""
->>> t1 = Tag.objects.create(name='t1')
->>> t2 = Tag.objects.create(name='t2', parent=t1)
+>>> generic = NamedCategory.objects.create(name="Generic")
+>>> t1 = Tag.objects.create(name='t1', category=generic)
+>>> t2 = Tag.objects.create(name='t2', parent=t1, category=generic)
 >>> t3 = Tag.objects.create(name='t3', parent=t1)
 >>> t4 = Tag.objects.create(name='t4', parent=t3)
 >>> t5 = Tag.objects.create(name='t5', parent=t3)
@@ -726,6 +734,12 @@ Bug #6981
 >>> Tag.objects.select_related('parent').order_by('name')
 [<Tag: t1>, <Tag: t2>, <Tag: t3>, <Tag: t4>, <Tag: t5>]
 
+Bug #9926
+>>> Tag.objects.select_related("parent", "category").order_by('name')
+[<Tag: t1>, <Tag: t2>, <Tag: t3>, <Tag: t4>, <Tag: t5>]
+>>> Tag.objects.select_related('parent', "parent__category").order_by('name')
+[<Tag: t1>, <Tag: t2>, <Tag: t3>, <Tag: t4>, <Tag: t5>]
+
 Bug #6180, #6203 -- dates with limits and/or counts
 >>> Item.objects.count()
 4
diff --git a/tests/regressiontests/select_related_regress/models.py b/tests/regressiontests/select_related_regress/models.py
index eca620ccf7..b2664f9398 100644
--- a/tests/regressiontests/select_related_regress/models.py
+++ b/tests/regressiontests/select_related_regress/models.py
@@ -65,6 +65,23 @@ class Client(models.Model):
     state = models.ForeignKey(State, null=True)
     status = models.ForeignKey(ClientStatus)
 
+# Some model inheritance exercises
+class Parent(models.Model):
+    name = models.CharField(max_length=10)
+
+    def __unicode__(self):
+        return self.name
+
+class Child(Parent):
+    value = models.IntegerField()
+
+class Item(models.Model):
+    name = models.CharField(max_length=10)
+    child = models.ForeignKey(Child, null=True)
+
+    def __unicode__(self):
+        return self.name
+
 __test__ = {'API_TESTS': """
 Regression test for bug #7110. When using select_related(), we must query the
 Device and Building tables using two different aliases (each) in order to
@@ -140,4 +157,12 @@ for country before getting status.
 <ClientStatus: ClientStatus object>
 >>> Client.objects.select_related('status')[0].status
 <ClientStatus: ClientStatus object>
+
+Exercising select_related() with multi-table model inheritance.
+>>> c1 = Child.objects.create(name="child1", value=42)
+>>> _ = Item.objects.create(name="item1", child=c1)
+>>> _ = Item.objects.create(name="item2")
+>>> Item.objects.select_related("child").order_by("name")
+[<Item: item1>, <Item: item2>]
+
 """}