From 6b542cd03a6a5279b97205c261c9c2b38f313b5d Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sat, 13 Jun 2026 09:25:25 -0300 Subject: [PATCH] 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 --- ...5_pgrst_refresh_schemas.supabase_admin.sql | 57 +++++++++++++++++++ ...0260613000002_f5_pgrst_schemas_trigger.sql | 45 +++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 database-novo/manual/f5_pgrst_refresh_schemas.supabase_admin.sql create mode 100644 database-novo/migrations/20260613000002_f5_pgrst_schemas_trigger.sql diff --git a/database-novo/manual/f5_pgrst_refresh_schemas.supabase_admin.sql b/database-novo/manual/f5_pgrst_refresh_schemas.supabase_admin.sql new file mode 100644 index 0000000..1deb122 --- /dev/null +++ b/database-novo/manual/f5_pgrst_refresh_schemas.supabase_admin.sql @@ -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(); diff --git a/database-novo/migrations/20260613000002_f5_pgrst_schemas_trigger.sql b/database-novo/migrations/20260613000002_f5_pgrst_schemas_trigger.sql new file mode 100644 index 0000000..c79dd7b --- /dev/null +++ b/database-novo/migrations/20260613000002_f5_pgrst_schemas_trigger.sql @@ -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;