Skip to content

Commit c2fbcad

Browse files
committed
Add basic support for JSONField
Initial support for Json, does not have lookups like `contains` and `has` yet.
1 parent 33b4e23 commit c2fbcad

File tree

7 files changed

+63
-9
lines changed

7 files changed

+63
-9
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Thumbs.db
77

88
*.egg-info
9-
9+
*.dll
1010
tests/local_settings.py
1111

1212
# Virtual Env

mssql/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
8888
'IntegerField': 'int',
8989
'IPAddressField': 'nvarchar(15)',
9090
'GenericIPAddressField': 'nvarchar(39)',
91+
'JSONField': 'nvarchar(max)',
9192
'NullBooleanField': 'bit',
9293
'OneToOneField': 'int',
9394
'PositiveIntegerField': 'int',

mssql/compiler.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from django.db.models.sql import compiler
1414
from django.db.transaction import TransactionManagementError
1515
from django.db.utils import NotSupportedError
16-
16+
from django.db.models.fields.json import compile_json_path, KeyTransform as json_KeyTransform
1717

1818
def _as_sql_agv(self, compiler, connection):
1919
return self.as_sql(compiler, connection, template='%(function)s(CONVERT(float, %(field)s))')
@@ -42,6 +42,11 @@ def _as_sql_greatest(self, compiler, connection):
4242
template = '(SELECT MAX(value) FROM (VALUES (%(expressions)s)) AS _%(function)s(value))'
4343
return self.as_sql(compiler, connection, arg_joiner='), (', template=template)
4444

45+
def _as_sql_json_keytransform(self, compiler, connection):
46+
lhs, params, key_transforms = self.preprocess_lhs(compiler, connection)
47+
json_path = compile_json_path(key_transforms)
48+
49+
return 'JSON_VALUE(%s, %%s)' % lhs, tuple(params) + (json_path,)
4550

4651
def _as_sql_least(self, compiler, connection):
4752
# SQL Server does not provide LEAST function,
@@ -382,6 +387,8 @@ def _as_microsoft(self, node):
382387
as_microsoft = _as_sql_count
383388
elif isinstance(node, Greatest):
384389
as_microsoft = _as_sql_greatest
390+
if isinstance(node, json_KeyTransform):
391+
as_microsoft = _as_sql_json_keytransform
385392
elif isinstance(node, Least):
386393
as_microsoft = _as_sql_least
387394
elif isinstance(node, Length):

mssql/features.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,35 @@
66

77

88
class DatabaseFeatures(BaseDatabaseFeatures):
9-
has_native_uuid_field = False
109
allow_sliced_subqueries_with_in = False
1110
can_introspect_autofield = True
11+
can_introspect_json_field = False
1212
can_introspect_small_integer_field = True
1313
can_return_columns_from_insert = True
1414
can_return_id_from_insert = True
1515
can_use_chunked_reads = False
1616
for_update_after_from = True
1717
greatest_least_ignores_nulls = True
18+
has_json_operators = False
19+
has_native_json_field = False
20+
has_native_uuid_field = False
1821
has_real_datatype = True
1922
has_select_for_update = True
2023
has_select_for_update_nowait = True
2124
has_select_for_update_skip_locked = True
22-
ignores_table_name_case = True
2325
ignores_quoted_identifier_case = True
26+
ignores_table_name_case = True
2427
order_by_nulls_first = True
2528
requires_literal_defaults = True
2629
requires_sqlparse_for_splitting = False
2730
supports_boolean_expr_in_select_clause = False
2831
supports_deferrable_unique_constraints = False
2932
supports_ignore_conflicts = False
3033
supports_index_on_text_field = False
31-
supports_json_field = False
34+
supports_json_field_contains = False
3235
supports_order_by_nulls_modifier = False
3336
supports_paramstyle_pyformat = False
37+
supports_primitives_in_json_field = False
3438
supports_regex_backreferencing = True
3539
supports_sequence_reset = False
3640
supports_subqueries_in_group_by = False
@@ -65,3 +69,7 @@ def has_zoneinfo_database(self):
6569
with self.connection.cursor() as cursor:
6670
cursor.execute("SELECT TOP 1 1 FROM sys.time_zone_info")
6771
return cursor.fetchone() is not None
72+
73+
@cached_property
74+
def supports_json_field(self):
75+
return self.connection.sql_server_version >= 2016

mssql/functions.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from django.db.models.functions import Cast
77
from django.db.models.functions.math import ATan2, Log, Ln, Mod, Round
88
from django.db.models.expressions import Case, Exists, OrderBy, When
9-
from django.db.models.lookups import Lookup, In
9+
from django.db.models.lookups import Lookup, In, Exact
10+
from django.db.models.fields.json import KeyTransform, KeyTransformExact
1011

1112
DJANGO3 = VERSION[0] >= 3
1213

@@ -108,12 +109,22 @@ def split_parameter_list_as_sql(self, compiler, connection):
108109

109110
return in_clause, ()
110111

112+
def KeyTransformExact_process_rhs(self, compiler, connection):
113+
if isinstance(self.rhs, KeyTransform):
114+
return super(Exact, self).process_rhs(compiler, connection)
115+
rhs, rhs_params = super(Exact, self).process_rhs(compiler, connection)
116+
if connection.vendor == 'microsoft':
117+
if rhs_params != [None]:
118+
rhs_params = [params.strip('"') for params in rhs_params]
119+
return rhs, rhs_params
120+
111121
ATan2.as_microsoft = sqlserver_atan2
112-
Log.as_microsoft = sqlserver_log
122+
In.split_parameter_list_as_sql = split_parameter_list_as_sql
123+
KeyTransformExact.process_rhs = KeyTransformExact_process_rhs
113124
Ln.as_microsoft = sqlserver_ln
125+
Log.as_microsoft = sqlserver_log
114126
Mod.as_microsoft = sqlserver_mod
115127
Round.as_microsoft = sqlserver_round
116-
In.split_parameter_list_as_sql = split_parameter_list_as_sql
117128

118129
if DJANGO3:
119130
Lookup.as_microsoft = sqlserver_lookup

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
install_requires=[
3737
'pyodbc>=3.0',
3838
],
39+
package_data={'mssql': ['regex_clr.dll']},
3940
classifiers=CLASSIFIERS,
4041
keywords='django',
4142
)

testapp/settings.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,33 @@
186186
'queries.test_db_returning.ReturningValuesTests.test_insert_returning_multiple',
187187
'dbshell.tests.DbshellCommandTestCase.test_command_missing',
188188
'schema.tests.SchemaTests.test_char_field_pk_to_auto_field',
189-
'datetimes.tests.DateTimesTests.test_21432'
189+
'datetimes.tests.DateTimesTests.test_21432',
190+
191+
#JSONFields
192+
'model_fields.test_jsonfield.TestQuerying.test_has_any_keys',
193+
'model_fields.test_jsonfield.TestQuerying.test_has_key',
194+
'model_fields.test_jsonfield.TestQuerying.test_has_key_deep',
195+
'model_fields.test_jsonfield.TestQuerying.test_has_key_list',
196+
'model_fields.test_jsonfield.TestQuerying.test_has_key_null_value',
197+
'model_fields.test_jsonfield.TestQuerying.test_has_keys',
198+
'model_fields.test_jsonfield.TestQuerying.test_key_iregex',
199+
'model_fields.test_jsonfield.TestQuerying.test_key_quoted_string',
200+
'model_fields.test_jsonfield.TestQuerying.test_key_regex',
201+
'model_fields.test_jsonfield.TestQuerying.test_lookups_with_key_transform',
202+
'model_fields.test_jsonfield.TestQuerying.test_order_grouping_custom_decoder',
203+
'model_fields.test_jsonfield.TestQuerying.test_ordering_grouping_by_count',
204+
'model_fields.test_jsonfield.TestQuerying.test_ordering_grouping_by_key_transform',
205+
'model_fields.test_jsonfield.JSONFieldTests.test_db_check_constraints',
206+
'model_fields.test_jsonfield.TestQuerying.test_isnull_key',
207+
'model_fields.test_jsonfield.TestQuerying.test_key_in',
208+
'model_fields.test_jsonfield.TestQuerying.test_key_transform_expression',
209+
'model_fields.test_jsonfield.TestQuerying.test_key_values',
210+
'model_fields.test_jsonfield.TestQuerying.test_nested_key_transform_expression',
211+
'model_fields.test_jsonfield.TestQuerying.test_none_key',
212+
'model_fields.test_jsonfield.TestQuerying.test_none_key_and_exact_lookup',
213+
'model_fields.test_jsonfield.TestQuerying.test_none_key_exclude',
214+
'model_fields.test_jsonfield.TestQuerying.test_ordering_by_transform',
215+
'model_fields.test_jsonfield.TestQuerying.test_shallow_lookup_obj_target'
190216
]
191217

192218
REGEX_TESTS = ['lookup.tests.LookupTests.test_regex',

0 commit comments

Comments
 (0)