mirror of
				https://github.com/django/django.git
				synced 2025-10-30 17:16:10 +00:00 
			
		
		
		
	Fixed #8439 -- Complex combinations of Q-objects (using both conjunctions and
disjunctions) were producing incorrect SQL when nullable relations were involved. This fixes that. git-svn-id: http://code.djangoproject.com/svn/django/trunk@8832 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -15,7 +15,8 @@ GET_ITERATOR_CHUNK_SIZE = 100 | ||||
| LOOKUP_SEP = '__' | ||||
|  | ||||
| # Constants to make looking up tuple values clearer. | ||||
| # Join lists | ||||
| # Join lists (indexes into the tuples that are values in the alias_map | ||||
| # dictionary in the Query class). | ||||
| TABLE_NAME = 0 | ||||
| RHS_ALIAS = 1 | ||||
| JOIN_TYPE = 2 | ||||
|   | ||||
| @@ -716,7 +716,6 @@ class Query(object): | ||||
|             alias = table_name | ||||
|             self.table_map[alias] = [alias] | ||||
|         self.alias_refcount[alias] = 1 | ||||
|         #self.alias_map[alias] = None | ||||
|         self.tables.append(alias) | ||||
|         return alias, True | ||||
|  | ||||
| @@ -1188,6 +1187,8 @@ class Query(object): | ||||
|                 subtree = False | ||||
|             connector = AND | ||||
|             for child in q_object.children: | ||||
|                 if connector == OR: | ||||
|                     refcounts_before = self.alias_refcount.copy() | ||||
|                 if isinstance(child, Node): | ||||
|                     self.where.start_subtree(connector) | ||||
|                     self.add_q(child, used_aliases) | ||||
| @@ -1195,6 +1196,27 @@ class Query(object): | ||||
|                 else: | ||||
|                     self.add_filter(child, connector, q_object.negated, | ||||
|                             can_reuse=used_aliases) | ||||
|                 if connector == OR: | ||||
|                     # Aliases that were newly added or not used at all need to | ||||
|                     # be promoted to outer joins if they are nullable relations. | ||||
|                     # (they shouldn't turn the whole conditional into the empty | ||||
|                     # set just because they don't match anything). | ||||
|                     # FIXME: There's some (a lot of!) overlap with the similar | ||||
|                     # OR promotion in add_filter(). It's not quite identical, | ||||
|                     # but is very similar. So pulling out the common bits is | ||||
|                     # something for later (code smell: too much indentation | ||||
|                     # here) | ||||
|                     considered = {} | ||||
|                     for alias in self.tables: | ||||
|                         if alias not in used_aliases: | ||||
|                             continue | ||||
|                         if (alias not in refcounts_before or | ||||
|                                 self.alias_refcount[alias] == | ||||
|                                 refcounts_before[alias]): | ||||
|                             parent = self.alias_map[alias][LHS_ALIAS] | ||||
|                             must_promote = considered.get(parent, False) | ||||
|                             promoted = self.promote_alias(alias, must_promote) | ||||
|                             considered[alias] = must_promote or promoted | ||||
|                 connector = q_object.connector | ||||
|             if q_object.negated: | ||||
|                 self.where.negate() | ||||
|   | ||||
| @@ -238,6 +238,11 @@ __test__ = {'API_TESTS':""" | ||||
| >>> n2 = Note.objects.create(note='n2', misc='bar') | ||||
| >>> n3 = Note.objects.create(note='n3', misc='foo') | ||||
|  | ||||
| >>> ann1 = Annotation.objects.create(name='a1', tag=t1) | ||||
| >>> ann1.notes.add(n1) | ||||
| >>> ann2 = Annotation.objects.create(name='a2', tag=t4) | ||||
| >>> ann2.notes.add(n2, n3) | ||||
|  | ||||
| Create these out of order so that sorting by 'id' will be different to sorting | ||||
| by 'info'. Helps detect some problems later. | ||||
| >>> e2 = ExtraInfo.objects.create(info='e2', note=n2) | ||||
| @@ -842,8 +847,6 @@ unpickling. | ||||
| True | ||||
|  | ||||
| Bug #7277 | ||||
| >>> ann1 = Annotation.objects.create(name='a1', tag=t1) | ||||
| >>> ann1.notes.add(n1) | ||||
| >>> n1.annotation_set.filter(Q(tag=t5) | Q(tag__children=t5) | Q(tag__children__children=t5)) | ||||
| [<Annotation: a1>] | ||||
|  | ||||
| @@ -931,10 +934,25 @@ Bug #7302: reserved names are appropriately escaped | ||||
| >>> _ = ReservedName.objects.create(name='b',order=37) | ||||
| >>> ReservedName.objects.all().order_by('order') | ||||
| [<ReservedName: b>, <ReservedName: a>] | ||||
|  | ||||
| >>> ReservedName.objects.extra(select={'stuff':'name'}, order_by=('order','stuff')) | ||||
| [<ReservedName: b>, <ReservedName: a>] | ||||
|  | ||||
| Bug #8439 -- complex combinations of conjunctions, disjunctions and nullable | ||||
| relations. | ||||
| >>> Author.objects.filter(Q(item__note__extrainfo=e2)|Q(report=r1, name='xyz')) | ||||
| [<Author: a2>] | ||||
| >>> Author.objects.filter(Q(report=r1, name='xyz')|Q(item__note__extrainfo=e2)) | ||||
| [<Author: a2>] | ||||
| >>> Annotation.objects.filter(Q(tag__parent=t1)|Q(notes__note='n1', name='a1')) | ||||
| [<Annotation: a1>] | ||||
| >>> xx = ExtraInfo.objects.create(info='xx', note=n3) | ||||
| >>> Note.objects.filter(Q(extrainfo__author=a1)|Q(extrainfo=xx)) | ||||
| [<Note: n1>, <Note: n3>] | ||||
| >>> xx.delete() | ||||
| >>> q = Note.objects.filter(Q(extrainfo__author=a1)|Q(extrainfo=xx)).query | ||||
| >>> len([x[2] for x in q.alias_map.values() if x[2] == q.LOUTER and q.alias_refcount[x[1]]]) | ||||
| 1 | ||||
|  | ||||
| """} | ||||
|  | ||||
| # In Python 2.3 and the Python 2.6 beta releases, exceptions raised in __len__ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user