Skip to content

Commit 693244b

Browse files
Merge pull request #108 from Maksim-Burtsev/106-map
Update MapType p_type method (closes issue #106)
2 parents 554fbf0 + 79ad584 commit 693244b

File tree

3 files changed

+49
-26
lines changed

3 files changed

+49
-26
lines changed

aiochclient/_types.pyx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ RE_MAP = re.compile(r"^Map\((.*)\)$")
6767
RE_REPLACE_QUOTE = re.compile(r"(?<!\\)'")
6868

6969

70+
cdef str remove_single_quotes(str string):
71+
if string[0] == string[-1] == "'":
72+
return string[1:-1]
73+
return string
74+
75+
7076
cdef str decode(char* val):
7177
"""
7278
Converting bytes from clickhouse with
@@ -169,7 +175,7 @@ cdef class StrType:
169175

170176
cdef str _convert(self, str string):
171177
if self.container:
172-
return string.strip("'")
178+
return remove_single_quotes(string)
173179
return string
174180

175181
cpdef str p_type(self, str string):
@@ -529,7 +535,7 @@ cdef class TupleType:
529535
return self._convert(value.decode())
530536

531537

532-
cdef class MapType:
538+
cdef class MapType:
533539

534540
cdef:
535541
str name
@@ -545,19 +551,10 @@ cdef class MapType:
545551
self.key_type = what_py_type(tps[:comma_index], container=True)
546552
self.value_type = what_py_type(tps[comma_index + 1:], container=True)
547553

548-
cdef dict _convert(self, string):
549-
if isinstance(string, str):
550-
string = RE_REPLACE_QUOTE.sub('"', string)
551-
string = string.replace('\\', '\\\\')
552-
dict_from_string = json.loads(string)
553-
else:
554-
dict_from_string = string
554+
cdef dict _convert(self, str string):
555+
key, value = string[1:-1].split(':', 1)
555556
return {
556-
self.key_type.p_type(
557-
decode(key.encode()) if isinstance(key, str) else key
558-
): self.value_type.p_type(
559-
decode(val.encode()) if isinstance(val, str) else val
560-
) for key, val in dict_from_string.items()
557+
self.key_type.p_type(decode(key.encode())): self.value_type.p_type(decode(value.encode()))
561558
}
562559

563560
cpdef dict p_type(self, string):

aiochclient/types.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ def datetime_parse_f(string):
3737
RE_REPLACE_QUOTE = re.compile(r"(?<!\\)'")
3838

3939

40+
def remove_single_quotes(string: str) -> str:
41+
if string[0] == string[-1] == "'":
42+
return string[1:-1]
43+
return string
44+
45+
4046
class BaseType(ABC):
4147
__slots__ = ("name", "container")
4248

@@ -141,9 +147,9 @@ def unconvert(value) -> bytes:
141147

142148

143149
class StrType(BaseType):
144-
def p_type(self, string: str):
150+
def p_type(self, string: str) -> str:
145151
if self.container:
146-
return string.strip("'")
152+
return remove_single_quotes(string)
147153
return string
148154

149155
@staticmethod
@@ -316,18 +322,12 @@ def __init__(self, name: str, **kwargs):
316322
self.key_type = what_py_type(tps[:comma_index], container=True)
317323
self.value_type = what_py_type(tps[comma_index + 1 :], container=True)
318324

319-
def p_type(self, string: Any) -> dict:
320-
if isinstance(string, str):
321-
string = RE_REPLACE_QUOTE.sub('"', string)
322-
string = string.replace('\\', '\\\\')
323-
string = json.loads(string)
325+
def p_type(self, string: str) -> dict[Any, Any]:
326+
key, value = string[1:-1].split(':', 1)
324327
return {
325-
self.key_type.p_type(
326-
self.decode(key.encode()) if isinstance(key, str) else key
327-
): self.value_type.p_type(
328-
self.decode(val.encode()) if isinstance(val, str) else val
328+
self.key_type.p_type(self.decode(key.encode())): self.value_type.p_type(
329+
self.decode(value.encode())
329330
)
330-
for key, val in string.items()
331331
}
332332

333333
def convert(self, value: bytes) -> dict:

tests.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def rows(uuid):
7272
True,
7373
{"hello": "world {' and other things"},
7474
{"hello": {"inner": "world {' and other things"}},
75+
{'key1': {'key2': [uuid]}},
7576
[(1, 2), (3, 4)],
7677
[('hello', dt.date(2018, 9, 21)), ('world', dt.date(2018, 9, 22))],
7778
],
@@ -124,6 +125,7 @@ def rows(uuid):
124125
False,
125126
{"hello": "world {'"},
126127
{"hello": {"inner": "world {'"}},
128+
{'key1': {'key2': [uuid, uuid, uuid]}},
127129
[(0, 1)],
128130
[
129131
('hello', dt.date(2018, 9, 21)),
@@ -215,6 +217,7 @@ async def all_types_db(chclient, rows):
215217
bool Bool,
216218
map Map(String, String),
217219
map_map Map(String, Map(String, String)),
220+
map_map_array_uuid Map(String, Map(String, Array(UUID))),
218221
nested_int Nested(value1 Integer, value2 Integer),
219222
nested_str_date Nested(value1 String, value2 Date)
220223
) ENGINE = Memory
@@ -622,6 +625,21 @@ async def test_map_map(self):
622625
assert record[0] == result
623626
assert record["map_map"] == result
624627

628+
async def test_map_map_array_uuid(self, uuid):
629+
result = {'key1': {'key2': [uuid]}}
630+
print(await self.select_field("map_map_array_uuid"))
631+
assert await self.select_field("map_map_array_uuid") == result
632+
record = await self.select_record("map_map_array_uuid")
633+
assert record[0] == result
634+
assert record["map_map_array_uuid"] == result
635+
636+
result = ("{'key1':{'key2':" f"['{str(uuid)}']" "}}").encode()
637+
print(await self.select_field_bytes("map_map_array_uuid"))
638+
assert await self.select_field_bytes("map_map_array_uuid") == result
639+
record = await self.select_record_bytes("map_map_array_uuid")
640+
assert record[0] == result
641+
assert record["map_map_array_uuid"] == result
642+
625643
async def test_nullable(self):
626644
result = 0
627645
assert await self.select_field("nullable") == result
@@ -1090,6 +1108,14 @@ async def test_json_insert_select(self):
10901108
}
10911109
]
10921110

1111+
async def test_map_map_array_uuid_json(self, uuid):
1112+
result = await self.ch.fetch(
1113+
"SELECT map_map_array_uuid FROM all_types WHERE has(nested_int.value1, 0) format JSONEachRow"
1114+
)
1115+
assert result[0]['map_map_array_uuid'] == {
1116+
'key1': {'key2': [str(uuid), str(uuid), str(uuid)]}
1117+
}
1118+
10931119
async def test_select_nested_json(self):
10941120
result = await self.ch.fetch(
10951121
"SELECT nested_int, nested_str_date FROM all_types WHERE has(nested_int.value1, 0) format JSONEachRow"

0 commit comments

Comments
 (0)