-- ============================================================================= -- F1.1 — Schema-per-tenant: coluna tenants.slug -- Plano: novo-rumo.txt + docs/F0_categorizacao.md (decisão Q1) -- Slug é a base do nome do schema físico: tenant_. Unico e imutável. -- ============================================================================= BEGIN; ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS slug text; -- Geração de slug a partir do nome (sanitizado pra identificador Postgres) CREATE OR REPLACE FUNCTION public.generate_tenant_slug(p_name text) RETURNS text LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public', 'pg_temp' AS $$ DECLARE base text; cand text; n int := 1; BEGIN base := lower(coalesce(nullif(trim(p_name), ''), 'tenant')); base := translate(base, 'áàâãäåéèêëíìîïóòôõöúùûüçñýÿ', 'aaaaaaeeeeiiiiooooouuuucnyy'); base := regexp_replace(base, '[^a-z0-9]+', '_', 'g'); base := regexp_replace(base, '^_+|_+$', '', 'g'); base := left(base, 48); IF base = '' OR base !~ '^[a-z]' THEN base := 't_' || base; base := left(base, 48); END IF; cand := base; WHILE EXISTS (SELECT 1 FROM public.tenants WHERE slug = cand) LOOP n := n + 1; cand := left(base, 44) || '_' || n; END LOOP; RETURN cand; END; $$; -- Backfill dos tenants existentes DO $$ DECLARE r record; BEGIN FOR r IN SELECT id, name FROM public.tenants WHERE slug IS NULL ORDER BY created_at, id LOOP UPDATE public.tenants SET slug = public.generate_tenant_slug(r.name) WHERE id = r.id; RAISE NOTICE 'tenant % -> slug %', r.id, (SELECT slug FROM public.tenants WHERE id = r.id); END LOOP; END $$; ALTER TABLE public.tenants ALTER COLUMN slug SET NOT NULL; ALTER TABLE public.tenants ADD CONSTRAINT tenants_slug_key UNIQUE (slug); ALTER TABLE public.tenants ADD CONSTRAINT tenants_slug_format CHECK (slug ~ '^[a-z][a-z0-9_]{1,47}$'); -- Auto-gera no INSERT (provisionamento atual não conhece slug); imutável no UPDATE CREATE OR REPLACE FUNCTION public.trg_tenants_slug() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public', 'pg_temp' AS $$ BEGIN IF TG_OP = 'INSERT' THEN IF NEW.slug IS NULL OR trim(NEW.slug) = '' THEN NEW.slug := public.generate_tenant_slug(NEW.name); END IF; RETURN NEW; END IF; IF NEW.slug IS DISTINCT FROM OLD.slug THEN RAISE EXCEPTION 'tenants.slug é imutável (tenant %, % -> %)', OLD.id, OLD.slug, NEW.slug; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_tenants_slug_ins ON public.tenants; CREATE TRIGGER trg_tenants_slug_ins BEFORE INSERT ON public.tenants FOR EACH ROW EXECUTE FUNCTION public.trg_tenants_slug(); DROP TRIGGER IF EXISTS trg_tenants_slug_upd ON public.tenants; CREATE TRIGGER trg_tenants_slug_upd BEFORE UPDATE OF slug ON public.tenants FOR EACH ROW EXECUTE FUNCTION public.trg_tenants_slug(); COMMIT;