Skip to content

Commit 907b364

Browse files
authored
FIX: Timeout during cursor creation and not execute (#348)
### 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#40635](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/40635) <!-- External contributors: GitHub Issue --> > GitHub Issue: #291 ------------------------------------------------------------------- ### Summary This pull request refactors how query timeouts are set for statement handles in the `mssql_python/cursor.py` module. The main improvement is moving the logic for setting the query timeout from the `execute` method to the cursor initialization process, ensuring the timeout is consistently applied whenever the statement handle is allocated or reset. Statement handle timeout management: * Introduced a new `_set_timeout` method to set the query timeout attribute on the statement handle during cursor initialization, following best practices for performance. (`mssql_python/cursor.py`) * Removed redundant timeout-setting logic from the `execute` method, centralizing timeout management in the cursor lifecycle. (`mssql_python/cursor.py`)
1 parent f119d05 commit 907b364

File tree

4 files changed

+338
-18
lines changed

4 files changed

+338
-18
lines changed

mssql_python/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ class ConstantsDDBC(Enum):
180180
# Reset Connection Constants
181181
SQL_RESET_CONNECTION_YES = 1
182182

183+
# Query Timeout Constants
184+
SQL_ATTR_QUERY_TIMEOUT = 0
185+
183186

184187
class GetInfoConstants(Enum):
185188
"""

mssql_python/cursor.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -681,13 +681,34 @@ def _initialize_cursor(self) -> None:
681681
Initialize the DDBC statement handle.
682682
"""
683683
self._allocate_statement_handle()
684+
self._set_timeout()
684685

685686
def _allocate_statement_handle(self) -> None:
686687
"""
687688
Allocate the DDBC statement handle.
688689
"""
689690
self.hstmt = self._connection._conn.alloc_statement_handle()
690691

692+
def _set_timeout(self) -> None:
693+
"""
694+
Set the query timeout attribute on the statement handle.
695+
This is called once when the cursor is created and after any handle reallocation.
696+
Following pyodbc's approach for better performance.
697+
"""
698+
if self._timeout > 0:
699+
logger.debug("_set_timeout: Setting query timeout=%d seconds", self._timeout)
700+
try:
701+
timeout_value = int(self._timeout)
702+
ret = ddbc_bindings.DDBCSQLSetStmtAttr(
703+
self.hstmt,
704+
ddbc_sql_const.SQL_ATTR_QUERY_TIMEOUT.value,
705+
timeout_value,
706+
)
707+
check_error(ddbc_sql_const.SQL_HANDLE_STMT.value, self.hstmt, ret)
708+
logger.debug("Query timeout set to %d seconds", timeout_value)
709+
except Exception as e: # pylint: disable=broad-exception-caught
710+
logger.warning("Failed to set query timeout: %s", str(e))
711+
691712
def _reset_cursor(self) -> None:
692713
"""
693714
Reset the DDBC statement handle.
@@ -1216,20 +1237,6 @@ def execute( # pylint: disable=too-many-locals,too-many-branches,too-many-state
12161237
encoding_settings = self._get_encoding_settings()
12171238

12181239
# Apply timeout if set (non-zero)
1219-
if self._timeout > 0:
1220-
logger.debug("execute: Setting query timeout=%d seconds", self._timeout)
1221-
try:
1222-
timeout_value = int(self._timeout)
1223-
ret = ddbc_bindings.DDBCSQLSetStmtAttr(
1224-
self.hstmt,
1225-
ddbc_sql_const.SQL_ATTR_QUERY_TIMEOUT.value,
1226-
timeout_value,
1227-
)
1228-
check_error(ddbc_sql_const.SQL_HANDLE_STMT.value, self.hstmt, ret)
1229-
logger.debug("Set query timeout to %d seconds", timeout_value)
1230-
except Exception as e: # pylint: disable=broad-exception-caught
1231-
logger.warning("Failed to set query timeout: %s", str(e))
1232-
12331240
logger.debug("execute: Creating parameter type list")
12341241
param_info = ddbc_bindings.ParamInfo
12351242
parameters_type = []

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2837,7 +2837,6 @@ py::object FetchLobColumnData(SQLHSTMT hStmt, SQLUSMALLINT colIndex, SQLSMALLINT
28372837
}
28382838

28392839
// For SQL_C_CHAR data, decode using the specified encoding
2840-
// Create py::bytes once to avoid double allocation
28412840
py::bytes raw_bytes(buffer.data(), buffer.size());
28422841
try {
28432842
py::object decoded = raw_bytes.attr("decode")(charEncoding, "strict");
@@ -2916,7 +2915,6 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
29162915
if (numCharsInData < dataBuffer.size()) {
29172916
// SQLGetData will null-terminate the data
29182917
// Use Python's codec system to decode bytes with specified encoding
2919-
// Create py::bytes once to avoid double allocation
29202918
py::bytes raw_bytes(reinterpret_cast<char*>(dataBuffer.data()),
29212919
static_cast<size_t>(dataLen));
29222920
try {
@@ -4395,8 +4393,17 @@ PYBIND11_MODULE(ddbc_bindings, m) {
43954393
"Set the decimal separator character");
43964394
m.def(
43974395
"DDBCSQLSetStmtAttr",
4398-
[](SqlHandlePtr stmt, SQLINTEGER attr, SQLPOINTER value) {
4399-
return SQLSetStmtAttr_ptr(stmt->get(), attr, value, 0);
4396+
[](SqlHandlePtr stmt, SQLINTEGER attr, py::object value) {
4397+
SQLPOINTER ptr_value;
4398+
if (py::isinstance<py::int_>(value)) {
4399+
// For integer attributes like SQL_ATTR_QUERY_TIMEOUT
4400+
ptr_value =
4401+
reinterpret_cast<SQLPOINTER>(static_cast<SQLULEN>(value.cast<int64_t>()));
4402+
} else {
4403+
// For pointer attributes
4404+
ptr_value = value.cast<SQLPOINTER>();
4405+
}
4406+
return SQLSetStmtAttr_ptr(stmt->get(), attr, ptr_value, 0);
44004407
},
44014408
"Set statement attributes");
44024409
m.def("DDBCSQLGetTypeInfo", &SQLGetTypeInfo_Wrapper,

0 commit comments

Comments
 (0)