From 9bbb6e2d2536c4ac20dc13a94c1f80494e51f8d9 Mon Sep 17 00:00:00 2001 From: Bo Marchman Date: Thu, 2 Mar 2017 09:36:25 -0500 Subject: [PATCH] Fixed #26522 -- Fixed a nondeterministic AssertionError in QuerySet combining. Thanks Andrew Brown for the test case. --- AUTHORS | 1 + django/db/models/sql/query.py | 2 +- tests/queries/models.py | 5 +++++ tests/queries/tests.py | 17 +++++++++++++++-- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 7932c6c5b6..1c945a58c2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -110,6 +110,7 @@ answer newbie questions, and generally made Django that much better: berto Bill Fenner Bjørn Stabell + Bo Marchman Bojan Mihelac Bouke Haarsma Božidar Benko diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 7af04e7d77..53ad31a4e5 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -126,7 +126,7 @@ class Query: # types they are. The key is the alias of the joined table (possibly # the table name) and the value is a Join-like object (see # sql.datastructures.Join for more information). - self.alias_map = {} + self.alias_map = OrderedDict() # Sometimes the query contains references to aliases in outer queries (as # a result of split_exclude). Correct alias quoting needs to know these # aliases too. diff --git a/tests/queries/models.py b/tests/queries/models.py index 25fd673272..797d755002 100644 --- a/tests/queries/models.py +++ b/tests/queries/models.py @@ -663,6 +663,11 @@ class Classroom(models.Model): students = models.ManyToManyField(Student, related_name='classroom') +class Teacher(models.Model): + schools = models.ManyToManyField(School) + friends = models.ManyToManyField('self') + + class Ticket23605AParent(models.Model): pass diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 86e8405822..19ef508eb1 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -24,8 +24,9 @@ from .models import ( ProxyCategory, ProxyObjectA, ProxyObjectB, Ranking, Related, RelatedIndividual, RelatedObject, Report, ReservedName, Responsibility, School, SharedConnection, SimpleCategory, SingleObject, SpecialCategory, - Staff, StaffUser, Student, Tag, Task, Ticket21203Child, Ticket21203Parent, - Ticket23605A, Ticket23605B, Ticket23605C, TvChef, Valid, X, + Staff, StaffUser, Student, Tag, Task, Teacher, Ticket21203Child, + Ticket21203Parent, Ticket23605A, Ticket23605B, Ticket23605C, TvChef, Valid, + X, ) @@ -1382,6 +1383,18 @@ class Queries4Tests(TestCase): self.assertEqual(len(combined), 1) self.assertEqual(combined[0].name, 'a1') + def test_join_reuse_order(self): + # Join aliases are reused in order. This shouldn't raise AssertionError + # because change_map contains a circular reference (#26522). + s1 = School.objects.create() + s2 = School.objects.create() + s3 = School.objects.create() + t1 = Teacher.objects.create() + otherteachers = Teacher.objects.exclude(pk=t1.pk).exclude(friends=t1) + qs1 = otherteachers.filter(schools=s1).filter(schools=s2) + qs2 = otherteachers.filter(schools=s1).filter(schools=s3) + self.assertQuerysetEqual(qs1 | qs2, []) + def test_ticket7095(self): # Updates that are filtered on the model being updated are somewhat # tricky in MySQL.