Skip to content

このインターフェースはシンプルでパフォーマンスに優れるように設計されており、クエリにはタグ付きテンプレートリテラルを使用し、コネクションプーリング、トランザクション、プリペアドステートメントなどの機能を提供します。

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 サポート(および認証モード)
  • 環境変数による自動構成

データベースサポート

Bun.SQL は複数のデータベースシステム向けの統一された API を提供します:

PostgreSQL

PostgreSQL は以下の場合に使用されます:

  • 接続文字列が SQLite または MySQL パターンに一致しない場合(フォールバックアダプター)
  • 接続文字列が明示的に postgres:// または postgresql:// プロトコルを使用している場合
  • 接続文字列が提供されておらず、環境変数が PostgreSQL を指している場合
ts
import { sql } from "bun";
// DATABASE_URL が設定されていない場合、または PostgreSQL URL の場合に使用
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+ と完全互換性のあるタグ付きテンプレートリテラルインターフェースを提供します:

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 フォーマットを受け入れます:

ts
// 標準 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 接続: PostgreSQL と同様の構成可能な SSL モード
  • 接続属性: 監視のためにサーバーに送信されるクライアント情報
  • クエリパイプライン: 応答を待たずに複数のプリペアドステートメントを実行

SQLite

SQLite サポートは Bun.SQL に組み込まれており、同じタグ付きテンプレートリテラルインターフェースを提供します:

ts
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 フォーマットを受け入れます:

ts
// 標準 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");

// 特別な :memory: データベース
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"); // 読み取り - 書き込み - 作成モード(デフォルト)

NOTE

プロトコルなしのシンプルなファイル名(`"myapp.db"` など)は、PostgreSQL との曖昧さを避けるために `{ adapter: "sqlite" }` を明示的に指定する必要があります。

SQLite 固有のオプション

SQLite データベースは追加の構成オプションをサポートします:

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

  // SQLite 固有のオプション
  readonly: false, // 読み取り専用モードで開く
  create: true, // データベースが存在しない場合に作成
  readwrite: true, // 読み取りおよび書き込み用に開く

  // 追加の Bun:sqlite オプション
  strict: true, // 厳密モードを有効化
  safeIntegers: false, // 整数に JavaScript 数値を使用
});

URL のクエリパラメーターは解析されてこれらのオプションに設定されます:

  • ?mode=roreadonly: true
  • ?mode=rwreadonly: false, create: false
  • ?mode=rwcreadonly: false, create: true(デフォルト)

データの挿入

JavaScript 値を SQL テンプレートリテラルに直接渡すことができ、エスケープは自動的に処理されます。

ts
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')

一括挿入

オブジェクトの配列を SQL テンプレートリテラルに渡すこともでき、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)}`;

挿入する列の選択

sql(object, ...string) を使用して挿入する列を選択できます。各列はオブジェクト上で定義されている必要があります。

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

await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// name と email 列のみを挿入し、他のフィールドを無視

クエリ結果

デフォルトでは、Bun の SQL クライアントはクエリ結果をオブジェクトの配列として返します。各オブジェクトは列名をキーとする行を表します。ただし、データを異なる形式で取得したい場合があります。このために、クライアントは 2 つの追加メソッドを提供します。

sql``.values() フォーマット

sql``.values() メソッドは、行をオブジェクトではなく値の配列として返します。各行はクエリの列と同じ順序の値の配列になります。

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

これは以下のようなものを返します:

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

sql``.values() は、クエリ結果に重複した列名が返される場合に特に役立ちます。オブジェクト(デフォルト)を使用する場合、最後の列名がオブジェクトのキーとして使用されるため、重複した列名は互いに上書きされますが、sql``.values() を使用すると、各列が配列に存在するため、インデックスで重複した列の値にアクセスできます。

sql``.raw() フォーマット

.raw() メソッドは、行を Buffer オブジェクトの配列として返します。これはバイナリデータの操作やパフォーマンス上の理由で役立ちます。

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

SQL フラグメント

データベースアプリケーションで一般的な必要性は、ランタイム条件に基づいてクエリを動的に構築することです。Bun は SQL インジェクションのリスクなしにこれを行うための安全な方法を提供します。

動的なテーブル名

テーブルやスキーマを動的に参照する必要がある場合は、sql() ヘルパーを使用して適切なエスケープを確保します:

ts
// テーブルを安全に参照
await sql`SELECT * FROM ${sql("users")}`;

// スキーマ修飾付き
await sql`SELECT * FROM ${sql("public.users")}`;

条件付きクエリ

sql() ヘルパーを使用して条件付き句を含むクエリを構築できます。これにより、アプリケーションのニーズに適応する柔軟なクエリを作成できます:

ts
// オプションの WHERE 句
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
  SELECT * FROM users
  WHERE active = ${true}
  ${filterAge ? ageFilter : sql``}
`;

更新時の動的な列

sql(object, ...string) を使用して更新する列を選択できます。各列はオブジェクト上で定義されている必要があります。列が指定されていない場合、すべてのキーが行の更新に使用されます。

ts
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 クエリも簡単になります。オプションで、オブジェクトの配列を渡し、リストの作成に使用するキーを指定できます。

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 ヘルパー

sql.array ヘルパーは、JavaScript 配列から PostgreSQL 配列リテラルを作成します:

ts
// 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])

NOTE

`sql.array` は PostgreSQL のみです。多次元配列と NULL 要素はまだサポートされていない可能性があります。

sql``.simple()

PostgreSQL ワイヤープロトコルは「シンプル」と「拡張」の 2 種類のクエリをサポートしています。シンプルクエリは複数のステートメントを含めることができますがパラメーターをサポートしておらず、拡張クエリ(デフォルト)はパラメーターをサポートしますが 1 つのステートメントのみを許可します。

単一のクエリで複数のステートメントを実行するには、sql``.simple() を使用します:

ts
// 1 つのクエリで複数のステートメント
await sql`
  SELECT 1;
  SELECT 2;
`.simple();

シンプルクエリは、データベースマイグレーションやセットアップスクリプトでよく役立ちます。

シンプルクエリはパラメーター(${value})を使用できないことに注意してください。パラメーターが必要な場合は、クエリを個別のステートメントに分割する必要があります。

ファイル内のクエリ

sql.file メソッドを使用してファイルからクエリを読み取り、実行できます。ファイルに $1、$2 などが含まれている場合は、クエリにパラメーターを渡せます。パラメーターが使用されていない場合、ファイルごとに複数のコマンドを実行できます。

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

安全でないクエリ

sql.unsafe 関数を使用して生の SQL 文字列を実行できます。ユーザー入力をエスケープしないため、注意して使用してください。パラメーターが使用されていない場合、クエリごとに複数のコマンドを実行できます。

ts
// パラメーターなしで複数のコマンド
const result = await sql.unsafe(`
  SELECT ${userColumns} FROM users;
  SELECT ${accountColumns} FROM accounts;
`);

// パラメーターを使用(1 つのコマンドのみ許可)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);

クエリの実行とキャンセル

Bun の SQL は遅延評価であり、await されるか .execute() で実行されるまで実行を開始しません。 クエリオブジェクトで cancel() メソッドを呼び出すことで、現在実行中のクエリをキャンセルできます。

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

データベース環境変数

sql 接続パラメーターは環境変数を使用して構成できます。クライアントはこれらの変数を特定の優先順位でチェックし、接続文字列フォーマットに基づいてデータベースタイプを自動的に検出します。

自動データベース検出

Bun.sql() を引数なしで使用するか、new SQL() を接続文字列で使用する場合、アダプターは URL フォーマットに基づいて自動的に検出されます:

MySQL 自動検出

接続文字列がこれらのパターンに一致する場合、MySQL は自動的に選択されます:

  • mysql://... - MySQL プロトコル URL
  • mysql2://... - MySQL2 プロトコル URL(互換性エイリアス)
ts
// これらはすべて自動的に 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 自動検出

接続文字列がこれらのパターンに一致する場合、SQLite は自動的に選択されます:

  • :memory: - インメモリデータベース
  • sqlite://... - SQLite プロトコル URL
  • sqlite:... - スラッシュなしの SQLite プロトコル
  • file://... - ファイルプロトコル URL
  • file:... - スラッシュなしのファイルプロトコル
ts
// これらはすべて自動的に 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 自動検出

PostgreSQL は、MySQL または SQLite パターンに一致しない接続文字列のデフォルトです:

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 環境変数

MySQL 接続は環境変数を通じて構成できます:

bash
# 主要接続 URL(最初にチェック)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"

# 代替:MySQL プロトコルの DATABASE_URL
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"

接続 URL が提供されていない場合、MySQL はこれらの個別パラメーターをチェックします:

環境変数デフォルト値説明
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_URLSSL/TLS 有効化接続 URL
TLS_DATABASE_URL代替 SSL/TLS 有効化接続 URL

接続 URL が提供されていない場合、システムはこれらの個別パラメーターをチェックします:

環境変数フォールバック変数デフォルト値説明
PGHOST-localhostデータベースホスト
PGPORT-5432データベースポート
PGUSERNAMEPGUSER, USER, USERNAMEpostgresデータベースユーザー
PGPASSWORD-(空)データベースパスワード
PGDATABASE-ユーザー名データベース名

SQLite 環境変数

SQLite 接続は、SQLite 互換 URL を含む場合 DATABASE_URL を通じて構成できます:

bash
# これらはすべて SQLite として認識
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"

注: PostgreSQL 固有の環境変数(POSTGRES_URLPGHOST など)は、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

--sql-preconnect フラグは、起動時に構成された環境変数を使用して PostgreSQL 接続を自動的に確立します。接続が失敗しても、アプリケーションはクラッシュしません。エラーは適切に処理されます。


接続オプション

SQL コンストラクターにオプションを渡すことで、データベース接続を手動で構成できます。オプションはデータベースアダプターによって異なります:

MySQL オプション

ts
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 オプション

ts
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 オプション

ts
import { SQL } from "bun";

const sql = new SQL({
  // SQLite に必須
  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 と同様に、savepoint を通じてネストされたトランザクションをサポートします。
  • 同時アクセス: SQLite はファイルロックを通じて同時アクセスを処理します。より良い同時実行性のために WAL モードを使用してください。
  • メモリデータベース: :memory: を使用すると、接続ライフタイムの間のみ存在する一時データベースが作成されます。

動的パスワード

クライアントがアクセストークンなどの代替認証スキームや、ローテーションパスワードを使用するデータベースへの接続を必要とする場合、接続時に動的パスワード値を解決する同期または非同期関数のいずれかを提供できます。

ts
import { SQL } from "bun";

const sql = new SQL(url, {
  // 他の接続構成
  ...
  // データベースユーザーのパスワード関数
  password: async () => await signer.getAuthToken(),
});

SQLite 固有の機能

クエリ実行

SQLite は非同期 I/O を使用する PostgreSQL とは異なり、クエリを同期的に実行します。ただし、API は Promise を使用して一貫性を保ちます:

ts
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 ステートメントを使用して SQLite の動作を構成できます:

ts
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 よりも柔軟な型システムを持っています:

ts
// 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         -- バイナリデータ
  )
`;

// JavaScript 値は自動的に変換
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;

トランザクション

新しいトランザクションを開始するには、sql.begin を使用します。このメソッドは PostgreSQL と SQLite の両方で動作します。PostgreSQL の場合、プールから専用接続を予約します。SQLite の場合、単一接続でトランザクションを開始します。

BEGIN コマンドは自動的に送信され、指定した任意の構成が含まれます。トランザクション中にエラーが発生した場合、ROLLBACK がトリガーされてプロセスがスムーズに続行されます。

基本的なトランザクション

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

  // エラーがスローされない場合、トランザクションは自動的にコミット
  // エラーが発生した場合、ロールバック
});

必要に応じて、コールバック関数からクエリの配列を返すことで、トランザクション内のリクエストをパイプラインすることもできます:

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

セーブポイント

SQL のセーブポイントは、トランザクション内の中間チェックポイントを作成し、操作全体に影響を与えることなく部分的なロールバックを可能にします。これらは複雑なトランザクションで役立ち、エラー回復と一貫した結果の維持を可能にします。

ts
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("セーブポイントにロールバック");
    }
  });

  // セーブポイントがロールバックされた場合でもトランザクションを続行
  await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});

分散トランザクション

2 相コミット(2PC)は、フェーズ 1 でコーディネーターがノードを準備し、データが書き込まれてコミット準備ができていることを確認し、フェーズ 2 でコーディネーターの決定に基づいてノードがコミットまたはロールバックする分散トランザクションプロトコルです。このプロセスはデータの耐久性と適切なロック管理を確保します。

PostgreSQL と MySQL では、分散トランザクションは元のセッションを超えて持続し、特権ユーザーまたはコーディネーターが後でコミットまたはロールバックできます。これは堅牢な分散トランザクション、リカバリプロセス、および管理操作をサポートします。

分散トランザクション中に例外が発生し、キャッチされない場合、システムは自動的にすべての変更をロールバックします。すべてが正常に進む場合、後でトランザクションをコミットまたはロールバックする柔軟性が維持されます。

ts
// 分散トランザクションを開始
await sql.beginDistributed("tx1", async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});

// 後で、コミットまたはロールバック
await sql.commitDistributed("tx1");
// または
await sql.rollbackDistributed("tx1");

認証

Bun は SCRAM-SHA-256(SASL)、MD5、およびクリアテキスト認証をサポートしています。より良いセキュリティのために SASL をお勧めします。詳細については Postgres SASL Authentication をご覧ください。

SSL モードの概要

PostgreSQL は、安全な接続が確立される方法を制御するためにさまざまな SSL/TLS モードをサポートしています。これらのモードは、接続時の動作と実行される証明書検証のレベルを決定します。

ts
const sql = new SQL({
  hostname: "localhost",
  username: "user",
  password: "password",
  ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});
SSL モード説明
disableSSL/TLS は使用されません。サーバーが SSL を必要とする場合、接続は失敗します。
prefer最初に SSL を試し、SSL が失敗した場合は非 SSL にフォールバックします。指定がない場合のデフォルトモード。
require証明書検証なしで SSL が必要です。SSL を確立できない場合、失敗します。
verify-caサーバー証明書が信頼された CA によって署名されていることを検証します。検証が失敗した場合、失敗します。
verify-full最も安全なモード。証明書とホスト名の一致を検証します。信頼できない証明書と MITM 攻撃から保護します。

接続文字列での使用

SSL モードは接続文字列でも指定できます:

ts
// 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 の SQL クライアントはコネクションプールを自動的に管理します。これは、複数のクエリで再利用されるデータベース接続のプールです。これにより、クエリごとに接続を確立して閉じるオーバーヘッドを削減し、データベースへの同時接続の数を管理するのに役立ちます。

ts
const sql = new SQL({
  // プール構成
  max: 20, // 最大 20 の同時接続
  idleTimeout: 30, // 30 秒後にアイドル接続を閉じる
  maxLifetime: 3600, // 最大接続ライフタイム 1 時間
  connectionTimeout: 10, // 接続タイムアウト 10 秒
});

クエリが実行されるまで接続は作成されません。

ts
const sql = Bun.SQL(); // 接続は作成されません

await sql`...`; // プールは max に達するまで開始(可能であれば)、最初に利用可能な接続が使用されます
await sql`...`; // 以前の接続が再利用されます

// 2 つの接続が同時に使用されます
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 }); // 即座にプールからすべての接続を閉じます

予約接続

Bun を使用すると、プールから接続を予約し、単一の接続をラップするクライアントを返すことができます。これは、分離された接続でクエリを実行するために使用できます。

ts
// プールから専用接続を取得
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`;
} // 自動的にリリース

プリペアドステートメント

デフォルトでは、Bun の SQL クライアントはクエリが静的であると推測できるクエリに対して、名前付きプリペアドステートメントを自動的に作成します。これにより、パフォーマンスが向上します。ただし、接続オプションで prepare: false を設定することで、この動作を変更できます:

ts
const sql = new SQL({
  // ... 他のオプション ...
  prepare: false, // サーバー上の名前付きプリペアドステートメントの永続化を無効化
});

prepare: false が設定されている場合:

クエリは引き続き「拡張」プロトコルを使用して実行されますが、名前なしプリペアドステートメント を使用して実行されます。名前なしプリペアドステートメントは、宛先として名前なしステートメントを指定する次の Parse ステートメントが発行されるまでしか持続しません。

  • パラメーターバインディングは引き続き SQL インジェクションから安全です
  • 各クエリはサーバーによって最初から解析および計画されます
  • クエリはパイプラインされません

prepare: false を使用したい場合:

  • トランザクションモードで PGBouncer を使用する場合(ただし PGBouncer 1.21.0 以降は、適切に構成されている場合、プロトコルレベルの名前付きプリペアドステートメントがサポートされています)
  • クエリ実行プランをデバッグする場合
  • クエリプランを頻繁に再生成する必要がある動的 SQL を使用する場合
  • クエリごとに複数のコマンドはサポートされません(sql``.simple() を使用しない限り)

プリペアドステートメントを無効にすると、異なるパラメーターで頻繁に実行されるクエリのパフォーマンスに影響を与える可能性があることに注意してください。サーバーは各クエリを最初から解析および計画する必要があるためです。


エラーハンドリング

クライアントはさまざまな失敗シナリオに対して型付きエラーを提供します。エラーはデータベース固有であり、基本エラークラスから拡張されます:

エラークラス

ts
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); // PostgreSQL からの役立つヒント
  } 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_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_BASE64無効な SASL 署名エンコーディング
ERR_POSTGRES_SASL_SIGNATURE_MISMATCHSASL 署名検証に失敗しました

クエリエラー

クエリエラー説明
ERR_POSTGRES_SYNTAX_ERROR無効な SQL 構文(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 値はサポートされていません

プロトコルエラー

プロトコルエラー説明
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制約違反(UNIQUE、CHECK、NOT NULL など)
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_AUTH23認証が拒否されました

エラーハンドリングの例:

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
} catch (error) {
  if (error instanceof SQL.SQLiteError) {
    if (error.code === "SQLITE_CONSTRAINT") {
      console.log("制約違反:", error.message);
      // 一意の制約違反を処理
    }
  }
}

数値と BigInt

Bun の SQL クライアントには、53 ビット整数の範囲を超える大きな数値の特別な処理が含まれています。動作方法は次のとおりです:

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

大きな数値を文字列の代わりに BigInt として必要な場合は、SQL クライアントを初期化する際に bigint オプションを true に設定することで有効にできます:

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

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

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

ロードマップ

まだ完了していないことがいくつかあります:

  • --db-preconnect Bun CLI フラグによる接続事前ロード
  • 列名変換(例:snake_case から camelCase へ)。これは主に C++ での WebKit の WTF::String を使用した Unicode 対応の大文字小文字変更の実装に依存しています。
  • 列型変換

データベース固有の機能

認証方法

MySQL は自動的にネゴシエートされる複数の認証プラグインをサポートしています:

  • mysql_native_password - 従来の MySQL 認証、広く互換性あり
  • caching_sha2_password - MySQL 8.0+ でデフォルト、RSA キー交換によるより安全
  • sha256_password - SHA-256 ベースの認証

クライアントは、サーバーによって要求された場合、SSL 非接続での安全なパスワード交換を含む認証プラグインの切り替えを自動的に処理します。

プリペアドステートメントとパフォーマンス

MySQL はパラメーター化クエリに対してサーバーサイドプリペアドステートメントを使用します:

ts
// これは自動的にサーバー上にプリペアドステートメントを作成
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 はマルチステートメントクエリから複数の結果セットを返すことができます:

ts
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 サポートを確保します。これは現代の MySQL アプリケーションに推奨される文字セットです。

接続属性

Bun はより良い監視のために MySQL にクライアント情報を自動的に送信します:

ts
// これらの属性は自動的に送信:
// _client_name: "Bun"
// _client_version: <bun バージョン>
// これらは MySQL の performance_schema.session_connect_attrs で確認可能

型処理

MySQL 型は自動的に JavaScript 型に変換されます:

MySQL 型JavaScript 型注釈
INT, TINYINT, MEDIUMINTnumber安全な整数範囲内
BIGINTstring, number または BigInt値が i32/u32 サイズに収まる場合は number、それ以外は bigint オプションに基づき string または BigInt
DECIMAL, NUMERICstring精度を保持するため
FLOAT, DOUBLEnumber
DATEDateJavaScript Date オブジェクト
DATETIME, TIMESTAMPDateタイムゾーン処理付き
TIMEnumberマイクロ秒の合計
YEARnumber
CHAR, VARCHAR, VARSTRING, STRINGstring
TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXTstring
TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOBstringBLOB 型は TEXT 型のエイリアス
JSONobject/array自動的に解析
BIT(1)booleanMySQL の BIT(1)
GEOMETRYstringジオメトリデータ

PostgreSQL との違い

API は統一されていますが、いくつかの動作の違いがあります:

  1. パラメータープレースホルダー: MySQL は内部的に ? を使用しますが、Bun は自動的に $1, $2 スタイルに変換します
  2. RETURNING 句: MySQL は RETURNING をサポートしていません。result.lastInsertRowid を使用するか、別の SELECT を使用してください
  3. 配列型: MySQL は PostgreSQL のようなネイティブ配列型を持っていません

MySQL 固有の機能

まだ LOAD DATA INFILE サポートを実装していません

PostgreSQL 固有の機能

まだ以下を実装していません:

  • COPY サポート
  • LISTEN サポート
  • NOTIFY サポート

また、以下のようなより一般的でない機能も実装していません:

  • GSSAPI 認証
  • SCRAM-SHA-256-PLUS サポート
  • Point および PostGIS 型
  • すべての多次元整数配列型(いくつかの型のみサポート)

一般的なパターンとベストプラクティス

MySQL 結果セットの操作

ts
// INSERT 後の挿入 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 エラーハンドリング

ts
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 ではないのですか?

将来的により多くのデータベースドライバーを追加する計画でした。現在 MySQL サポートが追加され、この統一された API は PostgreSQL、MySQL、および SQLite をサポートしています。

どのデータベースアダプターが使用されているかをどのように知ることができますか?

アダプターは接続文字列から自動的に検出されます:

  • mysql:// または mysql2:// で始まる URL は MySQL を使用
  • SQLite パターン(:memory:sqlite://file://)に一致する URL は SQLite を使用
  • その他はすべて PostgreSQL がデフォルト

MySQL ストアドプロシージャはサポートされていますか?

はい、ストアドプロシージャは OUT パラメーターと複数の結果セットを含めて完全にサポートされています:

ts
// ストアドプロシージャを呼び出し
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;

// OUT パラメーターを取得
const outParam = await mysql`SELECT @total_orders as total`;

MySQL 固有の SQL 構文を使用できますか?

はい、MySQL 固有の構文はすべて使用できます:

ts
// MySQL 固有の構文は問題なく動作
await mysql`SET @user_id = ${userId}`;
await mysql`SHOW TABLES`;
await mysql`DESCRIBE users`;
await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;

なぜ既存のライブラリを使用しないのですか?

postgres.js、pg、node-postgres などの npm パッケージも Bun で使用できます。これらは素晴らしいオプションです。

2 つの理由があります:

  1. 開発者にとって、Bun に組み込まれたデータベースドライバーがある方がシンプルだと考えています。ライブラリショッピングに費やす時間は、アプリの構築に費やすことができる時間です。
  2. ライブラリで実装するのが難しいオブジェクトを作成するために、JavaScriptCore エンジンの内部を利用しています

クレジット

API インターフェースのインスピレーションを提供してくれた @porsagerpostgres.js に感謝します。

Bun by www.bunjs.com.cn 編集