A interface é projetada para ser simples e performática, usando template literals marcadas para consultas e oferecendo recursos como pooling de conexões, transações e prepared statements.
import { sql, SQL } from "bun";
// PostgreSQL (padrão)
const users = await sql`
SELECT * FROM users
WHERE active = ${true}
LIMIT ${10}
`;
// Com MySQL
const mysql = new SQL("mysql://user:pass@localhost:3306/mydb");
const mysqlResults = await mysql`
SELECT * FROM users
WHERE active = ${true}
`;
// Com SQLite
const sqlite = new SQL("sqlite://myapp.db");
const sqliteResults = await sqlite`
SELECT * FROM users
WHERE active = ${1}
`;Recursos
- Template literals marcadas para proteger contra injeção de SQL
- Transações
- Parâmetros nomeados e posicionais
- Pooling de conexões
- Suporte a
BigInt - Suporte a autenticação SASL (SCRAM-SHA-256), MD5 e Clear Text
- Timeouts de conexão
- Retorno de linhas como objetos de dados, arrays de arrays ou Buffer
- Suporte a protocolo binário torna mais rápido
- Suporte a TLS (e modo de autenticação)
- Configuração automática com variáveis de ambiente
Suporte a Bancos de Dados
Bun.SQL fornece uma API unificada para múltiplos sistemas de banco de dados:
PostgreSQL
PostgreSQL é usado quando:
- A string de conexão não corresponde aos padrões SQLite ou MySQL (é o adaptador fallback)
- A string de conexão usa explicitamente os protocolos
postgres://oupostgresql:// - Nenhuma string de conexão é fornecida e variáveis de ambiente apontam para PostgreSQL
import { sql } from "bun";
// Usa PostgreSQL se DATABASE_URL não estiver definida ou for uma URL PostgreSQL
await sql`SELECT ...`;
import { SQL } from "bun";
const pg = new SQL("postgres://user:pass@localhost:5432/mydb");
await pg`SELECT ...`;MySQL
Suporte MySQL é built-in no Bun.SQL, fornecendo a mesma interface de template literal marcada com compatibilidade total para MySQL 5.7+ e MySQL 8.0+:
import { SQL } from "bun";
// Conexão MySQL
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // protocolo mysql2 também funciona
// Usando objeto de opções
const mysql3 = new SQL({
adapter: "mysql",
hostname: "localhost",
port: 3306,
database: "myapp",
username: "dbuser",
password: "secretpass",
});
// Funciona com parâmetros - usa automaticamente prepared statements
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;
// Transações funcionam igual ao PostgreSQL
await mysql.begin(async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = ${userId}`;
});
// Inserts em massa
const newUsers = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;Formatos de String de Conexão MySQL">
MySQL aceita vários formatos de URL para strings de conexão:
// Protocolo mysql:// padrão
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // Porta padrão 3306
// Protocolo mysql2:// (compatibilidade com pacote npm mysql2)
new SQL("mysql2://user:pass@localhost:3306/database");
// Com parâmetros de query
new SQL("mysql://user:pass@localhost/db?ssl=true");
// Conexão via socket Unix
new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");Recursos Específicos do MySQL">
Bancos de dados MySQL suportam:
- Prepared statements: Automaticamente criados para consultas parametrizadas com cache de statements
- Protocolo binário: Para melhor performance com prepared statements e manipulação precisa de tipos
- Múltiplos result sets: Suporte para stored procedures retornando múltiplos result sets
- Plugins de autenticação: Suporte para mysql_native_password, caching_sha2_password (padrão MySQL 8.0), e sha256_password
- Conexões SSL/TLS: Modos SSL configuráveis similares ao PostgreSQL
- Atributos de conexão: Informações do cliente enviadas ao servidor para monitoramento
- Pipelining de queries: Executar múltiplos prepared statements sem esperar respostas
SQLite
Suporte SQLite é built-in no Bun.SQL, fornecendo a mesma interface de template literal marcada:
import { SQL } from "bun";
// Banco de dados em memória
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");
// Banco de dados baseado em arquivo
const sql1 = new SQL("sqlite://myapp.db");
// Usando objeto de opções
const sql2 = new SQL({
adapter: "sqlite",
filename: "./data/app.db",
});
// Para nomes de arquivo simples, especifique adapter explicitamente
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });Formatos de String de Conexão SQLite">
SQLite aceita vários formatos de URL para strings de conexão:
// Protocolo sqlite:// padrão
new SQL("sqlite://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // Sem barras
// Protocolo file:// (também reconhecido como SQLite)
new SQL("file://path/to/database.db");
new SQL("file:path/to/database.db");
// Banco de dados especial :memory:
new SQL(":memory:");
new SQL("sqlite://:memory:");
new SQL("file://:memory:");
// Caminhos relativos e absolutos
new SQL("sqlite://./local.db"); // Relativo ao diretório atual
new SQL("sqlite://../parent/db.db"); // Diretório pai
new SQL("sqlite:///absolute/path.db"); // Caminho absoluto
// Com parâmetros de query
new SQL("sqlite://data.db?mode=ro"); // Modo apenas leitura
new SQL("sqlite://data.db?mode=rw"); // Modo leitura-escrita (sem criar)
new SQL("sqlite://data.db?mode=rwc"); // Modo leitura-escrita-criar (padrão)NOTE
Nomes de arquivo simples sem protocolo (como `"myapp.db"`) requerem especificar explicitamente `{ adapter: "sqlite" }` para evitar ambiguidade com PostgreSQL.Opções Específicas do SQLite">
Bancos de dados SQLite suportam opções de configuração adicionais:
const sql = new SQL({
adapter: "sqlite",
filename: "app.db",
// Opções específicas do SQLite
readonly: false, // Abrir em modo apenas leitura
create: true, // Criar banco de dados se não existir
readwrite: true, // Abrir para leitura e escrita
// Opções adicionais do Bun:sqlite
strict: true, // Habilitar modo estrito
safeIntegers: false, // Usar números JavaScript para inteiros
});Parâmetros de query na URL são parseados para definir estas opções:
?mode=ro→readonly: true?mode=rw→readonly: false, create: false?mode=rwc→readonly: false, create: true(padrão)
Inserindo dados
Você pode passar valores JavaScript diretamente para a template literal SQL e o escaping será tratado para você.
import { sql } from "bun";
// Insert básico com valores diretos
const [user] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
// Usando helper de objeto para sintaxe mais limpa
const userData = {
name: "Alice",
email: "alice@example.com",
};
const [newUser] = await sql`
INSERT INTO users ${sql(userData)}
RETURNING *
`;
// Expande para: INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')Insert em Massa
Você também pode passar arrays de objetos para a template literal SQL e será expandido para um statement INSERT INTO ... VALUES ....
const users = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" },
{ name: "Charlie", email: "charlie@example.com" },
];
await sql`INSERT INTO users ${sql(users)}`;Escolhendo colunas para insert
Você pode usar sql(object, ...string) para escolher quais colunas inserir. Cada uma das colunas deve estar definida no objeto.
const user = {
name: "Alice",
email: "alice@example.com",
age: 25,
};
await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// Insere apenas colunas name e email, ignorando outros camposResultados de Query
Por padrão, o cliente SQL do Bun retorna resultados de query como arrays de objetos, onde cada objeto representa uma linha com nomes de colunas como chaves. No entanto, há casos onde você pode querer os dados em um formato diferente. O cliente fornece dois métodos adicionais para este propósito.
Formato sql``.values()
O método sql``.values() retorna linhas como arrays de valores em vez de objetos. Cada linha se torna um array onde os valores estão na mesma ordem que as colunas na sua query.
const rows = await sql`SELECT * FROM users`.values();
console.log(rows);Isso retorna algo como:
[
["Alice", "alice@example.com"],
["Bob", "bob@example.com"],
];sql``.values() é especialmente útil se nomes de colunas duplicados são retornados nos resultados da query. Ao usar objetos (o padrão), o último nome de coluna é usado como chave no objeto, o que significa que nomes de colunas duplicados se sobrescrevem — mas ao usar sql``.values(), cada coluna está presente no array então você pode acessar os valores de colunas duplicadas por índice.
Formato sql``.raw()
O método .raw() retorna linhas como arrays de objetos Buffer. Isso pode ser útil para trabalhar com dados binários ou por razões de performance.
const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]Fragmentos SQL
Uma necessidade comum em aplicações de banco de dados é a capacidade de construir queries dinamicamente baseado em condições de runtime. Bun fornece maneiras seguras de fazer isso sem risco de injeção de SQL.
Nomes de Tabelas Dinâmicas
Quando você precisa referenciar tabelas ou schemas dinamicamente, use o helper sql() para garantir escaping adequado:
// Referencia tabelas com segurança de forma dinâmica
await sql`SELECT * FROM ${sql("users")}`;
// Com qualificação de schema
await sql`SELECT * FROM ${sql("public.users")}`;Queries Condicionais
Você pode usar o helper sql() para construir queries com cláusulas condicionais. Isso permite criar queries flexíveis que se adaptam às necessidades da sua aplicação:
// Cláusulas WHERE opcionais
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
SELECT * FROM users
WHERE active = ${true}
${filterAge ? ageFilter : sql``}
`;Colunas dinâmicas em updates
Você pode usar sql(object, ...string) para escolher quais colunas atualizar. Cada uma das colunas deve estar definida no objeto. Se as colunas não forem informadas, todas as chaves serão usadas para atualizar a linha.
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// usa todas as chaves do objeto para atualizar a linha
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;Valores dinâmicos e where in
Listas de valores também podem ser criadas dinamicamente, tornando queries where in simples também. Opcionalmente você pode passar um array de objetos e informar qual chave usar para criar a lista.
await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`;
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
];
await sql`SELECT * FROM users WHERE id IN ${sql(users, "id")}`;Helper sql.array
O helper sql.array cria literais de array PostgreSQL a partir de arrays JavaScript:
// Cria literais de array para PostgreSQL
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// Gera: INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])
// Funciona com arrays numéricos também
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// Gera: SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])NOTE
`sql.array` é apenas para PostgreSQL. Arrays multidimensionais e elementos NULL podem não ser suportados ainda.sql``.simple()
O protocolo wire do PostgreSQL suporta dois tipos de queries: "simple" e "extended". Queries simple podem conter múltiplos statements mas não suportam parâmetros, enquanto queries extended (o padrão) suportam parâmetros mas permitem apenas um statement.
Para rodar múltiplos statements em uma única query, use sql``.simple():
// Múltiplos statements em uma query
await sql`
SELECT 1;
SELECT 2;
`.simple();Queries simple são frequentemente úteis para migrações de banco de dados e scripts de setup.
Note que queries simple não podem usar parâmetros (${value}). Se você precisa de parâmetros, deve dividir sua query em statements separados.
Queries em arquivos
Você pode usar o método sql.file para ler uma query de um arquivo e executá-la, se o arquivo incluir $1, $2, etc você pode passar parâmetros para a query. Se nenhum parâmetro for usado, pode executar múltiplos comandos por arquivo.
const result = await sql.file("query.sql", [1, 2, 3]);Queries Unsafe
Você pode usar a função sql.unsafe para executar strings SQL brutas. Use isso com cautela, pois não fará escape de input do usuário. Executar mais de um comando por query é permitido se nenhum parâmetro for usado.
// Múltiplos comandos sem parâmetros
const result = await sql.unsafe(`
SELECT ${userColumns} FROM users;
SELECT ${accountColumns} FROM accounts;
`);
// Usando parâmetros (apenas um comando é permitido)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);Executar e Cancelar Queries
O SQL do Bun é lazy, o que significa que só começará a executar quando awaited ou executado com .execute(). Você pode cancelar uma query que está executando atualmente chamando o método cancel() no objeto da query.
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;Variáveis de Ambiente do Banco de Dados
Parâmetros de conexão sql podem ser configurados usando variáveis de ambiente. O cliente verifica estas variáveis em uma ordem específica de precedência e detecta automaticamente o tipo de banco de dados baseado no formato da string de conexão.
Detecção Automática de Banco de Dados
Ao usar Bun.sql() sem argumentos ou new SQL() com uma string de conexão, o adaptador é automaticamente detectado baseado no formato da URL:
Detecção Automática do MySQL
MySQL é automaticamente selecionado quando a string de conexão corresponde a estes padrões:
mysql://...- URLs de protocolo MySQLmysql2://...- URLs de protocolo MySQL2 (alias de compatibilidade)
// Todos estes usam MySQL automaticamente (sem adapter necessário)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");
// Funciona com variável de ambiente DATABASE_URL
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.jsDetecção Automática do SQLite
SQLite é automaticamente selecionado quando a string de conexão corresponde a estes padrões:
:memory:- Banco de dados em memóriasqlite://...- URLs de protocolo SQLitesqlite:...- Protocolo SQLite sem barrasfile://...- URLs de protocolo de arquivofile:...- Protocolo de arquivo sem barras
// Todos estes usam SQLite automaticamente (sem adapter necessário)
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");
// Funciona com variável de ambiente DATABASE_URL
DATABASE_URL=":memory:" bun run app.js
DATABASE_URL="sqlite://myapp.db" bun run app.js
DATABASE_URL="file://./data/app.db" bun run app.jsDetecção Automática do PostgreSQL
PostgreSQL é o padrão para strings de conexão que não correspondem aos padrões MySQL ou SQLite:
# PostgreSQL é detectado para estes padrões
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js
# Ou qualquer URL que não corresponda aos padrões MySQL ou SQLite
DATABASE_URL="localhost:5432/mydb" bun run app.jsVariáveis de Ambiente do MySQL
Conexões MySQL podem ser configuradas via variáveis de ambiente:
# URL de conexão primária (verificada primeiro)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"
# Alternativa: DATABASE_URL com protocolo MySQL
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"Se nenhuma URL de conexão for fornecida, MySQL verifica estes parâmetros individuais:
| Variável de Ambiente | Valor Padrão | Descrição |
|---|---|---|
MYSQL_HOST | localhost | Host do banco de dados |
MYSQL_PORT | 3306 | Porta do banco de dados |
MYSQL_USER | root | Usuário do banco de dados |
MYSQL_PASSWORD | (vazio) | Senha do banco de dados |
MYSQL_DATABASE | mysql | Nome do banco de dados |
MYSQL_URL | (vazio) | URL de conexão primária do MySQL |
TLS_MYSQL_DATABASE_URL | (vazio) | URL de conexão SSL/TLS |
Variáveis de Ambiente do PostgreSQL
As seguintes variáveis de ambiente podem ser usadas para definir a conexão PostgreSQL:
| Variável de Ambiente | Descrição |
|---|---|
POSTGRES_URL | URL de conexão primária para PostgreSQL |
DATABASE_URL | URL de conexão alternativa (auto-detect) |
PGURL | URL de conexão alternativa |
PG_URL | URL de conexão alternativa |
TLS_POSTGRES_DATABASE_URL | URL de conexão SSL/TLS |
TLS_DATABASE_URL | URL de conexão SSL/TLS alternativa |
Se nenhuma URL de conexão for fornecida, o sistema verifica estes parâmetros individuais:
| Variável de Ambiente | Variáveis Fallback | Valor Padrão | Descrição |
|---|---|---|---|
PGHOST | - | localhost | Host do banco |
PGPORT | - | 5432 | Porta do banco |
PGUSERNAME | PGUSER, USER, USERNAME | postgres | Usuário do banco |
PGPASSWORD | - | (vazio) | Senha do banco |
PGDATABASE | - | username | Nome do banco |
Variáveis de Ambiente do SQLite
Conexões SQLite podem ser configuradas via DATABASE_URL quando contém uma URL compatível com SQLite:
# Todos estes são reconhecidos como SQLite
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"Nota: Variáveis de ambiente específicas do PostgreSQL (POSTGRES_URL, PGHOST, etc.) são ignoradas ao usar SQLite.
Pré-conexão em Runtime
O Bun pode pré-conectar ao PostgreSQL na inicialização para melhorar performance estabelecendo conexões de banco de dados antes do seu código de aplicação rodar. Isso é útil para reduzir latência de conexão na primeira query de banco de dados.
# Habilitar pré-conexão PostgreSQL
bun --sql-preconnect index.js
# Funciona com variável de ambiente DATABASE_URL
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js
# Pode ser combinado com outras flags de runtime
bun --sql-preconnect --hot index.jsA flag --sql-preconnect estabelecerá automaticamente uma conexão PostgreSQL usando suas variáveis de ambiente configuradas na inicialização. Se a conexão falhar, não quebrará sua aplicação - o erro será tratado graciosamente.
Opções de Conexão
Você pode configurar sua conexão de banco de dados manualmente passando opções para o construtor SQL. Opções variam dependendo do adaptador de banco de dados:
Opções do MySQL
import { SQL } from "bun";
const sql = new SQL({
// Requerido para MySQL ao usar objeto de opções
adapter: "mysql",
// Detalhes de conexão
hostname: "localhost",
port: 3306,
database: "myapp",
username: "dbuser",
password: "secretpass",
// Conexão via socket Unix (alternativa a hostname/port)
// socket: "/var/run/mysqld/mysqld.sock",
// Configurações de pool de conexões
max: 20, // Máximo de conexões no pool (padrão: 10)
idleTimeout: 30, // Fecha conexões ociosas após 30s
maxLifetime: 0, // Tempo de vida da conexão em segundos (0 = para sempre)
connectionTimeout: 30, // Timeout ao estabelecer novas conexões
// Opções SSL/TLS
ssl: "prefer", // ou "disable", "require", "verify-ca", "verify-full"
// tls: {
// rejectUnauthorized: true,
// ca: "path/to/ca.pem",
// key: "path/to/key.pem",
// cert: "path/to/cert.pem",
// },
// Callbacks
onconnect: client => {
console.log("Conectado ao MySQL");
},
onclose: (client, err) => {
if (err) {
console.error("Erro de conexão MySQL:", err);
} else {
console.log("Conexão MySQL fechada");
}
},
});Opções do PostgreSQL
import { SQL } from "bun";
const sql = new SQL({
// Detalhes de conexão (adapter é auto-detectado como PostgreSQL)
url: "postgres://user:pass@localhost:5432/dbname",
// Parâmetros de conexão alternativos
hostname: "localhost",
port: 5432,
database: "myapp",
username: "dbuser",
password: "secretpass",
// Configurações de pool de conexões
max: 20, // Máximo de conexões no pool
idleTimeout: 30, // Fecha conexões ociosas após 30s
maxLifetime: 0, // Tempo de vida da conexão em segundos (0 = para sempre)
connectionTimeout: 30, // Timeout ao estabelecer novas conexões
// Opções SSL/TLS
tls: true,
// tls: {
// rejectUnauthorized: true,
// requestCert: true,
// ca: "path/to/ca.pem",
// key: "path/to/key.pem",
// cert: "path/to/cert.pem",
// checkServerIdentity(hostname, cert) {
// ...
// },
// },
// Callbacks
onconnect: client => {
console.log("Conectado ao PostgreSQL");
},
onclose: client => {
console.log("Conexão PostgreSQL fechada");
},
});Opções do SQLite
import { SQL } from "bun";
const sql = new SQL({
// Requerido para SQLite
adapter: "sqlite",
filename: "./data/app.db", // ou ":memory:" para banco em memória
// Modos de acesso específicos do SQLite
readonly: false, // Abrir em modo apenas leitura
create: true, // Criar banco de dados se não existir
readwrite: true, // Permitir operações de leitura e escrita
// Manipulação de dados SQLite
strict: true, // Habilitar modo estrito para melhor segurança de tipos
safeIntegers: false, // Usar BigInt para inteiros excedendo range de número JS
// Callbacks
onconnect: client => {
console.log("Banco de dados SQLite aberto");
},
onclose: client => {
console.log("Banco de dados SQLite fechado");
},
});Notas de Conexão SQLite">
- Pooling de Conexões: SQLite não usa pooling de conexões pois é um banco de dados baseado em arquivo. Cada instância
SQLrepresenta uma única conexão. - Transações: SQLite suporta transações aninhadas através de savepoints, similar ao PostgreSQL.
- Acesso Concorrente: SQLite gerencia acesso concorrente através de locking de arquivo. Use modo WAL para melhor concorrência.
- Bancos de Dados em Memória: Usar
:memory:cria um banco de dados temporário que existe apenas durante o tempo de vida da conexão.
Senhas Dinâmicas
Quando clientes precisam usar esquemas de autenticação alternativos como tokens de acesso ou conexões a bancos de dados com senhas rotativas, forneça uma função síncrona ou assíncrona que resolverá o valor da senha dinâmica no momento da conexão.
import { SQL } from "bun";
const sql = new SQL(url, {
// Outra config de conexão
...
// Função de senha para o usuário do banco de dados
password: async () => await signer.getAuthToken(),
});Recursos Específicos do SQLite
Execução de Queries
SQLite executa queries sincronamente, diferente do PostgreSQL que usa I/O assíncrono. No entanto, a API permanece consistente usando Promises:
const sqlite = new SQL("sqlite://app.db");
// Funciona igual ao PostgreSQL, mas executa sincronamente por baixo
const users = await sqlite`SELECT * FROM users`;
// Parâmetros funcionam identicamente
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;Pragmas do SQLite
Você pode usar statements PRAGMA para configurar comportamento do SQLite:
const sqlite = new SQL("sqlite://app.db");
// Habilitar chaves estrangeiras
await sqlite`PRAGMA foreign_keys = ON`;
// Definir modo de journal para WAL para melhor concorrência
await sqlite`PRAGMA journal_mode = WAL`;
// Verificar integridade
const integrity = await sqlite`PRAGMA integrity_check`;Diferenças de Tipos de Dados
SQLite tem um sistema de tipos mais flexível que PostgreSQL:
// SQLite armazena dados em 5 classes de armazenamento: NULL, INTEGER, REAL, TEXT, BLOB
const sqlite = new SQL("sqlite://app.db");
// SQLite é mais leniente com tipos
await sqlite`
CREATE TABLE flexible (
id INTEGER PRIMARY KEY,
data TEXT, -- Pode armazenar números como strings
value NUMERIC, -- Pode armazenar inteiros, reais ou texto
blob BLOB -- Dados binários
)
`;
// Valores JavaScript são automaticamente convertidos
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;Transações
Para iniciar uma nova transação, use sql.begin. Este método funciona tanto para PostgreSQL quanto para SQLite. Para PostgreSQL, reserva uma conexão dedicada do pool. Para SQLite, inicia uma transação na conexão única.
O comando BEGIN é enviado automaticamente, incluindo quaisquer configurações opcionais que você especificar. Se um erro ocorrer durante a transação, um ROLLBACK é acionado para garantir que o processo continue suavemente.
Transações Básicas
await sql.begin(async tx => {
// Todas as queries nesta função rodam em uma transação
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;
// Transação automaticamente faz commit se nenhum erro for lançado
// Faz rollback se qualquer erro ocorrer
});Também é possível pipeline das requests em uma transação se necessário retornando um array com queries da função callback assim:
await sql.begin(async tx => {
return [
tx`INSERT INTO users (name) VALUES (${"Alice"})`,
tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`,
];
});Savepoints
Savepoints em SQL criam checkpoints intermediários dentro de uma transação, permitindo rollbacks parciais sem afetar toda a operação. São úteis em transações complexas, permitindo recuperação de erros e mantendo resultados consistentes.
await sql.begin(async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx.savepoint(async sp => {
// Esta parte pode ser rolled back separadamente
await sp`UPDATE users SET status = 'active'`;
if (someCondition) {
throw new Error("Rollback para savepoint");
}
});
// Continua com transação mesmo se savepoint rolled back
await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});Transações Distribuídas
Two-Phase Commit (2PC) é um protocolo de transação distribuída onde a Fase 1 tem o coordenador preparando nós garantindo que dados estão escritos e prontos para commit, enquanto a Fase 2 finaliza com nós fazendo commit ou rollback baseado na decisão do coordenador. Este processo garante durabilidade de dados e gerenciamento adequado de locks.
Em PostgreSQL e MySQL, transações distribuídas persistem além de sua sessão original, permitindo que usuários ou coordenadores privilegiados façam commit ou rollback delas depois. Isso suporta transações distribuídas robustas, processos de recuperação e operações administrativas.
Cada sistema de banco de dados implementa transações distribuídas de forma diferente:
PostgreSQL suporta nativamente através de transações preparadas, enquanto MySQL usa Transações XA.
Se qualquer exceção ocorrer durante a transação distribuída e não for capturada, o sistema automaticamente fará rollback de todas as mudanças. Quando tudo procede normalmente, você mantém a flexibilidade de fazer commit ou rollback da transação depois.
// Inicia uma transação distribuída
await sql.beginDistributed("tx1", async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});
// Depois, faz commit ou rollback
await sql.commitDistributed("tx1");
// ou
await sql.rollbackDistributed("tx1");Autenticação
Bun suporta autenticação SCRAM-SHA-256 (SASL), MD5 e Clear Text. SASL é recomendado para melhor segurança. Verifique Autenticação SASL do Postgres para mais informações.
Visão Geral de Modos SSL
PostgreSQL suporta diferentes modos SSL/TLS para controlar como conexões seguras são estabelecidas. Estes modos determinam o comportamento ao conectar e o nível de verificação de certificado realizado.
const sql = new SQL({
hostname: "localhost",
username: "user",
password: "password",
ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});| Modo SSL | Descrição |
|---|---|
disable | Nenhum SSL/TLS usado. Conexões falham se servidor requer SSL. |
prefer | Tenta SSL primeiro, fallback para não-SSL se SSL falhar. Modo padrão se nenhum especificado. |
require | Requer SSL sem verificação de certificado. Falha se SSL não puder ser estabelecido. |
verify-ca | Verifica se certificado do servidor é assinado por CA confiável. Falha se verificação falhar. |
verify-full | Modo mais seguro. Verifica certificado e correspondência de hostname. Protege contra certificados não confiáveis e ataques MITM. |
Usando Com Strings de Conexão
O modo SSL também pode ser especificado em strings de conexão:
// Usando modo prefer
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");
// Usando modo verify-full
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=verify-full");Pooling de Conexões
O cliente SQL do Bun gerencia automaticamente um pool de conexões, que é um pool de conexões de banco de dados que são reutilizadas para múltiplas queries. Isso ajuda a reduzir o overhead de estabelecer e fechar conexões para cada query, e também ajuda a gerenciar o número de conexões concorrentes ao banco de dados.
const sql = new SQL({
// Configuração do pool
max: 20, // Máximo 20 conexões concorrentes
idleTimeout: 30, // Fecha conexões ociosas após 30s
maxLifetime: 3600, // Tempo de vida máximo da conexão 1 hora
connectionTimeout: 10, // Timeout de conexão 10s
});Nenhuma conexão será feita até que uma query seja feita.
const sql = Bun.SQL(); // nenhuma conexão é criada
await sql`...`; // pool é iniciado até max ser atingido (se possível), primeira conexão disponível é usada
await sql`...`; // conexão anterior é reutilizada
// duas conexões são usadas agora ao mesmo tempo
await Promise.all([
sql`INSERT INTO users ${sql({ name: "Alice" })}`,
sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);
await sql.close(); // aguarda todas as queries terminarem e fecha todas as conexões do pool
await sql.close({ timeout: 5 }); // espera 5 segundos e fecha todas as conexões do pool
await sql.close({ timeout: 0 }); // fecha todas as conexões do pool imediatamenteConexões Reservadas
Bun permite reservar uma conexão do pool e retorna um cliente que envolve a conexão única. Isso pode ser usado para rodar queries em uma conexão isolada.
// Obtém conexão exclusiva do pool
const reserved = await sql.reserve();
try {
await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
// Importante: Libera conexão de volta ao pool
reserved.release();
}
// Ou usando Symbol.dispose
{
using reserved = await sql.reserve();
await reserved`SELECT 1`;
} // Automaticamente liberadaPrepared Statements
Por padrão, o cliente SQL do Bun automaticamente cria prepared statements nomeados para queries onde pode ser inferido que a query é estática. Isso fornece melhor performance. No entanto, você pode mudar este comportamento definindo prepare: false nas opções de conexão:
const sql = new SQL({
// ... outras opções ...
prepare: false, // Desabilita persistência de prepared statements nomeados no servidor
});Quando prepare: false está definido:
Queries ainda são executadas usando o protocolo "extended", mas são executadas usando prepared statements sem nome, um prepared statement sem nome dura apenas até o próximo statement Parse especificando o statement sem nome como destino ser emitido.
- Binding de parâmetros ainda é seguro contra injeção de SQL
- Cada query é parseada e planejada do zero pelo servidor
- Queries não serão pipelined
Você pode querer usar prepare: false quando:
- Usando PGBouncer em modo transaction (embora desde PGBouncer 1.21.0, prepared statements nomeados em nível de protocolo são suportados quando configurados apropriadamente)
- Debugando planos de execução de query
- Trabalhando com SQL dinâmico onde planos de query precisam ser regenerados frequentemente
- Mais de um comando por query não será suportado (a menos que você use
sql``.simple())
Note que desabilitar prepared statements pode impactar performance para queries que são executadas frequentemente com parâmetros diferentes, pois o servidor precisa parsear e planejar cada query do zero.
Tratamento de Erros
O cliente fornece erros tipados para diferentes cenários de falha. Erros são específicos do banco de dados e estendem de classes de erro base:
Classes de Erro
import { SQL } from "bun";
try {
await sql`SELECT * FROM users`;
} catch (error) {
if (error instanceof SQL.PostgresError) {
// Erro específico do PostgreSQL
console.log(error.code); // Código de erro do PostgreSQL
console.log(error.detail); // Mensagem de erro detalhada
console.log(error.hint); // Dica útil do PostgreSQL
} else if (error instanceof SQL.SQLiteError) {
// Erro específico do SQLite
console.log(error.code); // Código de erro do SQLite (e.g., "SQLITE_CONSTRAINT")
console.log(error.errno); // Número de erro do SQLite
console.log(error.byteOffset); // Offset de bytes no statement SQL (se disponível)
} else if (error instanceof SQL.SQLError) {
// Erro SQL genérico (classe base)
console.log(error.message);
}
}Códigos de Erro Específicos do PostgreSQL">
Erros de Conexão PostgreSQL
| Erros de Conexão | Descrição |
|---|---|
ERR_POSTGRES_CONNECTION_CLOSED | Conexão foi terminada ou nunca estabelecida |
ERR_POSTGRES_CONNECTION_TIMEOUT | Falha ao estabelecer conexão dentro do timeout |
ERR_POSTGRES_IDLE_TIMEOUT | Conexão fechada por inatividade |
ERR_POSTGRES_LIFETIME_TIMEOUT | Conexão excedeu tempo de vida máximo |
ERR_POSTGRES_TLS_NOT_AVAILABLE | Conexão SSL/TLS não disponível |
ERR_POSTGRES_TLS_UPGRADE_FAILED | Falha ao upgrade de conexão para SSL/TLS |
Erros de Autenticação
| Erros de Autenticação | Descrição |
|---|---|
ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2 | Autenticação de senha falhou |
ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHOD | Servidor solicitou método auth desconhecido |
ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHOD | Servidor solicitou método auth não suportado |
ERR_POSTGRES_INVALID_SERVER_KEY | Chave de servidor inválida durante autenticação |
ERR_POSTGRES_INVALID_SERVER_SIGNATURE | Assinatura de servidor inválida |
ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64 | Codificação de assinatura SASL inválida |
ERR_POSTGRES_SASL_SIGNATURE_MISMATCH | Verificação de assinatura SASL falhou |
Erros de Query
| Erros de Query | Descrição |
|---|---|
ERR_POSTGRES_SYNTAX_ERROR | Sintaxe SQL inválida (estende SyntaxError) |
ERR_POSTGRES_SERVER_ERROR | Erro geral do servidor PostgreSQL |
ERR_POSTGRES_INVALID_QUERY_BINDING | Binding de parâmetro inválido |
ERR_POSTGRES_QUERY_CANCELLED | Query foi cancelada |
ERR_POSTGRES_NOT_TAGGED_CALL | Query foi chamada sem tagged call |
Erros de Tipo de Dado
| Erros de Tipo de Dado | Descrição |
|---|---|
ERR_POSTGRES_INVALID_BINARY_DATA | Formato de dados binários inválido |
ERR_POSTGRES_INVALID_BYTE_SEQUENCE | Sequência de bytes inválida |
ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODING | Erro de encoding |
ERR_POSTGRES_INVALID_CHARACTER | Caractere inválido nos dados |
ERR_POSTGRES_OVERFLOW | Overflow numérico |
ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMAT | Formato binário não suportado |
ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZE | Tamanho de inteiro não suportado |
ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YET | Arrays multidimensionais não suportados |
ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YET | Valores NULL em arrays não suportados |
Erros de Protocolo
| Erros de Protocolo | Descrição |
|---|---|
ERR_POSTGRES_EXPECTED_REQUEST | Esperado request do cliente |
ERR_POSTGRES_EXPECTED_STATEMENT | Esperado prepared statement |
ERR_POSTGRES_INVALID_BACKEND_KEY_DATA | Dados de chave de backend inválidos |
ERR_POSTGRES_INVALID_MESSAGE | Mensagem de protocolo inválida |
ERR_POSTGRES_INVALID_MESSAGE_LENGTH | Comprimento de mensagem inválido |
ERR_POSTGRES_UNEXPECTED_MESSAGE | Tipo de mensagem inesperado |
Erros de Transação
| Erros de Transação | Descrição |
|---|---|
ERR_POSTGRES_UNSAFE_TRANSACTION | Operação de transação insegura detectada |
ERR_POSTGRES_INVALID_TRANSACTION_STATE | Estado de transação inválido |
Erros Específicos do SQLite
Erros do SQLite fornecem códigos e números de erro que correspondem aos códigos de erro padrão do SQLite:
Códigos de Erro Comuns do SQLite">
| Código de Erro | errno | Descrição |
|---|---|---|
SQLITE_CONSTRAINT | 19 | Violação de constraint (UNIQUE, CHECK, NOT NULL, etc.) |
SQLITE_BUSY | 5 | Banco de dados está travado |
SQLITE_LOCKED | 6 | Tabela no banco de dados está travada |
SQLITE_READONLY | 8 | Tentativa de escrita em banco apenas leitura |
SQLITE_IOERR | 10 | Erro de I/O de disco |
SQLITE_CORRUPT | 11 | Imagem de disco do banco está malformada |
SQLITE_FULL | 13 | Banco de dados ou disco está cheio |
SQLITE_CANTOPEN | 14 | Incapaz de abrir arquivo de banco de dados |
SQLITE_PROTOCOL | 15 | Erro de protocolo de lock do banco de dados |
SQLITE_SCHEMA | 17 | Schema do banco de dados mudou |
SQLITE_TOOBIG | 18 | String ou BLOB excede limite de tamanho |
SQLITE_MISMATCH | 20 | Incompatibilidade de tipo de dado |
SQLITE_MISUSE | 21 | Biblioteca usada incorretamente |
SQLITE_AUTH | 23 | Autorização negada |
Exemplo de tratamento de erro:
const sqlite = new SQL("sqlite://app.db");
try {
await sqlite`INSERT INTO users (id, name) VALUES (1, 'Alice')`;
await sqlite`INSERT INTO users (id, name) VALUES (1, 'Bob')`; // ID duplicado
} catch (error) {
if (error instanceof SQL.SQLiteError) {
if (error.code === "SQLITE_CONSTRAINT") {
console.log("Violação de constraint:", error.message);
// Lida com violação de constraint unique
}
}
}Números e BigInt
O cliente SQL do Bun inclui manipulação especial para números grandes que excedem o range de um inteiro de 53-bit. Veja como funciona:
import { sql } from "bun";
const [{ x, y }] = await sql`SELECT 9223372036854777 as x, 12345 as y`;
console.log(typeof x, x); // "string" "9223372036854777"
console.log(typeof y, y); // "number" 12345BigInt em vez de Strings
Se você precisa de números grandes como BigInt em vez de strings, pode habilitar isso definindo a opção bigint como true ao inicializar o cliente SQL:
const sql = new SQL({
bigint: true,
});
const [{ x }] = await sql`SELECT 9223372036854777 as x`;
console.log(typeof x, x); // "bigint" 9223372036854777nRoadmap
Ainda há algumas coisas que não terminamos.
- Pré-carregamento de conexão via flag
--db-preconnectdo Bun CLI - Transforms de nome de coluna (e.g.
snake_caseparacamelCase). Isso está majoritariamente bloqueado em uma implementação unicode-aware de mudança de case em C++ usandoWTF::Stringdo WebKit. - Transforms de tipo de coluna
Recursos Específicos do Banco de Dados
Métodos de Autenticação
MySQL suporta múltiplos plugins de autenticação que são automaticamente negociados:
mysql_native_password- Autenticação MySQL tradicional, amplamente compatívelcaching_sha2_password- Padrão no MySQL 8.0+, mais seguro com troca de chave RSAsha256_password- Autenticação baseada em SHA-256
O cliente automaticamente lida com troca de plugin de autenticação quando solicitado pelo servidor, incluindo troca segura de senha sobre conexões não-SSL.
Prepared Statements & Performance
MySQL usa prepared statements do lado do servidor para todas as queries parametrizadas:
// Isto automaticamente cria um prepared statement no servidor
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;
// Prepared statements são cacheados e reutilizados para queries idênticas
for (const id of userIds) {
// Mesmo prepared statement é reutilizado
await mysql`SELECT * FROM users WHERE id = ${id}`;
}
// Query pipelining - múltiplos statements enviados sem esperar
const [users, orders, products] = await Promise.all([
mysql`SELECT * FROM users WHERE active = ${true}`,
mysql`SELECT * FROM orders WHERE status = ${"pending"}`,
mysql`SELECT * FROM products WHERE in_stock = ${true}`,
]);Múltiplos Result Sets
MySQL pode retornar múltiplos result sets de queries multi-statement:
const mysql = new SQL("mysql://user:pass@localhost/mydb");
// Queries multi-statement com método simple()
const multiResults = await mysql`
SELECT * FROM users WHERE id = 1;
SELECT * FROM orders WHERE user_id = 1;
`.simple();Character Sets & Collations
Bun.SQL automaticamente usa character set utf8mb4 para conexões MySQL, garantindo suporte Unicode completo incluindo emojis. Este é o character set recomendado para aplicações MySQL modernas.
Atributos de Conexão
Bun automaticamente envia informações do cliente para MySQL para melhor monitoramento:
// Estes atributos são enviados automaticamente:
// _client_name: "Bun"
// _client_version: <versão do bun>
// Você pode ver estes no performance_schema.session_connect_attrs do MySQLManipulação de Tipos
Tipos do MySQL são automaticamente convertidos para tipos JavaScript:
| Tipo MySQL | Tipo JavaScript | Notas |
|---|---|---|
| INT, TINYINT, MEDIUMINT | number | Dentro do range seguro de inteiros |
| BIGINT | string, number ou BigInt | Se o valor cabe em i32/u32 será number caso contrário string ou BigInt Baseado na opção bigint |
| DECIMAL, NUMERIC | string | Para preservar precisão |
| FLOAT, DOUBLE | number | |
| DATE | Date | Objeto Date JavaScript |
| DATETIME, TIMESTAMP | Date | Com manipulação de timezone |
| TIME | number | Total de microssegundos |
| YEAR | number | |
| CHAR, VARCHAR, VARSTRING, STRING | string | |
| TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXT | string | |
| TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOB | string | Tipos BLOB são alias para tipos TEXT |
| JSON | object/array | Automaticamente parseado |
| BIT(1) | boolean | BIT(1) no MySQL |
| GEOMETRY | string | Dados de geometria |
Diferenças do PostgreSQL
Embora a API seja unificada, há algumas diferenças comportamentais:
- Placeholders de parâmetro: MySQL usa
?internamente mas Bun converte estilo$1, $2automaticamente - Cláusula RETURNING: MySQL não suporta RETURNING; use
result.lastInsertRowidou SELECT separado - Tipos de array: MySQL não tem tipos de array nativos como PostgreSQL
Recursos Específicos do MySQL
Ainda não implementamos suporte a LOAD DATA INFILE
Recursos Específicos do PostgreSQL
Ainda não implementamos estes:
- Suporte a
COPY - Suporte a
LISTEN - Suporte a
NOTIFY
Também não implementamos alguns dos recursos mais incomuns como:
- Autenticação GSSAPI
- Suporte a
SCRAM-SHA-256-PLUS - Tipos Point & PostGIS
- Todos os tipos de array de inteiros multidimensionais (apenas alguns tipos são suportados)
Padrões Comuns & Melhores Práticas
Trabalhando com Result Sets do MySQL
// Obtendo ID de insert após INSERT
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // LAST_INSERT_ID() do MySQL
// Lidando com linhas afetadas
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // Número de linhas atualizadas
// Usando funções específicas do MySQL
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;Tratamento de Erros do MySQL
try {
await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
if (error.code === "ER_DUP_ENTRY") {
console.log("Entrada duplicada detectada");
} else if (error.code === "ER_ACCESS_DENIED_ERROR") {
console.log("Acesso negado");
} else if (error.code === "ER_BAD_DB_ERROR") {
console.log("Banco de dados não existe");
}
// Códigos de erro do MySQL são compatíveis com pacotes mysql/mysql2
}Dicas de Performance para MySQL
- Use pooling de conexões: Defina tamanho de pool
maxapropriado baseado na sua carga de trabalho - Habilite prepared statements: Eles são habilitados por padrão e melhoram performance
- Use transações para operações em massa: Agrupe queries relacionadas em transações
- Indexe apropriadamente: MySQL depende fortemente de índices para performance de query
- Use charset
utf8mb4: É definido por padrão e manipula todos os caracteres Unicode
Perguntas Frequentes
Por que isso é `Bun.sql` e não `Bun.postgres`?
O plano era adicionar mais drivers de banco de dados no futuro. Agora com suporte MySQL adicionado, esta API unificada suporta PostgreSQL, MySQL e SQLite.
Como sei qual adapter de banco de dados está sendo usado?
O adapter é automaticamente detectado da string de conexão:
- URLs começando com `mysql://` ou `mysql2://` usam MySQL
- URLs correspondendo padrões SQLite (`:memory:`, `sqlite://`, `file://`) usam SQLite
- Todo resto padrão é PostgreSQL
Stored procedures do MySQL são suportadas?">
Sim, stored procedures são totalmente suportadas incluindo parâmetros OUT e múltiplos result sets:
```ts
// Chama stored procedure
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;
// Obtém parâmetro OUT
const outParam = await mysql`SELECT @total_orders as total`;
```
Posso usar sintaxe SQL específica do MySQL?">
Sim, você pode usar qualquer sintaxe específica do MySQL:
```ts
// Sintaxe específica do MySQL funciona bem
await mysql`SET @user_id = ${userId}`;
await mysql`SHOW TABLES`;
await mysql`DESCRIBE users`;
await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
```
Por que não apenas usar uma biblioteca existente?
Pacotes npm como postgres.js, pg e node-postgres podem ser usados no Bun também. São ótimas opções.
Duas razões:
- Achamos que é mais simples para desenvolvedores ter um driver de banco de dados built-in no Bun. O tempo que você gasta library shopping é tempo que poderia estar construindo seu app.
- Aproveitamos alguns internals do engine JavaScriptCore para torná-lo mais rápido para criar objetos que seriam difíceis de implementar em uma biblioteca
Créditos
Grandes agradecimentos a @porsager pelo postgres.js pela inspiração para a interface da API.