Ticket #16715: patch-nested-foreign-keys-with-test-r16923.patch

File patch-nested-foreign-keys-with-test-r16923.patch, 12.3 KB (added by Sebastian Goll, 13 years ago)
  • django/db/models/sql/compiler.py

     
    671671                self.query.related_select_fields.extend(model._meta.fields)
    672672
    673673                next = requested.get(f.related_query_name(), {})
    674                 new_nullable = f.null or None
     674                # Use True here because we are looking at the _reverse_ side of
     675                # the relation, which is always nullable.
     676                new_nullable = True
    675677
    676678                self.fill_related_selections(model._meta, table, cur_depth+1,
    677679                    used, next, restricted, new_nullable)
  • django/db/models/sql/query.py

     
    13671367                        orig_opts._join_cache[name] = (table, from_col, to_col,
    13681368                                opts, target)
    13691369
     1370                    # If adding another join to a nullable field or to an
     1371                    # outer join, the new join will be nullable as well.
     1372                    nullable_rel = (self.alias_map[alias][NULLABLE] or
     1373                        self.alias_map[alias][JOIN_TYPE] == self.LOUTER)
    13701374                    alias = self.join((alias, table, from_col, to_col),
    1371                             exclusions=exclusions, nullable=field.null)
     1375                            exclusions=exclusions,
     1376                            nullable=field.null or nullable_rel)
    13721377                    joins.append(alias)
    13731378                else:
    13741379                    # Non-relation fields.
  • tests/regressiontests/nested_foreign_keys/tests.py

     
     1from django.test import TestCase
     2
     3from models import Person, Movie, Event, Screening, ScreeningNullFK, Package, PackageNullFK
     4
     5
     6# These are tests for #16715. The basic scheme is always the same: 3 models with
     7# 2 relations. The first relation may be null, while the second is non-nullable.
     8# In some cases, Django would pick the wrong join type for the second relation,
     9# resulting in missing objects in the queryset.
     10#
     11#   Model A
     12#   | (Relation A/B : nullable)
     13#   Model B
     14#   | (Relation B/C : non-nullable)
     15#   Model C
     16#
     17# Because of the possibility of NULL rows resulting from the LEFT OUTER JOIN
     18# between Model A and Model B (i.e. instances of A without reference to B),
     19# the second join must also be LEFT OUTER JOIN, so that we do not ignore
     20# instances of A that do not reference B.
     21#
     22# Relation A/B can either be an explicit foreign key or an implicit reverse
     23# relation such as introduced by one-to-one relations (through multi-table
     24# inheritance).
     25class NestedForeignKeysTests(TestCase):
     26    def setUp(self):
     27        self.director = Person.objects.create(name=u'Terry Gilliam / Terry Jones')
     28        self.movie = Movie.objects.create(title=u'Monty Python and the Holy Grail', director=self.director)
     29
     30
     31    # This test failed in #16715 because in some cases INNER JOIN was selected
     32    # for the second foreign key relation instead of LEFT OUTER JOIN.
     33    def testInheritance(self):
     34        some_event = Event.objects.create()
     35        screening = Screening.objects.create(movie=self.movie)
     36
     37        self.assertEqual(len(Event.objects.all()), 2)
     38        self.assertEqual(len(Event.objects.select_related('screening')), 2)
     39        # This failed.
     40        self.assertEqual(len(Event.objects.select_related('screening__movie')), 2)
     41
     42        self.assertEqual(len(Event.objects.values()), 2)
     43        self.assertEqual(len(Event.objects.values('screening__pk')), 2)
     44        self.assertEqual(len(Event.objects.values('screening__movie__pk')), 2)
     45        self.assertEqual(len(Event.objects.values('screening__movie__title')), 2)
     46        # This failed.
     47        self.assertEqual(len(Event.objects.values('screening__movie__pk', 'screening__movie__title')), 2)
     48
     49        # Simple filter/exclude queries for good measure.
     50        self.assertEqual(Event.objects.filter(screening__movie=self.movie).count(), 1)
     51        self.assertEqual(Event.objects.exclude(screening__movie=self.movie).count(), 1)
     52
     53
     54    # These all work because the second foreign key in the chain has null=True.
     55    def testInheritanceNullFK(self):
     56        some_event = Event.objects.create()
     57        screening = ScreeningNullFK.objects.create(movie=None)
     58        screening_with_movie = ScreeningNullFK.objects.create(movie=self.movie)
     59
     60        self.assertEqual(len(Event.objects.all()), 3)
     61        self.assertEqual(len(Event.objects.select_related('screeningnullfk')), 3)
     62        self.assertEqual(len(Event.objects.select_related('screeningnullfk__movie')), 3)
     63
     64        self.assertEqual(len(Event.objects.values()), 3)
     65        self.assertEqual(len(Event.objects.values('screeningnullfk__pk')), 3)
     66        self.assertEqual(len(Event.objects.values('screeningnullfk__movie__pk')), 3)
     67        self.assertEqual(len(Event.objects.values('screeningnullfk__movie__title')), 3)
     68        self.assertEqual(len(Event.objects.values('screeningnullfk__movie__pk', 'screeningnullfk__movie__title')), 3)
     69
     70        self.assertEqual(Event.objects.filter(screeningnullfk__movie=self.movie).count(), 1)
     71        self.assertEqual(Event.objects.exclude(screeningnullfk__movie=self.movie).count(), 2)
     72
     73
     74    # This test failed in #16715 because in some cases INNER JOIN was selected
     75    # for the second foreign key relation instead of LEFT OUTER JOIN.
     76    def testExplicitForeignKey(self):
     77        package = Package.objects.create()
     78        screening = Screening.objects.create(movie=self.movie)
     79        package_with_screening = Package.objects.create(screening=screening)
     80
     81        self.assertEqual(len(Package.objects.all()), 2)
     82        self.assertEqual(len(Package.objects.select_related('screening')), 2)
     83        self.assertEqual(len(Package.objects.select_related('screening__movie')), 2)
     84
     85        self.assertEqual(len(Package.objects.values()), 2)
     86        self.assertEqual(len(Package.objects.values('screening__pk')), 2)
     87        self.assertEqual(len(Package.objects.values('screening__movie__pk')), 2)
     88        self.assertEqual(len(Package.objects.values('screening__movie__title')), 2)
     89        # This failed.
     90        self.assertEqual(len(Package.objects.values('screening__movie__pk', 'screening__movie__title')), 2)
     91
     92        self.assertEqual(Package.objects.filter(screening__movie=self.movie).count(), 1)
     93        self.assertEqual(Package.objects.exclude(screening__movie=self.movie).count(), 1)
     94
     95
     96    # These all work because the second foreign key in the chain has null=True.
     97    def testExplicitForeignKeyNullFK(self):
     98        package = PackageNullFK.objects.create()
     99        screening = ScreeningNullFK.objects.create(movie=None)
     100        screening_with_movie = ScreeningNullFK.objects.create(movie=self.movie)
     101        package_with_screening = PackageNullFK.objects.create(screening=screening)
     102        package_with_screening_with_movie = PackageNullFK.objects.create(screening=screening_with_movie)
     103
     104        self.assertEqual(len(PackageNullFK.objects.all()), 3)
     105        self.assertEqual(len(PackageNullFK.objects.select_related('screening')), 3)
     106        self.assertEqual(len(PackageNullFK.objects.select_related('screening__movie')), 3)
     107
     108        self.assertEqual(len(PackageNullFK.objects.values()), 3)
     109        self.assertEqual(len(PackageNullFK.objects.values('screening__pk')), 3)
     110        self.assertEqual(len(PackageNullFK.objects.values('screening__movie__pk')), 3)
     111        self.assertEqual(len(PackageNullFK.objects.values('screening__movie__title')), 3)
     112        self.assertEqual(len(PackageNullFK.objects.values('screening__movie__pk', 'screening__movie__title')), 3)
     113
     114        self.assertEqual(PackageNullFK.objects.filter(screening__movie=self.movie).count(), 1)
     115        self.assertEqual(PackageNullFK.objects.exclude(screening__movie=self.movie).count(), 2)
     116
     117
     118# Some additional tests for #16715. The only difference is the depth of the
     119# nesting as we now use 4 models instead of 3 (and thus 3 relations). This
     120# checks if promotion of join types works for deeper nesting too.
     121class DeeplyNestedForeignKeysTests(TestCase):
     122    def setUp(self):
     123        self.director = Person.objects.create(name=u'Terry Gilliam / Terry Jones')
     124        self.movie = Movie.objects.create(title=u'Monty Python and the Holy Grail', director=self.director)
     125
     126
     127    def testInheritance(self):
     128        some_event = Event.objects.create()
     129        screening = Screening.objects.create(movie=self.movie)
     130
     131        self.assertEqual(len(Event.objects.all()), 2)
     132        self.assertEqual(len(Event.objects.select_related('screening__movie__director')), 2)
     133
     134        self.assertEqual(len(Event.objects.values()), 2)
     135        self.assertEqual(len(Event.objects.values('screening__movie__director__pk')), 2)
     136        self.assertEqual(len(Event.objects.values('screening__movie__director__name')), 2)
     137        self.assertEqual(len(Event.objects.values('screening__movie__director__pk', 'screening__movie__director__name')), 2)
     138        self.assertEqual(len(Event.objects.values('screening__movie__pk', 'screening__movie__director__pk')), 2)
     139        self.assertEqual(len(Event.objects.values('screening__movie__pk', 'screening__movie__director__name')), 2)
     140        self.assertEqual(len(Event.objects.values('screening__movie__title', 'screening__movie__director__pk')), 2)
     141        self.assertEqual(len(Event.objects.values('screening__movie__title', 'screening__movie__director__name')), 2)
     142
     143        self.assertEqual(Event.objects.filter(screening__movie__director=self.director).count(), 1)
     144        self.assertEqual(Event.objects.exclude(screening__movie__director=self.director).count(), 1)
     145
     146
     147    def testExplicitForeignKey(self):
     148        package = Package.objects.create()
     149        screening = Screening.objects.create(movie=self.movie)
     150        package_with_screening = Package.objects.create(screening=screening)
     151
     152        self.assertEqual(len(Package.objects.all()), 2)
     153        self.assertEqual(len(Package.objects.select_related('screening__movie__director')), 2)
     154
     155        self.assertEqual(len(Package.objects.values()), 2)
     156        self.assertEqual(len(Package.objects.values('screening__movie__director__pk')), 2)
     157        self.assertEqual(len(Package.objects.values('screening__movie__director__name')), 2)
     158        self.assertEqual(len(Package.objects.values('screening__movie__director__pk', 'screening__movie__director__name')), 2)
     159        self.assertEqual(len(Package.objects.values('screening__movie__pk', 'screening__movie__director__pk')), 2)
     160        self.assertEqual(len(Package.objects.values('screening__movie__pk', 'screening__movie__director__name')), 2)
     161        self.assertEqual(len(Package.objects.values('screening__movie__title', 'screening__movie__director__pk')), 2)
     162        self.assertEqual(len(Package.objects.values('screening__movie__title', 'screening__movie__director__name')), 2)
     163
     164        self.assertEqual(Package.objects.filter(screening__movie__director=self.director).count(), 1)
     165        self.assertEqual(Package.objects.exclude(screening__movie__director=self.director).count(), 1)
  • tests/regressiontests/nested_foreign_keys/models.py

     
     1from django.db import models
     2
     3
     4class Person(models.Model):
     5    name = models.CharField(max_length=200)
     6
     7
     8class Movie(models.Model):
     9    title = models.CharField(max_length=200)
     10    director = models.ForeignKey(Person)
     11
     12
     13class Event(models.Model):
     14    pass
     15
     16
     17class Screening(Event):
     18    movie = models.ForeignKey(Movie)
     19
     20class ScreeningNullFK(Event):
     21    movie = models.ForeignKey(Movie, null=True)
     22
     23
     24class Package(models.Model):
     25    screening = models.ForeignKey(Screening, null=True)
     26
     27class PackageNullFK(models.Model):
     28    screening = models.ForeignKey(ScreeningNullFK, null=True)
Back to Top