Skip to content

Commit c794a8f

Browse files
committed
perf(cve): index cwe_id so CWE lookup stops scanning the catalog
cwe_lookup reports how many CVEs map to a weakness via SELECT COUNT(*) FROM cves WHERE cwe_id = ?. That column had no index, so every uncached lookup scanned the entire CVE table: several seconds per request on a cold page cache, sub-second only once warm. Response bodies are identical — this is latency only. The guard does not copy the SQL. It traces the statement count_cves_for_cwe actually issues and plans that, so it follows the query if it is ever rewritten. Verified by mutation: dropping the index and switching the predicate to LIKE each turn it red.
1 parent 5b75b14 commit c794a8f

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

app/db.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ def init_cve_db():
211211
con.execute("CREATE INDEX IF NOT EXISTS idx_cves_published ON cves(published)")
212212
con.execute("CREATE INDEX IF NOT EXISTS idx_cves_epss ON cves(epss_score)")
213213
con.execute("CREATE INDEX IF NOT EXISTS idx_cves_kev ON cves(in_kev)")
214+
con.execute("CREATE INDEX IF NOT EXISTS idx_cves_cwe ON cves(cwe_id)")
214215
# Migration: add cwes JSON-array column if missing (existing installs); backfill from cwe_id.
215216
cve_cols = {row[1] for row in con.execute("PRAGMA table_info(cves)")}
216217
if "cwes" not in cve_cols:

app/tests/test_db.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,3 +871,39 @@ def test_ip_cache_with_age_versioned(self):
871871
payload, age = result
872872
assert payload == {"x": 1}
873873
assert age >= 0
874+
875+
876+
def test_cwe_count_query_uses_index(monkeypatch):
877+
"""cwe_lookup counts CVEs per weakness; unindexed this is a full catalog scan.
878+
879+
Plans the SQL count_cves_for_cwe actually issues (captured via trace), not a
880+
copy of it, so the guard follows the query if it is ever rewritten.
881+
"""
882+
import contextlib
883+
884+
import db
885+
886+
captured: list[str] = []
887+
real_get_cve_db = db.get_cve_db
888+
889+
@contextlib.contextmanager
890+
def traced_get_cve_db():
891+
with real_get_cve_db() as con:
892+
con.set_trace_callback(captured.append)
893+
try:
894+
yield con
895+
finally:
896+
con.set_trace_callback(None)
897+
898+
monkeypatch.setattr(db, "get_cve_db", traced_get_cve_db)
899+
db.count_cves_for_cwe("CWE-79")
900+
901+
selects = [sql for sql in captured if sql.lstrip().upper().startswith("SELECT")]
902+
assert selects, f"count_cves_for_cwe issued no SELECT; captured: {captured}"
903+
sql = selects[-1]
904+
905+
with real_get_cve_db() as con:
906+
plan = con.execute("EXPLAIN QUERY PLAN " + sql, ("CWE-79",) * sql.count("?")).fetchall()
907+
detail = " ".join(str(row[-1]) for row in plan)
908+
assert "SCAN cves" not in detail, f"cwe_id count fell back to a full scan: {detail}"
909+
assert "idx_cves_cwe" in detail, f"expected idx_cves_cwe in plan, got: {detail}"

0 commit comments

Comments
 (0)