Skip to content

Commit 9eb92ed

Browse files
authored
FEAT: Support correct type handling of None parameters in parameter binding (#208)
### Work Item / Issue Reference <!-- IMPORTANT: Please follow the PR template guidelines below. For mssql-python maintainers: Insert your ADO Work Item ID below (e.g. AB#37452) For external contributors: Insert Github Issue number below (e.g. #149) Only one reference is required - either GitHub issue OR ADO Work Item. --> <!-- mssql-python maintainers: ADO Work Item --> > [AB#38339](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/38339) <!-- External contributors: GitHub Issue --> > GitHub Issue: #<ISSUE_NUMBER> ------------------------------------------------------------------- ### Summary <!-- Insert your summary of changes below. Minimum 10 characters required. --> This pull request adds support for using the `SQLDescribeParam` ODBC API to determine the correct SQL type for parameters with unknown types (such as `None` values), improving parameter binding accuracy—especially for binary columns. The changes include adding the necessary function pointer, loading it from the driver, and updating the parameter binding logic to call `SQLDescribeParam` when needed. **ODBC API Integration:** * Added the `SQLDescribeParamFunc` typedef and declared/defined the `SQLDescribeParam_ptr` function pointer to enable calling `SQLDescribeParam` from the driver. [[1]](diffhunk://#diff-85167a2d59779df18704284ab7ce46220c3619408fbf22c631ffdf29f794d635R132-R133) [[2]](diffhunk://#diff-85167a2d59779df18704284ab7ce46220c3619408fbf22c631ffdf29f794d635R177-R178) [[3]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1R139-R140) [[4]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1R820-R821) [[5]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1L801-R832) **Parameter Binding Improvements:** * Updated the logic in `BindParameters` to call `SQLDescribeParam` for parameters with unknown types, ensuring correct SQL type, column size, and decimal digits are used for `None` values and improving support for binary columns. **Minor Cleanups:** * Removed a TODO comment in `_map_sql_type` as type detection is now handled more robustly. <!-- ### 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 -->
1 parent 940597e commit 9eb92ed

File tree

3 files changed

+42
-7
lines changed

3 files changed

+42
-7
lines changed

mssql_python/cursor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def _map_sql_type(self, param, parameters_list, i):
231231
"""
232232
if param is None:
233233
return (
234-
ddbc_sql_const.SQL_VARCHAR.value, # TODO: Add SQLDescribeParam to get correct type
234+
ddbc_sql_const.SQL_VARCHAR.value,
235235
ddbc_sql_const.SQL_C_DEFAULT.value,
236236
1,
237237
0,

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ SQLParamDataFunc SQLParamData_ptr = nullptr;
140140
SQLPutDataFunc SQLPutData_ptr = nullptr;
141141
SQLTablesFunc SQLTables_ptr = nullptr;
142142

143+
SQLDescribeParamFunc SQLDescribeParam_ptr = nullptr;
144+
143145
namespace {
144146

145147
const char* GetSqlCTypeAsString(const SQLSMALLINT cType) {
@@ -212,12 +214,12 @@ std::string DescribeChar(unsigned char ch) {
212214
// Given a list of parameters and their ParamInfo, calls SQLBindParameter on each of them with
213215
// appropriate arguments
214216
SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params,
215-
const std::vector<ParamInfo>& paramInfos,
217+
std::vector<ParamInfo>& paramInfos,
216218
std::vector<std::shared_ptr<void>>& paramBuffers) {
217219
LOG("Starting parameter binding. Number of parameters: {}", params.size());
218220
for (int paramIndex = 0; paramIndex < params.size(); paramIndex++) {
219221
const auto& param = params[paramIndex];
220-
const ParamInfo& paramInfo = paramInfos[paramIndex];
222+
ParamInfo& paramInfo = paramInfos[paramIndex];
221223
LOG("Binding parameter {} - C Type: {}, SQL Type: {}", paramIndex, paramInfo.paramCType, paramInfo.paramSQLType);
222224
void* dataPtr = nullptr;
223225
SQLLEN bufferLength = 0;
@@ -283,11 +285,37 @@ SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params,
283285
if (!py::isinstance<py::none>(param)) {
284286
ThrowStdException(MakeParamMismatchErrorStr(paramInfo.paramCType, paramIndex));
285287
}
286-
// TODO: This wont work for None values added to BINARY/VARBINARY columns. None values
287-
// of binary columns need to have C type = SQL_C_BINARY & SQL type = SQL_BINARY
288+
SQLSMALLINT sqlType = paramInfo.paramSQLType;
289+
SQLULEN columnSize = paramInfo.columnSize;
290+
SQLSMALLINT decimalDigits = paramInfo.decimalDigits;
291+
if (sqlType == SQL_UNKNOWN_TYPE) {
292+
SQLSMALLINT describedType;
293+
SQLULEN describedSize;
294+
SQLSMALLINT describedDigits;
295+
SQLSMALLINT nullable;
296+
RETCODE rc = SQLDescribeParam_ptr(
297+
hStmt,
298+
static_cast<SQLUSMALLINT>(paramIndex + 1),
299+
&describedType,
300+
&describedSize,
301+
&describedDigits,
302+
&nullable
303+
);
304+
if (!SQL_SUCCEEDED(rc)) {
305+
LOG("SQLDescribeParam failed for parameter {} with error code {}", paramIndex, rc);
306+
return rc;
307+
}
308+
sqlType = describedType;
309+
columnSize = describedSize;
310+
decimalDigits = describedDigits;
311+
}
288312
dataPtr = nullptr;
289313
strLenOrIndPtr = AllocateParamBuffer<SQLLEN>(paramBuffers);
290314
*strLenOrIndPtr = SQL_NULL_DATA;
315+
bufferLength = 0;
316+
paramInfo.paramSQLType = sqlType;
317+
paramInfo.columnSize = columnSize;
318+
paramInfo.decimalDigits = decimalDigits;
291319
break;
292320
}
293321
case SQL_C_STINYINT:
@@ -767,6 +795,8 @@ DriverHandle LoadDriverOrThrowException() {
767795
SQLPutData_ptr = GetFunctionPointer<SQLPutDataFunc>(handle, "SQLPutData");
768796
SQLTables_ptr = GetFunctionPointer<SQLTablesFunc>(handle, "SQLTablesW");
769797

798+
SQLDescribeParam_ptr = GetFunctionPointer<SQLDescribeParamFunc>(handle, "SQLDescribeParam");
799+
770800
bool success =
771801
SQLAllocHandle_ptr && SQLSetEnvAttr_ptr && SQLSetConnectAttr_ptr &&
772802
SQLSetStmtAttr_ptr && SQLGetConnectAttr_ptr && SQLDriverConnect_ptr &&
@@ -777,7 +807,8 @@ DriverHandle LoadDriverOrThrowException() {
777807
SQLDescribeCol_ptr && SQLMoreResults_ptr && SQLColAttribute_ptr &&
778808
SQLEndTran_ptr && SQLDisconnect_ptr && SQLFreeHandle_ptr &&
779809
SQLFreeStmt_ptr && SQLGetDiagRec_ptr && SQLParamData_ptr &&
780-
SQLPutData_ptr && SQLTables_ptr;
810+
SQLPutData_ptr && SQLTables_ptr &&
811+
SQLDescribeParam_ptr;
781812

782813
if (!success) {
783814
ThrowStdException("Failed to load required function pointers from driver.");
@@ -1072,7 +1103,7 @@ SQLRETURN SQLTables_wrap(SqlHandlePtr StatementHandle,
10721103
// be prepared in a previous call.
10731104
SQLRETURN SQLExecute_wrap(const SqlHandlePtr statementHandle,
10741105
const std::wstring& query /* TODO: Use SQLTCHAR? */,
1075-
const py::list& params, const std::vector<ParamInfo>& paramInfos,
1106+
const py::list& params, std::vector<ParamInfo>& paramInfos,
10761107
py::list& isStmtPrepared, const bool usePrepare = true) {
10771108
LOG("Execute SQL Query - {}", query.c_str());
10781109
if (!SQLPrepare_ptr) {

mssql_python/pybind/ddbc_bindings.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ typedef SQLRETURN (SQL_API* SQLFreeStmtFunc)(SQLHSTMT, SQLUSMALLINT);
211211
typedef SQLRETURN (SQL_API* SQLGetDiagRecFunc)(SQLSMALLINT, SQLHANDLE, SQLSMALLINT, SQLWCHAR*, SQLINTEGER*,
212212
SQLWCHAR*, SQLSMALLINT, SQLSMALLINT*);
213213

214+
typedef SQLRETURN (SQL_API* SQLDescribeParamFunc)(SQLHSTMT, SQLUSMALLINT, SQLSMALLINT*, SQLULEN*, SQLSMALLINT*, SQLSMALLINT*);
215+
214216
// DAE APIs
215217
typedef SQLRETURN (SQL_API* SQLParamDataFunc)(SQLHSTMT, SQLPOINTER*);
216218
typedef SQLRETURN (SQL_API* SQLPutDataFunc)(SQLHSTMT, SQLPOINTER, SQLLEN);
@@ -257,6 +259,8 @@ extern SQLFreeStmtFunc SQLFreeStmt_ptr;
257259
// Diagnostic APIs
258260
extern SQLGetDiagRecFunc SQLGetDiagRec_ptr;
259261

262+
extern SQLDescribeParamFunc SQLDescribeParam_ptr;
263+
260264
// DAE APIs
261265
extern SQLParamDataFunc SQLParamData_ptr;
262266
extern SQLPutDataFunc SQLPutData_ptr;

0 commit comments

Comments
 (0)