Skip to content

Commit 22a95e6

Browse files
fix nullable json inserts
1 parent 94e5d5d commit 22a95e6

3 files changed

Lines changed: 80 additions & 1 deletion

File tree

clickhouse_connect/datatypes/dynamic.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
SHARED_DATA_TYPE: ClickHouseType
1616
STRING_DATA_TYPE: ClickHouseType
17+
_JSON_NULL = b'null'
18+
_JSON_NULL_STR = 'null'
1719

1820
json_serialization_format = 0x1
1921

@@ -129,13 +131,22 @@ def json_sample_size(_, sample: Collection) -> int:
129131

130132

131133
def write_json(ch_type: ClickHouseType, column: Sequence, dest: bytearray, ctx: InsertContext):
134+
if ch_type.nullable:
135+
dest += bytearray(1 if v is None else 0 for v in column)
136+
132137
first = first_value(column, ch_type.nullable)
133138
write_col = column
134139
encoding = ctx.encoding or ch_type.encoding
135140
if not isinstance(first, str) and ch_type.write_format(ctx) != 'string':
136141
to_json = any_to_json
137-
write_col = [to_json(v) for v in column]
142+
if ch_type.nullable:
143+
write_col = [_JSON_NULL if v is None else to_json(v) for v in column]
144+
else:
145+
write_col = [to_json(v) for v in column]
138146
encoding = None
147+
else:
148+
write_col = [_JSON_NULL_STR if v is None else v for v in column]
149+
139150
handle_error(data_conv.write_str_col(write_col, ch_type.nullable, encoding, dest), ctx)
140151

141152

tests/integration_tests/test_dynamic.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from ipaddress import IPv4Address
33
from typing import Callable
44
from uuid import UUID
5+
import json
56

67
import pytest
78

@@ -150,6 +151,26 @@ def test_typed_json(test_client: Client, table_context: Callable):
150151
assert json1['a']['b'] == datetime.datetime(2020, 10, 15, 10, 15, 44, 877000)
151152

152153

154+
def test_nullable_json(test_client: Client, table_context: Callable):
155+
type_available(test_client, "json")
156+
with table_context("nullable_json", [
157+
"key Int32",
158+
"value_1 Nullable(JSON)",
159+
"value_2 Nullable(JSON)",
160+
"value_3 Nullable(JSON)"
161+
]):
162+
v1 = {"item_a": 5, "item_b": 10}
163+
164+
test_client.insert("nullable_json", [[1, v1, json.dumps(v1), None], [2, v1, None, None]])
165+
result = test_client.query('SELECT * FROM nullable_json ORDER BY key')
166+
assert result.result_set[0][1] == v1
167+
assert result.result_set[1][1] == v1
168+
assert result.result_set[0][2] == v1
169+
assert result.result_set[1][2] is None
170+
assert result.result_set[0][3] is None
171+
assert result.result_set[1][3] is None
172+
173+
153174
def test_complex_json(test_client: Client, table_context: Callable):
154175
type_available(test_client, 'json')
155176
if not test_client.min_version('24.10'):

tests/integration_tests/test_pandas.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,50 @@ def test_pandas_small_blocks(test_config: TestConfig, test_client: Client):
285285
res = test_client.query_df('SELECT number, randomString(512) FROM numbers(1000000)',
286286
settings={'max_block_size': 250})
287287
assert len(res) == 1000000
288+
289+
290+
def test_pandas_string_to_df_insert(test_client: Client, table_context: Callable):
291+
with table_context(
292+
"test_pandas_string_to_df_insert",
293+
[
294+
"id UInt32",
295+
"timestamp Nullable(DateTime)",
296+
"json_data Nullable(JSON)",
297+
],
298+
):
299+
300+
df = pd.DataFrame(
301+
[[1, "simple"], [2, "with spaces"], [3, "特殊字符"], [4, ""]],
302+
columns=["id", "s"],
303+
)
304+
305+
json_data_dict = {"vm": "", "App Name": "MKT"}
306+
json_data_dict2 = {"Room": "Leo"}
307+
308+
data = [
309+
{
310+
"id": 1,
311+
"timestamp": datetime(year=2025, month=7, day=5, hour=12),
312+
"json_data": json_data_dict,
313+
},
314+
{
315+
"id": 2,
316+
"timestamp": datetime(year=2025, month=7, day=6, hour=12),
317+
"json_data": json_data_dict2,
318+
},
319+
{
320+
"id": 3,
321+
"timestamp": datetime(year=2025, month=7, day=7, hour=12),
322+
"json_data": None,
323+
},
324+
]
325+
326+
df = pd.DataFrame(data)
327+
test_client.insert_df("test_pandas_string_to_df_insert", df)
328+
result_df = test_client.query_df(
329+
"SELECT * FROM test_pandas_string_to_df_insert ORDER BY id"
330+
)
331+
332+
assert result_df.iloc[0]["json_data"] == json_data_dict
333+
assert result_df.iloc[1]["json_data"] == json_data_dict2
334+
assert result_df.iloc[2]["json_data"] is None

0 commit comments

Comments
 (0)