Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mssql_python/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ class ConstantsDDBC(Enum):
# Reset Connection Constants
SQL_RESET_CONNECTION_YES = 1

# Query Timeout Constants
SQL_ATTR_QUERY_TIMEOUT = 0


class GetInfoConstants(Enum):
"""
Expand Down
35 changes: 21 additions & 14 deletions mssql_python/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,13 +681,34 @@ def _initialize_cursor(self) -> None:
Initialize the DDBC statement handle.
"""
self._allocate_statement_handle()
self._set_timeout()

def _allocate_statement_handle(self) -> None:
"""
Allocate the DDBC statement handle.
"""
self.hstmt = self._connection._conn.alloc_statement_handle()

def _set_timeout(self) -> None:
"""
Set the query timeout attribute on the statement handle.
This is called once when the cursor is created and after any handle reallocation.
Following pyodbc's approach for better performance.
"""
if self._timeout > 0:
logger.debug("_set_timeout: Setting query timeout=%d seconds", self._timeout)
try:
timeout_value = int(self._timeout)
ret = ddbc_bindings.DDBCSQLSetStmtAttr(
self.hstmt,
ddbc_sql_const.SQL_ATTR_QUERY_TIMEOUT.value,
timeout_value,
)
check_error(ddbc_sql_const.SQL_HANDLE_STMT.value, self.hstmt, ret)
logger.debug("Query timeout set to %d seconds", timeout_value)
except Exception as e: # pylint: disable=broad-exception-caught
logger.warning("Failed to set query timeout: %s", str(e))

def _reset_cursor(self) -> None:
"""
Reset the DDBC statement handle.
Expand Down Expand Up @@ -1216,20 +1237,6 @@ def execute( # pylint: disable=too-many-locals,too-many-branches,too-many-state
encoding_settings = self._get_encoding_settings()

# Apply timeout if set (non-zero)
if self._timeout > 0:
logger.debug("execute: Setting query timeout=%d seconds", self._timeout)
try:
timeout_value = int(self._timeout)
ret = ddbc_bindings.DDBCSQLSetStmtAttr(
self.hstmt,
ddbc_sql_const.SQL_ATTR_QUERY_TIMEOUT.value,
timeout_value,
)
check_error(ddbc_sql_const.SQL_HANDLE_STMT.value, self.hstmt, ret)
logger.debug("Set query timeout to %d seconds", timeout_value)
except Exception as e: # pylint: disable=broad-exception-caught
logger.warning("Failed to set query timeout: %s", str(e))

logger.debug("execute: Creating parameter type list")
param_info = ddbc_bindings.ParamInfo
parameters_type = []
Expand Down
15 changes: 11 additions & 4 deletions mssql_python/pybind/ddbc_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2837,7 +2837,6 @@ py::object FetchLobColumnData(SQLHSTMT hStmt, SQLUSMALLINT colIndex, SQLSMALLINT
}

// For SQL_C_CHAR data, decode using the specified encoding
// Create py::bytes once to avoid double allocation
py::bytes raw_bytes(buffer.data(), buffer.size());
try {
py::object decoded = raw_bytes.attr("decode")(charEncoding, "strict");
Expand Down Expand Up @@ -2916,7 +2915,6 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
if (numCharsInData < dataBuffer.size()) {
// SQLGetData will null-terminate the data
// Use Python's codec system to decode bytes with specified encoding
// Create py::bytes once to avoid double allocation
py::bytes raw_bytes(reinterpret_cast<char*>(dataBuffer.data()),
static_cast<size_t>(dataLen));
try {
Expand Down Expand Up @@ -4395,8 +4393,17 @@ PYBIND11_MODULE(ddbc_bindings, m) {
"Set the decimal separator character");
m.def(
"DDBCSQLSetStmtAttr",
[](SqlHandlePtr stmt, SQLINTEGER attr, SQLPOINTER value) {
return SQLSetStmtAttr_ptr(stmt->get(), attr, value, 0);
[](SqlHandlePtr stmt, SQLINTEGER attr, py::object value) {
SQLPOINTER ptr_value;
if (py::isinstance<py::int_>(value)) {
// For integer attributes like SQL_ATTR_QUERY_TIMEOUT
ptr_value =
reinterpret_cast<SQLPOINTER>(static_cast<SQLULEN>(value.cast<int64_t>()));
} else {
// For pointer attributes
ptr_value = value.cast<SQLPOINTER>();
}
return SQLSetStmtAttr_ptr(stmt->get(), attr, ptr_value, 0);
},
"Set statement attributes");
m.def("DDBCSQLGetTypeInfo", &SQLGetTypeInfo_Wrapper,
Expand Down
Loading
Loading