The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

DBIx::Custom::Guide::Ja - DBIx::Customの日本語ガイド

ガイド

DBIx::Customはデータベースへのクエリの発行を簡単に行うための クラスです。DBIx::ClassDBIx::Simpleと同じように DBIのラッパクラスになっています。DBIx::Classよりも簡単に、 DBIx::Simpleよりもはるかに柔軟なことを行うことができます。

DBIx::CustomはO/Rマッパーではありません。O/Rマッパーは 便利ですが、O/Rマッパのたくさんの文法を覚える必要があります。 また、O/Rマッパによって生成されたSQLは非効率なことがありますし、 複雑なSQLを生成することができないので、 生のSQLを発行しなければならない場合がたくさんあります。

DBIx::CustomはO/Rマッパとは対照的な設計が行われています。 DBIx::Customの主な目的は、SQLを尊重しつつ、DBIだけでは とてもめんどうな作業を簡単にすることです。もしSQLについて 多くの知識を持っているならば、DBIx::Customでそのまま 活用することができます。

DBIx::Customの仕組みを簡単に説明しておきましょう。 DBIx::Customでは、タグと呼ばれるものを SQLの中に埋め込むことができます。

    select * from book where {= title} and {=author};

{}で囲まれた部分がタグです。このSQLは実際に実行されるときには 次のようにプレースホルダに展開されます。

    select * from book where title = ? and author = ?;

これらの展開にはどのような意味があるのでしょうかと質問 されることかと思います。この簡単な仕組みの上に非常にたくさんの 有用で便利で使いやすい機能が構築されます。それは以下のようなものです。

1. プレースホルダのパラメータをハッシュリファレンスで指定

DBIをそのまま使うのであればプレースホルダのパラメータは配列 で指定する必要があります。

    $sth->execute(@bind);

DBIx::Customを利用するのであればハッシュリファレンスで指定すること できます。

    my $param = {title => 'Perl', author => 'Ken'};
    $dbi->execute($sql, $param);
2. パラメータのフィルタリング

たとえば、日付の列は、Perlで扱うときにはTime::Pieceなどの日付オブジェクト で扱い、データベースに格納するときはデータベースの日付型に変換したい と思うのではないでしょうか。またデータベースから取り出すときは データベースの日付型から日付オブジェクトに変換したと思うのでは ないでしょうか。

このようなときはフィルタ機能を使うことができます。

まずフィルタを登録します。

    $dbi->register_filter(
        tp_to_date => sub {
            ...
        },
        date_to_tp => sub {
            ...
        }
    );

次にテーブルの各列にこのフィルタを適用します。

    $dbi->apply_filter('book',
        'issue_date' => {out => 'tp_to_date', in => 'date_to_tp'}
    );

outはPerlからデータベースに保存する方向、inはデータベースからPerlに取得する方向です。

SQLを発行するときにテーブルの指定を行えば、自動的にこのフィルタが適用されます。

    $dbi->execute($sql, $param, table => 'book');
3. 選択的な検索条件

生のDBIを利用しているとき一番たいへんなのは選択的な検索条件を作成したいときです。

たとえば、検索条件にtitleとauthorが指定された場合は次のSQLを

    select * from book where title = ? and author = ?;

titleだけの場合は次のSQLを

    select * from book where title = ?;
    

authorだけの場合は次のSQLを発行した場合を考えましょう。

    select * from book where author = ?;

これはとても大変な作業なので、通常はSQL::Abstractを動的に生成してくれる モジュールを利用することになります。

DBIx::Customはさらに簡単で便利な方法を用意しています。

    my $where = $dbi->where;
    $where->param({title => 'Perl'});
    $where->clause(
        ['and', '{= title}', {'= author'}]
    );

    my $sql = "select * from book $where";

詳しい説明は後ほど行いますが、上記のように記述すれば、 DBIx::Customでは選択的な検索条件を持つWhere句を生成することができます。 検索条件が入れ子になった構造やorについても対応しています。

4. 挿入、更新、削除、選択を行うためのメソッド

DBIx::CustomではSQLをさらに簡単に実行するための メソッドも提供しています。 insert(), update(), delete(),select()などの シュガーメソッドを使って、挿入、更新、削除、選択という操作を行うことが できます。

    my $param = {title => 'Perl', author => 'Ken'};
    $dbi->insert(table => 'book', param => $param);
5. テーブル単位の操作の登録

テーブルに対して操作を登録することができます。これによって テーブル名を繰り返し指定する必要がなくなり、ソースコードの 見通しが良くなります。

    $dbi->talbe('book',
        list => sub {
            ...
        },
        list_somethin => sub {
            
        }
    );

登録したメソッドはそのまま利用することができます。

    $dbi->table('book')->list;

通常O/Rマッパはテーブルに対応するクラスを作成しなければ ならないことが多いですが、DBIx::Customではこの作業を簡便に しており、上記のように登録することができます。

DBIx::CustomDBIを補うとても便利なモジュールです。 興味をもたれた方は、この後で詳しい解説を行いますので、 ご覧になってみてください。

1. データベースへの接続

まずDBIx::Customを読み込みます。

    use DBIx::Custom;

DBIx::Customオブジェクトを生成し、データベースに接続するには connect()メソッドを使用します。

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

data_sourceはデータベースシステムに応じたフォーマットで 指定する必要があります。以下にデータベースごとのフォーマット 方法のサンプルを掲載しておきます。

MySQL

    "dbi:mysql:database=$database"
    "dbi:mysql:database=$database;host=$hostname;port=$port"

SQLite

    "dbi:SQLite:dbname=$database"
    "dbi:SQLite:dbname=:memory:"

PostgreSQL

    "dbi:Pg:dbname=$dbname"

Oracle

    "dbi:Oracle:$dbname"
    "dbi:Oracle:host=$host;sid=$sid"

ODBC(Microsoft Access)

    "dbi:ODBC:driver=Microsoft Access Driver (*.mdb);dbq=hoge.mdb"

ODBC(SQL Server)

   "dbi:ODBC:driver={SQL Server};Server=(local);database=test;Trusted_Connection=yes;AutoTranslate=No;"

また認証が求められる場合は、userpasswordユーザ名と パスワードを指定する必要があります。

DBIx::CustomDBIのラッパです。 DBIのデータベースハンドルはdbhで取得することができます。

    my $dbh = $dbi->dbh;

DBIx::Customではデータベースハンドル属性にはデフォルトで次のものが設定されます。

    $dbi->dbh->{RaiseError} = 1;
    $dbi->dbh->{PrintError} = 0;
    $dbi->dbh->{AutoCommit} = 1;

この設定を行っているので、致命的なエラーが起こると、 例外が発生しプログラムは終了します。 またクエリが発行されると自動的にコミットされます。

2. 挿入、更新、削除、選択のためのメソッド

DBIx::Customは、 insert()update()delete()select() のような挿入、更新、削除、選択を行うためのメソッドを持っています。 簡単なことをを行うのであれば、SQLを自分で記述する必要がありません。

データの挿入 insert()

データベースにデータを挿入するにはinsert()を使用します。

    $dbi->insert(table  => 'book',
                 param  => {title => 'Perl', author => 'Ken'});

tableにはテーブル名、paramには挿入したいデータを指定します。

次のSQLが発行されます。

    insert into (title, author) values (?, ?);

データの更新 update()

データベースのデータを更新するには、update()を使用します。

    $dbi->update(table  => 'book', 
                 param  => {title => 'Perl', author => 'Ken'}, 
                 where  => {id => 5});

tableにはテーブル名、paramには挿入したいデータ、whereには 条件を指定します。

次のSQLが発行されます。

    update book set title = ?, author = ?;

updateメソッドは安全のため where句のないSQLを発行することを許可していません。 もしすべての行を更新したい場合は update_all()を使用してください。

    $dbi->update_all(table  => 'book', 
                     param  => {title => 'Perl', author => 'Ken'});

データの削除 delete()

データベースのデータを1件削除するには、delete()を使用します。

    $dbi->delete(table  => 'book',
                 where  => {author => 'Ken'});

tableにはテーブル名、whereには条件を指定します。

次のSQLが発行されます。

    delete from book where id = ?;

deleteメソッドは安全のため where句のないSQLを発行することを許可していません。 もしすべての行を削除したい場合は delete_all()を使用してください。

    $dbi->delete_all(table  => 'book');

データの選択 select()

行を選択するにはselect()を使用します。

    my $result = $dbi->select(table => 'book');

tableだけを指定して、他の条件を指定しない場合は次のSQLが発行されます。

    select * from book;

select()メソッドの戻り値はDBIx::Custom::Result オブジェクトです。行をフェッチするにはfetch()を使用します。

    while (my $row = $result->fetch) {
        my $title  = $row->[0];
        my $author = $row->[1];
    }

DBIx::Custom::Resultについてはこの後"3. 行のフェッチ" in 3. 行のフェッチで詳しく扱います。

さまざまなselect()の使い方を見ていきましょう。 次のselectは行の名前とwhere句を指定したものです。

    my $result = $dbi->select(
        table  => 'book',
        column => ['author',  'title'],
        where  => {author => 'Ken'}
    );

columnには列名を、whereには条件を指定することができます。 次のSQLが発行されます。

    select author, title from book where author = ?;

テーブルを結合したい場合ははrelationにテーブルの 関係を記述します。

    my $result = $dbi->select(
        table    => ['book', 'rental'],
        where    => {book.name => 'Perl'},
        relation => {'book.id' => 'rental.book_id'}
    );

bookテーブルのid列とrentalテーブルのbook_idが関連付けられます。 次のSQLが発行されます。

    select * from book, rental where book.name = ? and book.id = rental.book_id;

SQL文の末尾に文字列を追加したい場合は<append>を使用します。

    my $result = $dbi->select(
        table  => 'book',
        where  => {author => 'Ken'},
        append => 'for update',
    );

次のSQLが発行されます。

    select * book where author = ? for update;

またappendは、selectだけでなくinsert()update()update_all() delete()delete_all()select()で使用することもできます。

SQLの実行 execute()

任意のSQLを実行するにはexecuteメソッドを使用します。

    $dbi->execute("select * from book;");

execute()DBIx::Customの根幹のメソッドでありタグを展開します。

    $dbi->execute(
        "select * from book {= title} and {= author};"
        param => {title => 'Perl', author => 'Ken'}
    );

上記のタグを含んだSQLは次のように展開されます。

    select * from book title = ? and author = ?;

SQLが実行されるときにプレースホルダ(?)に対応する位置にtitleとauthor の値がが自動的に埋め込まれます。

タグについては"5. タグ" in 5. タグで詳しく解説しますが、 ひとつの注意点があります。 タグを展開するために{}は予約語になっています。 もし利用したい場合は直前に\をおいてエスケープを行う必要があります。

    $dbi->execute("... \\{ ... \\} ...");

\自体がPerlのエスケープ文字ですので、二つ必要になるという点に注意してください。

またexecuteのキュートな機能として、SQLの最後にセミコロンをおかなくても かまいません。

    $dbi->execute('select * from book');

3. 行のフェッチ

select()メソッドの戻り値はDBIx::Custom::Resultオブジェクトです。 DBIx::Custom::Resultには行をフェッチするためのさまざまなメソッドが 用意されています。

1行づつフェッチ(配列) fetch()

一行フェッチして配列のリファレンスに格納するにはfetch()を使用します。

    while (my $row = $result->fetch) {
        my $title  = $row->[0];
        my $author = $row->[1];
    }

whileループを使って、すべての行を取得することができます。

最初の行だけフェッチ(配列) fetch_first()

一行だけフェッチして配列のリファレンスに格納するにはfetch_first() を使用します。

    my $row = $result->fetch_first;

一行のフェッチが終わった後はそれ以上フェッチできなくなります。 内部的には1行のフェッチが終わった後に ステートメントハンドルのfinish()が実行されます。

複数行を順にフェッチ(配列) fetch_multi()

複数行をフェッチして配列のリファレンスを要素に持つ 配列のリファレンスに格納するにはfetch_multi()を使用します。

    while (my $rows = $result->fetch_multi(2)) {
        my $title0   = $rows->[0][0];
        my $author0  = $rows->[0][1];
        
        my $title1   = $rows->[1][0];
        my $author1  = $rows->[1][1];
    }

引数には取り出したい行数を指定します。

指定した行を格納した次のようなデータを取得できます。

    [
        ['Perl', 'Ken'],
        ['Ruby', 'Mark']
    ]

すべての行をフェッチ(配列) fetch_all

すべての行をフェッチして配列のリファレンスを要素に持つ 配列のリファレンスに格納するにはfetch_all()を使用します。

    my $rows = $result->fetch_all;

すべての行を格納した次のようなデータを取得できます。

    [
        ['Perl', 'Ken'],
        ['Ruby', 'Mark']
    ]

1行づつフェッチ(ハッシュ) fetch_hash()

一行フェッチしてハッシュのリファレンスに格納するにはfetch_hash()を使用します。

    while (my $row = $result->fetch_hash) {
        my $title  = $row->{title};
        my $author = $row->{author};
    }

最初の行だけフェッチ(ハッシュ) fetch_hash_first()

一行だけフェッチしてハッシュのリファレンスに格納するには fetch_hash_first()を使用します。

    my $row = $result->fetch_hash_first;

一行のフェッチが終わった後はそれ以上フェッチできなくなります。 内部的には1行のフェッチが終わった後に ステートメントハンドルのfinish()が実行されます。

複数行を順にフェッチ(ハッシュ) fetch_hash_multi()

複数行をフェッチしてハッシュのリファレンスを要素に持つ 配列のリファレンスに格納するにはfetch_hash_multi() を使用します。

    while (my $rows = $result->fetch_hash_multi(5)) {
        my $title0   = $rows->[0]{title};
        my $author0  = $rows->[0]{author};
        my $title1  = $rows->[1]{title};
        my $author1 = $rows->[1]{author};
    }

引数には取り出したい行数を指定します。

指定した行を格納した次のようなデータを取得できます。

    [
        {title => 'Perl', author => 'Ken'},
        {title => 'Ruby', author => 'Mark'}
    ]

すべての行をフェッチ(ハッシュ) fetch_hash_all()

すべての行をフェッチしてハッシュのリファレンスを要素に持つ 配列のリファレンスに格納するにはfetch_hash_all() を使用します。

    my $rows = $result->fetch_hash_all;

すべての行を格納した次のようなデータを取得できます。

    [
        {title => 'Perl', author => 'Ken'},
        {title => 'Ruby', author => 'Mark'}
    ]

ステートメントハンドル sth()

ステートメントハンドルに直接アクセスしたい場合は <sth>で取得することができます。

    my $sth = $result->sth;

フェッチのパフォーマンスが用件を満たさないときには、 ステートメントハンドルから 利用できる速度の速いメソッドを利用することができます。

4. フィルタリング

データベースにデータを登録するときやデータベースからデータを取得する ときに自動的に値の変換を行いたい場合が多いと思います。 たとえば、日付を表現する列の場合は、 データベースに登録する場合はTime::Pieceオブジェクトから データベースの日付のフォーマットに、 データベースからデータを取得するときは、その逆を行えると便利です。

フィルタの登録 register_filter()

フィルタを登録するにはregister_filter()を使用します。

    $dbi->register_filter(
        # Time::Piece object to DATE format
        tp_to_date => sub {
            my $date = shift;

            return '0000-00-00' unless $tp;
            return $tp->strftime('%Y-%m-%d');
        },
        
        # DATE to Time::Piece object
        date_to_tp => sub {
            my $date = shift;

            return if $date eq '0000-00-00';
            return Time::Piece->strptime($date, '%Y-%m-%d');
        },
    );

登録したフィルタはapply_filter()などで利用することができます。

フィルタの適用 apply_filter()

作成したフィルタを適用するには、apply_filter()を使用します。

    $dbi->apply_filter('book',
        issue_date => {out => 'tp_to_date', in => 'date_to_tp'},
        first_issue_date => {out => 'tp_to_date', in => 'date_to_tp'}
    );

第一引数はテーブル名です。第二引数以降は、列名とフィルタルールのペアを記述します。 フィルタルールのoutには、データベースにデータを送信するときに適用するフィルタを、 フィルタルールのinには、データベースからデータを取得するときに適用するフィルタを 記述します。outがデータベースに送信する方向、inがデータベースから取り出す方向です。 フィルタには、register_filterで登録したフィルタ名の他に、コードリファレンスを 指定することもできます。

    issue_date => {out => sub { ... }, in => sub { ... }}

適用されたフィルタはinsert()update()update_all()delete()delete_all()select()で有効になります。

    my $tp = Time::Piece->strptime('2010/10/14', '%Y/%m/%d');
    my $result = $dbi->select(table => 'book', where => {issu_date => $tp});

データベースにデータが送信されるときに、Time::Pieceオブジェクトは データベースの日付のフォーマット「2010-10-14」に変換されます。

また逆にデータをフェッチするときには、データベースの日付のフォーマットは タイムピースオブジェクトに変換されます。

    my $row = $resutl->fetch_hash_first;
    my $tp = $row->{issue_date};

このような自動的に実行されるフィルタを登録できることがDBIx::Customの 特徴のひとつです。

apply_filter()で適用されたフィルタはテーブル名をを含む列名に対しても有効です。

    $dbi->select(
        table => 'book',
        where => {'book.title' => 'Perl', 'book.author' => 'Ken'}
    );

テーブルを区別する必要があるときに便利な機能です。

個別のフィルタの適用 filter

apply_filter()を使って最初にすべてのテーブルの列について フィルタを定義することもできますが、 個別にフィルタを適用することもできます。 個別のフィルタはapply_filter()で適用したフィルタを上書きます。 個別のフィルタはSQLのasを使って、列の別名を作成する必要がある場合に活躍します。

データベースに送信する場合に、個別のフィルタを適用するには、各メソッドの filterオプションを使用します。個別のフィルタは、insert()update()update_all()delete()delete_all()select()execute() で使用することができます。

insert()の例を示します。

    $dbi->insert(
        table => 'book',
        param => {issue_date => $tp, first_issue_date => $tp},
        filter => {issue_date => 'tp_to_date', first_issue_date => 'tp_to_date'}
    );

execute()の例を示します。

my $sql = <<"EOS"; select YEAR(issue_date) as issue_year from book where YEAR(issue_date) = {? issue_year} EOS

    my $result = $dbi->execute(
        $sql,
        param => {issue_year => '2010'},
        filter => {issue_year => 'tp_to_year'}
    );

これはfilterを使う良くある例です。issue_dateの変換についてはapply_filter() で登録してあるのですが、新しく作成した列であるissue_yearについては、 何の変換も登録されていません。ですので、個別にフィルタを設定しています。

また反対に行をフェッチするときにも個別のフィルタを適用することができます。 フィルタを適用するには、 DBIx::Custom::Resultクラスのfilterメソッドを使用します。

    $result->filter(issue_year => 'year_to_tp');

頻繁に利用するのであれば、個別に登録するよりもapply_filter()で登録 しておいたほうが便利でしょう。apply_filter()は存在しない列に対しても フィルタを適用できるからです。

    $dbi->apply_filter('book',
        'issue_year' => {out => 'tp_to_year', in => 'year_to_tp'}
    );

最終出力のためのフィルタリング end_filter()

DBIx::Custom::Resultではさらに最後にもう一度、フィルタを追加で 登録することができます。たとえばHTMLに出力したい場合に、Time::Piece オブジェクトから読みやすい記述に変換することができます。 最後のフィルタを登録するには、end_filter()を使用します。

    $result->end_filter(issue_date => sub {
        my $tp = shift;
        
        return '' unless $tp;
        return $tp->strftime('%Y/%m/%d %h:%m:%s (%a)');
    });

日付を見やすい形にフォーマットすることができます。

フィルタはフェッチを行う前に登録しておく必要があることに 注意してください。

    $result->filter(...);
    $result->end_filter(...);
    my $row = $result->fetch_hash_first;

列の情報を元にフィルタを適用する each_column()

日付型の列は手動で設定しなくても、自動的に設定できると便利です。 このためにデータベースのテーブルの列のすべての情報を 順番に処理するためのeach_column()があります。

    $dbi->each_column(
        sub {
            my ($self, $table, $column, $info) = @_;
            
            my $type = $info->{TYPE_NAME};
            
            my $filter = $type eq 'DATE'     ? {out => 'tp_to_date', in => 'date_to_tp'}
                       : $type eq 'DATETIME' ? {out => 'tp_to_datetime', in => 'datetime_to_tp'}
                                             : undef;
            
            $self->apply_filter($table, $column, $filter)
              if $filter;
        }
    );

each_columnはコールバックを受け取ります。コールバックの引数は 順番にDBIx::Customオブジェクト、テーブル名、列名、列の情報です。 列の型名の情報をもとに自動的に、フィルタを適用しています。

ひとつの注意点としてコールバックの中から、コールバックの外側 の変数を参照しないように注意してください。each_columnは 高々1回だけ実行されるだけなので、ほとんどの場合問題ありませんが、 循環参照によるメモリリークが発生してしまう可能性を持っているからです。

5. タグ

タグの機能

DBIx::CustomはSQLの中にタグを埋め込む機能を持っています。

    select * from book where {= title} and {like author};

{= title}と{like author}の部分がタグです。タグは次のような形式 を持ちます。

    {タグ名 引数1 引数2 ...}
    

タグは{で始まり、}で終わります。最初の{とタグ名の間 には空白を挿入しないよう注意してください。

タグの機能のために{}は予約語になっています。 もし利用したい場合は直前に\をおいてエスケープを行う必要があります。

    select from book \\{ ... \\}

\自体がPerlのエスケープ文字ですので、 エスケープする場合は\が二つ必要になるという点に注意してください。

上記のタグはSQLが実行される前に次のSQLに展開されます。

    select * from book where title = ? and author like ?;

タグを含むSQLを実行するにはexecute()を使用します。

    my $sql = "select * from book where {= author} and {like title};"
    $dbi->execute($sql, param => {title => 'Perl', author => '%Ken%'});

paramオプションを使って、プレースホルダに埋め込みたい値を ハッシュリファレンスで指定することができます。

他のメソッドと同様にexecute()においてもfilterを指定することができます。

    $dbi->execute($sql, param => {title => 'Perl', author => '%Ken%'}
                  filter => {title => 'to_something');

executeのひとつの注意点としてはapply_filter()で適用されたフィルタ はデフォルトでは有効ではないということに注意してください。 apply_filter()で適用されたフィルタを有効にするには、 tableを指定する必要があります。

    $dbi->execute($sql, param => {title => 'Perl', author => '%Ken%'}
                  table => ['book']);

タグ一覧

DBIx::Customでは次のタグが使用可能です。

このようにタグを使ってSQL文を表現するのがDBIx::Customの 特徴です。以下のタグが利用可能です。

?

?タグは以下のように展開されます。

    {? NAME}    ->   ?

=

=タグは以下のように展開されます。

    {= NAME}    ->   NAME = ?

<>

<>タグは以下のように展開されます。

    {<> NAME}   ->   NAME <> ?

<

<タグは以下のように展開されます。

    {< NAME}    ->   NAME < ?

>

>タグは以下のように展開されます。

    {> NAME}    ->   NAME > ?

>=

>=タグは以下のように展開されます。

    {>= NAME}   ->   NAME >= ?

<=

<=タグは以下のように展開されます。

    {<= NAME}   ->   NAME <= ?

like

likeタグは以下のように展開されます。

    {like NAME}   ->   NAME like ?

in

inタグは以下のように展開されます。プレースホルダの 数を引数で指定する必要があることに注意してください。

    {in NAME COUNT}   ->   NAME in [?, ?, ..]

insert_param

insert_paramタグは以下のように展開されます。

    {insert_param NAME1 NAME2}   ->   (NAME1, NAME2) values (?, ?)

update_param

update_paramタグは以下のように展開されます。

    {update_param NAME1 NAME2}   ->   set NAME1 = ?, NAME2 = ?

同名の列の扱い

同名の列を含むタグがある場合にも、SQLを実行することができます。 たとえば、二つの日付で比較しなければならない場合を 考えて見ましょう。

    my $sql = "select * from table where {> date} and {< date};";

このような場合は対応するパラメータの値を配列のリファレンスにします。

    my $dbi->execute($sql, param => {date => ['2010-10-01', '2012-02-10']});

タグの追加 register_tag()

DBIx::Customではタグを独自に追加することができます。 タグを追加するにはregister_tag()を使用します。

    $dbi->register_tag(
        '=' => sub {
            my $column = shift;
            
            return ["$column = ?", [$column]];
        }
    );

ここではデフォルトの=タグがどのように実装されているかを示しています。 タグを登録する関数の引数はタグの中に書かれた引数になります。

    {タグ名 引数1 引数2}

=タグの場合は

    {= title}

という形式ですから、サブルーチンにはtitleというひとつの列名がわたってきます。

サブルーチンの戻り値には次の形式の配列のリファレンスを返す必要があります。

    [
        展開後の文字列,
        [プレースホルダに埋め込みに利用する列名1, 列名2, ...]
    ]

一つ目の要素は展開後の文字列です。この例では

    'title = ?'

を返す必要があります。

二つ目の要素はプレースホルダに埋め込みに利用する列名を含む配列の リファレンスです。今回の例では

    ['title']

を返す必要があります。複数のプレースホルダを含む場合は、この部分が 複数になります。insert_paramタグやupdate_paramタグは この部分が実際複数になっています。

上記を合わせると

    ['title = ?', ['title']]
    

を返す必要があるということです。

タグの実装の他のサンプルはDBIx::Custom::Tagのソースコード をご覧になってみてください。

6. Where句の動的な生成

Where句の動的な生成 where()

複数の検索条件を指定して、検索を行いたい場合があります。 次の3つのケースのwhere句を考えてみましょう。 下記のようなwhere句が必要になります。

titleの値だけで検索したい場合

    where {= title}

authorの値だけで検索したい場合

    where {= author}

titleとauthorの両方の値で検索したい場合

    where {= title} and {=author}

DBIx::Customでは動的なWhere句の生成をサポートしています。 まずwhere()DBIx::Custom::Whereオブジェクトを生成します。

    my $where = $dbi->where;

次にclause()を使用してwhere句を記述します。

    $where->clause(
        ['and', '{= title'}, '{= author}']
    );

clauseの指定方法は次のようになります。

    ['or' あるいは 'and', タグ1, タグ2, タグ3]

第一引数にはorあるいはandを指定します。第二引数以降には 検索条件をタグを使って記述します。

clauseの指定は入れ子にすることもでき、さらに複雑な条件 を記述することもできます。

    ['and', 
      '{= title}', 
      ['or', '{= author}', '{like date}']
    ]

このようにclauseを設定した後にparamにパラメータを指定します。

    my $param => {title => 'Perl'};
    $where->param($param);

この例ではtitleだけがパラメータに含まれています。

この後to_string()を実行すると$paramに含まれるパラメータを満たす where句を生成することができます。

    my $where_clause = $where->to_string;

パラメータはtitleだけですので、次のようなwhere句が生成されます。

    where {= title}

またDBIx::Customは文字列の評価をオーバーロードして、to_string() を呼び出すようにしていますので、次のようにしてwhere句を生成することも できます。

    my $where_clause = "$where";

これはSQLの中にwhere句を埋め込むときにとても役立つ機能です。

同一の列名を含む場合

タグの中に同一の名前を持つものが存在した場合でも動的に where句を作成することができます。

たとえば、パラメータとして開始日付と終了日付を受け取ったことを 考えてみてください。

    my $param = {start_date => '2010-11-15', end_date => '2011-11-21'};

また開始日付と終了日付の片方だけや、どちらも受け取らない場合もあるかもしれません。

この場合は次のようなパラメータに変換することで対応することができます。

    my $p = {date => ['2010-11-15', '2011-11-21']};

値が配列のリファレンスになっていることに注目してください。このようにすれば 同名の列を含むタグに順番に埋め込むことができます。

    $where->clause(
        ['and', '{> date}', '{< date}']
    );
    $where->param($p);

また開始日付が存在しない場合は次のようなデータを作成します。

    my $p = {date => [$dbi->not_exists, '2011-11-21']};

DBIx::Customnot_existsでDBIx::Custom::NotExistsオブジェクトを 取得できます。これは対応する値が存在しないことを示すためのものです。

また終了日付が存在しない場合は次のようなデータを作成します。

    my $p = {date => ['2010-11-15']};

どちらも存在しない場合は次のようなデータを作成します。

    my $p = {date => []};

少し難しいので一番簡単に作成できるロジックを示しておきます。

    my @date;
    push @date, exists $param->{start_date} ? $param->{start_date}
                                            : $dbi->not_exists;
    push @date, $param->{end_date} if exists $param->{end_date};
    my $p = {date => \@date};

select()との連携

DBIx::Custom::Whereオブジェクトは select()whereに直接渡すことが できます。

    my $where = $dbi->where;
    $where->clause(...);
    $where->param($param);
    my $result = $dbi->select(table => 'book', where => $where);

あるいはupdate()delete()のwhereに指定することも可能です。

execute()との連携

execute()との連携です。SQLを作成するときに埋め込むことができます。

    my $where = $dbi->where;
    $where->clause(...);
    $where->param($param);

    my $sql = <<"EOS"
    select * from book;
    $where
    EOS

    $dbi->execute($sql, param => $param);

7. パフォーマンスの改善

クエリの作成

もしinsert()メソッドを使用してインサートを実行した場合、 必要なパフォーマンスを得られない場合があるかもしれません。 insert()メソッドは、SQL文とステートメントハンドルを 毎回作成するためです。

そのような場合は、queryオプションを指定することで、 クエリを取得することができます。

    my $query = $dbi->insert(table => 'book', param => $param, query => 1);

またcreate_query()メソッドを使って任意のSQLのクエリを作成 することもできます。

    my $query = $dbi->create_query(
        "insert into book {insert_param title author};";
    );

戻り値はDBIx::Custom::Queryオブジェクトです。 このオブジェクトはSQL文とパラメータバインド時の列名を 保持しています。またステートメントハンドルも保持しています。

    {
        sql     => 'insert into book (title, author) values (?, ?);',
        columns => ['title', 'author'],
        sth     => $sth
    }

クエリオブジェクトを使って繰り返し実行するにはexecute()を使用します。

    my $params = [
        {title => 'Perl',      author => 'Ken'},
        {title => 'Good days', author => 'Mike'}
    ];
    
    foreach my $param (@$paramss) {
        $dbi->execute($query, table => 'book', param => $input);
    }

executeメソッドの第一引数にクエリオブジェトを渡すことができます。 insert()メソッドよりも高速です。

注意点がいくつかあります。それはパラメータの数は必ず同じでなくてはならない ということです。最初に3つのパラメータだけを渡したのに、次の実行では 二つのパラメータを渡すと予期しない結果になります。それは 動的に生成されたSQLに含まれるプレースホルダの数が異なるからです。 またexecute()によっては自動的にはフィルタが有効にならないので、 tableを指定する必要のあることに注意してください。 本当に必要な場合だけ利用してください。

8. その他の機能

メソッドの登録

メソッドを登録するにはmethod()を使用します。

    $dbi->method(
        update_or_insert => sub {
            my $self = shift;
            # something
        },
        find_or_create   => sub {
            my $self = shift;
            # something
        }
    );

<method()>で登録したメソッドは DBIx::Customオブジェクトから直接呼び出すことができます。

    $dbi->update_or_insert;
    $dbi->find_or_create;

結果クラスの変更

必要ならば結果クラスを変更することができます。

    package MyResult;
    use base 'DBIx::Custom::Result';
    
    sub some_method { ... }

    1;
    
    package main;
    
    use MyResult;
    
    my $dbi = DBIx::Custom->connect(...);
    $dbi->result_class('MyResult');

キャッシング

タグの展開後のSQLはパフォーマンスの理由のためにキャッシュされます。 これはchaceで設定でき、デフォルトではキャッシュを行う設定です。

    $dbi->cache(1);

キャッシュ方法はcache_methodにメソッドを指定することで 変更することができます。 データの保存と取得のためのメソッドを定義します。

デフォルトでは次のようにメモリ上にキャッシュを行うものになっています。

    $dbi->cache_method(sub {
        sub {
            my $self = shift;
            
            $self->{_cached} ||= {};
            
            if (@_ > 1) {
                # Set
                $self->{_cached}{$_[0]} = $_[1] 
            }
            else {
                # Get
                return $self->{_cached}{$_[0]}
            }
        }
    });
    

第一はDBIx::Customオブジェクトです。 第二引数はタグの展開される前のSQLです。 第三引数はタグの展開後のSQLです。

自分で作成する場合は第三引数が存在した場合はキャッシュを設定し、 存在しなかった場合はキャッシュを取得する実装に してください。