このインターフェースはシンプルでパフォーマンスに優れるように設計されており、クエリにはタグ付きテンプレートリテラルを使用し、コネクションプーリング、トランザクション、プリペアドステートメントなどの機能を提供します。
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 を指している場合
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+ と完全互換性のあるタグ付きテンプレートリテラルインターフェースを提供します:
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 フォーマットを受け入れます:
// 標準 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 に組み込まれており、同じタグ付きテンプレートリテラルインターフェースを提供します:
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 フォーマットを受け入れます:
// 標準 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 データベースは追加の構成オプションをサポートします:
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=ro→readonly: true?mode=rw→readonly: false, create: false?mode=rwc→readonly: false, create: true(デフォルト)
データの挿入
JavaScript 値を SQL テンプレートリテラルに直接渡すことができ、エスケープは自動的に処理されます。
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 ... ステートメントに展開されます。
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) を使用して挿入する列を選択できます。各列はオブジェクト上で定義されている必要があります。
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() メソッドは、行をオブジェクトではなく値の配列として返します。各行はクエリの列と同じ順序の値の配列になります。
const rows = await sql`SELECT * FROM users`.values();
console.log(rows);これは以下のようなものを返します:
[
["Alice", "alice@example.com"],
["Bob", "bob@example.com"],
];sql``.values() は、クエリ結果に重複した列名が返される場合に特に役立ちます。オブジェクト(デフォルト)を使用する場合、最後の列名がオブジェクトのキーとして使用されるため、重複した列名は互いに上書きされますが、sql``.values() を使用すると、各列が配列に存在するため、インデックスで重複した列の値にアクセスできます。
sql``.raw() フォーマット
.raw() メソッドは、行を Buffer オブジェクトの配列として返します。これはバイナリデータの操作やパフォーマンス上の理由で役立ちます。
const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]SQL フラグメント
データベースアプリケーションで一般的な必要性は、ランタイム条件に基づいてクエリを動的に構築することです。Bun は SQL インジェクションのリスクなしにこれを行うための安全な方法を提供します。
動的なテーブル名
テーブルやスキーマを動的に参照する必要がある場合は、sql() ヘルパーを使用して適切なエスケープを確保します:
// テーブルを安全に参照
await sql`SELECT * FROM ${sql("users")}`;
// スキーマ修飾付き
await sql`SELECT * FROM ${sql("public.users")}`;条件付きクエリ
sql() ヘルパーを使用して条件付き句を含むクエリを構築できます。これにより、アプリケーションのニーズに適応する柔軟なクエリを作成できます:
// オプションの 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) を使用して更新する列を選択できます。各列はオブジェクト上で定義されている必要があります。列が指定されていない場合、すべてのキーが行の更新に使用されます。
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 クエリも簡単になります。オプションで、オブジェクトの配列を渡し、リストの作成に使用するキーを指定できます。
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 配列リテラルを作成します:
// 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() を使用します:
// 1 つのクエリで複数のステートメント
await sql`
SELECT 1;
SELECT 2;
`.simple();シンプルクエリは、データベースマイグレーションやセットアップスクリプトでよく役立ちます。
シンプルクエリはパラメーター(${value})を使用できないことに注意してください。パラメーターが必要な場合は、クエリを個別のステートメントに分割する必要があります。
ファイル内のクエリ
sql.file メソッドを使用してファイルからクエリを読み取り、実行できます。ファイルに $1、$2 などが含まれている場合は、クエリにパラメーターを渡せます。パラメーターが使用されていない場合、ファイルごとに複数のコマンドを実行できます。
const result = await sql.file("query.sql", [1, 2, 3]);安全でないクエリ
sql.unsafe 関数を使用して生の SQL 文字列を実行できます。ユーザー入力をエスケープしないため、注意して使用してください。パラメーターが使用されていない場合、クエリごとに複数のコマンドを実行できます。
// パラメーターなしで複数のコマンド
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() メソッドを呼び出すことで、現在実行中のクエリをキャンセルできます。
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;データベース環境変数
sql 接続パラメーターは環境変数を使用して構成できます。クライアントはこれらの変数を特定の優先順位でチェックし、接続文字列フォーマットに基づいてデータベースタイプを自動的に検出します。
自動データベース検出
Bun.sql() を引数なしで使用するか、new SQL() を接続文字列で使用する場合、アダプターは URL フォーマットに基づいて自動的に検出されます:
MySQL 自動検出
接続文字列がこれらのパターンに一致する場合、MySQL は自動的に選択されます:
mysql://...- MySQL プロトコル URLmysql2://...- MySQL2 プロトコル URL(互換性エイリアス)
// これらはすべて自動的に 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.jsSQLite 自動検出
接続文字列がこれらのパターンに一致する場合、SQLite は自動的に選択されます:
:memory:- インメモリデータベースsqlite://...- SQLite プロトコル URLsqlite:...- スラッシュなしの SQLite プロトコルfile://...- ファイルプロトコル URLfile:...- スラッシュなしのファイルプロトコル
// これらはすべて自動的に 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.jsPostgreSQL 自動検出
PostgreSQL は、MySQL または SQLite パターンに一致しない接続文字列のデフォルトです:
# これらのパターンは 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.jsMySQL 環境変数
MySQL 接続は環境変数を通じて構成できます:
# 主要接続 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_HOST | localhost | データベースホスト |
MYSQL_PORT | 3306 | データベースポート |
MYSQL_USER | root | データベースユーザー |
MYSQL_PASSWORD | (空) | データベースパスワード |
MYSQL_DATABASE | mysql | データベース名 |
MYSQL_URL | (空) | MySQL の主要接続 URL |
TLS_MYSQL_DATABASE_URL | (空) | SSL/TLS 有効化接続 URL |
PostgreSQL 環境変数
以下の環境変数は PostgreSQL 接続を定義するために使用できます:
| 環境変数 | 説明 |
|---|---|
POSTGRES_URL | PostgreSQL の主要接続 URL |
DATABASE_URL | 代替接続 URL(自動検出) |
PGURL | 代替接続 URL |
PG_URL | 代替接続 URL |
TLS_POSTGRES_DATABASE_URL | SSL/TLS 有効化接続 URL |
TLS_DATABASE_URL | 代替 SSL/TLS 有効化接続 URL |
接続 URL が提供されていない場合、システムはこれらの個別パラメーターをチェックします:
| 環境変数 | フォールバック変数 | デフォルト値 | 説明 |
|---|---|---|---|
PGHOST | - | localhost | データベースホスト |
PGPORT | - | 5432 | データベースポート |
PGUSERNAME | PGUSER, USER, USERNAME | postgres | データベースユーザー |
PGPASSWORD | - | (空) | データベースパスワード |
PGDATABASE | - | ユーザー名 | データベース名 |
SQLite 環境変数
SQLite 接続は、SQLite 互換 URL を含む場合 DATABASE_URL を通じて構成できます:
# これらはすべて SQLite として認識
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"注: PostgreSQL 固有の環境変数(POSTGRES_URL、PGHOST など)は、SQLite 使用時には無視されます。
ランタイム事前接続
Bun はアプリケーションコードの実行前にデータベース接続を確立することで、パフォーマンスを向上させるために起動時に PostgreSQL に事前接続できます。これは最初のデータベースクエリでの接続レイテンシを削減するのに役立ちます。
# 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 オプション
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 オプション
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 オプション
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:を使用すると、接続ライフタイムの間のみ存在する一時データベースが作成されます。
動的パスワード
クライアントがアクセストークンなどの代替認証スキームや、ローテーションパスワードを使用するデータベースへの接続を必要とする場合、接続時に動的パスワード値を解決する同期または非同期関数のいずれかを提供できます。
import { SQL } from "bun";
const sql = new SQL(url, {
// 他の接続構成
...
// データベースユーザーのパスワード関数
password: async () => await signer.getAuthToken(),
});SQLite 固有の機能
クエリ実行
SQLite は非同期 I/O を使用する PostgreSQL とは異なり、クエリを同期的に実行します。ただし、API は Promise を使用して一貫性を保ちます:
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 の動作を構成できます:
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 よりも柔軟な型システムを持っています:
// 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 がトリガーされてプロセスがスムーズに続行されます。
基本的なトランザクション
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`;
// エラーがスローされない場合、トランザクションは自動的にコミット
// エラーが発生した場合、ロールバック
});必要に応じて、コールバック関数からクエリの配列を返すことで、トランザクション内のリクエストをパイプラインすることもできます:
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 のセーブポイントは、トランザクション内の中間チェックポイントを作成し、操作全体に影響を与えることなく部分的なロールバックを可能にします。これらは複雑なトランザクションで役立ち、エラー回復と一貫した結果の維持を可能にします。
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 では、分散トランザクションは元のセッションを超えて持続し、特権ユーザーまたはコーディネーターが後でコミットまたはロールバックできます。これは堅牢な分散トランザクション、リカバリプロセス、および管理操作をサポートします。
分散トランザクション中に例外が発生し、キャッチされない場合、システムは自動的にすべての変更をロールバックします。すべてが正常に進む場合、後でトランザクションをコミットまたはロールバックする柔軟性が維持されます。
// 分散トランザクションを開始
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 モードをサポートしています。これらのモードは、接続時の動作と実行される証明書検証のレベルを決定します。
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 | 最も安全なモード。証明書とホスト名の一致を検証します。信頼できない証明書と MITM 攻撃から保護します。 |
接続文字列での使用
SSL モードは接続文字列でも指定できます:
// 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 クライアントはコネクションプールを自動的に管理します。これは、複数のクエリで再利用されるデータベース接続のプールです。これにより、クエリごとに接続を確立して閉じるオーバーヘッドを削減し、データベースへの同時接続の数を管理するのに役立ちます。
const sql = new SQL({
// プール構成
max: 20, // 最大 20 の同時接続
idleTimeout: 30, // 30 秒後にアイドル接続を閉じる
maxLifetime: 3600, // 最大接続ライフタイム 1 時間
connectionTimeout: 10, // 接続タイムアウト 10 秒
});クエリが実行されるまで接続は作成されません。
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 を使用すると、プールから接続を予約し、単一の接続をラップするクライアントを返すことができます。これは、分離された接続でクエリを実行するために使用できます。
// プールから専用接続を取得
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 を設定することで、この動作を変更できます:
const sql = new SQL({
// ... 他のオプション ...
prepare: false, // サーバー上の名前付きプリペアドステートメントの永続化を無効化
});prepare: false が設定されている場合:
クエリは引き続き「拡張」プロトコルを使用して実行されますが、名前なしプリペアドステートメント を使用して実行されます。名前なしプリペアドステートメントは、宛先として名前なしステートメントを指定する次の Parse ステートメントが発行されるまでしか持続しません。
- パラメーターバインディングは引き続き SQL インジェクションから安全です
- 各クエリはサーバーによって最初から解析および計画されます
- クエリはパイプラインされません
prepare: false を使用したい場合:
- トランザクションモードで PGBouncer を使用する場合(ただし PGBouncer 1.21.0 以降は、適切に構成されている場合、プロトコルレベルの名前付きプリペアドステートメントがサポートされています)
- クエリ実行プランをデバッグする場合
- クエリプランを頻繁に再生成する必要がある動的 SQL を使用する場合
- クエリごとに複数のコマンドはサポートされません(
sql``.simple()を使用しない限り)
プリペアドステートメントを無効にすると、異なるパラメーターで頻繁に実行されるクエリのパフォーマンスに影響を与える可能性があることに注意してください。サーバーは各クエリを最初から解析および計画する必要があるためです。
エラーハンドリング
クライアントはさまざまな失敗シナリオに対して型付きエラーを提供します。エラーはデータベース固有であり、基本エラークラスから拡張されます:
エラークラス
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_AVAILABLE | SSL/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_MISMATCH | SASL 署名検証に失敗しました |
クエリエラー
| クエリエラー | 説明 |
|---|---|
ERR_POSTGRES_SYNTAX_ERROR | 無効な SQL 構文(SyntaxError を拡張) |
ERR_POSTGRES_SERVER_ERROR | PostgreSQL サーバーからの一般エラー |
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_CONSTRAINT | 19 | 制約違反(UNIQUE、CHECK、NOT NULL など) |
SQLITE_BUSY | 5 | データベースがロックされています |
SQLITE_LOCKED | 6 | データベース内のテーブルがロックされています |
SQLITE_READONLY | 8 | 読み取り専用データベースへの書き込み試行 |
SQLITE_IOERR | 10 | ディスク I/O エラー |
SQLITE_CORRUPT | 11 | データベースディスクイメージが破損しています |
SQLITE_FULL | 13 | データベースまたはディスクがいっぱいです |
SQLITE_CANTOPEN | 14 | データベースファイルを開けません |
SQLITE_PROTOCOL | 15 | データベースロックプロトコルエラー |
SQLITE_SCHEMA | 17 | データベーススキーマが変更されました |
SQLITE_TOOBIG | 18 | 文字列または BLOB がサイズ制限を超えています |
SQLITE_MISMATCH | 20 | データ型の不一致 |
SQLITE_MISUSE | 21 | ライブラリが誤って使用されました |
SQLITE_AUTH | 23 | 認証が拒否されました |
エラーハンドリングの例:
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 ビット整数の範囲を超える大きな数値の特別な処理が含まれています。動作方法は次のとおりです:
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 に設定することで有効にできます:
const sql = new SQL({
bigint: true,
});
const [{ x }] = await sql`SELECT 9223372036854777 as x`;
console.log(typeof x, x); // "bigint" 9223372036854777nロードマップ
まだ完了していないことがいくつかあります:
--db-preconnectBun 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 はパラメーター化クエリに対してサーバーサイドプリペアドステートメントを使用します:
// これは自動的にサーバー上にプリペアドステートメントを作成
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 はマルチステートメントクエリから複数の結果セットを返すことができます:
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 にクライアント情報を自動的に送信します:
// これらの属性は自動的に送信:
// _client_name: "Bun"
// _client_version: <bun バージョン>
// これらは MySQL の performance_schema.session_connect_attrs で確認可能型処理
MySQL 型は自動的に JavaScript 型に変換されます:
| MySQL 型 | JavaScript 型 | 注釈 |
|---|---|---|
| INT, TINYINT, MEDIUMINT | number | 安全な整数範囲内 |
| BIGINT | string, number または BigInt | 値が i32/u32 サイズに収まる場合は number、それ以外は bigint オプションに基づき string または BigInt |
| DECIMAL, NUMERIC | string | 精度を保持するため |
| FLOAT, DOUBLE | number | |
| DATE | Date | JavaScript Date オブジェクト |
| DATETIME, TIMESTAMP | Date | タイムゾーン処理付き |
| TIME | number | マイクロ秒の合計 |
| YEAR | number | |
| CHAR, VARCHAR, VARSTRING, STRING | string | |
| TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXT | string | |
| TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOB | string | BLOB 型は TEXT 型のエイリアス |
| JSON | object/array | 自動的に解析 |
| BIT(1) | boolean | MySQL の BIT(1) |
| GEOMETRY | string | ジオメトリデータ |
PostgreSQL との違い
API は統一されていますが、いくつかの動作の違いがあります:
- パラメータープレースホルダー: MySQL は内部的に
?を使用しますが、Bun は自動的に$1, $2スタイルに変換します - RETURNING 句: MySQL は RETURNING をサポートしていません。
result.lastInsertRowidを使用するか、別の SELECT を使用してください - 配列型: MySQL は PostgreSQL のようなネイティブ配列型を持っていません
MySQL 固有の機能
まだ LOAD DATA INFILE サポートを実装していません
PostgreSQL 固有の機能
まだ以下を実装していません:
COPYサポートLISTENサポートNOTIFYサポート
また、以下のようなより一般的でない機能も実装していません:
- GSSAPI 認証
SCRAM-SHA-256-PLUSサポート- Point および PostGIS 型
- すべての多次元整数配列型(いくつかの型のみサポート)
一般的なパターンとベストプラクティス
MySQL 結果セットの操作
// 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 エラーハンドリング
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 のパフォーマンスヒント
- コネクションプーリングを使用: ワークロードに基づいて適切な
maxプールサイズを設定 - プリペアドステートメントを有効化: デフォルトで有効になっており、パフォーマンスを向上
- 一括操作にトランザクションを使用: 関連するクエリをトランザクションでグループ化
- 適切にインデックス: MySQL はクエリパフォーマンスのためにインデックスに大きく依存
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 つの理由があります:
- 開発者にとって、Bun に組み込まれたデータベースドライバーがある方がシンプルだと考えています。ライブラリショッピングに費やす時間は、アプリの構築に費やすことができる時間です。
- ライブラリで実装するのが難しいオブジェクトを作成するために、JavaScriptCore エンジンの内部を利用しています
クレジット
API インターフェースのインスピレーションを提供してくれた @porsager の postgres.js に感謝します。