Fixed #32650 -- Fixed handling subquery aliasing on queryset combination.
This issue started manifesting itself when nesting a combined subquery
relying on exclude() since 8593e162c9
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.
This commit is contained in:
parent
34d1905712
commit
6d0cbe42c3
|
@ -631,6 +631,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()
|
||||
|
|
|
@ -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`).
|
||||
|
|
|
@ -2063,36 +2063,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):
|
||||
|
||||
|
@ -2790,6 +2804,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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue