Garantias do banco
O que o PostgreSQL impõe sozinho, independentemente do código que escreve nele.
O princípio é simples: o que pode ser uma constraint vira constraint. As entities declaram PK, UNIQUE, CHECK, enums em texto, índices únicos parciais e colunas geradas; o SQL escrito à mão guarda só o que o ORM não expressa (EXCLUDE, FKs compostas que reusam colunas, FKs para domínios posteriores e triggers). Regras que dependem de várias linhas ou de contexto da requisição ficam na aplicação, e cada página de domínio diz quais são.
Em números
Contagem do banco migrado (82 tabelas, incluindo as de infraestrutura):
| Garantia | Quantidade |
|---|---|
| Chaves estrangeiras | 175 (166 nas tabelas de domínio, 69 delas compostas) |
| CHECK | 92 |
| UNIQUE (constraints) | 87 |
| Índices únicos | 17 (16 parciais) |
| EXCLUDE | 4 |
| Triggers | 22, mais 2 constraint triggers adiadas |
| Colunas geradas | 2 |
Nenhuma FK é DEFERRABLE e nenhuma tem ação em cascata. A extensão btree_gist é habilitada para os EXCLUDE.
Mesmo tenant por construção
Quando uma tabela de tenant aponta para outra, a FK inclui tenant_id dos dois lados. A tabela de destino declara
um UNIQUE "de apoio" com (id, tenant_id) (ou mais colunas) só para servir de alvo:
-- membership_teams só aceita um time e uma membership do próprio tenant
FOREIGN KEY (team_id, tenant_id) REFERENCES teams (id, tenant_id)
FOREIGN KEY (membership_id, tenant_id) REFERENCES memberships (id, tenant_id)O mesmo padrão prende outras colunas copiadas. membership_brokers.user_id é uma cópia de memberships.user_id,
presa por FOREIGN KEY (membership_id, user_id, tenant_id) REFERENCES memberships (id, user_id, tenant_id); o perfil
de um vínculo com corretor precisa ser daquele corretor ((access_profile_id, tenant_id, broker_id)); e o perfil
de um vínculo de tenant precisa ter scope = 'tenant', via a coluna gerada access_profiles.scope e um CHECK no
vínculo.
Uma ativa por vez
As unicidades condicionais são índices únicos parciais: valem só para as linhas abertas. Exemplos:
| Índice | Garante |
|---|---|
memberships_live_key | uma membership ativa por (tenant, usuário); quem foi removido pode voltar |
membership_brokers_live_key | um vínculo aberto por (membership, corretor) |
platform_grants_live_key | um grant de plataforma ativo por usuário |
credential_versions_one_active_key | uma versão ativa por credencial |
bank_assignments_one_open_key | uma atribuição aberta por usuário bancário |
user_logins_live_key | um identificador ativo por (tenant, tipo, valor normalizado) |
O índice único parcial impede duas linhas abertas, mas não impede períodos fechados sobrepostos. Onde isso importa, o banco usa EXCLUDE: ver Integridade temporal.
Páginas
- Integridade temporal: vigências
[início, fim)e os 4 EXCLUDE. - Teto de escopo das permissões: triggers, locks e a convenção de SQL congelado.
- Chaves identity: PKs
GENERATED ALWAYS AS IDENTITY. - Auditoria das execuções: a trilha permanente de tasks e eventos.