Skip to content

Commit 566188a

Browse files
authored
Feat JSONField (#21)
#17 JSON field support
1 parent bca7a05 commit 566188a

File tree

25 files changed

+279
-41
lines changed

25 files changed

+279
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Fix [bug when save django model instance](https://github.com/jayvynl/django-clickhouse-backend/issues/9).
66
- Support [clickhouse-driver 0.2.6](https://github.com/mymarilyn/clickhouse-driver), drop support for python3.6.
77
- Support [Django 4.2](https://docs.djangoproject.com).
8+
- Support [clickhouse JSON type](https://clickhouse.com/docs/en/sql-reference/data-types/json).
89

910
### 1.0.2 (2023-02-28)
1011
- Fix test db name when NAME not provided in DATABASES setting.

clickhouse_backend/backend/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
2828
"BigAutoField": "Int64",
2929
"IPAddressField": "IPv4",
3030
"GenericIPAddressField": "IPv6",
31+
"JSONField": "JSON",
3132
"BinaryField": "String",
3233
"CharField": "FixedString(%(max_length)s)",
3334
"DateField": "Date32",

clickhouse_backend/backend/features.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,23 @@ def uses_savepoints(self):
100100
supports_partial_indexes = False
101101

102102
# Does the backend support JSONField?
103-
supports_json_field = False
103+
supports_json_field = True
104+
# Can the backend introspect a JSONField?
105+
can_introspect_json_field = True
106+
# Does the backend support primitives in JSONField?
107+
supports_primitives_in_json_field = False
108+
# Is there a true datatype for JSON?
109+
has_native_json_field = True
110+
# Does the backend use PostgreSQL-style JSON operators like '->'?
111+
has_json_operators = False
112+
# Does the backend support __contains and __contained_by lookups for
113+
# a JSONField?
114+
supports_json_field_contains = False
115+
# Does value__d__contains={'f': 'g'} (without a list around the dict) match
116+
# {'d': [{'f': 'g'}]}?
117+
json_key_contains_list_matching_requires_list = False
118+
# Does the backend support JSONObject() database function?
119+
has_json_object_function = False
104120

105121
# Does the backend support column collations?
106122
supports_collation_on_charfield = False

clickhouse_backend/backend/introspection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ def get_field_type(self, data_type, description):
3939
return "TupleField"
4040
elif data_type.startswith("Map"):
4141
return "MapField"
42+
elif data_type == "Object('json')":
43+
return "JSONField"
4244

4345
return f"{data_type}Field" # Int8
4446

clickhouse_backend/backend/operations.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from django.conf import settings
21
from django.db.backends.base.operations import BaseDatabaseOperations
32

43
from clickhouse_backend import compat
5-
from clickhouse_backend.utils import get_timezone
4+
from clickhouse_backend.driver import JSON
65
from clickhouse_backend.driver.client import insert_pattern
6+
from clickhouse_backend.utils import get_timezone
77

88

99
class DatabaseOperations(BaseDatabaseOperations):
@@ -294,6 +294,9 @@ def adapt_datetimefield_value(self, value):
294294
def adapt_decimalfield_value(self, value, max_digits=None, decimal_places=None):
295295
return value
296296

297+
def adapt_json_value(self, value, encoder):
298+
return JSON(value)
299+
297300
def explain_query_prefix(self, format=None, **options):
298301
# bypass normal explain prefix insert in compiler.as_sql
299302
return ""

clickhouse_backend/driver/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from .connection import Connection
1111
# Binary is compatible for django's BinaryField.
12-
from .types import Binary # NOQA
12+
from .types import Binary, JSON # NOQA
1313

1414

1515
def connect(dsn=None, host=None,

clickhouse_backend/driver/escape.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def escape_param(item, context, for_server=False):
6161
return "[%s]" % ', '.join(str(escape_param(x, context, for_server=for_server)) for x in item)
6262

6363
elif isinstance(item, tuple):
64-
return "(%s)" % ', '.join(str(escape_param(x, context, for_server=for_server)) for x in item)
64+
return "tuple(%s)" % ', '.join(str(escape_param(x, context, for_server=for_server)) for x in item)
6565

6666
elif isinstance(item, Enum):
6767
return escape_param(item.value, context, for_server=for_server)
@@ -72,6 +72,15 @@ def escape_param(item, context, for_server=False):
7272
elif isinstance(item, types.Binary):
7373
return escape_binary(item, context)
7474

75+
elif isinstance(item, types.JSON):
76+
value = item.value
77+
if isinstance(value, list):
78+
return escape_param([types.JSON(v) for v in value], context, for_server=for_server)
79+
elif isinstance(value, dict):
80+
return escape_param(tuple(types.JSON(v) for v in value.values()), context, for_server=for_server)
81+
else:
82+
return escape_param(value, context, for_server=for_server)
83+
7584
else:
7685
return item
7786

clickhouse_backend/driver/types.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
Binary = bytes
2+
3+
4+
class JSON:
5+
def __init__(self, value):
6+
self.value = value

clickhouse_backend/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from clickhouse_backend.patch import patch_all
12
from .base import ClickhouseModel
23
from .engines import *
34
from .engines import __all__ as engines_all # NOQA
@@ -15,3 +16,4 @@
1516
*fucntions_all,
1617
*indexes_all,
1718
]
19+
patch_all()

clickhouse_backend/models/base.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from django.db import models
22
from django.db.migrations import state
3-
from django.db.models import functions
43
from django.db.models import options
5-
from django.db.models.manager import BaseManager
64

75
from .query import QuerySet
86
from .sql import Query
@@ -16,15 +14,6 @@
1614
state.DEFAULT_NAMES = options.DEFAULT_NAMES
1715

1816

19-
def as_clickhouse(self, compiler, connection, **extra_context):
20-
return functions.Random.as_sql(
21-
self, compiler, connection, function="rand64", **extra_context
22-
)
23-
24-
25-
functions.Random.as_clickhouse = as_clickhouse
26-
27-
2817
class ClickhouseManager(models.Manager):
2918
_queryset_class = QuerySet
3019

0 commit comments

Comments
 (0)