summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2021-04-20 22:25:52 -0400
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-04-21 10:32:39 +0200
commit48e19bae49f271cccbb8a8f4549c9366b7cecac6 (patch)
treecc52844e77b7884251c60071933b86751249035a
parent1cc2eaf02d2aa64f7ca4ef52f3d9f13381540007 (diff)
[3.2.x] Fixed #32650 -- Fixed handling subquery aliasing on queryset combination.
This issue started manifesting itself when nesting a combined subquery relying on exclude() since 8593e162c9cb63a6c0b06daf045bc1c21eb4d7c1 but sql.Query.combine never properly handled subqueries outer refs in the first place, see QuerySetBitwiseOperationTests.test_subquery_aliases() (refs #27149). Thanks Raffaele Salmaso for the report. Backport of 6d0cbe42c3d382e5393d4af48185c546bb0ada1f from main
-rw-r--r--django/db/models/sql/query.py4
-rw-r--r--docs/releases/3.2.1.txt5
-rw-r--r--tests/queries/tests.py41
3 files changed, 44 insertions, 6 deletions
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 2c6a50934d..73c9e5ca4f 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -634,6 +634,10 @@ class Query(BaseExpression):
joinpromoter.add_votes(rhs_votes)
joinpromoter.update_join_types(self)
+ # Combine subqueries aliases to ensure aliases relabelling properly
+ # handle subqueries when combining where and select clauses.
+ self.subq_aliases |= rhs.subq_aliases
+
# Now relabel a copy of the rhs where-clause and add it to the current
# one.
w = rhs.where.clone()
diff --git a/docs/releases/3.2.1.txt b/docs/releases/3.2.1.txt
index 4aece451e1..07d449ae8d 100644
--- a/docs/releases/3.2.1.txt
+++ b/docs/releases/3.2.1.txt
@@ -51,3 +51,8 @@ Bugfixes
* Fixed a bug in Django 3.2 where a system check would crash on the
:setting:`STATICFILES_DIRS` setting with a list of 2-tuples of
``(prefix, path)`` (:ticket:`32665`).
+
+* Fixed a long standing bug involving queryset bitwise combination when used
+ with subqueries that began manifesting in Django 3.2, due to a separate fix
+ using ``Exists`` to ``exclude()`` multi-valued relationships
+ (:ticket:`32650`).
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 37ba239419..584784fd1d 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -2079,36 +2079,50 @@ class SubqueryTests(TestCase):
)
-@skipUnlessDBFeature('allow_sliced_subqueries_with_in')
class QuerySetBitwiseOperationTests(TestCase):
@classmethod
def setUpTestData(cls):
- school = School.objects.create()
- cls.room_1 = Classroom.objects.create(school=school, has_blackboard=False, name='Room 1')
- cls.room_2 = Classroom.objects.create(school=school, has_blackboard=True, name='Room 2')
- cls.room_3 = Classroom.objects.create(school=school, has_blackboard=True, name='Room 3')
- cls.room_4 = Classroom.objects.create(school=school, has_blackboard=False, name='Room 4')
+ cls.school = School.objects.create()
+ cls.room_1 = Classroom.objects.create(school=cls.school, has_blackboard=False, name='Room 1')
+ cls.room_2 = Classroom.objects.create(school=cls.school, has_blackboard=True, name='Room 2')
+ cls.room_3 = Classroom.objects.create(school=cls.school, has_blackboard=True, name='Room 3')
+ cls.room_4 = Classroom.objects.create(school=cls.school, has_blackboard=False, name='Room 4')
+ @skipUnlessDBFeature('allow_sliced_subqueries_with_in')
def test_or_with_rhs_slice(self):
qs1 = Classroom.objects.filter(has_blackboard=True)
qs2 = Classroom.objects.filter(has_blackboard=False)[:1]
self.assertCountEqual(qs1 | qs2, [self.room_1, self.room_2, self.room_3])
+ @skipUnlessDBFeature('allow_sliced_subqueries_with_in')
def test_or_with_lhs_slice(self):
qs1 = Classroom.objects.filter(has_blackboard=True)[:1]
qs2 = Classroom.objects.filter(has_blackboard=False)
self.assertCountEqual(qs1 | qs2, [self.room_1, self.room_2, self.room_4])
+ @skipUnlessDBFeature('allow_sliced_subqueries_with_in')
def test_or_with_both_slice(self):
qs1 = Classroom.objects.filter(has_blackboard=False)[:1]
qs2 = Classroom.objects.filter(has_blackboard=True)[:1]
self.assertCountEqual(qs1 | qs2, [self.room_1, self.room_2])
+ @skipUnlessDBFeature('allow_sliced_subqueries_with_in')
def test_or_with_both_slice_and_ordering(self):
qs1 = Classroom.objects.filter(has_blackboard=False).order_by('-pk')[:1]
qs2 = Classroom.objects.filter(has_blackboard=True).order_by('-name')[:1]
self.assertCountEqual(qs1 | qs2, [self.room_3, self.room_4])
+ def test_subquery_aliases(self):
+ combined = School.objects.filter(pk__isnull=False) & School.objects.filter(
+ Exists(Classroom.objects.filter(
+ has_blackboard=True,
+ school=OuterRef('pk'),
+ )),
+ )
+ self.assertSequenceEqual(combined, [self.school])
+ nested_combined = School.objects.filter(pk__in=combined.values('pk'))
+ self.assertSequenceEqual(nested_combined, [self.school])
+
class CloneTests(TestCase):
@@ -2802,6 +2816,21 @@ class ExcludeTests(TestCase):
)
self.assertIn('exists', captured_queries[0]['sql'].lower())
+ def test_exclude_subquery(self):
+ subquery = JobResponsibilities.objects.filter(
+ responsibility__description='bar',
+ ) | JobResponsibilities.objects.exclude(
+ job__responsibilities__description='foo',
+ )
+ self.assertSequenceEqual(
+ Job.objects.annotate(
+ responsibility=subquery.filter(
+ job=OuterRef('name'),
+ ).values('id')[:1]
+ ),
+ [self.j1, self.j2],
+ )
+
class ExcludeTest17600(TestCase):
"""