Skip to content

Commit 78ddbbb

Browse files
FIX: NULL parameter type mapping for VARBINARY columns (#458) (#466)
### Work Item / Issue Reference <!-- IMPORTANT: Please follow the PR template guidelines below. For mssql-python maintainers: Insert your ADO Work Item ID below For external contributors: Insert Github Issue number below Only one reference is required - either GitHub issue OR ADO Work Item. --> <!-- mssql-python maintainers: ADO Work Item --> > [AB#42894](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/42894) <!-- External contributors: GitHub Issue --> > GitHub Issue: #458 ------------------------------------------------------------------- ### Summary <!-- Insert your summary of changes below. Minimum 10 characters required. --> This pull request makes a targeted fix to the parameter type mapping logic for NULL values in the `mssql_python/cursor.py` file. The change improves compatibility and prevents implicit conversion errors when executing queries with NULL parameters. Parameter mapping improvement: * Changed the mapping for NULL parameters in the `_map_sql_type` function to use `SQL_UNKNOWN_TYPE` instead of `SQL_VARCHAR`, allowing the driver to correctly determine the column type and avoid conversion errors. <!-- ### PR Title Guide > For feature requests FEAT: (short-description) > For non-feature requests like test case updates, config updates , dependency updates etc CHORE: (short-description) > For Fix requests FIX: (short-description) > For doc update requests DOC: (short-description) > For Formatting, indentation, or styling update STYLE: (short-description) > For Refactor, without any feature changes REFACTOR: (short-description) > For release related changes, without any feature changes RELEASE: #<RELEASE_VERSION> (short-description) ### Contribution Guidelines External contributors: - Create a GitHub issue first: https://github.com/microsoft/mssql-python/issues/new - Link the GitHub issue in the "GitHub Issue" section above - Follow the PR title format and provide a meaningful summary mssql-python maintainers: - Create an ADO Work Item following internal processes - Link the ADO Work Item in the "ADO Work Item" section above - Follow the PR title format and provide a meaningful summary --> --------- Co-authored-by: Jahnvi Thakkar <61936179+jahnvi480@users.noreply.github.com>
1 parent 064f543 commit 78ddbbb

3 files changed

Lines changed: 46 additions & 13 deletions

File tree

mssql_python/cursor.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ def _map_sql_type( # pylint: disable=too-many-arguments,too-many-positional-arg
396396
if param is None:
397397
logger.debug("_map_sql_type: NULL parameter - index=%d", i)
398398
return (
399-
ddbc_sql_const.SQL_VARCHAR.value,
399+
ddbc_sql_const.SQL_UNKNOWN_TYPE.value,
400400
ddbc_sql_const.SQL_C_DEFAULT.value,
401401
1,
402402
0,
@@ -2208,6 +2208,16 @@ def executemany( # pylint: disable=too-many-locals,too-many-branches,too-many-s
22082208
min_val=min_val,
22092209
max_val=max_val,
22102210
)
2211+
2212+
# For executemany with all-NULL columns, SQL_UNKNOWN_TYPE doesn't work
2213+
# with array binding. Fall back to SQL_VARCHAR as a safe default.
2214+
if (
2215+
sample_value is None
2216+
and paraminfo.paramSQLType == ddbc_sql_const.SQL_UNKNOWN_TYPE.value
2217+
):
2218+
paraminfo.paramSQLType = ddbc_sql_const.SQL_VARCHAR.value
2219+
paraminfo.columnSize = 1
2220+
22112221
# Special handling for binary data in auto-detected types
22122222
if paraminfo.paramSQLType in (
22132223
ddbc_sql_const.SQL_BINARY.value,

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -471,14 +471,21 @@ SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params,
471471
hStmt, static_cast<SQLUSMALLINT>(paramIndex + 1), &describedType,
472472
&describedSize, &describedDigits, &nullable);
473473
if (!SQL_SUCCEEDED(rc)) {
474-
LOG("BindParameters: SQLDescribeParam failed for "
475-
"param[%d] (NULL parameter) - SQLRETURN=%d",
476-
paramIndex, rc);
477-
return rc;
474+
// SQLDescribeParam can fail for generic SELECT statements where
475+
// no table column is referenced. Fall back to SQL_VARCHAR as a safe
476+
// default.
477+
LOG_WARNING("BindParameters: SQLDescribeParam failed for "
478+
"param[%d] (NULL parameter) - SQLRETURN=%d, falling back to "
479+
"SQL_VARCHAR",
480+
paramIndex, rc);
481+
sqlType = SQL_VARCHAR;
482+
columnSize = 1;
483+
decimalDigits = 0;
484+
} else {
485+
sqlType = describedType;
486+
columnSize = describedSize;
487+
decimalDigits = describedDigits;
478488
}
479-
sqlType = describedType;
480-
columnSize = describedSize;
481-
decimalDigits = describedDigits;
482489
}
483490
dataPtr = nullptr;
484491
strLenOrIndPtr = AllocateParamBuffer<SQLLEN>(paramBuffers);
@@ -4048,7 +4055,8 @@ size_t calculateRowSize(py::list& columnNames, SQLUSMALLINT numCols) {
40484055
break;
40494056
case SQL_SS_UDT:
40504057
rowSize += (static_cast<SQLLEN>(columnSize) == SQL_NO_TOTAL || columnSize == 0)
4051-
? SQL_MAX_LOB_SIZE : columnSize;
4058+
? SQL_MAX_LOB_SIZE
4059+
: columnSize;
40524060
break;
40534061
case SQL_BINARY:
40544062
case SQL_VARBINARY:
@@ -4112,8 +4120,7 @@ SQLRETURN FetchMany_wrap(SqlHandlePtr StatementHandle, py::list& rows, int fetch
41124120

41134121
if ((dataType == SQL_WVARCHAR || dataType == SQL_WLONGVARCHAR || dataType == SQL_VARCHAR ||
41144122
dataType == SQL_LONGVARCHAR || dataType == SQL_VARBINARY ||
4115-
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML ||
4116-
dataType == SQL_SS_UDT) &&
4123+
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML || dataType == SQL_SS_UDT) &&
41174124
(columnSize == 0 || columnSize == SQL_NO_TOTAL || columnSize > SQL_MAX_LOB_SIZE)) {
41184125
lobColumns.push_back(i + 1); // 1-based
41194126
}
@@ -4252,8 +4259,7 @@ SQLRETURN FetchAll_wrap(SqlHandlePtr StatementHandle, py::list& rows,
42524259

42534260
if ((dataType == SQL_WVARCHAR || dataType == SQL_WLONGVARCHAR || dataType == SQL_VARCHAR ||
42544261
dataType == SQL_LONGVARCHAR || dataType == SQL_VARBINARY ||
4255-
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML ||
4256-
dataType == SQL_SS_UDT) &&
4262+
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML || dataType == SQL_SS_UDT) &&
42574263
(columnSize == 0 || columnSize == SQL_NO_TOTAL || columnSize > SQL_MAX_LOB_SIZE)) {
42584264
lobColumns.push_back(i + 1); // 1-based
42594265
}

tests/test_004_cursor.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,23 @@ def test_varbinary_full_capacity(cursor, db_connection):
658658
db_connection.commit()
659659

660660

661+
def test_execute_none_into_varbinary_column(cursor, db_connection):
662+
from mssql_python.constants import ConstantsDDBC
663+
664+
drop_table_if_exists(cursor, "#test_varbinary_null")
665+
try:
666+
cursor.execute("CREATE TABLE #test_varbinary_null (data VARBINARY(100))")
667+
db_connection.commit()
668+
cursor.setinputsizes([(ConstantsDDBC.SQL_VARBINARY.value, 100, 0)])
669+
cursor.execute("INSERT INTO #test_varbinary_null (data) VALUES (?)", None)
670+
db_connection.commit()
671+
cursor.execute("SELECT data FROM #test_varbinary_null")
672+
row = cursor.fetchone()
673+
assert row[0] is None
674+
finally:
675+
drop_table_if_exists(cursor, "#test_varbinary_null")
676+
677+
661678
def test_varbinary_max(cursor, db_connection):
662679
"""Test SQL_VARBINARY with MAX length"""
663680
try:

0 commit comments

Comments
 (0)