Skip to content

Commit

Permalink
feat: Add JSON type (#317)
Browse files Browse the repository at this point in the history
* feat: Add JSON type, fixed #199

* feat: Add json type test

* fix: flake8
  • Loading branch information
akkuman authored Jul 10, 2024
1 parent 4e2fc97 commit e0f0f6e
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 0 deletions.
11 changes: 11 additions & 0 deletions clickhouse_sqlalchemy/drivers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
'FixedString': types.String,
'Enum8': types.Enum8,
'Enum16': types.Enum16,
'Object(\'json\')': types.JSON,
'_array': types.Array,
'_nullable': types.Nullable,
'_lowcardinality': types.LowCardinality,
Expand Down Expand Up @@ -135,6 +136,16 @@ class ClickHouseDialect(default.DefaultDialect):

inspector = ClickHouseInspector

def __init__(
self,
json_serializer=None,
json_deserializer=None,
**kwargs,
):
default.DefaultDialect.__init__(self, **kwargs)
self._json_deserializer = json_deserializer
self._json_serializer = json_serializer

def initialize(self, connection):
super(ClickHouseDialect, self).initialize(connection)

Expand Down
3 changes: 3 additions & 0 deletions clickhouse_sqlalchemy/drivers/compilers/typecompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ def visit_numeric(self, type_, **kw):
def visit_boolean(self, type_, **kw):
return 'Bool'

def visit_json(self, type_, **kw):
return 'JSON'

def visit_nested(self, nested, **kwargs):
ddl_compiler = self.dialect.ddl_compiler(self.dialect, None)
cols_create = [
Expand Down
2 changes: 2 additions & 0 deletions clickhouse_sqlalchemy/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
'Decimal',
'IPv4',
'IPv6',
'JSON',
'Nested',
'Tuple',
'Map',
Expand Down Expand Up @@ -68,6 +69,7 @@
from .common import Enum8
from .common import Enum16
from .common import Decimal
from .common import JSON
from .common import Tuple
from .common import Map
from .common import AggregateFunction
Expand Down
4 changes: 4 additions & 0 deletions clickhouse_sqlalchemy/types/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class Boolean(types.Boolean, ClickHouseTypeEngine):
pass


class JSON(types.JSON, ClickHouseTypeEngine):
__visit_name__ = 'json'


class Array(ClickHouseTypeEngine):
__visit_name__ = 'array'

Expand Down
59 changes: 59 additions & 0 deletions tests/types/test_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import json
from sqlalchemy import Column, text, inspect, func
from sqlalchemy.sql.ddl import CreateTable

from clickhouse_sqlalchemy import types, engines, Table
from tests.testcase import BaseTestCase, CompilationTestCase
from tests.util import class_name_func
from parameterized import parameterized_class
from tests.session import native_session


class JSONCompilationTestCase(CompilationTestCase):
def test_create_table(self):
table = Table(
'test', CompilationTestCase.metadata(),
Column('x', types.JSON),
engines.Memory()
)

self.assertEqual(
self.compile(CreateTable(table)),
'CREATE TABLE test (x JSON) ENGINE = Memory'
)


@parameterized_class(
[{'session': native_session}],
class_name_func=class_name_func
)
class JSONTestCase(BaseTestCase):
table = Table(
'test', BaseTestCase.metadata(),
Column('x', types.JSON),
engines.Memory()
)

def test_select_insert(self):
data = {'k1': 1, 'k2': '2', 'k3': True}

self.table.drop(bind=self.session.bind, if_exists=True)
try:
# http session is unsupport
self.session.execute(
text('SET allow_experimental_object_type = 1;')
)
self.session.execute(text(self.compile(CreateTable(self.table))))
self.session.execute(self.table.insert(), [{'x': data}])
coltype = inspect(self.session.bind).get_columns('test')[0]['type']
self.assertIsInstance(coltype, types.JSON)
# https://clickhouse.com/docs/en/sql-reference/functions/json-functions#tojsonstring
# The json type returns a tuple of values by default,
# which needs to be converted to json using the
# toJSONString function.
res = self.session.query(
func.toJSONString(self.table.c.x)
).scalar()
self.assertEqual(json.loads(res), data)
finally:
self.table.drop(bind=self.session.bind, if_exists=True)

0 comments on commit e0f0f6e

Please sign in to comment.