tursodatabase/turso · error · ProgrammingError

query timeout must be non-negative

Error message

query timeout must be non-negative

What it means

Connection.set_query_timeout(milliseconds) in pyturso sets a per-statement deadline enforced inside the engine, with 0 meaning 'disabled'. The Python wrapper checks the argument before it reaches the engine and raises ProgrammingError for any negative number. The negative value never reaches the Rust core; it is rejected at the API boundary.

Solutions

  1. Pass 0 to disable the timeout instead of a negative value
  2. Clamp computed deadlines before the call: set_query_timeout(max(0, remaining_ms))
  3. If you meant seconds, multiply by 1000 and pass a non-negative result

Example fix

// before
conn.set_query_timeout(remaining_ms)  // remaining_ms can be -500 after a slow retry

// after
conn.set_query_timeout(max(0, remaining_ms))
Defensive patterns

Strategy: validation

Validate before calling

def safe_set_query_timeout(conn, milliseconds: int) -> None:
    if milliseconds < 0:
        raise ValueError(f"timeout must be >= 0, got {milliseconds}")
    conn.set_query_timeout(milliseconds)

Try / catch

from turso.lib import ProgrammingError

try:
    conn.set_query_timeout(ms)
except ProgrammingError:
    conn.set_query_timeout(0)  # fall back to 'disabled'

Prevention

When it happens

Trigger: Calling conn.set_query_timeout(-1) directly, or passing a computed value like (deadline_ms - now_ms) that has already gone negative because the deadline is in the past. Also passing seconds where milliseconds are expected makes surprising values but only negative ones raise.

Common situations: Deadline math based on wall-clock time that expires before the call; code ported from APIs where -1 means 'no timeout'; unit confusion between seconds and milliseconds.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-20). Data as JSON: /api/errors/29822dbf469dbaaf. Report an issue: GitHub.

Appendix: source

Thrown at bindings/python/turso/lib.py:556

        ``OperationalError`` ("interrupted"). The underlying execution methods
        release the GIL, so a watchdog thread can actually run while a query is
        busy. If no statement is running the call is a no-op.
        """
        try:
            self._conn.interrupt()
        except Exception as exc:  # noqa: BLE001
            raise _map_turso_exception(exc)

    def set_query_timeout(self, milliseconds: int) -> None:
        """
        Set the maximum time (in milliseconds) a single statement may run before
        it is interrupted (raising ``OperationalError``). ``0`` disables the
        timeout. Unlike ``interrupt()`` this needs no watchdog thread: the
        deadline is enforced inside the engine. Turso extension (not in stdlib
        ``sqlite3``).
        """
        if milliseconds < 0:
            raise ProgrammingError("query timeout must be non-negative")
        try:
            self._conn.set_query_timeout(milliseconds)
        except Exception as exc:  # noqa: BLE001
            raise _map_turso_exception(exc)

    def get_query_timeout(self) -> int:
        """Return the current per-statement query timeout in milliseconds (``0`` = disabled)."""
        try:
            return self._conn.get_query_timeout()
        except Exception as exc:  # noqa: BLE001
            raise _map_turso_exception(exc)

    def _maybe_implicit_begin(self, sql: str) -> None:
        """
        Implement sqlite3 legacy implicit transaction behavior:

        If autocommit is LEGACY_TRANSACTION_CONTROL, isolation_level is not None, sql is a DML
        (INSERT/UPDATE/DELETE/REPLACE), and there is no open transaction, issue:

View on GitHub (pinned to c1e5928725)