Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions src/test/test_flybase_stocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ def test_nonexistent_name(self):
assert result["match_type"] == "NOT_FOUND"


class TestConnectionStatementTimeout:
@pytest.mark.integration
def test_requested_timeout_is_applied(self):
# chado.flybase.org ignores a startup "-c statement_timeout" option and
# keeps the role's 120s, so check the session actually got ours, and
# that it survives a rollback.
from vfbquery.flybase_db import get_connection
conn = get_connection(statement_timeout_ms=45000)
try:
assert conn.execute("SHOW statement_timeout").fetchone()[0] == "45s"
conn.rollback()
assert conn.execute("SHOW statement_timeout").fetchone()[0] == "45s"
finally:
conn.close()


# ---------------------------------------------------------------------------
# find_stocks tests
# ---------------------------------------------------------------------------
Expand Down
19 changes: 15 additions & 4 deletions src/vfbquery/flybase_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,24 @@
def get_connection(statement_timeout_ms=60000):
"""Get a connection to FlyBase Chado with statement timeout.

The timeout is applied with ``set_config`` after connecting. Passing it as a
startup option (``options="-c statement_timeout=..."``) has no effect on
chado.flybase.org: sessions keep the ``chado_public`` role's 120s default,
so every caller's requested timeout was silently ignored (2026-10). The
setting is committed straight away so a later rollback can't revert it.

:param statement_timeout_ms: SQL statement timeout in milliseconds (default 60s)
:return: psycopg connection
"""
return psycopg.connect(
**FLYBASE_DB,
options=f"-c statement_timeout={statement_timeout_ms}",
)
conn = psycopg.connect(**FLYBASE_DB)
try:
conn.execute("SELECT set_config('statement_timeout', %s, false)",
(str(int(statement_timeout_ms)),))
conn.commit()
except BaseException:
conn.close()
raise
return conn


def is_connection_error(exc):
Expand Down
13 changes: 11 additions & 2 deletions src/vfbquery/flybase_stocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,25 @@ def _resolve_entity_impl(conn, name_or_id):
}

# 3. Broad ILIKE search
# A '%...%' pattern can't use the feature.name index, so filter on the
# type_id index instead. Passing the type ids as a literal array lets the
# planner use it (~1.5M rows); filtering on c.name via the join made it
# scan all ~98M feature rows — ~95s for a name with no match, past the
# server's 120s statement limit on CI (2026-10).
type_ids = _run_query(conn, """
SELECT cvterm_id FROM cvterm WHERE name = ANY(%(types)s)
""", {"types": feature_types})
df = _run_query(conn, """
SELECT DISTINCT f.name, f.uniquename, c.name AS type
FROM feature f
JOIN cvterm c ON f.type_id = c.cvterm_id
WHERE f.is_obsolete = false
AND c.name = ANY(%(types)s)
AND f.type_id = ANY(%(type_ids)s)
AND (f.name ILIKE %(pattern)s)
ORDER BY c.name, f.name
LIMIT 20
""", {"pattern": "%" + name_or_id + "%", "types": feature_types})
""", {"pattern": "%" + name_or_id + "%",
"type_ids": [] if type_ids.empty else type_ids["cvterm_id"].tolist()})

if len(df) > 0:
return {
Expand Down
Loading