golang-migrateを使っている環境でRailsのdb/structure.sql相当のファイルを作成する方法

仕事で携わっているウェブアプリケーションでは、DBのマイグレーションにgolang-migrateを使っています。

DBをマイグレーションするだけのツールとしてはこれで十分なのですが、現在のスキーマダンプ、Railsでいうところのdb/structure.sqlをダンプする機能がなくて困っていたので、それ相当のファイルを作成する方法を考えました。

スキーマダンプには

  • DBスキーマの信頼できる情報源となり、現在のスキーマを概観できる
  • ダンプにスキーマのバージョンが記録されていて、バージョン管理システムの異なるブランチで同時に作業したとき、両方をマージしようとするとコンフリクトするので不整合に気づきやすい

などの利点があり*1、Railsのようなフレームワークを使っていなくとも、これに相当するファイルがあると便利です。

ファイルを作成する手順としては、DBからスキーマとそのバージョンを取得し、ファイルに書き出すだけです。

仕事ではMySQLを使っているので、bashとMySQLのコマンドラインツールを使った例を示します。ツールは例示したものである必要はなく、スキーマとそのバージョンが取得できればやり方はなんでも構いません。他の言語・DBでも同じ考え方でスキーマダンプを作成できるでしょう。

DBからスキーマのバージョンを取得してファイルに書き出す

まずはスキーマのバージョンを取得します。golang-migrateはDBがMySQLの場合、デフォルトではschema_migrationsテーブルにバージョンを記録するので、それをmysqlコマンドで取得します*2

$ echo "/* Schema version: $(
mysql \
  --host 127.0.0.1 \
  --port 13306 \
  --user root \
  --password=root \
  --skip-column-names \
  --silent \
  --execute "SELECT version FROM database.schema_migrations"
) */" > structure.sql

mysqlコマンドは単に --execute オプションを指定するだけだと、罫線を使った表を出力しますが、今回はカラム名は必要なく、SELECTした値だけが欲しいので、 --silent も指定しています。

なお、パスワードを直接指定しているのはあくまで例示のためで、実際には環境変数などを介して渡すのが良いでしょう(次に紹介するmysqldumpも同様です)。

DBからスキーマを取得してファイルに追記する

次にDBスキーマをダンプします。mysqldumpを使って、先ほどバージョンを出力したファイルにスキーマを追記します。

mysqldump \
  --host 127.0.0.1 \
  --port 13306 \
  --user root \
  --password=root \
  --compact \
  --no-data \
  --single-transaction \
  --skip-dump-date \
  database \
  >> structure.sql

database はダンプしたいデータベースの名前です。

mysqldumpはデフォルトだとテーブルの行や様々なコメントなどを出力しますが、今回はスキーマだけ出力できればいいので、 --compact --no-data オプションを使って CREATE TABLE 文だけが出力されるようにします。

また、--single-transaction を指定してトランザクション内でダンプすることで、仮にダンプ中にスキーマが変更されても出力結果が変わらないようにします。

--skip-dump-date オプションは、スキーマの変更以外でダンプファイルが変更されないように、ダンプした日付の出力をスキップする設定です。

これらのコマンドをシェルスクリプトにしておけば、いつでも誰でも手元のスキーマをファイルにダンプできるようになります。

さらに、ダンプをソースコードリポジトリにコミットしておいて、CIでもスキーマダンプを生成し、コミット済みのものと差分があったら失敗するようにしておけば、マイグレーションの適用漏れなどのも不整合も発生しにくくなるでしょう。

*1:Active Record マイグレーション - Railsガイド

*2:migrateコマンドでも取得できるのですが、ダンプを出力する環境でmigrateとMySQLコマンドラインツールを両方インストールするのが面倒だったので、mysqlコマンドを使います