Kaydet (Commit) e124d2da authored tarafından François Freitag's avatar François Freitag Kaydeden (comit) Tim Graham

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

Prevented queries from reusing trimmed joins.
üst ad36e548
...@@ -258,6 +258,7 @@ answer newbie questions, and generally made Django that much better: ...@@ -258,6 +258,7 @@ answer newbie questions, and generally made Django that much better:
flavio.curella@gmail.com flavio.curella@gmail.com
Florian Apolloner <florian@apolloner.eu> Florian Apolloner <florian@apolloner.eu>
Francisco Albarran Cristobal <pahko.xd@gmail.com> Francisco Albarran Cristobal <pahko.xd@gmail.com>
François Freitag <mail@franek.fr>
Frank Tegtmeyer <fte@fte.to> Frank Tegtmeyer <fte@fte.to>
Frank Wierzbicki Frank Wierzbicki
Frank Wiles <frank@revsys.com> Frank Wiles <frank@revsys.com>
......
...@@ -1192,10 +1192,12 @@ class Query: ...@@ -1192,10 +1192,12 @@ class Query:
return self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]), return self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]),
can_reuse, e.names_with_path) can_reuse, e.names_with_path)
if can_reuse is not None: # Update used_joins before trimming since they are reused to determine
can_reuse.update(join_list) # which joins could be later promoted to INNER.
used_joins = set(used_joins).union(set(join_list)) used_joins = set(used_joins).union(set(join_list))
targets, alias, join_list = self.trim_joins(sources, join_list, path) 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: if field.is_relation:
# No support for transforms for relational fields # No support for transforms for relational fields
......
...@@ -648,8 +648,6 @@ class Employment(models.Model): ...@@ -648,8 +648,6 @@ class Employment(models.Model):
title = models.CharField(max_length=128) title = models.CharField(max_length=128)
# Bug #22429
class School(models.Model): class School(models.Model):
pass pass
...@@ -659,6 +657,8 @@ class Student(models.Model): ...@@ -659,6 +657,8 @@ class Student(models.Model):
class Classroom(models.Model): class Classroom(models.Model):
name = models.CharField(max_length=20)
has_blackboard = models.NullBooleanField()
school = models.ForeignKey(School, models.CASCADE) school = models.ForeignKey(School, models.CASCADE)
students = models.ManyToManyField(Student, related_name='classroom') students = models.ManyToManyField(Student, related_name='classroom')
......
...@@ -3147,6 +3147,25 @@ class JoinReuseTest(TestCase): ...@@ -3147,6 +3147,25 @@ class JoinReuseTest(TestCase):
qs = Author.objects.filter(report__name='r4').filter(report__name='r1') qs = Author.objects.filter(report__name='r4').filter(report__name='r1')
self.assertEqual(str(qs.query).count('JOIN'), 2) 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): class DisjunctionPromotionTests(TestCase):
def test_disjunction_promotion_select_related(self): def test_disjunction_promotion_select_related(self):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment