# PG-API
[](https://www.rust-lang.org/)
[](LICENSE.md)
[](CHANGELOG.md)
> Um driver PostgreSQL de alta performance via REST API, construído em Rust.
PG-API permite executar queries SQL, transações e gerenciar bancos de dados PostgreSQL através de uma API REST simples e segura, com autenticação via API keys, rate limiting e connection pooling.
---
## ✨ Funcionalidades
- **🔌 Query Execution** - Execute queries SQL via HTTP POST
- **📦 Batch Operations** - Múltiplas queries em uma única requisição
- **🔒 Transações** - Suporte a transações ACID
- **🔑 Autenticação** - API keys com controle de acesso granular
- **⏱️ Rate Limiting** - Limitação de requisições por conta
- **🏊 Connection Pooling** - Pool de conexões PostgreSQL eficiente\n- **🚦 Limite de concorrência** - Semáforos por conta com liberação segura
- **📊 Observabilidade** - Métricas opcionais para OpenSearch
- **📖 OpenAPI** - Documentação automática em `/docs`
---
## 🔒 Segurança (v0.3.0)
- **Fail-closed**: sem `config/accounts.json` válido o servidor não inicia (sem credenciais padrão).
- **API keys com hash SHA-256** em repouso; chaves legadas em texto puro continuam funcionando com aviso de rotação.
- **Gate SQL real**: 1 statement por request, `WITH` com escrita exige permissão de escrita, `COPY`/controle transacional/comandos de sessão e funções perigosas (`pg_read_file`, `dblink`) negados.
- **`options` aplicadas**: `statement_timeout` (default 60s, teto 300s) e `read_only`.
- **Transação real** em `/v1/transaction`; batch/transação limitados a 50 itens.
- **Sessões transacionais** em `/v1/transaction/sessions` para operações
cross-request com snapshot estável e commit/rollback explícitos; sessões
abandonadas expiram após 15 minutos.
- **CORS e limites** (`tamanho do body`, timeout) lidos do `server.json`; rode atrás de TLS (ex.: nginx).
⬆️ **Migrando de 0.3.7**: sessões transacionais persistentes, níveis de isolamento explícitos, disposable databases autorizados, backup remoto, blocos DO em migrations, healing de conexões e diagnóstico de erros PostgreSQL preservado. Detalhes no [CHANGELOG](CHANGELOG.md).
⬆️ **Migrando de 0.2.x**: garanta que `accounts.json` existe, rode com a mesma config, troque `PG_API_KEY` quando puder e trate os novos códigos `MULTI_STATEMENT_NOT_ALLOWED`, `STATEMENT_NOT_ALLOWED` e `STATEMENT_TIMEOUT`. Detalhes no [CHANGELOG](CHANGELOG.md).
---
## 🚀 Quick Start
### 1. Instalação
```bash
# Clone o repositório
git clone https://gitlab.com/aerunti/pg-api.git
cd pg-api
# Build do projeto
cargo build --release
```
### 2. Configuração
```bash
# Crie a configuração de contas
mkdir -p config
cat > config/accounts.json << 'EOF'
{
"accounts": [
{
"id": "acc_001",
"name": "Default Account",
"api_keys": ["sk_test_123456789"],
"role": "owner",
"rate_limit": 1000,
"max_connections": 50,
"databases": [
{
"database": "postgres",
"username": "postgres",
"password": "postgres",
"permissions": ["SELECT", "INSERT", "UPDATE", "DELETE"]
}
]
}
]
}
EOF
# Configuração do servidor
cat > config/server.json << 'EOF'
{
"host": "127.0.0.1",
"port": 8580,
"log_level": "info"
}
EOF
```
### 3. Execute
```bash
# Modo desenvolvimento
cargo run
# Modo produção
./target/release/pg-api
```
O servidor estará disponível em `http://localhost:8580`
---
## 📖 Documentação
- **[Quick Start Guide](docs/QUICKSTART.md)** - Tutorial passo a passo
- **[API Reference](docs/API.md)** - Documentação completa dos endpoints
- **[Observability Guide](docs/OBSERVABILITY.md)** - Configuração de métricas
- **[Changelog](CHANGELOG.md)** - Histórico de versões
---
## 🛠️ Exemplo de Uso
### cURL
```bash
# Health check
curl http://localhost:8580/health
# Executar uma query
curl -X POST http://localhost:8580/v1/query \
-H "X-API-Key: sk_test_123456789" \
-H "Content-Type: application/json" \
-d '{
"database": "postgres",
"query": "SELECT version()"
}'
```
### Python
```python
import requests
client = requests.Session()
client.headers.update({"X-API-Key": "sk_test_123456789"})
# Executar query
response = client.post(
"http://localhost:8580/v1/query",
json={
"database": "postgres",
"query": "SELECT * FROM users WHERE id = $1",
"params": [1]
}
)
print(response.json())
```
### Rust
```rust
use reqwest::Client;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client
.post("http://localhost:8580/v1/query")
.header("X-API-Key", "sk_test_123456789")
.json(&json!({
"database": "postgres",
"query": "SELECT version()"
}))
.send()
.await?;
println!("{}", response.text().await?);
Ok(())
}
```
Veja mais exemplos em [`examples/`](examples/).
---
## 📋 Endpoints Principais
| GET | `/health` | Health check |
| POST | `/v1/query` | Executar query SQL |
| POST | `/v1/batch` | Executar batch de queries |
| POST | `/v1/transaction` | Executar transação |
| POST | `/v1/transaction/sessions` | Abrir sessão transacional |
| POST | `/v1/transaction/sessions/{id}/query` | Executar query na sessão |
| POST | `/v1/transaction/sessions/{id}/commit` | Confirmar sessão |
| DELETE | `/v1/transaction/sessions/{id}` | Desfazer sessão |
| GET | `/v1/databases` | Listar databases |
| GET | `/v1/account` | Informações da conta |
| GET | `/docs` | Documentação Swagger UI |
---
## 🏗️ Arquitetura
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Cliente │────▶│ Axum/HTTP │────▶│ Middleware │
│ (cURL/JS) │ │ Server │ │(Auth/Rate) │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌─────────────────────┘
▼
┌─────────────────┐
│ Query Handler │
└────────┬────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Pool │ │ Batch │ │ Tx │
│ Manager │ │ Handler │ │ Handler │
└────┬────┘ └─────────┘ └─────────┘
│
▼
┌─────────┐
│PostgreSQL│
└─────────┘
```
### Higiene das conexões do pool
Cada query roda dentro da própria transação e a conexão **só** volta ao pool
quando o encerramento (`COMMIT`/`ROLLBACK`) é confirmado pelo PostgreSQL. Se o
cliente desconectar no meio da query, se uma sessão transacional expirar sem
commit/rollback ou se o `ROLLBACK` falhar, a conexão é descartada em vez de
reciclada. Sem essa garantia, o request seguinte recebia uma conexão
`idle in transaction (aborted)` e falhava no `BEGIN` com
`25P02 current transaction is aborted`.
---
## ⚙️ Configuração
### Configuração efetiva
```bash
# Servidor
APP__ADDR=127.0.0.1:8580
APP__LOG_LEVEL=info
# O servidor e o pool são configurados em arquivos JSON.
# CONFIG_DIR aponta para o diretório que contém:
# server.json (host/port)
# pool.json (max_connections, min_idle, worker_threads)
# accounts.json (database, username, password, permissions)
CONFIG_DIR=/etc/pg-api
# Observabilidade (opcional)
OPENSEARCH_ENABLED=false
OPENSEARCH_API_URL=https://opensearch.example.com
OPENSEARCH_API_TOKEN=sk_live_xxxxx
```
O `accounts.json` é obrigatório. Cada database deve apontar para o PostgreSQL
real que o processo do PG-API alcança pela rede; criar um banco em outro host
não o torna automaticamente disponível. O `pool.json` limita conexões por
instância do PG-API e não aumenta `max_connections` do PostgreSQL. Configure
`pool.max_connections` abaixo do limite do PostgreSQL, preservando conexões
para outros serviços.
Para claims concorrentes, use `/v1/transaction` com uma transação real e
`SELECT ... FOR UPDATE SKIP LOCKED`; não use `/v1/batch`, pois batch não é
atômico. O corpo de `/v1/transaction` é um array de `QueryRequest`, cada item
contendo `database`, `query` e opcionalmente `params`.
### Sessões Transacionais
Sessões persistentes em `/v1/transaction/sessions` permitem operações
cross-request com snapshot estável. Cada sessão mantém uma transação
aberta; use `commit` ou `rollback` para finalizar. Sessões abandonadas
expiram após 15 minutos.
Se uma query da sessão falhar, `/v1/transaction/sessions/{id}/query` responde
com o envelope JSON padrão e preserva a mensagem específica do PostgreSQL
(incluindo os detalhes diagnósticos retornados pelo banco). A resposta inclui
`metadata.request_id`; o log registra esse identificador, a mensagem completa e
o SQLSTATE para correlação e auditoria.
### Níveis de Isolamento
Transações em `/v1/transaction` suportam níveis de isolamento explícitos
(`READ COMMITTED`, `REPEATABLE READ`, `SERIALIZABLE`) via o campo
`isolation_level` no corpo da requisição.
---
## 🧪 Testes
```bash
# Rodar todos os testes
cargo test
# Rodar com output detalhado
cargo test -- --nocapture
# Benchmarks
cargo bench
```
Os testes de integração pulam automaticamente quando o servidor não está no ar.
Para exercitar higiene de conexão e diagnóstico de erro contra uma instância real:
```bash
PG_API_TEST_URL=http://127.0.0.1:8581 \
PG_API_TEST_KEY=sk_test \
PG_API_TEST_DB=pgapi_test \
cargo test --test connection_hygiene
```
---
## 🤝 Contribuindo
1. Fork o projeto
2. Crie sua branch (`git checkout -b feature/nova-feature`)
3. Commit suas mudanças (`git commit -am 'feat: nova feature'`)
4. Push para a branch (`git push origin feature/nova-feature`)
5. Abra um Pull Request
---
## 📄 Licença
Este projeto está licenciado sob a licença MIT - veja [LICENSE.md](LICENSE.md) para detalhes.
---
## 🔗 Links
- **Repositório**: https://gitlab.com/aerunti/pg-api
- **Documentação API**: http://localhost:8580/docs (quando rodando)
- **OpenAPI Spec**: http://localhost:8580/openapi.json
---
<p align="center">
Desenvolvido com ❤️ por <strong>Aerun Serviços de Tecnologia</strong>
</p>