Skip to content

Commit d5e56af

Browse files
authored
FEAT: Support 5.2- Added JSONARRAY Feature Support (#460)
* added jsonarray support * added version check
1 parent 07bee47 commit d5e56af

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

azure-pipelines.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ jobs:
176176
Python3.10 - Django 5.2:
177177
python.version: '3.10'
178178
tox.env: 'py310-django52'
179-
179+
180180
Python3.13 - Django 5.1:
181181
python.version: '3.13'
182182
tox.env: 'py313-django51'

mssql/functions.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
from django.db.models.lookups import In, Lookup
1717
from django.db.models.query import QuerySet
1818
from django.db.models.sql.query import Query
19+
# import value and JSONArray for Django 5.2+
20+
if VERSION >= (5, 2):
21+
from django.db.models import Value
22+
from django.db.models.functions import JSONArray
1923

2024
if VERSION >= (3, 1):
2125
from django.db.models.fields.json import (
@@ -225,6 +229,70 @@ def unquote_json_rhs(rhs_params):
225229
rhs_params = [param.replace('"', '') for param in rhs_params]
226230
return rhs_params
227231

232+
def sqlserver_json_array(self, compiler, connection, **extra_context):
233+
"""
234+
SQL Server implementation of JSONArray.
235+
"""
236+
elements = [] # List to hold SQL fragments for each array element
237+
params = [] # List to hold parameters for the SQL query
238+
239+
# Iterate through each source expression (element of the array)
240+
for arg in self.source_expressions:
241+
# Check if the argument is a Value instance
242+
if isinstance(arg, Value):
243+
# If it's a Value, we need to handle it based on its type
244+
val = arg.value
245+
# If the value is None, we represent it as SQL NULL
246+
if val is None:
247+
elements.append('NULL')
248+
elif isinstance(val, (int, float)):
249+
# Numbers are inserted as it is, without quotes
250+
elements.append('%s')
251+
params.append(str(val))
252+
elif isinstance(val, (list, dict)):
253+
# Nested JSON structures are handled with JSON_QUERY
254+
elements.append('JSON_QUERY(%s)')
255+
params.append(json.dumps(val))
256+
else:
257+
# Strings and other types are cast to NVARCHAR(MAX)
258+
elements.append('CAST(%s AS NVARCHAR(MAX))')
259+
params.append(str(val))
260+
else:
261+
# Compile non-Value expressions (e.g., fields, functions)
262+
arg_sql, arg_params = compiler.compile(arg)
263+
if isinstance(arg, JSONArray):
264+
# Nested JSONArray: use its SQL directly
265+
elements.append(arg_sql)
266+
else:
267+
# Other expressions: cast to NVARCHAR(MAX)
268+
elements.append(f'CAST({arg_sql} AS NVARCHAR(MAX))')
269+
if arg_params:
270+
params.extend(arg_params)
271+
# If there are no elements, return an empty JSON array
272+
if not elements:
273+
return "JSON_QUERY('[]')", []
274+
275+
# Build the SQL for the JSON array using STRING_AGG and CASE for formatting
276+
sql = (
277+
"JSON_QUERY(("
278+
"SELECT '[' + "
279+
"STRING_AGG("
280+
"CASE "
281+
"WHEN value IS NULL THEN 'null' " # NULLs as JSON null
282+
"WHEN ISJSON(value) = 1 THEN value " # Valid JSON: insert as-is
283+
"WHEN ISNUMERIC(value) = 1 THEN CAST(value AS NVARCHAR(MAX)) " # Numbers: insert as-is
284+
"ELSE CONCAT('\"', REPLACE(REPLACE(value, '\\', '\\\\'), '\"', '\\\"'), '\"') " # Strings: escape and quote
285+
"END, "
286+
"','"
287+
") + ']' "
288+
f"FROM (VALUES {','.join('(' + el + ')' for el in elements)}) AS t(value)))"
289+
)
290+
291+
return sql, params
292+
293+
# Register for Django 5.2+ so that JSONArray uses this implementation on SQL Server
294+
if VERSION >= (5, 2):
295+
JSONArray.as_microsoft = sqlserver_json_array
228296

229297
def json_KeyTransformExact_process_rhs(self, compiler, connection):
230298
rhs, rhs_params = key_transform_exact_process_rhs(self, compiler, connection)

0 commit comments

Comments
 (0)