Banco de Dados - ThiagoGomesRocha/Marrone GitHub Wiki
Acesso a MySQL, PostgreSQL, SQL Server, Oracle e IBM DB2 direto dos scripts de pré e
pós-requisição, através de bru.db. O SQL é seu: não há ORM traduzindo o que você escreveu.
O princípio que governa tudo nesta feature:
A string de conexão do script leva apenas host, porta e base. Usuário e senha vêm do cofre do sistema operacional, no momento de conectar.
Isso significa que a coleção pode ser versionada e compartilhada sem vazar credencial — o
.bru não contém, e não pode conter, usuário e senha.
| Onde o script roda | De onde vêm as credenciais |
|---|---|
| Aplicação (desktop) | cofre do sistema — Settings → Database |
CLI (marrone run) |
variáveis de ambiente |
No CLI (não existe safeStorage fora do Electron):
MARRONE_DB_POSTGRES_USER=usuario \
MARRONE_DB_POSTGRES_PASSWORD=senha \
marrone run --env LocalO padrão é <SGBD> em maiúsculas: MARRONE_DB_POSTGRES_*, MARRONE_DB_MYSQL_*,
MARRONE_DB_MSSQL_*, MARRONE_DB_ORACLE_*, MARRONE_DB_DB2_*. Não existe variável genérica
(de propósito: uma credencial global valeria para qualquer banco, inclusive um que você não
pretendia conectar a partir do CI).
| Função | Para que serve |
|---|---|
bru.db.connect(sgbd, connectionString) |
abre (ou reaproveita) uma conexão |
bru.db.query(conn, sql, params) |
consulta que devolve registros |
bru.db.execute(conn, sql, params) |
escrita (INSERT / UPDATE / DELETE / DDL) |
bru.db.disconnect(conn) |
fecha a conexão |
bru.db.transaction(conn, async () => { … }) |
executa um bloco dentro de uma transação |
Os SGBDs são identificados por postgres, mysql, mssql, oracle e db2.
// ✅ certo — o Marrone aplica as credenciais do cofre
const conn = await bru.db.connect('postgres', 'postgres://localhost:5432/mydb');// ❌ errado — isto é RECUSADO com erro explícito
const conn = await bru.db.connect('postgres', 'postgres://joao:senha@localhost:5432/mydb');A recusa é intencional: se a senha entrasse na string, ela iria automaticamente para o
arquivo .bru, para o controle de versão e para o relatório.
O painel Settings → Database → Exemplos de uso mostra, para cada SGBD, exatamente o script abaixo — e é ele que você deve seguir:
// Bloco tests — confirma NO BANCO o que a requisição gravou.
test('valida dados salvos no bd', async function () {
const conn = await bru.db.connect('postgres', 'postgres://localhost:5432/mydb');
try {
// Placeholder deste SGBD: $1
const pedido = await bru.db.query(conn, 'SELECT * FROM pedidos WHERE id = $1', [123]);
// 1 registro => objeto único | vários => lista | nenhum => null
expect(pedido).to.not.be.null;
console.log(pedido);
} finally {
// Fechar SEMPRE: conexão pendente com driver nativo (DB2) derruba o processo.
await bru.db.disconnect(conn);
}
});Três detalhes que não são decorativos:
-
async function— oawaitsó existe dentro de função assíncrona, e otest()do Marrone aguarda a promise do callback. -
try/finallynodisconnect— se a query falhar, a conexão ainda é fechada. Fechar o aplicativo com uma conexão ODBC pendente derruba o processo (segmentation fault). -
bru.db.*— não existedbglobal nemdbQuery()/dbExecute(). Os globais do sandbox sãotest,bru,req,res,expect,assert,jwteconsole.
O estilo do placeholder muda de banco para banco. A chamada é sempre posicional
([valor1, valor2]); quem traduz o array é o Marrone.
| SGBD | sgbd |
String de conexão | Placeholder |
|---|---|---|---|
| PostgreSQL | postgres |
postgres://localhost:5432/mydb |
$1, $2
|
| MySQL | mysql |
mysql://localhost:3306/mydb |
? |
| SQL Server | mssql |
mssql://localhost:1433/mydb |
@p1, @p2
|
| Oracle | oracle |
oracle://localhost:1521/mydb |
:1, :2
|
| IBM DB2 | db2 |
db2://localhost:50000/mydb |
? |
// MySQL
await bru.db.execute(conn, 'UPDATE pedidos SET status = ? WHERE id = ?', ['processando', 123]);
const pedido = await bru.db.query(conn, 'SELECT * FROM pedidos WHERE id = ?', [123]);// SQL Server — parâmetros NOMEADOS. O array posicional é convertido pela ordem
// numérica dos placeholders (@p1, @p2, …); repetir o mesmo placeholder liga o valor uma vez.
await bru.db.execute(conn, 'UPDATE pedidos SET status = @p1 WHERE id = @p2', ['processando', 123]);// Oracle — posicional numerado, em thin mode (não precisa de Instant Client)
await bru.db.execute(conn, 'UPDATE pedidos SET status = :1 WHERE id = :2', ['processando', 123]);É o padrão mais útil: em vez de confiar só na resposta da API, você confirma no banco o que ela gravou. A coleção de exemplo que acompanha a aplicação faz exatamente isso, e é escrita para não falhar numa instalação nova (sem banco configurado):
const sgbd = bru.getEnvVar('dbSgbd'); // postgres | mysql | mssql | oracle | db2
const connectionString = bru.getEnvVar('dbConnectionString'); // SEM usuário e senha
const validationQuery = bru.getEnvVar('dbValidationQuery'); // a SUA consulta
const validationParam = bru.getEnvVar('dbValidationParam'); // opcional: valores em JSON, ex. [1]
if (!sgbd || !connectionString || !validationQuery) {
console.log('[banco] exemplo não configurado — defina dbSgbd, dbConnectionString e dbValidationQuery no ambiente ativo.');
} else {
test('valida dados salvos no bd', async function () {
const conn = await bru.db.connect(sgbd, connectionString);
try {
const params = validationParam ? JSON.parse(validationParam) : undefined;
const pedido = await bru.db.query(conn, validationQuery, params);
expect(pedido).to.not.be.null;
console.log(pedido);
} finally {
await bru.db.disconnect(conn);
}
});
}Passo a passo na interface:
- Settings → Database: escolha o SGBD e salve usuário e senha.
- Crie (ou edite) um ambiente com
dbSgbd,dbConnectionStringedbValidationQuery. - Selecione esse ambiente e rode a requisição.
- Abra a aba Tests e o console: o valor do banco aparece no
console.log.
Para comparar o banco com o que a API respondeu — só faz sentido quando esta requisição gravou a linha:
expect(pedido.email).to.equal(res.getBody().email);const conn = await bru.db.connect('postgres', 'postgres://localhost:5432/mydb');
await bru.db.transaction(conn, async () => {
await bru.db.execute(conn, 'UPDATE contas SET saldo = saldo - $1 WHERE id = $2', [100, 1]);
await bru.db.execute(conn, 'UPDATE contas SET saldo = saldo + $1 WHERE id = $2', [100, 2]);
});
await bru.db.disconnect(conn);Se o bloco lançar, o Marrone executa ROLLBACK e propaga o erro; se terminar, COMMIT.
| Situação | Retorno |
|---|---|
| Nenhum registro | null |
| Exatamente 1 registro | objeto |
| 2 ou mais registros | lista de objetos |
Coluna com valor null
|
a chave é omitida do objeto |
| Coluna com alias | usa o alias |
Os valores chegam ao script já convertidos para tipos seguros:
| No banco | No script |
|---|---|
TIMESTAMP / DATE
|
string ISO 8601 em UTC (2024-01-02T03:04:05.678Z) |
BLOB / BYTEA
|
string em base64 |
BIGINT, DECIMAL / NUMERIC
|
string (para não perder precisão) |
| inteiro comum | number |
Se um DECIMAL voltar como "19.90" em vez de 19.9, isso é intencional: virar number
perderia a precisão e a formatação original.
⚠️ Exceção: SQL Server. O drivertediousconverteDECIMAL/NUMERICparanumberantes de o Marrone ver o valor, e isso não é configurável. Se a precisão importa, converta para texto no próprio SQL:SELECT CAST(saldo AS VARCHAR(50)) AS saldo FROM contas.
Cada operação de banco entra no relatório de execução, na aba Database Operations: data e hora, SGBD, SQL, tempo e status, além de filtros por SGBD/status e um botão Copiar SQL.
Duas decisões de privacidade:
- os valores dos parâmetros aparecem como
***(o SQL continua legível); - o conjunto de linhas não é armazenado — apenas a contagem.
O relatório carrega Vue 3 e Naive UI de uma CDN em tempo de execução: sem internet os componentes visuais não renderizam (os dados continuam no arquivo).
- Uma credencial por SGBD. Duas contas no mesmo banco (leitura e escrita, por exemplo) ainda não são suportadas.
- O relatório precisa de internet para renderizar.
-
O "Testar conexão" exige o driver instalado — a mensagem aponta o pacote que falta
(
DRIVER_NOT_INSTALLED→npm install <pacote>). -
ibm_db(DB2) é o único driver nativo e o único que exige cuidado no empacotamento. - Os testes de integração com banco real são opt-in — veja Testes automatizados.
-
No CLI, o pacote precisa estar construído:
npm run buildempackages/marrone-database.
O detalhamento de cada decisão (por que drivers diretos e não Sequelize, por que uma conexão
dedicada por entrada de cache, o contrato de erro) está em
docs/database-integration.md e em
packages/marrone-database/ARCHITECTURE.md.
Ver também: documentação do Bruno — scripting de pré e
pós-requisição, variáveis, ambientes e runner, que é a base sobre a qual bru.db foi
construído.