Ticket #14056: 14056_join_by_reverse_relation.diff

File 14056_join_by_reverse_relation.diff, 7.1 KB (added by Alexey Smolsky, 14 years ago)
  • django/db/models/sql/compiler.py

     
    437437            if not self.query.alias_refcount[alias]:
    438438                continue
    439439            try:
    440                 name, alias, join_type, lhs, lhs_col, col, nullable = self.query.alias_map[alias]
     440                (name, alias, join_type, lhs,
     441                        lhs_col, col, nullable, direct) = self.query.alias_map[alias]
    441442            except KeyError:
    442443                # Extra tables can end up in self.tables, but not in the
    443444                # alias_map if they aren't in a join. That's OK. We skip them.
  • django/db/models/sql/constants.py

     
    2424LHS_JOIN_COL = 4
    2525RHS_JOIN_COL = 5
    2626NULLABLE = 6
     27DIRECT = 7
    2728
    2829# How many results to expect from a cursor.execute call
    2930MULTI = 'multi'
  • django/db/models/sql/query.py

     
    806806        return len([1 for count in self.alias_refcount.itervalues() if count])
    807807
    808808    def join(self, connection, always_create=False, exclusions=(),
    809             promote=False, outer_if_first=False, nullable=False, reuse=None):
     809            promote=False, outer_if_first=False, nullable=False, reuse=None,
     810            direct=True):
    810811        """
    811812        Returns an alias for the join in 'connection', either reusing an
    812813        existing alias for that join or creating a new one. 'connection' is a
     
    837838
    838839        If 'nullable' is True, the join can potentially involve NULL values and
    839840        is a candidate for promotion (to "left outer") when combining querysets.
     841
     842        If 'direct' is False, the join were got by reverse relation.
    840843        """
    841844        lhs, table, lhs_col, col = connection
    842845        if lhs in self.alias_map:
     
    874877            join_type = self.LOUTER
    875878        else:
    876879            join_type = self.INNER
    877         join = (table, alias, join_type, lhs, lhs_col, col, nullable)
     880        join = (table, alias, join_type, lhs, lhs_col, col, nullable, direct)
    878881        self.alias_map[alias] = join
    879882        if t_ident in self.join_map:
    880883            self.join_map[t_ident] += (alias,)
     
    12861289                        joins.extend([int_alias, alias])
    12871290                elif field.rel:
    12881291                    # One-to-one or many-to-one field
     1292                    # (ForeignKey defined on the current model)
    12891293                    if cached_data:
    12901294                        (table, from_col, to_col, opts, target) = cached_data
    12911295                    else:
     
    13331337                            reuse=can_reuse)
    13341338                    joins.extend([int_alias, alias])
    13351339                else:
    1336                     # One-to-many field (ForeignKey defined on the target model)
     1340                    # One-to-one or one-to-many fields
     1341                    # (ForeignKey defined on the target model)
    13371342                    if cached_data:
    13381343                        (table, from_col, to_col, opts, target) = cached_data
    13391344                    else:
     
    13491354
    13501355                    alias = self.join((alias, table, from_col, to_col),
    13511356                            dupe_multis, exclusions, nullable=True,
    1352                             reuse=can_reuse)
     1357                            reuse=can_reuse, direct=False)
    13531358                    joins.append(alias)
    13541359
    13551360            for (dupe_opts, dupe_col) in dupe_set:
     
    14071412        alias = join_list[-1]
    14081413        while final > 1:
    14091414            join = self.alias_map[alias]
    1410             if col != join[RHS_JOIN_COL] or join[JOIN_TYPE] != self.INNER:
     1415            if col != join[RHS_JOIN_COL] or join[JOIN_TYPE] != self.INNER or \
     1416               not join[DIRECT]:
    14111417                break
    14121418            self.unref_alias(alias)
    14131419            alias = join[LHS_ALIAS]
  • tests/regressiontests/many_to_one_regress/tests.py

     
    11from django.db import models
    22from django.test import TestCase
    33
    4 from models import First, Second, Third, Parent, Child, Category, Record, Relation
     4from models import (First, Second, Third, Parent, Child,
     5                    Category, Record, Relation, Place, Restaurant)
    56
    67class ManyToOneRegressionTests(TestCase):
    78    def test_object_creation(self):
     
    103104        # of a model, and interrogate its related field.
    104105        cat = models.ForeignKey(Category)
    105106        self.assertEqual('id', cat.rel.get_related_field().name)
     107
     108    def test_reverse_many_to_one(self):
     109        """
     110        Regression test for #14056
     111
     112        Reverse lookups generated joins which have been optimized incorrectly.
     113        """
     114        p1 = Place.objects.create(name='Demon Dogs')
     115        r1 = Restaurant.objects.create(place=p1)
     116        self.assertQuerysetEqual(
     117                Place.objects.filter(restaurant__place=p1),
     118                ['<Place: Demon Dogs the place>']
     119        )
     120
     121        p2 = Place.objects.create(name='Cute Cats')
     122        self.assertQuerysetEqual(
     123                Place.objects.filter(restaurant__place=p2),
     124                []
     125        )
  • tests/regressiontests/many_to_one_regress/models.py

     
    4444
    4545    def __unicode__(self):
    4646        return u"%s - %s" % (self.left.category.name, self.right.category.name)
     47
     48# Reverse lookup (#14056)
     49class Place(models.Model):
     50    name = models.CharField(max_length=50)
     51
     52    def __unicode__(self):
     53        return u"%s the place" % self.name
     54
     55class Restaurant(models.Model):
     56    place = models.ForeignKey(Place)
  • tests/regressiontests/one_to_one_regress/tests.py

     
    128128                Target.objects.exclude(pointer2=None),
    129129                []
    130130        )
     131
     132    def test_reverse_one_to_one(self):
     133        """
     134        Regression test for #14056
     135
     136        Reverse lookups generated joins which have been optimized incorrectly.
     137        """
     138        self.assertQuerysetEqual(
     139                Place.objects.filter(restaurant__place=self.p1),
     140                ['<Place: Demon Dogs the place>']
     141        )
     142
     143        p2 = Place.objects.create(name='Cute Cats', address='123 Street')
     144        self.assertQuerysetEqual(
     145                Place.objects.filter(restaurant__place=p2),
     146                []
     147        )
Back to Top