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:
parent
3cdfb47e93
commit
12f6259903
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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__
|
||||||
|
Loading…
x
Reference in New Issue
Block a user