1
0
mirror of https://github.com/django/django.git synced 2025-06-08 04:59:13 +00:00

[2.2.x] Fixed #30335, #29139 -- Fixed crash when ordering or aggregating over a nested JSONField key transform.

Backport of d87bd29c4f8dfcdf3f4a4eb8340e6770a2416fe3 from master.
This commit is contained in:
can 2019-04-17 09:24:28 +03:00 committed by Mariusz Felisiak
parent ef38777ee8
commit e85317d731
3 changed files with 36 additions and 5 deletions

View File

@ -17,6 +17,7 @@ from django.db.utils import DatabaseError, NotSupportedError
from django.utils.deprecation import ( from django.utils.deprecation import (
RemovedInDjango30Warning, RemovedInDjango31Warning, RemovedInDjango30Warning, RemovedInDjango31Warning,
) )
from django.utils.hashable import make_hashable
from django.utils.inspect import func_supports_parameter from django.utils.inspect import func_supports_parameter
FORCE = object() FORCE = object()
@ -135,9 +136,10 @@ class SQLCompiler:
# wrapping () because they could be removed when a subquery is # wrapping () because they could be removed when a subquery is
# the "rhs" in an expression (see Subquery._prepare()). # the "rhs" in an expression (see Subquery._prepare()).
sql = '(%s)' % sql sql = '(%s)' % sql
if (sql, tuple(params)) not in seen: params_hash = make_hashable(params)
if (sql, params_hash) not in seen:
result.append((sql, params)) result.append((sql, params))
seen.add((sql, tuple(params))) seen.add((sql, params_hash))
return result return result
def collapse_group_by(self, expressions, having): def collapse_group_by(self, expressions, having):
@ -361,9 +363,10 @@ class SQLCompiler:
# is refactored into expressions, then we can check each part as we # is refactored into expressions, then we can check each part as we
# generate it. # generate it.
without_ordering = self.ordering_parts.search(sql).group(1) without_ordering = self.ordering_parts.search(sql).group(1)
if (without_ordering, tuple(params)) in seen: params_hash = make_hashable(params)
if (without_ordering, params_hash) in seen:
continue continue
seen.add((without_ordering, tuple(params))) seen.add((without_ordering, params_hash))
result.append((resolved, (sql, params, is_ref))) result.append((resolved, (sql, params, is_ref)))
return result return result

View File

@ -33,3 +33,8 @@ Bugfixes
* Reverted an optimization in Django 2.2 (:ticket:`29725`) that caused the * Reverted an optimization in Django 2.2 (:ticket:`29725`) that caused the
inconsistent behavior of ``count()`` and ``exists()`` on a reverse inconsistent behavior of ``count()`` and ``exists()`` on a reverse
many-to-many relationship with a custom manager (:ticket:`30325`). many-to-many relationship with a custom manager (:ticket:`30325`).
* Fixed a regression in Django 2.2 where
:class:`~django.core.paginator.Paginator` crashed when ``object_list`` was
a queryset ordered or aggregated over a nested ``JSONField`` key transform
(:ticket:`30335`).

View File

@ -1,10 +1,11 @@
import datetime import datetime
import operator
import uuid import uuid
from decimal import Decimal from decimal import Decimal
from django.core import checks, exceptions, serializers from django.core import checks, exceptions, serializers
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Q from django.db.models import Count, Q
from django.forms import CharField, Form, widgets from django.forms import CharField, Form, widgets
from django.test.utils import isolate_apps from django.test.utils import isolate_apps
from django.utils.html import escape from django.utils.html import escape
@ -15,6 +16,7 @@ from .models import JSONModel, PostgreSQLModel
try: try:
from django.contrib.postgres import forms from django.contrib.postgres import forms
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.fields.jsonb import KeyTextTransform, KeyTransform
except ImportError: except ImportError:
pass pass
@ -153,6 +155,27 @@ class TestQuerying(PostgreSQLTestCase):
query = JSONModel.objects.filter(field__name__isnull=False).order_by('field__ord') query = JSONModel.objects.filter(field__name__isnull=False).order_by('field__ord')
self.assertSequenceEqual(query, [objs[4], objs[2], objs[3], objs[1], objs[0]]) self.assertSequenceEqual(query, [objs[4], objs[2], objs[3], objs[1], objs[0]])
def test_ordering_grouping_by_key_transform(self):
base_qs = JSONModel.objects.filter(field__d__0__isnull=False)
for qs in (
base_qs.order_by('field__d__0'),
base_qs.annotate(key=KeyTransform('0', KeyTransform('d', 'field'))).order_by('key'),
):
self.assertSequenceEqual(qs, [self.objs[8]])
qs = JSONModel.objects.filter(field__isnull=False)
self.assertQuerysetEqual(
qs.values('field__d__0').annotate(count=Count('field__d__0')).order_by('count'),
[1, 10],
operator.itemgetter('count'),
)
self.assertQuerysetEqual(
qs.filter(field__isnull=False).annotate(
key=KeyTextTransform('f', KeyTransform('1', KeyTransform('d', 'field'))),
).values('key').annotate(count=Count('key')).order_by('count'),
[(None, 0), ('g', 1)],
operator.itemgetter('key', 'count'),
)
def test_deep_values(self): def test_deep_values(self):
query = JSONModel.objects.values_list('field__k__l') query = JSONModel.objects.values_list('field__k__l')
self.assertSequenceEqual( self.assertSequenceEqual(