unoh.github.com

Migrateのご紹介

Tue Sep 04 23:30:30 -0700 2007

こんにちは、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に依存しており、このまま死なせるにはあまりにも惜しいツールですので、僕自身がメンテナンスに名乗りを上げるかもしれません。そういった意味で、あえてこの場で紹介させていただきました。