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

Assignees

No one assigned

    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