summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCaio Ariede <caio.ariede@gmail.com>2019-05-25 10:19:32 -0300
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-05-28 10:05:50 +0200
commita3f91891d2c7f4bdc33f48ae70820ef6f36da26e (patch)
treeb663c5f1240d0223079cc87113b3f41734c10dbf
parentb2790f74d4f38c8b297b7c1cef6875d2378f6fa6 (diff)
Fixed #30315 -- Fixed crash of ArrayAgg and StringAgg with ordering when used in Subquery.
-rw-r--r--django/contrib/postgres/aggregates/mixins.py6
-rw-r--r--docs/releases/2.2.2.txt4
-rw-r--r--tests/postgres_tests/test_aggregates.py35
3 files changed, 43 insertions, 2 deletions
diff --git a/django/contrib/postgres/aggregates/mixins.py b/django/contrib/postgres/aggregates/mixins.py
index 6c1ce53b75..4625738beb 100644
--- a/django/contrib/postgres/aggregates/mixins.py
+++ b/django/contrib/postgres/aggregates/mixins.py
@@ -33,6 +33,12 @@ class OrderableAggMixin:
return sql, sql_params + ordering_params
return super().as_sql(compiler, connection, ordering='')
+ def set_source_expressions(self, exprs):
+ # Extract the ordering expressions because ORDER BY clause is handled
+ # in a custom way.
+ self.ordering = exprs[self._get_ordering_expressions_index():]
+ return super().set_source_expressions(exprs[:self._get_ordering_expressions_index()])
+
def get_source_expressions(self):
return self.source_expressions + self.ordering
diff --git a/docs/releases/2.2.2.txt b/docs/releases/2.2.2.txt
index fc47100844..08af175caa 100644
--- a/docs/releases/2.2.2.txt
+++ b/docs/releases/2.2.2.txt
@@ -21,3 +21,7 @@ Bugfixes
* Fixed a regression in Django 2.2 where auto-reloader doesn't detect changes
in ``manage.py`` file when using ``StatReloader`` (:ticket:`30479`).
+
+* Fixed crash of :class:`~django.contrib.postgres.aggregates.ArrayAgg` and
+ :class:`~django.contrib.postgres.aggregates.StringAgg` with ``ordering``
+ argument when used in a ``Subquery`` (:ticket:`30315`).
diff --git a/tests/postgres_tests/test_aggregates.py b/tests/postgres_tests/test_aggregates.py
index 23f3c59ded..9bd5b70a9e 100644
--- a/tests/postgres_tests/test_aggregates.py
+++ b/tests/postgres_tests/test_aggregates.py
@@ -1,7 +1,8 @@
import json
-from django.db.models.expressions import F, Value
-from django.db.models.functions import Concat, Substr
+from django.db.models import CharField
+from django.db.models.expressions import F, OuterRef, Subquery, Value
+from django.db.models.functions import Cast, Concat, Substr
from django.test.utils import Approximate
from . import PostgreSQLTestCase
@@ -202,6 +203,36 @@ class TestGeneralAggregate(PostgreSQLTestCase):
values = AggregateTestModel.objects.none().aggregate(jsonagg=JSONBAgg('integer_field'))
self.assertEqual(values, json.loads('{"jsonagg": []}'))
+ def test_string_agg_array_agg_ordering_in_subquery(self):
+ stats = []
+ for i, agg in enumerate(AggregateTestModel.objects.order_by('char_field')):
+ stats.append(StatTestModel(related_field=agg, int1=i, int2=i + 1))
+ stats.append(StatTestModel(related_field=agg, int1=i + 1, int2=i))
+ StatTestModel.objects.bulk_create(stats)
+
+ for aggregate, expected_result in (
+ (
+ ArrayAgg('stattestmodel__int1', ordering='-stattestmodel__int2'),
+ [('Foo1', [0, 1]), ('Foo2', [1, 2]), ('Foo3', [2, 3]), ('Foo4', [3, 4])],
+ ),
+ (
+ StringAgg(
+ Cast('stattestmodel__int1', CharField()),
+ delimiter=';',
+ ordering='-stattestmodel__int2',
+ ),
+ [('Foo1', '0;1'), ('Foo2', '1;2'), ('Foo3', '2;3'), ('Foo4', '3;4')],
+ ),
+ ):
+ with self.subTest(aggregate=aggregate.__class__.__name__):
+ subquery = AggregateTestModel.objects.filter(
+ pk=OuterRef('pk'),
+ ).annotate(agg=aggregate).values('agg')
+ values = AggregateTestModel.objects.annotate(
+ agg=Subquery(subquery),
+ ).order_by('char_field').values_list('char_field', 'agg')
+ self.assertEqual(list(values), expected_result)
+
class TestAggregateDistinct(PostgreSQLTestCase):
@classmethod