omoonのブログ

旅の思い出を写真とともに書いてみるか。

DBIで文字コード変換その後

http://d.hatena.ne.jp/omoon/20070710/1184034594
http://q.hatena.ne.jp/1183789393

このあたりに書いていたことの顛末です。とりあえず、この形で様子を見る、というのができたので書いときます。とりあえず今のところ暫定版。変化があり次第追記します。

結論から言うと、DBIのサブクラスを作って実装しました。
主に、この部分を参考にしました。
http://search.cpan.org/~timb/DBI-1.58/DBI.pm#Subclassing_the_DBI

やったことは、以下の3つ。

  • prepare部分でsql文をencode(cp932へ)
  • execute部分でプレースホルダへの変数をencode(cp932へ)
  • fetch部分で戻り値をdecode(cp932から)

上2つで、更新(insert, update, delete)は問題なし。参照もfetchrow_arrayref、fetchrow_hashrefで値がとれることは確認できました。

fetchrow_hashrefはfetch部分をオーバーライドすることでうまくいくのですが、fetchrow_arrayrefはfetchrow_arrayrefをオーバーライドしないとうまくいかないようです(それ以外は試していません。ま、追々)。

ということで、参照系はfetchとfetchrow_arrayrefをオーバーライド。
下記、実際のコードです。

 1 package MyDBI;
 2 use strict;
 3 use warnings;
 4 use base qw(DBI); 
 5     
 6 package MyDBI::db;
 7 use base qw(DBI::db);
 8 
 9 sub prepare { 
10     my ($dbh, @args) = @_;
11     # sql文が入っている
12     @args = map { Encode::encode('cp932', $_) } @args;
13     return $dbh->SUPER::prepare(@args);
14 }   
15     
16 package MyDBI::st;
17 use base qw(DBI::st);
18     
19 sub execute {
20     my ($sth, @args) = @_;
21     # プレースホルダの値が入っている
22     @args = map { Encode::encode('cp932', $_) } @args;
23     return $sth->SUPER::execute(@args);
24 }   
25     
26 sub fetch {
27     my ($sth, @args) = @_;
28     my $row = $sth->SUPER::fetch(@args) or return;
29     foreach my $val (@$row) {
30         $val = Encode::decode('cp932', $val);
31     }
32     return $row;
33 }
34     
35 sub fetchrow_arrayref {
36     my ($sth, @args) = @_;
37     my $array_ref = $sth->SUPER::fetchrow_arrayref(@args) or return;
38     foreach my $value (@$array_ref) {
39         $value = Encode::decode('cp932', $value);
40     }   
41     return $array_ref;
42 }   
43 
44 1;

http://d.hatena.ne.jp/omoon/20070710/1184034594
に書いていたコードでは正常に動かない。
色々突っ込みどころは満載ですが、特に28行目と37行目の

or return;

が要ります。でないと、ループします。というかしました。(apacheのエラーログ爆発した。)

さあ、これで、こんな感じでutf8のスクリプトから呼び出しても、、

use MyDBI;
my $dbh = MyDBI->connect('DBI:mysql::localhost', 'user', 'pass');
my $sth;

$sth = $dbh->prepare("update foo set bar=? where baz=?");
$sth->execute('あいうえお', 'あ');

$sth = $dbh->prepare("select bar, baz from foo where baz=?"); 
$sth->execute('あ');
while (my $str = $sth->fetchrow_arrayref) {
    my ($foo, $bar) = @$str;
}

みたいな感じで、sjismysqlと無事お話ができるようになりました。とさ。