※本記事は個人の技術検証に基づくものであり、所属組織の公式見解ではありません。
【警告:ご利用は計画的に】
この記事で紹介する方法は、少々トリッキーな「禁じ手」に近い実装です。
本番環境にデプロイする前に、必ずサンドボックス(テスト環境)で動作確認を行い、副作用でデータが「無(Void)」に還らないことを確認してください。
📚 この記事で分かること
- Drizzle ORMでPostgreSQLのEnum値を追加する際の落とし穴
- PostgreSQLのトランザクション内でEnum値を使用できない理由
-
COMMIT; BEGIN;を使ったトランザクション分離の回避策 - Drizzle ORMのロールバック機能の現状(2026年1月時点)
対象読者: Drizzle ORM利用者、PostgreSQL Enum型を扱うエンジニア
はじめに
こんにちは!Dirbato - Backbeat の田島です。
普段はTypeORMという使い慣れた武器で戦っていますが、最近のプロジェクト引き継ぎで Drizzle ORM という新たな聖遺物を手にすることになりました。
Drizzle、いいですね。drizzle-kit studio とか、まるでデバッグ用のチートツールを使っているような快適さがあります。
しかし、光が強ければ影も濃いもの。今回は、PostgreSQLの Enum型 を更新しようとした際に遭遇した「不可視の壁」と、それをパワープレイでぶち抜いた記録を共有します。
さあ、冒険の始まりだ!
クエスト:Enum値を更新して、テーブルのカラムも一気に書き換えろ!
今回のミッションは至ってシンプル。
- スキーマファイル(
schema.ts)を更新してEnumに新しいステータスを追加。 - その新しいステータスを使って、既存のレコードを一括更新(UPDATE)する。
まさに「ジョブチェンジ」のような簡単な作業だと思っていました。
ステップ1:Enumの定義を書き換える
まずは schema.ts を修正します。suspended(停職)なんて生ぬるい、これからは banned(追放)の時代だ!と言わんばかりの修正です。
export const userStatusEnum = pgEnum("user_status", [
"active",
"inactive",
"suspended",
+ "banned", // 新しく追加する強力なステータス
]);
そして、おなじみのコマンドでマイグレーションファイルを生成します。
npx drizzle-kit generate --name=add_banned_to_user_status_enum
生成されたSQLはこんな感じ。
ALTER TYPE "user_status" ADD VALUE 'banned';
よし、これで「追放(banned)」という概念がこの世界(DB)に誕生しました。
ステップ2:データを一括変換するカスタムマイグレーション
次に、既存の suspended ユーザーをすべて banned に書き換える処理を書きます。
空のマイグレーションファイルを生成しましょう。
npx drizzle-kit generate --custom --name=update_user_suspended_to_banned
中身に魂を込めます。
UPDATE "users" SET "status" = 'banned' WHERE "status" = 'suspended';
準備は整った。いざ、migrate コマンドを詠唱(実行)!
npx drizzle-kit migrate
絶望のPostgresError:「なん……だと……?」
意気揚々と実行した私を待っていたのは、成功のログではなく、赤い警告文字でした。
PostgresError: unsafe use of new value "banned" of enum type user
「新しい値 "banned" を使うのは安全じゃないよ!」
PostgreSQLからの非情な宣告。
実はPostgreSQLの仕様で、「Enum値の追加」と「その新しい値の使用」は同じトランザクション内で行うことができない のです。
PostgreSQLくん曰く、「さっき追加されたばかりの値は、まだキャッシュとかの関係で本当に信じていいのか確信が持てないんだ。一旦落ち着いて(トランザクションを終えて)からにしてくれないか?」とのこと。
「ファイルが分かれているんだから、別々のトランザクションで実行してくれよ!」と叫びたくなりますが、Drizzleの migrate コマンドはこれらを一気に実行しようとします。
解決策:強制的にトランザクションをぶった斬る
一つずつ手動でマイグレーションを実行するのは、エンジニアとしてのプライドが許しません。
そこで、「トランザクションをコード内で強制終了して再開する」 というパワープレイ(禁呪)を繰り出します。
ズバッと解決方法!
Enumを追加するSQLファイルの末尾に、COMMIT; と BEGIN; をねじ込みます。
ALTER TYPE "user_status" ADD VALUE 'banned';
+ -- ここで一旦セーブ(コミット)して、新しい冒険(トランザクション)を始める
+ COMMIT;
+ BEGIN;
こうすることで、PostgreSQLに対して「Enumの追加はここで完全に確定したぞ!さあ、次のトランザクションだ!」と強制的に認識させることができます。まさに 「俺のターンはまだ終了していないぜ!」 状態。
これで、後続の UPDATE 処理でも banned が安全な値として認識され、無事にマイグレーションが成功します!
余談:戻れない道(ロールバックの不在)
「こんなことしたらロールバックできなくなるんじゃ……」と心配になった賢者の皆様、鋭いです。
Drizzle ORMの現状(2026年1月現在)
そもそも、Drizzleには標準でロールバック(Down Migration)の機能が備わっていません。
GitHub Discussion #1339
つまり、途中でエラーが起きてもどのみち手動復旧が必要な「ハードコアモード」なのです。
この COMMIT; 戦法を使う場合、Enumの追加だけが先に反映されてしまうため、後続のテーブル更新で失敗した時の状態管理にはより一層の注意が必要です。
まとめ
- PostgreSQLのEnum追加と使用は、同じトランザクションでは共存できない(仕様という名のバリア)。
- Drizzleで一気にやりたいなら、SQLファイルに
COMMIT; BEGIN;を書いてトランザクションを分離する力技がある。 - ただし、これは 「命を大事に」 ではなく 「ガンガンいこうぜ」 な設定なので、テスト環境での検証は必須。
「もっとスマートにやりたいよ!」という方は、TypeORMやPrismaといった別の聖遺物(ORM)を検討するのも一つの手かもしれません(笑)。
それでは、良き開発ライフを!
執筆: 田島 @ Dirbato - Backbeat
趣味: 新しいライブラリを触って、エラーメッセージと対話すること。
参考資料
- PostgreSQL 12.0 Release Notes
- Drizzle ORM GitHub Discussion #1339: Migrations Rollback
- Stack Overflow: New enum values must be committed before they can be used
- GitHub Issue #3249: Drizzle kit applies multiple migration files in the same transaction
参照データソース: