@@ -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; |