プロジェクト

全般

プロフィール

Bug(バグ) #3135

Youichi Kimura10年以上前に更新

h3. Overview (現象)

Doctrine でサブクエリの WHERE 節に @filed IN ?@ の形式のものがあると、 DQL から SQL を生成する際に Invalid parameter number のエラーが発生する。

<pre>
SELECT 〜 WHERE field = (SELECT field FROM table WHERE field IN ?);
</pre>

<pre>
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
</pre>

次のようにパラメーター数分の @?@ が DQL 時点で展開されている場合はエラーは発生しない。

<pre>
SELECT 〜 WHERE field = (SELECT field FROM table WHERE field IN (?, ?, ?));
</pre>

具体的には以下のようなコードでエラーが発生する。

*例1* : サブクエリの生成に andWhereIn() を利用する場合
<pre>
opActivateBehavior::disable(); // あってもなくてもエラー

$q = Doctrine_Core::getTable('MemberConfig')->createQuery('c');

$q2 = $q->createSubquery()
->select('m.id')
->from('Member m')
->andWhereIn('m.id', array('1', '2'));

$q->andWhere('c.member_id IN ('.$q2->getDql().')');
$q->execute();
</pre>

*例2* : サブクエリを手動で記述する場合
<pre>
opActivateBehavior::disable(); // あってもなくてもエラー

$q = Doctrine_Core::getTable('MemberConfig')->createQuery('c');

$q->andWhere('c.member_id IN (SELECT m.id FROM Member m WHERE m.id IN ?)', ?', array(array('1', '2')));
$q->execute();
</pre>

※ただし、上の2つの例は APC のキャッシュが有効な状態では2回目以降のアクセスで成功していた

本問題は基本的には Doctrine のバグである可能性が高いが、例1 に関しては opDoctrineQuery を利用しない場合はエラーが発生しないため、OpenPNE の問題とも考えられる可能性がある。

本問題は #3052 の調査中に発見した。(#3052 は 例1 のケースに該当する)

h3. Causes (原因)

DQL から SQL を生成するタイミングで @filed IN ?@ の形式は、 @field IN (?, ?, ?)@ のようにパラメーター配列の要素数に応じた形式に変換されるが、サブクエリ内の変換処理を行なう際にパラメーターを正しく扱えていないようで、要素数を間違ったり、パラメーターが展開されなかったりでエラーになる。

opDoctrineQuery::andWhereIn() は、パフォーマンスチューニングのため @filed IN ?@ の形式の DQL を生成するが (#991)、親クラスの Doctrine_Query_Abstract::andWhereIn() をそのまま使う場合は DQL 時点でパラメーターの展開が行われるためこの問題が発生しない。

h3. Way to fix (修正内容)

例1 だけを修正するのであれば、以下のようにサブクエリ利用前提の場合はパフォーマンスチューニングのためのコードを利用しないという修正案が考えられます。

<pre>
--- a/lib/util/opDoctrineQuery.class.php
+++ b/lib/util/opDoctrineQuery.class.php
@@ -189,6 +189,11 @@ class opDoctrineQuery extends Doctrine_Query
}
}

+ if ($this->isSubquery())
+ {
+ return parent::andWhereIn($expr, $params, $not);
+ }
+
$this->addWhereInCount(count($params));

if ($not)
</pre>

ただし、この修正では例2は動作しないため修正としては不十分かもしれない。

戻る