Fixed #26551 -- Fixed negated Q() queries that span relations.

Prevented queries from reusing trimmed joins.
This commit is contained in:
François Freitag 2016-10-26 18:01:37 -04:00 committed by Tim Graham
parent ad36e5480d
commit e124d2da94
4 changed files with 26 additions and 4 deletions

View File

@ -258,6 +258,7 @@ answer newbie questions, and generally made Django that much better:
flavio.curella@gmail.com
Florian Apolloner <florian@apolloner.eu>
Francisco Albarran Cristobal <pahko.xd@gmail.com>
François Freitag <mail@franek.fr>
Frank Tegtmeyer <fte@fte.to>
Frank Wierzbicki
Frank Wiles <frank@revsys.com>

View File

@ -1192,10 +1192,12 @@ class Query:
return self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]),
can_reuse, e.names_with_path)
if can_reuse is not None:
can_reuse.update(join_list)
# Update used_joins before trimming since they are reused to determine
# which joins could be later promoted to INNER.
used_joins = set(used_joins).union(set(join_list))
targets, alias, join_list = self.trim_joins(sources, join_list, path)
if can_reuse is not None:
can_reuse.update(join_list)
if field.is_relation:
# No support for transforms for relational fields

View File

@ -648,8 +648,6 @@ class Employment(models.Model):
title = models.CharField(max_length=128)
# Bug #22429
class School(models.Model):
pass
@ -659,6 +657,8 @@ class Student(models.Model):
class Classroom(models.Model):
name = models.CharField(max_length=20)
has_blackboard = models.NullBooleanField()
school = models.ForeignKey(School, models.CASCADE)
students = models.ManyToManyField(Student, related_name='classroom')

View File

@ -3147,6 +3147,25 @@ class JoinReuseTest(TestCase):
qs = Author.objects.filter(report__name='r4').filter(report__name='r1')
self.assertEqual(str(qs.query).count('JOIN'), 2)
def test_inverted_q_across_relations(self):
"""
When a trimmable join is specified in the query (here school__), the
ORM detects it and removes unnecessary joins. The set of reusable joins
are updated after trimming the query so that other lookups don't
consider that the outer query's filters are in effect for the subquery
(#26551).
"""
springfield_elementary = School.objects.create()
hogward = School.objects.create()
Student.objects.create(school=springfield_elementary)
hp = Student.objects.create(school=hogward)
Classroom.objects.create(school=hogward, name='Potion')
Classroom.objects.create(school=springfield_elementary, name='Main')
qs = Student.objects.filter(
~(Q(school__classroom__name='Main') & Q(school__classroom__has_blackboard=None))
)
self.assertSequenceEqual(qs, [hp])
class DisjunctionPromotionTests(TestCase):
def test_disjunction_promotion_select_related(self):