Summary
The Spanner DBAPI layer (spanner_dbapi) does not pass the timeout parameter when calling _SnapshotBase.execute_sql(), causing all queries to use the gRPC default timeout of 3600 seconds. This prevents DBAPI consumers (SQLAlchemy, Django, raw DBAPI) from controlling per-statement gRPC deadlines.
Background
_SnapshotBase.execute_sql() accepts a timeout parameter that controls the gRPC deadline for the ExecuteStreamingSql RPC. When not provided, it defaults to gapic_v1.method.DEFAULT, which resolves to default_timeout=3600.0 in the gRPC transport layer.
The DBAPI calls execute_sql() in three code paths, none of which pass timeout=:
- Snapshot reads — cursor._handle_DQL_with_snapshot() calls snapshot.execute_sql(sql, params, param_types, request_options=...) without timeout=
- Transaction reads/writes — connection.run_statement() calls transaction.execute_sql(sql, params, param_types=..., request_options=...) without timeout=
- Autocommit DML — cursor._do_execute_update_in_autocommit() calls transaction.execute_sql(sql, params=..., param_types=..., last_statement=True) without timeout=
Since timeout defaults to gapic_v1.method.DEFAULT, which resolves to default_timeout=3600.0 in the gRPC transport layer (services/spanner/transports/base.py), all DBAPI queries have a 3600-second gRPC deadline regardless of the caller's intent.
Timeline
Other execution parameters (request_options, request_priority, transaction_tag, request_tag) were each wired through incrementally via the same pattern: a Connection property plus pass-through in cursor methods. The timeout parameter was not included in any of these additions.
Proposed Change
Add a timeout property to Connection, following the same pattern used for staleness, read_only, and request_priority. Pass timeout= in the three cursor/connection methods that call execute_sql().
Files changed
- connection.py — Add self._timeout = None to __init__, add timeout property/setter, pass timeout= in run_statement()
- cursor.py — Pass timeout= in _handle_DQL_with_snapshot() and _do_execute_update_in_autocommit()
Usage
from google.cloud.spanner_dbapi import connect
conn = connect(instance_id, database_id, project=project)
conn.timeout = 60 # 60-second gRPC deadline for subsequent statements
cursor = conn.cursor()
cursor.execute("SELECT * FROM my_table")
This also enables framework integration. The companion change in python-spanner-sqlalchemy can wire this through execution_options:
engine.execution_options(timeout=60)
Related
Summary
The Spanner DBAPI layer (spanner_dbapi) does not pass the timeout parameter when calling _SnapshotBase.execute_sql(), causing all queries to use the gRPC default timeout of 3600 seconds. This prevents DBAPI consumers (SQLAlchemy, Django, raw DBAPI) from controlling per-statement gRPC deadlines.
Background
_SnapshotBase.execute_sql() accepts a timeout parameter that controls the gRPC deadline for the ExecuteStreamingSql RPC. When not provided, it defaults to gapic_v1.method.DEFAULT, which resolves to default_timeout=3600.0 in the gRPC transport layer.
The DBAPI calls execute_sql() in three code paths, none of which pass timeout=:
Since timeout defaults to gapic_v1.method.DEFAULT, which resolves to default_timeout=3600.0 in the gRPC transport layer (services/spanner/transports/base.py), all DBAPI queries have a 3600-second gRPC deadline regardless of the caller's intent.
Timeline
Other execution parameters (request_options, request_priority, transaction_tag, request_tag) were each wired through incrementally via the same pattern: a Connection property plus pass-through in cursor methods. The timeout parameter was not included in any of these additions.
Proposed Change
Add a timeout property to Connection, following the same pattern used for staleness, read_only, and request_priority. Pass timeout= in the three cursor/connection methods that call execute_sql().
Files changed
Usage
This also enables framework integration. The companion change in python-spanner-sqlalchemy can wire this through execution_options:
Related