-- ============================================================================= -- F6.1 — Migração de DADOS public -> schemas tenant (cutover) -- -- ⚠️ APLICAR COMO supabase_admin (precisa SET session_replication_role=replica -- pra desabilitar checagem de FK durante o bulk insert — postgres não pode). -- -- docker exec -i -e PGPASSWORD=postgres supabase_db_agenciapsi-primesakai \ -- psql -U supabase_admin -h 127.0.0.1 -d postgres -v ON_ERROR_STOP=1 \ -- < database-novo/manual/f6_1_migrate_data.supabase_admin.sql -- -- COPIA (não move) os dados de cada tenant pras suas tabelas no schema. Os -- dados continuam em public até o DROP da F6.3. Idempotente via ON CONFLICT -- DO NOTHING (rodar de novo não duplica). -- -- * tabelas com tenant_id: INSERT ... SELECT WHERE tenant_id = , sem a -- coluna tenant_id (não existe no schema) -- * 3 filhas sem tenant_id (commitment_services, insurance_plan_services, -- recurrence_rule_services): particionadas via JOIN no pai -- * financial_categories / therapist_payout_records: 0 linhas, ignoradas -- * as 6 tabelas anon-facing (F1b) NÃO existem no schema → naturalmente fora -- * reset de sequences (4 tabelas bigserial) ao final -- ============================================================================= SET session_replication_role = replica; DO $$ DECLARE t_row record; tab record; v_cols text; v_n bigint; -- filhas sem tenant_id: tabela -> (pai, fk_local, pk_pai) child_joins jsonb := jsonb_build_object( 'commitment_services', jsonb_build_object('parent','agenda_eventos','fk','commitment_id'), 'insurance_plan_services', jsonb_build_object('parent','insurance_plans','fk','insurance_plan_id'), 'recurrence_rule_services', jsonb_build_object('parent','recurrence_rules','fk','rule_id') ); cj jsonb; BEGIN FOR t_row IN SELECT t.id AS tenant_id, ts.schema_name FROM public.tenants t JOIN public.tenant_schemas ts ON ts.tenant_id = t.id ORDER BY t.created_at, t.id LOOP FOR tab IN SELECT c.relname AS table_name FROM pg_class c WHERE c.relnamespace = t_row.schema_name::regnamespace AND c.relkind = 'r' AND c.relname NOT LIKE '\_%' ORDER BY c.relname LOOP -- pula se a tabela não existe em public (defensivo) IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema='public' AND table_name=tab.table_name) THEN CONTINUE; END IF; -- colunas presentes em AMBOS (schema e public): exclui tenant_id -- (some no schema), singleton (só no schema, fica no default) e -- colunas GENERATED (net_amount, margin_brl — não aceitam INSERT) SELECT string_agg(quote_ident(sc.column_name), ', ' ORDER BY sc.ordinal_position) INTO v_cols FROM information_schema.columns sc WHERE sc.table_schema = t_row.schema_name AND sc.table_name = tab.table_name AND sc.is_generated = 'NEVER' AND EXISTS (SELECT 1 FROM information_schema.columns pc WHERE pc.table_schema='public' AND pc.table_name=tab.table_name AND pc.column_name = sc.column_name AND pc.is_generated = 'NEVER'); IF v_cols IS NULL THEN CONTINUE; END IF; cj := child_joins -> tab.table_name; IF cj IS NOT NULL THEN -- filha sem tenant_id: particiona via JOIN no pai EXECUTE format( 'INSERT INTO %I.%I (%s) SELECT %s FROM public.%I ch ' || 'JOIN public.%I p ON p.id = ch.%I WHERE p.tenant_id = %L ' || 'ON CONFLICT DO NOTHING', t_row.schema_name, tab.table_name, v_cols, (SELECT string_agg('ch.'||quote_ident(x), ', ' ORDER BY ord) FROM (SELECT trim(both ' ' from unnest(string_to_array(v_cols, ','))) AS x, generate_subscripts(string_to_array(v_cols, ','),1) AS ord) y), tab.table_name, (cj->>'parent'), (cj->>'fk'), t_row.tenant_id ); ELSIF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name=tab.table_name AND column_name='tenant_id') THEN -- tabela com tenant_id: filtro direto EXECUTE format( 'INSERT INTO %I.%I (%s) SELECT %s FROM public.%I WHERE tenant_id = %L ON CONFLICT DO NOTHING', t_row.schema_name, tab.table_name, v_cols, v_cols, tab.table_name, t_row.tenant_id ); ELSE -- sem tenant_id e não é filha mapeada (financial_categories etc.): -- só migra se tiver 0 dependência de tenant — pula (vazias hoje) CONTINUE; END IF; GET DIAGNOSTICS v_n = ROW_COUNT; IF v_n > 0 THEN RAISE NOTICE 'F6.1 %.%: % linhas', t_row.schema_name, tab.table_name, v_n; END IF; END LOOP; END LOOP; END $$; -- --------------------------------------------------------------------------- -- Reset de sequences (tabelas bigserial) em cada schema -- --------------------------------------------------------------------------- DO $$ DECLARE t_row record; r record; v_seq text; BEGIN FOR t_row IN SELECT schema_name FROM public.tenant_schemas LOOP FOR r IN SELECT c.relname AS table_name, a.attname AS column_name FROM pg_attrdef d JOIN pg_class c ON c.oid = d.adrelid JOIN pg_attribute a ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE c.relnamespace = t_row.schema_name::regnamespace AND pg_get_expr(d.adbin, d.adrelid) LIKE 'nextval(%' LOOP v_seq := pg_get_serial_sequence(format('%I.%I', t_row.schema_name, r.table_name), r.column_name); IF v_seq IS NOT NULL THEN EXECUTE format('SELECT setval(%L, COALESCE((SELECT MAX(%I) FROM %I.%I), 0) + 1, false)', v_seq, r.column_name, t_row.schema_name, r.table_name); RAISE NOTICE 'F6.1 seq %.% -> %', t_row.schema_name, r.table_name, v_seq; END IF; END LOOP; END LOOP; END $$; SET session_replication_role = origin;