19
6

More than 1 year has passed since last update.

【公式ドキュメント和訳】TypeORM Migration

Last updated at Posted at 2021-10-10

この記事は、2021/10/10時点のTypeORMの仕様に依存します。
TypeORMのバージョンが違ったり、記事の公開から長い時間が経っている場合、正確ではない可能性があります。

typeorm: v0.2.38
翻訳元: migrations.md
ライセンス: The MIT License

目次

マイグレーションとは

本番環境でもモデルの変更をデータベースに同期する必要があるわけですが、本番環境のデータベースにデータを入れた状態で、synchronize: trueを使うのは危険です。そこで、マイグレーションを使います。

マイグレーションの正体は普通のJSファイルで、SQLを使用してデータベースのスキーマを更新したり、既存のデータベースに変更を加えるというモノです。

では、すでにデータベースと、Postエンティティがあると仮定します。

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Post {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    text: string;

}

このエンティティは、数ヶ月の間、本番環境で特に何も変更を加えられていませんでした。そして、その間に数千件の投稿がデータベースに保存されていました。

この状態で、titleというカラム名をnameに変更する必要が出てきました。どうすれば良いでしょうか。

正解は、以下のSQLを実行するマイグレーションを新規作成すれば良いです。(PostgreSQLの例)

ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name";

このSQLを実行すれば、データベースが更新されます。TypeORMはこのようなSQLを記述する 場所 を提供し、必要なときに実行できるようにします。この"場所"のことをマイグレーションと呼んでいます。

マイグレーションを作成する

事前にCLIのインストールが必要です。

マイグレーションを作成する前に、セットアップが必要です。

{
    "type": "mysql",
    "host": "localhost",
    "port": 3306,
    "username": "test",
    "password": "test",
    "database": "test",
    "entities": ["entity/*.js"],
    "migrationsTableName": "custom_migration_table",
    "migrations": ["migration/*.js"],
    "cli": {
        "migrationsDir": "migration"
    }
}

オプションの詳細は以下の通りです。

  • "migrationsTableName": "migrations"
    • マイグレーションテーブルの名前をオプションのmigrations以外にする場合に指定します。
  • "migrations": ["migration/*.js"]
    • マイグレーションの読み込み先のディレクトリを指定します。
  • "cli": { "migrationsDir": "migration" }
    • CLIを利用して作成するマイグレーションの保存先ディレクトリを指定します。

セットアップが完了したら、CLIでマイグレーションを作成することができます。

typeorm migration:create -n PostRefactoring

PostRefactoringというのがマイグレーションの名前を指していて、ここには好きな名前を指定することができます。このコマンドを実行すると、"migration"ディレクトリに{TIMESTAMP}-PostRefactoring.tsという形式のファイルが作成されます。{TIMESTAMP}の部分は、マイグレーションを作成した日時のタイムスタンプです。では、ファイルを開いて、SQLを書き込んでみましょう。

マイグレーションの中身は、以下のようになっているはずです。

import {MigrationInterface, QueryRunner} from "typeorm";

export class PostRefactoringTIMESTAMP implements MigrationInterface {

    async up(queryRunner: QueryRunner): Promise<void> {

    }

    async down(queryRunner: QueryRunner): Promise<void> {

    }


}

updownという二つのメソッドがあります。upはマイグレーションそのものを書くためのもので、downupで行った変更を取り消すためのものです。

updownも、QueryRunnerオブジェクトを持っています。データベースに対する操作は、全てこのオブジェクトを介して行います。Query Runnerについてはこちらを参照してください。

Postを変更するマイグレーションは以下のようになります。

import {MigrationInterface, QueryRunner} from "typeorm";

export class PostRefactoringTIMESTAMP implements MigrationInterface {

    async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" RENAME COLUMN "title" TO "name"`);
    }

    async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" RENAME COLUMN "name" TO "title"`); // reverts things made in "up" method
    }
}

マイグレーションを実行する

マイグレーションを作成したら、CLIコマンドで実行することができます。

typeorm migration:run

typeorm migration:createtypeorm migration:generateコマンドをoフラグを付けずに実行した場合、.tsファイルが生成されます。(詳しくは、Generating migrationsをご参照ください。)migration:runmigration:revertコマンドでは、.jsファイルしか扱うことができません。従って、TypeScriptファイルは、マイグレーションの実行前にトランスパイルされている必要があります。他の方法としては、typeormts-nodeと組み合わせて使用することで、.ts拡張子のマイグレーションファイルを実行することができます。

  • ts-nodeのサンプル
ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run
  • ts-nodeのサンプル(node_modulesを参照しない場合)
ts-node $(yarn bin typeorm) migration:run

これらのコマンドは、保留中の全てのマイグレーションをタイムスタンプ順で実行します。言い換えると、作成したマイグレーションのupメソッド内に書いたSQLクエリが全て実行されるということです。これで、データベーススキーマを更新することができました。

マイグレーションを取り消すときは、以下のコマンドを実行します。

typeorm migration:revert

このコマンドは、実行した中で最新のマイグレーションのdownを実行します。複数のマイグレーションを取り消す必要がある時は、このコマンドを複数回呼び出してください。

マイグレーションを自動生成する

TypeORMでは、スキーマに対して行った変更から、自動的にマイグレーションを生成する機能があります。

titleカラムを持つPostエンティティがあったとして、titlenameに変更した場合を想定しましょう。以下のコマンドを実行したとします。

typeorm migration:generate -n PostRefactoring

すると、以下の内容を含む{TIMESTAMP}-PostRefactoring.tsの形式のファイルが自動生成されます。

import {MigrationInterface, QueryRunner} from "typeorm";

export class PostRefactoringTIMESTAMP implements MigrationInterface {

    async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
    }

    async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "name" RENAME TO "title"`);
    }


}

oフラグ(--outputJsのエイリアス)を使って、JavaScriptファイルを生成することもできます。TypeScriptのパッケージがインストールされていなくて、JavaScriptのみのプロジェクトの場合は便利です。その場合は、以下の内容を含んだ{TIMESTAMP}-PostRefactoring.jsファイルが生成されます。

const { MigrationInterface, QueryRunner } = require("typeorm");

module.exports = class PostRefactoringTIMESTAMP {

    async up(queryRunner) {
        await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
    }

    async down(queryRunner) {
        await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
    }
}

自分でSQLクエリを書く必要がないというのは便利ですね。ただし、この方法では、モデルに変更を加えるたびにマイグレーションを生成してください。複数行のSQLクエリを含むマイグレーションを生成するには、pフラグ(--prettyのエイリアス)を使用してください。

コネクションオプション

デフォルトではないコネクションに対してrunrevertを実行する必要がある場合は、-cフラグ(--connectionのエイリアス)を使用して、コンフィグ名を引数として渡すことができます。

ここの説明はちょっと分かりにくいので、こっちを参照→Using ormconfig.json

マイグレーションを記述するときに利用できるAPI

APIを使ってデータベーススキーマを変更するときには、QueryRunnerを使用します。

サンプル

import {MigrationInterface, QueryRunner, Table, TableIndex, TableColumn, TableForeignKey } from "typeorm";

export class QuestionRefactoringTIMESTAMP implements MigrationInterface {

    async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.createTable(new Table({
            name: "question",
            columns: [
                {
                    name: "id",
                    type: "int",
                    isPrimary: true
                },
                {
                    name: "name",
                    type: "varchar",
                }
            ]
        }), true)

        await queryRunner.createIndex("question", new TableIndex({
            name: "IDX_QUESTION_NAME",
            columnNames: ["name"]
        }));

        await queryRunner.createTable(new Table({
            name: "answer",
            columns: [
                {
                    name: "id",
                    type: "int",
                    isPrimary: true
                },
                {
                    name: "name",
                    type: "varchar",
                },
                {
                  name: 'created_at',
                  type: 'timestamp',
                  default: 'now()'
                }
            ]
        }), true);

        await queryRunner.addColumn("answer", new TableColumn({
            name: "questionId",
            type: "int"
        }));

        await queryRunner.createForeignKey("answer", new TableForeignKey({
            columnNames: ["questionId"],
            referencedColumnNames: ["id"],
            referencedTableName: "question",
            onDelete: "CASCADE"
        }));
    }

    async down(queryRunner: QueryRunner): Promise<void> {
        const table = await queryRunner.getTable("answer");
        const foreignKey = table.foreignKeys.find(fk => fk.columnNames.indexOf("questionId") !== -1);
        await queryRunner.dropForeignKey("answer", foreignKey);
        await queryRunner.dropColumn("answer", "questionId");
        await queryRunner.dropTable("answer");
        await queryRunner.dropIndex("question", "IDX_QUESTION_NAME");
        await queryRunner.dropTable("question");
    }

}

マイグレーションを記述するときに利用できるAPI

getDatabases(): Promise<string[]>

全てのデータベース名を取得。


getSchemas(database?: string): Promise<string[]>
  • database - データベースが指定されると、そのデータベースのスキーマを返す

全てのスキーマ名を取得。SQLServerとPostgreSQLでのみ使用可能。


getTable(tableName: string): Promise<Table|undefined>
  • tableName - 読み込むテーブル名

指定した名前のテーブルを読み込む。


getTables(tableNames: string[]): Promise<Table[]>
  • tableNames - 読み込むテーブル名(複数)

指定した名前のテーブルを読み込む(複数)。


hasDatabase(database: string): Promise<boolean>
  • database - チェックするデータベース名

指定した名前のデータベースが存在するかをチェックする。


hasSchema(schema: string): Promise<boolean>
  • schema - チェックするスキーマ名

指定した名前のスキーマが存在するかチェックする。SQLServerとPostgreSQLでのみ使用可能。


hasTable(table: Table|string): Promise<boolean>
  • table - テーブルのオブジェクト、もしくは名前

テーブルが存在しているかチェックする。


hasColumn(table: Table|string, columnName: string): Promise<boolean>
  • table - テーブルのオブジェクト、もしくは名前
  • columnName - チェックするカラムの名前

テーブルに指定したカラムが存在するかチェックする。


createDatabase(database: string, ifNotExist?: boolean): Promise<void>
  • database - データベース名
  • ifNotExist - すでにデータベースが存在したとき、trueが指定されていればスキップするが、そうでなければ例外が投げられる

新しいデータベースを作成する。


dropDatabase(database: string, ifExist?: boolean): Promise<void>
  • database - データベース名
  • ifExist - データベースが存在しなかったとき、trueが指定されていればスキップするが、そうでなければ例外が投げられる

データベースを削除する。


createSchema(schemaPath: string, ifNotExist?: boolean): Promise<void>
  • schemaPath - スキーマ名。SQLServerではスキーマパスをパラメータとして渡せる。スキーマパスを渡した場合、指定されたデータベースにスキーマが作成される。
  • ifNotExist - すでにスキーマが存在したとき、trueが指定されていればスキップするが、そうでなければ例外が投げられる

新しいテーブルスキーマを作成する。


dropSchema(schemaPath: string, ifExist?: boolean, isCascade?: boolean): Promise<void>
  • schemaPath - スキーマ名。SQLServerではスキーマパスをパラメータとして渡せる。スキーマパスを渡した場合、指定されたデータベースにスキーマが削除される。
  • ifExist - データベースが存在しなかったとき、trueが指定されていればスキップするが、そうでなければ例外が投げられる
  • isCascade - trueが指定された場合、スキーマに含まれるオブジェクト(テーブルや関数)も削除される。PostgreSQLでのみ指定可能。

テーブルスキーマを削除する。


createTable(table: Table, ifNotExist?: boolean, createForeignKeys?: boolean, createIndices?: boolean): Promise<void>
  • table - テーブルオブジェクト。
  • ifNotExist - すでにスキーマが存在したとき、trueが指定されていればスキップするが、そうでなければ例外が投げられる
  • createForeignKeys - テーブル作成時に外部キーを作成するかどうか(デフォルトはtrue
  • createIndices - テーブル作成時にインデックスを作成するかどうか(デフォルトはtrue

テーブルを作成する。


以降はAPIの詳細が続くので、原文を参照してください。

19
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
6