フルNext.jsアプリケーション作成の一環で、DBマイグレーションツールにprismaを使ってみたので、書き方のメモです。
いきなりですが、prismaは細かくDDL文を制御できず、思ったテーブルを作れなかったので、細かくテーブル定義を設定したい場合はTypeORMの方がいいかもしれません。
準備
プロジェクトにprismaをインストールし、初期ファイル(prisma/schema.prisma)を作成します。
$ yarn add -D prisma $ yarn run prisma init
schema.prismaの書き方
公式ドキュメントの書き方を参考に、スキーマをschema.prismaに記載していきます。
記載する際の注意点・考慮点は以下です。
- providerを
mysql
に変更する @db.Timestamp
ではなく@db.Timestamp(0)
のように書かないとマイグレーション出力時にエラーになる@updatedAt
をつけてもON UPDATE CURRENT_TIMESTAMP
がつかない- つけたい場合はマイグレーションファイルを作成してから、手動で追記するしかないっぽい(追記して実行したら反映はされる
- model名をキャメルケースで書いても、DDLでスネークケースのテーブル名に変換されるわけではない
- スネークケースのテーブル名にしたければmodel名をそうしておく
- @relationのnameはmodel間のマッチングキーに利用する
- サポートされていない型(例えばgeometry)を使う場合は、
Unsupported("geometry")
のように書く
// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema datasource db { provider = "mysql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model Area { id Int @id @default(autoincrement()) name String @db.VarChar(32) description String? @db.Text created_at DateTime @default(now()) @db.Timestamp(0) updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0) shop Shop[] @relation("shop_fk_1") } model Shop { id Int @id @default(autoincrement()) name String @db.VarChar(128) area_id Int area Area @relation(name: "shop_fk_1", fields: [area_id], references: [id]) address String? @db.VarChar(128) tel String? @db.VarChar(128) access String? @db.Text localton Unsupported("geometry")? url String @db.Text created_at DateTime @default(now()) @db.Timestamp(0) updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0) shop_article Shop_Article[] @relation("shop_article_fk_1") } model Shop_Article { id Int @id @default(autoincrement()) status ArticleStatus? published_at DateTime @default(now()) @db.Timestamp(0) title String? @db.Text image_url String? @db.Text shop_id Int? shop Shop? @relation(name: "shop_article_fk_1", fields: [shop_id], references: [id]) created_at DateTime @default(now()) @db.Timestamp(0) updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0) shop_article_image Shop_Article_Image[] @relation("shop_article_image_fk_1") @@index([shop_id]) @@index(fields: [published_at], name: "idx_shop_article_published_at") } enum ArticleStatus { draft published } model Shop_Article_Image { shop_article_id Int shop_article Shop_Article @relation(name: "shop_article_image_fk_1", fields: [shop_article_id], references: [id]) order Int image_url String @db.Text created_at DateTime @default(now()) @db.Timestamp(0) updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0) @@id([shop_article_id, order]) }
※例は既存アプリケーションの書き直しなのでtimestamp型ですが、今から作るなら範囲を考えてdatetime型を使ってください。
TIMESTAMP[(fsp)]
タイムスタンプです。範囲は '1970-01-01 00:00:01.000000' UTC から '2038-01-19 03:14:07.999999' UTC です。TIMESTAMP 値は、エポック ('1970-01-01 00:00:00' UTC) からの秒数として格納されます。TIMESTAMP は、'1970-01-01 00:00:00' という値を表すことはできません。これは、エポックからの秒数が 0 であることと同等で、0 という値は '0000-00-00 00:00:00'、つまり「ゼロ」の TIMESTAMP 値を表すために予約されているからです。
MySQL 5.6.4 以降では、小数秒の精度を指定するために 0 から 6 の範囲でオプションの fsp 値を指定できます。0 の値は、小数部がないことを表します。省略した場合、デフォルトの精度は 0 です。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 11.1.2 日付と時間型の概要
migration.sql
上記のschema.prismaから出力されたマイグレーションファイルは以下のようになりました。
-- CreateTable CREATE TABLE `Area` ( `id` INTEGER NOT NULL AUTO_INCREMENT, `name` VARCHAR(32) NOT NULL, `description` TEXT, `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), `updated_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- CreateTable CREATE TABLE `Shop` ( `id` INTEGER NOT NULL AUTO_INCREMENT, `name` VARCHAR(128) NOT NULL, `area_id` INTEGER NOT NULL, `address` VARCHAR(128), `tel` VARCHAR(128), `access` TEXT, `localton` geometry, `url` TEXT NOT NULL, `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), `updated_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- CreateTable CREATE TABLE `Shop_Article` ( `id` INTEGER NOT NULL AUTO_INCREMENT, `status` ENUM('draft', 'published'), `published_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), `title` TEXT, `image_url` TEXT, `shop_id` INTEGER, `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), `updated_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), INDEX `Shop_Article.shop_id_index`(`shop_id`), INDEX `idx_shop_article_published_at`(`published_at`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- CreateTable CREATE TABLE `Shop_Article_Image` ( `shop_article_id` INTEGER NOT NULL, `order` INTEGER NOT NULL, `image_url` TEXT NOT NULL, `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), `updated_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), PRIMARY KEY (`shop_article_id`, `order`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- AddForeignKey ALTER TABLE `Shop` ADD FOREIGN KEY (`area_id`) REFERENCES `Area`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE `Shop_Article` ADD FOREIGN KEY (`shop_id`) REFERENCES `Shop`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE `Shop_Article_Image` ADD FOREIGN KEY (`shop_article_id`) REFERENCES `Shop_Article`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
外部キー制約でON DELETE CASCADE
を付けたくない場合は、手動で消さないとダメそうです。
また、DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
あたりは接続しているMySQLの設定から自動取得しているようでした。
書くときのコツ
最初に全テーブルのカラム定義を書いてしまい、その後にインデックスや外部キー制約を書いていくのが良さそうでした。
特に外部キー制約関連は、途中まで書いてフォーマットをかけると、prismaのフォーマッタが自動で埋めてくれることがあるのですが、
relation nameを使わないキャメルケースの定義を追加するので、
同じ意図の定義が、自分が書いたものとフォーマッタで複数追加されたりしたので、注意が必要でした。
まとめ
マイグレーションにおいてはTypeORMの方が細かく指定できていいかもしれないですが、prismaはSQLで出力されるのでDDLを変更できる人であればなんとでもできそうでした。