From 9df71371c2d70f61ae0f8916422487bb031a7164 Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Sun, 21 Mar 2010 20:47:52 +0000 Subject: [PATCH] Fixed #12822: Don't copy the _aggregate_select_cache when cloning a query set, as that can lead to incorrect SQL being generated for the query. Thanks to mat for the report and test, tobias for the fix, and Alex for review. git-svn-id: http://code.djangoproject.com/svn/django/trunk@12830 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/sql/query.py | 10 ++-- .../aggregation_regress/tests.py | 48 +++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 tests/regressiontests/aggregation_regress/tests.py diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index ffc9d3b13fd..9819be09a1f 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -240,10 +240,12 @@ class Query(object): obj.aggregate_select_mask = None else: obj.aggregate_select_mask = self.aggregate_select_mask.copy() - if self._aggregate_select_cache is None: - obj._aggregate_select_cache = None - else: - obj._aggregate_select_cache = self._aggregate_select_cache.copy() + # _aggregate_select_cache cannot be copied, as doing so breaks the + # (necessary) state in which both aggregates and + # _aggregate_select_cache point to the same underlying objects. + # It will get re-populated in the cloned queryset the next time it's + # used. + obj._aggregate_select_cache = None obj.max_depth = self.max_depth obj.extra = self.extra.copy() if self.extra_select_mask is None: diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py new file mode 100644 index 00000000000..cf0f58a9a86 --- /dev/null +++ b/tests/regressiontests/aggregation_regress/tests.py @@ -0,0 +1,48 @@ +from django.test import TestCase +from django.db.models import Max + +from regressiontests.aggregation_regress.models import * + + +class AggregationTests(TestCase): + + def test_aggregates_in_where_clause(self): + """ + Regression test for #12822: DatabaseError: aggregates not allowed in + WHERE clause + + Tests that the subselect works and returns results equivalent to a + query with the IDs listed. + + Before the corresponding fix for this bug, this test passed in 1.1 and + failed in 1.2-beta (trunk). + """ + qs = Book.objects.values('contact').annotate(Max('id')) + qs = qs.order_by('contact').values_list('id__max', flat=True) + # don't do anything with the queryset (qs) before including it as a + # subquery + books = Book.objects.order_by('id') + qs1 = books.filter(id__in=qs) + qs2 = books.filter(id__in=list(qs)) + self.assertEqual(list(qs1), list(qs2)) + + def test_aggregates_in_where_clause_pre_eval(self): + """ + Regression test for #12822: DatabaseError: aggregates not allowed in + WHERE clause + + Same as the above test, but evaluates the queryset for the subquery + before it's used as a subquery. + + Before the corresponding fix for this bug, this test failed in both + 1.1 and 1.2-beta (trunk). + """ + qs = Book.objects.values('contact').annotate(Max('id')) + qs = qs.order_by('contact').values_list('id__max', flat=True) + # force the queryset (qs) for the subquery to be evaluated in its + # current state + list(qs) + books = Book.objects.order_by('id') + qs1 = books.filter(id__in=qs) + qs2 = books.filter(id__in=list(qs)) + self.assertEqual(list(qs1), list(qs2))