Skip to content

Commit 985bcee

Browse files
fix(db): add forwarder_sent.audit_log_id real UUID FK to audit_log (mig 072)
Companion to the soft-FK partial index from mig 063. Adds a proper nullable UUID column + ON DELETE SET NULL FK so the orphan-reconciler and team-deletion cascade can use a typed JOIN instead of regex-based TEXT comparison. Why a separate column: forwarder_sent.audit_id is TEXT on purpose — legacy emit sites write placeholder IDs like "reminder-<resource>-<stage>" that cannot be parsed as UUIDs. A FK on the TEXT column would reject those rows (see mig 063 for full rationale). This column is populated only when audit_id IS a real UUID; placeholder-id rows keep NULL. One-time backfill: UPDATE sets audit_log_id = audit_id::uuid for existing rows whose audit_id is UUID-shaped AND whose id still exists in audit_log (ON DELETE SET NULL already handles deletions). The mig-063 partial index keeps the scan fast even at scale. Wire-up required: the worker's event_email_forwarder must set audit_log_id on new inserts (companion PR in worker repo).
1 parent 30e1a88 commit 985bcee

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
-- 072_forwarder_sent_audit_log_fk.sql
2+
--
3+
-- Adds a real UUID foreign key column to forwarder_sent so the orphan-
4+
-- reconciler and team-deletion cascade can use a proper JOIN instead of
5+
-- regex-based TEXT comparison.
6+
--
7+
-- WHY NOT JUST A FK ON audit_id
8+
-- forwarder_sent.audit_id is TEXT on purpose: legacy emit sites write
9+
-- synthetic placeholder values ("reminder-<resource_id>-<stage>",
10+
-- "provider-<grace_id>") that cannot be parsed as UUIDs. A FK on the
11+
-- TEXT column would reject every one of those rows (see mig 063 comment
12+
-- for the full rationale). This migration adds a SEPARATE nullable UUID
13+
-- column (audit_log_id) that the worker populates only when audit_id IS
14+
-- a real UUID — old rows and placeholder-id rows keep NULL, which is safe.
15+
--
16+
-- WIRE-UP REQUIRED
17+
-- The worker's event_email_forwarder must set audit_log_id = audit_id::uuid
18+
-- when it inserts a new row whose audit_id matches the UUID regex. That
19+
-- change ships in the companion worker PR. Until then, existing rows and
20+
-- new rows from placeholder-id emitters will have audit_log_id = NULL.
21+
--
22+
-- BACKFILL
23+
-- A one-time UPDATE backfills all existing rows whose audit_id is UUID-
24+
-- shaped. This is safe: the partial index from mig 063 makes the scan
25+
-- instant; ON DELETE SET NULL means team deletion remains non-destructive.
26+
--
27+
-- ROLLBACK
28+
-- ALTER TABLE forwarder_sent DROP COLUMN IF EXISTS audit_log_id;
29+
30+
BEGIN;
31+
32+
ALTER TABLE forwarder_sent
33+
ADD COLUMN IF NOT EXISTS audit_log_id UUID
34+
REFERENCES audit_log(id) ON DELETE SET NULL;
35+
36+
-- Back-fill existing rows whose audit_id is already a UUID.
37+
-- The partial index from mig 063 keeps this UPDATE fast even on large tables.
38+
UPDATE forwarder_sent
39+
SET audit_log_id = audit_id::uuid
40+
WHERE audit_id ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
41+
AND audit_log_id IS NULL
42+
AND EXISTS (SELECT 1 FROM audit_log al WHERE al.id = audit_id::uuid);
43+
44+
CREATE INDEX IF NOT EXISTS idx_forwarder_sent_audit_log_id
45+
ON forwarder_sent (audit_log_id)
46+
WHERE audit_log_id IS NOT NULL;
47+
48+
COMMIT;

0 commit comments

Comments
 (0)