Bug(バグ) #3135
Youichi Kimura さんが11年以上前に更新
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は動作しないため修正としては不十分かもしれない。