Files
agenciapsilmno/database-novo/manual/f6_1_migrate_data.supabase_admin.sql
T
Leonardo 003f2eb2a6 F6.0+F6.1 schema-per-tenant: clones dos 9 tenants + migracao de dados
- F6.0 (migration): clone_tenant_template pros 9 tenants existentes (schemas
  vazios; dispara trigger F5 -> expostos no PostgREST)
- F6.1 (manual supabase_admin): copia dados public -> schemas com
  session_replication_role=replica (desabilita FK check, so supabase_admin).
  Tabelas com tenant_id por filtro; 3 filhas sem tenant_id (commitment_services,
  insurance_plan_services, recurrence_rule_services) por JOIN no pai; exclui
  colunas GENERATED (net_amount, margin_brl); reset de 4 sequences; ON CONFLICT
  DO NOTHING (idempotente). Dados continuam em public (DROP so na F6.3)
- Verificado: contagens public vs schemas batem (35 patients, 37 eventos,
  355 mensagens, 54 financeiro...); seeds default replicados por schema

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 09:41:03 -03:00

141 lines
6.5 KiB
SQL

-- =============================================================================
-- 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 = <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;