Conversation
… jahnvi/setinputsize_with_decimal
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR fixes a crash when binding Python Decimal values to SQL DECIMAL/NUMERIC using setinputsizes() (notably with executemany) by switching to string-based binding and adding regression tests.
Changes:
- Map SQL
DECIMAL/NUMERICtoSQL_C_CHARfor C-type binding and convert PythonDecimalvalues to strings during parameter processing. - Update
executemanypreprocessing to convertDecimalvalues when SQL type isDECIMAL/NUMERIC. - Add new cursor tests covering
setinputsizes()+SQL_DECIMAL/SQL_NUMERICwithexecutemany,execute, andNULL.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| mssql_python/cursor.py | Switches DECIMAL/NUMERIC binding to SQL_C_CHAR and adds Decimal-to-string conversions in parameter handling paths. |
| tests/test_004_cursor.py | Adds regression tests validating Decimal binding via setinputsizes() for execute/executemany and NULL handling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql_python/cursor.pyLines 2323-2332 2323 processed_row[i] = format(val, "f")
2324 else:
2325 try:
2326 processed_row[i] = format(decimal.Decimal(str(val)), "f")
! 2327 except Exception as e: # pylint: disable=broad-exception-caught
! 2328 raise ValueError(
2329 f"Failed to convert parameter at row {row}, column {i} to Decimal: {e}"
2330 ) from e
2331 processed_parameters.append(processed_row)📋 Files Needing Attention📉 Files with overall lowest coverage (click to expand)mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 67.8%
mssql_python.row.py: 70.5%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 74.5%
mssql_python.pybind.connection.connection.cpp: 75.8%
mssql_python.__init__.py: 77.3%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 79.6%
mssql_python.connection.py: 85.2%🔗 Quick Links
|
bewithgaurav
left a comment
There was a problem hiding this comment.
core fix lgtm, have added some commenst around tests, suggestions to improve code coverage and a possible unrelated corner case in this path
will await responses
| else: | ||
| try: | ||
| processed_row[i] = format(decimal.Decimal(str(val)), "f") | ||
| except Exception as e: # pylint: disable=broad-exception-caught |
There was a problem hiding this comment.
saw in the coverage report that these lines are uncovered
for the except branch that fires when a non-Decimal value in a DECIMAL/NUMERIC column can't be converted via decimal.Decimal(str(val)), you need to pass an unconvertible value (e.g., a non-numeric string) to a column typed as SQL_DECIMAL - this might cover this branch
| # Convert Decimal to string for SQL_C_CHAR binding (GH-503) | ||
| if isinstance(parameter, decimal.Decimal) and sql_type in ( | ||
| ddbc_sql_const.SQL_DECIMAL.value, | ||
| ddbc_sql_const.SQL_NUMERIC.value, |
There was a problem hiding this comment.
while checking cases, there's a corner case for MONEY type (pre-existing - didn't cause due to this PR) which is:
Decimal("1000000000000000.00") (outside MONEY range, no setinputsizes)
│
_map_sql_type() → paramCType = SQL_C_NUMERIC (expects NumericData)
│
processing loop → format(val, "f") → string "1000000000000000.00"
│
C++ binding → "Is this NumericData?" → NO → RuntimeErrorthis might be out of scope of this PR but we need to address
we can start a new issue for this - will let you take a call
| finally: | ||
| cursor.execute("DROP TABLE IF EXISTS #test_sis_dec_null") | ||
|
|
||
|
|
There was a problem hiding this comment.
suggesting some more additional tests:
-
High-precision Decimal via setinputsizes
- What: Test
Decimal("12345678901234567890.123456789012345678")withsetinputsizes([(SQL_DECIMAL, 38, 18)]) - Why: Verifies string-based binding preserves full DECIMAL(38,18) precision
- How: Insert high-precision value, read back, compare
- What: Test
-
Negative zero
- What: Test
Decimal("-0.00")withsetinputsizes(SQL_DECIMAL) - Why:
format(Decimal("-0.00"), "f")produces"-0.00"- verify SQL Server handles this
- What: Test
-
Mixed NULL/non-NULL in executemany
- What:
executemanywith some rows havingNonefor the DECIMAL column, others havingDecimalvalues - Why: Exercises the NULL handling path alongside the Decimal→string conversion in the same batch
- What:
| ddbc_sql_const.SQL_WLONGVARCHAR.value: ddbc_sql_const.SQL_C_WCHAR.value, | ||
| ddbc_sql_const.SQL_DECIMAL.value: ddbc_sql_const.SQL_C_NUMERIC.value, | ||
| ddbc_sql_const.SQL_NUMERIC.value: ddbc_sql_const.SQL_C_NUMERIC.value, | ||
| ddbc_sql_const.SQL_DECIMAL.value: ddbc_sql_const.SQL_C_CHAR.value, |
There was a problem hiding this comment.
Changing SQL_DECIMAL/SQL_NUMERIC from SQL_C_NUMERIC to SQL_C_CHAR is a behavioral change that affects all decimal/numeric bindings globally, not just setinputsizes paths. Any downstream code or user code that relied on the previous SQL_C_NUMERIC binding behavior (e.g., receiving NumericData structs) will now receive strings instead. This is the most impactful change in the PR.
Ensure integration tests cover the non-setinputsizes path (plain execute with Decimal parameters without calling setinputsizes) to confirm no regression. The MONEY corner case flagged by @bewithgaurav is a concrete example of potential fallout.
Work Item / Issue Reference
Summary
This pull request improves the handling of Python
Decimalvalues when binding to SQLDECIMALandNUMERICtypes, especially when usingsetinputsizesandexecutemany. It fixes a runtime error by ensuringDecimalobjects are converted to strings for proper binding, and adds comprehensive tests to verify this behavior.Decimal binding and conversion improvements:
DECIMALandNUMERICtypes to useSQL_C_CHARinstead ofSQL_C_NUMERICin_get_c_type_for_sql_type, enabling string-based binding for decimals._create_parameter_types_listandexecutemanyto convert PythonDecimalobjects to strings when binding to SQLDECIMALorNUMERICcolumns.Testing enhancements:
test_004_cursor.pyto verify thatsetinputsizeswithSQL_DECIMALandSQL_NUMERICaccepts PythonDecimalvalues, works with bothexecutemanyandexecute, and correctly handlesNULLvalues.