Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #20528 -- regression in select_related join promotion

The join used by select_related was incorrectly INNER when the query
had an ORed filter for nullable join that was trimmed away. Fixed this
by forcing the join type to LOUTER even when a join was trimmed away
in ORed queries.
  • Loading branch information...
commit 89bf7a45250cf554295f3d17d708311d3edba101 1 parent b7bd708
Anssi Kääriäinen authored June 10, 2013
4  django/db/models/sql/query.py
@@ -1913,5 +1913,7 @@ def alias_diff(refcounts_before, refcounts_after):
1913 1913
     Given the before and after copies of refcounts works out which aliases
1914 1914
     have been added to the after copy.
1915 1915
     """
  1916
+    # Use -1 as default value so that any join that is created, then trimmed
  1917
+    # is seen as added.
1916 1918
     return set(t for t in refcounts_after
1917  
-               if refcounts_after[t] > refcounts_before.get(t, 0))
  1919
+               if refcounts_after[t] > refcounts_before.get(t, -1))
49  tests/queries/tests.py
@@ -16,15 +16,16 @@
16 16
 from django.utils import unittest
17 17
 from django.utils.datastructures import SortedDict
18 18
 
19  
-from .models import (Annotation, Article, Author, Celebrity, Child, Cover,
20  
-    Detail, DumbCategory, ExtraInfo, Fan, Item, LeafA, Join, LeafB, LoopX, LoopZ,
21  
-    ManagedModel, Member, NamedCategory, Note, Number, Plaything, PointerA,
22  
-    Ranking, Related, Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten,
23  
-    Node, ObjectA, ObjectB, ObjectC, CategoryItem, SimpleCategory,
24  
-    SpecialCategory, OneToOneCategory, NullableName, ProxyCategory,
25  
-    SingleObject, RelatedObject, ModelA, ModelB, ModelC, ModelD, Responsibility,
26  
-    Job, JobResponsibilities, BaseA, Identifier, Program, Channel, Page,
27  
-    Paragraph, Chapter, Book, MyObject, Order, OrderItem)
  19
+from .models import (
  20
+    Annotation, Article, Author, Celebrity, Child, Cover, Detail, DumbCategory,
  21
+    ExtraInfo, Fan, Item, LeafA, Join, LeafB, LoopX, LoopZ, ManagedModel,
  22
+    Member, NamedCategory, Note, Number, Plaything, PointerA, Ranking, Related,
  23
+    Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten, Node, ObjectA,
  24
+    ObjectB, ObjectC, CategoryItem, SimpleCategory, SpecialCategory,
  25
+    OneToOneCategory, NullableName, ProxyCategory, SingleObject, RelatedObject,
  26
+    ModelA, ModelB, ModelC, ModelD, Responsibility, Job, JobResponsibilities,
  27
+    BaseA, FK1, Identifier, Program, Channel, Page, Paragraph, Chapter, Book,
  28
+    MyObject, Order, OrderItem)
28 29
 
29 30
 
30 31
 class BaseQuerysetTest(TestCase):
@@ -2620,6 +2621,19 @@ def test_revfk_noreuse(self):
2620 2621
         self.assertEqual(str(qs.query).count('JOIN'), 2)
2621 2622
 
2622 2623
 class DisjunctionPromotionTests(TestCase):
  2624
+    def test_disjuction_promotion_select_related(self):
  2625
+        fk1 = FK1.objects.create(f1='f1', f2='f2')
  2626
+        basea = BaseA.objects.create(a=fk1)
  2627
+        qs = BaseA.objects.filter(Q(a=fk1) | Q(b=2))
  2628
+        self.assertEqual(str(qs.query).count(' JOIN '), 0)
  2629
+        qs = qs.select_related('a', 'b')
  2630
+        self.assertEqual(str(qs.query).count(' INNER JOIN '), 0)
  2631
+        self.assertEqual(str(qs.query).count(' LEFT OUTER JOIN '), 2)
  2632
+        with self.assertNumQueries(1):
  2633
+            self.assertQuerysetEqual(qs, [basea], lambda x: x)
  2634
+            self.assertEqual(qs[0].a, fk1)
  2635
+            self.assertIs(qs[0].b, None)
  2636
+
2623 2637
     def test_disjunction_promotion1(self):
2624 2638
         # Pre-existing join, add two ORed filters to the same join,
2625 2639
         # all joins can be INNER JOINS.
@@ -2669,17 +2683,23 @@ def test_disjunction_promotion3_failing(self):
2669 2683
         self.assertEqual(str(qs.query).count('INNER JOIN'), 1)
2670 2684
         self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 1)
2671 2685
 
2672  
-    def test_disjunction_promotion4(self):
  2686
+    @unittest.expectedFailure
  2687
+    def test_disjunction_promotion4_failing(self):
  2688
+        # Failure because no join repromotion
2673 2689
         qs = BaseA.objects.filter(Q(a=1) | Q(a=2))
2674 2690
         self.assertEqual(str(qs.query).count('JOIN'), 0)
2675 2691
         qs = qs.filter(a__f1='foo')
2676 2692
         self.assertEqual(str(qs.query).count('INNER JOIN'), 1)
  2693
+
  2694
+    def test_disjunction_promotion4(self):
2677 2695
         qs = BaseA.objects.filter(a__f1='foo')
2678 2696
         self.assertEqual(str(qs.query).count('INNER JOIN'), 1)
2679 2697
         qs = qs.filter(Q(a=1) | Q(a=2))
2680 2698
         self.assertEqual(str(qs.query).count('INNER JOIN'), 1)
2681 2699
 
2682  
-    def test_disjunction_promotion5(self):
  2700
+    @unittest.expectedFailure
  2701
+    def test_disjunction_promotion5_failing(self):
  2702
+        # Failure because no join repromotion logic.
2683 2703
         qs = BaseA.objects.filter(Q(a=1) | Q(a=2))
2684 2704
         # Note that the above filters on a force the join to an
2685 2705
         # inner join even if it is trimmed.
@@ -2688,15 +2708,10 @@ def test_disjunction_promotion5(self):
2688 2708
         # So, now the a__f1 join doesn't need promotion.
2689 2709
         self.assertEqual(str(qs.query).count('INNER JOIN'), 1)
2690 2710
         self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 1)
2691  
-
2692  
-    @unittest.expectedFailure
2693  
-    def test_disjunction_promotion5_failing(self):
2694 2711
         qs = BaseA.objects.filter(Q(a__f1='foo') | Q(b__f1='foo'))
2695 2712
         # Now the join to a is created as LOUTER
2696 2713
         self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 0)
2697  
-        # The below filter should force the a to be inner joined. But,
2698  
-        # this is failing as we do not have join unpromotion logic.
2699  
-        qs = BaseA.objects.filter(Q(a=1) | Q(a=2))
  2714
+        qs = qs.objects.filter(Q(a=1) | Q(a=2))
2700 2715
         self.assertEqual(str(qs.query).count('INNER JOIN'), 1)
2701 2716
         self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 1)
2702 2717
 

0 notes on commit 89bf7a4

Please sign in to comment.
Something went wrong with that request. Please try again.