1 contributor
=encoding utf8
=head1 NAME
DBIx::Custom::Guide::Ja - DBIx::Customのガイドブック
=head1 ガイド
L<DBIx::Custom>はSQLの実行を簡単に行うためのクラスです。
L<DBIx::Class>やL<DBIx::Simple>と同じように
L<DBI>のラッパクラスになっています。L<DBIx::Class>よりも簡単に、
L<DBIx::Simple>よりもはるかに柔軟なことを行うことができます。
L<DBIx::Custom>はO/Rマッパーではありません。O/Rマッパーは
便利ですが、O/Rマッパのたくさんの文法を覚える必要があります。
また、O/Rマッパによって生成されたSQLは非効率なことがありますし、
複雑なSQLを生成することができないので、
生のSQLを実行しなければならない場合がたくさんあります。
L<DBIx::Custom>はO/Rマッパとは対照的な設計が行われています。
L<DBIx::Custom>の主な目的は、SQLを尊重しつつ、L<DBI>だけでは
とてもめんどうな作業を簡単にすることです。もしSQLについて
多くの知識を持っているならば、L<DBIx::Custom>でそのまま
活用することができます。
L<DBIx::Custom>の仕組みを少しだけ説明しておきます。
L<DBIx::Custom>では、タグと呼ばれるものを
SQLの中に埋め込むことができます。
select * from book where {= title} and {=author};
{}で囲まれた部分がタグです。このSQLは実際に実行されるときには
次のようにプレースホルダに展開されます。
select * from book where title = ? and author = ?;
これらの展開にはどのような意味があるのでしょうかと質問
されるかもしれません。この簡単な仕組みの上に
便利な機能が実装されます。それは以下のようなものです。
=over 4
=item 1. プレースホルダにバインドする値をハッシュリファレンスで指定
L<DBI>を使うのであればプレースホルダにバインドする値は配列
で指定する必要があります。
$sth->execute(@bind);
L<DBIx::Custom>を利用するのであればハッシュリファレンスで指定すること
できます。
my $param = {title => 'Perl', author => 'Ken'};
$dbi->execute($sql, $param);
=item 2. 値のフィルタリング
L<DBIx::Custom>はフィルタリングの機能を提供します。
たとえば、日付の列は、Perlで扱うときにはC<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に取得する方向です。
多くのメソッドで自動的にこのフィルタが有効になります。
$dbi->insert(table => 'book', param => {issue_date => $tp});
=item 3. 選択的な検索条件
L<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 = ?;
これはとても大変な作業なので、通常はL<SQL::Abstract>を動的に生成してくれる
モジュールを利用することになります。
L<DBIx::Custom>はさらに簡単で便利な方法を用意しています。
# Whereオブジェクト
my $where = $dbi->where;
# 検索条件
$where->clause(
['and', '{= title}', {'= author'}]
);
# 必要な列を自動的に選択するための設定
$where->param({title => 'Perl'});
# SQLへのWhere句の埋め込み
my $sql = "select * from book $where";
詳しい説明は後ほど行いますが、上記のように記述すれば、
L<DBIx::Custom>では選択的な検索条件を持つWhere句を生成することができます。
検索条件が入れ子になった構造やorについても対応しています。
=item 4. 挿入、更新、削除、選択を行うためのメソッド
L<DBIx::Custom>では挿入、更新、削除、選択を行うための
メソッドを提供しています。
C<insert()>, C<update()>, C<delete()>,C<select()>などがあります。
my $param = {title => 'Perl', author => 'Ken'};
$dbi->insert(table => 'book', param => $param);
=item 5. テーブルのためのメソッドの登録
テーブルのためにメソッドを登録することができます。
$dbi->table('book')->method(
list => sub {
...
},
something => sub {
...
}
);
メソッドの利用です。
$dbi->table('book')->list;
多くのO/Rマッパではテーブルのためのクラスを作成する必要がありますが、
L<DBIx::Custom>では簡単です。
=back
L<DBIx::Custom>はとても便利です。
興味をもたれた方は、続きをご覧になってみてください。
=head2 1. データベースへの接続
L<DBIx::Custom>を読み込みます。
use DBIx::Custom;
データベースに接続するにはC<connect()>メソッドを使用します。
戻り値はL<DBIx::Custom>オブジェクトです。
my $dbi = DBIx::Custom->connect(
data_source => "dbi:mysql:database=bookstore",
user => 'ken',
password => '!LFKD%$&',
dbi_options => {mysql_enable_utf8 => 1}
);
C<data_source>はデータベースシステムに応じたものである必要があります。
以下はデータソースのサンプルです。
B<MySQL>
"dbi:mysql:database=$database"
"dbi:mysql:database=$database;host=$hostname;port=$port"
B<SQLite>
"dbi:SQLite:dbname=$database"
"dbi:SQLite:dbname=:memory:"
B<PostgreSQL>
"dbi:Pg:dbname=$dbname"
B<Oracle>
"dbi:Oracle:$dbname"
"dbi:Oracle:host=$host;sid=$sid"
B<ODBC(Microsoft Access)>
"dbi:ODBC:driver=Microsoft Access Driver (*.mdb);dbq=hoge.mdb"
B<ODBC(SQL Server)>
"dbi:ODBC:driver={SQL Server};Server=(local);database=test;Trusted_Connection=yes;AutoTranslate=No;"
認証が必要な場合は、C<user>とC<password>を指定できます。
L<DBIx::Custom>はL<DBI>のラッパークラスです。
L<DBI>のデータベースハンドルは取得するにあhC<dbh()>を使用します。
my $dbh = $dbi->dbh;
L<DBIx::Custom>ではデータベースハンドル属性にはデフォルトで次のものが設定されます。
$dbi->dbh->{RaiseError} = 1;
$dbi->dbh->{PrintError} = 0;
$dbi->dbh->{AutoCommit} = 1;
致命的なエラーが起こるとプログラムは終了します。
SQLが実行されると自動的にコミットされます。
=head2 2. 挿入、更新、削除、選択のためのメソッド
下記のメソッドがあります。
=head3 行の挿入 C<insert()>
データベースに行を挿入するにはC<insert()>を使用します。
$dbi->insert(table => 'book',
param => {title => 'Perl', author => 'Ken'});
C<table>はテーブル名、C<param>は挿入する行のデータです。
次のSQLが実行されます。
insert into (title, author) values (?, ?);
=head3 データの更新 C<update()>
データベースの行を更新するには、C<update()>を使用します。
$dbi->update(table => 'book',
param => {title => 'Perl', author => 'Ken'},
where => {id => 5});
C<table>はテーブル名、C<param>は更新データ、C<where>は
条件です。
次のSQLが実行されます。
update book set title = ?, author = ?;
安全のためC<where>のないupdate()を実効することはできません。
もしすべての行を更新したい場合は
C<update_all()>を使用してください。
$dbi->update_all(table => 'book',
param => {title => 'Perl', author => 'Ken'});
=head3 データの削除 C<delete()>
データベースの行を1件削除するには、C<delete()>を使用します。
$dbi->delete(table => 'book',
where => {author => 'Ken'});
C<table>はテーブル名、C<where>は条件です。
次のSQLが実行されます。
delete from book where id = ?;
安全のためC<where>のないC<delete()>を実効することはできません。
もしすべての行を削除したい場合は
C<delete_all()>を使用してください。
$dbi->delete_all(table => 'book');
=head3 データの選択 C<select()>
行を選択するにはC<select()>を使用します。
my $result = $dbi->select(table => 'book');
次のSQLが実行されます。
select * from book;
戻り値はL<DBIx::Custom::Result>
オブジェクトです。行をフェッチするにはC<fetch()>を使用します。
while (my $row = $result->fetch) {
my $title = $row->[0];
my $author = $row->[1];
}
L<DBIx::Custom::Result>についてはL<3. 行のフェッチ/"3. 行のフェッチ">を見てください。
サンプルを続けます。
my $result = $dbi->select(
table => 'book',
column => ['author', 'title'],
where => {author => 'Ken'}
);
C<column>は列名、C<where>は条件です。
次のSQLが実行されます。
select author, title from book where author = ?;
次のサンプルです。
my $result = $dbi->select(
table => ['book', 'rental'],
where => {book.name => 'Perl'},
relation => {'book.id' => 'rental.book_id'}
);
C<relation>テーブル間の関係です。これは内部結合です。
次のSQLが実行されます。
bookテーブルのid列とrentalテーブルのbook_idが関連付けられます。
次のSQLが実行されます。
select * from book, rental where book.name = ? and book.id = rental.book_id;
次のサンプルです。
my $result = $dbi->select(
table => 'book',
where => {author => 'Ken'},
append => 'for update',
);
C<append>はSQLの末尾に追加される文字列です。
次のSQLが実行されます。
select * book where author = ? for update;
またC<append>は、C<select>だけでなくC<insert()>、C<update()>、C<update_all()>
C<delete()>、C<delete_all()>、C<select()>で使用することもできます。
=head3 SQLの実行 C<execute()>
SQLを実行するにはC<execute()>を使用します。
$dbi->execute("select * from book;");
タグを処理してSQLを実行します。
$dbi->execute(
"select * from book {= title} and {= author};"
param => {title => 'Perl', author => 'Ken'}
);
次のSQLが実行されます。
select * from book title = ? and author = ?;
プレースホルダにtitleとauthorの値が埋め込まれます。
タグについてはL<5. タグ/"5. タグ">を見てください。
またC<execute()>のSQLの末尾にはセミコロンを置く必要はありません。
$dbi->execute('select * from book');
=head2 3. 行のフェッチ
C<select()>メソッドの戻り値はL<DBIx::Custom::Result>オブジェクトです。
L<DBIx::Custom::Result>には行をフェッチするためのさまざまなメソッドが
用意されています。
=head3 1行づつフェッチ(配列) C<fetch()>
一行フェッチして配列のリファレンスに格納するにはC<fetch()>を使用します。
while (my $row = $result->fetch) {
my $title = $row->[0];
my $author = $row->[1];
}
whileループを使って、すべての行を取得することができます。
=head3 最初の行だけフェッチ(配列) C<fetch_first()>
一行だけフェッチして配列のリファレンスに格納するにはC<fetch_first()>
を使用します。
my $row = $result->fetch_first;
一行のフェッチが終わった後はそれ以上フェッチできなくなります。
内部的には1行のフェッチが終わった後に
ステートメントハンドルのC<finish()>が実行されます。
=head3 複数行を順にフェッチ(配列) C<fetch_multi()>
複数行をフェッチして配列のリファレンスを要素に持つ
配列のリファレンスに格納するにはC<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']
]
=head3 すべての行をフェッチ(配列) C<fetch_all>
すべての行をフェッチして配列のリファレンスを要素に持つ
配列のリファレンスに格納するにはC<fetch_all()>を使用します。
my $rows = $result->fetch_all;
すべての行を格納した次のようなデータを取得できます。
[
['Perl', 'Ken'],
['Ruby', 'Mark']
]
=head3 1行づつフェッチ(ハッシュ) C<fetch_hash()>
一行フェッチしてハッシュのリファレンスに格納するにはC<fetch_hash()>を使用します。
while (my $row = $result->fetch_hash) {
my $title = $row->{title};
my $author = $row->{author};
}
=head3 最初の行だけフェッチ(ハッシュ) C<fetch_hash_first()>
一行だけフェッチしてハッシュのリファレンスに格納するには
C<fetch_hash_first()>を使用します。
my $row = $result->fetch_hash_first;
一行のフェッチが終わった後はそれ以上フェッチできなくなります。
内部的には1行のフェッチが終わった後に
ステートメントハンドルのC<finish()>が実行されます。
=head3 複数行を順にフェッチ(ハッシュ) C<fetch_hash_multi()>
複数行をフェッチしてハッシュのリファレンスを要素に持つ
配列のリファレンスに格納するにはC<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'}
]
=head3 すべての行をフェッチ(ハッシュ) C<fetch_hash_all()>
すべての行をフェッチしてハッシュのリファレンスを要素に持つ
配列のリファレンスに格納するにはC<fetch_hash_all()>
を使用します。
my $rows = $result->fetch_hash_all;
すべての行を格納した次のようなデータを取得できます。
[
{title => 'Perl', author => 'Ken'},
{title => 'Ruby', author => 'Mark'}
]
=head3 ステートメントハンドル C<sth()>
ステートメントハンドルに直接アクセスしたい場合は
<sth>で取得することができます。
my $sth = $result->sth;
フェッチのパフォーマンスが用件を満たさないときには、
ステートメントハンドルから
利用できる速度の速いメソッドを利用することができます。
=head2 4. フィルタリング
データベースにデータを登録するときやデータベースからデータを取得する
ときに自動的に値の変換を行いたい場合が多いと思います。
たとえば、日付を表現する列の場合は、
データベースに登録する場合はL<Time::Piece>オブジェクトから
データベースの日付のフォーマットに、
データベースからデータを取得するときは、その逆を行えると便利です。
=head3 フィルタの登録 C<register_filter()>
フィルタを登録するにはC<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');
},
);
登録したフィルタはC<apply_filter()>などで利用することができます。
=head3 フィルタの適用 C<apply_filter()>
作成したフィルタを適用するには、C<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がデータベースから取り出す方向です。
フィルタには、C<register_filter>で登録したフィルタ名の他に、コードリファレンスを
指定することもできます。
issue_date => {out => sub { ... }, in => sub { ... }}
適用されたフィルタはC<insert()>、C<update()>、C<update_all()>、C<delete()>、
C<delete_all()>、C<select()>で有効になります。
my $tp = Time::Piece->strptime('2010/10/14', '%Y/%m/%d');
my $result = $dbi->select(table => 'book', where => {issu_date => $tp});
データベースにデータが送信されるときに、L<Time::Piece>オブジェクトは
データベースの日付のフォーマット「2010-10-14」に変換されます。
また逆にデータをフェッチするときには、データベースの日付のフォーマットは
タイムピースオブジェクトに変換されます。
my $row = $resutl->fetch_hash_first;
my $tp = $row->{issue_date};
このような自動的に実行されるフィルタを登録できることがL<DBIx::Custom>の
特徴のひとつです。
C<apply_filter()>で適用されたフィルタはテーブル名をを含む列名に対しても有効です。
$dbi->select(
table => 'book',
where => {'book.title' => 'Perl', 'book.author' => 'Ken'}
);
テーブルを区別する必要があるときに便利な機能です。
=head3 個別のフィルタの適用 C<filter>
C<apply_filter()>を使って最初にすべてのテーブルの列について
フィルタを定義することもできますが、
個別にフィルタを適用することもできます。
個別のフィルタはC<apply_filter()>で適用したフィルタを上書きます。
個別のフィルタはSQLのasを使って、列の別名を作成する必要がある場合に活躍します。
データベースに送信する場合に、個別のフィルタを適用するには、各メソッドの
C<filter>オプションを使用します。個別のフィルタは、C<insert()>、C<update()>、
C<update_all()>、C<delete()>、C<delete_all()>、C<select()>、C<execute()>
で使用することができます。
C<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'}
);
C<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'}
);
これはC<filter>を使う良くある例です。issue_dateの変換についてはC<apply_filter()>
で登録してあるのですが、新しく作成した列であるissue_yearについては、
何の変換も登録されていません。ですので、個別にフィルタを設定しています。
また反対に行をフェッチするときにも個別のフィルタを適用することができます。
フィルタを適用するには、
C<DBIx::Custom::Result>クラスのC<filter>メソッドを使用します。
$result->filter(issue_year => 'year_to_tp');
頻繁に利用するのであれば、個別に登録するよりもC<apply_filter()>で登録
しておいたほうが便利でしょう。C<apply_filter()>は存在しない列に対しても
フィルタを適用できるからです。
$dbi->apply_filter('book',
'issue_year' => {out => 'tp_to_year', in => 'year_to_tp'}
);
=head3 最終出力のためのフィルタリング C<end_filter()>
C<DBIx::Custom::Result>ではさらに最後にもう一度、フィルタを追加で
登録することができます。たとえばHTMLに出力したい場合に、Time::Piece
オブジェクトから読みやすい記述に変換することができます。
最後のフィルタを登録するには、C<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;
=head3 列の情報を元にフィルタを適用する C<each_column()>
日付型の列は手動で設定しなくても、自動的に設定できると便利です。
このためにデータベースのテーブルの列のすべての情報を
順番に処理するためのC<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はコールバックを受け取ります。コールバックの引数は
順番にL<DBIx::Custom>オブジェクト、テーブル名、列名、列の情報です。
列の型名の情報をもとに自動的に、フィルタを適用しています。
ひとつの注意点としてコールバックの中から、コールバックの外側
の変数を参照しないように注意してください。each_columnは
高々1回だけ実行されるだけなので、ほとんどの場合問題ありませんが、
循環参照によるメモリリークが発生してしまう可能性を持っているからです。
=head2 5. タグ
=head3 タグの機能
L<DBIx::Custom>はSQLの中にタグを埋め込む機能を持っています。
select * from book where {= title} and {like author};
{= title}と{like author}の部分がタグです。タグは次のような形式
を持ちます。
{タグ名 引数1 引数2 ...}
タグはC<{>で始まり、C<}>で終わります。最初のC<{>とタグ名の間
には空白を挿入しないよう注意してください。
タグの機能のためにC<{>とC<}>は予約語になっています。
もし利用したい場合は直前に\をおいてエスケープを行う必要があります。
select from book \\{ ... \\}
C<\>自体がPerlのエスケープ文字ですので、
エスケープする場合はC<\>が二つ必要になるという点に注意してください。
上記のタグはSQLが実行される前に次のSQLに展開されます。
select * from book where title = ? and author like ?;
タグを含むSQLを実行するにはC<execute()>を使用します。
my $sql = "select * from book where {= author} and {like title};"
$dbi->execute($sql, param => {title => 'Perl', author => '%Ken%'});
C<param>オプションを使って、プレースホルダに埋め込みたい値を
ハッシュリファレンスで指定することができます。
他のメソッドと同様にC<execute()>においてもC<filter>を指定することができます。
$dbi->execute($sql, param => {title => 'Perl', author => '%Ken%'}
filter => {title => 'to_something');
C<execute>のひとつの注意点としてはC<apply_filter()>で適用されたフィルタ
はデフォルトでは有効ではないということに注意してください。
C<apply_filter()>で適用されたフィルタを有効にするには、
C<table>を指定する必要があります。
$dbi->execute($sql, param => {title => 'Perl', author => '%Ken%'}
table => ['book']);
=head3 タグ一覧
L<DBIx::Custom>では次のタグが使用可能です。
このようにタグを使ってSQL文を表現するのがL<DBIx::Custom>の
特徴です。以下のタグが利用可能です。
=head4 C<?>
C<?>タグは以下のように展開されます。
{? NAME} -> ?
=head4 C<=>
C<=>タグは以下のように展開されます。
{= NAME} -> NAME = ?
=head4 C<E<lt>E<gt>>
C<E<lt>E<gt>>タグは以下のように展開されます。
{<> NAME} -> NAME <> ?
=head4 C<E<lt>>
C<E<lt>>タグは以下のように展開されます。
{< NAME} -> NAME < ?
=head4 C<E<gt>>
C<E<gt>>タグは以下のように展開されます。
{> NAME} -> NAME > ?
=head4 C<E<gt>=>
C<E<gt>=>タグは以下のように展開されます。
{>= NAME} -> NAME >= ?
=head4 C<E<lt>=>
C<E<lt>=>タグは以下のように展開されます。
{<= NAME} -> NAME <= ?
=head4 C<like>
C<like>タグは以下のように展開されます。
{like NAME} -> NAME like ?
=head4 C<in>
C<in>タグは以下のように展開されます。プレースホルダの
数を引数で指定する必要があることに注意してください。
{in NAME COUNT} -> NAME in [?, ?, ..]
=head4 C<insert_param>
C<insert_param>タグは以下のように展開されます。
{insert_param NAME1 NAME2} -> (NAME1, NAME2) values (?, ?)
=head4 C<update_param>
C<update_param>タグは以下のように展開されます。
{update_param NAME1 NAME2} -> set NAME1 = ?, NAME2 = ?
=head3 同名の列の扱い
同名の列を含むタグがある場合にも、SQLを実行することができます。
たとえば、二つの日付で比較しなければならない場合を
考えて見ましょう。
my $sql = "select * from table where {> date} and {< date};";
このような場合は対応するパラメータの値を配列のリファレンスにします。
my $dbi->execute($sql, param => {date => ['2010-10-01', '2012-02-10']});
=head3 タグの追加 C<register_tag()>
L<DBIx::Custom>ではタグを独自に追加することができます。
タグを追加するにはC<register_tag()>を使用します。
$dbi->register_tag(
'=' => sub {
my $column = shift;
return ["$column = ?", [$column]];
}
);
ここではデフォルトのC<=>タグがどのように実装されているかを示しています。
タグを登録する関数の引数はタグの中に書かれた引数になります。
{タグ名 引数1 引数2}
C<=>タグの場合は
{= title}
という形式ですから、サブルーチンにはtitleというひとつの列名がわたってきます。
サブルーチンの戻り値には次の形式の配列のリファレンスを返す必要があります。
[
展開後の文字列,
[プレースホルダに埋め込みに利用する列名1, 列名2, ...]
]
一つ目の要素は展開後の文字列です。この例では
'title = ?'
を返す必要があります。
二つ目の要素はプレースホルダに埋め込みに利用する列名を含む配列の
リファレンスです。今回の例では
['title']
を返す必要があります。複数のプレースホルダを含む場合は、この部分が
複数になります。C<insert_param>タグやC<update_param>タグは
この部分が実際複数になっています。
上記を合わせると
['title = ?', ['title']]
を返す必要があるということです。
タグの実装の他のサンプルはL<DBIx::Custom::Tag>のソースコード
をご覧になってみてください。
=head2 6. Where句の動的な生成
=head3 Where句の動的な生成 where()
複数の検索条件を指定して、検索を行いたい場合があります。
次の3つのケースのwhere句を考えてみましょう。
下記のようなwhere句が必要になります。
titleの値だけで検索したい場合
where {= title}
authorの値だけで検索したい場合
where {= author}
titleとauthorの両方の値で検索したい場合
where {= title} and {=author}
L<DBIx::Custom>では動的なWhere句の生成をサポートしています。
まずC<where()>でL<DBIx::Custom::Where>オブジェクトを生成します。
my $where = $dbi->where;
次にC<clause()>を使用してwhere句を記述します。
$where->clause(
['and', '{= title'}, '{= author}']
);
clauseの指定方法は次のようになります。
['or' あるいは 'and', タグ1, タグ2, タグ3]
第一引数にはorあるいはandを指定します。第二引数以降には
検索条件をタグを使って記述します。
C<clause>の指定は入れ子にすることもでき、さらに複雑な条件
を記述することもできます。
['and',
'{= title}',
['or', '{= author}', '{like date}']
]
このようにC<clause>を設定した後にC<param>にパラメータを指定します。
my $param => {title => 'Perl'};
$where->param($param);
この例ではtitleだけがパラメータに含まれています。
この後C<to_string()>を実行すると$paramに含まれるパラメータを満たす
where句を生成することができます。
my $where_clause = $where->to_string;
パラメータはtitleだけですので、次のようなwhere句が生成されます。
where {= title}
またL<DBIx::Custom>は文字列の評価をオーバーロードして、C<to_string()>
を呼び出すようにしていますので、次のようにしてwhere句を生成することも
できます。
my $where_clause = "$where";
これはSQLの中にwhere句を埋め込むときにとても役立つ機能です。
=head3 同一の列名を含む場合
タグの中に同一の名前を持つものが存在した場合でも動的に
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']};
L<DBIx::Custom>のC<not_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};
=head3 C<select()>との連携
L<DBIx::Custom::Where>オブジェクトは
C<select()>のC<where>に直接渡すことが
できます。
my $where = $dbi->where;
$where->clause(...);
$where->param($param);
my $result = $dbi->select(table => 'book', where => $where);
あるいはC<update()>、C<delete()>のwhereに指定することも可能です。
=head3 C<execute()>との連携
C<execute()>との連携です。SQLを作成するときに埋め込むことができます。
my $where = $dbi->where;
$where->clause(...);
$where->param($param);
my $sql = <<"EOS"
select * from book;
$where
EOS
$dbi->execute($sql, param => $param);
=head2 7. テーブルモデル
=head3 テーブルオブジェクトの作成 C<table()>
L<DBIx::Custom>はロジックとしてテーブルを中心にすえた
モデルの作成を支援します。
アプリケーションのロジックを記述するときに、そのロジックを
データベースのテーブルにすえることは、RDBMSを利用する
モデルであれば、コードの重複も少なく
わかりやすいものになります。
テーブルオブジェクトを生成するにはC<table()>を使用します。
my $table = $dbi->table('book');
実際にデータベースにテーブルは存在している必要はありません。
これは仮想的なテーブルオブジェクトです。これは
L<DBIx::Customm::Table>オブジェクトになります。
テーブルオブジェクトからはC<insert()>、C<update()>、C<update_all()>、
C<delete()>、C<delete_all()>、C<select()>などのメソッド呼び出すことができます。
L<DBIx::Custom>と異なるところは、C<table>を必ずしも指定する必要が
ないということです。
$table->insert(param => $param);
C<table>の値は自動的にbookに設定されます。
またテーブルオブジェクトには独自のメソッドを追加することができます。
$table->method(
register => sub {
my $self = shift;
my $table_name = $self->name;
# something
},
list => sub {
my $self = shift;
my $table_name = $self->name;
# something
}
);
メソッドに渡される第一引数はL<DBIx::Custom::Table>オブジェクトです。
C<name()>を使用して、テーブル名を取得することができます。
このようにして登録したメソッドは直接呼び出すことができます。
$table->register(...);
$table->list(...);
またテーブル専用のメソッドをオーバーライドして作成することもできます。
$table->method(
insert => sub {
my $self = shift;
$self->base_insert(...);
# something
}
);
もともと存在していたC<insert()>を呼ぶにはC<base_$method>とします。L<DBIx::Custom::Table>
のオーバーライドの機能は簡易的なものですが、とても便利です。
=head2 テーブルで共有のメソッドの登録
すべてのテーブルでメソッドを共有するにはC<table>メソッドでテーブルを作成する前に、
C<base_table>にメソッドを登録しておきます。
$dbi->base_table->method(
count => sub {
my $self = shift;
return $self->select(column => ['count(*)']);
}
);
またテーブルからはL<DBIx::Custom>とL<DBI>のすべてのメソッドを呼び出すことができます。
# DBIx::Custom method
$table->execute($sql);
# DBI method
$table->begin_work;
$table->commit;
=head2 一般的なモデルの構成
一般的には、L<DBIx::Custom>を継承してコンストラクタの中に、モデルを作成
するのがよいでしょう。
package MyDBI;
use base 'DBIx::Custom';
sub connect {
my $self = shift->SUPER::connect(@_);
$self->base_table->method(
... => sub { ... }
);
$self->table('book')->method(
insert_multi => sub { ... },
... => sub { ... }
);
$self->table('company')->method(
... => sub { ... },
);
}
このようにして定義しておけば、次のように利用することができます。
my $dbi = MyDBI->connect(...);
$dbi->table('book')->insert_multi(...);
=head2 8. パフォーマンスの改善
=head3 クエリの作成
もしC<insert()>メソッドを使用してインサートを実行した場合、
必要なパフォーマンスを得られない場合があるかもしれません。
C<insert()>メソッドは、SQL文とステートメントハンドルを
毎回作成するためです。
そのような場合は、C<query>オプションを指定することで、
クエリを取得することができます。
my $query = $dbi->insert(table => 'book', param => $param, query => 1);
またC<create_query()>メソッドを使って任意のSQLのクエリを作成
することもできます。
my $query = $dbi->create_query(
"insert into book {insert_param title author};";
);
戻り値はL<DBIx::Custom::Query>オブジェクトです。
このオブジェクトはSQL文とパラメータバインド時の列名を
保持しています。またステートメントハンドルも保持しています。
{
sql => 'insert into book (title, author) values (?, ?);',
columns => ['title', 'author'],
sth => $sth
}
クエリオブジェクトを使って繰り返し実行するにはC<execute()>を使用します。
my $params = [
{title => 'Perl', author => 'Ken'},
{title => 'Good days', author => 'Mike'}
];
foreach my $param (@$paramss) {
$dbi->execute($query, table => 'book', param => $input);
}
C<execute>メソッドの第一引数にクエリオブジェトを渡すことができます。
C<insert()>メソッドよりも高速です。
注意点がいくつかあります。それはパラメータの数は必ず同じでなくてはならない
ということです。最初に3つのパラメータだけを渡したのに、次の実行では
二つのパラメータを渡すと予期しない結果になります。それは
動的に生成されたSQLに含まれるプレースホルダの数が異なるからです。
またC<execute()>によっては自動的にはフィルタが有効にならないので、
C<table>を指定する必要のあることに注意してください。
本当に必要な場合だけ利用してください。
=head2 9. その他の機能
=head3 メソッドの登録
メソッドを登録するにはC<method()>を使用します。
$dbi->method(
update_or_insert => sub {
my $self = shift;
# something
},
find_or_create => sub {
my $self = shift;
# something
}
);
<method()>で登録したメソッドは
L<DBIx::Custom>オブジェクトから直接呼び出すことができます。
$dbi->update_or_insert;
$dbi->find_or_create;
=head3 結果クラスの変更
必要ならば結果クラスを変更することができます。
package MyResult;
use base 'DBIx::Custom::Result';
sub some_method { ... }
1;
package main;
use MyResult;
my $dbi = DBIx::Custom->connect(...);
$dbi->result_class('MyResult');
=head3 キャッシング
タグの展開後のSQLはパフォーマンスの理由のためにキャッシュされます。
これはC<chace>で設定でき、デフォルトではキャッシュを行う設定です。
$dbi->cache(1);
キャッシュ方法はC<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]}
}
}
});
第一はL<DBIx::Custom>オブジェクトです。
第二引数はタグの展開される前のSQLです。
第三引数はタグの展開後のSQLです。
自分で作成する場合は第三引数が存在した場合はキャッシュを設定し、
存在しなかった場合はキャッシュを取得する実装に
してください。
=cut