db: Drizzle schemas on domain Postgres schemas, baseline migration, reorg SQL

- pgSchema tables for auth, money, house, ref, games, cs, animaldex, site, meta
  (lists excluded via schemaFilter; runtime list tables are managed by meta.*)
- drizzle/ baseline matches the reorged DB exactly (catalog diff = 0 on rehearsal)
- db/reorg: one-time SQL that moves the flat public schema (rehearsed on a prod copy)
- scripts/db-migrate.ts: migrator with --init to record the baseline and a guard
  against running the baseline on a non-empty, unrecorded database
This commit is contained in:
2026-10-23 22:03:42 +00:00
parent f94e3dc8ac
commit 64fb8df262
29 changed files with 12507 additions and 189 deletions

68
db/reorg/08_verify.sql Normal file
View File

@@ -0,0 +1,68 @@
-- 08_verify.sql: hard assertions; any failure aborts the whole transaction.
DO $$
DECLARE r record; a bigint; bad text := '';
BEGIN
-- every moved table keeps its exact row count
-- expected = prod count before + rows imported from dev (cs.* only; prod cs was empty)
FOR r IN SELECT m.*, b.n + coalesce(i.n, 0) AS n FROM legacy.reorg_table_map m
JOIN legacy.reorg_counts_before b ON b.table_name = m.old_name
LEFT JOIN legacy.reorg_imports i ON i.dst = m.new_schema || '.' || m.new_name LOOP
EXECUTE format('SELECT count(*) FROM %I.%I', r.new_schema, r.new_name) INTO a;
IF a <> r.n THEN bad := bad || format('%s.%s %s<>%s; ', r.new_schema, r.new_name, a, r.n); END IF;
END LOOP;
IF bad <> '' THEN RAISE EXCEPTION 'row count mismatch: %', bad; END IF;
IF (SELECT count(*) FROM legacy.reorg_counts_before) <> 58 THEN RAISE EXCEPTION 'expected 58 source tables'; END IF;
IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public') THEN RAISE EXCEPTION 'public not empty'; END IF;
END $$;
-- lists: every legacy item is in exactly one lists.* table, except the 4 merged duplicates; done count preserved
DO $$
DECLARE t record; n bigint; total bigint := 0; done bigint := 0; d bigint;
dropped bigint := (SELECT count(*) FROM legacy.reorg_list_fixes WHERE action = 'dedupe-drop');
BEGIN
FOR t IN SELECT * FROM meta.tables WHERE kind = 'list' LOOP
EXECUTE format('SELECT count(*) FROM lists.%I', t.table_name) INTO n;
total := total + n;
IF t.has_done THEN EXECUTE format('SELECT count(*) FROM lists.%I WHERE done_at IS NOT NULL', t.table_name) INTO d; done := done + d; END IF;
END LOOP;
IF total + dropped <> (SELECT count(*) FROM legacy.list_items) THEN
RAISE EXCEPTION 'lists total % + % dropped <> legacy %', total, dropped, (SELECT count(*) FROM legacy.list_items); END IF;
-- expected done = legacy completions minus dedupe pairs where BOTH copies were done (they collapse to one)
d := (SELECT count(*) FROM legacy.list_item_completions)
- (SELECT count(*) FROM dedupe x
WHERE EXISTS (SELECT 1 FROM legacy.list_item_completions c WHERE c.item_id = x.keep_id)
AND EXISTS (SELECT 1 FROM legacy.list_item_completions c WHERE c.item_id = x.drop_id));
IF done <> d THEN RAISE EXCEPTION 'done count % <> expected %', done, d; END IF;
IF (SELECT count(*) FROM meta.tables WHERE kind = 'list') <> (SELECT count(*) FROM legacy.lists) THEN
RAISE EXCEPTION 'list count mismatch'; END IF;
RAISE NOTICE 'lists OK: % rows (+% merged dupes), % done', total, dropped, done;
END $$;
-- every table with updated_at has the trigger
DO $$
DECLARE missing text;
BEGIN
SELECT string_agg(n.nspname || '.' || c.relname, ', ') INTO missing
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attname = 'updated_at' AND NOT a.attisdropped
WHERE c.relkind = 'r' AND n.nspname NOT IN ('legacy','pg_catalog','information_schema') AND n.nspname !~ '^pg_'
AND NOT EXISTS (SELECT 1 FROM pg_trigger tg WHERE tg.tgrelid = c.oid AND tg.tgfoid = 'meta.touch_updated_at()'::regprocedure);
IF missing IS NOT NULL THEN RAISE EXCEPTION 'missing updated_at trigger: %', missing; END IF;
-- every table has a PK
SELECT string_agg(n.nspname || '.' || c.relname, ', ') INTO missing
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r' AND n.nspname NOT IN ('legacy','pg_catalog','information_schema') AND n.nspname !~ '^pg_'
AND NOT EXISTS (SELECT 1 FROM pg_constraint k WHERE k.conrelid = c.oid AND k.contype = 'p');
IF missing IS NOT NULL THEN RAISE EXCEPTION 'tables without PK: %', missing; END IF;
END $$;
-- list rows keep their original timestamps
DO $$
DECLARE t record; n bigint; bad bigint := 0;
BEGIN
FOR t IN SELECT table_name FROM meta.tables WHERE kind = 'list' LOOP
EXECUTE format('SELECT count(*) FROM lists.%I x JOIN legacy.list_items i ON i.id = x.id WHERE x.updated_at <> i.updated_at OR x.created_at <> i.created_at', t.table_name) INTO n;
bad := bad + n;
END LOOP;
IF bad > 0 THEN RAISE EXCEPTION '% list rows lost their original timestamps', bad; END IF;
END $$;