From 541f4ea546ad3065852db816769ba6b584e3f373 Mon Sep 17 00:00:00 2001 From: Mark Lavin Date: Fri, 5 Jun 2015 10:48:57 -0400 Subject: [PATCH] Fixed #24924 -- Join promotion for multiple Case expressions --- django/db/models/query_utils.py | 4 +--- docs/releases/1.8.3.txt | 4 ++++ tests/expressions_case/tests.py | 42 +++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index 8ac6e008ba..5d0b4e619d 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -89,10 +89,8 @@ class Q(tree.Node): def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False): # We must promote any new joins to left outer joins so that when Q is # used as an expression, rows aren't filtered due to joins. - joins_before = query.tables[:] clause, joins = query._add_q(self, reuse, allow_joins=allow_joins, split_subq=False) - joins_to_promote = [j for j in joins if j not in joins_before] - query.promote_joins(joins_to_promote) + query.promote_joins(joins) return clause @classmethod diff --git a/docs/releases/1.8.3.txt b/docs/releases/1.8.3.txt index d544183445..6ac0d3f592 100644 --- a/docs/releases/1.8.3.txt +++ b/docs/releases/1.8.3.txt @@ -53,3 +53,7 @@ Bugfixes * Fixed queryset annotations when using ``Case`` expressions with ``exclude()`` (:ticket:`24833`). + +* Corrected join promotion for multiple ``Case`` expressions. Annotating a + query with multiple ``Case`` expressions could unexpectedly filter out + results (:ticket:`24924`). diff --git a/tests/expressions_case/tests.py b/tests/expressions_case/tests.py index 31ec88c7ec..2b269b5893 100644 --- a/tests/expressions_case/tests.py +++ b/tests/expressions_case/tests.py @@ -1060,6 +1060,48 @@ class CaseExpressionTests(TestCase): lambda x: (x, x.foo) ) + def test_join_promotion_multiple_annonations(self): + o = CaseTestModel.objects.create(integer=1, integer2=1, string='1') + # Testing that: + # 1. There isn't any object on the remote side of the fk_rel + # relation. If the query used inner joins, then the join to fk_rel + # would remove o from the results. So, in effect we are testing that + # we are promoting the fk_rel join to a left outer join here. + # 2. The default value of 3 is generated for the case expression. + self.assertQuerysetEqual( + CaseTestModel.objects.filter(pk=o.pk).annotate( + foo=Case( + When(fk_rel__pk=1, then=2), + default=3, + output_field=models.IntegerField() + ), + bar=Case( + When(fk_rel__pk=1, then=4), + default=5, + output_field=models.IntegerField() + ), + ), + [(o, 3, 5)], + lambda x: (x, x.foo, x.bar) + ) + # Now 2 should be generated, as the fk_rel is null. + self.assertQuerysetEqual( + CaseTestModel.objects.filter(pk=o.pk).annotate( + foo=Case( + When(fk_rel__isnull=True, then=2), + default=3, + output_field=models.IntegerField() + ), + bar=Case( + When(fk_rel__isnull=True, then=4), + default=5, + output_field=models.IntegerField() + ), + ), + [(o, 2, 4)], + lambda x: (x, x.foo, x.bar) + ) + def test_m2m_exclude(self): CaseTestModel.objects.create(integer=10, integer2=1, string='1') qs = CaseTestModel.objects.values_list('id', 'integer').annotate(