From e277c1411b670d34f99e7c003e1d155b3eb53323 Mon Sep 17 00:00:00 2001 From: gargsaumya Date: Thu, 28 Aug 2025 16:34:59 +0530 Subject: [PATCH 1/3] support for None parameters --- mssql_python/cursor.py | 2 +- mssql_python/pybind/ddbc_bindings.cpp | 37 ++++++++++++++++++++++++--- mssql_python/pybind/ddbc_bindings.h | 4 +++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index 5bdeaed9..88152aa2 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -231,7 +231,7 @@ def _map_sql_type(self, param, parameters_list, i): """ if param is None: return ( - ddbc_sql_const.SQL_VARCHAR.value, # TODO: Add SQLDescribeParam to get correct type + ddbc_sql_const.SQL_VARCHAR.value, ddbc_sql_const.SQL_C_DEFAULT.value, 1, 0, diff --git a/mssql_python/pybind/ddbc_bindings.cpp b/mssql_python/pybind/ddbc_bindings.cpp index 8a88688a..22ac1400 100644 --- a/mssql_python/pybind/ddbc_bindings.cpp +++ b/mssql_python/pybind/ddbc_bindings.cpp @@ -140,6 +140,8 @@ SQLParamDataFunc SQLParamData_ptr = nullptr; SQLPutDataFunc SQLPutData_ptr = nullptr; SQLTablesFunc SQLTables_ptr = nullptr; +SQLDescribeParamFunc SQLDescribeParam_ptr = nullptr; + namespace { const char* GetSqlCTypeAsString(const SQLSMALLINT cType) { @@ -283,11 +285,37 @@ SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params, if (!py::isinstance(param)) { ThrowStdException(MakeParamMismatchErrorStr(paramInfo.paramCType, paramIndex)); } - // TODO: This wont work for None values added to BINARY/VARBINARY columns. None values - // of binary columns need to have C type = SQL_C_BINARY & SQL type = SQL_BINARY + SQLSMALLINT sqlType = paramInfo.paramSQLType; + SQLULEN columnSize = paramInfo.columnSize; + SQLSMALLINT decimalDigits = paramInfo.decimalDigits; + if (sqlType == SQL_UNKNOWN_TYPE) { + SQLSMALLINT describedType; + SQLULEN describedSize; + SQLSMALLINT describedDigits; + SQLSMALLINT nullable; + RETCODE rc = SQLDescribeParam_ptr( + hStmt, + static_cast(paramIndex + 1), + &describedType, + &describedSize, + &describedDigits, + &nullable + ); + if (!SQL_SUCCEEDED(rc)) { + LOG("SQLDescribeParam failed for parameter {}", paramIndex); + return rc; + } + sqlType = describedType; + columnSize = describedSize; + decimalDigits = describedDigits; + } dataPtr = nullptr; strLenOrIndPtr = AllocateParamBuffer(paramBuffers); *strLenOrIndPtr = SQL_NULL_DATA; + bufferLength = 0; + const_cast(paramInfo).paramSQLType = sqlType; + const_cast(paramInfo).columnSize = columnSize; + const_cast(paramInfo).decimalDigits = decimalDigits; break; } case SQL_C_STINYINT: @@ -769,6 +797,8 @@ DriverHandle LoadDriverOrThrowException() { SQLPutData_ptr = GetFunctionPointer(handle, "SQLPutData"); SQLTables_ptr = GetFunctionPointer(handle, "SQLTablesW"); + SQLDescribeParam_ptr = GetFunctionPointer(handle, "SQLDescribeParam"); + bool success = SQLAllocHandle_ptr && SQLSetEnvAttr_ptr && SQLSetConnectAttr_ptr && SQLSetStmtAttr_ptr && SQLGetConnectAttr_ptr && SQLDriverConnect_ptr && @@ -779,7 +809,8 @@ DriverHandle LoadDriverOrThrowException() { SQLDescribeCol_ptr && SQLMoreResults_ptr && SQLColAttribute_ptr && SQLEndTran_ptr && SQLDisconnect_ptr && SQLFreeHandle_ptr && SQLFreeStmt_ptr && SQLGetDiagRec_ptr && SQLParamData_ptr && - SQLPutData_ptr && SQLTables_ptr; + SQLPutData_ptr && SQLTables_ptr && + SQLDescribeParam_ptr; if (!success) { ThrowStdException("Failed to load required function pointers from driver."); diff --git a/mssql_python/pybind/ddbc_bindings.h b/mssql_python/pybind/ddbc_bindings.h index 2ae13459..521a007b 100644 --- a/mssql_python/pybind/ddbc_bindings.h +++ b/mssql_python/pybind/ddbc_bindings.h @@ -211,6 +211,8 @@ typedef SQLRETURN (SQL_API* SQLFreeStmtFunc)(SQLHSTMT, SQLUSMALLINT); typedef SQLRETURN (SQL_API* SQLGetDiagRecFunc)(SQLSMALLINT, SQLHANDLE, SQLSMALLINT, SQLWCHAR*, SQLINTEGER*, SQLWCHAR*, SQLSMALLINT, SQLSMALLINT*); +typedef SQLRETURN (SQL_API* SQLDescribeParamFunc)(SQLHSTMT, SQLUSMALLINT, SQLSMALLINT*, SQLULEN*, SQLSMALLINT*, SQLSMALLINT*); + // DAE APIs typedef SQLRETURN (SQL_API* SQLParamDataFunc)(SQLHSTMT, SQLPOINTER*); typedef SQLRETURN (SQL_API* SQLPutDataFunc)(SQLHSTMT, SQLPOINTER, SQLLEN); @@ -257,6 +259,8 @@ extern SQLFreeStmtFunc SQLFreeStmt_ptr; // Diagnostic APIs extern SQLGetDiagRecFunc SQLGetDiagRec_ptr; +extern SQLDescribeParamFunc SQLDescribeParam_ptr; + // DAE APIs extern SQLParamDataFunc SQLParamData_ptr; extern SQLPutDataFunc SQLPutData_ptr; From a22a0380d031d13a9b99c52a5b19c6be085d31e0 Mon Sep 17 00:00:00 2001 From: gargsaumya Date: Thu, 28 Aug 2025 16:44:51 +0530 Subject: [PATCH 2/3] copilot review suggestion --- mssql_python/pybind/ddbc_bindings.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mssql_python/pybind/ddbc_bindings.cpp b/mssql_python/pybind/ddbc_bindings.cpp index 22ac1400..5b1c2fe2 100644 --- a/mssql_python/pybind/ddbc_bindings.cpp +++ b/mssql_python/pybind/ddbc_bindings.cpp @@ -214,7 +214,7 @@ std::string DescribeChar(unsigned char ch) { // Given a list of parameters and their ParamInfo, calls SQLBindParameter on each of them with // appropriate arguments SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params, - const std::vector& paramInfos, + std::vector& paramInfos, std::vector>& paramBuffers) { LOG("Starting parameter binding. Number of parameters: {}", params.size()); for (int paramIndex = 0; paramIndex < params.size(); paramIndex++) { @@ -302,7 +302,7 @@ SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params, &nullable ); if (!SQL_SUCCEEDED(rc)) { - LOG("SQLDescribeParam failed for parameter {}", paramIndex); + LOG("SQLDescribeParam failed for parameter {} with error code {}", paramIndex, rc); return rc; } sqlType = describedType; @@ -313,9 +313,9 @@ SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params, strLenOrIndPtr = AllocateParamBuffer(paramBuffers); *strLenOrIndPtr = SQL_NULL_DATA; bufferLength = 0; - const_cast(paramInfo).paramSQLType = sqlType; - const_cast(paramInfo).columnSize = columnSize; - const_cast(paramInfo).decimalDigits = decimalDigits; + paramInfo.paramSQLType = sqlType; + paramInfo.columnSize = columnSize; + paramInfo.decimalDigits = decimalDigits; break; } case SQL_C_STINYINT: From 0b7e699b80f9a929259650582232cb6b1ccb48fe Mon Sep 17 00:00:00 2001 From: gargsaumya Date: Thu, 28 Aug 2025 17:40:36 +0530 Subject: [PATCH 3/3] minor --- mssql_python/pybind/ddbc_bindings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mssql_python/pybind/ddbc_bindings.cpp b/mssql_python/pybind/ddbc_bindings.cpp index 5b1c2fe2..30cfca48 100644 --- a/mssql_python/pybind/ddbc_bindings.cpp +++ b/mssql_python/pybind/ddbc_bindings.cpp @@ -219,7 +219,7 @@ SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params, LOG("Starting parameter binding. Number of parameters: {}", params.size()); for (int paramIndex = 0; paramIndex < params.size(); paramIndex++) { const auto& param = params[paramIndex]; - const ParamInfo& paramInfo = paramInfos[paramIndex]; + ParamInfo& paramInfo = paramInfos[paramIndex]; LOG("Binding parameter {} - C Type: {}, SQL Type: {}", paramIndex, paramInfo.paramCType, paramInfo.paramSQLType); void* dataPtr = nullptr; SQLLEN bufferLength = 0; @@ -1105,7 +1105,7 @@ SQLRETURN SQLTables_wrap(SqlHandlePtr StatementHandle, // be prepared in a previous call. SQLRETURN SQLExecute_wrap(const SqlHandlePtr statementHandle, const std::wstring& query /* TODO: Use SQLTCHAR? */, - const py::list& params, const std::vector& paramInfos, + const py::list& params, std::vector& paramInfos, py::list& isStmtPrepared, const bool usePrepare = true) { LOG("Execute SQL Query - {}", query.c_str()); if (!SQLPrepare_ptr) {