ffb25cb 13 years ago
3 contributor
728 lines | 23.815kb
=encoding utf8

=head1 NAME

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

=head1 ガイド

L<DBIx::Custom>はデータベースへのクエリの発行を簡単に行うための
クラスです。L<DBIx::Class>やL<DBIx::Simple>と同じように
L<DBI>のラッパクラスになっています。

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

L<DBIx::Custom>はO/RマッパとL<DBI>の中間に位置するモジュールです。
L<DBIx::Custom>は柔軟なハッシュパラメータバインディングとフィルタリング
のシステムを提供します。またSQLを簡単に実行するための、
C<insert()>, C<update()>, C<delete()>,C<select()>などの
シュガーメソッドも提供します。

L<DBIx::Custom>はSQLを尊重します。SQLはとても複雑で、美しくはありません。
けれども、SQLはデファクトスタンダードな技術です。
ですので、データベースを学ぶすべての人はSQLを知っています。
あなたがすでにSQLを知っているなら、L<DBIx::Custom>を使って
何かを行うために覚えることはとても少ないです。

では使い方を解説します。

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

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

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

B<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;"

L<DBIx::Custom>はL<DBI>のラッパです。
L<DBI>オブジェクトはC<dbh>で取得することができます。

    my $dbh = $dbi->dbh;

データベースハンドル属性にはデフォルトで次のものが設定されます。
    
    $dbi->dbh->{RaiseError} = 1;
    $dbi->dbh->{PrintError} = 0;
    $dbi->dbh->{AutoCommit} = 1;

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

=head2 2. シュガーメソッド

L<DBIx::Custom>は、
C<insert()>、C<update()>、C<delete()>、C<select()>
のようなシュガーメソッドを持っています。
小さなことを行うのであれば、SQL文を
作成する必要はありません。

=head3 C<insert()>

C<insert>メソッドです。データベースにデータを挿入します。

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

これは次のL<DBI>の操作と同じです。

    my $sth = $dbh->prepare('insert into (title, author) values (?, ?);');
    $sth->execute('Perl', 'Ken');

=head3 C<update()>

C<update>メソッドです。データベースのデータを更新します。

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

これは次のL<DBI>の操作と同じです。

    my $sth = $dbh->prepare(
        'update books set title = ?, author = ? where id = ?;');
    $sth->execute('Perl', 'Ken', 5);

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

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

=head3 C<delete()>

C<delete>メソッドです。データベースのデータを削除します。

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

これは次のL<DBI>の操作と同じです。

    my $sth = $dbh->prepare('delete from books where id = ?;');
    $sth->execute('Ken');

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

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

=head3 C<select()>

C<select>メソッドです。テーブル名だけを指定しています。

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

これは次のL<DBI>の操作と同じです。

    my $sth = $dbh->prepare('select * from books;);
    $sth->execute;

C<select()>メソッドの戻り値はL<DBIx::Custom::Result>
オブジェクトです。C<fetch>メソッドを使用して
行をフェッチすることができます。

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

次のC<select>は行の名前とwhere句を指定したものです。

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

次のL<DBI>の操作と同じです。
    
    my $sth = $dbh->prepare(
        'select author, title from books where author = ?;');
    $sht->execute('Ken');

テーブルをjoinしたい場合はC<relation>を使用します。

    my $result = $dbi->select(
        table    => ['books', 'rental'],
        column   => ['books.name as book_name']
        relation => {'books.id' => 'rental.book_id'}
    );

次のL<DBI>の操作と同じです。

    my $sth = $dbh->prepare(
        'select books.name as book_name from books, rental' .
        'where books.id = rental.book_id;');
    $sth->execute;

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

    my $result = $dbi->select(
        table  => 'books',
        where  => {author => 'Ken'},
        append => 'order by price limit 5',
    );

次のL<DBI>の操作と同じです。

    my $sth = $dbh->prepare(
        'select * books where author = ? order by price limit 5;');
    $sth->execute;

C<append>オプションは、C<insert()>、C<update()>、C<update_all()>
C<delete()>、C<select>メソッドで使用することが
できます。

この後のフィルタリングの解説で詳しく扱いますが、値をフィルタリングしたい
場合はC<filter>オプションを使用することができます。

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

C<filter>オプションは、C<insert()>、C<update()>、C<update_all()>
C<delete()>、C<select>メソッドで使用することが
できます。

C<select()>メソッドのC<where>オプションではハッシュの代わりに
タグを利用することもできます。これによって柔軟な
条件を指定することができます。

    # Select, more flexible where
    my $result = $dbi->select(
        table  => 'books',
        where  => ['{= author} and {like title}', 
                   {author => 'Ken', title => '%Perl%'}]
    );

タグについては以降で解説します。

=head2 3. 行のフェッチ

C<select()>メソッドの戻り値であるL<DBIx::Custom::Result>
には行をフェッチするためのさまざまなメソッドが
用意されています。
(このセクションの解説では「配列」は「配列のリファレンス」を
「ハッシュ」は「ハッシュのリファレンス」を意味しますので
注意してください。)

=head3 C<fetch>

一行フェッチして配列に格納します。

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

=head3 C<fetch_first>

一行だけフェッチして配列に格納します。

    my $row = $result->fetch_first;

フェッチが終わった後は、ステートメントハンドルからC<finish()>
メソッドが呼び出されてそれ以上フェッチできなくなります。

=head3 C<fetch_multi>

複数行をフェッチして配列の配列に格納します。

    while (my $rows = $result->fetch_multi(5)) {
        my $first_author  = $rows->[0][0];
        my $first_title   = $rows->[0][1];
        my $second_author = $rows->[1][0];
        my $second_value  = $rows->[1][1];
    }

=head3 C<fetch_all>

すべての行をフェッチして配列の配列に格納します。

    my $rows = $result->fetch_all;

=head3 C<fetch_hash>

一行フェッチしてハッシュに格納します。

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

=head3 C<fetch_hash_first>

一行だけフェッチしてハッシュに格納します。

    my $row = $result->fetch_hash_first;

フェッチが終わった後は、ステートメントハンドルからC<finish()>
メソッドが呼び出されてそれ以上フェッチできなくなります。

=head3 C<fetch_hash_multi>

複数行をフェッチしてハッシュの配列に格納します。

    while (my $rows = $result->fetch_hash_multi(5)) {
        my $first_title   = $rows->[0]{title};
        my $first_author  = $rows->[0]{author};
        my $second_title  = $rows->[1]{title};
        my $second_author = $rows->[1]{author};
    }

=head3 C<fetch_all>

すべての行をフェッチしてハッシュの配列に格納します。

    my $rows = $result->fetch_hash_all;

L<DBI>のステートメントハンドルに直接アクセスしたい場合は
<sth>を使用します。

    my $sth = $result->sth;

=head2 4. ハッシュパラメタバインド

L<DBIx::Custom>はハッシュパラメタバインドを提供します。

まず最初にL<DBI>による通常のパラメタバインドをご覧ください。

    use DBI;
    my $dbh = DBI->connect(...);
    my $sth = $dbh->prepare(
        "select * from books where author = ? and title like ?;"
    );
    $sth->execute('Ken', '%Perl%');

これはデータベースシステムがSQLをキャッシュすることができ、
パラメータは自動的にクォートされるので、
パフォーマンス面でも、セキュリティ面でも
とても良い方法です。


L<DBIx::Custom>はこれを改善して、ハッシュで
パラメタを指定できるようにしました。

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

C<{= author}>とC<{like title}>はタグと呼ばれます。
タグは内部ではプレースホルダを含む文字列に置き換えられます。

    select * from books where {= author} and {like title}

という文は以下のSQLに置き換えられます。

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

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

    [TAG]                       [REPLACED]
    {? NAME}               ->   ?
    {= NAME}               ->   NAME = ?
    {<> NAME}              ->   NAME <> ?
    
    {< NAME}               ->   NAME < ?
    {> NAME}               ->   NAME > ?
    {>= NAME}              ->   NAME >= ?
    {<= NAME}              ->   NAME <= ?
    
    {like NAME}            ->   NAME like ?
    {in NAME COUNT}        ->   NAME in [?, ?, ..]
    
    {insert_param NAME1 NAME2}   ->   (NAME1, NAME2) values (?, ?)
    {update_param NAME1 NAME2}   ->   set NAME1 = ?, NAME2 = ?

これらの変換はL<DBIx::Custom::QueryBuilder>によって行われます。

C<{>とC<}>は予約語です。これらの文字を使いたい場合は
「\」を使ってエスケープする必要があります。
'\'はPerlのエスケープ文字なので、
エスケープするためには'\\'と書く必要があることに注意
してください。

    'select * from books \\{ something statement \\}'

=head2 5. フィルタリング

=head3 パラメタバインド時のフィルタリング

データベースに登録するデータをフィルタリングしたい場合
があります。たとえば、内部文字列で文字列を保持している場合は
データベースにデータを登録する前に、バイト文字列に変換する
必要があります。L<DBIx::Custom>のフィルタリングシステムは
あるデータを他のデータに変換するのを手助けしてくれます。

フィルタリングを利用するにはまず、
C<register_filter()>メソッドを使用して
フィルタを登録しておく必要があります。

    $dbi->register_filter(
        to_upper_case => sub {
            my $value = shift;
            return uc $value;
        }
    );

デフォルトのフィルタとしてC<encode_utf8>とC<decode_utf8>
が登録されています。

登録されているフィルタはC<execute()>メソッドのC<filter>オプション
で指定することができます。

    my $result = $dbi->execute(
        "select * from books where {= author} and {like title};"
        param  => {author => 'Ken', title => '%Perl%'},
        filter => {author => 'to_upper_case, title => 'encode_utf8'}
    );

この例ではC<author>の値はバインドされるときに大文字に変換され、
C<title>の値はバイト文字列に変換されます。

C<filter>オプションは
C<insert()>、C<update()>、 C<update_all()>,
C<delete()>、C<select()>
メソッドにおいても使用することができます。

    # insert() with filter option
    $dbi->insert(table  => 'books',
                 param  => {title => 'Perl', author => 'Ken'},
                 filter => {title => 'encode_utf8'});
    
    # select() with filter option
    my $result = $dbi->select(
        table  => 'books',
        column => [qw/author title/],
        where  => {author => 'Ken'},
        append => 'order by id limit 1',
        filter => {title => 'encode_utf8'}
    );

C<filter>で指定されたフィルタはそれぞれのパラメータの値に働きますが、
すべてのパラメータのためのデフォルトのフィルタを設定することもできます。

    $dbi->default_bind_filter('encode_utf8');

C<filter()>オプションで指定されたフィルタは、
デフォルトのフィルタを上書きます。
    
    $dbi->default_bind_filter('encode_utf8');
    $dbi->insert(
        table  => 'books',
        param  => {title => 'Perl', author => 'Ken', price => 1000},
        filter => {author => 'to_upper_case', price => undef}
    );

C<title>の値にはデフォルトのフィルタであるC<encode_utf8>が適用され
C<author>の値にはC<to_upper_case>が適用されます。
C<price>にはundefを設定しているのでフィルタが解除されます。

B<フィルタのサンプル>

MySQL

    # Time::Piece object to DATETIME format
    tp_to_datetime => sub {
        return shift->strftime('%Y-%m-%d %H:%M:%S');
    }
    
    # Time::Piece object to DATE format
    tp_to_date => sub {
        return shift->strftime('%Y-%m-%d');
    }
    
    # DATETIME to Time::Piece object
    datetime_to_tp => sub {
        return Time::Piece->strptime(shift, '%Y-%m-%d %H:%M:%S');
    }
    
    # DATE to Time::Piece object
    date_to_tp => sub {
        return Time::Piece->strptime(shift, '%Y-%m-%d');
    }

SQLite
    
    # Time::Piece object to DATETIME format
    tp_to_datetime => sub {
        return shift->strftime('%Y-%m-%d %H:%M:%S');
    }
    
    # Time::Piece object to DATE format
    tp_to_date => sub {
        return shift->strftime('%Y-%m-%d');
    }
    
    # DATETIME to Time::Piece object
    datetime_to_tp => sub {
        return Time::Piece->strptime(shift, $FORMATS->{db_datetime});
    }
    
    # DATE to Time::Piece object
    date_to_tp => sub {
        return Time::Piece->strptime(shift, $FORMATS->{db_date});
    }

=head3 行のフェッチ時のフィルタリング

行をフェッチするときのフィルタも設定することができます。
これはL<DBIx::Custom::Result>クラスのC<filter>メソッドを使って
行います。

    my $result = $dbi->select(table => 'books');
    $result->filter({title => 'decode_utf8', author => 'to_upper_case'});

フィルタはそれぞれの列の値に作用しますが、
すべての列のためのデフォルのトフィルタを設定することもできます。

    $dbi->default_fetch_filter('decode_utf8');

C<filter>メソッドで設定されたフィルタはデフォルトのフィルタを上書きます。

    $dbi->default_fetch_filter('decode_utf8');
    my $result = $dbi->select(
        table => 'books',
        columns => ['title', 'author', 'price']
    );
    $result->filter({author => 'to_upper_case', price => undef});

C<title>の値にはデフォルトのフィルタであるC<decode_utf8>が適用され
C<author>の値にはC<to_upper_case>が適用されます。
C<price>にはundefを設定しているのでフィルタが解除されます。

フェッチのためのフィルタにおいて、
たとえ、列名が大文字を含む場合であっても
列名は小文字であることに注意してください。
これはデータベースシステムに依存させないための要件です。

=head2 6. パフォーマンスの改善

=head3 フィルタのチェックを無効にする

フィルタのチェックがデフォルトでは有効になっています。
これは正しいフィルタ名が指定されているかどうかをチェック
するためのものですが、ときにはパフォーマンスに影響を
及ぼす可能性があります。

フィルタのチェックを無効にするにはC<filter_check>
に0を設定してください。

    $dbi->filter_check(0);

=head3 シュガーメソッドを使わない

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

そのような場合は、C<create_query()>メソッドによって
クエリを用意しておくことができます。
    
    my $query = $dbi->create_query(
        "insert into books {insert_param title author};"
    );

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

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

クエリオブジェクトを使って繰り返し実行するには次のようにします。
    
    my $inputs = [
        {title => 'Perl',      author => 'Ken'},
        {title => 'Good days', author => 'Mike'}
    ];
    
    foreach my $input (@$inputs) {
        $dbi->execute($query, $input);
    }

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

=head3 キャッシング

C<execute()>メソッドはデフォルトでSQL文への解析結果をキャッシュします。

    $dbi->cache(1);

キャッシングはメモリ上で行われますが、C<cache_method()>
を使って変更することができます。これはキャッシュされるデータの保存と取得
のためのメソッドを定義するのに利用されます。

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

最初の引数はL<DBIx::Custom>です。第二引数は、
"select * from books where {= title} and {= author};";
のようなSQL文の源です。
第三引数は
{sql => "select * from books where title = ? and author = ?",
 columns => ['title', 'author']}
のような解析後のデータです。これはハッシュリファレンスである必要
があります。
もし引数がふたつより大きい場合は、このメソッドはキャッシュを保存
するのに利用されます。そうでない場合はキャッシュを取得するのに
利用されます。

=head2 7. その他の機能

=head3 トランザクション

トランザクションを便利に利用するために、
C<begin_work()>、C<commit()>、C<rollback()>
という三つのメソッドが容易されています。
これはL<DBI>の同名のメソッドと同じ機能を持ちます。

    $dbi->begin_work;
    
    eval {
        $dbi->update(...);
        $dbi->update(...);
    };
    
    if ($@) {
        $dbi->rollback;
    }
    else {
        $dbi->commit;
    }

=head3 selectメソッドの結果クラスの変更

必要ならばC<select()>メソッドの結果クラスを変更することができます。

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

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

=head3 L<DBIx::Custom::QueryBuilder>の機能の拡張

新しいタグが欲しい場合はL<DBIx::Custom::QueryBuilder>の機能を拡張
することができます。

    my $dbi = DBIx::Custom->connect(...);
    $dbi->query_builder->register_tag_processor(
        name => sub {
           ...
        }
    );

=head3 ヘルパーメソッドの登録

ヘルパーメソッドを登録することができます。

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

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

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

=head3 ユーティリティメソッド(実験的)

C<expand>メソッドを使用すると次のようなハッシュに含まれる
テーブル名と列名を結合することができます。

    my %expanded = $dbi->expand(\%source);

以下のハッシュ

    {books => {title => 'Perl', author => 'Ken'}}

は次のように展開されます。

    ('books.title' => 'Perl', 'books.author' => 'Ken')

これはテーブル名を含むselect文で利用すると便利です。

    my $param = {title => 'Perl', author => '%Ken%'};
    $dbi->execute(
        'select * from books where {= books.title} && {like books.author};',
        param => {$dbi->expand({books => $param})}
    );

=cut