4444
4545Description = 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
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+
105118def 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+
138170class 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