mirror of
https://github.com/django/django.git
synced 2025-07-01 00:09:13 +00:00
Fixed #36085 -- Added JSONField support for negative array indexing on SQLite.
This commit is contained in:
parent
a8716f3c4c
commit
8620a3b0c7
@ -347,6 +347,8 @@ class BaseDatabaseFeatures:
|
|||||||
json_key_contains_list_matching_requires_list = False
|
json_key_contains_list_matching_requires_list = False
|
||||||
# Does the backend support JSONObject() database function?
|
# Does the backend support JSONObject() database function?
|
||||||
has_json_object_function = True
|
has_json_object_function = True
|
||||||
|
# Does the backend support negative JSON array indexing?
|
||||||
|
supports_json_negative_indexing = True
|
||||||
|
|
||||||
# Does the backend support column collations?
|
# Does the backend support column collations?
|
||||||
supports_collation_on_charfield = True
|
supports_collation_on_charfield = True
|
||||||
|
@ -793,6 +793,12 @@ class BaseDatabaseOperations:
|
|||||||
# Hook for backends (e.g. NoSQL) to customize formatting.
|
# Hook for backends (e.g. NoSQL) to customize formatting.
|
||||||
return sqlparse.format(sql, reindent=True, keyword_case="upper")
|
return sqlparse.format(sql, reindent=True, keyword_case="upper")
|
||||||
|
|
||||||
|
def format_json_path_numeric_index(self, num):
|
||||||
|
"""
|
||||||
|
Hook for backends to customize array indexing in JSON paths.
|
||||||
|
"""
|
||||||
|
return "[%s]" % num
|
||||||
|
|
||||||
def compile_json_path(self, key_transforms, include_root=True):
|
def compile_json_path(self, key_transforms, include_root=True):
|
||||||
"""
|
"""
|
||||||
Hook for backends to customize all aspects of JSON path construction.
|
Hook for backends to customize all aspects of JSON path construction.
|
||||||
@ -805,5 +811,13 @@ class BaseDatabaseOperations:
|
|||||||
path.append(".")
|
path.append(".")
|
||||||
path.append(json.dumps(key_transform))
|
path.append(json.dumps(key_transform))
|
||||||
else:
|
else:
|
||||||
path.append("[%s]" % num)
|
if (
|
||||||
|
num < 0
|
||||||
|
and not self.connection.features.supports_json_negative_indexing
|
||||||
|
):
|
||||||
|
raise NotSupportedError(
|
||||||
|
"Using negative JSON array indices is not supported on this "
|
||||||
|
"database backend."
|
||||||
|
)
|
||||||
|
path.append(self.format_json_path_numeric_index(num))
|
||||||
return "".join(path)
|
return "".join(path)
|
||||||
|
@ -58,6 +58,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||||||
supports_stored_generated_columns = True
|
supports_stored_generated_columns = True
|
||||||
supports_virtual_generated_columns = True
|
supports_virtual_generated_columns = True
|
||||||
|
|
||||||
|
supports_json_negative_indexing = False
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def minimum_database_version(self):
|
def minimum_database_version(self):
|
||||||
if self.connection.mysql_is_mariadb:
|
if self.connection.mysql_is_mariadb:
|
||||||
|
@ -73,6 +73,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||||||
requires_compound_order_by_subquery = True
|
requires_compound_order_by_subquery = True
|
||||||
allows_multiple_constraints_on_same_fields = False
|
allows_multiple_constraints_on_same_fields = False
|
||||||
supports_json_field_contains = False
|
supports_json_field_contains = False
|
||||||
|
supports_json_negative_indexing = False
|
||||||
supports_collation_on_textfield = False
|
supports_collation_on_textfield = False
|
||||||
test_now_utc_template = "CURRENT_TIMESTAMP AT TIME ZONE 'UTC'"
|
test_now_utc_template = "CURRENT_TIMESTAMP AT TIME ZONE 'UTC'"
|
||||||
django_test_expected_failures = {
|
django_test_expected_failures = {
|
||||||
|
@ -441,3 +441,6 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||||||
|
|
||||||
def force_group_by(self):
|
def force_group_by(self):
|
||||||
return ["GROUP BY TRUE"] if Database.sqlite_version_info < (3, 39) else []
|
return ["GROUP BY TRUE"] if Database.sqlite_version_info < (3, 39) else []
|
||||||
|
|
||||||
|
def format_json_path_numeric_index(self, num):
|
||||||
|
return "[#%s]" % num if num < 0 else super().format_json_path_numeric_index(num)
|
||||||
|
@ -208,6 +208,9 @@ Models
|
|||||||
* :meth:`.QuerySet.raw` now supports models with a
|
* :meth:`.QuerySet.raw` now supports models with a
|
||||||
:class:`~django.db.models.CompositePrimaryKey`.
|
:class:`~django.db.models.CompositePrimaryKey`.
|
||||||
|
|
||||||
|
* :class:`~django.db.models.JSONField` now supports
|
||||||
|
:ref:`negative array indexing <key-index-and-path-transforms>` on SQLite.
|
||||||
|
|
||||||
Pagination
|
Pagination
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -1092,6 +1092,8 @@ Unless you are sure you wish to work with SQL ``NULL`` values, consider setting
|
|||||||
|
|
||||||
.. fieldlookup:: jsonfield.key
|
.. fieldlookup:: jsonfield.key
|
||||||
|
|
||||||
|
.. _key-index-and-path-transforms:
|
||||||
|
|
||||||
Key, index, and path transforms
|
Key, index, and path transforms
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
@ -1134,6 +1136,22 @@ array:
|
|||||||
>>> Dog.objects.filter(data__owner__other_pets__0__name="Fishy")
|
>>> Dog.objects.filter(data__owner__other_pets__0__name="Fishy")
|
||||||
<QuerySet [<Dog: Rufus>]>
|
<QuerySet [<Dog: Rufus>]>
|
||||||
|
|
||||||
|
If the key is a negative integer, it cannot be used in a filter keyword
|
||||||
|
directly, but you can still use dictionary unpacking to use it in a query:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> Dog.objects.filter(**{"data__owner__other_pets__-1__name": "Fishy"})
|
||||||
|
<QuerySet [<Dog: Rufus>]>
|
||||||
|
|
||||||
|
.. admonition:: MySQL, MariaDB, and Oracle
|
||||||
|
|
||||||
|
Negative JSON array indices are not supported.
|
||||||
|
|
||||||
|
.. versionchanged:: 6.0
|
||||||
|
|
||||||
|
SQLite support for negative JSON array indices was added.
|
||||||
|
|
||||||
If the key you wish to query by clashes with the name of another lookup, use
|
If the key you wish to query by clashes with the name of another lookup, use
|
||||||
the :lookup:`contains <jsonfield.contains>` lookup instead.
|
the :lookup:`contains <jsonfield.contains>` lookup instead.
|
||||||
|
|
||||||
|
@ -785,6 +785,21 @@ class TestQuerying(TestCase):
|
|||||||
[self.objs[5]],
|
[self.objs[5]],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@skipIfDBFeature("supports_json_negative_indexing")
|
||||||
|
def test_unsupported_negative_lookup(self):
|
||||||
|
msg = (
|
||||||
|
"Using negative JSON array indices is not supported on this database "
|
||||||
|
"backend."
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||||
|
NullableJSONModel.objects.filter(**{"value__-2": 1}).get()
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_json_negative_indexing")
|
||||||
|
def test_shallow_list_negative_lookup(self):
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
NullableJSONModel.objects.filter(**{"value__-2": 1}), [self.objs[5]]
|
||||||
|
)
|
||||||
|
|
||||||
def test_shallow_obj_lookup(self):
|
def test_shallow_obj_lookup(self):
|
||||||
self.assertCountEqual(
|
self.assertCountEqual(
|
||||||
NullableJSONModel.objects.filter(value__a="b"),
|
NullableJSONModel.objects.filter(value__a="b"),
|
||||||
@ -817,12 +832,26 @@ class TestQuerying(TestCase):
|
|||||||
[self.objs[5]],
|
[self.objs[5]],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_json_negative_indexing")
|
||||||
|
def test_deep_negative_lookup_array(self):
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
NullableJSONModel.objects.filter(**{"value__-1__0": 2}),
|
||||||
|
[self.objs[5]],
|
||||||
|
)
|
||||||
|
|
||||||
def test_deep_lookup_mixed(self):
|
def test_deep_lookup_mixed(self):
|
||||||
self.assertSequenceEqual(
|
self.assertSequenceEqual(
|
||||||
NullableJSONModel.objects.filter(value__d__1__f="g"),
|
NullableJSONModel.objects.filter(value__d__1__f="g"),
|
||||||
[self.objs[4]],
|
[self.objs[4]],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_json_negative_indexing")
|
||||||
|
def test_deep_negative_lookup_mixed(self):
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
NullableJSONModel.objects.filter(**{"value__d__-1__f": "g"}),
|
||||||
|
[self.objs[4]],
|
||||||
|
)
|
||||||
|
|
||||||
def test_deep_lookup_transform(self):
|
def test_deep_lookup_transform(self):
|
||||||
self.assertCountEqual(
|
self.assertCountEqual(
|
||||||
NullableJSONModel.objects.filter(value__c__gt=2),
|
NullableJSONModel.objects.filter(value__c__gt=2),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user