今日のCPANモジュール(跡地)

宣伝と注意書き

このサイトが元になったCPANモジュールガイドという本を書きました。

本書では取り上げていません。代わりに、DBIを直接使って同様のことを行う方法を説明しています。また、SQL::Makerも扱っています。

DBIx::Simpleが使っているSQL::AbstractはLIMIT句が使えないので、SQL::Abstract::Limitを併用しないといけないのがめんどうなのと、DBIそのものだけでもかなり高機能なので、DBIに慣れておく方がつぶしが効くのではないか、という判断です。

2007-03-20

use DBIx::Simple;

今みたら、読者数60人を超えてました。このメルマガは会社にしか告知していなくて、会社で登録してるのはとりあえず3,4人です。せっかく書くならまぐまぐにしておけば、誰かが見つけて登録してくれるかも、とか思っていたのですが、意外とたくさん登録してくれていてうれしいです。

今回はデータベースまわりのモジュールを取り上げます。

Perl から DB 接続するときは DBI.pm を使うというのは定番で、これはすばらしいモジュールなのですが、ステートメントハンドラが出てきたりレイヤーとしては低い層の API です。そこで普通なにがしかのラッパーモジュール経由で DBI を使います。

軽量・シンプルで小粋なラッパーをご所望であれば、DBIx::Simple がおすすめです。もっといろんなことを期待するのであれば、本格 ORマッパー DBIx::Class がいちおしです。

今回は DBIx::Simple を解説します。Web 系アプリでは DBIx::Class を使うことのほうが多いですが、簡単な処理系バッチを書くときなどは DBIx::Simple の方を使うこともあります。

使い方は簡単です。素の DBI.pm での操作と比較しながら見てみてください。

接続

接続まわりは DBI と同じです。

# MySQLの例
my @dsn = (
    'dbi:mysql:host=localhost;database=test;',
    'test',
    'test',
    { RaiseError => 1 },
);

my $db = DBIx::Simple->connect(@dsn)
  or die DBIx::Simple->error;

# SQLiteの例
my $db = DBIx::Simple->connect('dbi:SQLite:dbname=../db/hoge.db')
  or die DBIx::Simple->error;

テスト用テーブルを作っておくといろいろ試せてよいです。今回以下のようなスキーマを前提に話を進めます。

http://e8y.net/mag/009-dbix-simple/sample.sql(文字コードUTF-8、SQLite 用 create 文です)

SQL文の実行

一番基本的なクエリ発行は query() です。以下のように使います。

my $rs = $db->query('select * from spell where class = ?', 1) 
  or die $db->error;

query() の第一引数に SQL文、2個目以降に ? にバインドする値を渡します。そして結果のオブジェクトが返ります。(失敗した場合は 偽 が返り、上記のように $db->error でエラー内容を取り出せます)

この結果オブジェクト(DBIx::Simple::Result)がいい感じです。

DBIx::Simple::Result

件数

件数を引くには $rs->rows() を使います。

print "Results:". $rs->rows;

query() に渡した SQL が更新系(insert, update, delete)だった場合は、影響を受けた件数が取れます。

データのイテレート

select系の場合は、結果オブジェクトの hash() というメソッドを使うと、以下のように結果をハッシュで受け取りながらイテレートできます。

use Data::Dumper;

while (my $row = $rs->hash) {
    warn Dumper $row;
}

# 結果 ---

$VAR1 = {
          'word' => 'メラ',
          'desc' => '火の玉をぶつける',
          'class' => '1'
        };
$VAR1 = {
          'word' => 'ギラ',
          'desc' => '炎',
          'class' => '1'
        };

$rs->hash のところを array() に変えると、ハッシュじゃなくリストリファレンスで行を取得できます。

my $rs = $db->query('select desc, word from spell where class = ?', 2);

while (my $row = $rs->array) {
    warn Dumper $row;
}

# 結果 ---

$VAR1 = [
          'HPを回復する',
          'ホイミ'
        ];
$VAR1 = [
          '毒を治療する',
          'キアリー'
        ];
$VAR1 = [
          '生き返らせる',
          'ザオリク'
        ];

ほかにも、渡した変数に展開する into() という小技もあります。

my $rs = $db->query('select word, classmap.name 
                     from spell left join classmap 
                     on spell.class = classmap.id
                     limit 3');

while ( my $row = $rs->into( my ($word, $class) ) ) {
    warn "* $word ($class系)

"; }

# 結果 ---

* メラ (攻撃系)
* ギラ (攻撃系)
* ホイミ (回復系)

count(*) の結果など、値を直接を受け取りたい場合は、list() という array() のリファレンスにならないバージョンを使います。

my ($count) = $db->query('select count(*) from spell')->list;

結果の全取得

hashes()arrays() を使うと、全行が返ります。

my $set = $db->query('select word, desc from spell class = ?', 3)->hashes;
warn Dumper $set;

# 結果 ---

$VAR1 = [
          {
            'word' => 'バルシーラ',
            'desc' => 'ふっとばす'
          },
          {
            'word' => 'ギラ',
            'desc' => '消し去る'
          },
          {
            'word' => 'モシャス',
            'desc' => '能力をコピーする'
          }
        ];

全取得メソッドは、上記のようにスカラで受け取ればリストのリファレンスに、@set = とリストで受け取ればリストで受け取れます。

実際は $rs->hash() を while で回すことが多いので全取得はあまり使いませんが、map_hashes() という、あるフィールドをキーにしたハッシュの構造体にして返すというのが便利なのでたまに使います。

my $rs = $db->query('select word, classmap.name, desc 
                     from spell left join classmap 
                     on spell.class = classmap.id
                     where class >= 4');

warn Dumper { $rs->map_hashes('word') };

# 結果 ---

$VAR1 = {
          'ラリホー' => {
                          'desc' => '眠らせる',
                          'name' => '防御'
                        },
          'マホトーン' => {
                            'desc' => '相手の呪文を無効にする',
                            'name' => '防御'
                          },
          'ルーラ' => {
                        'desc' => '移動する',
                        'name' => '移動'
                      }
        };

色物的ですが、DBIx::XHTML_Table や Text::Table というモジュールとの連携も組み込まれていて、html()

print $db->query('select word as "呪文", desc as "説明" from spell limit 2')->html;

で以下みたいな HTML テーブルを書き出せたり(内部で DBIx::XHTML_Table を使っています)

<table>
        <thead>
        <tr>
                <th>呪文</th>
                <th>説明</th>
        </tr>
        </thead>
        <tbody>
        <tr>
                <td>メラ</td>
                <td>火の玉をぶつける</td>
        </tr>
        <tr>
                <td>ギラ</td>
                <td>炎</td>
        </tr>
        </tbody>
</table>

また、text()

print $db->query('select class, word from spell where class = ?', 4)->text('table');

で以下みたいなアスキーテーブルを書き出せたり(内部で Text::Table を使っています)します。日本語の数え方の問題でアスキーテーブルはちょっと崩れてしまいますが。おまけ機能的な感じですね。

class |      word      
------+----------------
  4   | ラリホー   
  4   | マホトーン

このように、DBIx::Simple の結果オブジェクトは直感的にデータを引き出せて便利です。しかし、DBIx::Simple がオススメなのはこれだけじゃなくて、SQL::Abstract という名モジュールが組み込まれていることにあります。

SQL::Abstract

結果オブジェクトを取得するのに、上記例では query() を使っていましたが、その代わりに select()insert()update()delete() というそのまんまなメソッドを使うことができます。SQL 文とプレースホルダ形式よりだいぶわかりやすいです。裏側で、SQL::Abstract が Perl の構造式を SQL 文に変換してくれます。

※ 現在のバージョン(1.27)では、select()、insert()、update()、delete() を使う前に $db->abstract; が必要です。バグレポート出したのですが最近返事があったので近く作者が修復してくれるはずです。(追記:修復済みです)

使い方ですが、例えば

my $rs = $db->query('select word, desc from spell 
                     where word (class = ? OR class = ?) and 
                     desc like ?', 2, 3, "%回復%");

の代わりに、以下のようにできます。

my $rs = $db->select('spell', ['word', 'desc'], {
    class => [ 2, 3 ],
    desc => { like => '%回復%' },
});

insert などは、abstract メソッドの方がよっぽどわかりやすいです。

my $rs = $db->query('insert into spell (word, class, desc) values (?, ?, ?)', 
                    "パルプンテ", 6, "何が起こるかわからない");

は insert() だとこう書けます。

my $rs = $db->insert('spell', {
    word  => "パルプンテ",
    class => 6,
    desc  => "何が起こるかわからない",
});

他の例については、Examples をながめるとつかめると思います。

More

LEFT JOIN などのリレーションをもっと透過的に操作したいとか、日付型フィールドは日付オブジェクトとして扱いたい、など、もっと高度なモデルが欲しい場合も多いと思います。

そんなときは今なら DBIx::Class という うってつけのものがあります。次回はこれを取り上げる予定です。DBIx::Class も同じ SQL::Abstract を使っており、DBIx::Simple から DBIx::Class への移行は比較的簡単です。