[1.8.x] Fixed #24766 -- Added join promotion for Case expressions

Backport of be9d645346 from master
This commit is contained in:
Anssi Kääriäinen 2015-05-11 10:02:41 +03:00 committed by Tim Graham
parent d3a8f36fdb
commit 056a91dbfa
3 changed files with 42 additions and 1 deletions

View File

@ -85,7 +85,12 @@ class Q(tree.Node):
return clone
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
clause, _ = query._add_q(self, reuse, allow_joins=allow_joins)
# 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)
joins_to_promote = [j for j in joins if j not in joins_before]
query.promote_joins(joins_to_promote)
return clause
@classmethod

View File

@ -13,3 +13,7 @@ Bugfixes
* Fixed crash when reusing the same ``Case`` instance in a query
(:ticket:`24752`).
* Corrected join promotion for ``Case`` expressions. For example, annotating a
query with a ``Case`` expression could unexpectedly filter out results
(:ticket:`24766`).

View File

@ -1031,6 +1031,38 @@ class CaseExpressionTests(TestCase):
transform=attrgetter('integer', 'test')
)
def test_join_promotion(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()
),
),
[(o, 3)],
lambda x: (x, x.foo)
)
# 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()
),
),
[(o, 2)],
lambda x: (x, x.foo)
)
class CaseDocumentationExamples(TestCase):
@classmethod