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:
68
db/reorg/08_verify.sql
Normal file
68
db/reorg/08_verify.sql
Normal 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 $$;
|
||||
Reference in New Issue
Block a user