Monalisa
Garantias do banco

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):

GarantiaQuantidade
Chaves estrangeiras175 (166 nas tabelas de domínio, 69 delas compostas)
CHECK92
UNIQUE (constraints)87
Índices únicos17 (16 parciais)
EXCLUDE4
Triggers22, mais 2 constraint triggers adiadas
Colunas geradas2

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:

ÍndiceGarante
memberships_live_keyuma membership ativa por (tenant, usuário); quem foi removido pode voltar
membership_brokers_live_keyum vínculo aberto por (membership, corretor)
platform_grants_live_keyum grant de plataforma ativo por usuário
credential_versions_one_active_keyuma versão ativa por credencial
bank_assignments_one_open_keyuma atribuição aberta por usuário bancário
user_logins_live_keyum 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

Nesta página