読者です 読者をやめる 読者になる 読者になる

風柳メモ

ソフトウェア・プログラミング関連の覚書が中心。

MDB2で接続しなおしたときだけ、通らなくなるクエリがあって悩んだ

PHP上でMDB2を使用してMySQLにアクセスしているとき、あるタイミングでだけ、通らなくなるクエリ(プリペアドステートメント)が出てきて原因がわからず、ほぼ半日無駄にしてしまった。



■追記

MDB2: 2.5.0b5 (beta)
MDB2_Driver_mysql: 1.5.0b4 (beta)

を入れたら、あっさりと不具合が解消されました、くそう……。
でもWeb版PEARからだとアップデートできなかったので、無理やり上書きなんだよな、ちとそれが不安…。



結論としては、

  • 処理の途中で、MDB2のコネクションを張りなおすと、特定のクエリ(プリペアドステートメント)が通らなくなる。
  • 具体的には、$i:=0 のようなユーザ変数を含んだプリペアドステートメントを使うと発生する。
    一見、「:=」とプレースホルダーを混同しているかのようにも思えるが、それにしては、一回目の接続時には問題なく通るのが解せない…。
    →【追記】プリペアドステートメントを使わずに&$mdb2->query($query);で直接クエリ発行した場合は何回目でも正常に動作するので、やはりプリペアドステートメントの解釈部分の不具合のようだ。
  • 対処方法は…とにかく一通りの処理が終わるまで、MDB2のコネクションを維持しておくしかない?

ということ、らしい。うーむ。

ちなみにバージョンは、

MDB2: 2.4.1 (stable)
MDB2_Driver_mysql: 1.4.1 (stable)
MySQLサーバ:5.0.51a-log - MySQL Community Server (GPL)

MDB2を最新にしてみたほうがいいんだろうか……(stable)じゃなくて、2.5.0b5 (beta)なのが気になるのだけれども。

再現用PHPソースコード

id, name というカラムを持つ company というテーブルに対してのSQLクエリを発行するもの。クエリ自体には意味なし。

<?php
require_once('MDB2.php');
header('Content-Type: text/plain; charset=utf-8');

$db_dsn = array(    //  DSN: Data Source Name
    'phptype'   => 'mysql',
    'database'  => '*your-database*',
    'hostspec'  => '*your-hostspec*',
    'username'  => '*your-username*',
    'password'  => '*your-password*',
    'charset'   => 'utf8',
    'new_link'  =>  true,
);

$db_options = array(
    'portability'   =>  MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL,
);

function    is_error(&$dbobj, $message='') {
    if (!PEAR::isError($dbobj)) return false;
    echo sprintf("%s\n", $message);
    echo sprintf("Standard Message: %s\n", $dbobj->getMessage());
    echo sprintf("Standard Code: %s\n", $dbobj->getCode());
    return true;
}

function    &mdb2_connect($db_dsn, $db_options) {
    for (;;) {
        $mdb2 = &MDB2::connect($db_dsn, $db_options);
        if (is_error($mdb2, 'Error in MDB2::connect()')) {
            $mdb2 = null;
            break;
        }
        $mdb2->query("SET NAMES 'utf-8'");
        $mdb2->loadModule('Extended');
        $mdb2->setFetchMode(MDB2_FETCHMODE_ASSOC);
        break;
    }
    return $mdb2;
}

function    test($message='', $query) {
    global $db_dsn, $db_options;
    
    $in_val_dict = array('company_id' => 8);
    $in_val_type_dict = array('company_id' => 'integer');
    $out_val_type_list = array('integer', 'integer', 'text');
    
    echo sprintf("■ %s: \"%s\"\n", $message, $query);
    for (;;) {
        $mdb2 = &mdb2_connect($db_dsn, $db_options);
        if (!$mdb2) break;
        
        $db_dth = &$mdb2->prepare(
            $query
        ,   $in_val_type_dict
        ,   $out_val_type_list
        );
        $db_res = &$db_dth->execute($in_val_dict);
        
        $db_dth->free();
        
        if (is_error($db_res, 'Error in $db_dth->execute()')) {
            break;
        }
        var_dump($db_res->fetchAll());
        
        break;
    }
    if ($mdb2) {
        $mdb2->disconnect();
        $mdb2->free();
        unset($mdb2);
    }
    echo "=====================================================================\n\n";
}

$query_A = "SELECT row_num, id, name FROM (SELECT *, @i:=@i+1 AS row_num FROM (SELECT id, name FROM company WHERE id = :company_id) AS t1, (SELECT @i:=0) AS t2) AS t3";
$query_B = "SELECT 1 AS row_num, id, name FROM company WHERE id = :company_id";


test('1回目', $query_A);
test('2回目', $query_B);
test('3回目', $query_A);
test('4回目', $query_B);

?>

実行結果

1回目は問題なく通ったクエリが、3回目にエラーになっている。

■ 1回目: "SELECT row_num, id, name FROM (SELECT *, @i:=@i+1 AS row_num FROM (SELECT id, name FROM company WHERE id = :company_id) AS t1, (SELECT @i:=0) AS t2) AS t3"
array(1) {
  [0]=>
  array(3) {
    ["row_num"]=>
    int(1)
    ["id"]=>
    int(8)
    ["name"]=>
    string(15) "ダミー企業"
  }
}
=====================================================================

■ 2回目: "SELECT 1 AS row_num, id, name FROM company WHERE id = :company_id"
array(1) {
  [0]=>
  array(3) {
    ["row_num"]=>
    int(1)
    ["id"]=>
    int(8)
    ["name"]=>
    string(15) "ダミー企業"
  }
}
=====================================================================

■ 3回目: "SELECT row_num, id, name FROM (SELECT *, @i:=@i+1 AS row_num FROM (SELECT id, name FROM company WHERE id = :company_id) AS t1, (SELECT @i:=0) AS t2) AS t3"
Error in $db_dth->execute()
Standard Message: MDB2 Error: unknown error
Standard Code: -1
=====================================================================

■ 4回目: "SELECT 1 AS row_num, id, name FROM company WHERE id = :company_id"
array(1) {
  [0]=>
  array(3) {
    ["row_num"]=>
    int(1)
    ["id"]=>
    int(8)
    ["name"]=>
    string(15) "ダミー企業"
  }
}
=====================================================================