F5 schema-per-tenant: PostgREST expoe schemas tenant dinamicamente

- manual/f5_pgrst_refresh_schemas.supabase_admin.sql: refresh_pgrst_schemas()
  (owned supabase_admin, postgres nao e superuser neste stack) seta
  pgrst.db_schemas in-database na role authenticator a partir de tenant_schemas
  + NOTIFY pgrst reload config/schema. Expoe/retira schema SEM restart; GUC
  persiste entre stop/start
- migration 20260613000002: trigger em tenant_schemas dispara o refresh a cada
  clone/drop (clone/drop nao precisam ser tocados)
- config.toml: baseline public,graphql_public + comentario explicando que a
  config in-db supersede em runtime
- E2E testado via HTTP: clone -> pgrst.db_schemas inclui tenant_x -> REST
  Accept-Profile retorna 200 (vs 406 schema inexistente); drop -> volta 406.
  Sem restart de container

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-06-13 09:25:25 -03:00
parent 07437c9ff4
commit 6b542cd03a
2 changed files with 102 additions and 0 deletions
@@ -0,0 +1,57 @@
-- =============================================================================
-- F5 (parte supabase_admin) — refresh dinâmico dos schemas expostos no PostgREST
--
-- ⚠️ APLICAR COMO supabase_admin (postgres NÃO é superuser neste stack e não
-- consegue ALTER ROLE authenticator). Mesmo padrão do gotcha de `documents`.
--
-- docker exec -e PGPASSWORD=postgres supabase_db_agenciapsi-primesakai \
-- psql -U supabase_admin -h 127.0.0.1 -d postgres \
-- -f /dev/stdin < database-novo/manual/f5_pgrst_refresh_schemas.supabase_admin.sql
--
-- A config in-database do PostgREST (db-config, ligada por padrão) lê
-- pgrst.db_schemas da role `authenticator`. Setar essa GUC + NOTIFY reload
-- expõe/retira schemas tenant SEM restart do container. A GUC persiste em
-- pg_db_role_setting (sobrevive a supabase stop/start).
--
-- A lista é derivada SEMPRE de public.tenant_schemas (fonte da verdade dos
-- schemas provisionados). Disparada pelo trigger em tenant_schemas (migration
-- 20260613000002) a cada clone/drop de tenant.
-- =============================================================================
CREATE OR REPLACE FUNCTION public.refresh_pgrst_schemas()
RETURNS text
LANGUAGE plpgsql
SECURITY DEFINER -- roda como o OWNER (supabase_admin/superuser)
SET search_path TO 'public', 'pg_temp'
AS $$
DECLARE
v_list text;
BEGIN
SELECT string_agg(s, ', ' ORDER BY ord, s)
INTO v_list
FROM (
SELECT 'public'::text AS s, 0 AS ord
UNION ALL SELECT 'graphql_public', 1
UNION ALL SELECT schema_name, 2 FROM public.tenant_schemas
) x;
-- baseline defensivo se a tabela ainda não existir / vazia
IF v_list IS NULL OR v_list = '' THEN
v_list := 'public, graphql_public';
END IF;
EXECUTE format('ALTER ROLE authenticator SET pgrst.db_schemas = %L', v_list);
NOTIFY pgrst, 'reload config'; -- re-lê db_schemas
NOTIFY pgrst, 'reload schema'; -- reconstrói o cache de schema
RETURN v_list;
END;
$$;
-- Garante owner superuser (caso a função já existisse owned por postgres)
ALTER FUNCTION public.refresh_pgrst_schemas() OWNER TO supabase_admin;
REVOKE ALL ON FUNCTION public.refresh_pgrst_schemas() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.refresh_pgrst_schemas() TO postgres, service_role;
-- Seta o baseline imediatamente
SELECT public.refresh_pgrst_schemas();
@@ -0,0 +1,45 @@
-- =============================================================================
-- F5 — Trigger que re-expõe schemas tenant no PostgREST a cada clone/drop
--
-- public.refresh_pgrst_schemas() (criada em manual/f5_pgrst_refresh_schemas.
-- supabase_admin.sql, owned por supabase_admin) seta pgrst.db_schemas + NOTIFY.
-- Este trigger em tenant_schemas a dispara automaticamente — clone_tenant_template
-- e drop_tenant_schema NÃO precisam ser tocados (eles inserem/removem em
-- tenant_schemas, o que aciona o refresh no COMMIT).
--
-- PRÉ-REQUISITO: aplicar f5_pgrst_refresh_schemas.supabase_admin.sql ANTES desta
-- migration (a função precisa existir e estar owned por supabase_admin).
-- =============================================================================
BEGIN;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'public' AND p.proname = 'refresh_pgrst_schemas'
) THEN
RAISE EXCEPTION 'F5: public.refresh_pgrst_schemas() não existe — aplique manual/f5_pgrst_refresh_schemas.supabase_admin.sql primeiro';
END IF;
END $$;
-- Trigger function (owned por postgres) só delega pro refresh (SECDEF supabase_admin)
CREATE OR REPLACE FUNCTION public.trg_refresh_pgrst_schemas()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public', 'pg_temp'
AS $$
BEGIN
PERFORM public.refresh_pgrst_schemas();
RETURN NULL;
END;
$$;
DROP TRIGGER IF EXISTS trg_tenant_schemas_pgrst_refresh ON public.tenant_schemas;
CREATE TRIGGER trg_tenant_schemas_pgrst_refresh
AFTER INSERT OR DELETE OR UPDATE OF schema_name ON public.tenant_schemas
FOR EACH STATEMENT
EXECUTE FUNCTION public.trg_refresh_pgrst_schemas();
COMMIT;