Bun implementiert nativ einen hochperformanten SQLite3 Treiber. Um ihn zu verwenden, importieren Sie aus dem eingebauten bun:sqlite Modul.
import { Database } from "bun:sqlite";
const db = new Database(":memory:");
const query = db.query("select 'Hello world' as message;");
query.get();{ message: "Hello world" }Die API ist einfach, synchron und schnell. Dank an better-sqlite3 und seine Mitwirkenden für die Inspiration der API von bun:sqlite.
Funktionen umfassen:
- Transaktionen
- Parameter (benannt und positionell)
- Vorbereitete Anweisungen
- Datentyp-Konvertierungen (
BLOBwird zuUint8Array) - Abfrageergebnisse auf Klassen mappen ohne ORM -
query.as(MyClass) - Die schnellste Performance aller SQLite-Treiber für JavaScript
bigint-Unterstützung- Multi-Query-Anweisungen (z.B.
SELECT 1; SELECT 2;) in einem einzigen Aufruf von database.run(query)
Das bun:sqlite Modul ist etwa 3-6x schneller als better-sqlite3 und 8-9x schneller als deno.land/x/sqlite für Leseabfragen. Jeder Treiber wurde gegen den Northwind Traders Datensatz gebenchmarkt. Sehen und führen Sie die Benchmark-Quelle aus.
Datenbank
Um eine SQLite3-Datenbank zu öffnen oder zu erstellen:
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite");Um eine In-Memory-Datenbank zu öffnen:
import { Database } from "bun:sqlite";
// alle diese tun das Gleiche
const db = new Database(":memory:");
const db = new Database();
const db = new Database("");Um im readonly-Modus zu öffnen:
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { readonly: true });Um die Datenbank zu erstellen, wenn die Datei nicht existiert:
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { create: true });Strenger Modus
Standardmäßig erfordert bun:sqlite, dass bindende Parameter das $, : oder @ Präfix enthalten, und wirft keinen Fehler, wenn ein Parameter fehlt.
Um stattdessen einen Fehler zu werfen, wenn ein Parameter fehlt, und das Binden ohne Präfix zu ermöglichen, setzen Sie strict: true im Database-Konstruktor:
import { Database } from "bun:sqlite";
const strict = new Database(":memory:", { strict: true });
// wirft Fehler wegen des Tippfehlers:
const query = strict.query("SELECT $message;").all({ messag: "Hello world" });
const notStrict = new Database(":memory:");
// wirft keinen Fehler:
notStrict.query("SELECT $message;").all({ messag: "Hello world" });Laden via ES-Modul-Import
Sie können auch ein Import-Attribut verwenden, um eine Datenbank zu laden.
import db from "./mydb.sqlite" with { type: "sqlite" };
console.log(db.query("select * from users LIMIT 1").get());Dies ist äquivalent zu folgendem:
import { Database } from "bun:sqlite";
const db = new Database("./mydb.sqlite");.close(throwOnError: boolean = false)
Um eine Datenbankverbindung zu schließen, aber bestehenden Abfragen zu erlauben, fertig zu werden, rufen Sie .close(false) auf:
const db = new Database();
// ... Dinge tun
db.close(false);Um die Datenbank zu schließen und einen Fehler zu werfen, wenn ausstehende Abfragen vorhanden sind, rufen Sie .close(true) auf:
const db = new Database();
// ... Dinge tun
db.close(true);NOTE
`close(false)` wird automatisch aufgerufen, wenn die Datenbank garbage collected wird. Es ist sicher, es mehrmals aufzurufen, hat aber nach dem ersten Mal keine Wirkung.using-Anweisung
Sie können die using-Anweisung verwenden, um sicherzustellen, dass eine Datenbankverbindung geschlossen wird, wenn der using-Block verlassen wird.
import { Database } from "bun:sqlite";
{
using db = new Database("mydb.sqlite");
using query = db.query("select 'Hello world' as message;");
console.log(query.get());
}{ message: "Hello world" }.serialize()
bun:sqlite unterstützt SQLite's eingebauten Mechanismus zum Serialisieren und Deserialisieren von Datenbanken in und aus dem Speicher.
const olddb = new Database("mydb.sqlite");
const contents = olddb.serialize(); // => Uint8Array
const newdb = Database.deserialize(contents);Intern ruft .serialize() sqlite3_serialize auf.
.query()
Verwenden Sie die db.query()-Methode auf Ihrer Database-Instanz, um eine SQL-Abfrage vorzubereiten. Das Ergebnis ist eine Statement-Instanz, die auf der Database-Instanz gecacht wird. Die Abfrage wird nicht ausgeführt.
const query = db.query(`select "Hello world" as message`);NOTE
**Was bedeutet "gecached"?**Das Caching bezieht sich auf die kompilierte vorbereitete Anweisung (die SQL-Bytecode), nicht auf die Abfrageergebnisse. Wenn Sie db.query() mehrmals mit demselben SQL-String aufrufen, gibt Bun dasselbe gecachte Statement-Objekt zurück, anstatt den SQL neu zu kompilieren.
Es ist völlig sicher, ein gecachtes Statement mit verschiedenen Parameterwerten wiederzuverwenden:
const query = db.query("SELECT * FROM users WHERE id = ?");
query.get(1); // ✓ Funktioniert
query.get(2); // ✓ Funktioniert auch - Parameter werden jedes Mal frisch gebunden
query.get(3); // ✓ Funktioniert immer nochVerwenden Sie .prepare() anstelle von .query(), wenn Sie eine frische Statement-Instanz möchten, die nicht gecacht ist, zum Beispiel wenn Sie dynamisch SQL generieren und den Cache nicht mit Einmal-Abfragen füllen möchten.
// die vorbereitete Anweisung ohne Caching kompilieren
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");WAL-Modus
SQLite unterstützt den Write-Ahead-Log-Modus (WAL), der die Performance dramatisch verbessert, besonders in Situationen mit vielen gleichzeitigen Lesern und einem einzelnen Schreiber. Es wird allgemein empfohlen, den WAL-Modus für die meisten typischen Anwendungen zu aktivieren.
Um den WAL-Modus zu aktivieren, führen Sie diese Pragma-Abfrage am Anfang Ihrer Anwendung aus:
db.run("PRAGMA journal_mode = WAL;");Was ist der WAL-Modus?">
Im WAL-Modus werden Schreibvorgänge in die Datenbank direkt in eine separate Datei geschrieben, die "WAL-Datei" (Write-Ahead-Log) genannt wird. Diese Datei wird später in die Hauptdatenbankdatei integriert. Denken Sie daran als einen Puffer für ausstehende Schreibvorgänge. Lesen Sie die SQLite-Dokumentation für eine detailliertere Übersicht.
Auf macOS können WAL-Dateien standardmäßig persistent sein. Dies ist kein Fehler, es ist so, wie macOS die Systemversion von SQLite konfiguriert hat.
Statements
Ein Statement ist eine vorbereitete Abfrage, was bedeutet, dass es geparst und in eine effiziente Binärform kompiliert wurde. Es kann mehrmals auf performante Weise ausgeführt werden.
Erstellen Sie ein Statement mit der .query-Methode auf Ihrer Database-Instanz.
const query = db.query(`select "Hello world" as message`);Abfragen können Parameter enthalten. Diese können numerisch (?1) oder benannt ($param oder :param oder @param) sein.
const query = db.query(`SELECT ?1, ?2;`);
const query = db.query(`SELECT $param1, $param2;`);Werte werden an diese Parameter gebunden, wenn die Abfrage ausgeführt wird. Ein Statement kann mit mehreren verschiedenen Methoden ausgeführt werden, wobei jede die Ergebnisse in einer anderen Form zurückgibt.
Werte binden
Um Werte an ein Statement zu binden, übergeben Sie ein Objekt an die .all(), .get(), .run() oder .values()-Methode.
const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });Sie können auch mit positionellen Parametern binden:
const query = db.query(`select ?1;`);
query.all("Hello world");strict: true ermöglicht das Binden von Werten ohne Präfixe
Standardmäßig sind die $, : und @ Präfixe enthalten, wenn Werte an benannte Parameter gebunden werden. Um ohne diese Präfixe zu binden, verwenden Sie die strict-Option im Database-Konstruktor.
import { Database } from "bun:sqlite";
const db = new Database(":memory:", {
// Werte ohne Präfixe binden
strict: true,
});
const query = db.query(`select $message;`);
// strict: true
query.all({ message: "Hello world" });
// strict: false
// query.all({ $message: "Hello world" });.all()
Verwenden Sie .all(), um eine Abfrage auszuführen und die Ergebnisse als Array von Objekten zurückzuerhalten.
const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });[{ message: "Hello world" }]Intern ruft dies sqlite3_reset auf und ruft wiederholt sqlite3_step auf, bis es SQLITE_DONE zurückgibt.
.get()
Verwenden Sie .get(), um eine Abfrage auszuführen und das erste Ergebnis als Objekt zurückzuerhalten.
const query = db.query(`select $message;`);
query.get({ $message: "Hello world" });{ $message: "Hello world" }Intern ruft dies sqlite3_reset gefolgt von sqlite3_step auf, bis es nicht mehr SQLITE_ROW zurückgibt. Wenn die Abfrage keine Zeilen zurückgibt, wird undefined zurückgegeben.
.run()
Verwenden Sie .run(), um eine Abfrage auszuführen und undefined zurückzuerhalten. Dies ist nützlich für Schema-modifizierende Abfragen (z.B. CREATE TABLE) oder Massen-Schreiboperationen.
const query = db.query(`create table foo;`);
query.run();{
lastInsertRowid: 0,
changes: 0,
}Intern ruft dies sqlite3_reset auf und ruft sqlite3_step einmal auf. Das Durchschreiten aller Zeilen ist nicht notwendig, wenn Sie sich nicht für die Ergebnisse interessieren.
Die lastInsertRowid-Eigenschaft gibt die ID der letzten in die Datenbank eingefügten Zeile zurück. Die changes-Eigenschaft ist die Anzahl der von der Abfrage betroffenen Zeilen.
.as(Class) - Abfrageergebnisse auf eine Klasse mappen
Verwenden Sie .as(Class), um eine Abfrage auszuführen und die Ergebnisse als Instanzen einer Klasse zurückzuerhalten. Dies ermöglicht es Ihnen, Methoden und Getter/Setter an Ergebnisse anzuhängen.
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);true
trueAls Performance-Optimierung wird der Klassenkonstruktor nicht aufgerufen, Standard-Initialisierer werden nicht ausgeführt, und private Felder sind nicht zugänglich. Dies ist eher wie die Verwendung von Object.create als new. Der Klasse-Prototyp wird dem Objekt zugewiesen, Methoden werden angehängt, und Getter/Setter werden eingerichtet, aber der Konstruktor wird nicht aufgerufen.
Die Datenbankspalten werden als Eigenschaften auf der Klasseninstanz gesetzt.
.iterate() (@@iterator)
Verwenden Sie .iterate(), um eine Abfrage auszuführen und Ergebnisse inkrementell zurückzugeben. Dies ist nützlich für große Ergebnissätze, die Sie Zeile für Zeile verarbeiten möchten, ohne alle Ergebnisse in den Speicher zu laden.
const query = db.query("SELECT * FROM foo");
for (const row of query.iterate()) {
console.log(row);
}Sie können auch das @@iterator-Protokoll verwenden:
const query = db.query("SELECT * FROM foo");
for (const row of query) {
console.log(row);
}.values()
Verwenden Sie values(), um eine Abfrage auszuführen und alle Ergebnisse als Array von Arrays zurückzuerhalten.
const query = db.query(`select $message;`);
query.values({ $message: "Hello world" });
query.values(2);[
[ "Iron Man", 2008 ],
[ "The Avengers", 2012 ],
[ "Ant-Man: Quantumania", 2023 ],
]Intern ruft dies sqlite3_reset auf und ruft wiederholt sqlite3_step auf, bis es SQLITE_DONE zurückgibt.
.finalize()
Verwenden Sie .finalize(), um ein Statement zu zerstören und alle damit verbundenen Ressourcen freizugeben. Einmal finalisiert, kann ein Statement nicht erneut ausgeführt werden. Typischerweise wird der Garbage Collector dies für Sie tun, aber explizite Finalisierung kann in performance-sensitiven Anwendungen nützlich sein.
const query = db.query("SELECT title, year FROM movies");
const movies = query.all();
query.finalize();.toString()
Das Aufrufen von toString() auf einer Statement-Instanz gibt die erweiterte SQL-Abfrage aus. Dies ist nützlich zum Debuggen.
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"Intern ruft dies sqlite3_expanded_sql auf. Die Parameter werden unter Verwendung der zuletzt gebundenen Werte erweitert.
Parameter
Abfragen können Parameter enthalten. Diese können numerisch (?1) oder benannt ($param oder :param oder @param) sein. Binden Sie Werte an diese Parameter, wenn Sie die Abfrage ausführen:
const query = db.query("SELECT * FROM foo WHERE bar = $bar");
const results = query.all({
$bar: "bar",
});[{ "$bar": "bar" }]Nummerierte (positionelle) Parameter funktionieren ebenfalls:
const query = db.query("SELECT ?1, ?2");
const results = query.all("hello", "goodbye");[
{
"?1": "hello",
"?2": "goodbye",
},
];Ganzzahlen
SQLite unterstützt vorzeichenbehaftete 64-Bit-Ganzzahlen, aber JavaScript unterstützt nur vorzeichenbehaftete 52-Bit-Ganzzahlen oder Ganzzahlen beliebiger Präzision mit bigint.
bigint-Eingabe wird überall unterstützt, aber standardmäßig gibt bun:sqlite Ganzzahlen als number-Typen zurück. Wenn Sie Ganzzahlen größer als 2^53 handhaben müssen, setzen Sie die safeIntegers-Option auf true beim Erstellen einer Database-Instanz. Dies validiert auch, dass an bun:sqlite übergebene bigint-Werte 64 Bit nicht überschreiten.
Standardmäßig gibt bun:sqlite Ganzzahlen als number-Typen zurück. Wenn Sie Ganzzahlen größer als 2^53 handhaben müssen, können Sie den bigint-Typ verwenden.
safeIntegers: true
Wenn safeIntegers true ist, gibt bun:sqlite Ganzzahlen als bigint-Typen zurück:
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);9007199254741093nWenn safeIntegers true ist, wirft bun:sqlite einen Fehler, wenn ein bigint-Wert in einem gebundenen Parameter 64 Bit überschreitet:
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);
}BigInt value '81129638414606663681390495662081' is out of rangesafeIntegers: false (Standard)
Wenn safeIntegers false ist, gibt bun:sqlite Ganzzahlen als number-Typen zurück und kürzt alle Bits jenseits von 53:
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);9007199254741092Transaktionen
Transaktionen sind ein Mechanismus zum Ausführen mehrerer Abfragen auf atomare Weise; das heißt, entweder alle Abfragen sind erfolgreich oder keine davon. Erstellen Sie eine Transaktion mit der db.transaction()-Methode:
const insertCat = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
for (const cat of cats) insertCat.run(cat);
});Zu diesem Zeitpunkt haben wir noch keine Katzen eingefügt! Der Aufruf von db.transaction() gibt eine neue Funktion (insertCats) zurück, die die Funktion umhüllt, die die Abfragen ausführt.
Um die Transaktion auszuführen, rufen Sie diese Funktion auf. Alle Argumente werden an die umhüllte Funktion durchgereicht; der Rückgabewert der umhüllten Funktion wird von der Transaktionsfunktion zurückgegeben. Die umhüllte Funktion hat auch Zugriff auf den this-Kontext, wie dort definiert, wo die Transaktion ausgeführt wird.
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(`Inserted ${count} cats`);Der Treiber wird automatisch eine Transaktion begin, wenn insertCats aufgerufen wird, und sie commiten, wenn die umhüllte Funktion zurückkehrt. Wenn eine Ausnahme ausgelöst wird, wird die Transaktion zurückgerollt. Die Ausnahme wird wie üblich weitergegeben; sie wird nicht abgefangen.
NOTE
**Verschachtelte Transaktionen** — Transaktionsfunktionen können von innerhalb anderer Transaktionsfunktionen aufgerufen werden. Wenn dies geschieht, wird die innere Transaktion zu einem [Savepoint](https://www.sqlite.org/lang_savepoint.html).Verschachteltes Transaktionsbeispiel anzeigen">
// 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); // verschachtelte Transaktion
});
adopt([
{ $name: "Joey", $age: 2 },
{ $name: "Sally", $age: 4 },
{ $name: "Junior", $age: 1 },
]);Transaktionen gibt es auch in deferred-, immediate- und exclusive-Versionen.
insertCats(cats); // verwendet "BEGIN"
insertCats.deferred(cats); // verwendet "BEGIN DEFERRED"
insertCats.immediate(cats); // verwendet "BEGIN IMMEDIATE"
insertCats.exclusive(cats); // verwendet "BEGIN EXCLUSIVE".loadExtension()
Um eine SQLite-Erweiterung zu laden, rufen Sie .loadExtension(name) auf Ihrer Database-Instanz auf
import { Database } from "bun:sqlite";
const db = new Database();
db.loadExtension("myext");NOTE
**MacOS-Benutzer** Standardmäßig wird macOS mit Apples proprietärem Build von SQLite ausgeliefert, das keine Erweiterungen unterstützt. Um Erweiterungen zu verwenden, müssen Sie ein Vanilla-Build von SQLite installieren.brew install sqlite
which sqlite # Pfad zur Binärdatei erhaltenUm bun:sqlite auf das neue Build zu verweisen, rufen Sie Database.setCustomSQLite(path) auf, bevor Sie Database-Instanzen erstellen. (Auf anderen Betriebssystemen ist dies ein No-Op.) Übergeben Sie einen Pfad zur SQLite .dylib-Datei, nicht zur ausführbaren Datei. Mit aktuellen Versionen von Homebrew ist dies so etwas wie /opt/homebrew/Cellar/sqlite/<version>/libsqlite3.dylib.
import { Database } from "bun:sqlite";
Database.setCustomSQLite("/path/to/libsqlite.dylib");
const db = new Database();
db.loadExtension("myext");.fileControl(cmd: number, value: any)
Um die erweiterte sqlite3_file_control-API zu verwenden, rufen Sie .fileControl(cmd, value) auf Ihrer Database-Instanz auf.
import { Database, constants } from "bun:sqlite";
const db = new Database();
// Sicherstellen, dass der WAL-Modus NICHT persistent ist
// dies verhindert, dass WAL-Dateien nach dem Schließen der Datenbank verweilen
db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);value kann sein:
numberTypedArrayundefinedodernull
Referenz
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; // Statement zerstören und Ressourcen bereinigen
toString(): string; // zu SQL serialisieren
columnNames: string[]; // die Spaltennamen des Ergebnissatzes
columnTypes: string[]; // Typen basierend auf tatsächlichen Werten in der ersten Zeile (zuerst .get()/.all() aufrufen)
declaredTypes: (string | null)[]; // Typen aus dem CREATE TABLE-Schema (zuerst .get()/.all() aufrufen)
paramsCount: number; // die Anzahl der vom Statement erwarteten Parameter
native: any; // das native Objekt, das das Statement repräsentiert
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>;Datentypen
| JavaScript-Typ | SQLite-Typ |
|---|---|
string | TEXT |
number | INTEGER oder DECIMAL |
boolean | INTEGER (1 oder 0) |
Uint8Array | BLOB |
Buffer | BLOB |
bigint | INTEGER |
null | NULL |