Skip to content

L'interface est conçue pour être simple et performante, utilisant des template literals tagués pour les requêtes et offrant des fonctionnalités telles que la mise en pool des connexions, les transactions et les instructions préparées.

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

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

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

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

Fonctionnalités

  • Template literals tagués pour se protéger contre les injections SQL
  • Transactions
  • Paramètres nommés et positionnels
  • Mise en pool des connexions
  • Prise en charge de BigInt
  • Prise en charge de l'authentification SASL (SCRAM-SHA-256), MD5 et texte clair
  • Délais d'expiration des connexions
  • Retour des lignes sous forme d'objets de données, de tableaux de tableaux ou de Buffer
  • Prise en charge du protocole binaire pour plus de rapidité
  • Prise en charge de TLS (et mode d'authentification)
  • Configuration automatique avec variable d'environnement

Prise en charge des bases de données

Bun.SQL fournit une API unifiée pour plusieurs systèmes de bases de données :

PostgreSQL

PostgreSQL est utilisé lorsque :

  • La chaîne de connexion ne correspond pas aux modèles SQLite ou MySQL (c'est l'adaptateur de repli)
  • La chaîne de connexion utilise explicitement les protocoles postgres:// ou postgresql://
  • Aucune chaîne de connexion n'est fournie et les variables d'environnement pointent vers PostgreSQL
ts
import { sql } from "bun";
// Utilise PostgreSQL si DATABASE_URL n'est pas défini ou est une URL PostgreSQL
await sql`SELECT ...`;

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

MySQL

La prise en charge de MySQL est intégrée dans Bun.SQL, fournissant la même interface de template literal tagué avec une compatibilité complète pour MySQL 5.7+ et MySQL 8.0+ :

ts
import { SQL } from "bun";

// Connexion MySQL
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // le protocole mysql2 fonctionne aussi

// Utilisation d'un objet d'options
const mysql3 = new SQL({
  adapter: "mysql",
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",
});

// Fonctionne avec des paramètres - utilise automatiquement des instructions préparées
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// Les transactions fonctionnent comme avec 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}`;
});

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

Formats de chaîne de connexion MySQL">

MySQL accepte différents formats d'URL pour les chaînes de connexion :

ts
// Protocole standard mysql://
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // Port par défaut 3306

// Protocole mysql2:// (compatibilité avec le package npm mysql2)
new SQL("mysql2://user:pass@localhost:3306/database");

// Avec des paramètres de requête
new SQL("mysql://user:pass@localhost/db?ssl=true");

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

Fonctionnalités spécifiques à MySQL">

Les bases de données MySQL prennent en charge :

  • Instructions préparées : Créées automatiquement pour les requêtes paramétrées avec mise en cache des instructions
  • Protocole binaire : Pour de meilleures performances avec les instructions préparées et une gestion précise des types
  • Jeux de résultats multiples : Prise en charge des procédures stockées renvoyant plusieurs jeux de résultats
  • Plugins d'authentification : Prise en charge de mysql_native_password, caching_sha2_password (par défaut MySQL 8.0) et sha256_password
  • Connexions SSL/TLS : Modes SSL configurables similaires à PostgreSQL
  • Attributs de connexion : Informations client envoyées au serveur pour la surveillance
  • Pipelining de requêtes : Exécuter plusieurs instructions préparées sans attendre les réponses

SQLite

La prise en charge de SQLite est intégrée dans Bun.SQL, fournissant la même interface de template literal tagué :

ts
import { SQL } from "bun";

// Base de données en mémoire
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");

// Base de données basée sur un fichier
const sql1 = new SQL("sqlite://myapp.db");

// Utilisation d'un objet d'options
const sql2 = new SQL({
  adapter: "sqlite",
  filename: "./data/app.db",
});

// Pour les noms de fichiers simples, spécifiez l'adaptateur explicitement
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });

Formats de chaîne de connexion SQLite">

SQLite accepte différents formats d'URL pour les chaînes de connexion :

ts
// Protocole standard sqlite://
new SQL("sqlite://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // Sans barres obliques

// Protocole file:// (également reconnu comme SQLite)
new SQL("file://path/to/database.db");
new SQL("file:path/to/database.db");

// Base de données spéciale :memory:
new SQL(":memory:");
new SQL("sqlite://:memory:");
new SQL("file://:memory:");

// Chemins relatifs et absolus
new SQL("sqlite://./local.db"); // Relatif au répertoire actuel
new SQL("sqlite://../parent/db.db"); // Répertoire parent
new SQL("sqlite:///absolute/path.db"); // Chemin absolu

// Avec des paramètres de requête
new SQL("sqlite://data.db?mode=ro"); // Mode lecture seule
new SQL("sqlite://data.db?mode=rw"); // Mode lecture-écriture (pas de création)
new SQL("sqlite://data.db?mode=rwc"); // Mode lecture-écriture-création (par défaut)
<Note>
Les noms de fichiers simples sans protocole (comme `"myapp.db"`) nécessitent de spécifier explicitement `{ adapter: "sqlite" }` pour éviter toute ambiguïté avec PostgreSQL.
</Note>

Options spécifiques à SQLite">

Les bases de données SQLite prennent en charge des options de configuration supplémentaires :

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

  // Options spécifiques à SQLite
  readonly: false, // Ouvrir en mode lecture seule
  create: true, // Créer la base de données si elle n'existe pas
  readwrite: true, // Ouvrir pour la lecture et l'écriture

  // Options Bun:sqlite supplémentaires
  strict: true, // Activer le mode strict
  safeIntegers: false, // Utiliser les nombres JavaScript pour les entiers
});

Les paramètres de requête dans l'URL sont analysés pour définir ces options :

  • ?mode=roreadonly: true
  • ?mode=rwreadonly: false, create: false
  • ?mode=rwcreadonly: false, create: true (par défaut)

Insertion de données

Vous pouvez passer des valeurs JavaScript directement dans le template literal SQL et l'échappement sera géré pour vous.

ts
import { sql } from "bun";

// Insertion de base avec des valeurs directes
const [user] = await sql`
  INSERT INTO users (name, email) 
  VALUES (${name}, ${email})
  RETURNING *
`;

// Utilisation de l'aide d'objet pour une syntaxe plus propre
const userData = {
  name: "Alice",
  email: "alice@example.com",
};

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

Insertion en bloc

Vous pouvez également passer des tableaux d'objets au template literal SQL et il sera développé en une instruction INSERT INTO ... VALUES ....

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

Sélection des colonnes à insérer

Vous pouvez utiliser sql(object, ...string) pour choisir quelles colonnes insérer. Chacune des colonnes doit être définie sur l'objet.

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

await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// Insère uniquement les colonnes name et email, en ignorant les autres champs

Résultats de requête

Par défaut, le client SQL de Bun renvoie les résultats de requête sous forme de tableaux d'objets, où chaque objet représente une ligne avec les noms de colonnes comme clés. Cependant, il y a des cas où vous pourriez vouloir les données dans un format différent. Le client fournit deux méthodes supplémentaires à cet effet.

Format sql``.values()

La méthode sql``.values() renvoie les lignes sous forme de tableaux de valeurs plutôt que d'objets. Chaque ligne devient un tableau où les valeurs sont dans le même ordre que les colonnes de votre requête.

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

Cela renvoie quelque chose comme :

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

sql``.values() est particulièrement utile si des noms de colonnes en double sont renvoyés dans les résultats de la requête. Lors de l'utilisation d'objets (par défaut), le dernier nom de colonne est utilisé comme clé dans l'objet, ce qui signifie que les noms de colonnes en double s'écrasent les uns les autres — mais lors de l'utilisation de sql``.values(), chaque colonne est présente dans le tableau afin que vous puissiez accéder aux valeurs des colonnes en double par index.

Format sql``.raw()

La méthode .raw() renvoie les lignes sous forme de tableaux d'objets Buffer. Cela peut être utile pour travailler avec des données binaires ou pour des raisons de performance.

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

Fragments SQL

Un besoin courant dans les applications de base de données est la capacité de construire des requêtes dynamiquement en fonction des conditions d'exécution. Bun fournit des moyens sûrs de le faire sans risquer d'injection SQL.

Noms de tables dynamiques

Lorsque vous devez référencer des tables ou des schémas dynamiquement, utilisez l'aide sql() pour assurer un échappement approprié :

ts
// Référencer les tables en toute sécurité de manière dynamique
await sql`SELECT * FROM ${sql("users")}`;

// Avec qualification de schéma
await sql`SELECT * FROM ${sql("public.users")}`;

Requêtes conditionnelles

Vous pouvez utiliser l'aide sql() pour construire des requêtes avec des clauses conditionnelles. Cela vous permet de créer des requêtes flexibles qui s'adaptent aux besoins de votre application :

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

Colonnes dynamiques dans les mises à jour

Vous pouvez utiliser sql(object, ...string) pour choisir quelles colonnes mettre à jour. Chacune des colonnes doit être définie sur l'objet. Si les colonnes ne sont pas renseignées, toutes les clés seront utilisées pour mettre à jour la ligne.

ts
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// utilise toutes les clés de l'objet pour mettre à jour la ligne
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;

Valeurs dynamiques et where in

Les listes de valeurs peuvent également être créées dynamiquement, rendant les requêtes where in simples aussi. Vous pouvez éventuellement passer un tableau d'objets et indiquer quelle clé utiliser pour créer la liste.

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

Aide sql.array

L'aide sql.array crée des littéraux de tableau PostgreSQL à partir de tableaux JavaScript :

ts
// Créer des littéraux de tableau pour PostgreSQL
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// Génère : INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])

// Fonctionne avec les tableaux numériques aussi
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// Génère : SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])

NOTE

`sql.array` est spécifique à PostgreSQL. Les tableaux multidimensionnels et les éléments NULL peuvent ne pas être encore pris en charge.

sql``.simple()

Le protocole filaire PostgreSQL prend en charge deux types de requêtes : "simple" et "extended". Les requêtes simples peuvent contenir plusieurs instructions mais ne prennent pas en charge les paramètres, tandis que les requêtes étendues (par défaut) prennent en charge les paramètres mais n'autorisent qu'une seule instruction.

Pour exécuter plusieurs instructions dans une seule requête, utilisez sql``.simple() :

ts
// Plusieurs instructions dans une requête
await sql`
  SELECT 1;
  SELECT 2;
`.simple();

Les requêtes simples sont souvent utiles pour les migrations de bases de données et les scripts de configuration.

Notez que les requêtes simples ne peuvent pas utiliser de paramètres (${value}). Si vous avez besoin de paramètres, vous devez diviser votre requête en instructions séparées.

Requêtes dans des fichiers

Vous pouvez utiliser la méthode sql.file pour lire une requête depuis un fichier et l'exécuter, si le fichier inclut $1, $2, etc. vous pouvez passer des paramètres à la requête. Si aucun paramètre n'est utilisé, elle peut exécuter plusieurs commandes par fichier.

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

Requêtes non sécurisées

Vous pouvez utiliser la fonction sql.unsafe pour exécuter des chaînes SQL brutes. Utilisez-la avec prudence, car elle n'échappera pas les entrées utilisateur. L'exécution de plusieurs commandes par requête est autorisée si aucun paramètre n'est utilisé.

ts
// Plusieurs commandes sans paramètres
const result = await sql.unsafe(`
  SELECT ${userColumns} FROM users;
  SELECT ${accountColumns} FROM accounts;
`);

// Utilisation de paramètres (une seule commande est autorisée)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);

Exécution et annulation de requêtes

Le SQL de Bun est paresseux, ce qui signifie qu'il ne commencera à s'exécuter que lorsqu'il sera attendu ou exécuté avec .execute(). Vous pouvez annuler une requête en cours d'exécution en appelant la méthode cancel() sur l'objet requête.

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

Variables d'environnement de la base de données

Les paramètres de connexion sql peuvent être configurés à l'aide de variables d'environnement. Le client vérifie ces variables dans un ordre de priorité spécifique et détecte automatiquement le type de base de données en fonction du format de la chaîne de connexion.

Détection automatique de la base de données

Lors de l'utilisation de Bun.sql() sans arguments ou new SQL() avec une chaîne de connexion, l'adaptateur est automatiquement détecté en fonction du format de l'URL :

Détection automatique de MySQL

MySQL est automatiquement sélectionné lorsque la chaîne de connexion correspond à ces modèles :

  • mysql://... - URLs de protocole MySQL
  • mysql2://... - URLs de protocole MySQL2 (alias de compatibilité)
ts
// Tous utilisent MySQL automatiquement (pas besoin d'adaptateur)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");

// Fonctionne avec la variable d'environnement DATABASE_URL
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.js

Détection automatique de SQLite

SQLite est automatiquement sélectionné lorsque la chaîne de connexion correspond à ces modèles :

  • :memory: - Base de données en mémoire
  • sqlite://... - URLs de protocole SQLite
  • sqlite:... - Protocole SQLite sans barres obliques
  • file://... - URLs de protocole de fichier
  • file:... - Protocole de fichier sans barres obliques
ts
// Tous utilisent SQLite automatiquement (pas besoin d'adaptateur)
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");

// Fonctionne avec la variable d'environnement 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.js

Détection automatique de PostgreSQL

PostgreSQL est la valeur par défaut pour les chaînes de connexion qui ne correspondent pas aux modèles MySQL ou SQLite :

bash
# PostgreSQL est détecté pour ces modèles
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 toute URL qui ne correspond pas aux modèles MySQL ou SQLite
DATABASE_URL="localhost:5432/mydb" bun run app.js

Variables d'environnement MySQL

Les connexions MySQL peuvent être configurées via des variables d'environnement :

bash
# URL de connexion principale (vérifiée en premier)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"

# Alternative : DATABASE_URL avec protocole MySQL
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"

Si aucune URL de connexion n'est fournie, MySQL vérifie ces paramètres individuels :

Variable d'environnementValeur par défautDescription
MYSQL_HOSTlocalhostHôte de la base de données
MYSQL_PORT3306Port de la base de données
MYSQL_USERrootUtilisateur de la base de données
MYSQL_PASSWORD(vide)Mot de passe de la base de données
MYSQL_DATABASEmysqlNom de la base de données
MYSQL_URL(vide)URL de connexion principale pour MySQL
TLS_MYSQL_DATABASE_URL(vide)URL de connexion activée SSL/TLS

Variables d'environnement PostgreSQL

Les variables d'environnement suivantes peuvent être utilisées pour définir la connexion PostgreSQL :

Variable d'environnementDescription
POSTGRES_URLURL de connexion principale pour PostgreSQL
DATABASE_URLURL de connexion alternative (détectée automatiquement)
PGURLURL de connexion alternative
PG_URLURL de connexion alternative
TLS_POSTGRES_DATABASE_URLURL de connexion activée SSL/TLS
TLS_DATABASE_URLURL de connexion alternative activée SSL/TLS

Si aucune URL de connexion n'est fournie, le système vérifie les paramètres individuels suivants :

Variable d'environnementVariables de repliValeur par défautDescription
PGHOST-localhostHôte de la base de données
PGPORT-5432Port de la base de données
PGUSERNAMEPGUSER, USER, USERNAMEpostgresUtilisateur de la base de données
PGPASSWORD-(vide)Mot de passe de la base de données
PGDATABASE-usernameNom de la base de données

Variables d'environnement SQLite

Les connexions SQLite peuvent être configurées via DATABASE_URL lorsqu'elle contient une URL compatible SQLite :

bash
# Tous sont reconnus comme SQLite
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"

Note : Les variables d'environnement spécifiques à PostgreSQL (POSTGRES_URL, PGHOST, etc.) sont ignorées lors de l'utilisation de SQLite.


Préconnexion au moment de l'exécution

Bun peut pré-se connecter à PostgreSQL au démarrage pour améliorer les performances en établissant des connexions de base de données avant que le code de votre application ne s'exécute. Cela est utile pour réduire la latence de connexion lors de la première requête de base de données.

bash
# Activer la préconnexion PostgreSQL
bun --sql-preconnect index.js

# Fonctionne avec la variable d'environnement DATABASE_URL
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js

# Peut être combiné avec d'autres indicateurs d'exécution
bun --sql-preconnect --hot index.js

L'indicateur --sql-preconnect établira automatiquement une connexion PostgreSQL en utilisant vos variables d'environnement configurées au démarrage. Si la connexion échoue, cela ne plantera pas votre application - l'erreur sera gérée gracieusement.


Options de connexion

Vous pouvez configurer manuellement votre connexion de base de données en passant des options au constructeur SQL. Les options varient selon l'adaptateur de base de données :

Options MySQL

ts
import { SQL } from "bun";

const sql = new SQL({
  // Requis pour MySQL lors de l'utilisation d'un objet d'options
  adapter: "mysql",

  // Détails de connexion
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Connexion via socket Unix (alternative à hostname/port)
  // socket: "/var/run/mysqld/mysqld.sock",

  // Paramètres de pool de connexions
  max: 20, // Nombre maximum de connexions dans le pool (par défaut : 10)
  idleTimeout: 30, // Fermer les connexions inactives après 30s
  maxLifetime: 0, // Durée de vie de la connexion en secondes (0 = pour toujours)
  connectionTimeout: 30, // Délai d'expiration lors de l'établissement de nouvelles connexions

  // Options 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",
  // },

  // Rappels
  onconnect: client => {
    console.log("Connecté à MySQL");
  },
  onclose: (client, err) => {
    if (err) {
      console.error("Erreur de connexion MySQL :", err);
    } else {
      console.log("Connexion MySQL fermée");
    }
  },
});

Options PostgreSQL

ts
import { SQL } from "bun";

const sql = new SQL({
  // Détails de connexion (l'adaptateur est détecté automatiquement comme PostgreSQL)
  url: "postgres://user:pass@localhost:5432/dbname",

  // Paramètres de connexion alternatifs
  hostname: "localhost",
  port: 5432,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Paramètres de pool de connexions
  max: 20, // Nombre maximum de connexions dans le pool
  idleTimeout: 30, // Fermer les connexions inactives après 30s
  maxLifetime: 0, // Durée de vie de la connexion en secondes (0 = pour toujours)
  connectionTimeout: 30, // Délai d'expiration lors de l'établissement de nouvelles connexions

  // Options 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) {
  //     ...
  //   },
  // },

  // Rappels
  onconnect: client => {
    console.log("Connecté à PostgreSQL");
  },
  onclose: client => {
    console.log("Connexion PostgreSQL fermée");
  },
});

Options SQLite

ts
import { SQL } from "bun";

const sql = new SQL({
  // Requis pour SQLite
  adapter: "sqlite",
  filename: "./data/app.db", // ou ":memory:" pour une base de données en mémoire

  // Modes d'accès spécifiques à SQLite
  readonly: false, // Ouvrir en mode lecture seule
  create: true, // Créer la base de données si elle n'existe pas
  readwrite: true, // Autoriser les opérations de lecture et d'écriture

  // Gestion des données SQLite
  strict: true, // Activer le mode strict pour une meilleure sécurité des types
  safeIntegers: false, // Utiliser BigInt pour les entiers dépassant la plage des nombres JS

  // Rappels
  onconnect: client => {
    console.log("Base de données SQLite ouverte");
  },
  onclose: client => {
    console.log("Base de données SQLite fermée");
  },
});

Notes sur les connexions SQLite">

  • Mise en pool des connexions : SQLite n'utilise pas la mise en pool des connexions car il s'agit d'une base de données basée sur des fichiers. Chaque instance SQL représente une seule connexion.
  • Transactions : SQLite prend en charge les transactions imbriquées via des points de sauvegarde, similaires à PostgreSQL.
  • Accès concurrent : SQLite gère l'accès concurrent via le verrouillage de fichiers. Utilisez le mode WAL pour une meilleure concurrence.
  • Bases de données en mémoire : L'utilisation de :memory: crée une base de données temporaire qui n'existe que pendant la durée de vie de la connexion.

Mots de passe dynamiques

Lorsque les clients doivent utiliser des schémas d'authentification alternatifs tels que des jetons d'accès ou des connexions à des bases de données avec des mots de passe tournants, fournissez une fonction synchrone ou asynchrone qui résoudra la valeur du mot de passe dynamique au moment de la connexion.

ts
import { SQL } from "bun";

const sql = new SQL(url, {
  // Autre configuration de connexion
  ...
  // Fonction de mot de passe pour l'utilisateur de la base de données
  password: async () => await signer.getAuthToken(),
});

Fonctionnalités spécifiques à SQLite

Exécution de requêtes

SQLite exécute les requêtes de manière synchrone, contrairement à PostgreSQL qui utilise des E/S asynchrones. Cependant, l'API reste cohérente en utilisant des promesses :

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

// Fonctionne comme PostgreSQL, mais s'exécute de manière synchrone sous le capot
const users = await sqlite`SELECT * FROM users`;

// Les paramètres fonctionnent de manière identique
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;

Pragmas SQLite

Vous pouvez utiliser des instructions PRAGMA pour configurer le comportement de SQLite :

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

// Activer les clés étrangères
await sqlite`PRAGMA foreign_keys = ON`;

// Définir le mode journal sur WAL pour une meilleure concurrence
await sqlite`PRAGMA journal_mode = WAL`;

// Vérifier l'intégrité
const integrity = await sqlite`PRAGMA integrity_check`;

Différences de types de données

SQLite a un système de types plus flexible que PostgreSQL :

ts
// SQLite stocke les données dans 5 classes de stockage : NULL, INTEGER, REAL, TEXT, BLOB
const sqlite = new SQL("sqlite://app.db");

// SQLite est plus indulgent avec les types
await sqlite`
  CREATE TABLE flexible (
    id INTEGER PRIMARY KEY,
    data TEXT,        -- Peut stocker des nombres sous forme de chaînes
    value NUMERIC,    -- Peut stocker des entiers, des réels ou du texte
    blob BLOB         -- Données binaires
  )
`;

// Les valeurs JavaScript sont automatiquement converties
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;

Transactions

Pour démarrer une nouvelle transaction, utilisez sql.begin. Cette méthode fonctionne à la fois pour PostgreSQL et SQLite. Pour PostgreSQL, elle réserve une connexion dédiée du pool. Pour SQLite, elle démarre une transaction sur la connexion unique.

La commande BEGIN est envoyée automatiquement, y compris toute configuration optionnelle que vous spécifiez. Si une erreur se produit pendant la transaction, un ROLLBACK est déclenché pour assurer la continuité du processus.

Transactions de base

ts
await sql.begin(async tx => {
  // Toutes les requêtes dans cette fonction s'exécutent dans une transaction
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;

  // La transaction est automatiquement validée si aucune erreur n'est lancée
  // Annulée si une erreur se produit
});

Il est également possible de piper les requêtes dans une transaction si nécessaire en renvoyant un tableau avec les requêtes depuis la fonction de rappel comme ceci :

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

Points de sauvegarde

Les points de sauvegarde en SQL créent des points de contrôle intermédiaires dans une transaction, permettant des retours en arrière partiels sans affecter l'ensemble de l'opération. Ils sont utiles dans les transactions complexes, permettant la récupération d'erreurs et le maintien de résultats cohérents.

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

  await tx.savepoint(async sp => {
    // Cette partie peut être annulée séparément
    await sp`UPDATE users SET status = 'active'`;
    if (someCondition) {
      throw new Error("Retour au point de sauvegarde");
    }
  });

  // Continuer avec la transaction même si le point de sauvegarde a été annulé
  await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});

Transactions distribuées

Two-Phase Commit (2PC) est un protocole de transaction distribuée où la phase 1 implique que le coordinateur prépare les nœuds en s'assurant que les données sont écrites et prêtes à être validées, tandis que la phase 2 finalise avec les nœuds validant ou annulant en fonction de la décision du coordinateur. Ce processus assure la durabilité des données et une gestion appropriée des verrous.

Dans PostgreSQL et MySQL, les transactions distribuées persistent au-delà de leur session d'origine, permettant aux utilisateurs privilégiés ou aux coordinateurs de les valider ou de les annuler ultérieurement. Cela prend en charge les transactions distribuées robustes, les processus de récupération et les opérations administratives.

Chaque système de base de données implémente les transactions distribuées différemment :

PostgreSQL les prend en charge nativement via des transactions préparées, tandis que MySQL utilise les transactions XA.

Si des exceptions se produisent pendant la transaction distribuée et ne sont pas attrapées, le système annulera automatiquement toutes les modifications. Lorsque tout se déroule normalement, vous conservez la flexibilité de valider ou d'annuler la transaction ultérieurement.

ts
// Démarrer une transaction distribuée
await sql.beginDistributed("tx1", async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});

// Plus tard, valider ou annuler
await sql.commitDistributed("tx1");
// ou
await sql.rollbackDistributed("tx1");

Authentification

Bun prend en charge l'authentification SCRAM-SHA-256 (SASL), MD5 et texte clair. SASL est recommandé pour une meilleure sécurité. Consultez Authentification SASL Postgres pour plus d'informations.

Aperçu des modes SSL

PostgreSQL prend en charge différents modes SSL/TLS pour contrôler comment les connexions sécurisées sont établies. Ces modes déterminent le comportement lors de la connexion et le niveau de vérification du certificat effectué.

ts
const sql = new SQL({
  hostname: "localhost",
  username: "user",
  password: "password",
  ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});
Mode SSLDescription
disableAucun SSL/TLS utilisé. Les connexions échouent si le serveur nécessite SSL.
preferEssaie SSL en premier, revient à non-SSL si SSL échoue. Mode par défaut si aucun n'est spécifié.
requireNécessite SSL sans vérification du certificat. Échoue si SSL ne peut pas être établi.
verify-caVérifie que le certificat du serveur est signé par une CA de confiance. Échoue si la vérification échoue.
verify-fullMode le plus sécurisé. Vérifie que le certificat et le nom d'hôte correspondent. Protège contre les certificats non fiables et les attaques MITM.

Utilisation avec les chaînes de connexion

Le mode SSL peut également être spécifié dans les chaînes de connexion :

ts
// Utilisation du mode prefer
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");

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

Mise en pool des connexions

Le client SQL de Bun gère automatiquement un pool de connexions, qui est un ensemble de connexions de base de données réutilisées pour plusieurs requêtes. Cela aide à réduire la surcharge d'établissement et de fermeture de connexions pour chaque requête, et aide également à gérer le nombre de connexions simultanées à la base de données.

ts
const sql = new SQL({
  // Configuration du pool
  max: 20, // Maximum 20 connexions simultanées
  idleTimeout: 30, // Fermer les connexions inactives après 30s
  maxLifetime: 3600, // Durée de vie maximale de la connexion 1 heure
  connectionTimeout: 10, // Délai d'expiration de connexion 10s
});

Aucune connexion ne sera établie tant qu'une requête n'est pas faite.

ts
const sql = Bun.SQL(); // aucune connexion n'est créée

await sql`...`; // le pool est démarré jusqu'à ce que max soit atteint (si possible), première connexion disponible est utilisée
await sql`...`; // la connexion précédente est réutilisée

// deux connexions sont utilisées maintenant en même temps
await Promise.all([
  sql`INSERT INTO users ${sql({ name: "Alice" })}`,
  sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);

await sql.close(); // attendre que toutes les requêtes se terminent et fermer toutes les connexions du pool
await sql.close({ timeout: 5 }); // attendre 5 secondes et fermer toutes les connexions du pool
await sql.close({ timeout: 0 }); // fermer immédiatement toutes les connexions du pool

Connexions réservées

Bun vous permet de réserver une connexion du pool et renvoie un client qui encapsule la connexion unique. Cela peut être utilisé pour exécuter des requêtes sur une connexion isolée.

ts
// Obtenir une connexion exclusive du pool
const reserved = await sql.reserve();

try {
  await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
  // Important : Relâcher la connexion au pool
  reserved.release();
}

// Ou en utilisant Symbol.dispose
{
  using reserved = await sql.reserve();
  await reserved`SELECT 1`;
} // Automatiquement relâchée

Instructions préparées

Par défaut, le client SQL de Bun crée automatiquement des instructions préparées nommées pour les requêtes où il peut être déduit que la requête est statique. Cela offre de meilleures performances. Cependant, vous pouvez modifier ce comportement en définissant prepare: false dans les options de connexion :

ts
const sql = new SQL({
  // ... autres options ...
  prepare: false, // Désactiver la persistance des instructions préparées nommées sur le serveur
});

Lorsque prepare: false est défini :

Les requêtes sont toujours exécutées en utilisant le protocole "extended", mais elles sont exécutées en utilisant des instructions préparées non nommées, une instruction préparée non nommée ne dure que jusqu'à ce que la prochaine instruction Parse spécifiant l'instruction non nommée comme destination soit émise.

  • La liaison de paramètres est toujours sûre contre les injections SQL
  • Chaque requête est analysée et planifiée à partir de zéro par le serveur
  • Les requêtes ne seront pas pipelinées

Vous voudrez peut-être utiliser prepare: false lorsque :

  • Utilisation de PGBouncer en mode transaction (bien que depuis PGBouncer 1.21.0, les instructions préparées nommées au niveau du protocole soient prises en charge lorsqu'elles sont configurées correctement)
  • Débogage des plans d'exécution de requêtes
  • Travail avec du SQL dynamique où les plans de requêtes doivent être régénérés fréquemment
  • Plus d'une commande par requête ne sera pas prise en charge (sauf si vous utilisez sql``.simple())

Notez que la désactivation des instructions préparées peut avoir un impact sur les performances pour les requêtes exécutées fréquemment avec différents paramètres, car le serveur doit analyser et planifier chaque requête à partir de zéro.


Gestion des erreurs

Le client fournit des erreurs typées pour différents scénarios d'échec. Les erreurs sont spécifiques à la base de données et étendent les classes d'erreurs de base :

Classes d'erreurs

ts
import { SQL } from "bun";

try {
  await sql`SELECT * FROM users`;
} catch (error) {
  if (error instanceof SQL.PostgresError) {
    // Erreur spécifique à PostgreSQL
    console.log(error.code); // Code d'erreur PostgreSQL
    console.log(error.detail); // Message d'erreur détaillé
    console.log(error.hint); // Indice utile de PostgreSQL
  } else if (error instanceof SQL.SQLiteError) {
    // Erreur spécifique à SQLite
    console.log(error.code); // Code d'erreur SQLite (par exemple, "SQLITE_CONSTRAINT")
    console.log(error.errno); // Numéro d'erreur SQLite
    console.log(error.byteOffset); // Offset d'octet dans l'instruction SQL (si disponible)
  } else if (error instanceof SQL.SQLError) {
    // Erreur SQL générique (classe de base)
    console.log(error.message);
  }
}

Codes d'erreur spécifiques à PostgreSQL">

Erreurs de connexion PostgreSQL

Erreurs de connexionDescription
ERR_POSTGRES_CONNECTION_CLOSEDLa connexion a été terminée ou n'a jamais été établie
ERR_POSTGRES_CONNECTION_TIMEOUTÉchec de l'établissement de la connexion dans le délai imparti
ERR_POSTGRES_IDLE_TIMEOUTConnexion fermée pour cause d'inactivité
ERR_POSTGRES_LIFETIME_TIMEOUTLa connexion a dépassé la durée de vie maximale
ERR_POSTGRES_TLS_NOT_AVAILABLEConnexion SSL/TLS non disponible
ERR_POSTGRES_TLS_UPGRADE_FAILEDÉchec de la mise à niveau de la connexion vers SSL/TLS

Erreurs d'authentification

Erreurs d'authentificationDescription
ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2Échec de l'authentification par mot de passe
ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHODLe serveur a demandé une méthode d'authentification inconnue
ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHODLe serveur a demandé une méthode d'authentification non prise en charge
ERR_POSTGRES_INVALID_SERVER_KEYClé de serveur invalide pendant l'authentification
ERR_POSTGRES_INVALID_SERVER_SIGNATURESignature de serveur invalide
ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64Encodage de signature SASL invalide
ERR_POSTGRES_SASL_SIGNATURE_MISMATCHÉchec de la vérification de signature SASL

Erreurs de requête

Erreurs de requêteDescription
ERR_POSTGRES_SYNTAX_ERRORSyntaxe SQL invalide (étend SyntaxError)
ERR_POSTGRES_SERVER_ERRORErreur générale du serveur PostgreSQL
ERR_POSTGRES_INVALID_QUERY_BINDINGLiaison de paramètres invalide
ERR_POSTGRES_QUERY_CANCELLEDLa requête a été annulée
ERR_POSTGRES_NOT_TAGGED_CALLLa requête a été appelée sans appel tagué

Erreurs de type de données

Erreurs de type de donnéesDescription
ERR_POSTGRES_INVALID_BINARY_DATAFormat de données binaires invalide
ERR_POSTGRES_INVALID_BYTE_SEQUENCESéquence d'octets invalide
ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODINGErreur d'encodage
ERR_POSTGRES_INVALID_CHARACTERCaractère invalide dans les données
ERR_POSTGRES_OVERFLOWDépassement numérique
ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMATFormat binaire non pris en charge
ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZETaille d'entier non prise en charge
ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YETTableaux multidimensionnels non pris en charge
ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YETValeurs NULL dans les tableaux non prises en charge

Erreurs de protocole

Erreurs de protocoleDescription
ERR_POSTGRES_EXPECTED_REQUESTRequête client attendue
ERR_POSTGRES_EXPECTED_STATEMENTInstruction préparée attendue
ERR_POSTGRES_INVALID_BACKEND_KEY_DATADonnées de clé backend invalides
ERR_POSTGRES_INVALID_MESSAGEMessage de protocole invalide
ERR_POSTGRES_INVALID_MESSAGE_LENGTHLongueur de message invalide
ERR_POSTGRES_UNEXPECTED_MESSAGEType de message inattendu

Erreurs de transaction

Erreurs de transactionDescription
ERR_POSTGRES_UNSAFE_TRANSACTIONOpération de transaction non sécurisée détectée
ERR_POSTGRES_INVALID_TRANSACTION_STATEÉtat de transaction invalide

Erreurs spécifiques à SQLite

Les erreurs SQLite fournissent des codes d'erreur et des numéros qui correspondent aux codes d'erreur standard de SQLite :

Codes d'erreur SQLite courants">

Code d'erreurerrnoDescription
SQLITE_CONSTRAINT19Violation de contrainte (UNIQUE, CHECK, NOT NULL, etc.)
SQLITE_BUSY5La base de données est verrouillée
SQLITE_LOCKED6La table dans la base de données est verrouillée
SQLITE_READONLY8Tentative d'écriture dans une base de données en lecture seule
SQLITE_IOERR10Erreur d'E/S disque
SQLITE_CORRUPT11L'image disque de la base de données est corrompue
SQLITE_FULL13La base de données ou le disque est plein
SQLITE_CANTOPEN14Impossible d'ouvrir le fichier de base de données
SQLITE_PROTOCOL15Erreur de protocole de verrouillage de base de données
SQLITE_SCHEMA17Le schéma de la base de données a changé
SQLITE_TOOBIG18La chaîne ou BLOB dépasse la limite de taille
SQLITE_MISMATCH20Incompatibilité de type de données
SQLITE_MISUSE21Bibliothèque utilisée incorrectement
SQLITE_AUTH23Autorisation refusée

Exemple de gestion des erreurs :

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')`; // ID en double
} catch (error) {
  if (error instanceof SQL.SQLiteError) {
    if (error.code === "SQLITE_CONSTRAINT") {
      console.log("Violation de contrainte :", error.message);
      // Gérer la violation de contrainte unique
    }
  }
}

Nombres et BigInt

Le client SQL de Bun inclut une gestion spéciale pour les grands nombres qui dépassent la plage d'un entier 53 bits. Voici comment cela fonctionne :

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 au lieu de chaînes

Si vous avez besoin de grands nombres sous forme de BigInt au lieu de chaînes, vous pouvez activer cela en définissant l'option bigint sur true lors de l'initialisation du client SQL :

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

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

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

Feuille de route

Il reste encore certaines choses que nous n'avons pas terminées.

  • Préchargement de connexion via l'indicateur CLI --db-preconnect de Bun
  • Transformations de noms de colonnes (par exemple snake_case vers camelCase). Cela est principalement bloqué sur une implémentation consciente d'unicode du changement de casse en C++ utilisant WTF::String de WebKit.
  • Transformations de types de colonnes

Fonctionnalités spécifiques aux bases de données

Méthodes d'authentification

MySQL prend en charge plusieurs plugins d'authentification qui sont automatiquement négociés :

  • mysql_native_password - Authentification MySQL traditionnelle, largement compatible
  • caching_sha2_password - Par défaut dans MySQL 8.0+, plus sécurisé avec échange de clés RSA
  • sha256_password - Authentification basée sur SHA-256

Le client gère automatiquement le changement de plugin d'authentification lorsque demandé par le serveur, y compris l'échange sécurisé de mots de passe sur des connexions non-SSL.

Instructions préparées et performances

MySQL utilise des instructions préparées côté serveur pour toutes les requêtes paramétrées :

ts
// Cela crée automatiquement une instruction préparée sur le serveur
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// Les instructions préparées sont mises en cache et réutilisées pour des requêtes identiques
for (const id of userIds) {
  // La même instruction préparée est réutilisée
  await mysql`SELECT * FROM users WHERE id = ${id}`;
}

// Pipelining de requêtes - plusieurs instructions envoyées sans attendre
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}`,
]);

Jeux de résultats multiples

MySQL peut renvoyer plusieurs jeux de résultats à partir de requêtes multi-instructions :

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

// Requêtes multi-instructions avec la méthode simple()
const multiResults = await mysql`
  SELECT * FROM users WHERE id = 1;
  SELECT * FROM orders WHERE user_id = 1;
`.simple();

Jeux de caractères et collationnements

Bun.SQL utilise automatiquement le jeu de caractères utf8mb4 pour les connexions MySQL, assurant une prise en charge complète d'Unicode y compris les emojis. C'est le jeu de caractères recommandé pour les applications MySQL modernes.

Attributs de connexion

Bun envoie automatiquement des informations client à MySQL pour une meilleure surveillance :

ts
// Ces attributs sont envoyés automatiquement :
// _client_name: "Bun"
// _client_version: <version de bun>
// Vous pouvez les voir dans performance_schema.session_connect_attrs de MySQL

Gestion des types

Les types MySQL sont automatiquement convertis en types JavaScript :

Type MySQLType JavaScriptNotes
INT, TINYINT, MEDIUMINTnumberDans la plage d'entiers sûrs
BIGINTstring, number ou BigIntSi la valeur tient dans i32/u32 la taille sera number sinon string ou BigInt selon l'option bigint
DECIMAL, NUMERICstringPour préserver la précision
FLOAT, DOUBLEnumber
DATEDateObjet Date JavaScript
DATETIME, TIMESTAMPDateAvec gestion du fuseau horaire
TIMEnumberTotal de microsecondes
YEARnumber
CHAR, VARCHAR, VARSTRING, STRINGstring
TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXTstring
TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOBstringLes types BLOB sont des alias pour les types TEXT
JSONobject/arrayAutomatiquement analysé
BIT(1)booleanBIT(1) dans MySQL
GEOMETRYstringDonnées géométriques

Différences par rapport à PostgreSQL

Bien que l'API soit unifiée, il y a quelques différences de comportement :

  1. Espaces réservés de paramètres : MySQL utilise ? en interne mais Bun convertit automatiquement le style $1, $2
  2. Clause RETURNING : MySQL ne prend pas en charge RETURNING ; utilisez result.lastInsertRowid ou un SELECT séparé
  3. Types de tableaux : MySQL n'a pas de types de tableaux natifs comme PostgreSQL

Fonctionnalités spécifiques à MySQL

Nous n'avons pas encore implémenté la prise en charge de LOAD DATA INFILE

Fonctionnalités spécifiques à PostgreSQL

Nous n'avons pas encore implémenté :

  • Prise en charge de COPY
  • Prise en charge de LISTEN
  • Prise en charge de NOTIFY

Nous n'avons pas non plus implémenté certaines des fonctionnalités les moins courantes comme :

  • Authentification GSSAPI
  • Prise en charge de SCRAM-SHA-256-PLUS
  • Types Point et PostGIS
  • Tous les types de tableaux d'entiers multidimensionnels (seuls quelques types sont pris en charge)

Modèles courants et meilleures pratiques

Travailler avec des jeux de résultats MySQL

ts
// Obtenir l'ID d'insertion après INSERT
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // LAST_INSERT_ID() de MySQL

// Gérer les lignes affectées
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // Nombre de lignes mises à jour

// Utilisation de fonctions spécifiques à MySQL
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;

Gestion des erreurs MySQL

ts
try {
  await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
  if (error.code === "ER_DUP_ENTRY") {
    console.log("Entrée en double détectée");
  } else if (error.code === "ER_ACCESS_DENIED_ERROR") {
    console.log("Accès refusé");
  } else if (error.code === "ER_BAD_DB_ERROR") {
    console.log("La base de données n'existe pas");
  }
  // Les codes d'erreur MySQL sont compatibles avec les packages mysql/mysql2
}

Conseils de performance pour MySQL

  1. Utiliser la mise en pool des connexions : Définir une taille de pool max appropriée en fonction de votre charge de travail
  2. Activer les instructions préparées : Elles sont activées par défaut et améliorent les performances
  3. Utiliser des transactions pour les opérations en bloc : Grouper les requêtes liées dans des transactions
  4. Indexer correctement : MySQL repose fortement sur les index pour les performances des requêtes
  5. Utiliser le jeu de caractères utf8mb4 : Il est défini par défaut et gère tous les caractères Unicode

Foire aux questions

Pourquoi est-ce `Bun.sql` et pas `Bun.postgres` ?
	Le plan était d'ajouter plus de pilotes de base de données à l'avenir. Maintenant avec la prise en charge de MySQL ajoutée, cette API unifiée prend en charge PostgreSQL, MySQL et SQLite.
Comment savoir quel adaptateur de base de données est utilisé ?
	L'adaptateur est automatiquement détecté depuis la chaîne de connexion :

	- Les URLs commençant par `mysql://` ou `mysql2://` utilisent MySQL
	- Les URLs correspondant aux modèles SQLite (`:memory:`, `sqlite://`, `file://`) utilisent SQLite
	- Tout le reste utilise PostgreSQL par défaut

Les procédures stockées MySQL sont-elles prises en charge ?">
	Oui, les procédures stockées sont entièrement prises en charge y compris les paramètres OUT et les jeux de résultats multiples :

	```ts
	// Appeler une procédure stockée
	const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;

	// Obtenir le paramètre OUT
	const outParam = await mysql`SELECT @total_orders as total`;
	```

Puis-je utiliser une syntaxe SQL spécifique à MySQL ?">
	Oui, vous pouvez utiliser n'importe quelle syntaxe spécifique à MySQL :

	```ts
	// La syntaxe spécifique à MySQL fonctionne bien
	await mysql`SET @user_id = ${userId}`;
	await mysql`SHOW TABLES`;
	await mysql`DESCRIBE users`;
	await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
	```

Pourquoi ne pas simplement utiliser une bibliothèque existante ?

Les packages npm comme postgres.js, pg et node-postgres peuvent également être utilisés dans Bun. Ce sont d'excellentes options.

Deux raisons pour lesquelles :

  1. Nous pensons qu'il est plus simple pour les développeurs d'avoir un pilote de base de données intégré dans Bun. Le temps que vous passez à chercher des bibliothèques est du temps que vous pourriez passer à construire votre application.
  2. Nous exploitons certains internes du moteur JavaScriptCore pour rendre plus rapide la création d'objets qui seraient difficiles à implémenter dans une bibliothèque

Crédits

Un grand merci à @porsager pour postgres.js pour l'inspiration de l'interface API.

Bun édité par www.bunjs.com.cn