データマイグレーション
マイグレーションは、通常、データベーススキーマを移行するために使用されますが、場合によっては、 データベースに格納されているデータ を移行する必要があります。 たとえば、シードデータを追加したり、空のカラムをカスタムデフォルト値で埋め戻しします。
この種のマイグレーションはデータマイグレーションと呼ばれます。 このドキュメントでは、Entを使用してデータマイグレーションを計画し、通常のスキーママイグレーションのワークフローに統合する方法について説明します。
マイグレーションの種類
Ent currently supports two types of migrations, versioned migration and declarative migration (also known as automatic migration). データマイグレーションはどちらのマイグレーションでも実行可能です。
バージョン管理型マイグレーション
バージョン管理型マイグレーションを使用する場合、データマイグレーションは同じ migrations ディレクトリに保存し、通常のマイグレーションと同じように実行します。 ただし、簡単にテストできるように、データマイグレーションとスキーママイグレーションを別々のファイルに保存することをオススメします。
このようなマイグレーションに使用される形式は SQL です。なぜなら、Ent スキーマが変更され、生成されたコードがデータマイグレーションファイルと互換性がなくなっても、ファイルを安全に実行できる (そして変更せずに保存できる) からです。
データマイグレーションスクリプトの作成方法には、手動と自動生成の2種類があります。 手動で編集すると、ユーザーはすべての SQL 文を書き、何が実行されるかを正確に制御することができます。 また、Entを使用して、データマイグレーションを生成することもできます。 場合によっては手動で修正・編集する必要があるため、自動生成されたファイルが正しく生成されたかを確認することをお勧めします。
手動での作成
1. Atlasをインストールしていない場合は、 getting-startedガイドをチェックしてください。
2. Atlasを使って、新しいマイグレーションファイルを作成します。
atlas migrate new <migration_name> \
--dir "file://my/project/migrations"
3. マイグレーションファイルを編集し、そこにカスタムデータマイグレーションを追加します。 例:
-- NULLまたはnullのtagsをデフォルト値でバックフィルします
UPDATE `users` SET `tags` = '["foo","bar"]' WHERE `tags` IS NULL OR JSON_CONTAINS(`tags`, 'null', '$');
4. マイグレーションディレクトリのintegrity fileを更新します。
atlas migrate hash \
--dir "file://my/project/migrations"
データマイグレーションファイルのテスト方法がわからない場合は、以下の テスト セクションを参照してください。
スクリプトを生成する
現在、Ent はデータマイグレーションファイルの生成に対応しています。 このオプションを使用することで、ユーザーは、ほとんどの場合、複雑な SQL 文を手動で記述するプロセスを簡略化できます。 それでも、一部のエッジケースでは手動で編集する必要があるため、生成されたファイルが正しく生成されたことの確認が推奨されます。
2. 最初のデータマイグレーション関数を作成します。 以下に、関数の書き方を示すいくつかの例を示します。
- Single Statement
- Multi Statement
- Data Seeding
package migratedata
// BackfillUnknown back-fills all empty users' names with the default value 'Unknown'.
func BackfillUnknown(dir *migrate.LocalDir) error {
w := &schema.DirWriter{Dir: dir}
client := ent.NewClient(ent.Driver(schema.NewWriteDriver(dialect.MySQL, w)))
// Change all empty names to 'unknown'.
err := client.User.
Update().
Where(
user.NameEQ(""),
).
SetName("Unknown").
Exec(context.Background())
if err != nil {
return fmt.Errorf("failed generating statement: %w", err)
}
// Write the content to the migration directory.
return w.FlushChange(
"unknown_names",
"Backfill all empty user names with default value 'unknown'.",
)
}
Then, using this function in ent/migrate/main.go will generate the following migration file:
-- Backfill all empty user names with default value 'unknown'.
UPDATE `users` SET `name` = 'Unknown' WHERE `users`.`name` = '';
package migratedata
// BackfillUserTags is used to generate the migration file '20221126185750_backfill_user_tags.sql'.
func BackfillUserTags(dir *migrate.LocalDir) error {
w := &schema.DirWriter{Dir: dir}
client := ent.NewClient(ent.Driver(schema.NewWriteDriver(dialect.MySQL, w)))
// Add defaults "foo" and "bar" tags for users without any.
err := client.User.
Update().
Where(func(s *sql.Selector) {
s.Where(
sql.Or(
sql.IsNull(user.FieldTags),
sqljson.ValueIsNull(user.FieldTags),
),
)
}).
SetTags([]string{"foo", "bar"}).
Exec(context.Background())
if err != nil