はじめに
Prisma Migrate のプロジェクトへの導入、データベース定義の更新、マイグレーションの実行やロールバックについて、実際にコマンドを使って試してみました。
新規プロジェクトで使い始める
新規プロジェクトで Prisma Migrate を使って、データベースを管理します。今回は公式のチュートリアルを参考に、MySQL
を使って実際に試してみました。
※ MySQL を利用する場合の接続情報についてはこちらのドキュメントを参照してください。
Prisma schema を作成
Prisma schema の定義は、schema.prisma
というファイルに作成していきます。こちらのファイルがない、またはセットアップしていない場合は、プロジェクトのセットアップをご確認ください。
今回はチュートリアルと同様に以下の通り、ジェネレーター、データソース、3つのデータモデルを定義しました。
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String @db.VarChar(255)
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
model Profile {
id Int @id @default(autoincrement())
bio String?
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
profile Profile?
}
マイグレーションを実行
Prisma schema を作成したので、migrate dev コマンドでマイグレーションを実行します。
% npx prisma migrate dev --name init
開発時は、上記のコマンドを実行して、マイグレーションファイルの作成とマイグレーションの適用を実行します。公式サイトでも解説されていますが、このコマンドは本番環境などの実働環境では利用しないでください。
実行後に生成されたマイグレーションファイルは、prisma/migrations/[日付]_[name]/migration.sql
が作成されていました。
migration.sql
の中身は、DDL となっており、今回の場合は、テーブルを作成する SQL や、外部キーを追加する SQL となっていました。
また、データベースを確認してみたところ、テーブルや外部キー制約などが作成されていました。
データベースの設計や実装を新規に作成していくプロジェクトなどでは、Prisma schema を作成するところから始めるのが、シンプルで導入も簡単にできそうですね。
既存プロジェクトに組み込む
こちらのパターンではすでにデータベースは実装済みの場合に Prisma Migrate で管理したい場合です。新規プロジェクトの場合と同様に、公式のチュートリアルを参考に、MySQL
を使って実際に試してみました。
既存のデータベースの状態
サンプルとして MySQL に以下のようなテーブルとデータが作成済みの状態となります。
CREATE TABLE orders
(
order_id INT AUTO_INCREMENT PRIMARY KEY,
order_date DATE NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL
);
CREATE TABLE order_details
(
order_detail_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders (order_id)
);
INSERT INTO orders (order_date, total_amount)
VALUES ('2024-07-01', 150.00),
('2024-07-02', 200.00);
INSERT INTO order_details (order_id, product_id, quantity, unit_price)
VALUES (1, 101, 2, 50.00),
(1, 102, 1, 50.00),
(2, 103, 4, 50.00);
Prisma 関連ファイルの状態を確認
prisma
ディレクトリ配下には、schema.prisma
のみ配置されていて、ファイルの中身は以下のように、ジェネレーターとデータソースの定義のみとなります。
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
実装済みのデータベースから Prisma schema を生成
db pull コマンドを利用して、実装済みデータベースから Prisma schema を生成します。
% npx prisma db pull
コマンドを実行すると schema.prisma ファイルがデータベースの実装情報に基づいたスキーマ情報で上書きされます。そのため、手動で編集した内容がある場合、編集内容が失われる可能性があるので注意が必要です。生成されるデータモデルの定義を確認したい場合は、schema.prisma ファイルを変更せずに画面上に表示することもできます(dry run のようなイメージ)。
% npx prisma db pull --print
コマンドを実行すると以下のようなデータモデルが生成されていました。
model order_details {
order_detail_id Int @id @default(autoincrement())
order_id Int
product_id Int
quantity Int
unit_price Decimal @db.Decimal(10, 2)
orders orders @relation(fields: [order_id], references: [order_id], onDelete: NoAction, onUpdate: NoAction, map: "order_details_ibfk_1")
@@index([order_id], map: "order_id")
}
model orders {
order_id Int @id @default(autoincrement())
order_date DateTime @db.Date
total_amount Decimal @db.Decimal(10, 2)
order_details order_details[]
}
すでに実装されているデータベース定義を変更するのは難しいけど、プログラムで扱う命名規約に合わせて、マッピングしたい場合は、@map
や @@map
を利用することができます。詳しくは公式ドキュメントを確認してください。
※order_id
をプログラム上は orderId
としたい場合など。
baseline migration を作成
これまでの手順では、実装済みのデータベースから schema.prisma ファイルにデータモデルを生成しただけです。ですので、Prisma Migrate に既存のテーブルはマイグレーションで作成されたリソースだということを認識させる必要があります。(これによりマイグレーション実行時に、すでに存在するテーブルやフィールドを作成するのを防ぐことができる)
以下のコマンドを実行して、ディレクトリを作成します。ここで、0_
とすることで、マイグレーションの適用順を最初にします。
% mkdir -p prisma/migrations/0_init
続いて、migrate diffコマンドでマイグレーション用のファイル(DDL)を生成します。
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script > prisma/migrations/0_init/migration.sql
マイグレーションを適用済みにする
migrate resolve コマンドを実行して、実装済みのデータベース定義をすでにマイグレーション実行済みとなるようにします。
% npx prisma migrate resolve --applied 0_init
コマンドを実行するとマイグレーション適用済みとなります。_prisma_migrations
テーブルを確認することで適用済みとなっていることを確認できます。
これで新規プロジェクトで使い始める
場合と同じ状態とすることができましたので、今後のマイグレーション情報の変更と適用は migrate dev コマンドを利用して行うことができます。
データベース定義を更新する
Prisma Migrate で管理しているデータベースを更新してみます。
テーブルにカラムを追加する
新規プロジェクトで使い始める
で作成した User
テーブルに jobTitle
カラムを追加してみます。
schema.prisma
ファイルのデータモデルを以下の通り更新します。
model User {
id Int @id @default(autoincrement())
jobTitle String // 追加
email String @unique
name String?
posts Post[]
profile Profile?
}
続いて migrate dev コマンドでマイグレーションを実行します。
% prisma migrate dev --name added_job_title
実行すると以下のようなエラーとなってしまいました。
Step 0 Added the required column `jobTitle` to the `User` table without a default value. There are 1 rows in this table, it is not possible to execute this step.
このエラーはデータが登録済みのテーブルに対して、NULL を許容していないカラムを追加しようとすることで起こるエラーのようでした。このような場合は、生成されたマイグレーションファイルを編集して再度マイグレーションを実行することで解決することができます。
まずは、以下のコマンドを実行してマイグレーションファイルを作成します。
% prisma migrate dev --name added_job_title --create-only
作成されたマイグレーションファイルを編集します。今回は、デフォルトバリューに空文字を設定してから、NOT NULL 制約を適用します。
ALTER TABLE `User` ADD COLUMN `jobTitle` VARCHAR(191) DEFAULT '';
ALTER TABLE `User` MODIFY COLUMN `jobTitle` VARCHAR(191) NOT NULL;
編集後、マイグレーションを実行します。
% prisma migrate dev --name added_job_title
これで NOT NULL 制約がついたカラムを作成することができました。データが登録済みのテーブルに変更を加える場合は、既存のデータを考慮する必要がありますね。
カラム名を変更する
既存のカラム名を変更してみます。カラム名の変更時は、
- リネーム後のカラムを作成する
- 既存のカラムとそのカラムのデータを削除する
という動きとなるようです。リネーム後のカラムにそのままデータを移行したい場合は、マイグレーションファイルを編集して対応することができそうですので、実際にやってみます。
まずは、schema.prisma
ファイルのデータモデルを更新します。
model User {
id Int @id @default(autoincrement())
jobTitle String
email String @unique
fullName String? // name → fullName にリネーム
posts Post[]
profile Profile?
}
create-only
オプションを使って、マイグレーションファイルを生成します。
% npx prisma migrate dev --name rename-migration --create-only
以下のような確認メッセージが表示されるので y
で yes
とします。
You are about to drop the column `name` on the `User` table, which still contains 1 non-null values.
✔ Are you sure you want to create this migration? … yes
作成されたマイグレーションファイルを編集します。今回は、DROP, ADD される処理を、RENAME されるように修正します。
ALTER TABLE `User`
RENAME COLUMN `name` TO `fullName`;
修正後、再度マイグレーションを実行します。
% npx prisma migrate dev --name rename-migration
name
に設定していたデータを保持したまま、fullName
にリネームすることができました。
マイグレーションを動作環境に適用する
これまでは主に開発時に利用するコマンドを利用していましたが、本番環境やテスト環境などにマイグレーションを実行する場合は migrate deploy コマンドを利用します。 migrate deploy コマンドを実行することで保留中(未適用)のマイグレーションを実行することができます。
% npx prisma migrate deploy
適用したマイグレーションをロールバックする
データベースのマイグレーションやアプリケーションのリリースを実行して動作確認してみると、アプリケーションが正しく動かないので、アプリケーションのロールバックが必要になることもありますよね。(このような状態は誰も望んでいないとは思いますが。。)
その際に実行したマイグレーションを元に戻す(ロールバック)方法について確認していきます。
ロールバック時の流れとしては、
- 開発環境でマイグレーションをもとに戻すクエリを生成
- 動作環境でマイグレーションを実行
- 動作環境で実行したマイグレーションをもとに戻すマイグレーションを実行
という流れになりますので、その実行方法を確認してみます。
マイグレーションの状態は、新規プロジェクトで使い始める
で最初に作成した状態から始めます。
マイグレーションで変更内容は、UserDetail
というテーブルを追加し、User
テーブルとリレーションを設定しました。
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
profile Profile?
UserDetail UserDetail? // 追加
}
// 追加
model UserDetail {
id Int @id @default(autoincrement())
detail String
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
この状態でマイグレーションをもとに戻すクエリを生成します(down.sql
)
% npx prisma migrate diff \
--from-schema-datamodel prisma/schema.prisma \
--to-schema-datasource prisma/schema.prisma \
--script > down.sql
down.sql
には、以下のような SQL が生成されており、今回作成するオブジェクトを削除するクエリとなっているようでした。
-- DropForeignKey
ALTER TABLE `UserDetail` DROP FOREIGN KEY `UserDetail_userId_fkey`;
-- DropTable
DROP TABLE `UserDetail`;
続いて、開発環境でマイグレーションを実行します。
% npx prisma migrate dev --name add_user_detail
これでマイグレーションファイルの生成が完了しましたので、管理しやすいように先ほど生成した down.sql
をマイグレーションファイルと同じディレクトリにコピーしておきます。これで準備完了です。
prisma
├── migrations
│ ├── 20240722093810_init
│ │ └── migration.sql
│ ├── 20240725232814_add_user_detail
│ │ ├── down.sql // ここにコピー
│ │ └── migration.sql
│ └── migration_lock.toml
└── schema.prisma
ある実行環境で migrate deploy コマンド などでマイグレーションを実行してマイグレーションを適用したとします。このマイグレーションをロールバックする(戻したい)場合の手順を実行していきます。
db execute コマンドを実行して、データベースに適用された変更を取り消します。
% npx prisma db execute --file ./prisma/migrations/[マイグレーションで生成されたディレクトリ名]/down.sql --schema prisma/schema.prisma
ここまでで、実行環境で動作するデータベースの定義は、マイグレーションの適用前に戻すことができているので、焦らなくても大丈夫です!
現在の状態は、稼働しているデータベースの状態とマイグレーションの状態に差異がある(schema drift
している)状態となりますので、それを解消します。
まずは、開発環境を実行環境と同様の状態とするために、db execute
(上記と同様)コマンドと prisma.schema
ファイルをデータベースと同期するために db pull
コマンドを実行します。
% npx prisma db execute --file ./prisma/migrations/[マイグレーションで生成されたディレクトリ名]/down.sql --schema prisma/schema.prisma
% npx prisma db pull
この状態でマイグレーションをロールバックするマイグレーションを作成します。
% npx prisma migrate dev --name add_user_detail_roll_back
生成されたマイグレーションの SQL を確認すると、最初に作成した down.sql
と同様の SQL が生成されていることを確認できました。
このマイグレーションは、実行環境のデータベースにはすでに適用済みのマイグレーションなので、すでに適用済みということを実行環境に適用しておきます。
% npx prisma migrate resolve --applied "20240727022602_add_user_detail_roll_back"
これでデータベースの状態とマイグレーションの状態の同期をとることができました。
Prisma Migrate を利用したロールバックについて
実際にマイグレーションした内容をロールバックしてみましたが、変更した内容や既存のデータの状態など、状況に応じて考慮するべき点が多く、素早く対応を求められる場面では、手順や対応内容が複雑に感じました。
Prisma Migrate でもロールバックすることはできそうですが、他の手法(例えば データベースのスナップショットから復元させるなど)でロールバックすることもできるので、どの方法が合っているかを見極めて、選択するのが良いのかと思います。
また、最初ドキュメントを読んでいたときに、migrate resolve コマンドの --rolled-back
オプションをつけて実行すれば、ロールバックされるのかなぁと思っていたのですが、これは今回の用途とは違い、マイグレーションが失敗した場合に利用するオプションでしたので、ご注意ください。(マイグレーションが成功したけど戻したい場合に利用するコマンドではない)