Skip to content

Die Schnittstelle ist einfach und performant gestaltet, verwendet getaggte Template-Literale für Abfragen und bietet Funktionen wie Connection Pooling, Transaktionen und vorbereitete Anweisungen.

ts
import { sql, SQL } from "bun";

// PostgreSQL (Standard)
const users = await sql`
  SELECT * FROM users
  WHERE active = ${true}
  LIMIT ${10}
`;

// Mit MySQL
const mysql = new SQL("mysql://user:pass@localhost:3306/mydb");
const mysqlResults = await mysql`
  SELECT * FROM users 
  WHERE active = ${true}
`;

// Mit SQLite
const sqlite = new SQL("sqlite://myapp.db");
const sqliteResults = await sqlite`
  SELECT * FROM users 
  WHERE active = ${1}
`;

Funktionen

  • Getaggte Template-Literale zum Schutz vor SQL-Injection
  • Transaktionen
  • Benannte und positionale Parameter
  • Connection Pooling
  • BigInt-Unterstützung
  • SASL-Auth-Unterstützung (SCRAM-SHA-256), MD5 und Klartext
  • Connection-Timeouts
  • Rückgabe von Zeilen als Datenobjekte, Arrays von Arrays oder Buffer
  • Binary-Protocol-Unterstützung macht es schneller
  • TLS-Unterstützung (und Auth-Modus)
  • Automatische Konfiguration mit Umgebungsvariable

Datenbankunterstützung

Bun.SQL bietet eine einheitliche API für mehrere Datenbanksysteme:

PostgreSQL

PostgreSQL wird verwendet, wenn:

  • Die Verbindungszeichenfolge nicht mit SQLite- oder MySQL-Mustern übereinstimmt (es ist der Fallback-Adapter)
  • Die Verbindungszeichenfolge explizit die Protokolle postgres:// oder postgresql:// verwendet
  • Keine Verbindungszeichenfolge angegeben ist und Umgebungsvariablen auf PostgreSQL verweisen
db.ts
ts
import { sql } from "bun";
// Verwendet PostgreSQL, wenn DATABASE_URL nicht gesetzt ist oder eine PostgreSQL-URL ist
await sql`SELECT ...`;

import { SQL } from "bun";
const pg = new SQL("postgres://user:pass@localhost:5432/mydb");
await pg`SELECT ...`;

MySQL

MySQL-Unterstützung ist in Bun.SQL integriert und bietet die gleiche Schnittstelle für getaggte Template-Literale mit vollständiger Kompatibilität für MySQL 5.7+ und MySQL 8.0+:

db.ts
ts
import { SQL } from "bun";

// MySQL-Verbindung
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // mysql2-Protokoll funktioniert ebenfalls

// Verwenden eines Optionsobjekts
const mysql3 = new SQL({
  adapter: "mysql",
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",
});

// Funktioniert mit Parametern - verwendet automatisch vorbereitete Anweisungen
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// Transaktionen funktionieren wie bei 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}`;
});

// Masseneinfügungen
const newUsers = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;

MySQL-Verbindungszeichenfolgenformate">

MySQL akzeptiert verschiedene URL-Formate für Verbindungszeichenfolgen:

ts
// Standard mysql://-Protokoll
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // Standard-Port 3306

// mysql2://-Protokoll (Kompatibilität mit mysql2 npm-Paket)
new SQL("mysql2://user:pass@localhost:3306/database");

// Mit Abfrageparametern
new SQL("mysql://user:pass@localhost/db?ssl=true");

// Unix-Socket-Verbindung
new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");

MySQL-spezifische Funktionen">

MySQL-Datenbanken unterstützen:

  • Vorbereitete Anweisungen: Automatisch erstellt für parametrisierte Abfragen mit Anweisungs-Caching
  • Binary-Protokoll: Für bessere Performance mit vorbereiteten Anweisungen und genauer Typbehandlung
  • Mehrere Ergebnissätze: Unterstützung für gespeicherte Prozeduren, die mehrere Ergebnissätze zurückgeben
  • Authentifizierungs-Plugins: Unterstützung für mysql_native_password, caching_sha2_password (MySQL 8.0-Standard) und sha256_password
  • SSL/TLS-Verbindungen: Konfigurierbare SSL-Modi ähnlich wie bei PostgreSQL
  • Verbindungsattribute: Client-Informationen, die zur Überwachung an den Server gesendet werden
  • Abfrage-Pipelining: Mehrere vorbereitete Anweisungen ohne Warten auf Antworten ausführen

SQLite

SQLite-Unterstützung ist in Bun.SQL integriert und bietet die gleiche Schnittstelle für getaggte Template-Literale:

ts
import { SQL } from "bun";

// In-Memory-Datenbank
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");

// Dateibasierte Datenbank
const sql1 = new SQL("sqlite://myapp.db");

// Verwenden eines Optionsobjekts
const sql2 = new SQL({
  adapter: "sqlite",
  filename: "./data/app.db",
});

// Für einfache Dateinamen Adapter explizit angeben
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });

SQLite-Verbindungszeichenfolgenformate">

SQLite akzeptiert verschiedene URL-Formate für Verbindungszeichenfolgen:

ts
// Standard sqlite://-Protokoll
new SQL("sqlite://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // Ohne Schrägstriche

// file://-Protokoll (wird ebenfalls als SQLite erkannt)
new SQL("file://path/to/database.db");
new SQL("file:path/to/database.db");

// Spezielle :memory:-Datenbank
new SQL(":memory:");
new SQL("sqlite://:memory:");
new SQL("file://:memory:");

// Relative und absolute Pfade
new SQL("sqlite://./local.db"); // Relativ zum aktuellen Verzeichnis
new SQL("sqlite://../parent/db.db"); // Übergeordnetes Verzeichnis
new SQL("sqlite:///absolute/path.db"); // Absoluter Pfad

// Mit Abfrageparametern
new SQL("sqlite://data.db?mode=ro"); // Nur-Lese-Modus
new SQL("sqlite://data.db?mode=rw"); // Lese-Schreib-Modus (kein Erstellen)
new SQL("sqlite://data.db?mode=rwc"); // Lese-Schreib-Erstellen-Modus (Standard)
<Note>
Einfache Dateinamen ohne Protokoll (wie `"myapp.db"`) erfordern die explizite Angabe von `{ adapter: "sqlite" }`, um Mehrdeutigkeiten mit PostgreSQL zu vermeiden.
</Note>

SQLite-spezifische Optionen">

SQLite-Datenbanken unterstützen zusätzliche Konfigurationsoptionen:

ts
const sql = new SQL({
  adapter: "sqlite",
  filename: "app.db",

  // SQLite-spezifische Optionen
  readonly: false, // Im Nur-Lese-Modus öffnen
  create: true, // Datenbank erstellen, falls nicht vorhanden
  readwrite: true, // Zum Lesen und Schreiben öffnen

  // Zusätzliche Bun:sqlite-Optionen
  strict: true, // Strengen Modus aktivieren
  safeIntegers: false, // JavaScript-Zahlen für Ganzzahlen verwenden
});

Abfrageparameter in der URL werden geparst, um diese Optionen festzulegen:

  • ?mode=roreadonly: true
  • ?mode=rwreadonly: false, create: false
  • ?mode=rwcreadonly: false, create: true (Standard)

Daten einfügen

Sie können JavaScript-Werte direkt an das SQL-Template-Literal übergeben, und die Maskierung wird für Sie handled.

ts
import { sql } from "bun";

// Grundlegendes Einfügen mit direkten Werten
const [user] = await sql`
  INSERT INTO users (name, email) 
  VALUES (${name}, ${email})
  RETURNING *
`;

// Objekthelfer für sauberere Syntax verwenden
const userData = {
  name: "Alice",
  email: "alice@example.com",
};

const [newUser] = await sql`
  INSERT INTO users ${sql(userData)}
  RETURNING *
`;
// Erweitert zu: INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')

Masseneinfügung

Sie können auch Arrays von Objekten an das SQL-Template-Literal übergeben, und es wird zu einer INSERT INTO ... VALUES ...-Anweisung erweitert.

ts
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)}`;

Spalten zum Einfügen auswählen

Sie können sql(object, ...string) verwenden, um auszuwählen, welche Spalten eingefügt werden sollen. Jede der Spalten muss im Objekt definiert sein.

ts
const user = {
  name: "Alice",
  email: "alice@example.com",
  age: 25,
};

await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// Fügt nur name- und email-Spalten ein und ignoriert andere Felder

Abfrageergebnisse

Standardmäßig gibt Buns SQL-Client Abfrageergebnisse als Arrays von Objekten zurück, wobei jedes Objekt eine Zeile mit Spaltennamen als Schlüssel darstellt. Es gibt jedoch Fälle, in denen Sie die Daten in einem anderen Format haben möchten. Der Client bietet zwei zusätzliche Methoden für diesen Zweck.

sql``.values()-Format

Die sql``.values()-Methode gibt Zeilen als Arrays von Werten zurück, anstatt als Objekte. Jede Zeile wird zu einem Array, wobei die Werte in der gleichen Reihenfolge wie die Spalten in Ihrer Abfrage sind.

ts
const rows = await sql`SELECT * FROM users`.values();
console.log(rows);

Dies gibt so etwas zurück wie:

ts
[
  ["Alice", "alice@example.com"],
  ["Bob", "bob@example.com"],
];

sql``.values() ist besonders nützlich, wenn doppelte Spaltennamen in den Abfrageergebnissen zurückgegeben werden. Bei Verwendung von Objekten (Standard) wird der letzte Spaltenname als Schlüssel im Objekt verwendet, was bedeutet, dass doppelte Spaltennamen sich gegenseitig überschreiben — aber bei Verwendung von sql``.values() ist jede Spalte im Array vorhanden, sodass Sie auf die Werte doppelter Spalten per Index zugreifen können.

sql``.raw()-Format

Die .raw()-Methode gibt Zeilen als Arrays von Buffer-Objekten zurück. Dies kann für die Arbeit mit Binärdaten oder aus Performance-Gründen nützlich sein.

ts
const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]

SQL-Fragmente

Ein häufiges Bedürfnis in Datenbankanwendungen ist die Möglichkeit, Abfragen dynamisch basierend auf Laufzeitbedingungen zu konstruieren. Bun bietet sichere Möglichkeiten, dies zu tun, ohne das Risiko einer SQL-Injection.

Dynamische Tabellennamen

Wenn Sie Tabellen oder Schemata dynamisch referenzieren müssen, verwenden Sie den sql()-Helfer, um eine ordnungsgemäße Maskierung sicherzustellen:

ts
// Tabellen sicher dynamisch referenzieren
await sql`SELECT * FROM ${sql("users")}`;

// Mit Schema-Qualifikation
await sql`SELECT * FROM ${sql("public.users")}`;

Bedingte Abfragen

Sie können den sql()-Helfer verwenden, um Abfragen mit bedingten Klauseln zu erstellen. Dies ermöglicht es Ihnen, flexible Abfragen zu erstellen, die sich an die Bedürfnisse Ihrer Anwendung anpassen:

ts
// Optionale WHERE-Klauseln
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
  SELECT * FROM users
  WHERE active = ${true}
  ${filterAge ? ageFilter : sql``}
`;

Dynamische Spalten in Updates

Sie können sql(object, ...string) verwenden, um auszuwählen, welche Spalten aktualisiert werden sollen. Jede der Spalten muss im Objekt definiert sein. Wenn die Spalten nicht angegeben werden, werden alle Schlüssel zum Aktualisieren der Zeile verwendet.

ts
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// Verwendet alle Schlüssel aus dem Objekt, um die Zeile zu aktualisieren
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;

Dynamische Werte und where in

Wertelisten können ebenfalls dynamisch erstellt werden, was where-in-Abfragen einfach macht. Optional können Sie ein Array von Objekten übergeben und angeben, welcher Schlüssel zum Erstellen der Liste verwendet werden soll.

ts
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")}`;

sql.array-Helfer

Der sql.array-Helfer erstellt PostgreSQL-Array-Literale aus JavaScript-Arrays:

ts
// Array-Literale für PostgreSQL erstellen
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// Erzeugt: INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])

// Funktioniert auch mit numerischen Arrays
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// Erzeugt: SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])

NOTE

`sql.array` ist nur für PostgreSQL. Mehrdimensionale Arrays und NULL-Elemente werden möglicherweise noch nicht unterstützt.

sql``.simple()

Das PostgreSQL-Wire-Protokoll unterstützt zwei Arten von Abfragen: "simple" und "extended". Einfache Abfragen können mehrere Anweisungen enthalten, unterstützen aber keine Parameter, während erweiterte Abfragen (Standard) Parameter unterstützen, aber nur eine Anweisung zulassen.

Um mehrere Anweisungen in einer einzigen Abfrage auszuführen, verwenden Sie sql``.simple():

ts
// Mehrere Anweisungen in einer Abfrage
await sql`
  SELECT 1;
  SELECT 2;
`.simple();

Einfache Abfragen sind oft nützlich für Datenbank-Migrationen und Setup-Skripte.

Beachten Sie, dass einfache Abfragen keine Parameter (${value}) verwenden können. Wenn Sie Parameter benötigen, müssen Sie Ihre Abfrage in separate Anweisungen aufteilen.

Abfragen in Dateien

Sie können die sql.file-Methode verwenden, um eine Abfrage aus einer Datei zu lesen und auszuführen. Wenn die Datei $1, $2 usw. enthält, können Sie Parameter an die Abfrage übergeben. Wenn keine Parameter verwendet werden, können mehrere Befehle pro Datei ausgeführt werden.

ts
const result = await sql.file("query.sql", [1, 2, 3]);

Unsichere Abfragen

Sie können die sql.unsafe-Funktion verwenden, um rohe SQL-Strings auszuführen. Verwenden Sie dies mit Vorsicht, da es Benutzereingaben nicht maskiert. Das Ausführen von mehr als einem Befehl pro Abfrage ist erlaubt, wenn keine Parameter verwendet werden.

ts
// Mehrere Befehle ohne Parameter
const result = await sql.unsafe(`
  SELECT ${userColumns} FROM users;
  SELECT ${accountColumns} FROM accounts;
`);

// Mit Parametern (nur ein Befehl erlaubt)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);

Ausführen und Abbrechen von Abfragen

Buns SQL ist lazy, was bedeutet, dass es erst mit der Ausführung beginnt, wenn es erwartet oder mit .execute() ausgeführt wird. Sie können eine aktuell ausgeführte Abfrage abbrechen, indem Sie die cancel()-Methode auf dem Abfrageobjekt aufrufen.

ts
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;

Datenbank-Umgebungsvariablen

sql-Verbindungsparameter können über Umgebungsvariablen konfiguriert werden. Der Client überprüft diese Variablen in einer bestimmten Reihenfolge und erkennt automatisch den Datenbanktyp basierend auf dem Format der Verbindungszeichenfolge.

Automatische Datenbankerkennung

Bei Verwendung von Bun.sql() ohne Argumente oder new SQL() mit einer Verbindungszeichenfolge wird der Adapter automatisch basierend auf dem URL-Format erkannt:

MySQL-Auto-Erkennung

MySQL wird automatisch ausgewählt, wenn die Verbindungszeichenfolge mit diesen Mustern übereinstimmt:

  • mysql://... - MySQL-Protokoll-URLs
  • mysql2://... - MySQL2-Protokoll-URLs (Kompatibilitäts-Alias)
ts
// Diese verwenden alle automatisch MySQL (kein Adapter erforderlich)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");

// Funktioniert mit DATABASE_URL-Umgebungsvariable
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.js

SQLite-Auto-Erkennung

SQLite wird automatisch ausgewählt, wenn die Verbindungszeichenfolge mit diesen Mustern übereinstimmt:

  • :memory: - In-Memory-Datenbank
  • sqlite://... - SQLite-Protokoll-URLs
  • sqlite:... - SQLite-Protokoll ohne Schrägstriche
  • file://... - File-Protokoll-URLs
  • file:... - File-Protokoll ohne Schrägstriche
ts
// Diese verwenden alle automatisch SQLite (kein Adapter erforderlich)
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");

// Funktioniert mit DATABASE_URL-Umgebungsvariable
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.js

PostgreSQL-Auto-Erkennung

PostgreSQL ist der Standard für Verbindungszeichenfolgen, die nicht mit MySQL- oder SQLite-Mustern übereinstimmen:

bash
# PostgreSQL wird für diese Muster erkannt
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js

# Oder jede URL, die nicht mit MySQL- oder SQLite-Mustern übereinstimmt
DATABASE_URL="localhost:5432/mydb" bun run app.js

MySQL-Umgebungsvariablen

MySQL-Verbindungen können über Umgebungsvariablen konfiguriert werden:

bash
# Primäre Verbindungs-URL (zuerst geprüft)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"

# Alternative: DATABASE_URL mit MySQL-Protokoll
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"

Wenn keine Verbindungs-URL angegeben wird, überprüft MySQL diese einzelnen Parameter:

UmgebungsvariableStandardwertBeschreibung
MYSQL_HOSTlocalhostDatenbank-Host
MYSQL_PORT3306Datenbank-Port
MYSQL_USERrootDatenbank-Benutzer
MYSQL_PASSWORD(leer)Datenbank-Passwort
MYSQL_DATABASEmysqlDatenbank-Name
MYSQL_URL(leer)Primäre Verbindungs-URL für MySQL
TLS_MYSQL_DATABASE_URL(leer)SSL/TLS-aktivierte Verbindungs-URL

PostgreSQL-Umgebungsvariablen

Die folgenden Umgebungsvariablen können verwendet werden, um die PostgreSQL-Verbindung zu definieren:

UmgebungsvariableBeschreibung
POSTGRES_URLPrimäre Verbindungs-URL für PostgreSQL
DATABASE_URLAlternative Verbindungs-URL (auto-erkannt)
PGURLAlternative Verbindungs-URL
PG_URLAlternative Verbindungs-URL
TLS_POSTGRES_DATABASE_URLSSL/TLS-aktivierte Verbindungs-URL
TLS_DATABASE_URLAlternative SSL/TLS-aktivierte Verbindungs-URL

Wenn keine Verbindungs-URL angegeben wird, überprüft das System die folgenden einzelnen Parameter:

UmgebungsvariableFallback-VariablenStandardwertBeschreibung
PGHOST-localhostDatenbank-Host
PGPORT-5432Datenbank-Port
PGUSERNAMEPGUSER, USER, USERNAMEpostgresDatenbank-Benutzer
PGPASSWORD-(leer)Datenbank-Passwort
PGDATABASE-usernameDatenbank-Name

SQLite-Umgebungsvariablen

SQLite-Verbindungen können über DATABASE_URL konfiguriert werden, wenn es eine SQLite-kompatible URL enthält:

bash
# Diese werden alle als SQLite erkannt
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"

Hinweis: PostgreSQL-spezifische Umgebungsvariablen (POSTGRES_URL, PGHOST usw.) werden bei Verwendung von SQLite ignoriert.


Laufzeit-Preconnection

Bun kann sich beim Start mit PostgreSQL vorverbinden, um die Performance zu verbessern, indem Datenbankverbindungen hergestellt werden, bevor Ihr Anwendungscode ausgeführt wird. Dies ist nützlich, um die Verbindungslatenz bei der ersten Datenbankabfrage zu reduzieren.

bash
# PostgreSQL-Preconnection aktivieren
bun --sql-preconnect index.js

# Funktioniert mit DATABASE_URL-Umgebungsvariable
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js

# Kann mit anderen Runtime-Flags kombiniert werden
bun --sql-preconnect --hot index.js

Das --sql-preconnect-Flag stellt automatisch eine PostgreSQL-Verbindung unter Verwendung Ihrer konfigurierten Umgebungsvariablen beim Start her. Wenn die Verbindung fehlschlägt, stürzt Ihre Anwendung nicht ab - der Fehler wird gracefully behandelt.


Verbindungsoptionen

Sie können Ihre Datenbankverbindung manuell konfigurieren, indem Sie Optionen an den SQL-Konstruktor übergeben. Die Optionen variieren je nach Datenbank-Adapter:

MySQL-Optionen

ts
import { SQL } from "bun";

const sql = new SQL({
  // Erforderlich für MySQL bei Verwendung eines Optionsobjekts
  adapter: "mysql",

  // Verbindungsdetails
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Unix-Socket-Verbindung (Alternative zu hostname/port)
  // socket: "/var/run/mysqld/mysqld.sock",

  // Connection-Pool-Einstellungen
  max: 20, // Maximale Verbindungen im Pool (Standard: 10)
  idleTimeout: 30, // Inaktive Verbindungen nach 30s schließen
  maxLifetime: 0, // Verbindungslebensdauer in Sekunden (0 = für immer)
  connectionTimeout: 30, // Timeout beim Herstellen neuer Verbindungen

  // SSL/TLS-Optionen
  ssl: "prefer", // oder "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("Mit MySQL verbunden");
  },
  onclose: (client, err) => {
    if (err) {
      console.error("MySQL-Verbindungsfehler:", err);
    } else {
      console.log("MySQL-Verbindung geschlossen");
    }
  },
});

PostgreSQL-Optionen

ts
import { SQL } from "bun";

const sql = new SQL({
  // Verbindungsdetails (Adapter wird automatisch als PostgreSQL erkannt)
  url: "postgres://user:pass@localhost:5432/dbname",

  // Alternative Verbindungsparameter
  hostname: "localhost",
  port: 5432,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Connection-Pool-Einstellungen
  max: 20, // Maximale Verbindungen im Pool
  idleTimeout: 30, // Inaktive Verbindungen nach 30s schließen
  maxLifetime: 0, // Verbindungslebensdauer in Sekunden (0 = für immer)
  connectionTimeout: 30, // Timeout beim Herstellen neuer Verbindungen

  // SSL/TLS-Optionen
  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("Mit PostgreSQL verbunden");
  },
  onclose: client => {
    console.log("PostgreSQL-Verbindung geschlossen");
  },
});

SQLite-Optionen

ts
import { SQL } from "bun";

const sql = new SQL({
  // Erforderlich für SQLite
  adapter: "sqlite",
  filename: "./data/app.db", // oder ":memory:" für In-Memory-Datenbank

  // SQLite-spezifische Zugriffsmodi
  readonly: false, // Im Nur-Lese-Modus öffnen
  create: true, // Datenbank erstellen, falls nicht vorhanden
  readwrite: true, // Lese- und Schreiboperationen erlauben

  // SQLite-Datenverarbeitung
  strict: true, // Strengen Modus für bessere Typsicherheit aktivieren
  safeIntegers: false, // BigInt für Ganzzahlen außerhalb des JS-Zahlenbereichs verwenden

  // Callbacks
  onconnect: client => {
    console.log("SQLite-Datenbank geöffnet");
  },
  onclose: client => {
    console.log("SQLite-Datenbank geschlossen");
  },
});

SQLite-Verbindungshinweise">

  • Connection Pooling: SQLite verwendet kein Connection Pooling, da es eine dateibasierte Datenbank ist. Jede SQL-Instanz stellt eine einzelne Verbindung dar.
  • Transaktionen: SQLite unterstützt verschachtelte Transaktionen durch Savepoints, ähnlich wie PostgreSQL.
  • Gleichzeitiger Zugriff: SQLite handhabt gleichzeitigen Zugriff durch Dateisperrung. Verwenden Sie den WAL-Modus für bessere Parallelität.
  • Speicherdatenbanken: Die Verwendung von :memory: erstellt eine temporäre Datenbank, die nur für die Lebensdauer der Verbindung existiert.

Dynamische Passwörter

Wenn Clients alternative Authentifizierungsschemata wie Zugriffstoken oder Verbindungen zu Datenbanken mit rotierenden Passwörtern verwenden müssen, stellen Sie entweder eine synchrone oder asynchrone Funktion bereit, die den dynamischen Passwortwert zum Zeitpunkt der Verbindung auflöst.

ts
import { SQL } from "bun";

const sql = new SQL(url, {
  // Andere Verbindungskonfiguration
  ...
  // Passwort-Funktion für den Datenbankbenutzer
  password: async () => await signer.getAuthToken(),
});

SQLite-spezifische Funktionen

Abfrageausführung

SQLite führt Abfragen synchron aus, im Gegensatz zu PostgreSQL, das asynchrone I/O verwendet. Die API bleibt jedoch konsistent unter Verwendung von Promises:

ts
const sqlite = new SQL("sqlite://app.db");

// Funktioniert wie PostgreSQL, führt aber intern synchron aus
const users = await sqlite`SELECT * FROM users`;

// Parameter funktionieren identisch
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;

SQLite-Pragmas

Sie können PRAGMA-Anweisungen verwenden, um das SQLite-Verhalten zu konfigurieren:

ts
const sqlite = new SQL("sqlite://app.db");

// Fremdschlüssel aktivieren
await sqlite`PRAGMA foreign_keys = ON`;

// Journal-Modus auf WAL für bessere Parallelität setzen
await sqlite`PRAGMA journal_mode = WAL`;

// Integrität prüfen
const integrity = await sqlite`PRAGMA integrity_check`;

Unterschiede bei Datentypen

SQLite hat ein flexibleres Typsystem als PostgreSQL:

ts
// SQLite speichert Daten in 5 Speicherklassen: NULL, INTEGER, REAL, TEXT, BLOB
const sqlite = new SQL("sqlite://app.db");

// SQLite ist toleranter mit Typen
await sqlite`
  CREATE TABLE flexible (
    id INTEGER PRIMARY KEY,
    data TEXT,        -- Kann Zahlen als Strings speichern
    value NUMERIC,    -- Kann Ganzzahlen, Reals oder Text speichern
    blob BLOB         -- Binärdaten
  )
`;

// JavaScript-Werte werden automatisch konvertiert
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;

Transaktionen

Um eine neue Transaktion zu starten, verwenden Sie sql.begin. Diese Methode funktioniert sowohl für PostgreSQL als auch für SQLite. Für PostgreSQL reserviert sie eine dedizierte Verbindung aus dem Pool. Für SQLite beginnt sie eine Transaktion auf der einzelnen Verbindung.

Der BEGIN-Befehl wird automatisch gesendet, einschließlich aller optionalen Konfigurationen, die Sie angeben. Wenn während der Transaktion ein Fehler auftritt, wird ein ROLLBACK ausgelöst, um einen reibungslosen Fortgang des Prozesses sicherzustellen.

Grundlegende Transaktionen

ts
await sql.begin(async tx => {
  // Alle Abfragen in dieser Funktion werden in einer Transaktion ausgeführt
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;

  // Transaktion wird automatisch committet, wenn keine Fehler ausgelöst werden
  // Rollback, wenn ein Fehler auftritt
});

Es ist auch möglich, die Anforderungen in einer Transaktion zu pipelinen, falls erforderlich, indem ein Array mit Abfragen aus der Callback-Funktion zurückgegeben wird:

ts
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 in SQL erstellen intermediate Checkpoints innerhalb einer Transaktion und ermöglichen partielle Rollbacks, ohne den gesamten Vorgang zu beeinträchtigen. Sie sind nützlich bei komplexen Transaktionen und ermöglichen Fehlerwiederherstellung und konsistente Ergebnisse.

ts
await sql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;

  await tx.savepoint(async sp => {
    // Dieser Teil kann separat zurückgerollt werden
    await sp`UPDATE users SET status = 'active'`;
    if (someCondition) {
      throw new Error("Rollback zum Savepoint");
    }
  });

  // Transaktion fortsetzen, auch wenn Savepoint zurückgerollt wurde
  await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});

Verteilte Transaktionen

Two-Phase Commit (2PC) ist ein verteiltes Transaktionsprotokoll, bei dem Phase 1 den Koordinator hat, der Knoten vorbereitet, indem sichergestellt wird, dass Daten geschrieben und zum Committen bereit sind, während Phase 2 mit Knoten abschließt, die entweder committen oder rollbacken basierend auf der Entscheidung des Koordinators. Dieser Prozess stellt Datenhaltbarkeit und ordnungsgemäßes Lock-Management sicher.

In PostgreSQL und MySQL persistieren verteilte Transaktionen über ihre ursprüngliche Sitzung hinaus und ermöglichen privilegierten Benutzern oder Koordinatoren, sie später zu committen oder zurückzurollen. Dies unterstützt robuste verteilte Transaktionen, Wiederherstellungsprozesse und administrative Vorgänge.

Jedes Datenbanksystem implementiert verteilte Transaktionen unterschiedlich:

PostgreSQL unterstützt sie nativ durch vorbereitete Transaktionen, während MySQL XA-Transaktionen verwendet.

Wenn während der verteilten Transaktion Ausnahmen auftreten und nicht abgefangen werden, rollt das System automatisch alle Änderungen zurück. Wenn alles normal verläuft, behalten Sie die Flexibilität, die Transaktion später entweder zu committen oder zurückzurollen.

ts
// Verteilte Transaktion beginnen
await sql.beginDistributed("tx1", async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});

// Später committen oder rollbacken
await sql.commitDistributed("tx1");
// oder
await sql.rollbackDistributed("tx1");

Authentifizierung

Bun unterstützt SCRAM-SHA-256 (SASL), MD5 und Klartext-Authentifizierung. SASL wird für bessere Sicherheit empfohlen. Weitere Informationen finden Sie unter Postgres SASL Authentication.

SSL-Modi-Übersicht

PostgreSQL unterstützt verschiedene SSL/TLS-Modi, um zu steuern, wie sichere Verbindungen hergestellt werden. Diese Modi bestimmen das Verhalten beim Verbinden und den Grad der Zertifikatsüberprüfung.

ts
const sql = new SQL({
  hostname: "localhost",
  username: "user",
  password: "password",
  ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});
SSL-ModusBeschreibung
disableKein SSL/TLS verwendet. Verbindungen schlagen fehl, wenn Server SSL erfordert.
preferVersucht zuerst SSL, fällt auf nicht-SSL zurück, wenn SSL fehlschlägt. Standardmodus, wenn keiner angegeben.
requireErfordert SSL ohne Zertifikatsüberprüfung. Schlägt fehl, wenn SSL nicht hergestellt werden kann.
verify-caÜberprüft, ob Server-Zertifikat von vertrauenswürdiger CA signiert ist. Schlägt fehl, wenn Überprüfung fehlschlägt.
verify-fullSicherster Modus. Überprüft Zertifikat und Hostnamen-Übereinstimmung. Schützt vor nicht vertrauenswürdigen Zertifikaten und MITM-Angriffen.

Verwendung mit Verbindungszeichenfolgen

Der SSL-Modus kann auch in Verbindungszeichenfolgen angegeben werden:

ts
// prefer-Modus verwenden
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");

// verify-full-Modus verwenden
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=verify-full");

Connection Pooling

Buns SQL-Client verwaltet automatisch einen Connection Pool, der ein Pool von Datenbankverbindungen ist, die für mehrere Abfragen wiederverwendet werden. Dies hilft, den Overhead beim Herstellen und Schließen von Verbindungen für jede Abfrage zu reduzieren, und hilft auch, die Anzahl der gleichzeitigen Verbindungen zur Datenbank zu verwalten.

ts
const sql = new SQL({
  // Pool-Konfiguration
  max: 20, // Maximal 20 gleichzeitige Verbindungen
  idleTimeout: 30, // Inaktive Verbindungen nach 30s schließen
  maxLifetime: 3600, // Maximale Verbindungslebensdauer 1 Stunde
  connectionTimeout: 10, // Verbindungs-Timeout 10s
});

Es wird keine Verbindung hergestellt, bis eine Abfrage ausgeführt wird.

ts
const sql = Bun.SQL(); // keine Verbindungen werden erstellt

await sql`...`; // Pool wird gestartet, bis max erreicht ist (wenn möglich), erste verfügbare Verbindung wird verwendet
await sql`...`; // vorherige Verbindung wird wiederverwendet

// zwei Verbindungen werden jetzt gleichzeitig verwendet
await Promise.all([
  sql`INSERT INTO users ${sql({ name: "Alice" })}`,
  sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);

await sql.close(); // Alle Abfragen abschließen lassen und alle Verbindungen aus dem Pool schließen
await sql.close({ timeout: 5 }); // 5 Sekunden warten und alle Verbindungen aus dem Pool schließen
await sql.close({ timeout: 0 }); // Alle Verbindungen aus dem Pool sofort schließen

Reservierte Verbindungen

Bun ermöglicht es Ihnen, eine Verbindung aus dem Pool zu reservieren, und gibt einen Client zurück, der die einzelne Verbindung umschließt. Dies kann verwendet werden, um Abfragen auf einer isolierten Verbindung auszuführen.

ts
// Exklusive Verbindung aus dem Pool erhalten
const reserved = await sql.reserve();

try {
  await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
  // Wichtig: Verbindung zurück an den Pool freigeben
  reserved.release();
}

// Oder mit Symbol.dispose verwenden
{
  using reserved = await sql.reserve();
  await reserved`SELECT 1`;
} // Automatisch freigegeben

Vorbereitete Anweisungen

Standardmäßig erstellt Buns SQL-Client automatisch benannte vorbereitete Anweisungen für Abfragen, bei denen abgeleitet werden kann, dass die Abfrage statisch ist. Dies bietet bessere Performance. Sie können dieses Verhalten jedoch ändern, indem Sie prepare: false in den Verbindungsoptionen setzen:

ts
const sql = new SQL({
  // ... andere Optionen ...
  prepare: false, // Persistieren benannter vorbereiteter Anweisungen auf dem Server deaktivieren
});

Wenn prepare: false gesetzt ist:

Abfragen werden weiterhin mit dem "extended"-Protokoll ausgeführt, aber sie werden mit unbenannten vorbereiteten Anweisungen ausgeführt. Eine unbenannte vorbereitete Anweisung dauert nur bis zur nächsten Parse-Anweisung, die die unbenannte Anweisung als Ziel angibt.

  • Parameter-Bindung ist weiterhin sicher gegen SQL-Injection
  • Jede Abfrage wird vom Server von Grund auf geparst und geplant
  • Abfragen werden nicht gepipelined

Sie möchten prepare: false möglicherweise verwenden, wenn:

  • Verwendung von PGBouncer im Transaktionsmodus (obwohl seit PGBouncer 1.21.0 Protokoll-Level benannte vorbereitete Anweisungen unterstützt werden, wenn ordnungsgemäß konfiguriert)
  • Debugging von Abfrageausführungsplänen
  • Arbeiten mit dynamischem SQL, bei dem Abfragepläne häufig neu generiert werden müssen
  • Mehr als ein Befehl pro Abfrage wird nicht unterstützt (es sei denn, Sie verwenden sql``.simple())

Beachten Sie, dass das Deaktivieren vorbereiteter Anweisungen die Performance für Abfragen beeinträchtigen kann, die häufig mit verschiedenen Parametern ausgeführt werden, da der Server jede Abfrage von Grund auf parsen und planen muss.


Fehlerbehandlung

Der Client bietet typisierte Fehler für verschiedene Fehlerszenarien. Fehler sind datenbankspezifisch und erweitern Basisklassen für Fehler:

Fehlerklassen

ts
import { SQL } from "bun";

try {
  await sql`SELECT * FROM users`;
} catch (error) {
  if (error instanceof SQL.PostgresError) {
    // PostgreSQL-spezifischer Fehler
    console.log(error.code); // PostgreSQL-Fehlercode
    console.log(error.detail); // Detaillierte Fehlermeldung
    console.log(error.hint); // Hilfreicher Hinweis von PostgreSQL
  } else if (error instanceof SQL.SQLiteError) {
    // SQLite-spezifischer Fehler
    console.log(error.code); // SQLite-Fehlercode (z.B. "SQLITE_CONSTRAINT")
    console.log(error.errno); // SQLite-Fehlernummer
    console.log(error.byteOffset); // Byte-Offset in SQL-Anweisung (falls verfügbar)
  } else if (error instanceof SQL.SQLError) {
    // Generischer SQL-Fehler (Basisklasse)
    console.log(error.message);
  }
}

PostgreSQL-spezifische Fehlercodes">

PostgreSQL-Verbindungsfehler

VerbindungsfehlerBeschreibung
ERR_POSTGRES_CONNECTION_CLOSEDVerbindung wurde getrennt oder nie hergestellt
ERR_POSTGRES_CONNECTION_TIMEOUTVerbindungsherstellung innerhalb des Timeout-Zeitraums fehlgeschlagen
ERR_POSTGRES_IDLE_TIMEOUTVerbindung wegen Inaktivität geschlossen
ERR_POSTGRES_LIFETIME_TIMEOUTVerbindung hat maximale Lebensdauer überschritten
ERR_POSTGRES_TLS_NOT_AVAILABLESSL/TLS-Verbindung nicht verfügbar
ERR_POSTGRES_TLS_UPGRADE_FAILEDUpgrade der Verbindung auf SSL/TLS fehlgeschlagen

Authentifizierungsfehler

AuthentifizierungsfehlerBeschreibung
ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2Passwort-Authentifizierung fehlgeschlagen
ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHODServer hat unbekannte Auth-Methode angefordert
ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHODServer hat nicht unterstützte Auth-Methode angefordert
ERR_POSTGRES_INVALID_SERVER_KEYUngültiger Serverschlüssel während der Authentifizierung
ERR_POSTGRES_INVALID_SERVER_SIGNATUREUngültige Serversignatur
ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64Ungültige SASL-Signatur-Codierung
ERR_POSTGRES_SASL_SIGNATURE_MISMATCHSASL-Signatur-Überprüfung fehlgeschlagen

Abfragefehler

AbfragefehlerBeschreibung
ERR_POSTGRES_SYNTAX_ERRORUngültige SQL-Syntax (erweitert SyntaxError)
ERR_POSTGRES_SERVER_ERRORAllgemeiner Fehler vom PostgreSQL-Server
ERR_POSTGRES_INVALID_QUERY_BINDINGUngültige Parameter-Bindung
ERR_POSTGRES_QUERY_CANCELLEDAbfrage wurde abgebrochen
ERR_POSTGRES_NOT_TAGGED_CALLAbfrage wurde ohne getaggten Aufruf aufgerufen

Datentyp-Fehler

Datentyp-FehlerBeschreibung
ERR_POSTGRES_INVALID_BINARY_DATAUngültiges Binärdaten-Format
ERR_POSTGRES_INVALID_BYTE_SEQUENCEUngültige Byte-Sequenz
ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODINGCodierungsfehler
ERR_POSTGRES_INVALID_CHARACTERUngültiges Zeichen in Daten
ERR_POSTGRES_OVERFLOWNumerischer Überlauf
ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMATNicht unterstütztes Binärformat
ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZEGanzzahlgröße nicht unterstützt
ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YETMehrdimensionale Arrays nicht unterstützt
ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YETNULL-Werte in Arrays nicht unterstützt

Protokollfehler

ProtokollfehlerBeschreibung
ERR_POSTGRES_EXPECTED_REQUESTClient-Anfrage erwartet
ERR_POSTGRES_EXPECTED_STATEMENTVorbereitete Anweisung erwartet
ERR_POSTGRES_INVALID_BACKEND_KEY_DATAUngültige Backend-Schlüsseldaten
ERR_POSTGRES_INVALID_MESSAGEUngültige Protokollnachricht
ERR_POSTGRES_INVALID_MESSAGE_LENGTHUngültige Nachrichtenlänge
ERR_POSTGRES_UNEXPECTED_MESSAGEUnerwarteter Nachrichtentyp

Transaktionsfehler

TransaktionsfehlerBeschreibung
ERR_POSTGRES_UNSAFE_TRANSACTIONUnsichere Transaktionsoperation erkannt
ERR_POSTGRES_INVALID_TRANSACTION_STATEUngültiger Transaktionszustand

SQLite-spezifische Fehler

SQLite-Fehler bieten Fehlercodes und Nummern, die den Standard-Fehlercodes von SQLite entsprechen:

Häufige SQLite-Fehlercodes">

FehlercodeerrnoBeschreibung
SQLITE_CONSTRAINT19Constraint-Verletzung (UNIQUE, CHECK, NOT NULL usw.)
SQLITE_BUSY5Datenbank ist gesperrt
SQLITE_LOCKED6Tabelle in der Datenbank ist gesperrt
SQLITE_READONLY8Versuch, in eine schreibgeschützte Datenbank zu schreiben
SQLITE_IOERR10Festplatten-I/O-Fehler
SQLITE_CORRUPT11Datenbank-Disk-Image ist beschädigt
SQLITE_FULL13Datenbank oder Festplatte ist voll
SQLITE_CANTOPEN14Datenbankdatei kann nicht geöffnet werden
SQLITE_PROTOCOL15Datenbank-Sperrprotokollfehler
SQLITE_SCHEMA17Datenbank-Schema hat sich geändert
SQLITE_TOOBIG18String oder BLOB überschreitet Größenlimit
SQLITE_MISMATCH20Datentyp-Unstimmigkeit
SQLITE_MISUSE21Bibliothek falsch verwendet
SQLITE_AUTH23Autorisierung verweigert

Beispiel-Fehlerbehandlung:

ts
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')`; // Doppelte ID
} catch (error) {
  if (error instanceof SQL.SQLiteError) {
    if (error.code === "SQLITE_CONSTRAINT") {
      console.log("Constraint-Verletzung:", error.message);
      // Einzigartige Constraint-Verletzung behandeln
    }
  }
}

Zahlen und BigInt

Buns SQL-Client beinhaltet spezielle Behandlung für große Zahlen, die den Bereich einer 53-Bit-Ganzzahl überschreiten. So funktioniert es:

ts
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" 12345

BigInt anstelle von Strings

Wenn Sie große Zahlen als BigInt anstelle von Strings benötigen, können Sie dies aktivieren, indem Sie die bigint-Option auf true setzen, wenn Sie den SQL-Client initialisieren:

ts
const sql = new SQL({
  bigint: true,
});

const [{ x }] = await sql`SELECT 9223372036854777 as x`;

console.log(typeof x, x); // "bigint" 9223372036854777n

Roadmap

Es gibt noch einige Dinge, die wir noch nicht fertiggestellt haben.

  • Verbindungsvorladung über --db-preconnect Bun CLI-Flag
  • Spaltenname-Transformationen (z.B. snake_case zu camelCase). Dies ist größtenteils blockiert durch eine Unicode-bewusste Implementierung der Groß-/Kleinschreibungsänderung in C++ unter Verwendung von WebKits WTF::String.
  • Spaltentyp-Transformationen

Datenbankspezifische Funktionen

Authentifizierungsmethoden

MySQL unterstützt mehrere Authentifizierungs-Plugins, die automatisch ausgehandelt werden:

  • mysql_native_password - Traditionelle MySQL-Authentifizierung, weitgehend kompatibel
  • caching_sha2_password - Standard in MySQL 8.0+, sicherer mit RSA-Schlüsselaustausch
  • sha256_password - SHA-256-basierte Authentifizierung

Der Client handhabt automatisch das Wechseln des Authentifizierungs-Plugins, wenn vom Server angefordert, einschließlich sicherem Passwortaustausch über nicht-SSL-Verbindungen.

Vorbereitete Anweisungen und Performance

MySQL verwendet serverseitige vorbereitete Anweisungen für alle parametrisierten Abfragen:

ts
// Dies erstellt automatisch eine vorbereitete Anweisung auf dem Server
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// Vorbereitete Anweisungen werden gecacht und für identische Abfragen wiederverwendet
for (const id of userIds) {
  // Gleiche vorbereitete Anweisung wird wiederverwendet
  await mysql`SELECT * FROM users WHERE id = ${id}`;
}

// Abfrage-Pipelining - mehrere Anweisungen werden ohne Warten auf Antworten gesendet
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}`,
]);

Mehrere Ergebnissätze

MySQL kann mehrere Ergebnissätze aus Multi-Statement-Abfragen zurückgeben:

ts
const mysql = new SQL("mysql://user:pass@localhost/mydb");

// Multi-Statement-Abfragen mit simple()-Methode
const multiResults = await mysql`
  SELECT * FROM users WHERE id = 1;
  SELECT * FROM orders WHERE user_id = 1;
`.simple();

Zeichensätze und Kollationen

Bun.SQL verwendet automatisch den utf8mb4-Zeichensatz für MySQL-Verbindungen und stellt vollständige Unicode-Unterstützung einschließlich Emojis sicher. Dies ist der empfohlene Zeichensatz für moderne MySQL-Anwendungen.

Verbindungsattribute

Bun sendet automatisch Client-Informationen an MySQL für bessere Überwachung:

ts
// Diese Attribute werden automatisch gesendet:
// _client_name: "Bun"
// _client_version: <bun version>
// Sie können diese in MySQLs performance_schema.session_connect_attrs sehen

Typbehandlung

MySQL-Typen werden automatisch in JavaScript-Typen konvertiert:

MySQL-TypJavaScript-TypHinweise
INT, TINYINT, MEDIUMINTnumberInnerhalb des sicheren Ganzzahlbereichs
BIGINTstring, number oder BigIntWenn der Wert in i32/u32 passt, ist es number, sonst string oder BigInt basierend auf bigint-Option
DECIMAL, NUMERICstringZur Erhaltung der Präzision
FLOAT, DOUBLEnumber
DATEDateJavaScript-Date-Objekt
DATETIME, TIMESTAMPDateMit Zeitzonenbehandlung
TIMEnumberSumme der Mikrosekunden
YEARnumber
CHAR, VARCHAR, VARSTRING, STRINGstring
TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXTstring
TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOBstringBLOB-Typen sind Alias für TEXT-Typen
JSONobject/arrayAutomatisch geparst
BIT(1)booleanBIT(1) in MySQL
GEOMETRYstringGeometriedaten

Unterschiede zu PostgreSQL

Obwohl die API vereinheitlicht ist, gibt es einige Verhaltensunterschiede:

  1. Parameter-Platzhalter: MySQL verwendet intern ?, aber Bun konvertiert $1, $2-Stil automatisch
  2. RETURNING-Klausel: MySQL unterstützt RETURNING nicht; verwenden Sie result.lastInsertRowid oder eine separate SELECT
  3. Array-Typen: MySQL hat keine nativen Array-Typen wie PostgreSQL

MySQL-spezifische Funktionen

Wir haben LOAD DATA INFILE-Unterstützung noch nicht implementiert

PostgreSQL-spezifische Funktionen

Wir haben diese noch nicht implementiert:

  • COPY-Unterstützung
  • LISTEN-Unterstützung
  • NOTIFY-Unterstützung

Wir haben auch einige der ungewöhnlicheren Funktionen noch nicht implementiert:

  • GSSAPI-Authentifizierung
  • SCRAM-SHA-256-PLUS-Unterstützung
  • Point- und PostGIS-Typen
  • Alle mehrdimensionalen Ganzzahl-Array-Typen (nur einige der Typen werden unterstützt)

Häufige Muster und bewährte Praktiken

Arbeiten mit MySQL-Ergebnissätzen

ts
// Insert-ID nach INSERT erhalten
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // MySQLs LAST_INSERT_ID()

// Betroffene Zeilen handhaben
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // Anzahl der aktualisierten Zeilen

// MySQL-spezifische Funktionen verwenden
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;

MySQL-Fehlerbehandlung

ts
try {
  await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
  if (error.code === "ER_DUP_ENTRY") {
    console.log("Doppelter Eintrag erkannt");
  } else if (error.code === "ER_ACCESS_DENIED_ERROR") {
    console.log("Zugriff verweigert");
  } else if (error.code === "ER_BAD_DB_ERROR") {
    console.log("Datenbank existiert nicht");
  }
  // MySQL-Fehlercodes sind kompatibel mit mysql/mysql2-Paketen
}

Performance-Tipps für MySQL

  1. Connection Pooling verwenden: Angemessene max-Pool-Größe basierend auf Ihrer Arbeitslast setzen
  2. Vorbereitete Anweisungen aktivieren: Sie sind standardmäßig aktiviert und verbessern die Performance
  3. Transaktionen für Massenoperationen verwenden: Verwandte Abfragen in Transaktionen gruppieren
  4. Ordentlich indexieren: MySQL verlässt sich stark auf Indizes für Abfrage-Performance
  5. utf8mb4-Zeichensatz verwenden: Er ist standardmäßig gesetzt und handhabt alle Unicode-Zeichen

Häufig gestellte Fragen

Warum ist das `Bun.sql` und nicht `Bun.postgres`?
	Der Plan war, in Zukunft weitere Datenbank-Treiber hinzuzufügen. Jetzt mit MySQL-Unterstützung unterstützt diese vereinheitlichte API PostgreSQL, MySQL und SQLite.
Woher weiß ich, welcher Datenbank-Adapter verwendet wird?
	Der Adapter wird automatisch aus der Verbindungszeichenfolge erkannt:

	- URLs, die mit `mysql://` oder `mysql2://` beginnen, verwenden MySQL
	- URLs, die mit SQLite-Mustern übereinstimmen (`:memory:`, `sqlite://`, `file://`), verwenden SQLite
	- Alles andere verwendet standardmäßig PostgreSQL

Werden MySQL-gespeicherte Prozeduren unterstützt?">
	Ja, gespeicherte Prozeduren werden vollständig unterstützt, einschließlich OUT-Parametern und mehreren Ergebnissätzen:

	```ts
	// Gespeicherte Prozedur aufrufen
	const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;

	// OUT-Parameter erhalten
	const outParam = await mysql`SELECT @total_orders as total`;
	```

Kann ich MySQL-spezifische SQL-Syntax verwenden?">
	Ja, Sie können jede MySQL-spezifische Syntax verwenden:

	```ts
	// MySQL-spezifische Syntax funktioniert einwandfrei
	await mysql`SET @user_id = ${userId}`;
	await mysql`SHOW TABLES`;
	await mysql`DESCRIBE users`;
	await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
	```

Warum nicht einfach eine bestehende Bibliothek verwenden?

npm-Pakete wie postgres.js, pg und node-postgres können auch in Bun verwendet werden. Sie sind großartige Optionen.

Zwei Gründe, warum:

  1. Wir denken, es ist einfacher für Entwickler, einen Datenbank-Treiber in Bun integriert zu haben. Die Zeit, die Sie mit Bibliothekssuche verbringen, ist Zeit, die Sie für den Aufbau Ihrer App verwenden könnten.
  2. Wir nutzen einige JavaScriptCore-Engine-Interna, um das Erstellen von Objekten schneller zu machen, was in einer Bibliothek schwierig zu implementieren wäre

Danksagungen

Großer Dank an @porsagers postgres.js für die Inspiration für die API-Schnittstelle.

Bun von www.bunjs.com.cn bearbeitet