DBI で printf オレオレ流
Kazuho@Cybozu Labs: DBI::Printf - A Yet Another Prepared Statement を読んでおもしろ,と思ったんですが,どうもこれを prepared statement と呼ぶことに抵抗感があった*1のでコメントしたら,丁寧にコメントを返してくださいました。感謝感謝。
で,お礼?としてどういう意図だったのか,というソースを出してみます。ただし,DBD::mysql の bind_param のバグ(⇒Kazuho@Cybozu Labs: MySQL の高速化プチBK,Bug #29528 for DBD-mysql: bind_param(..., SQL_FLOAT) ignores exponents)が直ること前提ですけど。
package DBIx::Prepare::Printf; use strict; use warnings; use Carp; our $VERSION = '0.01'; use DBI qw( :sql_types ); my %type = ( d => SQL_INTEGER, f => SQL_FLOAT, s => undef, ); sub prepare { my ($class, $dbh, $sql) = @_; my @types; $sql =~ s{ \% ( \% | d | f | s ) }{ $1 eq '%' ? '%' : (push @types, $type{$1}) && '?' }exgo; my $sth = $dbh->prepare($sql) or croak $dbh->errstr; return bless { sth => $sth, types => \@types, }, $class; } sub handle { return $_[0]->{sth}; } sub execute { my $self = shift; for my $i (0 .. $#_) { my $type = $self->{types}->[$i]; if (defined $type) { $self->handle->bind_param(1 + $i, $_[$i], $type); } else { $self->handle->bind_param(1 + $i, $_[$i]); } } return $self->handle->execute(); } sub AUTOLOAD { my $self = shift; my $name = our $AUTOLOAD; $name =~ s{.*::}{}o; return if $name eq 'DESTROY'; return $self->handle->$name(@_); } 1; __END__ =head1 NAME DBIx::Prepare::Printf =head1 SYNOPSIS use DBI; use DBIx::Prepare::Printf; my $dbh = DBI->connect('DBI:SQLite:test.db'); my $sth = DBIx::Prepare::Printf->prepare( $dbh, 'SELECT * FROM t WHERE str = %s OR int = %d OR float = %f' ); $sth->execute('string', 1, 1.1e1); =cut
何気に oku さんのコードをかりまくりです。あと DBD::SQLite でしかためしてない(型なし!)ので,型まわりがほんとにこれでうまくいくのかわかりません。
んでもこれ,結局 execute() に手間がかかっちゃってパフォーマンス悪そう*2 *3。
で,今気づいたんですけど,型を指定してパフォーマンスがあがるとうれしい例って select とかせいぜい update とかであって,prepare() して出来上がった(prepared)statement object に何回も execute() する状況があんまりない気がしてきました。oku さんのアプローチで充分なのか。くやしい!
2007/09/29 追記
execute をなるたけ軽くするというコンセプトなら prepare でコード展開して eval するというこんな感じのほうがいいかも。
sub prepare { my ($class, $dbh, $sql) = @_; my @types; $sql =~ s{ \% ( \% | d | f | s ) }{ $1 eq '%' ? '%' : (push @types, $type{$1}) && '?' }exgo; my $sth = $dbh->prepare($sql) or croak $dbh->errstr; my $src = 'sub {my $sth = shift;'; my $i = 0; foreach my $type (@types) { $src .= sprintf '$sth->bind_param(%u, shift%s);', ++ $i, defined $type ? sprintf(', %d', $type) : q{}; } $src .= '$sth->execute();}'; my $code = eval $src; return bless { sth => $sth, exec => $code, }, $class; } sub execute { my $self = shift; return &{ $self->{exec} }($self->{sth}, @_); }
execute を複数回実行しないとうまみがでないのは変わりませんが。