Commit 541f4ea5 authored by Mark Lavin's avatar Mark Lavin Committed by Tim Graham
Browse files

Fixed #24924 -- Join promotion for multiple Case expressions

parent f0450c9b
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -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
+4 −0
Original line number Diff line number Diff line
@@ -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`).
+42 −0
Original line number Diff line number Diff line
@@ -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(