Tracks

書いてみたい方はココを参照下さい。

DBIx::Inspector で DB の中をのぞきみる B!


tokuhirom


ベストスピーカー賞をもらった tokuhirom です。

1981忘年会でのみすぎて、二日酔いが大変ひどいです。

さて、今回は裏をかいて、DBIx::Inspector の話をかこうかおもいます。最近わたくしは、Web エンジニアの7つのはしかのうちの一つである O/R Mapper を書いてます。

これは、高速で、ミニマリスティックで、安定していて、DBI をリスペクトしていて、みたいな、そんなのをめざしてます。

O/R Mapper かく人って、O/R Mapper をかくときに必要なものとかを、まるっと同梱したがりがちなんですね。

そういうつくりにしてしまうと、別個なかんじの O/R Mapper をかこうとしたときに、つかいまわしがきかなくていっぱいコピペしなくちゃいけないです。

それかなしいよねってことで、みんなが共通につかえるパーツをちょっとずつかいてみようかな、とおもってます。

その運動の一貫として、DBIx::TransactionManager っていうモジュールが id:nekokak からでたりしてますね。

これ、まじべんりですね。すばらしいですね。Skinny からこういうかんじでコピペしてリリースしようとおもってるよーっていってから昼飯にいって、かえってきたらもうリリースされてました。

さて、いよいよ本題です。

今回つくった DBIx::Inspector っていうのは、DB のメタデータを、OOish にとりだしたい、というモジュールです。

DBIx::Skinny::Schema::Loader とか DBIx::Class::Schema::Loader とかって、無駄にクラスがおおすぎるとおもうんです。

DBI::* っていうネームスペースに、各ドライバごとに、こまかい調整するコードがはいってて、すごい無駄っぽい。

で、本質的には、O/R Mapper の Schema Loader が解決したい問題っていうのは、各DBの抽象化じゃないわけです。本来はそれは DBI がやってくれるべき話なわけで。

つーか、がんばって抽象化するんなら、その抽象化した成果を外にだしておけば、後の人が便利だろ、ということです。

で、それをちゃんとやったのが DBIx::Inspector です。

本来、DBI は table_info, foreign_key_info, column_info という、イントロスペクション用の API を用意しています。しかし、これらは、微妙に挙動がイマイチだったり、直接つかうのがダルいかんじになっています。

具体的には以下のような問題があります。

  • DBD::SQLite は foreign_key_info が未実装です
  • DBD::SQLite は table_info で sqlite_* という内部用のテーブルをかえしてくる(これもしかしてバグ?)
  • DBD::Pg は foreign_key_info のハッシュキーが他とちがう
  • DBD::Pg は foreign_key_info で undef をかえすときがある(これはよくわからん)
  • DBD::mysql は foreign_key_info で、PK の情報もかえしてくる(たぶんこれはバグ)。

このへんの問題を、まるっと隠蔽してくれます。

さて、うんちくはともかく実際のつかいかた。

DBIx::Skiny の schema をダンプしてみましょう。

#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Text::MicroTemplate qw/render_mt/;
use DBI;
use DBIx::Inspector;

# 実際にはスキーマつくる部分はすでに定義されている場合がおおいとおもう。
my $dbh = DBI->connect('dbi:SQLite:', '', '', {RaiseError => 1});
$dbh->do(q{create table t1 (c1 primary key,c2)});
$dbh->do(q{create table t2 (c3 primary key,c4)});

my $inspector = DBIx::Inspector->new(dbh => $dbh);

my $schema = 'MyApp::Schema';

print render_mt(<<'...', $schema, $inspector);
? my ($schema, $inspector) = @_;
# XXX THIS FILE IS GENERATED BY <?= $0 ?>
package <?= $schema ?>;
use strict;
use warnings;
use DBIx::Skinny::Schema;

? for my $table ($inspector->tables) {
install_table '<?= $table->name ?>' => sub {
?   if (my @pk = $table->primary_key) {
    pk      qw(<?= join ' ', map { $_->name } @pk ?>);
?   }
    columns qw(<?= join ' ', map { $_->name } $table->columns ?>);
};

? }

1;
...

ね。簡単でしょ。

O/R Mapper をつくる人は schema laoder 用のライブラリを用意するのが超簡単になります。実際、僕がつくってる O/R Mapper では、たかだか20行程度で実現できているのです。

といったところで、今回は小物を紹介してみました。次は、おまちかね。早口ならこの人! nipotan 関です。