CouchDB は面白いのですが、Erlang で書かれた daemon を動かす必要があるので、環境によっては使いにくい。
ということで、Perl + DBI のみで似たような動作をするものを作ってみました。daemon じゃなくて、Perl から直接 DB を扱うライブラリです。
Coderepos に置きました。http://svn.coderepos.org/share/lang/perl/DBIx-CouchLike/trunk
できることは単純。
対応している DBD はとりあえず SQLite と PostgreSQL で。
CRUD
use DBIx::CouchLike; use DBI; $dbh = DBI->connect("dbi:SQLite:dbname=test.db"); $couch = DBIx::CouchLike->new({ table => "foo", dbh => $dbh }); $couch->create_table(); # はじめて使う場合テーブル作成 # CREATE (id 自動発行) $id = $couch->post({ name => 'animal', tags => ['dog', 'cat']}); # CREATE (id を指定) $couch->post( $id => { name => 'animal', tags => ['dog', 'cat']} ); # または $couch->post({ _id => $id, name => 'animal', tags => ['dog', 'cat']}); # RETRIEVE $obj = $couch->get($id); # UPDATE $couch->put( $id, $obj ); # または $couch->put( $obj ); # $obj->{_id} が存在していること # DELETE $couch->delete($id);
Map / Reduce
map, reduce のための perl コードを文字列で用意します。
map の第1引数は保存されているオブジェクトそのもの。第2引数は新しい key => value ペアを登録するためのサブルーチンリファレンスです。
$map_sub_str = <<'END_OF_CODE'; sub { my ($obj, $emit) = @_; for my $tag ( @{ $obj->{tags} } ) { # tags の配列を順番に取得して # tag => name のペアに変換 $emit->( $tag => $obj->{name} ); } } END_OF_CODE
reduce の引数は key と value の配列リファレンス。
$reduce_sub_str = <<'END_OF_CODE'; sub { my ($keys, $values) = @_; # value の数を数えるだけ return scalar @$values; } END_OF_CODE
用意したコードを _design/*** という id で登録。
$couch->post({ _id => "_design/find", views => { tags => { map => $map_sub_str }, # map するだけのもの tags_count => { # map して reduce するもの map => $map_sub_str, reduce => $reduce_sub_str, }, }, });
map / reduce で処理するためのオブジェクトを保存。この例は SBM みたいにタグ付けされたデータだと思ってください。
$id_a = $couch->post({ name => "animal", tags => ["dog", "cat"] }); $id_c = $couch->post({ name => "unix", tags => ["cat", "less", "more"] });
登録された _design/find を取得するには view() を使います。まず map するだけのを取得してみると
@result = $couch->view("find/tags")->all;
以下のような値が返ります。順番は key, value の辞書順 (SQL の ORDER BY) です。
( { key => "cat", value => "animal", id => $id_a }, { key => "cat", value => "unix", id => $id_c }, { key => "dog", value => "animal", id => $id_a }, { key => "less", value => "unix", id => $id_c }, { key => "more", value => "unix", id => $id_c } )
指定した key だけ取得。
@result = $couch->view("find/tags", { key => "cat" })->all;
( { key => "cat", value => "animal", id => $id_a }, { key => "cat", value => "unix", id => $id_c } )
map された key => value ペアに対応する document も一緒に取得するには include_docs オプション。
$couch->view("find/tags", { key => "dog", include_docs => 1 })->next;
{ id => $id_a, key => 'dog' value => 'animal', document => { _id => $id_a, name => 'animal', tags => ['dog', 'cat' ] }, }
reduce するほうを取得するとこうなります。
@result = $couch->view("find/tags_count")->all;
({ key => 'cat', value => 2 }, { key => 'dog', value => 1 }, { key => 'less', value => 1 }, { key => 'more', value => 1 })
注意とか
- 分散処理とかレプリケーションとか、そういうのはまったく考慮されていません
- map, reduce で与えた文字列は eval で評価されますので、くれぐれもご注意ください