tsucchi’s diary(元はてなダイアリー)

はてなダイアリー(d.hatena.ne.jp/tsucchi1022)から移行したものです

DBのスキーマ変更をテストする

ずいぶん前から実施しているのだけど、書いてなかったので書きます。

皆様は DB のスキーマ変更(ALTER TABLE)ってどうやって管理していますか?SQL 手打ちは論外として、事前に変更の SQL を作るとしても、テストしたいですよね?

と、いうわけで、こんな感じに書ける物体を使っています。

use Test::MyProj::Schema;
use Test::More;

my $schema = Test::MyProj::Schema->new({
    svn_schema_base => 'Rel-1.00',
    patch_dir => '../../ddl/patch/Rel-1.01',
});
$schema->current_schema_ok();
$schema->patched_schema_ok();

テストファイルの名前を、テーブル名にしています。

で、current_schema_ok では、「今のスキーマ(ファイルに書かれた CREATE文)」と「実際のDB のスキーマ(SHOW CREATE TABLEしたやつ)」が一致しているかをテストします。

patched_schema_ok は、ちょっと分かりにくいのですが、svn_schema_base で「基準になるスキーマDDL のバージョン」を書いて、patch_dir に「そのリリースで適用するパッチ(ALTER TABLE)のディレクトリ」を書きます。つまり、「『前のバージョン(CREATE) + パッチ(ALTER TABLE) = 今のバージョン』になるよね!」というテストです。*1。「今のバージョン」は CREATE 文になるようにしています。*2

で、CI サーバでチェックしつつ、ステージング環境にアップした際もテストを実施してます。

本当はもうちょっと汎用的なつくりにして、github なり CPAN なりにアップしたいのですが、プロジェクトのディレクトリ構造に依存しまくるし、僕は仕事では svn しか使ってないので、他の VCS に対応とかやりたくないし、ってか今のプロジェクトでしか使ってないから、実は汎用化するモチベーションもあんまりないかも、みたいな感じです。

*1:この例だと、前のバージョンが Rel-1.00、今(開発中)のバージョンが Rel-1.01 です。

*2:「次のバージョン(1.02)」になった時に、「1.00の CREATE 」+「1.01のパッチ」+「1.02のパッチ」という形で管理するのはめんどくさいなー、と考えているためです。このへんは異論ありそう

Teng の不具合をみつけて直してもらったのと、機能追加してもらった話

togetter にまとめました。
Teng の不具合を直してもらった話 + 機能追加してもらった話

  • set_column() をつかって update した場合に、deflate がかからない不具合があって、それを直してもらった
  • テーブルのカラム名で CamelCase を使っている場合に Row オブジェクトのメソッド名がすべて小文字になってしまうのを、設定次第で CamelCase 使えるようにしてもらった

という内容です。現在リリースされている 0.14_05 は他の改修もいくつか入っているようですね。

ちょっと前まで、NAME_lc を手で NAME に書き換えた版を使ってて、「これじゃ(忘れたころに)アップグレードしたときハマりそうで怖いなー」とか思ってたので助かりました。

すぐに直してくれた nekokak++ です!(次回なんかあったらちゃんとパッチ書きます。ごめんなさいごめんなさい)*1

*1:ちなみに nekokak さんがサクッと直してるのをライブコーディング的に見てたわけで、「早いなー、すごいなー」とか思ってちょっと凹んだのは秘密だ。あ、書いちゃったから秘密じゃないか。。。

Teng の Row オブジェクトをテストする

最近、わりと普通な感じの web アプリを書いてたりします。で、Teng 使ってたりします。

Teng は Row オブジェクトにあれこれ機能を追加したりできて便利なのですが、ちょっと難しいロジックが入ったりするとテストを書きたくなってきます。そこで、こんなものを書いてみました。*1

package t::Util::TengRow;
use parent qw(Exporter);
use strict;
use warnings;
use utf8;
use DBI;
use MyProj::DB;# Teng のサブクラス

our @EXPORT = qw(row_from_hashref);

sub row_from_hashref {
    my ($table_name, $row_value_href) = @_;
    my $dbh = DBI->connect('DBI:Mock:', '', '');
    my $teng = MyProj::DB->new(dbh => $dbh);
    my $table = $teng->{schema}->get_table($table_name);

    my $row_data = {};
    for my $column ( @{ $table->{columns} } ) {
        $row_data->{$column} = $row_value_href->{$column};
    }

    return $table->{row_class}->new({
        sql        => "SELECT * FROM $table_name",
        row_data   => $row_data,
        teng       => $teng,
        table      => $table,
        table_name => $table_name,
    });
}

1;

テストコード側はこんな感じです。

use strict;
use warnings;
use t::Util::TengRow;

subtest 'add', sub {
    my $row = row_from_hashref('some_table', { value1 => 1, value2 => 2 });
    #add_value1_and_value2 は名前のとおり value1 と value2 を加算した結果を返すメソッドとします
    is( $row->add_value1_and_value2, 3 ); 
};
done_testing();

まあ、この例のレベルならテストコードいらないよねー*2、とか、$row->handle 経由であれこれするようなやつはアウトだとか、DBD::Mock 仕事してないじゃん*3、とか色々問題はあるのですが、とりあえずこんなの使ってますよ、と。

*1:Teng#single メソッドの Row オブジェクト生成部分をパクってテスト用に調整しただけです

*2:どのくらい難しい処理だったらテストを書くべきか悩むよね、と言う意味で

*3:仕事してないからいらない気もするし、かと言って空文字とか undef とか渡すのも怪しいし、でも通常利用してる DBD を渡すのはそれはそれで気持ち悪いし、悩みます

2011年まとめ的な何か

気がついたら年明けてたのですが、まあ気にしないでください。*1

ってか久しぶりの更新ですね。はてなブログのほうには割とどうでもいい話を少し書いたりしていましたが。。。


今年は震災があって、停電とかあって、序盤からなにやら大変な感じでした。

被災した方たちに比べたら僕たちの苦労なんてたいしたこと無いのでしょうけど、停電とかまじ勘弁ですね。。。


4月末にスマートフォン(IS05)に買い換えて、楽しく使わせてもらってます。


今年も YAPC::Asia に参加させていただきました。参加3回目で、今年は自分も発表させてもらえました。

懇親会も楽しかったし、良い経験でした。またいつかどこかで発表してみたいなー。


で、YAPC の日あたりから、 MySQL の PP のドライバをメンテすることになったりしました。

これについては、ちゃんと時間をとって、もっといいものにしたいなー、とは思っているのですが、なかなかうまくいってないです。期待してる人いたらごめんなさい。


年末は、Perl Advent Calendarに2つほど寄稿させていただきました。

ブクマとかぜんぜんつかなくて、「うーん」という感じではありますが、Test Track の完走に貢献できた気がするので、とりあえず自分で自分をほめてあげたいです。


そう、今年は「洋書がちゃんと読めるようになる」を目標にかかげて、xUTP の一人読書会を開催していたのでした。

で、結果は、というと、2章の序盤までしか読めませんでした。。。だめだめですね。。。来年(日付的には今年か。。。)も継続です。


2011年はある程度アウトプットもできた反面、「いい年のおっさん」*2なのに、ハッカーとして一流というわけではなく、かといってマネージメントできるわけでもない、という微妙な自分を感じてしまって、どうしたものか。。。と思うわけです。

2012年は、自分がどうありたいか、どうなりたいか、をちゃんと考えないとなー。。。

*1:2年ぶり2回目の「会社で年越し」をやってしまった私の心中お察しください。。。

*2:0xでももう20代なのです。。。

話題のはてなブログに招待いただきました

http://tsucchi.hatenablog.com/

まだほとんど何もしていないし、どう使うかも未定です*1。招待されたい方いましたら、@tsucchi まで。

*1:JSできたら色々できて楽しいんだろうなー。JS力とかCSS力低すぎて遊び方が思いつかないのです

ハッシュリファレンスのデリファレンス

ハッシュリファレンスのデリファレンスで初心者っぽいハマり方をしたので、メモ。

#!/usr/bin/perl
use strict;
use warnings;
use feature qw(say);

my $aa = {
    aaa => { bbb => 'ccc' },
    xxx => 'yyy',
};

say "aaa";
say $aa->{aaa}->{bbb};#ok

say "xxx";
say $aa->{xxx}->{bbb};#died

say "finished";

こんな感じのプログラムを作って実行すると、

tsucchi@seasons[121]% perl a.pl
aaa
ccc
xxx
Can't use string ("yyy") as a HASH ref while "strict refs" in use at a.pl line 15.

こんな感じで、$aa->{xxx}->{bbb}のデリファレンスでお亡くなりになります*1。まあこんなデータ構造を作る奴が悪いのですが、にしても、実際のプログラムで発生したときは、何が起きてるか分からなくてかなり焦りました。警告でもいいと思うんだけどなー。

対策が、no strict 'refs'か、ref $aa->{$key} eq 'HASH' みたいな判定を入れる、というのも「なんだかなー」という感じです。

まあやっぱりこういう変なデータ構造をつくっちゃダメですね。

*1:finished が表示されていないので、die しているのが分かる

DBD::mysqlPP の話

YAPC::Asia 2011 の1日目の LT のちょい前くらいに、軽く twitter の TL みてたら、こんなのがありました。

JVN#51216285 DBD::mysqlPP における SQL インジェクションの脆弱性

で、そこにこんな風に書いてありました。

対策方法

DBD::mysqlPP を使用しない
開発者によると、「DBD::mysqlPP は、ジョークプログラムとして作成されたものであり、プライベートな利用や MySQL 通信プロトコルの読解を助ける用途にのみ利用し、それ以外の用途においては同一の API を持つライブラリである DBD::mysql 最新版の使用を推奨する」とのことです。

で、「それはないわー」、と思ったので、
http://b.hatena.ne.jp/tsucchi1022/20111014#bookmark-63059158

これほんとに作者の弁?だったらひどいね。CPAN から消すか Acme に置くべきじゃね?「開発者によると、「DBD::mysqlPP は、ジョークプログラムとして作成されたものであり...」

こんな感じで軽く dis ったら、紆余曲折あって(?)、僕がメンテすることになってしまったでござる、という感じです。
僕が思ったのはこんな感じのことです、

  • これはジョークソフトでは無いと思う
    • もしそうなら CPAN に置くのはよくないし、置くとしても Acme::だよね*1
  • libmysql 入れられない/入れたくない事情があるかもしれなくて、そういう場面で使いたい人はいるであろう*2
  • 元の作者の方は、残念ながらメンテナンスするモチベーションを失ってしまっている
    • 残念だけど、そういうこともある。それは仕方ないと思う
    • セキュリティーホールが放置されるのは良くない
    • とはいえ、消してしまうのももったいないなー

なので、自分の力で何とかなるなら、何とかしたいなー、と思った次第です。とはいえ、MySQL の通信なんて分からんし、どうしたものかー、と思っていたら、セキュリティーホールはパッとソースを見たら何となくめどはついたし、@nihen さんも手をあげてくれたので、何とかなるだろう、と思って少しずつ手を入れてます。

取り急ぎ、セキュリティ fix だけ行ったバージョンを 0.05 として今朝 CPAN にアップしました。
DBD::mysqlPP

プロダクション環境なら、DBD::mysql を使っているだろうから問題ないとは思うけど、もし使っている人がいたら、バージョンアップしてみてください。(プレースホルダまわりのバグがあって、それもつぶれているはず)。通信ドライバのモジュールも別途あって、そっち一つだけバグを潰してあるので、一緒にあげた方が良いかもしれません。(http://search.cpan.org/~tsucchi/Net-MySQL-0.10/MySQL.pm)

今後ですが、

  • ドキュメントの整備
    • 「本番環境では使わないでね」って注意入れといた方がいい気がするし、supported OS とか古いし消したいなー、とか、そのへん
  • RT で報告されてるバグを潰す
  • utf8 まわりのあれこれ(を @nihen さんがやってくれるらしい)
  • テストの整備
    • Test::mysqld が良く分からないけど使えないので、何とかしないと
    • できたら DBD::mysql のテストが通せるようにしたいなー

とかそんな感じのことを考えてます。

リポジトリは僕の github アカウントで管理することにしたので、いじってみたい人は fork + pull-req してみてください。

*1:結構古いモジュールなので、当時は Acme 名前空間がなかったかもしれない

*2:とはいえ、基本的には DBD::mysql を使うべきである