Ticket #10790: 10790v4.diff
File 10790v4.diff, 9.3 KB (added by , 13 years ago) |
---|
-
tests/modeltests/null_trimjoin/__init__.py
1 # dummy text for patch -
tests/modeltests/null_trimjoin/tests.py
1 from django.test import TestCase 2 from models import Article, Reporter 3 4 class OneToOneTests(TestCase): 5 6 def setUp(self): 7 self.r = Reporter(name='John Smith') 8 self.r.save() 9 self.a = Article(headline="First", reporter=self.r) 10 self.a.save() 11 self.a2 = Article(headline="Second") 12 self.a2.save() 13 14 def test_query_with_isnull(self): 15 """Querying with isnull should not join Reporter table.""" 16 q = Article.objects.filter(reporter=None) 17 # check that reporter is not in the query's used_aliases 18 self.assertFalse('null_trimjoin_reporter' in q.query.used_aliases) 19 self.assertTrue('null_trimjoin_article' in q.query.used_aliases) 20 # but it should still be in query.tables 21 self.assertTrue('null_trimjoin_article' in q.query.tables) 22 23 def test_query_across_tables(self): 24 """Querying across several tables should strip only the last join, while 25 preserving the preceding left outer joins.""" 26 q = Article.objects.filter(reporter__type=None) 27 self.assertEquals(len(q), 2) 28 self.assertTrue('null_trimjoin_article' in q.query.used_aliases) 29 self.assertTrue('null_trimjoin_reporter' in q.query.used_aliases) 30 self.assertFalse('null_trimjoin_reportertype' in q.query.used_aliases) 31 32 def test_m2m_query(self): 33 """Querying across m2m field should not strip the m2m table from join.""" 34 q = Article.objects.filter(reporter__category__isnull=True) 35 self.assertTrue('null_trimjoin_article' in q.query.used_aliases) 36 self.assertTrue('null_trimjoin_reporter' in q.query.used_aliases) 37 self.assertTrue('null_trimjoin_category' in q.query.used_aliases) 38 39 def test_reverse_query(self): 40 """Reverse querying with isnull should not strip the join.""" 41 q = Reporter.objects.filter(article__isnull=True) 42 self.assertTrue('null_trimjoin_reporter' in q.query.used_aliases) 43 44 -
tests/modeltests/null_trimjoin/models.py
1 """ 2 Do not join table when querying on isnull 3 4 """ 5 6 from django.db import models 7 8 class Category(models.Model): 9 name = models.CharField(max_length=30) 10 11 class ReporterType(models.Model): 12 name = models.CharField(max_length=30) 13 14 class Reporter(models.Model): 15 name = models.CharField(max_length=30) 16 type = models.ForeignKey(ReporterType, null=True) 17 category = models.ManyToManyField(Category, null=True) 18 19 def __unicode__(self): 20 return self.name 21 22 class Article(models.Model): 23 headline = models.CharField(max_length=100) 24 reporter = models.ForeignKey(Reporter, null=True) 25 26 class Meta: 27 ordering = ('headline',) 28 29 def __unicode__(self): 30 return self.headline 31 -
django/db/models/sql/compiler.py
384 384 pieces = name.split(LOOKUP_SEP) 385 385 if not alias: 386 386 alias = self.query.get_initial_alias() 387 field, target, opts, joins, last, extra = self.query.setup_joins(pieces,388 opts, alias, False)387 field, target, opts, joins, last, extra, allow_trim_join = self.query.setup_joins( 388 pieces, opts, alias, False) 389 389 alias = joins[-1] 390 390 col = target.column 391 391 if not field.rel: -
django/db/models/sql/expressions.py
44 44 self.cols[node] = query.aggregate_select[node.name] 45 45 else: 46 46 try: 47 field, source, opts, join_list, last, _ = query.setup_joins(47 field, source, opts, join_list, last, _, allow_trim_join = query.setup_joins( 48 48 field_list, query.get_meta(), 49 49 query.get_initial_alias(), False) 50 50 col, _, join_list = query.trim_joins(source, join_list, last, False) -
django/db/models/sql/query.py
694 694 return True 695 695 return False 696 696 697 def demote_alias(self, alias): 698 """ 699 Demotes the join type of an alias to an inner join. 700 """ 701 data = list(self.alias_map[alias]) 702 data[JOIN_TYPE] = self.INNER 703 self.alias_map[alias] = tuple(data) 704 697 705 def promote_alias_chain(self, chain, must_promote=False): 698 706 """ 699 707 Walks along a chain of aliases, promoting the first nullable join and … … 991 999 # - this is an annotation over a model field 992 1000 # then we need to explore the joins that are required. 993 1001 994 field, source, opts, join_list, last, _ = self.setup_joins(1002 field, source, opts, join_list, last, _, allow_trim_join = self.setup_joins( 995 1003 field_list, opts, self.get_initial_alias(), False) 996 1004 997 1005 # Process the join chain to see if it can be trimmed … … 1083 1091 allow_many = trim or not negate 1084 1092 1085 1093 try: 1086 field, target, opts, join_list, last, extra_filters = self.setup_joins(1094 field, target, opts, join_list, last, extra_filters, allow_trim_join = self.setup_joins( 1087 1095 parts, opts, alias, True, allow_many, allow_explicit_fk=True, 1088 1096 can_reuse=can_reuse, negate=negate, 1089 1097 process_extras=process_extras) … … 1103 1111 self.promote_alias_chain(join_list) 1104 1112 join_promote = True 1105 1113 1114 # If we have a one2one or many2one field, we can trim the left outer 1115 # join from the end of a list of joins. 1116 # In order to do this, we convert alias join type back to INNER and 1117 # trim_joins later will do the strip for us. 1118 if allow_trim_join and field.rel: 1119 self.demote_alias(join_list[-1]) 1120 1106 1121 # Process the join list to see if we can remove any inner joins from 1107 1122 # the far end (fewer tables in a query is better). 1108 1123 nonnull_comparison = (lookup_type == 'isnull' and value is False) … … 1259 1274 dupe_set = set() 1260 1275 exclusions = set() 1261 1276 extra_filters = [] 1277 allow_trim_join = True 1262 1278 int_alias = None 1263 1279 for pos, name in enumerate(names): 1264 1280 if int_alias is not None: … … 1282 1298 raise FieldError("Cannot resolve keyword %r into field. " 1283 1299 "Choices are: %s" % (name, ", ".join(names))) 1284 1300 1301 # presence of indirect field in the filter requires 1302 # left outer join for isnull 1303 if not direct and allow_trim_join: 1304 allow_trim_join = False 1305 1285 1306 if not allow_many and (m2m or not direct): 1286 1307 for alias in joins: 1287 1308 self.unref_alias(alias) … … 1323 1344 extra_filters.extend(field.extra_filters(names, pos, negate)) 1324 1345 if direct: 1325 1346 if m2m: 1347 # null query on m2mfield requires outer join 1348 allow_trim_join = False 1326 1349 # Many-to-many field defined on the current model. 1327 1350 if cached_data: 1328 1351 (table1, from_col1, to_col1, table2, from_col2, … … 1443 1466 else: 1444 1467 raise FieldError("Join on field %r not permitted." % name) 1445 1468 1446 return field, target, opts, joins, last, extra_filters 1469 return field, target, opts, joins, last, extra_filters, allow_trim_join 1447 1470 1448 1471 def trim_joins(self, target, join_list, last, trim, nonnull_check=False): 1449 1472 """ … … 1605 1628 1606 1629 try: 1607 1630 for name in field_names: 1608 field, target, u2, joins, u3, u4 = self.setup_joins(1631 field, target, u2, joins, u3, u4, allow_trim_join = self.setup_joins( 1609 1632 name.split(LOOKUP_SEP), opts, alias, False, allow_m2m, 1610 1633 True) 1611 1634 final_alias = joins[-1] … … 1887 1910 """ 1888 1911 opts = self.model._meta 1889 1912 alias = self.get_initial_alias() 1890 field, col, opts, joins, last, extra = self.setup_joins(1913 field, col, opts, joins, last, extra, allow_trim_join = self.setup_joins( 1891 1914 start.split(LOOKUP_SEP), opts, alias, False) 1892 1915 select_col = self.alias_map[joins[1]][LHS_JOIN_COL] 1893 1916 select_alias = alias