Author: mtredinnick
Date: 2008-09-01 21:16:41 -0500 (Mon, 01 Sep 2008)
New Revision: 8832
Modified:
django/trunk/django/db/models/sql/constants.py
django/trunk/django/db/models/sql/query.py
django/trunk/tests/regressiontests/queries/models.py
Log:
Fixed #8439 -- Complex combinations of Q-objects (using both conjunctions and
disjunctions) were producing incorrect SQL when nullable relations were
involved. This fixes that.
Modified: django/trunk/django/db/models/sql/constants.py
===================================================================
--- django/trunk/django/db/models/sql/constants.py 2008-09-02 00:50:19 UTC
(rev 8831)
+++ django/trunk/django/db/models/sql/constants.py 2008-09-02 02:16:41 UTC
(rev 8832)
@@ -15,7 +15,8 @@
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
Modified: django/trunk/django/db/models/sql/query.py
===================================================================
--- django/trunk/django/db/models/sql/query.py 2008-09-02 00:50:19 UTC (rev
8831)
+++ django/trunk/django/db/models/sql/query.py 2008-09-02 02:16:41 UTC (rev
8832)
@@ -716,7 +716,6 @@
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 @@
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 @@
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()
Modified: django/trunk/tests/regressiontests/queries/models.py
===================================================================
--- django/trunk/tests/regressiontests/queries/models.py 2008-09-02
00:50:19 UTC (rev 8831)
+++ django/trunk/tests/regressiontests/queries/models.py 2008-09-02
02:16:41 UTC (rev 8832)
@@ -223,10 +223,10 @@
class ReservedName(models.Model):
name = models.CharField(max_length=20)
order = models.IntegerField()
-
+
def __unicode__(self):
return self.name
-
+
__test__ = {'API_TESTS':"""
>>> t1 = Tag.objects.create(name='t1')
>>> t2 = Tag.objects.create(name='t2', parent=t1)
@@ -238,6 +238,11 @@
>>> 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 @@
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 @@
>>> _ = 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__
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---