Skip to content

O Bun implementa nativamente um driver SQLite3 de alta performance. Para usá-lo, importe do módulo built-in bun:sqlite.

ts
import { Database } from "bun:sqlite";

const db = new Database(":memory:");
const query = db.query("select 'Hello world' as message;");
query.get();
txt
{ message: "Hello world" }

A API é simples, síncrona e rápida. Créditos ao better-sqlite3 e seus contribuidores por inspirar a API do bun:sqlite.

Recursos incluem:

  • Transações
  • Parâmetros (nomeados e posicionais)
  • Prepared statements
  • Conversões de tipo de dado (BLOB vira Uint8Array)
  • Mapeie resultados de query para classes sem ORM - query.as(MyClass)
  • A performance mais rápida de qualquer driver SQLite para JavaScript
  • Suporte a bigint
  • Statements multi-query (e.g. SELECT 1; SELECT 2;) em uma única chamada para database.run(query)

O módulo bun:sqlite é aproximadamente 3-6x mais rápido que better-sqlite3 e 8-9x mais rápido que deno.land/x/sqlite para queries de leitura. Cada driver foi benchmarked contra o dataset Northwind Traders. Veja e execute o código fonte do benchmark.


Database

Para abrir ou criar um banco de dados SQLite3:

ts
import { Database } from "bun:sqlite";

const db = new Database("mydb.sqlite");

Para abrir um banco de dados em memória:

ts
import { Database } from "bun:sqlite";

// todos estes fazem a mesma coisa
const db = new Database(":memory:");
const db = new Database();
const db = new Database("");

Para abrir em modo readonly:

ts
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { readonly: true });

Para criar o banco de dados se o arquivo não existir:

ts
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { create: true });

Modo estrito

Por padrão, bun:sqlite requer que parâmetros de binding incluam o prefixo $, :, ou @, e não lança erro se um parâmetro estiver faltando.

Para lançar erro quando um parâmetro estiver faltando e permitir binding sem prefixo, defina strict: true no construtor Database:

ts
import { Database } from "bun:sqlite";

const strict = new Database(":memory:", { strict: true });

// lança erro por causa do erro de digitação:
const query = strict.query("SELECT $message;").all({ messag: "Hello world" });

const notStrict = new Database(":memory:");
// não lança erro:
notStrict.query("SELECT $message;").all({ messag: "Hello world" });

Carregar via import de módulo ES

Você também pode usar um atributo de import para carregar um banco de dados.

ts
import db from "./mydb.sqlite" with { type: "sqlite" };

console.log(db.query("select * from users LIMIT 1").get());

Isto é equivalente ao seguinte:

ts
import { Database } from "bun:sqlite";
const db = new Database("./mydb.sqlite");

.close(throwOnError: boolean = false)

Para fechar uma conexão de banco de dados, mas permitir que queries existentes terminem, chame .close(false):

ts
const db = new Database();
// ... faça coisas
db.close(false);

Para fechar o banco de dados e lançar erro se houver queries pendentes, chame .close(true):

ts
const db = new Database();
// ... faça coisas
db.close(true);

NOTE

`close(false)` é chamado automaticamente quando o banco de dados é garbage collected. É seguro chamar múltiplas vezes mas não tem efeito após a primeira.

Statement using

Você pode usar o statement using para garantir que uma conexão de banco de dados seja fechada quando o bloco using for encerrado.

ts
import { Database } from "bun:sqlite";

{
  using db = new Database("mydb.sqlite");
  using query = db.query("select 'Hello world' as message;");
  console.log(query.get());
}
txt
{ message: "Hello world" }

.serialize()

bun:sqlite suporta o mecanismo built-in do SQLite para serializar e deserializar bancos de dados para e da memória.

ts
const olddb = new Database("mydb.sqlite");
const contents = olddb.serialize(); // => Uint8Array
const newdb = Database.deserialize(contents);

Internamente, .serialize() chama sqlite3_serialize.

.query()

Use o método db.query() na sua instância Database para preparar uma query SQL. O resultado é uma instância Statement que será cacheada na instância Database. A query não será executada.

ts
const query = db.query(`select "Hello world" as message`);

NOTE

**O que significa "cached"?**

O caching se refere ao prepared statement compilado (o bytecode SQL), não aos resultados da query. Quando você chama db.query() com a mesma string SQL múltiplas vezes, o Bun retorna o mesmo objeto Statement cacheado em vez de recompilar o SQL.

É completamente seguro reutilizar um statement cacheado com diferentes valores de parâmetro:

ts
const query = db.query("SELECT * FROM users WHERE id = ?");
query.get(1); // ✓ Funciona
query.get(2); // ✓ Também funciona - parâmetros são vinculados fresh cada vez
query.get(3); // ✓ Ainda funciona

Use .prepare() em vez de .query() quando quiser uma instância Statement fresh que não é cacheada, por exemplo se você está gerando SQL dinamicamente e não quer preencher o cache com queries one-off.

ts
// compila o prepared statement sem cache
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");

Modo WAL

SQLite suporta modo write-ahead log (WAL) que dramaticamente melhora performance, especialmente em situações com muitos leitores concorrentes e um único escritor. É amplamente recomendado habilitar modo WAL para a maioria das aplicações típicas.

Para habilitar modo WAL, execute esta query pragma no início da sua aplicação:

ts
db.run("PRAGMA journal_mode = WAL;");

O que é modo WAL?">

No modo WAL, escritas no banco de dados são escritas diretamente em um arquivo separado chamado "arquivo WAL" (write-ahead log). Este arquivo será integrado posteriormente no arquivo principal do banco de dados. Pense nisso como um buffer para escritas pendentes. Consulte a documentação do SQLite para uma visão geral mais detalhada.

No macOS, arquivos WAL podem ser persistentes por padrão. Isto não é um bug, é como o macOS configurou a versão do sistema do SQLite.


Statements

Um Statement é uma query preparada, o que significa que foi parseada e compilada em uma forma binária eficiente. Pode ser executada múltiplas vezes de forma performática.

Crie um statement com o método .query na sua instância Database.

ts
const query = db.query(`select "Hello world" as message`);

Queries podem conter parâmetros. Estes podem ser numéricos (?1) ou nomeados ($param ou :param ou @param).

ts
const query = db.query(`SELECT ?1, ?2;`);
const query = db.query(`SELECT $param1, $param2;`);

Valores são vinculados a estes parâmetros quando a query é executada. Um Statement pode ser executado com vários métodos diferentes, cada um retornando os resultados em uma forma diferente.

Vinculando valores

Para vincular valores a um statement, passe um objeto para o método .all(), .get(), .run(), ou .values().

ts
const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });

Você pode vincular usando parâmetros posicionais também:

ts
const query = db.query(`select ?1;`);
query.all("Hello world");

strict: true permite vincular valores sem prefixos

Por padrão, os prefixos $, :, e @ são incluídos ao vincular valores a parâmetros nomeados. Para vincular sem estes prefixos, use a opção strict no construtor Database.

ts
import { Database } from "bun:sqlite";

const db = new Database(":memory:", {
  // vincula valores sem prefixos
  strict: true, 
});

const query = db.query(`select $message;`);

// strict: true
query.all({ message: "Hello world" });

// strict: false
// query.all({ $message: "Hello world" });

.all()

Use .all() para executar uma query e obter os resultados como um array de objetos.

ts
const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });
txt
[{ message: "Hello world" }]

Internamente, isto chama sqlite3_reset e repetidamente chama sqlite3_step até retornar SQLITE_DONE.

.get()

Use .get() para executar uma query e obter o primeiro resultado como um objeto.

ts
const query = db.query(`select $message;`);
query.get({ $message: "Hello world" });
txt
{ $message: "Hello world" }

Internamente, isto chama sqlite3_reset seguido por sqlite3_step até não retornar mais SQLITE_ROW. Se a query não retornar linhas, undefined é retornado.

.run()

Use .run() para executar uma query e obter undefined. Isto é útil para queries que modificam schema (e.g. CREATE TABLE) ou operações de escrita em massa.

ts
const query = db.query(`create table foo;`);
query.run();
txt
{
  lastInsertRowid: 0,
  changes: 0,
}

Internamente, isto chama sqlite3_reset e chama sqlite3_step uma vez. Percorrer todas as linhas não é necessário quando você não se importa com os resultados.

A propriedade lastInsertRowid retorna o ID da última linha inserida no banco de dados. A propriedade changes é o número de linhas afetadas pela query.

.as(Class) - Mapeie resultados de query para uma classe

Use .as(Class) para executar uma query e obter os resultados como instâncias de uma classe. Isto permite anexar métodos & getters/setters aos resultados.

ts
class Movie {
  title: string;
  year: number;

  get isMarvel() {
    return this.title.includes("Marvel");
  }
}

const query = db.query("SELECT title, year FROM movies").as(Movie);
const movies = query.all();
const first = query.get();

console.log(movies[0].isMarvel);
console.log(first.isMarvel);
txt
true
true

Como uma otimização de performance, o construtor da classe não é chamado, inicializadores padrão não são executados, e campos privados não são acessíveis. Isto é mais como usar Object.create que new. O prototype da classe é atribuído ao objeto, métodos são anexados, e getters/setters são configurados, mas o construtor não é chamado.

As colunas do banco de dados são definidas como propriedades na instância da classe.

.iterate() (@@iterator)

Use .iterate() para executar uma query e incrementalmente retornar resultados. Isto é útil para grandes conjuntos de resultados que você quer processar uma linha por vez sem carregar todos os resultados na memória.

ts
const query = db.query("SELECT * FROM foo");
for (const row of query.iterate()) {
  console.log(row);
}

Você também pode usar o protocolo @@iterator:

ts
const query = db.query("SELECT * FROM foo");
for (const row of query) {
  console.log(row);
}

.values()

Use values() para executar uma query e obter todos os resultados como um array de arrays.

ts
const query = db.query(`select $message;`);

query.values({ $message: "Hello world" });
query.values(2);
txt
[
  [ "Iron Man", 2008 ],
  [ "The Avengers", 2012 ],
  [ "Ant-Man: Quantumania", 2023 ],
]

Internamente, isto chama sqlite3_reset e repetidamente chama sqlite3_step até retornar SQLITE_DONE.

.finalize()

Use .finalize() para destruir um Statement e liberar quaisquer recursos associados a ele. Uma vez finalizado, um Statement não pode ser executado novamente. Tipicamente, o garbage collector fará isto para você, mas finalização explícita pode ser útil em aplicações sensíveis a performance.

ts
const query = db.query("SELECT title, year FROM movies");
const movies = query.all();
query.finalize();

.toString()

Chamar toString() em uma instância Statement imprime a query SQL expandida. Isto é útil para debug.

ts
import { Database } from "bun:sqlite";

// setup
const query = db.query("SELECT $param;");

console.log(query.toString()); // => "SELECT NULL"

query.run(42);
console.log(query.toString()); // => "SELECT 42"

query.run(365);
console.log(query.toString()); // => "SELECT 365"

Internamente, isto chama sqlite3_expanded_sql. Os parâmetros são expandidos usando os valores vinculados mais recentemente.

Parâmetros

Queries podem conter parâmetros. Estes podem ser numéricos (?1) ou nomeados ($param ou :param ou @param). Vincule valores a estes parâmetros ao executar a query:

query.ts
ts
const query = db.query("SELECT * FROM foo WHERE bar = $bar");
const results = query.all({
  $bar: "bar",
});
txt
[{ "$bar": "bar" }]

Parâmetros numerados (posicionais) também funcionam:

ts
const query = db.query("SELECT ?1, ?2");
const results = query.all("hello", "goodbye");
txt
[
	{
		"?1": "hello",
		"?2": "goodbye",
	},
];

Inteiros

SQLite suporta inteiros de 64 bits com sinal, mas JavaScript só suporta inteiros de 52 bits com sinal ou inteiros de precisão arbitrária com bigint.

Input bigint é suportado em todos os lugares, mas por padrão bun:sqlite retorna inteiros como tipos number. Se você precisa lidar com inteiros maiores que 2^53, defina a opção safeIntegers como true ao criar uma instância Database. Isto também valida que bigint passados para bun:sqlite não excedem 64 bits.

Por padrão, bun:sqlite retorna inteiros como tipos number. Se você precisa lidar com inteiros maiores que 2^53, você pode usar o tipo bigint.

safeIntegers: true

Quando safeIntegers é true, bun:sqlite retornará inteiros como tipos bigint:

ts
import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: true });
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
const result = query.get();

console.log(result.max_int);
txt
9007199254741093n

Quando safeIntegers é true, bun:sqlite lançará erro se um valor bigint em um parâmetro vinculado exceder 64 bits:

ts
import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: true });
db.run("CREATE TABLE test (id INTEGER PRIMARY KEY, value INTEGER)");

const query = db.query("INSERT INTO test (value) VALUES ($value)");

try {
  query.run({ $value: BigInt(Number.MAX_SAFE_INTEGER) ** 2n });
} catch (e) {
  console.log(e.message);
}
txt
Valor BigInt '81129638414606663681390495662081' está fora do range

safeIntegers: false (padrão)

Quando safeIntegers é false, bun:sqlite retornará inteiros como tipos number e truncará quaisquer bits além de 53:

ts
import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: false });
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
const result = query.get();
console.log(result.max_int);
txt
9007199254741092

Transações

Transações são um mecanismo para executar múltiplas queries de forma atômica; isto é, ou todas as queries têm sucesso ou nenhuma tem. Crie uma transação com o método db.transaction():

ts
const insertCat = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
  for (const cat of cats) insertCat.run(cat);
});

Neste ponto, não inserimos nenhum gato! A chamada para db.transaction() retorna uma nova função (insertCats) que envolve a função que executa as queries.

Para executar a transação, chame esta função. Todos os argumentos serão passados para a função envolvida; o valor de retorno da função envolvida será retornado pela função de transação. A função envolvida também tem acesso ao contexto this conforme definido onde a transação é executada.

ts
const insert = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
  for (const cat of cats) insert.run(cat);
  return cats.length;
});

const count = insertCats([{ $name: "Keanu" }, { $name: "Salem" }, { $name: "Crookshanks" }]);

console.log(`Inserido ${count} gatos`);

O driver automaticamente begin uma transação quando insertCats é chamado e faz commit quando a função envolvida retorna. Se uma exceção for lançada, a transação será rolled back. A exceção propagará como normal; não é capturada.

NOTE

**Transações aninhadas** — Funções de transação podem ser chamadas de dentro de outras funções de transação. Ao fazer isso, a transação interna se torna um [savepoint](https://www.sqlite.org/lang_savepoint.html).

Ver exemplo de transação aninhada">

ts
// setup
import { Database } from "bun:sqlite";
const db = Database.open(":memory:");
db.run("CREATE TABLE expenses (id INTEGER PRIMARY KEY AUTOINCREMENT, note TEXT, dollars INTEGER);");
db.run("CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)");
const insertExpense = db.prepare("INSERT INTO expenses (note, dollars) VALUES (?, ?)");
const insert = db.prepare("INSERT INTO cats (name, age) VALUES ($name, $age)");
const insertCats = db.transaction(cats => {
  for (const cat of cats) insert.run(cat);
});

const adopt = db.transaction(cats => {
  insertExpense.run("adoption fees", 20);
  insertCats(cats); // transação aninhada
});

adopt([
  { $name: "Joey", $age: 2 },
  { $name: "Sally", $age: 4 },
  { $name: "Junior", $age: 1 },
]);

Transações também vêm com versões deferred, immediate, e exclusive.

ts
insertCats(cats); // usa "BEGIN"
insertCats.deferred(cats); // usa "BEGIN DEFERRED"
insertCats.immediate(cats); // usa "BEGIN IMMEDIATE"
insertCats.exclusive(cats); // usa "BEGIN EXCLUSIVE"

.loadExtension()

Para carregar uma extensão SQLite, chame .loadExtension(name) na sua instância Database

ts
import { Database } from "bun:sqlite";

const db = new Database();
db.loadExtension("myext");

NOTE

**Usuários MacOS** Por padrão, macOS vem com a build proprietária da Apple do SQLite, que não suporta extensões. Para usar extensões, você precisará instalar uma build vanilla do SQLite.
bash
brew install sqlite
which sqlite # obtém caminho do binário

Para apontar bun:sqlite para a nova build, chame Database.setCustomSQLite(path) antes de criar quaisquer instâncias Database. (Em outros sistemas operacionais, isto é no-op.) Passe um caminho para o arquivo .dylib do SQLite, não o executável. Com versões recentes do Homebrew isto é algo como /opt/homebrew/Cellar/sqlite/<version>/libsqlite3.dylib.

ts
import { Database } from "bun:sqlite";

Database.setCustomSQLite("/path/to/libsqlite.dylib");

const db = new Database();
db.loadExtension("myext");

.fileControl(cmd: number, value: any)

Para usar a API avançada sqlite3_file_control, chame .fileControl(cmd, value) na sua instância Database.

ts
import { Database, constants } from "bun:sqlite";

const db = new Database();
// Garante que modo WAL NÃO é persistente
// isto previne que arquivos wal fiquem lingering após o banco de dados ser fechado
db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);

value pode ser:

  • number
  • TypedArray
  • undefined ou null

Referência

ts
class Database {
  constructor(
    filename: string,
    options?:
      | number
      | {
          readonly?: boolean;
          create?: boolean;
          readwrite?: boolean;
          safeIntegers?: boolean;
          strict?: boolean;
        },
  );

  query<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
  prepare<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
  run(sql: string, params?: SQLQueryBindings): { lastInsertRowid: number; changes: number };
  exec = this.run;

  transaction(insideTransaction: (...args: any) => void): CallableFunction & {
    deferred: (...args: any) => void;
    immediate: (...args: any) => void;
    exclusive: (...args: any) => void;
  };

  close(throwOnError?: boolean): void;
}

class Statement<ReturnType, ParamsType> {
  all(...params: ParamsType[]): ReturnType[];
  get(...params: ParamsType[]): ReturnType | null;
  run(...params: ParamsType[]): {
    lastInsertRowid: number;
    changes: number;
  };
  values(...params: ParamsType[]): unknown[][];

  finalize(): void; // destroy statement and clean up resources
  toString(): string; // serialize to SQL

  columnNames: string[]; // os nomes das colunas do result set
  columnTypes: string[]; // tipos baseados em valores reais na primeira linha (chame .get()/.all() primeiro)
  declaredTypes: (string | null)[]; // tipos do schema CREATE TABLE (chame .get()/.all() primeiro)
  paramsCount: number; // o número de parâmetros esperados pelo statement
  native: any; // o objeto nativo representando o statement

  as<T>(Class: new (...args: any[]) => T): Statement<T, ParamsType>;
}

type SQLQueryBindings =
  | string
  | bigint
  | TypedArray
  | number
  | boolean
  | null
  | Record<string, string | bigint | TypedArray | number | boolean | null>;

Tipos de Dados

Tipo JavaScriptTipo SQLite
stringTEXT
numberINTEGER ou DECIMAL
booleanINTEGER (1 ou 0)
Uint8ArrayBLOB
BufferBLOB
bigintINTEGER
nullNULL

Bun by www.bunjs.com.cn edit