Skip to content
Merged
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
6 changes: 6 additions & 0 deletions migrations/0203_decision_ledger_anchors_row_hash_index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- #9489: anchorBackendsMissingForRowHash queries
-- `SELECT DISTINCT backend FROM decision_ledger_anchors WHERE row_hash = ? AND status = 'ok' AND backend IN (...)`
-- once per row being re-anchored; without an index on (row_hash, status) that is a full table scan per call.
-- Lead with row_hash (the equality predicate) and include status so the `status = 'ok'` filter is served from
-- the index rather than a row fetch. Sibling precedent: decision_ledger_record_id in migrations/0198.
CREATE INDEX IF NOT EXISTS decision_ledger_anchors_row_hash_status ON decision_ledger_anchors (row_hash, status);
18 changes: 18 additions & 0 deletions test/unit/ledger-anchor-persistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,22 @@ describe("anchorBackendsMissingForRowHash (#9489)", () => {
expect(await anchorBackendsMissingForRowHash(env, "hash-a", [])).toEqual([]);
expect(spy).not.toHaveBeenCalled();
});

it("is served by the (row_hash, status) index rather than a full table scan (#9652)", async () => {
const env = createTestEnv();
const idx = await env.DB.prepare("SELECT name FROM sqlite_master WHERE type='index' AND name = ?")
.bind("decision_ledger_anchors_row_hash_status")
.first<{ name: string }>();
expect(idx?.name).toBe("decision_ledger_anchors_row_hash_status");

// The exact predicate anchorBackendsMissingForRowHash runs — must SEARCH via the new index, not SCAN.
const plan = await env.DB.prepare(
"EXPLAIN QUERY PLAN SELECT DISTINCT backend FROM decision_ledger_anchors WHERE row_hash = ? AND status = 'ok' AND backend IN ('rekor', 'git')",
)
.bind("hash-a")
.all<{ detail: string }>();
const detail = (plan.results ?? []).map((row) => row.detail).join(" ");
expect(detail).toContain("decision_ledger_anchors_row_hash_status");
expect(detail).not.toContain("SCAN decision_ledger_anchors ");
});
});