diff options
| author | John Parton <john.parton.iv@gmail.com> | 2024-09-04 18:13:05 -0500 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2025-01-06 10:08:32 +0100 |
| commit | 40d5516385448a73426aad396778f369a363eda9 (patch) | |
| tree | 50e398547b428cbf1aa5d1a9ec6cd44d1fee7f76 /django | |
| parent | d734f1651ccc0a74325f7b55f7eecc68edef6453 (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__.py | 3 | ||||
| -rw-r--r-- | django/db/models/functions/json.py | 60 |
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() |
