summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrançois Freitag <mail@franek.fr>2016-10-26 18:01:37 -0400
committerTim Graham <timograham@gmail.com>2017-02-09 11:20:33 -0500
commite124d2da94cc1233729d8f0200809589f6c5afc8 (patch)
tree260c6245a9e66e1d77ede7ce9676b0373e3b897f
parentad36e5480d6cafb4031e8eb0bacc3a07d2fe4609 (diff)
Fixed #26551 -- Fixed negated Q() queries that span relations.
Prevented queries from reusing trimmed joins.
-rw-r--r--AUTHORS1
-rw-r--r--django/db/models/sql/query.py6
-rw-r--r--tests/queries/models.py4
-rw-r--r--tests/queries/tests.py19
4 files changed, 26 insertions, 4 deletions
diff --git a/AUTHORS b/AUTHORS
index 984b49f4f5..e67a9014d1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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>
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index f76519401b..9040c5a5a4 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -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
diff --git a/tests/queries/models.py b/tests/queries/models.py
index 592359c796..25fd673272 100644
--- a/tests/queries/models.py
+++ b/tests/queries/models.py
@@ -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')
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index d6e726d4d3..c4181a10b5 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -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):