diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index 594cd2b0..62572642 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -395,8 +395,7 @@ def _map_sql_type( # pylint: disable=too-many-arguments,too-many-positional-arg # Detect MONEY / SMALLMONEY range if SMALLMONEY_MIN <= param <= SMALLMONEY_MAX: - # smallmoney - parameters_list[i] = str(param) + parameters_list[i] = format(param, 'f') return ( ddbc_sql_const.SQL_VARCHAR.value, ddbc_sql_const.SQL_C_CHAR.value, @@ -405,8 +404,7 @@ def _map_sql_type( # pylint: disable=too-many-arguments,too-many-positional-arg False, ) if MONEY_MIN <= param <= MONEY_MAX: - # money - parameters_list[i] = str(param) + parameters_list[i] = format(param, 'f') return ( ddbc_sql_const.SQL_VARCHAR.value, ddbc_sql_const.SQL_C_CHAR.value, @@ -1916,13 +1914,12 @@ def executemany( # pylint: disable=too-many-locals,too-many-branches,too-many-s for i, val in enumerate(processed_row): if val is None: continue - # Convert Decimals for money/smallmoney to string if ( isinstance(val, decimal.Decimal) and parameters_type[i].paramSQLType == ddbc_sql_const.SQL_VARCHAR.value ): - processed_row[i] = str(val) + processed_row[i] = format(val, 'f') # Existing numeric conversion elif parameters_type[i].paramSQLType in ( ddbc_sql_const.SQL_DECIMAL.value, diff --git a/tests/test_004_cursor.py b/tests/test_004_cursor.py index bab755d2..83f61e06 100644 --- a/tests/test_004_cursor.py +++ b/tests/test_004_cursor.py @@ -13477,20 +13477,6 @@ def test_numeric_leading_zeros_precision_loss( actual == expected ), f"Leading zeros precision loss for {value}, expected {expected}, got {actual}" - except Exception as e: - # Handle cases where values get converted to scientific notation and cause SQL Server conversion errors - error_msg = str(e).lower() - if ( - "converting" in error_msg - and "varchar" in error_msg - and "numeric" in error_msg - ): - pytest.skip( - f"Value {value} converted to scientific notation, causing expected SQL Server conversion error: {e}" - ) - else: - raise # Re-raise unexpected errors - finally: try: cursor.execute(f"DROP TABLE {table_name}") @@ -13538,24 +13524,6 @@ def test_numeric_extreme_exponents_precision_loss( "1E-18" ), f"Extreme exponent value not preserved for {description}: {value} -> {actual}" - except Exception as e: - # Handle expected SQL Server validation errors for scientific notation values - error_msg = str(e).lower() - if "scale" in error_msg and "range" in error_msg: - # This is expected - SQL Server rejects invalid scale/precision combinations - pytest.skip( - f"Expected SQL Server scale/precision validation for {description}: {e}" - ) - elif any( - keyword in error_msg - for keyword in ["converting", "overflow", "precision", "varchar", "numeric"] - ): - # Other expected precision/conversion issues - pytest.skip( - f"Expected SQL Server precision limits or VARCHAR conversion for {description}: {e}" - ) - else: - raise # Re-raise if it's not a precision-related error finally: try: cursor.execute(f"DROP TABLE {table_name}") @@ -13563,7 +13531,6 @@ def test_numeric_extreme_exponents_precision_loss( except: pass # Table might not exist if creation failed - # --------------------------------------------------------- # Test 12: 38-digit precision boundary limits # --------------------------------------------------------- @@ -13659,6 +13626,72 @@ def test_numeric_beyond_38_digit_precision_negative( ), f"Expected SQL Server precision limit message for {description}, got: {error_msg}" +@pytest.mark.parametrize( + "values, description", + [ + # Small decimal values with scientific notation + ( + [ + decimal.Decimal('0.70000000000696'), + decimal.Decimal('1E-7'), + decimal.Decimal('0.00001'), + decimal.Decimal('6.96E-12'), + ], + "Small decimals with scientific notation" + ), + # Large decimal values with scientific notation + ( + [ + decimal.Decimal('4E+8'), + decimal.Decimal('1.521E+15'), + decimal.Decimal('5.748E+18'), + decimal.Decimal('1E+11') + ], + "Large decimals with positive exponents" + ), + # Medium-sized decimals + ( + [ + decimal.Decimal('123.456'), + decimal.Decimal('9999.9999'), + decimal.Decimal('1000000.50') + ], + "Medium-sized decimals" + ), + ], +) +def test_decimal_scientific_notation_to_varchar(cursor, db_connection, values, description): + """ + Test that Decimal values with scientific notation are properly converted + to VARCHAR without triggering 'varchar to numeric' conversion errors. + This verifies that the driver correctly handles Decimal to VARCHAR conversion + """ + table_name = "#pytest_decimal_varchar_conversion" + try: + cursor.execute(f"CREATE TABLE {table_name} (id INT IDENTITY(1,1), val VARCHAR(50))") + + for val in values: + cursor.execute(f"INSERT INTO {table_name} (val) VALUES (?)", (val,)) + db_connection.commit() + + cursor.execute(f"SELECT val FROM {table_name} ORDER BY id") + rows = cursor.fetchall() + + assert len(rows) == len(values), f"Expected {len(values)} rows, got {len(rows)}" + + for i, (row, expected_val) in enumerate(zip(rows, values)): + stored_val = decimal.Decimal(row[0]) + assert stored_val == expected_val, ( + f"{description}: Row {i} mismatch - expected {expected_val}, got {stored_val}" + ) + + finally: + try: + cursor.execute(f"DROP TABLE {table_name}") + db_connection.commit() + except: + pass + SMALL_XML = "1" LARGE_XML = "" + "".join(f"{i}" for i in range(10000)) + "" EMPTY_XML = "" @@ -14400,4 +14433,4 @@ def test_close(db_connection): except Exception as e: pytest.fail(f"Cursor close test failed: {e}") finally: - cursor = db_connection.cursor() + cursor = db_connection.cursor() \ No newline at end of file