Noh | エンジニア向け情報共有コミュニティ
Signup / Login

Cloudflare d1 & Drizzle orm でマイグレーション時に外部キーエラーになる

drizzle
typescript
javascript
sqlite
cloudflared1

投稿日: 2025/10/10

発生した問題

Cloudflare d1 & Drizzle orm でマイグレーション時に FOREIGN KEY constraint failed: SQLITE_CONSTRAINTというエラー が発生しました。

これは外部キー関連のエラーです。

自分はカラムのdefault指定を削除したときに発生したので、それを例として説明します。

このとき、Drizzleによって以下のようなSQLが発行されます。

PRAGMA foreign_keys=OFF;--> statement-breakpoint CREATE TABLE `__new_contents` ( `id` text PRIMARY KEY NOT NULL, `post_id` text NOT NULL, `object_key` text NOT NULL ); --> statement-breakpoint INSERT INTO `__new_contents`("id", "post_id", "object_key") SELECT "id", "post_id", "object_key" FROM `contents`;--> statement-breakpoint DROP TABLE `contents`;--> statement-breakpoint ALTER TABLE `__new_contents` RENAME TO `contents`;--> statement-breakpoint PRAGMA foreign_keys=ON;

このSQLでは

  1. 新しい__new_contentsテーブルを作成
  2. __new_contentsテーブルへcontentsテーブルの内容をコピー
  3. contentsテーブルを削除
  4. __new_contentsテーブルをcontentsテーブルへリネーム

という処理をしています。
contentsテーブルを直接変更するのではなく、別のテーブルに置き換えています。
これはマイグレーションでよく見られるSQLです。

さて、このSQLはCloudflare d1で問題が発生します。
マイグレーションのSQL実行自体もトランザクション内で実行されるので、外部キー制約が無効になっていないことで失敗します。
おそらくテーブル削除あたりで失敗している気がします。

報告されているIssueや関連するドキュメント

この問題はいかのissueで報告されていますが、2025年10月現在特に対応されていません。

https://github.com/drizzle-team/drizzle-orm/issues/4089

ドキュメントを流し読みすると、D1の外部キー制約については常にPRAGMA foreign_keys = onになるようです。
ちょっと自身がないですが、offにはできないということなんですかね?

そこでドキュメントでは

PRAGMA defer_foreign_keys = on; -- your sql PRAGMA defer_foreign_keys = off;

とするように書かれています。
これによって外部キーの適用を遅延させることができるようです。
これによってトランザクション終了時まで外部キー制約違反を無視できます。

なのでSQLを以下のように変更する必要があります。
自分は後述する理由でPRAGMA foreign_keysを一応残してますが、不要かもしれません。

PRAGMA defer_foreign_keys=on; -- this row is added. PRAGMA foreign_keys=OFF;--> statement-breakpoint CREATE TABLE `__new_contents` ( `id` text PRIMARY KEY NOT NULL, `post_id` text NOT NULL, `object_key` text NOT NULL ); --> statement-breakpoint INSERT INTO `__new_contents`("id", "post_id", "object_key") SELECT "id", "post_id", "object_key" FROM `contents`;--> statement-breakpoint DROP TABLE `contents`;--> statement-breakpoint ALTER TABLE `__new_contents` RENAME TO `contents`;--> statement-breakpoint PRAGMA foreign_keys=ON; PRAGMA defer_foreign_keys=off; -- this row is added.

Drizzleが生成したSQLを書き換えたくはありませんが、Drizzleが修正されるまではこのようにするしか無いです。

おまけ 他のORMはどうなってる

僕は以前PrismaとD1も使用していました。
その時のマイグレーションファイルが残っていたので紹介します。

これはbodyカラムを追加するマイグレーションです。

-- RedefineTables PRAGMA defer_foreign_keys=ON; PRAGMA foreign_keys=OFF; CREATE TABLE "new_User" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "email" TEXT NOT NULL, "name" TEXT, "body" TEXT NOT NULL DEFAULT '' ); INSERT INTO "new_User" ("email", "id", "name") SELECT "email", "id", "name" FROM "User"; DROP TABLE "User"; ALTER TABLE "new_User" RENAME TO "User"; CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); PRAGMA foreign_keys=ON; PRAGMA defer_foreign_keys=OFF;

PrismaではPRAGMA foreign_keysに加えて、その外側にPRAGMA defer_foreign_keysの設定が書かれてました。
自分は最終的にPrismaの生成したSQLを参考にしました。

完全に余談ですが、現在僕はPrismaを使ってません。
CloudflareはPrismaに力を入れていたようですが、d1のバッチ処理関連の機能がなくて僕はDrizzleに移行したという経緯があります。

yosi

Noh を作ってるエンジニア。

目次