Code

Ticket #12252: 12252a.diff

File 12252a.diff, 5.8 KB (added by benreynwar, 4 years ago)
Line 
1Index: django/db/models/sql/query.py
2===================================================================
3--- django/db/models/sql/query.py       (revision 12831)
4+++ django/db/models/sql/query.py       (working copy)
5@@ -407,6 +407,8 @@
6             "Cannot combine a unique query with a non-unique query."
7 
8         self.remove_inherited_models()
9+        l_tables = set([a for a in self.tables if self.alias_refcount[a]])
10+        r_tables = set([a for a in rhs.tables if rhs.alias_refcount[a]])
11         # Work out how to relabel the rhs aliases, if necessary.
12         change_map = {}
13         used = set()
14@@ -424,13 +426,19 @@
15             first = False
16 
17         # So that we don't exclude valid results in an "or" query combination,
18-        # the first join that is exclusive to the lhs (self) must be converted
19+        # all joins exclusive to either the lhs or the rhs must be converted
20         # to an outer join.
21         if not conjunction:
22-            for alias in self.tables[1:]:
23-                if self.alias_refcount[alias] == 1:
24-                    self.promote_alias(alias, True)
25-                    break
26+            # Update r_tables aliases.
27+            for alias in change_map:
28+                if alias in r_tables:
29+                    r_tables.remove(alias)
30+                    r_tables.add(change_map[alias])
31+            # Find aliases that are exclusive to rhs or lhs.
32+            # These are promoted to outer joins.
33+            outer_aliases = (l_tables | r_tables) - (l_tables & r_tables)
34+            for alias in outer_aliases:
35+                self.promote_alias(alias, True)
36 
37         # Now relabel a copy of the rhs where-clause and add it to the current
38         # one.
39Index: tests/regressiontests/queries/tests.py
40===================================================================
41--- tests/regressiontests/queries/tests.py      (revision 12831)
42+++ tests/regressiontests/queries/tests.py      (working copy)
43@@ -1,6 +1,6 @@
44 import unittest
45-from models import Tag, Annotation
46-from django.db.models import Count
47+from models import Tag, Annotation, ObjectA, ObjectB, ObjectC
48+from django.db.models import Count, Q
49 
50 class QuerysetOrderedTests(unittest.TestCase):
51     """
52@@ -24,4 +24,71 @@
53         qs = Annotation.objects.annotate(num_notes=Count('notes'))
54         self.assertEqual(qs.ordered, False)
55         self.assertEqual(qs.order_by('num_notes').ordered, True)
56-       
57\ No newline at end of file
58+
59+
60+class UnionTests(unittest.TestCase):
61+    """
62+    Tests for the union of two querysets.
63+    Bug #12252.
64+    """
65+   
66+    def __init__(self, *args, **kwargs):
67+        super(UnionTests, self).__init__(*args, **kwargs)
68+
69+    def setUp(self):
70+        # Don't bother setting up if already done
71+        if ObjectA.objects.all():
72+            return
73+        objectas = []
74+        objectbs = []
75+        objectcs = []
76+        a_info = ['one', 'two', 'three']
77+        for name in a_info:
78+            o = ObjectA(name=name)
79+            o.save()
80+            objectas.append(o)
81+        b_info = [('un', 1, objectas[0]), ('deux', 2, objectas[0]), ('trois', 3, objectas[2])]
82+        for name, number, objecta in b_info:
83+            o = ObjectB(name=name, number=number, objecta=objecta)
84+            o.save()
85+            objectbs.append(o)
86+        c_info = [('ein', objectas[2], objectbs[2]), ('zwei', objectas[1], objectbs[1])]
87+        for name, objecta, objectb in c_info:
88+            o = ObjectC(name=name, objecta=objecta, objectb=objectb)
89+            o.save()
90+            objectcs.append(o)
91+
92+    def check_union(self, model, Q1, Q2):
93+        filter = model.objects.filter
94+        self.assertEqual(set(filter(Q1) | filter(Q2)), set(filter(Q1 | Q2)))
95+        self.assertEqual(set(filter(Q2) | filter(Q1)), set(filter(Q1 | Q2)))
96+       
97+    def test_A_AB(self):
98+        Q1 = Q(name='two')
99+        Q2 = Q(objectb__name='deux')
100+        self.check_union(ObjectA, Q1, Q2)
101+       
102+    def test_A_AB2(self):
103+        Q1 = Q(name='two')
104+        Q2 = Q(objectb__name='deux', objectb__number=2)
105+        self.check_union(ObjectA, Q1, Q2)
106+
107+    def test_AB_ACB(self):
108+        Q1 = Q(objectb__name='deux')
109+        Q2 = Q(objectc__objectb__name='deux')
110+        self.check_union(ObjectA, Q1, Q2)
111+
112+    def test_BAB_BAC(self):
113+        Q1 = Q(objecta__objectb__name='deux')
114+        Q2 = Q(objecta__objectc__name='ein')
115+        self.check_union(ObjectB, Q1, Q2)
116+       
117+    def test_BAB_BACB(self):
118+        Q1 = Q(objecta__objectb__name='deux')
119+        Q2 = Q(objecta__objectc__objectb__name='trois')
120+        self.check_union(ObjectB, Q1, Q2)
121+
122+    def test_BA_BCA__BAB_BAC_BCA(self):
123+        Q1 = Q(objecta__name='one', objectc__objecta__name='two')
124+        Q2 = Q(objecta__objectc__name='ein', objectc__objecta__name='three', objecta__objectb__name='trois')
125+        self.check_union(ObjectB, Q1, Q2)
126Index: tests/regressiontests/queries/models.py
127===================================================================
128--- tests/regressiontests/queries/models.py     (revision 12831)
129+++ tests/regressiontests/queries/models.py     (working copy)
130@@ -275,7 +275,30 @@
131     def __unicode__(self):
132         return self.name
133 
134+# Bug #12252
135+class ObjectA(models.Model):
136+    name = models.CharField(max_length=50)
137 
138+    def __unicode__(self):
139+        return self.name
140+
141+class ObjectB(models.Model):
142+    name = models.CharField(max_length=50)
143+    objecta = models.ForeignKey(ObjectA)
144+    number = models.PositiveSmallIntegerField()
145+
146+    def __unicode__(self):
147+        return self.name
148+
149+class ObjectC(models.Model):
150+    name = models.CharField(max_length=50)
151+    objecta = models.ForeignKey(ObjectA)
152+    objectb = models.ForeignKey(ObjectB)
153+
154+    def __unicode__(self):
155+        return self.name
156+
157+
158 __test__ = {'API_TESTS':"""
159 >>> # Regression for #13156 -- exists() queries have minimal SQL
160 >>> from django.db import connection