Ticket #10790: 107090v3.diff
File 107090v3.diff, 9.2 KB (added by , 14 years ago) |
---|
-
django/db/models/sql/compiler.py
374 374 pieces = name.split(LOOKUP_SEP) 375 375 if not alias: 376 376 alias = self.query.get_initial_alias() 377 field, target, opts, joins, last, extra = self.query.setup_joins(pieces,378 opts, alias, False)377 field, target, opts, joins, last, extra, allow_trim_join = self.query.setup_joins( 378 pieces, opts, alias, False) 379 379 alias = joins[-1] 380 380 col = target.column 381 381 if not field.rel: -
django/db/models/sql/expressions.py
41 41 self.cols[node] = query.aggregate_select[node.name] 42 42 else: 43 43 try: 44 field, source, opts, join_list, last, _ = query.setup_joins(44 field, source, opts, join_list, last, _, allow_trim_join = query.setup_joins( 45 45 field_list, query.get_meta(), 46 46 query.get_initial_alias(), False) 47 47 col, _, join_list = query.trim_joins(source, join_list, last, False) -
django/db/models/sql/query.py
651 651 return True 652 652 return False 653 653 654 def demote_alias(self, alias): 655 """ 656 Demotes the join type of an alias to an inner join. 657 """ 658 data = list(self.alias_map[alias]) 659 data[JOIN_TYPE] = self.INNER 660 self.alias_map[alias] = tuple(data) 661 654 662 def promote_alias_chain(self, chain, must_promote=False): 655 663 """ 656 664 Walks along a chain of aliases, promoting the first nullable join and … … 929 937 # - this is an annotation over a model field 930 938 # then we need to explore the joins that are required. 931 939 932 field, source, opts, join_list, last, _ = self.setup_joins(940 field, source, opts, join_list, last, _, allow_trim_join = self.setup_joins( 933 941 field_list, opts, self.get_initial_alias(), False) 934 942 935 943 # Process the join chain to see if it can be trimmed … … 1021 1029 allow_many = trim or not negate 1022 1030 1023 1031 try: 1024 field, target, opts, join_list, last, extra_filters = self.setup_joins(1032 field, target, opts, join_list, last, extra_filters, allow_trim_join = self.setup_joins( 1025 1033 parts, opts, alias, True, allow_many, can_reuse=can_reuse, 1026 1034 negate=negate, process_extras=process_extras) 1027 1035 except MultiJoin, e: … … 1036 1044 # needed, as it's less efficient at the database level. 1037 1045 self.promote_alias_chain(join_list) 1038 1046 1047 # If we have a one2one or many2one field, we can trim the left outer 1048 # join from the end of a list of joins. 1049 # In order to do this, we convert alias join type back to INNER and 1050 # trim_joins later will do the strip for us. 1051 if allow_trim_join and field.rel: 1052 self.demote_alias(join_list[-1]) 1053 1039 1054 # Process the join list to see if we can remove any inner joins from 1040 1055 # the far end (fewer tables in a query is better). 1041 1056 col, alias, join_list = self.trim_joins(target, join_list, last, trim) … … 1166 1181 dupe_set = set() 1167 1182 exclusions = set() 1168 1183 extra_filters = [] 1184 allow_trim_join = True 1169 1185 for pos, name in enumerate(names): 1170 1186 try: 1171 1187 exclusions.add(int_alias) … … 1190 1206 raise FieldError("Cannot resolve keyword %r into field. " 1191 1207 "Choices are: %s" % (name, ", ".join(names))) 1192 1208 1209 # presence of indirect field in the filter requires 1210 # left outer join for isnull 1211 if not direct and allow_trim_join: 1212 allow_trim_join = False 1213 1193 1214 if not allow_many and (m2m or not direct): 1194 1215 for alias in joins: 1195 1216 self.unref_alias(alias) … … 1231 1252 extra_filters.extend(field.extra_filters(names, pos, negate)) 1232 1253 if direct: 1233 1254 if m2m: 1255 # null query on m2mfield requires outer join 1256 allow_trim_join = False 1234 1257 # Many-to-many field defined on the current model. 1235 1258 if cached_data: 1236 1259 (table1, from_col1, to_col1, table2, from_col2, … … 1340 1363 else: 1341 1364 raise FieldError("Join on field %r not permitted." % name) 1342 1365 1343 return field, target, opts, joins, last, extra_filters 1366 return field, target, opts, joins, last, extra_filters, allow_trim_join 1344 1367 1345 1368 def trim_joins(self, target, join_list, last, trim): 1346 1369 """ … … 1489 1512 1490 1513 try: 1491 1514 for name in field_names: 1492 field, target, u2, joins, u3, u4 = self.setup_joins(1515 field, target, u2, joins, u3, u4, allow_trim_join = self.setup_joins( 1493 1516 name.split(LOOKUP_SEP), opts, alias, False, allow_m2m, 1494 1517 True) 1495 1518 final_alias = joins[-1] … … 1766 1789 """ 1767 1790 opts = self.model._meta 1768 1791 alias = self.get_initial_alias() 1769 field, col, opts, joins, last, extra = self.setup_joins(1792 field, col, opts, joins, last, extra, allow_trim_join = self.setup_joins( 1770 1793 start.split(LOOKUP_SEP), opts, alias, False) 1771 1794 select_col = self.alias_map[joins[1]][LHS_JOIN_COL] 1772 1795 select_alias = alias -
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