@@ -0,0 +1,63 @@ |
| 1 | +-- SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | +-- |
| 3 | +-- PAYMENTS PRO03 — make billing_invoices polymorphic over subject. |
| 4 | +-- |
| 5 | +-- PRO02 Q1 ratified the hybrid table strategy: invoices and |
| 6 | +-- webhook-events go polymorphic (their UNIQUE indexes were already |
| 7 | +-- subject-agnostic), org_billing_states stays per-subject. After this |
| 8 | +-- migration billing_invoices carries (subject_kind, subject_id) |
| 9 | +-- alongside the legacy org_id column. |
| 10 | +-- |
| 11 | +-- **Two-step deploy.** This migration adds the new columns and |
| 12 | +-- backfills them but KEEPS org_id and its FK. A follow-up migration |
| 13 | +-- (post-PRO04 deploy) drops org_id once every call site reads from |
| 14 | +-- the polymorphic shape. Dropping it here would force a flag-day |
| 15 | +-- deploy where every reader/writer must be in lockstep with this |
| 16 | +-- migration. |
| 17 | + |
| 18 | +-- +goose Up |
| 19 | + |
| 20 | +CREATE TYPE billing_subject_kind AS ENUM ('user', 'org'); |
| 21 | + |
| 22 | +ALTER TABLE billing_invoices |
| 23 | + ADD COLUMN subject_kind billing_subject_kind, |
| 24 | + ADD COLUMN subject_id bigint; |
| 25 | + |
| 26 | +-- Backfill every existing row as an org invoice. Synchronous so the |
| 27 | +-- NOT NULL constraints below apply against a fully-populated column. |
| 28 | +UPDATE billing_invoices |
| 29 | +SET subject_kind = 'org', |
| 30 | + subject_id = org_id |
| 31 | +WHERE subject_kind IS NULL; |
| 32 | + |
| 33 | +ALTER TABLE billing_invoices |
| 34 | + ALTER COLUMN subject_kind SET NOT NULL, |
| 35 | + ALTER COLUMN subject_id SET NOT NULL; |
| 36 | + |
| 37 | +-- Cross-row consistency: the legacy org_id is preserved during the |
| 38 | +-- transitional window; while it exists, it must match subject_id when |
| 39 | +-- subject_kind='org'. Future invoice rows (PRO04+) for users carry |
| 40 | +-- org_id=NULL — relax the FK by making it nullable BEFORE this check |
| 41 | +-- so existing org rows stay valid and user rows can land. |
| 42 | +ALTER TABLE billing_invoices |
| 43 | + ALTER COLUMN org_id DROP NOT NULL; |
| 44 | + |
| 45 | +ALTER TABLE billing_invoices |
| 46 | + ADD CONSTRAINT billing_invoices_org_id_matches_subject CHECK ( |
| 47 | + org_id IS NULL |
| 48 | + OR (subject_kind = 'org' AND subject_id = org_id) |
| 49 | + ); |
| 50 | + |
| 51 | +-- New index for the polymorphic invoice-listing queries; mirrors the |
| 52 | +-- shape of the existing billing_invoices_org_created_idx. |
| 53 | +CREATE INDEX billing_invoices_subject_created_idx |
| 54 | + ON billing_invoices (subject_kind, subject_id, created_at DESC); |
| 55 | + |
| 56 | +-- +goose Down |
| 57 | + |
| 58 | +DROP INDEX IF EXISTS billing_invoices_subject_created_idx; |
| 59 | +ALTER TABLE billing_invoices DROP CONSTRAINT IF EXISTS billing_invoices_org_id_matches_subject; |
| 60 | +ALTER TABLE billing_invoices ALTER COLUMN org_id SET NOT NULL; |
| 61 | +ALTER TABLE billing_invoices DROP COLUMN IF EXISTS subject_id; |
| 62 | +ALTER TABLE billing_invoices DROP COLUMN IF EXISTS subject_kind; |
| 63 | +DROP TYPE IF EXISTS billing_subject_kind; |