1
0
mirror of https://github.com/django/django.git synced 2025-06-05 11:39:13 +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:
Malcolm Tredinnick 2008-09-02 02:16:41 +00:00
parent 3cdfb47e93
commit 12f6259903
3 changed files with 49 additions and 8 deletions

View File

@ -15,7 +15,8 @@ GET_ITERATOR_CHUNK_SIZE = 100
LOOKUP_SEP = '__' LOOKUP_SEP = '__'
# Constants to make looking up tuple values clearer. # 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 TABLE_NAME = 0
RHS_ALIAS = 1 RHS_ALIAS = 1
JOIN_TYPE = 2 JOIN_TYPE = 2

View File

@ -716,7 +716,6 @@ class Query(object):
alias = table_name alias = table_name
self.table_map[alias] = [alias] self.table_map[alias] = [alias]
self.alias_refcount[alias] = 1 self.alias_refcount[alias] = 1
#self.alias_map[alias] = None
self.tables.append(alias) self.tables.append(alias)
return alias, True return alias, True
@ -1188,6 +1187,8 @@ class Query(object):
subtree = False subtree = False
connector = AND connector = AND
for child in q_object.children: for child in q_object.children:
if connector == OR:
refcounts_before = self.alias_refcount.copy()
if isinstance(child, Node): if isinstance(child, Node):
self.where.start_subtree(connector) self.where.start_subtree(connector)
self.add_q(child, used_aliases) self.add_q(child, used_aliases)
@ -1195,6 +1196,27 @@ class Query(object):
else: else:
self.add_filter(child, connector, q_object.negated, self.add_filter(child, connector, q_object.negated,
can_reuse=used_aliases) 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 connector = q_object.connector
if q_object.negated: if q_object.negated:
self.where.negate() self.where.negate()

View File

@ -223,10 +223,10 @@ class Join(models.Model):
class ReservedName(models.Model): class ReservedName(models.Model):
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
order = models.IntegerField() order = models.IntegerField()
def __unicode__(self): def __unicode__(self):
return self.name return self.name
__test__ = {'API_TESTS':""" __test__ = {'API_TESTS':"""
>>> t1 = Tag.objects.create(name='t1') >>> t1 = Tag.objects.create(name='t1')
>>> t2 = Tag.objects.create(name='t2', parent=t1) >>> t2 = Tag.objects.create(name='t2', parent=t1)
@ -238,6 +238,11 @@ __test__ = {'API_TESTS':"""
>>> n2 = Note.objects.create(note='n2', misc='bar') >>> n2 = Note.objects.create(note='n2', misc='bar')
>>> n3 = Note.objects.create(note='n3', misc='foo') >>> 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 Create these out of order so that sorting by 'id' will be different to sorting
by 'info'. Helps detect some problems later. by 'info'. Helps detect some problems later.
>>> e2 = ExtraInfo.objects.create(info='e2', note=n2) >>> e2 = ExtraInfo.objects.create(info='e2', note=n2)
@ -842,8 +847,6 @@ unpickling.
True True
Bug #7277 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)) >>> n1.annotation_set.filter(Q(tag=t5) | Q(tag__children=t5) | Q(tag__children__children=t5))
[<Annotation: a1>] [<Annotation: a1>]
@ -931,10 +934,25 @@ Bug #7302: reserved names are appropriately escaped
>>> _ = ReservedName.objects.create(name='b',order=37) >>> _ = ReservedName.objects.create(name='b',order=37)
>>> ReservedName.objects.all().order_by('order') >>> ReservedName.objects.all().order_by('order')
[<ReservedName: b>, <ReservedName: a>] [<ReservedName: b>, <ReservedName: a>]
>>> ReservedName.objects.extra(select={'stuff':'name'}, order_by=('order','stuff')) >>> ReservedName.objects.extra(select={'stuff':'name'}, order_by=('order','stuff'))
[<ReservedName: b>, <ReservedName: a>] [<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__ # In Python 2.3 and the Python 2.6 beta releases, exceptions raised in __len__