TypeORM でプライマリーキーや外部キーの名前を変更する
Wednesday, May 04, 2022 05:17:00 PM
TypeORM では、主キーやインデックスはエンティティにデコレータを記述します。 たとえば以下のような感じです。
@Entity('テーブル名')
export class UserEntity {
@PrimaryGenerationColumn()
id!: string;
}
@PrimaryGenerationColumn
にはインデックス名を指定できないので、この状況で migration:generate
を実行すると、
TypeORM 独自のルールを使ったインデックス名で SQL が生成されます。
一方でインデックスの場合は、たとえば email
にユニーク制約を入れるとすると以下のように、インデックス名を指定できます。
@Entity('テーブル名')
export class UserEntity {
@PrimaryGenerationColumn()
id!: string;
@Index('UQ_User_email', {unique: true})
@Column()
email!: string;
}
また関連に関してもそうで、 User - hasMany -> Post のような関連があったとき、外部キー制約の名前も TypeORM ルールになります。
@Entity('テーブル名')
export class UserEntity {
@PrimaryGenerationColumn()
id!: string;
@Index('UQ_User_email', {unique: true})
@Column()
email!: string;
@OneToMany(() => PostEntity, (post) => post.user)
posts?: PostEntity[];
}
@Entity('テーブル名')
export class PostEntity {
@PrimaryGenerationColumn()
id!: string;
@ManyToOne(() => UserEntity, (user) => user.posts)
user!: UserEntity;
}
そこで、 @Index
以外で生成されるインデックスの名前をカスタマイズしたい!という場合は NamingStrategy
を使います。
この NamingStrategy
ですが、ドキュメントには詳しい解説がないので、この記事を書くことにしました。
NamingStrategy の作り方
まずはインデックス名をカスタムできるように、 DefaultNamingStrategy
を継承し、 NamingStrategyInterface
を実装するクラスを作ります。
class CustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
}
DefaultNamingStrategyのコード を参考にカスタマイズすると良いでしょう。
たとえばプライマリーキーの場合は以下のようになっています。
primaryKeyName(tableOrName: Table | string, columnNames: string[]): string {
// sort incoming column names to avoid issue when ["id", "name"] and ["name", "id"] arrays
const clonedColumnNames = [...columnNames]
clonedColumnNames.sort()
const tableName = this.getTableName(tableOrName)
const replacedTableName = tableName.replace(".", "_")
const key = `${replacedTableName}_${clonedColumnNames.join("_")}`
return "PK_" + RandomGenerator.sha1(key).substr(0, 27)
}
プライマリーキーに設定されているカラム名をソートしてから _
で結合して sha1 に変換したものを利用しています。
たとえばプロジェクトのルールで、プライマリーキーは PK_テーブル名
で良い場合は、以下のようなコードでオーバーライドして変更します。
class CustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
primaryKeyName(tableOrName: Table | string, columnNames: string[]): string {
const table = this.getTableName(tableOrName);
return `PK_${pascalCase(table)}`;
}
}
pascalCase
は change-case のようなライブラリ利用を想定しています。
NamingStrategy で変更できる名前
NamingStrategyInterfaceのコードを見るとわかります。 よく使いそうなものは、以下のあたりでしょうか。
- primaryKeyName 主キー名。デフォルトでは
PK_{sha1した値}
- foreignKeyName 外部キー名。デフォルトでは
FK_{sha1した値}
- indexName 複合キー名。デフォルトでは
IDX_{sha1した値}
もちろんこれ以外にもたくさんカスタマイズできる名前はあるので、マイグレーションファイルに生成された名称がプロジェクトルールに沿わない場合に 遭遇したら、インターフェースやデフォルトの実装を見てみると良いでしょう。
カスタム NamingStrategy の指定方法
マイグレーションコマンドから利用する DataSource のオプションに指定します。
export const AppDataSource = new DataSource{
// データベース接続オプション
migrations: ['db/migrations/*.ts'],
namingStrategy: new CustomNamingStrategy()
});
namingStrategy
を指定して、 npm run migration:generate
するとプロジェクトルールに沿った名前でマイグレーションファイルを生成できます。
package.json
のスクリプト例
"migration:generate": "ts-node ./node_modules/.bin/typeorm migration:generate -d db/datasouce.ts db/migrations",
さいごに
公式ドキュメントの情報が不足しているところなので、この記事が少しでも役立てば幸いです。