プロジェクト

全般

プロフィール

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は動作しないため修正としては不十分かもしれない。

戻る