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

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

Kappa という ORM を書いてみた話

このへんの続きみたいな話。

Kappaという ORM を書いています。欲しい機能は大体できて、まあドキュメントがアレなのですが、とりあえず自分で使う分には困らないし、頑張れば使いたい人も使えるかなー、くらいまではきたのでここに書いた次第です。

インストール

github から、SQL::ExecutorKappaを入れてください。SQL::Executor はもしかしたら CPAN に上げるかも。

使い方

コンストラクタで、dbh を渡します。あと、オプションで Row オブジェクトとテーブルクラス*1の namespace を渡せます。

use Kappa;
use DBI;
my $dbh = DBI->connect($dsn, $id, $pw);
my $db = Kappa->new($dbh, {
   row_namespace   => 'MyProj::DB::Row',
   table_namespace => 'MyProj::DB::Table',
});
my $row_obj = $db->select('SOME_TABLE', { id => 123 });
print $row_obj->id, $row_obj->value;

どちらのオプションも必須ではなくて、デフォルトでは Row オブジェクトは Kappa::Row, テーブルクラスは Kappa 自身を使うようになっています。Row オブジェクトもテーブルクラスも、テーブル毎に定義されたものがあればそれ、なければ、namespace 自身(この例では MyProj::DB::Row/Table)、それもなければデフォルト(Kappa::Row/Kappa)が返ります。

Row オブジェクト

select 系を叩いたときだけ返ります。select_row 系だと Row オブジェクトそのもの、select_all 系だと Row オブジェクトの配列、select_itr 系だと iterator 経由で、iterator#next を呼ぶと Row オブジェクトが返ります。

row_namespace を指定すると、テーブル単位で Row オブジェクトを定義できます。Row オブジェクトは基本的にカラムの情報を組み合わせてアレコレすること以上のことはできませんが。*2

あと、

my $db = Kappa->new($dbh, {
   row_namespace   => 'MyProj::DB::Row',
});
$db->row_object_enable(0);
my $row_href = $db->select('SOME_TABLE', { id => 123 });

みたいに、row_object_enable というメソッドを呼ぶと、Row オブジェクトの代わりに hashref (all系なら hashref の配列)が返るようになります。また、この row_object_enable は Guard オブジェクトを戻り値に返せるので、戻り値を受けると、スコープを抜けた際に、元の設定に戻します。

my $db = Kappa->new($dbh, {
   row_namespace   => 'MyProj::DB::Row',
});
{
    my $guard = $db->row_object_enable(0);
    my $row_href = $db->select('SOME_TABLE', { id => 123 });
    # ここまでは row_object_enable(0)が有効
}
# ここからは元通り Row オブジェクトが返る

あと、inflate したい人は、Row オブジェクトに inflate rule を書くことになると思います。

テーブルクラス

テーブル毎のロジックを置く場所です、ファクトリメソッドにテーブル名を指定して呼びます。

my $db = Kappa->new($dbh, {
   table_namespace => 'MyProj::DB::Table',
});
my $db_for_sometable = $db->create('SOME_TABLE');

なんでこんなものを用意したかというと、テーブルごとに面倒くさい SQL とか、マスタデータのコード/名称変換とか置けたら便利で見通しが良いかなー、と思ってのことです。deflate したい人はテーブルクラス毎に deflate rule を置くことになると思います。*3

やりたかったこと

最近の日記につらつら書いてましたが、大体こんな感じのことです

  • 簡単な SQL は簡単に実行したい
  • 面倒くさい SQL は手書き、その際に名前付きプレースホルダを使いたい
  • Row (だけ)ではなく、テーブルごとにある程度ロジックを置きたいな
  • スキーマクラスなしで動かしたい
ORM なんて世の中にいっぱいあるのに、どうして車輪の再発明をしたのか

ほしいものが無かったから、というのが超簡単な答え。

前にもちょっと書いたように、Teng を少し使っていて、上記の上半分は快適に要件満たせていたのだけど、下半分、とに「スキーマクラスなし」で動かせるほうがいいなー、と思いました。*4

で、DBIx::Simple だと要件だいたい満たせそうなのだけど、もう Teng 向けにかいちゃったコードが結構あるし、なんか DBIx::Simple のインターフェース(とくに ResultSet まわり)が見た感じ好きじゃないなー、と思ったし。。。みたいな感じです。

要は、DBIx::Simple みたいに使えて、Teng みたいなインターフェースを持ってる物体が欲しかったんですね。

インターフェースは変えない?

今のところ、変えるつもりはないのだけど、変えるかもしれません。「使ってます」って声があったら変更は慎重になると思います。あるいは、今のところ CPAN にあげる気は無いのだけど、気が変わって CPAN に上げたら、互換性を損なうような変更はしない(する場合はドキュメントに明記する)と思います。

なぜ Kappa?

DBIx::Lite とか、そんな感じだと良くわかんないし、ありそうだし(つーか今しらべたらあったw)。じゃあ SuperLight?Easy?なんかそういうの嫌だよね。

で、某アニメ*5で、「Kappa -> Parasite(パラサイトなのにパラシテと読まれる)->Tengu*6」という「絵しりとり」のネタをふと思い出して、「これだ!、ORM の名前は Kappa で、今 Teng 向けに書いちゃった部分の移植を容易にする為のブリッジをパラシテにしよう!」*7と思って決めた次第。ここ数週間は、超忙しかったのでどうやら少し頭がおかしかったようです。

まあそんな感じで、特に意味は無いんだけど、ウチの地域のマスコットが河童らしいし、ちょうどいいんじゃないですかね。CPAN のネームスペースはギリギリ空いてるっぽいし。

*1:多分これ私の造語

*2:この辺あんまりドキュメントに書いてないので、書かないと。。。

*3:僕自身はあんまり inflate/deflate いらないことが分かったし、それよりはスキーマクラスなしで動くことの方が重要だったため、deflate ルールを置く場所がここしかありません

*4:テーブル数が結構多いので、スキーマクラスの動的生成はそれなりに時間がかかって、特にテストコードが遅くなるのが嫌だった

*5:僕はアニメ見ないんだけど、コレは NHK でやってたので見てた。つーか娘が見てた

*6:ちなみになぜかTENGOと表記される

*7:パラシテは公開してません