diff options
| author | François Freitag <mail@franek.fr> | 2016-10-26 18:01:37 -0400 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2017-02-09 11:20:33 -0500 |
| commit | e124d2da94cc1233729d8f0200809589f6c5afc8 (patch) | |
| tree | 260c6245a9e66e1d77ede7ce9676b0373e3b897f | |
| parent | ad36e5480d6cafb4031e8eb0bacc3a07d2fe4609 (diff) | |
Fixed #26551 -- Fixed negated Q() queries that span relations.
Prevented queries from reusing trimmed joins.
| -rw-r--r-- | AUTHORS | 1 | ||||
| -rw-r--r-- | django/db/models/sql/query.py | 6 | ||||
| -rw-r--r-- | tests/queries/models.py | 4 | ||||
| -rw-r--r-- | tests/queries/tests.py | 19 |
4 files changed, 26 insertions, 4 deletions
@@ -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): |
