From 4acae21846f6212aa992763e587c7e201828d7b0 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Thu, 27 Apr 2017 00:49:17 -0400 Subject: [PATCH] Fixed #24254 -- Fixed queries using the __in lookup with querysets using distinct() and order_by(). Thanks Tim for the review. --- django/db/models/sql/compiler.py | 25 +++++++++++++++++++++++++ tests/queries/tests.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 2543755952..6874ae6ef4 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -523,6 +523,31 @@ class SQLCompiler: if for_update_part and not self.connection.features.for_update_after_from: result.append(for_update_part) + if self.query.subquery and extra_select: + # If the query is used as a subquery, the extra selects would + # result in more columns than the left-hand side expression is + # expecting. This can happen when a subquery uses a combination + # of order_by() and distinct(), forcing the ordering expressions + # to be selected as well. Wrap the query in another subquery + # to exclude extraneous selects. + sub_selects = [] + sub_params = [] + for select, _, alias in self.select: + if alias: + sub_selects.append("%s.%s" % ( + self.connection.ops.quote_name('subquery'), + self.connection.ops.quote_name(alias), + )) + else: + select_clone = select.relabeled_clone({select.alias: 'subquery'}) + subselect, subparams = select_clone.as_sql(self, self.connection) + sub_selects.append(subselect) + sub_params.extend(subparams) + return 'SELECT %s FROM (%s) AS subquery' % ( + ', '.join(sub_selects), + ' '.join(result), + ), sub_params + params + return ' '.join(result), tuple(params) finally: # Finally do cleanup - get rid of the joins we created above. diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 8bcf38f146..d1c1b2b4b0 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -2005,10 +2005,10 @@ class QuerysetOrderedTests(unittest.TestCase): class SubqueryTests(TestCase): @classmethod def setUpTestData(cls): - DumbCategory.objects.create(id=1) - DumbCategory.objects.create(id=2) - DumbCategory.objects.create(id=3) - DumbCategory.objects.create(id=4) + NamedCategory.objects.create(id=1, name='first') + NamedCategory.objects.create(id=2, name='second') + NamedCategory.objects.create(id=3, name='third') + NamedCategory.objects.create(id=4, name='fourth') def test_ordered_subselect(self): "Subselects honor any manual ordering" @@ -2064,6 +2064,28 @@ class SubqueryTests(TestCase): DumbCategory.objects.filter(id__in=DumbCategory.objects.order_by('-id')[1:]).delete() self.assertEqual(set(DumbCategory.objects.values_list('id', flat=True)), {3}) + def test_distinct_ordered_sliced_subquery(self): + # Implicit values('id'). + self.assertSequenceEqual( + NamedCategory.objects.filter( + id__in=NamedCategory.objects.distinct().order_by('name')[0:2], + ).order_by('name').values_list('name', flat=True), ['first', 'fourth'] + ) + # Explicit values('id'). + self.assertSequenceEqual( + NamedCategory.objects.filter( + id__in=NamedCategory.objects.distinct().order_by('-name').values('id')[0:2], + ).order_by('name').values_list('name', flat=True), ['second', 'third'] + ) + # Annotated value. + self.assertSequenceEqual( + DumbCategory.objects.filter( + id__in=DumbCategory.objects.annotate( + double_id=F('id') * 2 + ).order_by('id').distinct().values('double_id')[0:2], + ).order_by('id').values_list('id', flat=True), [2, 4] + ) + class CloneTests(TestCase):