Skip to content

connect does not honor timeout parameter #725

Description

Describe the bug

The docstring on db_connection.connect says: timeout (int): The timeout for the connection attempt, in seconds.. However, it gets mixed up down the line with the query timeout.

sqlstate = 'HYT00'
ddbc_error = '[Microsoft][ODBC Driver 18 for SQL Server]Query timeout expired'

    def raise_exception(sqlstate: str, ddbc_error: str) -> None:
        """
        Raise a custom exception based on the given SQLSTATE code.
        This function raises a custom exception based on the provided SQLSTATE code.
        If the code is not found in the mapping, a generic DatabaseError is raised.
    
        Args:
            sqlstate (str): The SQLSTATE code to map to a custom exception.
            ddbc_error (str): The DDBC error message.
    
        Raises:
            DatabaseError: If the SQLSTATE code is not found in the mapping.
        """
        exception_class = sqlstate_to_exception(sqlstate, ddbc_error)
        if exception_class:
            logger.error(f"Raising exception: {exception_class}")
>           raise exception_class
E           mssql_python.exceptions.OperationalError: Driver Error: Timeout expired; DDBC Error: [Microsoft]Query timeout expired

To reproduce

I used the following test case to reproduce the issue

async def test_connection_timeout_repurposes_to_query_timeout(connection_string):
    conn = mssql_python.connect(connection_string, timeout=3)
    cursor = conn.cursor()
    cursor.execute("WAITFOR DELAY '00:00:10'; SELECT 1")  # times out after ~3s

    val = cursor.fetchval()
    assert 1 == val

Expected behavior

connect(timeout=3) should make the connection attempt time out after 3s.

Further technical details

Python version: 3.13
SQL Server version: Azure SQL Server
Operating system: Windows 11

Additional context
Discovered by Claude as I was migrating from pyodbc/aiodbc to mssql-python, I did A/B tests and can confirm that I believe it's an "unintended" bug in the library, hopefully I did by due diligence correctly!

Some more context from Claude

In connection.py, timeout is documented twice as a login timeout — "Login timeout in seconds. 0 means no timeout." (line 295) and "The timeout for the connection attempt, in seconds" in connect(). Its complete set of uses:

631:  self._timeout = timeout          # stored
928:  return self._timeout             # property documented as "the current query timeout"
950:  self._timeout = value            # setter: "Set the query timeout for all operations"
1464: cursor = Cursor(self, timeout=self._timeout)

It is never passed to SQL_ATTR_LOGIN_TIMEOUT. It silently becomes the query timeout. The same file's own example (line 354) shows attrs_before={ms.SQL_ATTR_LOGIN_TIMEOUT: 30} as the way to set a login timeout — so the mechanism is known, the parameter is just mis-wired.

Metadata

Metadata

Labels

triage neededFor new issues, not triaged yet.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions