こんにちは、chihiroです。今回はデータベース・スキーマのバージョン管理ツールであるMigrateを紹介します。
Migrate
http://erosson.com/migrate/docs/index.html
インストール
開発版の方を使いますので、レポジトリからコードをチェックアウトしてインストールします。
$ svn co http://erosson.com/migrate/svn/migrate/branches/monkeypatch_removal migrate $ cd migrate $ python setup.py install
もしくは、easy_installで直接インストールします。
$ easy_install http://erosson.com/migrate/svn/migrate/branches/monkeypatch_removal
また、MigrateはPython製のORMであるSQLAlchemyをベースにしているので、こちらもインストールしておきます。
SQLAlchemyは近いうちにバージョン0.4系が正式版になると思われますが、今回は0.3.10でテストしました。
$ easy_install SQLAlchemy==0.3.10
レポジトリの初期化
インストールが完了すると、PythonのHomeディレクトリにbin/migrateというスクリプトがインストールされます。
最初に、このmigrateを実行してデータベース・レポジトリを初期化します。
初期化のコマンドは"create <レポジトリ名> <プロジェクトの説明文>"です。
$ migrate create db_repository "My blog project"
これでカレントディレクトリにdb_repositoryというレポジトリが作成されます。ここからは、ここで作成されたdb_repository/manage.pyというスクリプトを使って管理を行います。
まず、データベースにバージョン管理用のテーブルを作成します。
コマンドは"version_control <データベースのURL(DSN)>"です。
DSNについてはSQLAlchemyのドキュメントを参照してください。
$ python db_repository/manage.py version_control mysql://username:password@localhost/dbname
毎回DSNを指定するのは手間がかかるので、db_respository/manage.pyを書き換えて、このプロジェクトで使うDSNを指定しておくと便利です。
#!/usr/bin/env python # db_repository/manage.py from migrate.versioning.shell import main main(repository='db_repository', url='mysql://username:password@localhost/dbname')
スキーマのバージョンを確認する
Migrateレポジトリのバージョン番号を調べるには、"version"コマンドを使います。
$ python db_repository/manage.py version 0
一方、データベース側のバージョン番号を調べるには"db_version"コマンドを使います。
python db_repository/manage.py db_version 0
スキーマ定義スクリプトを書く
まず"script"コマンドを使ってテーブル定義用のスクリプトを作成します。
$ python db_repository/manage.py script script.py
"script.py"というのはレポジトリにコミットされない一時ファイルなので、実際にはどんな名前でも構いません。
このscript.pyを編集し、SQLAlchemyの流儀でテーブルを定義します。
# script.py from sqlalchemy import * from migrate import * def upgrade(): user = Table('user', Column('user_id', Integer, primary_key=True), Column('username', Unicode(50), nullable=False), Column('password', String(250), nullable=False), mysql_engine='InnoDB', ) post = Table('post', Column('post_id', Integer, primary_key=True), Column('user_id', Integer, ForeignKey(user.c.user_id), nullable=False), Column('title', Unicode(250), nullable=False), Column('content', Unicode, nullable=False), Column('created_at', DateTime, nullable=False), Column('updated_at', DateTime, nullable=True), mysql_engine='InnoDB', ) user.create(migrate_engine) post.create(migrate_engine) def downgrade(): migrate_engine.execute("DROP TABLE post") migrate_engine.execute("DROP TABLE user")
upgrade()関数にアップグレード時の操作を、downgrade()関数にダウングレード時の操作を書きます。
すべてのスキーマ操作をSQLAlchemyを使ってPython風に書くのが理想なのでしょうが、僕の場合は、「CREATE TABLEだけはSQLAlchemyを使って書き、それ以外の操作をmigrate_engine.executeを使ってSQLを直接発行する」というような使い方をしています。
スクリプトが完成したならば、テストを行います。
$ python db_repository/manage.py test script.py Upgrading... done Downgrading... done Success
アップグレード、ダウングレード共に問題ないことを確認した上で、レポジトリにコミットします。
$ python db_repository/manage.py commit script.py
コミットすると、db_repository/versionsディレクトリに"<バージョン番号>/<バージョン番号>.py"として先ほどのスクリプトがコピーされます。
"version"コマンドでレポジトリのバージョン番号を確認してみましょう。
$ python db_repository/manage.py version 1
アップグレード/ダウングレード
データベースのスキーマのバージョンを上げるには"upgrade"コマンドを使います。
$ python db_repository/manage.py upgrade 0 -> 1... done
"db_version"コマンドでデータベースのバージョン番号を確認してみます。
$ python db_repository/manage.py db_version 1
パラメータなしで"upgrade"コマンドを実行した場合、レポジトリの最新のバージョン番号まで自動的にアップグレードが行われます。もし、ある特定のバージョンまでアップグレードを行うときには、--versionパラメータを指定します。
$ python db_repository/manage.py upgrade --version=1 0 -> 1... done
ダウングレードする場合は、"downgrade"コマンドを使います。この時は、--versionパラメータでバージョン番号を明示的に指定しなくてはなりません。
$ python db_repository/manage.py downgrade --version=0 1 -> 0... done
Migrateを使った開発サイクル
ここまでのところを簡潔にまとめると、Migrateを使った基本的な開発サイクルは次のようになるでしょう。
$ python db_respository/manage.py script script.py $ python db_respository/manage.py test script.py $ python db_respository/manage.py commit script.py $ python db_respository/manage.py upgrade
余談ですが、この手のマイグレーションツールを初めて使い始めたとき、「ダウングレードってするの?」という疑問を抱くときがあります。個人的には、ダウングレードは結構使用しています。ただ、ほとんどが「アップグレードのスクリプトやスキーマ設計にミスがあった」という場合なので、開発スタイルや性格の問題なのかもしれませんが・・・
MigrateのTips
複数のURLを扱う
開発版データベースと本番データベースのように複数のデータベースを管理する必要がある場合、"manage"コマンドを使って、管理用スクリプトを複数作っておくと便利です。
$ python db_repository/manage.py manage db_manage.py \ --url=mysql://username:pass@localhost/db \ --repository=db_repository $ python db_repository/manage.py manage db_manage_dev.py \ --url=mysql://username:pass@localhost/db_dev \ --repository=db_repository
以降は、db_manage_dev.pyを使用して開発を行い、本番環境ではdb_manage.pyを使用してスキーマの管理を行います。
カラムの追加
公式のドキュメントの説明では、migrate.changesetを使ってカラムの追加、変更、削除を行えるとされていますが、正しく動作している感じがしません。
僕の場合は、migrate_engine.executeを使って直接ALTER文を発行しています。
まとめにかえて - Migrateの問題点
残念ながら、このツールは開発が止まってしまい、今後の動向は不透明です。また、エラーメッセージが非常に分かりにくいという問題点もあります。
しかし、現在僕が開発しているアプリケーションもこのMigrateに依存しており、このまま死なせるにはあまりにも惜しいツールですので、僕自身がメンテナンスに名乗りを上げるかもしれません。そういった意味で、あえてこの場で紹介させていただきました。