はるさめ.dev

Prisma でモデルをリネーム後 foreign key constraint failed on the field が発生。

に公開

背景

DB のスキーマ管理に Prisma を使用しています。

今回は以下のような Implicit many-to-many relations を使ったスキーマを定義していました。
(記事用の適当なスキーマです。)

schema.prisma
model Post {
  postId String @id @db.Uuid
  authors Operator[]
}

model Operator {
  operatorId String @id @db.Uuid
  posts Post[]
}

この Post というモデルの名前を Journal に変更しました。

schema.prisma
model Journal {
  journalId String @id @db.Uuid
  authors Operator[]
}

model Operator {
  articleId String id @db.Uuid
  articles Journal[]
}

prisma migrate dev を実行してマイグレーションファイルを生成したところ、以下のような新しく Journal テーブルを作成して Post テーブルを drop するようなマイグレーションファイルだったので、テーブルをリネームするようなマイグレーションファイルに手動で変更しました。

migration.sql
— 自動生成された マイグレーションファイル
/*
  Warnings:

  - You are about to drop the `Post` table. If the table is not empty, all the data it contains will be lost.
  - You are about to drop the `_OperatorToPost` table. If the table is not empty, all the data it contains will be lost.

*/
-- DropForeignKey
ALTER TABLE "_OperatorToPost" DROP CONSTRAINT "_OperatorToPost_A_fkey";

-- DropForeignKey
ALTER TABLE "_OperatorToPost" DROP CONSTRAINT "_OperatorToPost_B_fkey";

-- DropTable
DROP TABLE "Post";

-- DropTable
DROP TABLE "_OperatorToPost";

-- CreateTable
CREATE TABLE "Journal" (
    "journalId" UUID NOT NULL,

    CONSTRAINT "Journal_pkey" PRIMARY KEY ("journalId")
);

-- CreateTable
CREATE TABLE "_JournalToOperator" (
    "A" UUID NOT NULL,
    "B" UUID NOT NULL
);

-- CreateIndex
CREATE UNIQUE INDEX "_JournalToOperator_AB_unique" ON "_JournalToOperator"("A", "B");

-- CreateIndex
CREATE INDEX "_JournalToOperator_B_index" ON "_JournalToOperator"("B");

-- AddForeignKey
ALTER TABLE "_JournalToOperator" ADD CONSTRAINT "_JournalToOperator_A_fkey" FOREIGN KEY ("A") REFERENCES "Journal"("journalId") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "_JournalToOperator" ADD CONSTRAINT "_JournalToOperator_B_fkey" FOREIGN KEY ("B") REFERENCES "Operator"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
migration.sql
-- 書き直したマイグレーションファイル
-- モデル
ALTER TABLE "Post" RENAME TO "Article";
ALTER TABLE "Article" RENAME COLUMN "postId" to "articleId";

-- 中間テーブル
ALTER TABLE "_OperatorToPost" RENAME TO "_JournalToOperator";

-- ForeignKey
ALTER TABLE "_JournalToOperator" RENAME CONSTRAINT "_OperatorToPost_A_fkey" to "_JournalToOperator_A_fkey";
ALTER TABLE "_JournalToOperator" RENAME CONSTRAINT "_OperatorToPost_B_fkey" to "_JournalToOperator_B_fkey";

-- Index
ALTER INDEX "_OperatorToPost_AB_unique" RENAME TO "_JournalToOperator_AB_unique";
ALTER INDEX "_OperatorToPost_B_index" RENAME TO "_JournalToOperator_B_index";

マイグレーション自体はうまくいったものの、データを登録するときに foreign key constraint failed on the field が発生しました。

原因

Prisma で自動生成されるリレーションの中間テーブル(_OperatorToJournal)のカラムは A と B というカラム名で固定されています。
この際カラムの A と B の対応はモデルのアルファベット順と決められます。

Many-to-many relations#column | Prisma Docs

A relation table for an implicit m-n-relation must have exactly two columns:

  • A foreign key column that points to Category called A
  • A foreign key column that points to Post called B

The columns must be called A and B where A points to the model that comes first in the alphabet and B points to the model which comes last in the alphabet.

もともと Post と Operator というモデルだったので、カラム A は Operator、 カラム B は Postに対応していました。
しかし、モデル名が Journal と Operator に変わったので、カラム A は Journal、カラム B は Operator に対応するように変わりました。

上記のマイグレーションファイルではカラム A と カラム B がもとのままのため、データ登録時にカラム A に Journal、カラム B に Operator の ID を登録しようとしていたため、ForeignKey の制約に違反してしまっていました。

解決方法

上記の通りカラム A と B の対応があべこべになってしまっていることが原因なのでカラム A と B を入れ替えれば解決します。

-- モデル
ALTER TABLE "Post" RENAME TO "Article";
ALTER TABLE "Article" RENAME COLUMN "postId" to "articleId";

-- 中間テーブル
ALTER TABLE "_OperatorToPost" RENAME TO "_JournalToOperator";
-- <追加>モデルのアルファベット順に対応するため、カラム名を入れ替える
ALTER TABLE "_JournalToOperator" RENAME COLUMN "A" to "TEMP";
ALTER TABLE "_JournalToOperator" RENAME COLUMN "B" to "A";
ALTER TABLE "_JournalToOperator" RENAME COLUMN "TEMP" to "B";

-- ForeignKey
ALTER TABLE "_JournalToOperator" RENAME CONSTRAINT "_OperatorToPost_A_fkey" to "_JournalToOperator_A_fkey";
ALTER TABLE "_JournalToOperator" RENAME CONSTRAINT "_OperatorToPost_B_fkey" to "_JournalToOperator_B_fkey";

-- Index
ALTER INDEX "_OperatorToPost_AB_unique" RENAME TO "_JournalToOperator_AB_unique";
ALTER INDEX "_OperatorToPost_B_index" RENAME TO "_JournalToOperator_B_index";

補足

Prisma の Implicit many-to-many relations はスキーマをシンプルに保ってくれ、中間テーブルの機械的な作成を担ってくれるため便利ですが、暗黙がゆえ、仕様を理解していないとハマるポイントになるため、十分にマニュアルを読み込む必要がありそうです。

Prisma 的には Implicit many-to-many relations を推奨しているようですが、「Explicit is better than implicit.」という言葉もあるので、暗黙のリレーションを使うのか、明示的にモデルに記載するのかメリット・デメリットを加味してどちらを採用するか検討したほうが良さそうです。

参考

コメント