Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions mssql_python/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
101 changes: 67 additions & 34 deletions tests/test_004_cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down Expand Up @@ -13538,32 +13524,13 @@ 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}")
db_connection.commit()
except:
pass # Table might not exist if creation failed


# ---------------------------------------------------------
# Test 12: 38-digit precision boundary limits
# ---------------------------------------------------------
Expand Down Expand Up @@ -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 = "<root><item>1</item></root>"
LARGE_XML = "<root>" + "".join(f"<item>{i}</item>" for i in range(10000)) + "</root>"
EMPTY_XML = ""
Expand Down Expand Up @@ -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()