summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSage Abdullah <me@laymonage.com>2024-12-15 16:01:45 +0000
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2024-12-16 12:06:01 +0100
commitd7d711c68cc070d9b6962f43f97ece097162adcc (patch)
treec62e55926c88fc999cba8666d7595e054a1a4313
parentd36ad43f612295e2740fa26b7307d4041044ae3e (diff)
Refs #35718, Refs #32179 -- Moved JSONObject to django.db.models.functions.json.
-rw-r--r--django/db/models/functions/__init__.py6
-rw-r--r--django/db/models/functions/comparison.py62
-rw-r--r--django/db/models/functions/json.py64
-rw-r--r--docs/ref/models/database-functions.txt55
-rw-r--r--tests/db_functions/json/__init__.py0
-rw-r--r--tests/db_functions/json/test_json_object.py (renamed from tests/db_functions/comparison/test_json_object.py)0
6 files changed, 98 insertions, 89 deletions
diff --git a/django/db/models/functions/__init__.py b/django/db/models/functions/__init__.py
index cd7c801894..7e5e31e0f8 100644
--- a/django/db/models/functions/__init__.py
+++ b/django/db/models/functions/__init__.py
@@ -1,4 +1,4 @@
-from .comparison import Cast, Coalesce, Collate, Greatest, JSONObject, Least, NullIf
+from .comparison import Cast, Coalesce, Collate, Greatest, Least, NullIf
from .datetime import (
Extract,
ExtractDay,
@@ -25,6 +25,7 @@ from .datetime import (
TruncWeek,
TruncYear,
)
+from .json import JSONObject
from .math import (
Abs,
ACos,
@@ -97,7 +98,6 @@ __all__ = [
"Coalesce",
"Collate",
"Greatest",
- "JSONObject",
"Least",
"NullIf",
# datetime
@@ -125,6 +125,8 @@ __all__ = [
"TruncTime",
"TruncWeek",
"TruncYear",
+ # json
+ "JSONObject",
# math
"Abs",
"ACos",
diff --git a/django/db/models/functions/comparison.py b/django/db/models/functions/comparison.py
index 76ef5d219b..11af0c0750 100644
--- a/django/db/models/functions/comparison.py
+++ b/django/db/models/functions/comparison.py
@@ -1,9 +1,6 @@
"""Database functions that do comparisons or type conversions."""
-from django.db import NotSupportedError
from django.db.models.expressions import Func, Value
-from django.db.models.fields import TextField
-from django.db.models.fields.json import JSONField
from django.utils.regex_helper import _lazy_re_compile
@@ -143,65 +140,6 @@ class Greatest(Func):
return super().as_sqlite(compiler, connection, function="MAX", **extra_context)
-class JSONObject(Func):
- function = "JSON_OBJECT"
- output_field = JSONField()
-
- def __init__(self, **fields):
- expressions = []
- for key, value in fields.items():
- expressions.extend((Value(key), value))
- super().__init__(*expressions)
-
- def as_sql(self, compiler, connection, **extra_context):
- if not connection.features.has_json_object_function:
- raise NotSupportedError(
- "JSONObject() is not supported on this database backend."
- )
- return super().as_sql(compiler, connection, **extra_context)
-
- def join(self, args):
- pairs = zip(args[::2], args[1::2], strict=True)
- # Wrap 'key' in parentheses in case of postgres cast :: syntax.
- return ", ".join([f"({key}) VALUE {value}" for key, value in pairs])
-
- def as_native(self, compiler, connection, *, returning, **extra_context):
- return self.as_sql(
- compiler,
- connection,
- arg_joiner=self,
- template=f"%(function)s(%(expressions)s RETURNING {returning})",
- **extra_context,
- )
-
- def as_postgresql(self, compiler, connection, **extra_context):
- # Casting keys to text is only required when using JSONB_BUILD_OBJECT
- # or when using JSON_OBJECT on PostgreSQL 16+ with server-side bindings.
- # This is done in all cases for consistency.
- copy = self.copy()
- copy.set_source_expressions(
- [
- Cast(expression, TextField()) if index % 2 == 0 else expression
- for index, expression in enumerate(copy.get_source_expressions())
- ]
- )
-
- if connection.features.is_postgresql_16:
- return copy.as_native(
- compiler, connection, returning="JSONB", **extra_context
- )
-
- return super(JSONObject, copy).as_sql(
- compiler,
- connection,
- function="JSONB_BUILD_OBJECT",
- **extra_context,
- )
-
- def as_oracle(self, compiler, connection, **extra_context):
- return self.as_native(compiler, connection, returning="CLOB", **extra_context)
-
-
class Least(Func):
"""
Return the minimum expression.
diff --git a/django/db/models/functions/json.py b/django/db/models/functions/json.py
new file mode 100644
index 0000000000..25c3872419
--- /dev/null
+++ b/django/db/models/functions/json.py
@@ -0,0 +1,64 @@
+from django.db import NotSupportedError
+from django.db.models.expressions import Func, Value
+from django.db.models.fields import TextField
+from django.db.models.fields.json import JSONField
+from django.db.models.functions import Cast
+
+
+class JSONObject(Func):
+ function = "JSON_OBJECT"
+ output_field = JSONField()
+
+ def __init__(self, **fields):
+ expressions = []
+ for key, value in fields.items():
+ expressions.extend((Value(key), value))
+ super().__init__(*expressions)
+
+ def as_sql(self, compiler, connection, **extra_context):
+ if not connection.features.has_json_object_function:
+ raise NotSupportedError(
+ "JSONObject() is not supported on this database backend."
+ )
+ return super().as_sql(compiler, connection, **extra_context)
+
+ def join(self, args):
+ pairs = zip(args[::2], args[1::2], strict=True)
+ # Wrap 'key' in parentheses in case of postgres cast :: syntax.
+ return ", ".join([f"({key}) VALUE {value}" for key, value in pairs])
+
+ def as_native(self, compiler, connection, *, returning, **extra_context):
+ return self.as_sql(
+ compiler,
+ connection,
+ arg_joiner=self,
+ template=f"%(function)s(%(expressions)s RETURNING {returning})",
+ **extra_context,
+ )
+
+ def as_postgresql(self, compiler, connection, **extra_context):
+ # Casting keys to text is only required when using JSONB_BUILD_OBJECT
+ # or when using JSON_OBJECT on PostgreSQL 16+ with server-side bindings.
+ # This is done in all cases for consistency.
+ copy = self.copy()
+ copy.set_source_expressions(
+ [
+ Cast(expression, TextField()) if index % 2 == 0 else expression
+ for index, expression in enumerate(copy.get_source_expressions())
+ ]
+ )
+
+ if connection.features.is_postgresql_16:
+ return copy.as_native(
+ compiler, connection, returning="JSONB", **extra_context
+ )
+
+ return super(JSONObject, copy).as_sql(
+ compiler,
+ connection,
+ function="JSONB_BUILD_OBJECT",
+ **extra_context,
+ )
+
+ def as_oracle(self, compiler, connection, **extra_context):
+ return self.as_native(compiler, connection, returning="CLOB", **extra_context)
diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt
index eb08e160f7..f23585739e 100644
--- a/docs/ref/models/database-functions.txt
+++ b/docs/ref/models/database-functions.txt
@@ -163,31 +163,6 @@ and ``comment.modified``.
The PostgreSQL behavior can be emulated using ``Coalesce`` if you know
a sensible minimum value to provide as a default.
-``JSONObject``
---------------
-
-.. class:: JSONObject(**fields)
-
-Takes a list of key-value pairs and returns a JSON object containing those
-pairs.
-
-Usage example:
-
-.. code-block:: pycon
-
- >>> from django.db.models import F
- >>> from django.db.models.functions import JSONObject, Lower
- >>> Author.objects.create(name="Margaret Smith", alias="msmith", age=25)
- >>> author = Author.objects.annotate(
- ... json_object=JSONObject(
- ... name=Lower("name"),
- ... alias="alias",
- ... age=F("age") * 2,
- ... )
- ... ).get()
- >>> author.json_object
- {'name': 'margaret smith', 'alias': 'msmith', 'age': 50}
-
``Least``
---------
@@ -861,6 +836,36 @@ that deal with time-parts can be used with ``TimeField``:
2014-06-16 00:00:00+10:00 2
2016-01-01 04:00:00+11:00 1
+.. _json-functions:
+
+JSON Functions
+==============
+
+``JSONObject``
+--------------
+
+.. class:: JSONObject(**fields)
+
+Takes a list of key-value pairs and returns a JSON object containing those
+pairs.
+
+Usage example:
+
+.. code-block:: pycon
+
+ >>> from django.db.models import F
+ >>> from django.db.models.functions import JSONObject, Lower
+ >>> Author.objects.create(name="Margaret Smith", alias="msmith", age=25)
+ >>> author = Author.objects.annotate(
+ ... json_object=JSONObject(
+ ... name=Lower("name"),
+ ... alias="alias",
+ ... age=F("age") * 2,
+ ... )
+ ... ).get()
+ >>> author.json_object
+ {'name': 'margaret smith', 'alias': 'msmith', 'age': 50}
+
.. _math-functions:
Math Functions
diff --git a/tests/db_functions/json/__init__.py b/tests/db_functions/json/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/db_functions/json/__init__.py
diff --git a/tests/db_functions/comparison/test_json_object.py b/tests/db_functions/json/test_json_object.py
index 9a3d48288c..9a3d48288c 100644
--- a/tests/db_functions/comparison/test_json_object.py
+++ b/tests/db_functions/json/test_json_object.py