Skip to content

Commit 205e7cc

Browse files
committed
Fixes for PyMySQL compatibility
1 parent 201e3a7 commit 205e7cc

1 file changed

Lines changed: 57 additions & 4 deletions

File tree

singlestoredb/http.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
Description = Tuple[
4646
str, int, Optional[int], Optional[int], Optional[int],
47-
Optional[int], bool, Optional[int], Optional[int],
47+
Optional[int], bool,
4848
]
4949

5050

@@ -102,6 +102,19 @@
102102
])
103103

104104

105+
def get_precision_scale(type_code: str) -> tuple[Optional[int], Optional[int]]:
106+
"""Parse the precision and scale from a data type."""
107+
if '(' not in type_code:
108+
return (None, None)
109+
m = re.search(r'\(\s*(\d+)\s*,\s*(\d+)\s*\)', type_code)
110+
if m:
111+
return int(m.group(1)), int(m.group(2))
112+
m = re.search(r'\(\s*(\d+)\s*\)', type_code)
113+
if m:
114+
return (int(m.group(1)), None)
115+
raise ValueError(f'Unrecognized type code: {type_code}')
116+
117+
105118
def get_exc_type(code: int) -> type:
106119
"""Map error code to DB-API error type."""
107120
if code in _interface_errors:
@@ -135,6 +148,25 @@ def b64decode_converter(
135148
return converter(b64decode(x))
136149

137150

151+
class PyMyField(object):
152+
"""Field for PyMySQL compatibility."""
153+
154+
def __init__(self, name: str, flags: int, charset: int) -> None:
155+
self.name = name
156+
self.flags = flags
157+
self.charsetnr = charset
158+
159+
160+
class PyMyResult(object):
161+
"""Result for PyMySQL compatibility."""
162+
163+
def __init__(self) -> None:
164+
self.fields: list[PyMyField] = []
165+
166+
def append(self, item: PyMyField) -> None:
167+
self.fields.append(item)
168+
169+
138170
class Cursor(object):
139171
"""
140172
SingleStoreDB HTTP database cursor.
@@ -159,6 +191,14 @@ def __init__(self, connection: Connection):
159191
self.rowcount: int = 0
160192
self.messages: list[tuple[int, str]] = []
161193
self.lastrowid: Optional[int] = None
194+
self._pymy_results: list[PyMyResult] = []
195+
196+
@property
197+
def _result(self) -> Optional[PyMyResult]:
198+
"""Return Result object for PyMySQL compatibility."""
199+
if self._result_idx < 0:
200+
return None
201+
return self._pymy_results[self._result_idx]
162202

163203
@property
164204
def description(self) -> Optional[list[Description]]:
@@ -295,31 +335,44 @@ def execute(
295335

296336
for result in results:
297337

338+
pymy_res = PyMyResult()
298339
convs = []
299340

300341
description: list[Description] = []
301342
for i, col in enumerate(result.get('columns', [])):
343+
charset = 0
344+
flags = 0
302345
data_type = col['dataType'].split('(')[0]
303346
type_code = types.ColumnType.get_code(data_type)
347+
prec, scale = get_precision_scale(col['dataType'])
304348
converter = http_converters.get(type_code, None)
349+
if 'UNSIGNED' in data_type:
350+
flags = 32
305351
if data_type.endswith('BLOB') or data_type.endswith('BINARY'):
306352
converter = functools.partial(b64decode_converter, converter)
353+
charset = 63 # BINARY
307354
if type_code == 0: # DECIMAL
308355
type_code = types.ColumnType.get_code('NEWDECIMAL')
309356
elif type_code == 15: # VARCHAR / VARBINARY
310357
type_code = types.ColumnType.get_code('VARSTRING')
358+
if type_code == 246 and prec is not None: # NEWDECIMAL
359+
prec += 1 # for sign
360+
if scale is not None and scale > 0:
361+
prec += 1 # for decimal
311362
if converter is not None:
312363
convs.append((i, None, converter))
313364
description.append((
314-
col['name'], type_code,
315-
None, None, None, None,
316-
col.get('nullable', False), 0, 0,
365+
str(col['name']), type_code,
366+
None, None, prec, scale,
367+
col.get('nullable', False),
317368
))
369+
pymy_res.append(PyMyField(col['name'], flags, charset))
318370
self._descriptions.append(description)
319371

320372
rows = convert_rows(result.get('rows', []), convs)
321373

322374
self._results.append(rows)
375+
self._pymy_results.append(pymy_res)
323376

324377
self.rowcount = len(self._results[0])
325378
else:

0 commit comments

Comments
 (0)