# Spec 12 — PostgreSQL 18

> **Dono:** Core/Persistência · **Status:** Vigente · **Plano:** 08
> **Relaciona:** ADR-007, ADR-017, ADR-029, ADR-031
> **Objetivo:** contrato do banco — **PostgreSQL 18 é o único alvo** (container, sem porta exposta),
> tipos/convenções, recursos permitidos, migrações seguras e tuning; **sem branching de driver**.

## 1. Alvo único

- **`postgres:18-trixie`** pinado por **digest** (ADR-031), **um container por instância**, volume
  próprio, **sem porta publicada** (só a rede interna do Compose). Acesso via
  `docker compose exec postgres psql` (o `php-fpm` não traz o cliente `psql`).
- **PostgreSQL é o único alvo** (ADR-007). Migrations e DDL são **PG puro**: **nada** de
  `$isPgsql`/`getDriverName`/`if (driver === ...)`. Branching de driver nunca é testado e mascara
  bugs — foi deliberadamente removido (`decision_00a4mc8g`).
- `config/database.php` expõe `'default' => env('DB_CONNECTION', 'pgsql')` com **fallback `pgsql`**.
  Existe uma conexão **`mysql` excepcional** para um caso **pontual** de cliente, adaptado sob
  medida e **sem interferência no fluxo principal** (nunca no caminho crítico nem nos testes).
- `config/queue.php` (batching/failed) e o restante do projeto usam o mesmo `DB_CONNECTION` com
  fallback `pgsql`.
- **Ambiente de desenvolvimento e produção rodam o mesmo PG 18**; tudo é testado nele
  (`concordia_test` no `migrate:fresh`). Não há suíte em outro SGBD.

## 2. Tipos e convenções

| Uso | Tipo/decisão |
|---|---|
| PK | `uuid` com `uuidv7()` **nativo** (`DEFAULT uuidv7()`) |
| Instantes | `timestamptz` (`timestampsTz`, `softDeletesTz`) |
| Dinheiro | `numeric(p,s)` |
| Texto curto/longo | `text` + `CHECK`/validação na app (**não** `varchar(n)`) |
| Conteúdo/metadados/apresentação | `jsonb` (nunca HTML/CSS — spec 10 §1) |
| Nulabilidade | `NOT NULL` por padrão; `NULL` só quando significativo |
| FK | `onDelete` explícito (`nullOnDelete`/`cascade`/`restrict`) |
| Índices | nomeados `tabela_coluna_idx`; parciais quando úteis |

## 3. Recursos do PG 18 permitidos

- `uuidv7()` nativo — PK gerada pelo banco, dispensa geração na app.
- **Colunas geradas** `GENERATED ... STORED` quando eliminam código de sincronização
  (preferidas ao trigger para `tsvector`, spec 10 §5).
- `CREATE INDEX ... INCLUDE`, **skip scan**, **I/O assíncrono** (`io_method = worker`).
- `MERGE` **com parcimônia** (simplicidade, ADR-001).
- `ADD COLUMN ... DEFAULT` **não volátil** não reescreve a tabela.
- Extensões nativas antes de qualquer abstração própria (ADR-001).

## 4. Migrações seguras (expand & contract)

- **Alteração de coluna** em passos: `ADD COLUMN` nullable → **backfill em lotes** →
  `CHECK (... ) NOT VALID` → `VALIDATE CONSTRAINT` → `SET NOT NULL`.
- **Remoção** em 2 releases (o segundo só renomeia para `backup_*`); `down()` **nunca** dropa.
- `CREATE INDEX CONCURRENTLY` **fora** de transação; constraints/índices pesados validados com
  `EXPLAIN (ANALYZE, BUFFERS)`.
- `down()` renomeia tabela/coluna para `backup_<nome>_<timestamp>` (spec 10 §2).
- Segurança de transação: `BEGIN` + `SET lock_timeout = '3s'` + log estruturado + `rollback` em erro.

## 5. Tuning do container

Arquivo `docker/postgres/postgresql.conf` montado **read-only**; dimensionado por ambiente.

| Parâmetro | Valor-base (dev/1 instância) |
|---|---|
| `shared_buffers` | `256MB` |
| `effective_cache_size` | `768MB` |
| `work_mem` | `8MB` |
| `maintenance_work_mem` | `64MB` |
| `random_page_cost` | `1.1` (SSD/NVMe) |
| `effective_io_concurrency` | `200` |
| `io_method` | `worker` (I/O assíncrono PG 18) |
| `jit` | `on` |
| `track_io_timing` | `on` |
| `log_min_duration_statement` | `500ms` |

Produção ajusta pelos recursos do host (orçamento); dev usa valores conservadores. `timezone`
explícito (armazenamento sempre `timestamptz`/UTC na app).

## 6. Observabilidade do banco (ADR-024)

- `pg_stat_statements` (via `shared_preload_libraries`) + `auto_explain` (dev/debug).
- `EXPLAIN (ANALYZE, BUFFERS)` para validar índices; timeouts/erros logam em JSON.

## 7. Segurança e privilégio mínimo

- Papéis de banco com **privilégio mínimo** (app não é superuser; sem `CREATEDB`/`SUPERUSER`).
- Credenciais só por `.env` (nunca no repo); TLS `DB_SSLMODE` conforme topologia.
- Auditoria imutável por trigger particionada (spec 10 §7); `backup_*` preserva histórico.

## 8. Verificação

- `SELECT version();` = PostgreSQL 18.x; `SELECT uuidv7();` funciona.
- `ss -ltn` no host **não** expõe a porta do banco; `make exec cmd='php artisan migrate'` roda no container.
- Nenhuma migration usa sintaxe inválida para o PG 18 (`tools/migration-check`).
- `grep -rn "isPgsql\|getDriverName" src/ database/` = **vazio**.
- `sql` validado com `EXPLAIN (ANALYZE, BUFFERS)` quando relevante.
