Chapter 03

マイグレーション

Thirosue
Thirosue
2021.05.16に更新

マイグレーション

本チャプターでは、Prismaのマイグレーション機能を確認していきます。
Prismaは、マイグレーションを宣言的に利用でき、かなり使いやすいです。

スキーマ設定実施

prisma/schema.prismaにユーザテーブルの定義を追加する

TypeScriptの型表現式を用いて、テーブル定義を表現する。

prisma/schema.prisma
model User {
  id      Int      @default(autoincrement()) @id
  email   String   @unique
  name    String?
}

テーブル生成

以下コマンドを発行し、テーブルを生成を行う(DB初期マイグレーション)

npx prisma migrate dev --name init

SQLログを確認すると、以下コマンドが発行され、テーブルが作成される

	CREATE TABLE "User" (
	    "id" SERIAL NOT NULL,
	    "email" TEXT NOT NULL,
	    "name" TEXT,

	    PRIMARY KEY ("id")
	);

	-- CreateIndex
	CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");

	    PRIMARY KEY ("id")
	);

	-- CreateIndex
	CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");

prisma/migrations/${timestamp}_init/migration.sqlでも、発行されたSQLは確認できます。

prisma/migrations/20210515111505_init/migration.sql
-- CreateTable
CREATE TABLE "User" (
    "id" SERIAL NOT NULL,
    "email" TEXT NOT NULL,
    "name" TEXT,

    PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");

マイグレーション確認

  1. カラム追加
  2. 桁変更
  3. 属性変更(Not Null)
  4. テーブル追加
  5. 初期データ投入

1. カラム追加

ユーザテーブルに電話番号を追加

prisma/schema.prismaを以下の通り修正

prisma/schema.prisma
model User {
  id      Int      @default(autoincrement()) @id
  email   String   @unique
  name    String?
  tel    String? <--- 追加
}
  • 変更反映
npx prisma migrate dev --name add_tel_to_user

以下SQLが発行され、変更が反映される

StringはTEXT型にマッピングされる

2021-05-16 05:03:13.632 UTC [2225] LOG:  statement: -- AlterTable
	ALTER TABLE "User" ADD COLUMN     "tel" TEXT;
  • マイグレーションファイル一覧

以下のようにマイグレーションファイルが追加生成される。

% find ./prisma/migrations | sort | sed '1d;s/^\.//;s/\/\([^/]*\)$/|--\1/;s/\/[^/|]*/|  /g'
/Users/hirosue/workspace/hello-prisma
|  |  |--20210515111505_init
|  |  |  |--migration.sql
|  |  |--20210516050313_add_tel_to_user <-- 追加 
|  |  |  |--migration.sql <-- 追加
|  |  |--migration_lock.toml

2. 桁変更

TEXT型 で定義されたカラムを修正する。

prisma/schema.prismaを以下の通り修正

prisma/schema.prisma
model User {
  id      Int      @default(autoincrement()) @id
  email   String   @unique
  name    String?
  tel    String? @db.VarChar(11) <--- ☆☆☆ 修正
}
  • 変更反映
npx prisma migrate dev --name set_max_length_tel

以下SQLが発行され、変更が反映される

2021-05-16 05:36:11.585 UTC [2368] LOG:  statement: /*
	  Warnings:

	  - You are about to alter the column `tel` on the `User` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(11)`.

	*/
	-- AlterTable
	ALTER TABLE "User" ALTER COLUMN "tel" SET DATA TYPE VARCHAR(11);
  • マイグレーションファイル一覧

以下のようにマイグレーションファイルが追記される。

% find ./prisma/migrations | sort | sed '1d;s/^\.//;s/\/\([^/]*\)$/|--\1/;s/\/[^/|]*/|  /g'
/Users/hirosue/workspace/hello-prisma
|  |  |--20210515111505_init
|  |  |  |--migration.sql
|  |  |--20210516050313_add_tel_to_user
|  |  |  |--migration.sql
|  |  |--20210516053611_set_max_length_tel <-- 追加 
|  |  |  |--migration.sql <-- 追加 
|  |  |--migration_lock.toml

3. 属性変更(Not Null)

名前を Not Null へ変更

prisma/schema.prismaを以下の通り修正

prisma/schema.prisma
model User {
  id      Int      @default(autoincrement()) @id
  email   String   @unique
  name    String  <--- ☆☆☆ 修正
  tel    String? @db.VarChar(11)
}
  • 変更反映
npx prisma migrate dev --name set_not_null_name

以下SQLが発行され、変更が反映される

2021-05-16 05:42:07.761 UTC [2386] LOG:  statement: /*
	  Warnings:

	  - Made the column `name` on table `User` required. This step will fail if there are existing NULL values in that column.

	*/
	-- AlterTable
	ALTER TABLE "User" ALTER COLUMN "name" SET NOT NULL;
  • マイグレーションファイル一覧
% find ./prisma/migrations | sort | sed '1d;s/^\.//;s/\/\([^/]*\)$/|--\1/;s/\/[^/|]*/|  /g'
/Users/hirosue/workspace/hello-prisma
|  |  |--20210515111505_init
|  |  |  |--migration.sql
|  |  |--20210516050313_add_tel_to_user
|  |  |  |--migration.sql
|  |  |--20210516053611_set_max_length_tel
|  |  |  |--migration.sql
|  |  |--20210516054207_set_not_null_name <---- 追加
|  |  |  |--migration.sql <---- 追加
|  |  |--migration_lock.toml

4. テーブル追加

ユーザに紐づく投稿テーブルを追加する。

prisma/schema.prismaを以下の通り修正

prisma/schema.prisma
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String
  tel   String? @db.VarChar(11)
  posts   Post[] <---- ☆☆☆ 修正
}

// 追加
model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}
  • 変更反映
npx prisma migrate dev --name add_post

以下SQLが発行され、変更が反映される

2021-05-16 05:47:49.051 UTC [2403] LOG:  statement: -- CreateTable
	CREATE TABLE "Post" (
	    "id" SERIAL NOT NULL,
	    "title" TEXT NOT NULL,
	    "content" TEXT,
	    "published" BOOLEAN NOT NULL DEFAULT false,
	    "authorId" INTEGER,

	    PRIMARY KEY ("id")
	);

	-- AddForeignKey
	ALTER TABLE "Post" ADD FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

5. 初期データ投入

prisma/seed.tsファイルを作成し、以下の内容を記載する

コードで初期データをセットアップする

prisma/seed.ts
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

const posts:any = []

for(let i = 0; i < 100; i++) {
    posts.push({ title: `title ${i + 1}`})
}

async function main() {
    const alice = await prisma.user.create({
        data: {
            name: 'Alice',
            email: 'alice@example.com',
            posts: {
                create: posts
            }
        }
    })

    const bob = await prisma.user.create({
        data: {
            name: 'Bob',
            email: 'bob@example.com',
            posts: {
                create: {
                    title: 'Check out Prisma with Next.js',
                }
            }
        }
    })

    console.log({alice, bob})
}

main()
    .catch(e => {
        console.error(e)
        process.exit(1)
    })
    .finally(async () => {
        await prisma.$disconnect()
    })

初期データ投入

% npx prisma db seed --preview-feature
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Running ts-node "prisma/seed.ts" ...
{
  alice: { id: 1, email: 'alice@example.com', name: 'Alice', tel: null },
  bob: { id: 2, email: 'bob@example.com', name: 'Bob', tel: null }
}

🌱  Your database has been seeded.

ちなみに、以下の部分は

    const bob = await prisma.user.create({
        data: {
            name: 'Bob',
            email: 'bob@example.com',
            posts: {
                create: {
                    title: 'Check out Prisma with Next.js',
                }
            }
        }
    })

こんな感じに自動的にトランザクションを発行し、ACID特性のAtomicity(原子性)を担保してくれる

2021-05-16 10:44:45.102 UTC [2999] LOG:  statement: BEGIN
2021-05-16 10:44:45.103 UTC [2999] LOG:  execute s0: INSERT INTO "postgres"."User" ("email","name") VALUES ($1,$2) RETURNING "postgres"."User"."id"
2021-05-16 10:44:45.103 UTC [2999] DETAIL:  parameters: $1 = 'bob@example.com', $2 = 'Bob'
2021-05-16 10:44:45.105 UTC [2999] LOG:  execute s1: INSERT INTO "postgres"."Post" ("title","published","authorId") VALUES ($1,$2,$3) RETURNING "postgres"."Post"."id"
2021-05-16 10:44:45.105 UTC [2999] DETAIL:  parameters: $1 = 'Check out Prisma with Next.js', $2 = 'f', $3 = '2'
2021-05-16 10:44:45.106 UTC [2999] LOG:  execute s2: SELECT "postgres"."User"."id", "postgres"."User"."email", "postgres"."User"."name", "postgres"."User"."tel" FROM "postgres"."User" WHERE "postgres"."User"."id" = $1 LIMIT $2 OFFSET $3
2021-05-16 10:44:45.106 UTC [2999] DETAIL:  parameters: $1 = '2', $2 = '1', $3 = '0'
2021-05-16 10:44:45.108 UTC [2999] LOG:  statement: COMMIT

反映確認

Prismaは、ブラウザ上で動作するGUIのツール(Prisma Studio)も提供している。

npx prisma studio

記事データが101件登録されていることが確認できる。

現状、Prisma StudioにSQL発行機能はなさそう。