diff --git a/singlestoredb/http/connection.py b/singlestoredb/http/connection.py index 1d1dce5d8..1b5ceb515 100644 --- a/singlestoredb/http/connection.py +++ b/singlestoredb/http/connection.py @@ -647,23 +647,24 @@ def json_to_str(x: Any) -> Optional[str]: type_code = types.ColumnType.get_code(data_type) prec, scale = get_precision_scale(col['dataType']) converter = http_converters.get(type_code, None) + if 'UNSIGNED' in data_type: flags = 32 + if data_type.endswith('BLOB') or data_type.endswith('BINARY'): converter = functools.partial( b64decode_converter, converter, # type: ignore ) charset = 63 # BINARY + if type_code == 0: # DECIMAL type_code = types.ColumnType.get_code('NEWDECIMAL') elif type_code == 15: # VARCHAR / VARBINARY type_code = types.ColumnType.get_code('VARSTRING') - if type_code == 246 and prec is not None: # NEWDECIMAL - prec += 1 # for sign - if scale is not None and scale > 0: - prec += 1 # for decimal + if converter is not None: convs.append((i, None, converter)) + description.append( Description( str(col['name']), type_code, @@ -673,6 +674,7 @@ def json_to_str(x: Any) -> Optional[str]: ), ) pymy_res.append(PyMyField(col['name'], flags, charset)) + self._descriptions.append(description) self._schemas.append(get_schema(self._results_type, description)) @@ -936,7 +938,7 @@ def next(self) -> Optional[Result]: def __iter__(self) -> Iterable[Tuple[Any, ...]]: """Return result iterator.""" - return iter(self._rows) + return iter(self._rows[self._row_idx:]) def __enter__(self) -> 'Cursor': """Enter a context.""" diff --git a/singlestoredb/mysql/protocol.py b/singlestoredb/mysql/protocol.py index 8c43f6c84..378e2bee4 100644 --- a/singlestoredb/mysql/protocol.py +++ b/singlestoredb/mysql/protocol.py @@ -324,13 +324,26 @@ def _parse_field_descriptor(self, encoding): raise TypeError(f'unrecognized extended data type: {ext_type_code}') def description(self): - """Provides a 7-item tuple compatible with the Python PEP249 DB Spec.""" + """ + Provides a 9-item tuple. + + Standard descriptions only have 7 fields according to the Python + PEP249 DB Spec, but we need to surface information about unsigned + types and charsetnr for proper type handling. + + """ + precision = self.get_column_length() + if self.type_code in (FIELD_TYPE.DECIMAL, FIELD_TYPE.NEWDECIMAL): + if precision: + precision -= 1 # for the sign + if self.scale > 0: + precision -= 1 # for the decimal point return Description( self.name, self.type_code, None, # TODO: display_length; should this be self.length? self.get_column_length(), # 'internal_size' - self.get_column_length(), # 'precision' # TODO: why!?!? + precision, # 'precision' self.scale, self.flags % 2 == 0, self.flags, diff --git a/singlestoredb/tests/test_connection.py b/singlestoredb/tests/test_connection.py index 3efa7e47b..a02c4a9f2 100755 --- a/singlestoredb/tests/test_connection.py +++ b/singlestoredb/tests/test_connection.py @@ -1446,7 +1446,7 @@ def test_alltypes_polars(self): # Recent versions of polars have a problem with decimals class FixCompare(str): def __eq__(self, other): - return super().__eq__(other.replace('precision=None', 'precision=22')) + return super().__eq__(other.replace('precision=None', 'precision=20')) dtypes = [ ('id', 'Int32'), @@ -1469,10 +1469,10 @@ def __eq__(self, other): ('float', 'Float32'), ('double', 'Float64'), ('real', 'Float64'), - ('decimal', FixCompare('Decimal(precision=22, scale=6)')), - ('dec', FixCompare('Decimal(precision=22, scale=6)')), - ('fixed', FixCompare('Decimal(precision=22, scale=6)')), - ('numeric', FixCompare('Decimal(precision=22, scale=6)')), + ('decimal', FixCompare('Decimal(precision=20, scale=6)')), + ('dec', FixCompare('Decimal(precision=20, scale=6)')), + ('fixed', FixCompare('Decimal(precision=20, scale=6)')), + ('numeric', FixCompare('Decimal(precision=20, scale=6)')), ('date', 'Date'), ('time', "Duration(time_unit='us')"), ('time_6', "Duration(time_unit='us')"), @@ -1593,7 +1593,7 @@ def test_alltypes_no_nulls_polars(self): # Recent versions of polars have a problem with decimals class FixCompare(str): def __eq__(self, other): - return super().__eq__(other.replace('precision=None', 'precision=22')) + return super().__eq__(other.replace('precision=None', 'precision=20')) dtypes = [ ('id', 'Int32'), @@ -1616,10 +1616,10 @@ def __eq__(self, other): ('float', 'Float32'), ('double', 'Float64'), ('real', 'Float64'), - ('decimal', FixCompare('Decimal(precision=22, scale=6)')), - ('dec', FixCompare('Decimal(precision=22, scale=6)')), - ('fixed', FixCompare('Decimal(precision=22, scale=6)')), - ('numeric', FixCompare('Decimal(precision=22, scale=6)')), + ('decimal', FixCompare('Decimal(precision=20, scale=6)')), + ('dec', FixCompare('Decimal(precision=20, scale=6)')), + ('fixed', FixCompare('Decimal(precision=20, scale=6)')), + ('numeric', FixCompare('Decimal(precision=20, scale=6)')), ('date', 'Date'), ('time', "Duration(time_unit='us')"), ('time_6', "Duration(time_unit='us')"), @@ -1825,10 +1825,10 @@ def test_alltypes_arrow(self): ('float', 'float'), ('double', 'double'), ('real', 'double'), - ('decimal', 'decimal128(22, 6)'), - ('dec', 'decimal128(22, 6)'), - ('fixed', 'decimal128(22, 6)'), - ('numeric', 'decimal128(22, 6)'), + ('decimal', 'decimal128(20, 6)'), + ('dec', 'decimal128(20, 6)'), + ('fixed', 'decimal128(20, 6)'), + ('numeric', 'decimal128(20, 6)'), ('date', 'date64[ms]'), ('time', 'duration[us]'), ('time_6', 'duration[us]'), @@ -1964,10 +1964,10 @@ def test_alltypes_no_nulls_arrow(self): ('float', 'float'), ('double', 'double'), ('real', 'double'), - ('decimal', 'decimal128(22, 6)'), - ('dec', 'decimal128(22, 6)'), - ('fixed', 'decimal128(22, 6)'), - ('numeric', 'decimal128(22, 6)'), + ('decimal', 'decimal128(20, 6)'), + ('dec', 'decimal128(20, 6)'), + ('fixed', 'decimal128(20, 6)'), + ('numeric', 'decimal128(20, 6)'), ('date', 'date64[ms]'), ('time', 'duration[us]'), ('time_6', 'duration[us]'),