-- 02_schemas_move.sql: create schemas, snapshot counts, move + rename all 58 public tables. SET ROLE personal; CREATE SCHEMA auth; CREATE SCHEMA money; CREATE SCHEMA house; CREATE SCHEMA ref; CREATE SCHEMA games; CREATE SCHEMA cs; CREATE SCHEMA animaldex; CREATE SCHEMA site; CREATE SCHEMA meta; CREATE SCHEMA hardware; CREATE SCHEMA energy; CREATE SCHEMA legacy; COMMENT ON SCHEMA legacy IS 'Pre-reorg list tables + reorg bookkeeping. Drop 30 days after cutover.'; COMMENT ON SCHEMA ref IS 'Seeded third-party catalog data (Pokemon, CoD tracker definitions). Read-only in the GUI; seeders own it.'; COMMENT ON SCHEMA games IS 'Stan''s own game progress.'; -- exact row counts of every public table before anything moves CREATE TABLE legacy.reorg_counts_before (table_name text PRIMARY KEY, n bigint NOT NULL); DO $$ DECLARE t text; c bigint; BEGIN FOR t IN SELECT tablename FROM pg_tables WHERE schemaname = 'public' LOOP EXECUTE format('SELECT count(*) FROM public.%I', t) INTO c; INSERT INTO legacy.reorg_counts_before VALUES (t, c); END LOOP; END $$; CREATE TABLE legacy.reorg_table_map ( old_name text PRIMARY KEY, old_prefix text NOT NULL, -- prefix used by its index/constraint/sequence names new_schema text NOT NULL, new_name text NOT NULL, UNIQUE (new_schema, new_name) ); INSERT INTO legacy.reorg_table_map (old_name, old_prefix, new_schema, new_name) VALUES ('user','user','auth','user'), ('session','session','auth','session'), ('account','account','auth','account'), ('verification','verification','auth','verification'), ('money_accounts','money_accounts','money','accounts'), ('money_transactions','money_transactions','money','transactions'), ('money_recurring','money_recurring','money','recurring'), ('money_forecasts','money_forecasts','money','forecasts'), ('money_insurances','money_insurances','money','insurances'), ('money_wishlist','money_wishlist','money','wishlist'), ('money_projection_settings','money_projection_settings','money','projection_settings'), ('money_utility_settings','money_utility_settings','money','utility_settings'), ('money_utility_readings','money_utility_readings','money','utility_readings'), ('house_listings','house_listings','house','listings'), ('house_price_history','house_price_history','house','price_history'), ('house_searches','house_searches','house','searches'), ('house_worker_runs','house_worker_runs','house','worker_runs'), ('house_alert_rules','house_alert_rules','house','alert_rules'), ('pokemon','pokemon','ref','pokemon'), ('games','game','ref','pokemon_games'), ('pokemon_game_entries','pokemon_game_entry','ref','pokemon_game_entries'), ('pokemon_locations','pokemon_locations','ref','pokemon_locations'), ('pokemon_encounters','pokemon_encounters','ref','pokemon_encounters'), ('pokemon_evolutions','pokemon_evolutions','ref','pokemon_evolutions'), ('pokemon_dex_numbers','pokemon_dex_numbers','ref','pokemon_dex_numbers'), ('pokemon_moves','pokemon_moves','ref','pokemon_moves'), ('pokemon_learnsets','pokemon_learnsets','ref','pokemon_learnsets'), ('pokemon_research','pokemon_research','ref','pokemon_research'), ('pokemon_game_research','pokemon_game_research','ref','pokemon_game_research'), ('tracker_games','tracker_games','ref','tracker_games'), ('tracker_items','tracker_items','ref','tracker_items'), ('tracker_challenges','tracker_challenges','ref','tracker_challenges'), ('user_games','user_game','games','catch_em_all_games'), ('user_entries','user_entry','games','catch_em_all_entries'), ('catch_em_all_settings','catch_em_all_settings','games','catch_em_all_settings'), ('pokemon_journeys','pokemon_journeys','games','pokemon_journeys'), ('pokemon_seen','pokemon_seen','games','pokemon_seen'), ('pokemon_catches','pokemon_catches','games','pokemon_catches'), ('pokemon_milestones','pokemon_milestones','games','pokemon_milestones'), ('tracker_progress','tracker_progress','games','tracker_progress'), ('tracker_ranks','tracker_ranks','games','tracker_ranks'), ('tracker_weapon_ranks','tracker_weapon_ranks','games','tracker_weapon_ranks'), ('tracker_challenge_progress','tracker_challenge_progress','games','tracker_challenge_progress'), ('osu_entries','osu_entries','games','osu_entries'), ('cs_market_items','cs_market_items','cs','items'), ('cs_price_observations','cs_price_observations','cs','price_observations'), ('cs_item_dimensions','cs_item_dimensions','cs','item_dimensions'), ('cs_price_streams','cs_price_streams','cs','price_streams'), ('animals','animals','animaldex','animals'), ('sightings','sightings','animaldex','sightings'), ('photos','photos','animaldex','sighting_photos'), ('blog_articles','blog_articles','site','blog_articles'), ('tasks','tasks','site','tasks'), ('topics','topics','site','topics'), ('tasks_to_topics','tasks_to_topics','site','task_topics'), ('lists','lists','legacy','lists'), ('list_items','list_items','legacy','list_items'), ('list_item_completions','list_item_completions','legacy','list_item_completions'); DO $$ DECLARE missing text; BEGIN SELECT string_agg(tablename, ', ') INTO missing FROM pg_tables WHERE schemaname = 'public' AND tablename NOT IN (SELECT old_name FROM legacy.reorg_table_map); IF missing IS NOT NULL THEN RAISE EXCEPTION 'unmapped public tables: %', missing; END IF; END $$; -- types + shared trigger function ALTER TYPE public.availability SET SCHEMA ref; ALTER TYPE public.blog_article_status SET SCHEMA site; ALTER FUNCTION public.site_touch_updated_at() SET SCHEMA meta; ALTER FUNCTION meta.site_touch_updated_at() RENAME TO touch_updated_at; -- Duplicate indexes (hand SQL + Drizzle both created one). Keep Drizzle's `_index` / constraint-backed `_key`. DROP INDEX public.games_source_id_unique; -- dup of constraint games_source_id_key DROP INDEX public.pokemon_number_unique; -- dup of constraint pokemon_number_key DROP INDEX public.catch_em_all_settings_user_id_unique;-- dup of constraint ..._user_id_key DROP INDEX public.money_accounts_user_id_idx; DROP INDEX public.money_forecasts_user_date_idx; DROP INDEX public.money_insurances_user_id_idx; DROP INDEX public.money_recurring_user_id_idx; DROP INDEX public.money_transactions_account_id_idx; DROP INDEX public.money_transactions_user_date_idx; DROP INDEX public.money_wishlist_user_id_idx; DROP INDEX public.lists_slug_unique; -- dup of lists_slug_key (legacy table, tidy anyway) -- Move + rename tables; rename their constraints, indexes and owned sequences to the new prefix. CREATE FUNCTION pg_temp.reprefix(name text, old_name text, old_prefix text, new_name text) RETURNS text LANGUAGE sql IMMUTABLE AS $$ SELECT CASE WHEN name LIKE old_name || '\_%' THEN new_name || substr(name, length(old_name) + 1) WHEN name LIKE old_prefix || '\_%' THEN new_name || substr(name, length(old_prefix) + 1) ELSE NULL END $$; DO $$ DECLARE m record; r record; tbl regclass; newn text; BEGIN FOR m IN SELECT * FROM legacy.reorg_table_map ORDER BY old_name LOOP EXECUTE format('ALTER TABLE public.%I SET SCHEMA %I', m.old_name, m.new_schema); IF m.old_name <> m.new_name THEN EXECUTE format('ALTER TABLE %I.%I RENAME TO %I', m.new_schema, m.old_name, m.new_name); END IF; tbl := format('%I.%I', m.new_schema, m.new_name)::regclass; -- constraints (renaming a pk/unique constraint renames its index too) FOR r IN SELECT conname AS n FROM pg_constraint WHERE conrelid = tbl LOOP newn := pg_temp.reprefix(r.n, m.old_name, m.old_prefix, m.new_name); IF newn IS NOT NULL AND newn <> r.n THEN EXECUTE format('ALTER TABLE %s RENAME CONSTRAINT %I TO %I', tbl, r.n, newn); END IF; END LOOP; -- remaining plain indexes FOR r IN SELECT i.relname AS n FROM pg_index x JOIN pg_class i ON i.oid = x.indexrelid WHERE x.indrelid = tbl AND NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conindid = x.indexrelid AND c.conrelid = tbl) LOOP newn := pg_temp.reprefix(r.n, m.old_name, m.old_prefix, m.new_name); IF newn IS NOT NULL AND newn <> r.n THEN EXECUTE format('ALTER INDEX %I.%I RENAME TO %I', m.new_schema, r.n, newn); END IF; END LOOP; -- owned sequences FOR r IN SELECT s.relname AS n FROM pg_class s JOIN pg_depend d ON d.objid = s.oid AND d.deptype IN ('a','i') WHERE s.relkind = 'S' AND d.refobjid = tbl LOOP newn := pg_temp.reprefix(r.n, m.old_name, m.old_prefix, m.new_name); IF newn IS NOT NULL AND newn <> r.n THEN EXECUTE format('ALTER SEQUENCE %I.%I RENAME TO %I', m.new_schema, r.n, newn); END IF; END LOOP; END LOOP; IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public') THEN RAISE EXCEPTION 'public still has tables'; END IF; END $$; RESET ROLE;