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

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


> typeorm: v0.2.38
> 翻訳元: [migrations.md](https://github.com/typeorm/typeorm/blob/91d5b2fc374c2f7b1545d40ee76577272de21436/docs/migrations.md)
> ライセンス: [The MIT License](https://github.com/typeorm/typeorm/blob/91d5b2fc374c2f7b1545d40ee76577272de21436/LICENSE)

## 目次

* [マイグレーションとは](#マイグレーションとは)
* [マイグレーションを作成する](#マイグレーションを作成する)
* [マイグレーションを実行する](#マイグレーションを実行する)
* [マイグレーションを自動生成する](#マイグレーションを自動生成する)
* [コネクションオプション](#コネクションオプション)
* [マイグレーションを記述するときに利用できるAPI](#マイグレーションを記述するときに利用できるAPI)


## マイグレーションとは

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

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

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

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

@Entity()
export class Post {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    text: string;

}
```

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

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

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

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

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


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

> 事前に[CLIのインストール](https://github.com/typeorm/typeorm/blob/master/docs/using-cli.md#installing-cli)が必要です。

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

```json
{
    "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でマイグレーションを作成することができます。

```sh
typeorm migration:create -n PostRefactoring
```

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

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

```typescript
import {MigrationInterface, QueryRunner} from "typeorm";

export class PostRefactoringTIMESTAMP implements MigrationInterface {

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

    }

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

    }


}
```

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

`up`も`down`も、`QueryRunner`オブジェクトを持っています。データベースに対する操作は、全てこのオブジェクトを介して行います。[Query Runner](https://github.com/typeorm/typeorm/blob/master/docs/query-runner.md)についてはこちらを参照してください。

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

```typescript
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コマンドで実行することができます。

```sh
typeorm migration:run
```

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

* `ts-node`のサンプル

```sh
ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run
```

* `ts-node`のサンプル（`node_modules`を参照しない場合）

```
ts-node $(yarn bin typeorm) migration:run
```

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

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

```sh
typeorm migration:revert
```

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


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

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

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

```sh
typeorm migration:generate -n PostRefactoring
```

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

```typescript
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`ファイルが生成されます。

```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`のエイリアス）を使用してください。

## コネクションオプション

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

> ここの説明はちょっと分かりにくいので、こっちを参照→[Using ormconfig.json](https://github.com/typeorm/typeorm/blob/master/docs/using-ormconfig.md#using-ormconfigjson)


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

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

サンプル

```typescript
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の詳細が続くので、[原文](https://github.com/typeorm/typeorm/blob/master/docs/migrations.md#using-migration-api-to-write-migrations)を参照してください。