2010年6月3日木曜日

PDOとPrepared Statementへ対応するぞ、と

こちらも長年の課題だったPrepared Statement。
なにしろ今使っているSQLマッパー(ORMなのか?)は2002年ごろ作ったコードを基にしてます。改良を加えつつ、使ってたのですが、いよいよPDOとプリペアドステートメントへ対応させることに決定。

しかし・・・PDOも、そしてPrepared Statementも、
なんて使いづらいんだ!



Prepared Statementへの対応方法

方向としては、下記のように名前つきのパラメータ(以下、ホルダーと呼称)を使って構築します。

// 古きよきqueryを使った方法 
$rdb->query( "SELECT * FROM table WHERE id='1234'" );
// Prepared Statementを使った方法
$h = $rdb->prepare( "SELECT * FROM table WHERE id=:id" );
$h->execute( array( ':id' => '1234' ) );

どこかで、'1234'と':id'を結び付けておく必要があります。
次のような関数でホルダーを一元管理することにしました。

function getHolder( &$val ) {
  static $prepare_values=array();
  if( $val ) {
    $holder = ':prep_' . count( $prepare_values );
    $this->prepare_values[ $holder ] = $val;
    $val = $holder;
  }
  return $prepare_values;
}

$id = '1234';
getHolder( $id );
$h = $rdb->prepare( "SELECT * FROM table WHERE id={$id}" );
$h->execute( getHolder() );

SQLマッパーはクラスなので実際はメソードですが、こいつをマッパーのあちこちに仕掛けておけば、簡単に対応可能、というわけです。

PDOではrowCountが使えない

薄々知ってましたが、SELECTを発行した場合、結果セットの総数が分かりません。「SELECT COUNT(*) ...」で対応しろという話です。

 仕方がないので、対応しました。
「SELECT ~ FROM...」の「~」部分をCOUNT(*)に無理やり変更。

SELECT文を保存しておいて、rowCountが呼ばれたら、裏でSQL文を変更して、発行して、数を数えて、返す。
お、動いた、動いた。

が、これとPrepared Statementを組み合わせると大変な事態に。

MySQLのHY093エラー

MySQLで動作テスト開始をしたとたんのエラーコード。
テストのXAMPP環境だとエラーメッセージすら出ない。

調べると、「無効なパラメータ番号」という意味らしい。
なんとexecuteで与える配列で使ってないデータがあるとエラーになるらしい。

どんだけ細かいんだよ!
と文句をいいながら、適度なタイミングで「$prepare_values」をクリアすることに。この適度なタイミングってのが難しい。使っているうちにぐちゃぐちゃになりそうだ。

で、気がついた。

Prepared Statementを使って数える場合、万が一COUNT(*)に変更した部分にホルダーがあったら、絶対に動かんじゃないか。たとえば

例)
SELECT :what AS what, id, name FROM table WHERE type=:type

SELECT COUNT(*) FROM table WHERE type=:type

これだとHY093エラーで動かないのは確実。

こういうSELECT文の場合、裏で上手カウントするのは無理っぽい。
使う人が、明示的にSQLを二つ作って対応するしかないか。

まだまだあるぞPDOの妙なエラー

こんなエラーメッセージもありました。

Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.

書いてある通りにアトリビュート設定しても駄目。
バージョンの問題なのか?

原因は・・・
一度に二つのSQL文を渡してたからでした。
DROP TABLE IF EXIST table;
CREATE TABLE table ( ... );

ひとつずつ発行するとエラー無しになりました。

◆感想

新しいライブラリが登場しても、すぐ飛びつかない理由を思い出した。
マニュアルに出てこないような癖があるので避けてしまう。
面白いと思う人もいるけど、私は面倒くさがりだから。

0 件のコメント: