Database#
Rotas
/v1/clouddb/*· owner/admin · gated porCLOUDDB_ENABLED(off → 404)
O que é#
Um banco relacional gerenciado (PostgreSQL ou MySQL, em cima do Google Cloud SQL) que você provisiona e opera pela API, sem tocar no console da GCP. Além do banco em si, o módulo te dá:
- um SQL editor guardado (roda uma instrução por vez, default read-only),
- um schema declarativo versionado (descreve o schema uma vez, aplica com diff
- confirmação para mudanças destrutivas + rollback),
- multi-tenancy de end-customers (isola os clientes do SEU cliente por linha ou por schema), e
- billing (custo GCP + markup).
O que dá pra fazer#
- Provisionar instâncias Cloud SQL (
postgres/mysql) por projeto. - Rodar SQL com um guard de segurança (default-deny: sem statements empilhados, sem comentários; write/DDL só com flag explícita) + histórico.
- Declarar o schema (IR
catcher.schema.v1), pré-visualizar o diff, aplicar, versionar e fazer rollback. - Registrar/listar/remover end-customers (as empresas do seu cliente), com
isolamento
row_levelouschema_per_company. - Consultar custo/faturamento por projeto.
Como funciona por dentro (na prática)#
- Control-plane. A API guarda metadados (instâncias, projetos, versões de schema, end-customers) no banco do seu tenant; o banco de dados real fica na GCP.
- Provisionamento assíncrono + keyless.
POST .../instancesresponde na hora compendinge enfileira a criação fora do request path. Os adapters GCP reais (Cloud SQL Admin + Resource Manager) rodam com credencial keyless (ADC da VM, sem chave guardada). Você faz poll atéready. - SQL editor keyless. As queries conectam via Cloud SQL Connector + IAM DB
auth — nenhuma senha é armazenada. O guard classifica o verbo (
read/write/ddl) e grava okindno histórico. - Schema engine. Valida a IR → introspecta o estado atual → planeja o diff
(consciente de MySQL 8 vs Postgres) → aplica → grava uma versão forward-only
(rollback = reaplicar uma versão antiga como uma versão nova). Drops exigem
confirm: truee vêm com um relatório de impacto (tabelas/colunas dropadas + contagem estimada de linhas). - Tenancy de end-customers. O
applyroteia pelastrategydo projeto:row_levelinjetacompany_idnos models@scope(company);schema_per_companycria um schema/db por cliente sob demanda (catcher_c_<…>), com período de carência antes de dropar na remoção.
Fluxo típico (end-to-end)#
KEY="X-API-Key: ctc_…" ; B=https://data-api.catcher.one ; JS='Content-Type: application/json'
# 1. Projeto (uma vez)
PID=$(curl -sX POST $B/v1/clouddb/projects -H "$KEY" -H "$JS" \
-d '{"name":"Produção","hosting_mode":"catcher","strategy":"row_level"}' | jq -r .id)
# 2. Provisiona a instância (assíncrono)
IID=$(curl -sX POST $B/v1/clouddb/instances -H "$KEY" -H "$JS" \
-d "{\"project_id\":\"$PID\",\"engine\":\"postgres\",\"version\":\"POSTGRES_16\",\"region\":\"southamerica-east1\",\"tier\":\"db-custom-2-7680\",\"name\":\"app-db\"}" | jq -r .id)
# 3. Poll até ready
curl -s $B/v1/clouddb/instances -H "$KEY" | jq '.instances[] | select(.id=="'$IID'") | .status'
# 4. Declara o schema (preview → apply)
curl -sX POST $B/v1/clouddb/instances/$IID/schema/apply -H "$KEY" -H "$JS" -d '{
"ir": { "format_version":"catcher.schema.v1", "models":[
{"name":"User","table":"users","scope":"global","fields":[
{"name":"id","type":"uuid","primary":true},
{"name":"email","type":"string","unique":true,"size":255}]}]}}'
# 5. Roda uma query
curl -sX POST $B/v1/clouddb/instances/$IID/query -H "$KEY" -H "$JS" \
-d '{"sql":"SELECT * FROM users LIMIT 10"}'
Referência de endpoints#
Projetos#
| Método | Rota | O que faz |
|---|---|---|
POST |
/v1/clouddb/projects |
Cria projeto (name, hosting_mode, strategy) → 201 |
GET |
/v1/clouddb/projects |
Lista projetos |
Instâncias#
| Método | Rota | O que faz |
|---|---|---|
POST |
/v1/clouddb/instances |
Provisiona (project_id+engine+version+region+tier+name) → 202 {id,status:"pending"} |
GET |
/v1/clouddb/instances |
Lista (status: pending→provisioning→ready|failed, last_error) |
name da instância: ^[a-z][a-z0-9-]{0,62}$. engine: mysql/postgres.
SQL editor#
| Método | Rota | O que faz |
|---|---|---|
POST |
/v1/clouddb/instances/{id}/query |
Roda uma instrução. Body {sql, allow_write?, allow_ddl?} |
GET |
/v1/clouddb/instances/{id}/query/history |
Histórico (mais recente primeiro) |
Sucesso: 200 {ok:true, columns, rows, row_count, truncated, duration_ms}.
Erro de query (renderizável): 200 {ok:false, error, duration_ms}. O guard rejeita
; empilhado, comentários e verbos não-classificados; allow_write/allow_ddl
default false.
Schema engine (catcher.schema.v1)#
| Método | Rota | O que faz |
|---|---|---|
POST |
/v1/clouddb/instances/{id}/schema/preview |
Valida + retorna o diff, não aplica |
POST |
/v1/clouddb/instances/{id}/schema/apply |
Aplica + grava versão (confirm:true se destrutivo) |
GET |
/v1/clouddb/instances/{id}/schema/versions |
Versões (mais recente primeiro) |
POST |
/v1/clouddb/instances/{id}/schema/rollback |
Reaplica uma versão antiga como nova (version, confirm) |
IR: models[] com fields[]; type ∈ string,text,int,uint,decimal,bool,date,datetime,uuid,json; string sem size → 255 (> 65535 → use text); scope global|company. Destrutivo → 409 SCHEMA_DESTRUCTIVE_REQUIRES_CONFIRM (com diff + impact); IR inválida → 422 SCHEMA_INVALID (com issues).
End-customers (multi-tenancy)#
| Método | Rota | O que faz |
|---|---|---|
POST |
/v1/clouddb/projects/{pid}/companies |
Registra end-customer (external_id, name) → 201 |
GET |
/v1/clouddb/projects/{pid}/companies |
Lista (+ strategy + schema_name sob schema_per_company) |
DELETE |
/v1/clouddb/projects/{pid}/companies/{cid} |
Soft-delete (?confirm=true sob schema_per_company) → 204 |
Sob schema_per_company a remoção mantém o schema por CLOUDDB_COMPANY_REAP_GRACE_DAYS (default 7) antes do reaper dropar — reversível na janela.
Os "end-customers" aqui são os Tenants — o mesmo external_id (= tenant_id)
isola esse cliente também no Vector, Storage e Cache. Quando usar cada estratégia:
| Critério | row_level |
schema_per_company |
|---|---|---|
| Isolamento | lógico (filtro forçado) | físico (schema separado) |
| Ideal para | muitos clientes pequenos/homogêneos | separação forte, auditoria/compliance |
| Custo operacional | menor (um schema só) | maior (um schema por cliente) |
| Escala de nº de clientes | milhares | dezenas a centenas |
A strategy é definida por projeto (imutável na criação); se precisa das duas,
crie dois projetos. Visão transversal do eixo Tenant nos 4 produtos: Tenants.
Billing#
| Método | Rota | O que faz |
|---|---|---|
GET |
/v1/clouddb/billing/usage?from&to |
Custo GCP + faturado (markup +50% no modo catcher). Degrada com available:false se o export não estiver configurado |
Money em minor units (centavos) + display, sem float drift.
Códigos de erro do módulo#
INSTANCE_NOT_FOUND (404) · INSTANCE_NOT_READY (409) · PROJECT_NOT_FOUND
(404) · SCHEMA_INVALID (422, com issues) · SCHEMA_DESTRUCTIVE_REQUIRES_CONFIRM
(409, com diff+impact) · SCHEMA_VERSION_NOT_FOUND (404) ·
COMPANY_ALREADY_EXISTS (409) · COMPANY_NOT_FOUND (404) ·
COMPANY_REMOVE_REQUIRES_CONFIRM (409) · MISSING_FIELD (400).
Gotchas#
404numa rota válida =CLOUDDB_ENABLED=falseno ambiente (ou a feature degradou por não conseguir construir os clients GCP). Não é o seu request.- Provisionar é assíncrono — não trate o
202como "pronto"; faça poll atéready. - Drops nunca silenciosos —
apply/rollbackdestrutivo só passa comconfirm:true; leia oimpactantes. - SQL editor é control-plane, uma instrução por vez — não é um pool de conexão para a sua app rodar mil queries; é para administração/inspeção.