執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

SQL

Bun 提供原生繫結,通過統一的基於 Promise 的 API 來操作 SQL 資料庫,支援 PostgreSQL、MySQL 和 SQLite。

You write queries as tagged template literals, and the client supports connection pooling, transactions, and prepared statements.

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

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

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

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

功能特性

  • 帶標籤的模板字面量,可防止 SQL 注入
  • 事務
  • 命名引數和位置引數
  • 連線池
  • BigInt 支援
  • SASL(SCRAM-SHA-256)、MD5 和明文身份驗證
  • 連線超時
  • 將行作為資料物件、陣列的陣列或 Buffer 傳回
  • 二進位制協議支援,可提升速度
  • TLS 支援(以及身份驗證模式)
  • 使用環境變數自動設定
  • PostgreSQL LISTEN / NOTIFY

資料庫支援

Bun.SQL 提供多資料庫系統的統一 API:

PostgreSQL

Bun uses PostgreSQL when:

  • 連線字串不符合 SQLite 或 MySQL 格式(作為預設介面卡)
  • 明確使用 postgres:// 或 postgresql:// 協議的連線字串
  • 無連線字串,但環境變數指向 PostgreSQL
db.ts
import { sql } from "bun";
// 當未設定 DATABASE_URL 或為 PostgreSQL URL 時使用 PostgreSQL
await sql`SELECT ...`;

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

MySQL

MySQL 內置於 Bun.SQL 中,使用相同的標籤模板字面量介面,併兼容 MySQL 5.7+ 和 MySQL 8.0+:

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

// MySQL 連線
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // mysql2 協議同樣支援

// 使用選項物件
const mysql3 = new SQL({
  adapter: "mysql",
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",
});

// 支援引數 — 會自動使用預處理語句
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// 事務和 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}`;
});

// 批次插入
const newUsers = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;
MySQL 連線字串格式

MySQL 支援多種 URL 格式作為連線字串:

TypeScript
// 標準 mysql:// 協議
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // 預設埠 3306

// mysql2:// 協議(相容 mysql2 npm 包)
new SQL("mysql2://user:pass@localhost:3306/database");

// 含查詢引數
new SQL("mysql://user:pass@localhost/db?ssl=true");

// Unix 套接字連線
new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");
MySQL 特有功能

MySQL 資料庫支援:

  • 預處理語句:引數化查詢自動建立且快取語句
  • 二進位制協議:提升預處理語句效能並支援準確型別處理
  • 多結果集:支援儲存過程傳回多個結果集
  • 認證外掛:支援 mysql_native_password、caching_sha2_password(MySQL 8.0 預設)和 sha256_password
  • SSL/TLS 連線:SSL 模式設定類似 PostgreSQL
  • 連線屬性:客戶端資訊傳送到伺服器便於監控
  • 查詢流水線:無需等待響應即可執行多個預處理語句
MySQL 8 在明文 TCP 上進行身份驗證(allowPublicKeyRetrieval)

MySQL 8 賬戶預設使用 caching_sha2_password 外掛。使用者的首次連線需要完整身份驗證:在未加密的連線上,客戶端必須下載伺服器的 RSA 公鑰來加密密碼。Bun 預設拒絕這樣做(與 mysql2 和 Connector/J 的預設設定相同),因為網路路徑上的攻擊者可能替換自己的金鑰並獲取密碼。連線會失敗,並顯示 ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED。

請使用 TLS 連線;如果你信任網路環境(例如本地開發資料庫),也可以顯式選擇啟用:

TypeScript
const mysql = new SQL({
  adapter: "mysql",
  hostname: "localhost",
  username: "root",
  password: "secret",
  database: "myapp",
  allowPublicKeyRetrieval: true, // permit RSA public key retrieval without TLS
});

SQLite

SQLite 內置於 Bun.SQL 中,使用相同的標籤模板字面量介面:

TypeScript
import { SQL } from "bun";

// 記憶體資料庫
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");

// 檔案資料庫
const sql1 = new SQL("sqlite://myapp.db");

// 使用選項物件
const sql2 = new SQL({
  adapter: "sqlite",
  filename: "./data/app.db",
});

// 簡單檔名必須明確告知介面卡
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });
SQLite 連線字串格式

SQLite 支援多種 URL 格式作為連線字串:

TypeScript
// 標準 sqlite:// 協議
new SQL("sqlite://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // 無斜槓

// file:// 協議(也視為 SQLite)
new SQL("file://path/to/database.db");
new SQL("file:path/to/database.db");

// 特殊記憶體資料庫
new SQL(":memory:");
new SQL("sqlite://:memory:");
new SQL("file://:memory:");

// 相對和絕對路徑
new SQL("sqlite://./local.db"); // 相對於當前目錄
new SQL("sqlite://../parent/db.db"); // 上級目錄
new SQL("sqlite:///absolute/path.db"); // 絕對路徑

// 帶引數
new SQL("sqlite://data.db?mode=ro"); // 只讀
new SQL("sqlite://data.db?mode=rw"); // 讀寫無建立
new SQL("sqlite://data.db?mode=rwc"); // 讀寫並允許建立(預設)
SQLite 特定選項

SQLite 支援額外設定選項:

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

  // SQLite 特定選項
  readonly: false, // 只讀開啟
  create: true, // 不存在則建立資料庫
  readwrite: true, // 讀寫訪問

  // 額外 Bun:sqlite 選項
  strict: true, // 開啟嚴格模式
  safeIntegers: false, // 使用 JS 數字處理整數
});

Bun parses query parameters in the URL to set these options:

  • ?mode=ro → readonly: true
  • ?mode=rw → readonly: false, create: false
  • ?mode=rwc → readonly: false, create: true(預設)

插入資料

將 JavaScript 值直接傳遞給 SQL 模板字面量;Bun 會處理轉義。

TypeScript
import { sql } from "bun";

// 基礎插入,直接傳值
const [user] = await sql`
  INSERT INTO users (name, email) 
  VALUES (${name}, ${email})
  RETURNING *
`;

// 使用物件輔助,語法更簡潔
const userData = {
  name: "Alice",
  email: "alice@example.com",
};

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

批次插入

你也可以傳遞物件陣列,Bun 會將其展開為 INSERT INTO ... VALUES ... 語句。

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

選擇插入列

使用 sql(object, ...string) 選擇要插入的列。每一列都必須在物件中定義。

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

await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// 僅插入 name 和 email 列,其餘欄位忽略

查詢結果

預設情況下,Bun 的 SQL 客戶端將查詢結果傳回為物件陣列,其中每個物件表示一行,並以列名作為鍵。還提供另外兩種格式。

sql``.values() 格式

sql``.values() 方法將每一行傳回為值陣列,順序與查詢中的列順序相同。

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

行的形式如下:

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

當查詢傳回重複的列名時,sql``.values() 非常有用。對於物件(預設格式),由於列名是鍵,最後一個列會覆蓋前面的列。使用 sql``.values() 時,每一列都會存在於陣列中,因此可以通過索引讀取重複列。

sql``.raw() 格式

.raw() 方法將行傳回為 Buffer 物件陣列。二進位制資料處理或追求效能時可以使用它。

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

SQL 片段

Bun 可以根據執行環境條件動態建置查詢,同時避免 SQL 注入風險。

動態表名

要動態引用表或模式,請使用 sql() 輔助函式,它會對其進行轉義:

TypeScript
// 安全動態引用表名
await sql`SELECT * FROM ${sql("users")}`;

// 帶模式限定
await sql`SELECT * FROM ${sql("public.users")}`;

條件查詢

使用 sql() 輔助函式建置包含條件子句的查詢:

TypeScript
// 可選 WHERE 子句
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
  SELECT * FROM users
  WHERE active = ${true}
  ${filterAge ? ageFilter : sql``}
`;

更新時動態列

Use sql(object, ...string) to pick which columns to update. Each column must be defined on the object. If you don't list any columns, Bun uses all keys on the object.

TypeScript
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// 使用物件所有鍵更新
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;

動態值和 WHERE IN

值列表也可以動態建立,用於 WHERE IN 查詢。你還可以傳入物件陣列,並指定用於建置列表的鍵名。

TypeScript
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 輔助

sql.array 用於將 JS 陣列轉換為 PostgreSQL 陣列字面量:

TypeScript
// PostgreSQL 陣列字面量
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// 生成:INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])

// 也適用於數值陣列
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// 生成:SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])

sql``.simple()

PostgreSQL 協議支援兩類查詢:“簡單”和“擴充套件”查詢。簡單查詢支援多條語句但不支援引數,擴充套件查詢支援引數但只能一條語句。

要在一次查詢中執行多條語句,使用 sql``.simple():

TypeScript
// 一次執行多條語句
await sql`
  SELECT 1;
  SELECT 2;
`.simple();

簡單查詢適用於資料庫遷移和設定指令碼。

簡單查詢不能使用引數(${value})。如果需要使用引數,請將查詢拆分為多個獨立語句。

檔案查詢

sql.file 從檔案中讀取查詢並執行。如果檔案使用 $1 和 $2 之類的佔位符,可以向查詢傳遞引數。不傳遞引數時,檔案可以包含多個命令。

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

使用 SQLite 介面卡時,引數也可以是命名引數物件(:name、$name 或 @name 佔位符),形式與下文 sql.unsafe 接受的物件相同。

不安全查詢

sql.unsafe 執行原始 SQL 字串。請謹慎使用:它不會轉義使用者輸入。不傳遞引數時,字串可以包含多個命令。

TypeScript
// 多條命令無引數
const result = await sql.unsafe(`
  SELECT ${userColumns} FROM users;
  SELECT ${accountColumns} FROM accounts;
`);

// 帶引數(只支援單條命令)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);

使用 SQLite 介面卡時,引數也可以是使用 :name、$name 或 @name 佔位符的命名引數物件。物件鍵會保留字首({ ":id": 1 }),除非連線設定了 strict: true,此時允許使用不帶字首的鍵:

TypeScript
const sql = new SQL({ adapter: "sqlite", filename: "myapp.db", strict: true });
const result = await sql.unsafe("SELECT * FROM users WHERE id = :id", { id: 1 });

執行和取消查詢

查詢是惰性的:只有在使用 await 或通過 .execute() 執行環境才會開始執行。 要取消正在執行的查詢,請在查詢物件上呼叫 cancel()。

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

資料庫環境變數

你可以使用環境變數設定 sql 連線引數。客戶端會按照優先順序順序檢查這些變數,並根據連線字串的格式檢測資料庫型別。

自動資料庫檢測

當你不帶引數使用 Bun.sql(),或使用帶連線字串的 new SQL() 時,Bun 會根據 URL 格式檢測介面卡:

MySQL 自動檢測

Bun selects MySQL when the connection string matches these patterns:

  • mysql://... — MySQL 協議
  • mysql2://... — MySQL2 協議(相容別名)
TypeScript
// 以下均自動使用 MySQL(無需指定介面卡)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");

// 也支援通過環境變數 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

SQLite 自動檢測

Bun selects SQLite when the connection string matches these patterns:

  • :memory: — 記憶體資料庫
  • sqlite://... — SQLite 協議
  • sqlite:... — 無斜槓的 SQLite 協議
  • file://... — 檔案協議
  • file:... — 無斜槓的檔案協議
TypeScript
// 以下均自動使用 SQLite
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");

// 也支援環境變數 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

PostgreSQL 自動檢測

當不匹配 MySQL 或 SQLite 格式時,預設使用 PostgreSQL:

bash
# PostgreSQL 檢測範例
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js

# 或者任何其他不匹配 MySQL 和 SQLite 的 URL
DATABASE_URL="localhost:5432/mydb" bun run app.js

MySQL 環境變數

You can configure MySQL connections with environment variables:

bash
# 優先使用的連線 URL
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"

# 相容 DATABASE_URL 格式(MySQL 協議)
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"

Without a connection URL, Bun checks these individual parameters:

環境變數預設值說明
MYSQL_HOSTlocalhost資料庫主機
MYSQL_PORT3306埠
MYSQL_USERroot使用者名稱
MYSQL_PASSWORD空字串密碼
MYSQL_DATABASEmysql資料庫名
MYSQL_URL空字串MySQL 主要連線 URL
TLS_MYSQL_DATABASE_URL空字串啟用 SSL/TLS 的連線 URL

PostgreSQL 環境變數

以下環境變數用於定義 PostgreSQL 連線:

環境變數說明
POSTGRES_URLPostgreSQL 主要連線 URL
DATABASE_URL相容的另一個連線 URL
PGURL相容連線 URL
PG_URL相容連線 URL
TLS_POSTGRES_DATABASE_URL啟用 SSL/TLS 的連線 URL
TLS_DATABASE_URL另一個啟用 SSL/TLS 的連線 URL

Without a connection URL, Bun checks these individual parameters:

環境變數備用變數預設值說明
PGHOST-localhost資料庫主機
PGPORT-5432資料庫埠
PGUSERNAMEPGUSER, USER, USERNAMEpostgres資料庫使用者
PGPASSWORD-(空)資料庫密碼
PGDATABASE-username資料庫名稱
PGSSLMODE-disableSSL 模式(disable、allow、prefer、require、verify-ca、verify-full)

SQLite 環境變數

You can configure SQLite connections with DATABASE_URL when it contains a SQLite-compatible URL:

bash
# SQLite 連線範例
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"

Note: Bun ignores PostgreSQL-specific environment variables such as POSTGRES_URL and PGHOST when you use SQLite.


執行環境預連線

Bun 可以在應用程式碼執行之前,於啟動時預先連線到 PostgreSQL,因此首次查詢無需承擔連線延遲。

bash
# 啟用 PostgreSQL 預連線
bun --sql-preconnect index.js

# 配合 DATABASE_URL 環境變數使用
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js

# 可與其他執行環境引數結合
bun --sql-preconnect --hot index.js

The --sql-preconnect flag establishes a PostgreSQL connection at startup using your configured environment variables. If the connection fails, Bun handles the error without crashing your application.


連線選項

你可以通過向 SQL 建構函式傳遞選項來手動設定連線。選項因介面卡而異:

MySQL 選項

TypeScript
import { SQL } from "bun";

const sql = new SQL({
  // MySQL 必須指定
  adapter: "mysql",

  // 連線設定
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Unix 套接字連線(替代 hostname/port)
  // socket: "/var/run/mysqld/mysqld.sock",

  // 連線池設定
  max: 20, // 最大連線數(預設10)
  idleTimeout: 30, // 空閒連線 30 秒後關閉
  maxLifetime: 0, // 連線最大存活時間(秒),0 表示無限
  connectionTimeout: 30, // 建立連線超時(秒)

  // SSL/TLS 選項
  ssl: "prefer", // 可設 "disable", "require", "verify-ca", "verify-full"
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  // },

  // 回撥函式
  onconnect: client => {
    console.log("已連線到 MySQL");
  },
  onclose: (client, err) => {
    if (err) {
      console.error("MySQL 連線錯誤:", err);
    } else {
      console.log("MySQL 連線關閉");
    }
  },
});

PostgreSQL 選項

TypeScript
import { SQL } from "bun";

const sql = new SQL({
  // 連線設定(介面卡自動預設為 PostgreSQL)
  url: "postgres://user:pass@localhost:5432/dbname",

  // 可替代設定
  hostname: "localhost",
  port: 5432,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // 連線池設定
  max: 20, // 最大連線數
  idleTimeout: 30, // 空閒連線 30 秒關閉
  maxLifetime: 0, // 連線最大存活時間,0 表示無限
  connectionTimeout: 30, // 建立連線超時

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

  // 回撥函式
  onconnect: client => {
    console.log("已連線到 PostgreSQL");
  },
  onclose: client => {
    console.log("PostgreSQL 連線關閉");
  },
});

SQLite 選項

TypeScript
import { SQL } from "bun";

const sql = new SQL({
  // 必須指定介面卡
  adapter: "sqlite",
  filename: "./data/app.db", // 或 ":memory:" 表示記憶體資料庫

  // SQLite 檔案訪問模式
  readonly: false, // 只讀開啟
  create: true, // 不存在則建立
  readwrite: true, // 支援讀寫

  // SQLite 資料處理設定
  strict: true, // 開啟嚴格模式提升型別安全
  safeIntegers: false, // 超出 JS 數字範圍的整數使用 BigInt

  // 回撥函式
  onconnect: client => {
    console.log("SQLite 資料庫已開啟");
  },
  onclose: client => {
    console.log("SQLite 資料庫已關閉");
  },
});
SQLite 連線注意事項
  • 連線池:SQLite 是檔案型資料庫,不使用連線池,每個 SQL 例項對應單一連線。
  • 事務:SQLite 支援通過儲存點實現巢狀事務,類似 PostgreSQL。
  • 並行訪問:SQLite 通過檔案鎖控制並行,建議使用 WAL 日誌模式以提升並行能力。
  • 記憶體資料庫::memory: 建立臨時資料庫,僅存活於連線生命週期內。

動態密碼

對於訪問令牌等替代身份驗證方案,或使用輪換密碼的資料庫,請將 password 設定為同步或非同步函式。Bun 會在連線時呼叫該函式以獲取密碼。

TypeScript
import { SQL } from "bun";

const sql = new SQL(url, {
  // 其他連線設定
  ...
  // 用於獲取資料庫使用者密碼的函式
  password: async () => await signer.getAuthToken(),
});

SQLite 特有功能

查詢執行

SQLite 同步執行查詢,與使用非同步 I/O 的 PostgreSQL 不同。API 仍然傳回 Promise:

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

// 使用體驗和 PostgreSQL 一樣,但底層同步執行
const users = await sqlite`SELECT * FROM users`;

// 引數傳遞方式一致
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;

SQLite PRAGMA 語句

使用 PRAGMA 語句設定 SQLite 行為:

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

// 啟用外部索引鍵支援
await sqlite`PRAGMA foreign_keys = ON`;

// 設定 WAL 日誌模式提升並行
await sqlite`PRAGMA journal_mode = WAL`;

// 校驗資料庫完整性
const integrity = await sqlite`PRAGMA integrity_check`;

資料型別差異

SQLite 型別系統比 PostgreSQL 靈活:

TypeScript
// SQLite 儲存 5 種基礎型別:NULL、INTEGER、REAL、TEXT 和 BLOB
const sqlite = new SQL("sqlite://app.db");

// SQLite 型別寬鬆
await sqlite`
  CREATE TABLE flexible (
    id INTEGER PRIMARY KEY,
    data TEXT,        -- 數字能存為字串
    value NUMERIC,    -- 可以是整數、浮點或文本
    blob BLOB         -- 二進位制資料
  )
`;

// JS 值會自動轉換
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;

事務

用 sql.begin 啟動事務,支援 PostgreSQL 和 SQLite。PostgreSQL 會從連線池中保留連線,SQLite 則在單一連線上開始事務。

Bun sends the BEGIN command automatically, including any optional configurations you specify. If an error occurs during the transaction, Bun issues a ROLLBACK.

基本事務

TypeScript
await sql.begin(async tx => {
  // 該函式內所有查詢都運行於事務中
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;

  // 沒有拋錯則自動提交
  // 拋錯則回滾
});

要在事務中對查詢進行流水線處理,請從回撥中傳回查詢陣列:

TypeScript
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)

儲存點會在事務中建立中間檢查點,因此可以回滾其中一部分,而不會中止整個事務。

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

  await tx.savepoint(async sp => {
    // 此處段落可獨立回滾
    await sp`UPDATE users SET status = 'active'`;
    if (someCondition) {
      throw new Error("Rollback to savepoint");
    }
  });

  // 儲存點回滾後繼續事務
  await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});

分散式事務

Two-Phase Commit (2PC) is a distributed transaction protocol. In phase 1, the coordinator prepares each node, making sure its data is written and ready to commit. In phase 2, the nodes commit or roll back based on the coordinator's decision.

在 PostgreSQL 和 MySQL 中,分散式事務會在其原始會話結束後繼續存在,因此具有相應許可權的使用者或協調者可以在之後提交或回滾這些事務。PostgreSQL 將其實現為預備事務;MySQL 使用 XA 事務。

分散式事務期間未捕獲的異常會回滾所有更改。否則,你可以在之後提交或回滾該事務。

TypeScript
// 開始分散式事務
await sql.beginDistributed("tx1", async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});

// 後續提交或回滾
await sql.commitDistributed("tx1");
// 或
await sql.rollbackDistributed("tx1");

認證

Bun supports SCRAM-SHA-256 (SASL), MD5, and Clear Text authentication. We recommend SASL for better security. See Postgres SASL Authentication.

SSL 模式概述

PostgreSQL 的 SSL/TLS 模式控制是否需要安全連線,以及執行何種程度的證書驗證。

TypeScript
const sql = new SQL({
  hostname: "localhost",
  username: "user",
  password: "password",
  ssl: "disable", // 可選 "prefer" | "require" | "verify-ca" | "verify-full"
});
SSL 模式說明
disable不使用 SSL/TLS。如果伺服器要求 SSL,連線將失敗。如果未指定模式,這是預設模式。
prefer首先嚐試使用 SSL;如果 SSL 連線失敗,則回退到非 SSL 連線。
require要求使用 SSL,但不驗證證書。如果無法建立 SSL 連線,則失敗。
verify-ca驗證伺服器證書是否由受信任的 CA 簽名。如果驗證失敗,則失敗。
verify-full最安全的模式。驗證證書及主機名是否匹配。可防止不受信任的證書和中間人攻擊。

連線字串中設定

你也可以在連線字串中設定 SSL 模式:

TypeScript
// prefer 模式
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");

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

連線池

Bun's SQL client manages a connection pool. The pool reuses database connections across queries instead of opening and closing one for each query, and it caps the number of concurrent connections.

TypeScript
const sql = new SQL({
  max: 20, // 最大連線數 20
  idleTimeout: 30, // 空閒 30 秒關閉連線
  maxLifetime: 3600, // 連線最大存活 1 小時
  connectionTimeout: 10, // 連線超時 10 秒
});

Bun doesn't open a connection until you run a query.

TypeScript
const sql = Bun.SQL(); // 未連線

await sql`...`; // 連線池啟動,最多建立 max 連線,複用空閒連線
await sql`...`; // 複用已有連線

// 並行使用多個連線
await Promise.all([
  sql`INSERT INTO users ${sql({ name: "Alice" })}`,
  sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);

await sql.close(); // 等待所有查詢完成並關閉連線池
await sql.close({ timeout: 5 }); // 最多等待 5 秒關閉
await sql.close({ timeout: 0 }); // 立即關閉所有連線

保留連線

sql.reserve() 從連線池中獲取一個連線,並傳回一個封裝該連線的客戶端,以便你可以在隔離的連線上執行查詢。

TypeScript
// 保留獨佔連線
const reserved = await sql.reserve();

try {
  await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
  // 必須釋放連接回池
  reserved.release();
}

// 或使用 Symbol.dispose 自動釋放
{
  using reserved = await sql.reserve();
  await reserved`SELECT 1`;
} // 自動釋放

When every connection is busy, reserve() waits until one frees up. Pass an AbortSignal to stop waiting: the promise rejects with signal.reason and no connection is taken from the pool. Aborting after the promise resolved has no effect, so a connection you received must still be released.

TypeScript
// Give up if no connection frees up within 5 seconds
using reserved = await sql.reserve({ signal: AbortSignal.timeout(5000) });

LISTEN / NOTIFY(PostgreSQL)

sql.listen() 訂閱 PostgreSQL 通知頻道,sql.notify() 向頻道釋出通知。共享資料庫的行程可以將其用作輕量級訊息匯流排:快取失效、在插入行時喚醒工作行程(通常由呼叫 pg_notify 的觸發器完成)、分發小型事件。

TypeScript
import { sql } from "bun";

const subscription = await sql.listen("orders", payload => {
  console.log("new order", JSON.parse(payload));
});

await sql.notify("orders", JSON.stringify({ id: 42 }));

await subscription.unlisten();

listen() 會在 PostgreSQL 確認 LISTEN 後完成,因此在它完成後發出的 notify() 都能夠送達。它所完成得到的訂閱物件也是非同步可釋放物件:

TypeScript
{
  await using subscription = await sql.listen("orders", handleOrder);
  await doWork();
} // 在此處取消監聽

工作原理

  • All subscriptions on a client share one dedicated connection. The first listen() opens it, and removing the last subscription closes it. As a result, a client that never listens never pays for the connection, and unlistening everything lets the process exit without sql.close().
  • While anything is subscribed, that connection keeps the process alive, like a listening server.
  • If the connection drops, Bun re-establishes it with exponential backoff (250ms doubling to 32s, with jitter) and re-subscribes every channel. PostgreSQL only delivers to connected listeners, so notifications sent in between are lost. The optional third argument to listen() runs on the initial subscribe and after every reconnect, which is the place to catch up:
TypeScript
await sql.listen("orders", handleOrder, async () => {
  for (const order of await sql`SELECT * FROM orders WHERE processed = false`) handleOrder(order);
});
  • Every listen() call is its own subscription. Several on one channel share a single server-side LISTEN. Each callback receives every notification, and each handle's unlisten() removes only what its own call registered. A callback (either argument) that throws is reported as an uncaught exception and stays subscribed.
  • Bun quotes channel names as identifiers for you. Like any PostgreSQL identifier, they are limited to 63 bytes, and Bun rejects longer names rather than silently truncating them. PostgreSQL limits payloads to 8000 bytes by default.

notify()

notify() is an ordinary query (SELECT pg_notify($1, $2)) on whichever handle you call it through. On sql it uses the pool. Inside sql.begin() it runs in the transaction, so PostgreSQL delivers it on COMMIT and drops it on ROLLBACK. Calling notify() inside a transaction is how to announce a change only once it is visible:

TypeScript
await sql.begin(async tx => {
  const [order] = await tx`INSERT INTO orders ${sql(data)} RETURNING id`;
  await tx.notify("orders", String(order.id)); // 在事務提交時送達
});

負載是可選的:sql.notify("cache-invalidated") 就是 PostgreSQL 的裸 NOTIFY。保留連線和事務控制代碼也具有 listen();它始終使用客戶端共享的監聽連線。

LISTEN/NOTIFY 僅支援 PostgreSQL;在 MySQL 和 SQLite 上呼叫這些方法會被拒絕。


預處理語句

By default, Bun's SQL client creates named prepared statements for queries it can infer are static, which is faster. To disable named prepared statements, set prepare: false in the connection options:

TypeScript
const sql = new SQL({
  // 其他設定...
  prepare: false, // 停用伺服器端持久命名預處理語句
});

設定 prepare: false 後:

查詢仍使用“擴充套件”協議,但會作為未命名預處理語句執行。未命名預處理語句只會持續到下一條指定將未命名語句作為目標的 Parse 語句發出為止。

  • Parameter binding is still safe against SQL injection

  • The server parses and plans each query from scratch

  • Queries are not pipelined

  • Only one command per query is supported (unless you use sql``.simple())

  • 使用 PGBouncer 事務模式(1.21.0 以後的版本如果設定正確已支援命名語句)

  • 除錯查詢執行計劃

  • 動態 SQL 需頻繁重新規劃查詢

  • 不支援多條語句(除非用 .simple())

  • Using PGBouncer in transaction mode (though since PGBouncer 1.21.0, protocol-level named prepared statements are supported when configured properly)

  • Debugging query execution plans

  • Working with dynamic SQL where query plans need to be regenerated frequently

停用預處理語句可能會減慢使用不同引數頻繁執行的查詢,因為伺服器需要對每次查詢都從頭開始解析和規劃。


錯誤處理

客戶端為不同的失敗場景提供了型別化錯誤。錯誤具有資料庫特定性,並繼承自基礎錯誤類:

錯誤類範例

TypeScript
import { SQL } from "bun";

try {
  await sql`SELECT * FROM users`;
} catch (error) {
  if (error instanceof SQL.PostgresError) {
    // PostgreSQL 專用錯誤
    console.log(error.code); // PostgreSQL 錯誤碼
    console.log(error.detail); // 詳細錯誤資訊
    console.log(error.hint); // 提示
  } else if (error instanceof SQL.SQLiteError) {
    // SQLite 專用錯誤
    console.log(error.code); // SQLite 錯誤碼(如 "SQLITE_CONSTRAINT")
    console.log(error.errno); // SQLite 錯誤號
    console.log(error.byteOffset); // SQL 語句中的位元組偏移(若有)
  } else if (error instanceof SQL.SQLError) {
    // 通用 SQL 錯誤(基類)
    console.log(error.message);
  }
}
PostgreSQL 特定錯誤碼

PostgreSQL 連線錯誤

連線錯誤說明
ERR_POSTGRES_CONNECTION_CLOSED已建立的連線被終止
ERR_POSTGRES_CONNECTION_FAILED連線已被接受,但在握手完成前就被關閉(例如伺服器仍在啟動中)。在查詢等待期間會持續重試並進行退避,直到 connectionTimeout。注意:伺服器在啟動期間傳送的錯誤(如 57P03)會表現為 ERR_POSTGRES_SERVER_ERROR
ERR_POSTGRES_CONNECTION_REFUSED由於地址上沒有服務在監聽,連線被拒絕。會立即失敗且不會重試
ERR_POSTGRES_CONNECTION_TIMEOUT在超時時間內未能建立連線
ERR_POSTGRES_IDLE_TIMEOUT因空閒而關閉連線
ERR_POSTGRES_LIFETIME_TIMEOUT連線超過最大存活時間
ERR_POSTGRES_TLS_NOT_AVAILABLESSL/TLS 連線不可用
ERR_POSTGRES_TLS_UPGRADE_FAILED將連線升級為 SSL/TLS 失敗

認證錯誤

錯誤碼說明
ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2密碼認證失敗
ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHOD伺服器請求未知認證方法
ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHOD伺服器請求不支援的認證方式
ERR_POSTGRES_INVALID_SERVER_KEY認證時伺服器金鑰無效
ERR_POSTGRES_INVALID_SERVER_SIGNATURE伺服器簽名無效
ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64SASL 簽名 Base64 編碼錯誤
ERR_POSTGRES_SASL_SIGNATURE_MISMATCHSASL 簽名驗證失敗

查詢錯誤

錯誤碼說明
ERR_POSTGRES_SYNTAX_ERRORSQL 語法錯誤(繼承自 SyntaxError)
ERR_POSTGRES_SERVER_ERRORPostgreSQL 伺服器中的一般錯誤
ERR_POSTGRES_INVALID_QUERY_BINDING引數繫結錯誤
ERR_POSTGRES_QUERY_CANCELLED查詢被取消
ERR_POSTGRES_NOT_TAGGED_CALL查詢未使用標籤呼叫

資料型別錯誤

錯誤碼說明
ERR_POSTGRES_INVALID_BINARY_DATA二進位制資料格式無效
ERR_POSTGRES_INVALID_BYTE_SEQUENCE位元組序列無效
ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODING編碼錯誤
ERR_POSTGRES_INVALID_CHARACTER無效字元
ERR_POSTGRES_OVERFLOW數值溢位
ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMAT不支援的二進位制格式
ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZE整數大小不支援
ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YET暫不支援多維陣列
ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YET暫不支援陣列中包含 NULL

The client raises these errors while it decodes a result, after the server ran the query. Only that query rejects. The connection stays open, and the other queries on it continue. An INSERT, UPDATE or DELETE ... RETURNING can already be stored at that point, so do not retry a write blindly. To read the value, cast the column to text or use .raw().

Protocol Errors

錯誤碼說明
ERR_POSTGRES_EXPECTED_REQUEST期望客戶端請求
ERR_POSTGRES_EXPECTED_STATEMENT期望預處理語句
ERR_POSTGRES_INVALID_BACKEND_KEY_DATA後端金鑰資料無效
ERR_POSTGRES_INVALID_MESSAGE協議訊息無效
ERR_POSTGRES_INVALID_MESSAGE_LENGTH訊息長度無效
ERR_POSTGRES_UNEXPECTED_MESSAGE意外訊息型別

事務錯誤

錯誤碼說明
ERR_POSTGRES_UNSAFE_TRANSACTION發現不安全的事務操作
ERR_POSTGRES_INVALID_TRANSACTION_STATE事務狀態非法

SQLite 特有錯誤

SQLite 錯誤包含 SQLite 的標準錯誤碼和錯誤號:

常見 SQLite 錯誤碼
錯誤碼errno說明
SQLITE_CONSTRAINT19約束衝突(唯一、檢查、非空等)
SQLITE_BUSY5資料庫正被鎖定
SQLITE_LOCKED6表被鎖
SQLITE_READONLY8試圖寫入只讀資料庫
SQLITE_IOERR10磁碟 I/O 錯誤
SQLITE_CORRUPT11資料庫檔案損壞
SQLITE_FULL13磁碟空間或資料庫已滿
SQLITE_CANTOPEN14無法開啟資料庫檔案
SQLITE_PROTOCOL15資料庫鎖協議錯誤
SQLITE_SCHEMA17資料庫結構已更改
SQLITE_TOOBIG18字串或 BLOB 過長
SQLITE_MISMATCH20資料型別不匹配
SQLITE_MISUSE21錯誤使用 SQLite 庫
SQLITE_AUTH23訪問許可權被拒絕

錯誤處理範例:

TypeScript
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')`; // 主鍵重複
} catch (error) {
  if (error instanceof SQL.SQLiteError) {
    if (error.code === "SQLITE_CONSTRAINT") {
      console.log("約束衝突:", error.message);
      // 處理唯一約束衝突
    }
  }
}

數字與 BigInt

Bun returns PostgreSQL bigint (int8) values, and MySQL BIGINT values outside the 32-bit range, as strings:

TypeScript
import { sql } from "bun";

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

console.log(typeof x, x); // "字串" "9223372036854777"
console.log(typeof y, y); // "數字" 12345

使用 BigInt 代替字串

要將大數作為 BigInt 而不是字串獲取,請在建立 SQL 客戶端時將 bigint 選項設定為 true:

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

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

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

路線圖

我們尚未完成的事項:

  • Column name transforms (for example, snake_case to camelCase). This is mostly blocked on a unicode-aware implementation of changing the case in C++ using WebKit's WTF::String.
  • Column type transforms

資料庫特有功能

認證方式

MySQL 支援多種認證外掛,自動協商:

MySQL supports multiple authentication plugins, which the client negotiates automatically:

客戶端自動處理伺服器請求切換認證外掛,包括非 SSL 下的密碼安全交換。

The client automatically handles authentication plugin switching when the server requests it.

MySQL 對所有引數化查詢使用伺服器端預處理語句:

TypeScript
// 自動建立伺服器端預處理語句
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// 預處理語句快取並重復使用
for (const id of userIds) {
  await mysql`SELECT * FROM users WHERE id = ${id}`; // 重用同一預處理語句
}

// 查詢流水線 - 並行傳送多個語句
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}`,
]);

多結果集

MySQL 支援多語句查詢傳回多個結果集:

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

// 使用 simple() 多語句查詢
const multiResults = await mysql`
  SELECT * FROM users WHERE id = 1;
  SELECT * FROM orders WHERE user_id = 1;
`.simple();

字元集與排序規則

Bun.SQL 對 MySQL 連線使用 utf8mb4 字元集,涵蓋包括表情符號在內的所有 Unicode 字元。

連線屬性

Bun 會向 MySQL 傳送客戶端資訊以便進行監控:

TypeScript
// 自動傳送以下屬性:
// _client_name: "Bun"
// _client_version: <bun 版本號>
// 可以在 MySQL performance_schema.session_connect_attrs 中檢視

型別處理

Bun converts MySQL types to JavaScript types:

MySQL 型別JavaScript 型別說明
INT, TINYINT, MEDIUMINTnumber在安全整數範圍內
BIGINTstring, number or BigInt如果值適合 i32/u32,則為 number,否則根據 bigint 選項為 string 或 BigInt
DECIMAL, NUMERICstring用於保留精度
FLOAT, DOUBLEnumber
DATEDateJavaScript Date object
DATETIME, TIMESTAMPDateDecoded as UTC (see note below); 0000-00-00 becomes an Invalid Date
TIMEstringFormatted as HH:MM:SS (HHH:MM:SS above 99 hours); prepared queries drop fractional seconds
YEARnumber
CHAR, VARCHAR, VARSTRING, STRINGstring
TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXTstring
TINY BLOB, MEDIUM BLOB, BLOB, LONG BLOBBufferSame wire types as TEXT; Bun returns a Buffer when the column uses the binary character set
JSONobject/arrayAutomatically parsed
BIT(1)booleanBIT(1) in MySQL
GEOMETRYBufferBinary character set; the bytes are a 4-byte SRID followed by WKB

DATETIME and TIMESTAMP values have no timezone on the wire, so Bun reads them back as UTC. The Date you get has the same UTC wall-clock that was stored, regardless of the machine's timezone. Reading as UTC matches how Bun writes values (a bound Date stores its UTC components). The same applies to PostgreSQL's timestamp (without time zone); timestamptz carries an explicit offset and is unaffected.

Bun also sets time_zone = '+00:00' on every MySQL connection it opens. The server converts TIMESTAMP columns through the session time zone, so this keeps the stored instant equal to the Date you bound, and it makes server-side time functions such as NOW() and CURRENT_TIMESTAMP evaluate in UTC. There is no option to pick a different session time zone yet: a manual SET time_zone = ... reaches only the one pooled connection that runs it (#40254 tracks a per-connection option).

與 PostgreSQL 的差異

API 是統一的,但行為有所不同:

  1. 引數佔位符:MySQL 內部使用 ?,Bun 自動轉換 $1、$2 風格佔位符
  2. RETURNING 語法:MySQL 不支援 RETURNING,使用 result.lastInsertRowid 或單獨 SELECT
  3. 陣列型別:MySQL 無原生陣列型別,區別於 PostgreSQL

MySQL 特有功能

我們尚未實現對 LOAD DATA INFILE 的支援。

PostgreSQL 特有功能

尚未實現:

  • COPY 支援

未實現的一些較少用特性:

  • GSSAPI 認證
  • SCRAM-SHA-256-PLUS 支援
  • Point 和 PostGIS 型別
  • 多維整數陣列(僅支援部分型別)

常見模式與最佳實踐

處理 MySQL 結果集

TypeScript
// 獲取插入 ID
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // MySQL 的 LAST_INSERT_ID()

// 受影響的行數
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // 更新行數

// 使用 MySQL 專用函式
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;

MySQL 錯誤處理

TypeScript
try {
  await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
  if (error.code === "ER_DUP_ENTRY") {
    console.log("檢測到重複條目");
  } else if (error.code === "ER_ACCESS_DENIED_ERROR") {
    console.log("訪問被拒絕");
  } else if (error.code === "ER_BAD_DB_ERROR") {
    console.log("資料庫不存在");
  }
  // MySQL 錯誤碼與 mysql/mysql2 包相容
}

MySQL 效能最佳化建議

  1. 使用連線池:根據負載設定合理的 max 連線數
  2. 啟用預處理語句:預設開啟,提升效能
  3. 批次操作用事務包裹:關聯查詢放在同個事務裡
  4. 合理建立索引:MySQL 查詢效能相依索引策略
  5. 使用 utf8mb4 字元集:預設啟用,支援完整 Unicode 字元。

常見問題

為什麼是這個 `Bun.sql`,而不是 `Bun.postgres`?

最初的計劃是新增更多資料庫驅動。統一 API 現在支援 PostgreSQL、MySQL 和 SQLite。

How do I know which database adapter is being used?

Bun detects the adapter automatically from the connection string:

  • 以 mysql:// 或 mysql2:// 開頭為 MySQL
  • 匹配 SQLite 格式(:memory:、sqlite://、file://)為 SQLite
  • 其他預設使用 PostgreSQL
支援 MySQL 儲存過程嗎?

支援,包括 OUT 引數和多個結果集:

TypeScript
// 呼叫儲存過程
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;

// 獲取 OUT 引數
const outParam = await mysql`SELECT @total_orders as total`;
可以使用 MySQL 特有語法嗎?

當然,可以使用所有 MySQL 專屬語法:

TypeScript
// MySQL 特殊語法範例
await mysql`SET @user_id = ${userId}`;
await mysql`SHOW TABLES`;
await mysql`DESCRIBE users`;
await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;

為什麼不直接用已有庫?

你也可以在 Bun 中使用 postgres.js、pg 和 node-postgres 等 npm 包。它們都是很好的選擇。

但有兩個原因:

  1. 我們認為,對於開發者來說,將資料庫驅動內建到 Bun 中會更加簡單。你花在挑選庫上的時間,本可以用來建置應用。
  2. 我們利用了一些 JavaScriptCore 引擎內部機制來更快地建立物件,而這些機制很難在庫中實現。

鳴謝

特別感謝 @porsager 的 postgres.js 對 API 介面設計的啟發。