fix(db): 禁止修改已发布迁移并分批兼容旧库

This commit is contained in:
yml2213
2026-11-07 17:51:34 +08:00
parent 7b68d9b60a
commit 8ef8c61862
12 changed files with 253 additions and 123 deletions
+150
View File
@@ -0,0 +1,150 @@
-- 113 迁移前置兼容修复。
--
-- 113 已经进入生产,文件必须保持字节不变。旧快照库中存在两类历史脏数据,
-- 会让原版 113 在“加新约束/唯一索引”时失败:
-- 1. sales_daily_facts 仍有旧约束不接受的渠道值;
-- 2. cost_exceptions 同一范围的旧行会生成重复 scope_key。
--
-- 本文件不是迁移,不写 schema_migrations。它只做幂等兼容准备:
-- - 113 已执行时立即跳过;
-- - 清理旧库数据,让随后执行的原版 113 可以通过。
--
-- 由 deploy/ubuntu-deploy.sh 在数据库迁移前自动执行。
DO $preflight$
DECLARE
v_has_113 BOOLEAN := FALSE;
v_sales_has_mode BOOLEAN := FALSE;
v_scope_duplicates BIGINT := 0;
v_scope_missing BIGINT := 0;
BEGIN
IF to_regclass('public.schema_migrations') IS NOT NULL THEN
EXECUTE $query$
SELECT EXISTS (
SELECT 1
FROM public.schema_migrations migration
WHERE to_jsonb(migration)->>'filename' = '113_finance_channel_cutover.sql'
OR to_jsonb(migration)->>'name' = '113_finance_channel_cutover'
)
$query$ INTO v_has_113;
END IF;
IF v_has_113 THEN
RAISE NOTICE '113 已执行,跳过前置兼容修复';
RETURN;
END IF;
IF to_regclass('public.sales_daily_facts') IS NOT NULL THEN
SELECT EXISTS (
SELECT 1
FROM pg_attribute
WHERE attrelid = 'public.sales_daily_facts'::regclass
AND attname = 'fulfillment_mode'
AND NOT attisdropped
) INTO v_sales_has_mode;
IF v_sales_has_mode THEN
EXECUTE $sql$
ALTER TABLE public.sales_daily_facts
DROP CONSTRAINT IF EXISTS chk_sales_daily_facts_fulfillment_mode
$sql$;
EXECUTE $sql$
UPDATE public.sales_daily_facts
SET fulfillment_mode = CASE
WHEN fulfillment_mode = 'legacy_platform' THEN 'archived'
ELSE 'unclassified'
END
WHERE fulfillment_mode NOT IN (
'work_order', 'affiliate_dash', 'third_party_voucher',
'manual', 'unclassified', 'archived'
)
$sql$;
-- 迁移前临时使用新旧并集的约束;原版 113 会立即替换成最终约束。
EXECUTE $sql$
ALTER TABLE public.sales_daily_facts
ADD CONSTRAINT chk_sales_daily_facts_fulfillment_mode
CHECK (
fulfillment_mode IN (
'work_order', 'affiliate_dash', 'third_party_voucher',
'manual', 'unclassified', 'archived'
)
)
$sql$;
RAISE NOTICE 'sales_daily_facts 历史渠道值已兼容归一';
END IF;
END IF;
IF to_regclass('public.cost_exceptions') IS NOT NULL THEN
EXECUTE $sql$
ALTER TABLE public.cost_exceptions
ADD COLUMN IF NOT EXISTS scope_key TEXT,
ADD COLUMN IF NOT EXISTS item_count INTEGER NOT NULL DEFAULT 1,
ADD COLUMN IF NOT EXISTS first_seen_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS last_seen_at TIMESTAMPTZ
$sql$;
EXECUTE $sql$
WITH duplicate_scope_keys AS (
SELECT scope_key
FROM public.cost_exceptions
WHERE status <> 'resolved'
AND NULLIF(BTRIM(scope_key), '') IS NOT NULL
GROUP BY scope_key
HAVING COUNT(*) > 1
)
UPDATE public.cost_exceptions ce
SET scope_key =
COALESCE(NULLIF(BTRIM(ce.reason), ''), 'legacy')
|| '|'
|| COALESCE(ce.order_item_id::text, ce.subject_id::text, ce.id::text)
|| '|legacy:'
|| ce.id,
first_seen_at = COALESCE(ce.first_seen_at, ce.created_at),
last_seen_at = COALESCE(ce.last_seen_at, ce.created_at)
WHERE ce.scope_key IS NULL
OR BTRIM(ce.scope_key) = ''
OR ce.first_seen_at IS NULL
OR ce.last_seen_at IS NULL
OR (
ce.status <> 'resolved'
AND ce.scope_key IN (SELECT scope_key FROM duplicate_scope_keys)
)
$sql$;
EXECUTE $sql$
SELECT COUNT(*)
FROM (
SELECT scope_key
FROM public.cost_exceptions
WHERE status <> 'resolved'
AND NULLIF(BTRIM(scope_key), '') IS NOT NULL
GROUP BY scope_key
HAVING COUNT(*) > 1
) duplicate_scope_keys
$sql$ INTO v_scope_duplicates;
EXECUTE $sql$
SELECT COUNT(*)
FROM public.cost_exceptions
WHERE scope_key IS NULL OR BTRIM(scope_key) = ''
$sql$ INTO v_scope_missing;
IF v_scope_duplicates > 0 THEN
RAISE EXCEPTION
'cost_exceptions 仍有 % 组重复 scope_key,113 无法创建唯一索引',
v_scope_duplicates;
END IF;
IF v_scope_missing > 0 THEN
RAISE EXCEPTION
'cost_exceptions 仍有 % 行 scope_key 为空,113 的唯一索引前置数据不完整',
v_scope_missing;
END IF;
RAISE NOTICE 'cost_exceptions 历史 scope_key 已保证唯一';
END IF;
END
$preflight$;