Ticket #14056: 14056_join_by_reverse_relation.diff
File 14056_join_by_reverse_relation.diff, 7.1 KB (added by , 14 years ago) |
---|
-
django/db/models/sql/compiler.py
437 437 if not self.query.alias_refcount[alias]: 438 438 continue 439 439 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] 441 442 except KeyError: 442 443 # Extra tables can end up in self.tables, but not in the 443 444 # alias_map if they aren't in a join. That's OK. We skip them. -
django/db/models/sql/constants.py
24 24 LHS_JOIN_COL = 4 25 25 RHS_JOIN_COL = 5 26 26 NULLABLE = 6 27 DIRECT = 7 27 28 28 29 # How many results to expect from a cursor.execute call 29 30 MULTI = 'multi' -
django/db/models/sql/query.py
806 806 return len([1 for count in self.alias_refcount.itervalues() if count]) 807 807 808 808 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): 810 811 """ 811 812 Returns an alias for the join in 'connection', either reusing an 812 813 existing alias for that join or creating a new one. 'connection' is a … … 837 838 838 839 If 'nullable' is True, the join can potentially involve NULL values and 839 840 is a candidate for promotion (to "left outer") when combining querysets. 841 842 If 'direct' is False, the join were got by reverse relation. 840 843 """ 841 844 lhs, table, lhs_col, col = connection 842 845 if lhs in self.alias_map: … … 874 877 join_type = self.LOUTER 875 878 else: 876 879 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) 878 881 self.alias_map[alias] = join 879 882 if t_ident in self.join_map: 880 883 self.join_map[t_ident] += (alias,) … … 1286 1289 joins.extend([int_alias, alias]) 1287 1290 elif field.rel: 1288 1291 # One-to-one or many-to-one field 1292 # (ForeignKey defined on the current model) 1289 1293 if cached_data: 1290 1294 (table, from_col, to_col, opts, target) = cached_data 1291 1295 else: … … 1333 1337 reuse=can_reuse) 1334 1338 joins.extend([int_alias, alias]) 1335 1339 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) 1337 1342 if cached_data: 1338 1343 (table, from_col, to_col, opts, target) = cached_data 1339 1344 else: … … 1349 1354 1350 1355 alias = self.join((alias, table, from_col, to_col), 1351 1356 dupe_multis, exclusions, nullable=True, 1352 reuse=can_reuse )1357 reuse=can_reuse, direct=False) 1353 1358 joins.append(alias) 1354 1359 1355 1360 for (dupe_opts, dupe_col) in dupe_set: … … 1407 1412 alias = join_list[-1] 1408 1413 while final > 1: 1409 1414 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]: 1411 1417 break 1412 1418 self.unref_alias(alias) 1413 1419 alias = join[LHS_ALIAS] -
tests/regressiontests/many_to_one_regress/tests.py
1 1 from django.db import models 2 2 from django.test import TestCase 3 3 4 from models import First, Second, Third, Parent, Child, Category, Record, Relation 4 from models import (First, Second, Third, Parent, Child, 5 Category, Record, Relation, Place, Restaurant) 5 6 6 7 class ManyToOneRegressionTests(TestCase): 7 8 def test_object_creation(self): … … 103 104 # of a model, and interrogate its related field. 104 105 cat = models.ForeignKey(Category) 105 106 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
44 44 45 45 def __unicode__(self): 46 46 return u"%s - %s" % (self.left.category.name, self.right.category.name) 47 48 # Reverse lookup (#14056) 49 class Place(models.Model): 50 name = models.CharField(max_length=50) 51 52 def __unicode__(self): 53 return u"%s the place" % self.name 54 55 class Restaurant(models.Model): 56 place = models.ForeignKey(Place) -
tests/regressiontests/one_to_one_regress/tests.py
128 128 Target.objects.exclude(pointer2=None), 129 129 [] 130 130 ) 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 )