【Perl】DBIx::Simple ノススメ


  1. はじめに
  2. DBIx::Simple + SQL::Abstract
  3. 単行取得メソッド
  4. 全行取得メソッド
  5. オブジェクトを返すメソッド

はじめに

Perl において DB に接続し、SQL を扱うモジュールはもちろん DBI モジュールな訳だが、これを格段に使いやすくしてくれる DBIx::Simple モジュールというのがある。

use DBIx::Simple; – 今日のCPANモジュール(跡地)
http://e8y.net/mag/009-dbix-simple/

このサイトのまとめが非常に分かり易い。例を挙げるとこんな感じ。

my $sth = $dbh->prepare(<<SQL);
    select name, passwd
    from users
    where userid = ?
SQL
$sth->execute($id);
my ($name, $passwd) = $sth->fetchrow_array;

users テーブルからユーザー名とパスワードを取り出している。これを DBIx::Simple を使って表すと……

my $ds = DBIx::Simple->new($dbh);
$ds->query(<<SQL, $id)->into(my ($name, $passwd));
    select name, passwd
    from users
    where userid = ?
SQL

SQL の発行と結果の取得を一発でやってくれる。

DBIx::Simple + SQL::Abstract

更に、これに SQL::Abstract を連携させることによりモダンな記法が使える。

$ds->select("users",
    [qw! name passwd !],
    { userid => $id }
)->into(my ($name, $passwd));

この程度だと SQL::Abstract の恩恵はいまいち感じられないけど、複雑な WHERE 句を使うようになると本領発揮。

SELECT userid, name
FROM users
WHERE age > 20
AND gender = 'male'
AND prefecture IN ('東京都', '静岡県', '岡山県');

これを DBIx::SimpleSQL::Abstract)で表すと……

$ds->select("users".
    [qw! userid, name !],
    {
        age => { ">" => 20 },
        gender => "male",
        prefecture => { -in => [qw! 東京都 静岡県 岡山県 !] },
    },
);

不等号や IN 句や LIKE 句を使ったときに少し特殊な記法を使うことになるが、慣れれば圧倒的にこちらが見易い。

もっとも、JOIN や副問い合わせを使い出すと素の SQL より複雑になることもある。そのときはおとなしく最初に挙げた query メソッドを使おう。

【Perl】DBIx::Simple で副問い合わせや JOIN を使う | blog.delphinus.dev
https://blog.delphinus.dev/2011/09/use-subquery-and-join-with-dbix-simple.html

単行取得メソッド

SQL::Abstract による簡潔な記法も魅力だが、DBIx::Simple の真骨頂は様々な結果取得メソッドにある。

list メソッド

$ds->select("users",
    [qw! name passwd !], # WHERE 句を省略すると全行取得する
);
while (my ($name, $passwd) = $ds->list) {
    say "ユーザー名: $name, パスワード: $passwd";
}

DBI でいう fetchrow_array と同じものだ。

array メソッド

while (my $r = $ds->array) {
    say "ユーザー名: $r->[0], パスワード: $r->[1]";
}

同じく、DBI でいう fetchrow_arrayref

hash メソッド

while (my $h = $ds->hash) {
    say "ユーザー名: $h->{name}, パスワード: $h->{passwd}";
}

DBI でいう fetchrow_hashref

into メソッド

while ($ds->into(my ($name, $passwd)) {
    say "ユーザー名: $name, パスワード: $passwd";
}

DBIprepare, bind, execute をやるのと同じこと。list メソッドと違って配列のコピーが行われない分速い?(未検証)

全行取得メソッド

単行取得して while 文で回すのではなく、一度でテーブル全体を取得するメソッドたち。

flat メソッド

my @names = $ds->select("users",
    [qw! name !],
)->flat;

取得カラムが一つだけの時、全行を一つの配列に突っ込んでくれる。

arrays メソッド

my @data = $ds->select("users",
    [qw! name age gender !],
)->arrays;
for (@data) {
    say "ユーザー名: $_->[0], 年齢: $_->[1], 性別: $_->[2]";
}

一行を配列リファレンスに格納し、更に全行を配列に突っ込んで返す。

hashes メソッド

my @data = $ds->select("users",
    [qw! name age gender !],
)->hashes;
for (@data) {
    say "ユーザー名: $_->{name}, 年齢: $_->{age}, 性別: $_->{gender}";
}

一行をハッシュリファレンスに格納し、更に全行を配列に突っ込んで返す。

map メソッド

my %data = $ds->select("users",
    [qw! userid name !],
)->map;
while (my ($userid, $name) = each %data) {
    say "ユーザー ID: $userid, ユーザー名: $name";
}

一つ目のカラムをキーに、二つ目のカラムを値にしたハッシュを返す。カラムが 1 個だけだったり 3 個以上のときには使えない。また、一つ目のカラムの値に重複があった場合もおかしなことになってしまう。

map_arrays メソッド

my %data = $ds->select("users",
    [qw! userid name age !],
)->map_arrays(0);
while (my ($userid, $r) = each %data) {
    say "ユーザー ID: $userid, ユーザー名: $r->[0], 年齢: $r->[1]";
}

引数に指定したカラム(0 からスタートし、左から順に数える。)をキーに、その他の値を配列リファレンスに格納したものを値にしたハッシュを返す。引数を省略した場合は、一つ目のカラムがキーになる。

map_hashes メソッド

my %data = $ds->select("users",
    [qw! userid name age gender !],
)->map_hashes("userid");
while (my ($userid, $h) = each %data) {
    say "ユーザー ID: $userid, ユーザー名: $r->{name}, 年齢: $h->{age}";
}

引数に指定したカラムをキーに、その他の値をハッシュリファレンスに格納したものを値にしたハッシュを返す。この場合は引数を省略できない。

オブジェクトを返すメソッド

DBIx::Simple のバージョン 1.33 から object 及び、objects メソッドが実装されている。これは DBIx::Simple とは別の作者が作った DBix::Simple::OO と言うモジュールを本家に取り込んだもので、クエリの結果をオブジェクトで返すメソッドだ。

基本編

my $rs = $ds->select("users",
    [qw! userid name age gender !],
);
while (my $obj = $rs->object) {
    printf "ユーザー ID: %s, ユーザー名: %s, 年齢: %d, 性別: %s",
        $obj->userid, $obj->name, $obj->age, $obj->gender;
}
my @objs = $rs->objects;
for my $obj (@objs) {
    printf "ユーザー ID: %s, ユーザー名: %s, 年齢: %d, 性別: %s",
        $obj->userid, $obj->name, $obj->age, $obj->gender;
}

引数なしでこのメソッドを呼ぶと単純にハッシュのキーがメソッド名になって帰ってくる。これは Object::Accessor モジュールを使って実装されているので、このモジュールに固有のメソッド名(new とか mk_accessors とか)がカラム名にあってはいけない。

応用編

package MyRowObj;
sub new {
    my $class = shift;
    bless { @_ } => $class;
}
sub user_info {
    my $self = shift;
    "フルネームは $self->{first_name} $self->{last_name} です。";
}
1;

package main;

# (省略)

my $rs = $ds->select("users",
    [qw! first_name last_name !],
);
while (my $obj = $rs->object("MyRowObj")) {
    say $obj->user_info;
    # 「フルネームは Bill Gates です。」などといった文字列が表示される
}

独自のクラスからそれのインスタンスを返すこともできる。この例では、名前と名字からフルネームを作るメソッドを実装している。こんな単純なものではオブジェクトを作る分遅くなるだけだが、計算が複雑、かつ、よく利用するものを別パッケージにまとめておくといざというとき便利かも知れない。

コメントを残す