diff --git a/AUTHORS b/AUTHORS index d1280eb6c9..1479bb8786 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 d1c4e5446b..e51b1037ca 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -133,7 +133,7 @@ class Query(object): # 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/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index cb9e466248..139371d90e 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -40,3 +40,6 @@ Bugfixes weren't logged in the admin change history (:ticket:`27998`) and prevented ``ManyToManyField`` initial data in model forms from being affected by subsequent model changes (:ticket:`28543`). + +* Fixed non-deterministic results or an ``AssertionError`` crash in some + queries with multiple joins (:ticket:`26522`). diff --git a/tests/queries/models.py b/tests/queries/models.py index ab03b9c248..fd76623c33 100644 --- a/tests/queries/models.py +++ b/tests/queries/models.py @@ -709,6 +709,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 6633985f81..877cf8091e 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -28,9 +28,9 @@ from .models import ( ProxyCategory, ProxyObjectA, ProxyObjectB, Ranking, Related, RelatedIndividual, RelatedObject, Report, ReportComment, ReservedName, Responsibility, School, SharedConnection, SimpleCategory, SingleObject, - SpecialCategory, Staff, StaffUser, Student, Tag, Task, Ticket21203Child, - Ticket21203Parent, Ticket23605A, Ticket23605B, Ticket23605C, TvChef, Valid, - X, + SpecialCategory, Staff, StaffUser, Student, Tag, Task, Teacher, + Ticket21203Child, Ticket21203Parent, Ticket23605A, Ticket23605B, + Ticket23605C, TvChef, Valid, X, ) @@ -1391,6 +1391,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.