ユーティリティ

最後に実行されたSQLを取得する - last_sql

最後に実行されたSQLを取得するにはlast_sqlメソッドを使用します。

my $last_sql = $dbi->last_sql;

デバッグを行うときに便利です。

実行されたSQLを確認する

どのようなSQLが実際に実行されたのかを確認するにはDBIX_CUSTOM_DEBUGという環境変数に真の値(たとえば1)を設定します。標準エラー出力に実行されたSQLとバインドされた値が出力されます。

$ENV{DBIX_CUSTOM_DEBUG} = 1

ヘルパーメソッドを登録する - helper

ヘルパーメソッドを登録するにはhelperメソッドを使用します。

$dbi->helper(
  find_or_create   => sub {
    my $self = shift;
    ...
  }
);

登録したヘルパーメソッドはDBIx::Customオブジェクトから直接呼び出すことができます。

$dbi->find_or_create;

パラメーターをマージする - merge_param

二つのパラメータをマージするにはmerge_paramメソッドを使用します。

my $param = $dbi->merge_param({key1 => 1}, {key1 => 1, key2 => 2});

次のような新しいパラメータを取得します。同一のキーを含むときは、値は配列のリファレンスにマージされます。

{key1 => [1, 1], key2 => 2}

DBIx::Customオブジェクトを生成する - new

DBIx::Customオブジェクトを生成するにはnewメソッドを使用します。

my $dbi = DBIx::Custom->new(
  dsn => "dbi:mysql:database=dbname",
  user => 'ken',
  password => '!LFKD%$&',
  option => {mysql_enable_utf8 => 1}
);

connectメソッドとの違いは、newメソッドは接続を行わないということです。オブジェクトを、生成した後に接続したい場合は、connectメソッドを呼んでください。

$dbi->connect;

データベースハンドルを取得する - dbh

データベースハンドルを取得するにはdbhメソッドを使用します。

my $dbh = $dbi->dbh;

もしconnectorにコネクションマネージャが設定されていた場合は、データベースハンドルはコネクションマネージャ経由で取得します。

テーブルの存在を確認する

データベースにテーブルがすでに存在しているかを確認したい場合があります。そのような場合は、each_columnメソッドを使って、指定するテーブルがあるかを調べるのが簡単です。each_columnメソッドはすべての列を取得するメソッドですが、テーブルの情報も引数で受け取りますので、このような使い方もできます。

my $table_exists;
$dbi->each_column(sub {
  my ($dbi, $table, $column) = @_;
  $table_exists = 1 if $table eq 'book';
});

Mojolicious::LiteとDBIx::CustomとDBD::mysqlを使ってnode.jsのようにノンブロッキングでDBにアクセスする

DBIx::Customがまだ実験的な段階ですが、AnyEventの力を借りて、データベースに対してブロッキングせずにクエリを投げることができるようになりました。

5秒間待機するSQLを投げているのですが、複数のリクエストがあっても、ブロックせずにリクエストを処理することができます。これで、nodel.jsで行いたいようなデータベースに対してノンブロッキングでクエリを投げるということが、Perlでも簡単にできるようになりそうです。

use Mojolicious::Lite;
use EV;
use DBIx::Custom;

my $dbi = DBIx::Custom->connect(
  dsn => 'dbi:mysql:database=usertest',
  user => 'root'
);

$dbi->async_conf({
  prepare_attr => {async => 1},
  fh => sub { shift->dbh->mysql_fd }
});

get '/' => sub {
  my $self = shift;
  
  $self->render_later;
  $dbi->select('SLEEP(5), 3', async => sub {
    my ($dbi, $result) = @_;
    my $row = $result->fetch_one;
    $self->render(text => $row->[1]);
  });
};

app->start;

仕組みはこんな感じ。まずMojoliciousというのはノンブロッキングなHTTPサーバーを持っていて、内部実装としては中でIOのループをぐるぐると回している。MojoliciousはEVがあるときは、自前のMojo::IOLoopからEVを使ったIOループに切り替えることができる。DBIx::Customの中ではAnyEventを使って、イベントを登録している。EVを利用しているから、EVにイベントをどんどん登録する。Mojoliciousも自分のイベントをEVにどんどん登録する。ぐるぐるぐるーっと回って、IO待ちが終わったものから順に処理してくれる。

ただしこれは、まだ超実験的なだんかいなので、不具合がないかを試してみていただけるとうれしいです。

廃止予定のタグの解析機能を停止してパフォーマンスを上げる

廃止予定の機能が削除されるまでは、後方互換性の維持の方針のために、DBIx::Customのパフォーマンスはデフォルトでは最適化されていません。以前はタグという機能を実装していたのですが、パフォーマンス上の理由のために、この機能は名前つきプレースホルダーに置き換えられました。

タグの解析機能を停止させるとDBIx::Customのパフォーマンスを少し上げることができます。

$ENV{DBIX_CUSTOM_TAG_PARSE} = 0;

将来的には、他の多くの廃止予定の機能が取り除かれることで、パフォーマンスが全体的に若干向上します。

パラメーターをマッピングする - mapper

URLから受け取ってきたパラメータを、データベース用のパラメータに変換するということをすることが多いと思います。このような場合はmapperメソッドを使ってDBIx::Custom::Mapperオブジェクトを作成してそれを利用します。

# マッパーの生成
my $mapper = $dbi->mapper(param => $param);

パラメータは次のようなものだとします。

{
  t => 'Perl',
  p => 1000,
  a => 'Ken',
  b => 'XXX'
}

mapメソッドを使うとパラメータのマッピングを行うことができます。

# マッピング
my $new_param = $mapper->map(
  t => [title => '%<value>'], # キーと値をマッピング
  p => [price => '<value>'], # キーのみをマッピング
  a => [a => '%<value>%'] # 値のみをマッピング
);

書式は以下のようになります。

元のキー名 => [新しいキー名 => 新しい値]

元の値は<value>というフォーマットで表現できます。これを使って%Perlなどの新しい値を作成することもできます。

次のようなパラメータにマッピングされます。

{
  title => '%Perl',
  price => 1000,
  a => '%Ken%'
}

mapメソッドで指定されていないキーについてはマッピングされません。このサンプルではbというキーはマッピングされません。

値をもう少し柔軟に変更したい場合はサブルーチンのリファレンスを利用することができます。第一引数で元の値を取得できるので新しい値を返却します。

元のキー名 => [新しいキー名 => sub { return '%' . $_[0] . '%' }]

パラメータのマッピングの条件

デフォルトのマッピングの条件は次のとおりです。

値が定義されていて、長さがある

ですから以下のようなパラメータはマッピングされます。

title => 'Perl',

けれども、空の文字列や未定義値やキー自体が存在しないものはマッピングされません。これはマッピングがWebアプリケーションを想定しているので、このような仕様になっています。

title => '',
title => undef

この条件を変更するにはcondition属性を利用します。任意のサブルーチンのリファレンスか、特別な値として「length」「defined」「exists」という文字列を設定することができます。

$mapper->condition('length'); # デフォルトと同じ
$mapper->condition('defined'); # 値が定義されている
$mapper->condition('exists'); # キーが存在する
$mapper->condition(sub { defined $_[0] }); #任意のサブルーチンのリファレンス

mapメソッドで次のようにキーごとに個別に設定することも可能です。

$mapper->map(
  古いキー => [新しいキー => 新しい値, {condtion => 'defined'}],
);

関連情報