summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorJohn Parton <john.parton.iv@gmail.com>2024-09-04 18:13:05 -0500
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-01-06 10:08:32 +0100
commit40d5516385448a73426aad396778f369a363eda9 (patch)
tree50e398547b428cbf1aa5d1a9ec6cd44d1fee7f76 /django
parentd734f1651ccc0a74325f7b55f7eecc68edef6453 (diff)
Fixed #35718 -- Add JSONArray to django.db.models.functions.
Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Diffstat (limited to 'django')
-rw-r--r--django/db/models/functions/__init__.py3
-rw-r--r--django/db/models/functions/json.py60
2 files changed, 62 insertions, 1 deletions
diff --git a/django/db/models/functions/__init__.py b/django/db/models/functions/__init__.py
index 7e5e31e0f8..2d36c6945a 100644
--- a/django/db/models/functions/__init__.py
+++ b/django/db/models/functions/__init__.py
@@ -25,7 +25,7 @@ from .datetime import (
TruncWeek,
TruncYear,
)
-from .json import JSONObject
+from .json import JSONArray, JSONObject
from .math import (
Abs,
ACos,
@@ -126,6 +126,7 @@ __all__ = [
"TruncWeek",
"TruncYear",
# json
+ "JSONArray",
"JSONObject",
# math
"Abs",
diff --git a/django/db/models/functions/json.py b/django/db/models/functions/json.py
index 25c3872419..3a4c9c81b3 100644
--- a/django/db/models/functions/json.py
+++ b/django/db/models/functions/json.py
@@ -5,6 +5,66 @@ from django.db.models.fields.json import JSONField
from django.db.models.functions import Cast
+class JSONArray(Func):
+ function = "JSON_ARRAY"
+ output_field = JSONField()
+
+ def as_sql(self, compiler, connection, **extra_context):
+ if not connection.features.supports_json_field:
+ raise NotSupportedError(
+ "JSONFields are not supported on this database backend."
+ )
+ return super().as_sql(compiler, connection, **extra_context)
+
+ def as_native(self, compiler, connection, *, returning, **extra_context):
+ # PostgreSQL 16+ and Oracle remove SQL NULL values from the array by
+ # default. Adds the NULL ON NULL clause to keep NULL values in the
+ # array, mapping them to JSON null values, which matches the behavior
+ # of SQLite.
+ null_on_null = "NULL ON NULL" if len(self.get_source_expressions()) > 0 else ""
+
+ return self.as_sql(
+ compiler,
+ connection,
+ template=(
+ f"%(function)s(%(expressions)s {null_on_null} RETURNING {returning})"
+ ),
+ **extra_context,
+ )
+
+ def as_postgresql(self, compiler, connection, **extra_context):
+ # Casting source expressions is only required using JSONB_BUILD_ARRAY
+ # or when using JSON_ARRAY on PostgreSQL 16+ with server-side bindings.
+ # This is done in all cases for consistency.
+ casted_obj = self.copy()
+ casted_obj.set_source_expressions(
+ [
+ (
+ # Conditional Cast to avoid unnecessary wrapping.
+ expression
+ if isinstance(expression, Cast)
+ else Cast(expression, expression.output_field)
+ )
+ for expression in casted_obj.get_source_expressions()
+ ]
+ )
+
+ if connection.features.is_postgresql_16:
+ return casted_obj.as_native(
+ compiler, connection, returning="JSONB", **extra_context
+ )
+
+ return casted_obj.as_sql(
+ compiler,
+ connection,
+ function="JSONB_BUILD_ARRAY",
+ **extra_context,
+ )
+
+ def as_oracle(self, compiler, connection, **extra_context):
+ return self.as_native(compiler, connection, returning="CLOB", **extra_context)
+
+
class JSONObject(Func):
function = "JSON_OBJECT"
output_field = JSONField()