Bug(バグ) #3135
未完了サブクエリのWHERE節に「field IN ?」の形式のDQLがあると Invalid parameter number エラーが発生する
0%
説明
Overview (現象)¶
Doctrine でサブクエリの WHERE 節に filed IN ?
の形式のものがあると、 DQL から SQL を生成する際に Invalid parameter number のエラーが発生する。
SELECT 〜 WHERE field = (SELECT field FROM table WHERE field IN ?);
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
次のようにパラメーター数分の ?
が DQL 時点で展開されている場合はエラーは発生しない。
SELECT 〜 WHERE field = (SELECT field FROM table WHERE field IN (?, ?, ?));
具体的には以下のようなコードでエラーが発生する。
例1 : サブクエリの生成に andWhereIn() を利用する場合
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();
例2 : サブクエリを手動で記述する場合
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();
※ただし、上の2つの例は APC のキャッシュが有効な状態では2回目以降のアクセスで成功していた
本問題は基本的には Doctrine のバグである可能性が高いが、例1 に関しては opDoctrineQuery を利用しない場合はエラーが発生しないため、OpenPNE の問題とも考えられる可能性がある。
本問題は #3052 の調査中に発見した。(#3052 は 例1 のケースに該当する)
Causes (原因)¶
DQL から SQL を生成するタイミングで filed IN ?
の形式は、 field IN (?, ?, ?)
のようにパラメーター配列の要素数に応じた形式に変換されるが、サブクエリ内の変換処理を行なう際にパラメーターを正しく扱えていないようで、要素数を間違ったり、パラメーターが展開されなかったりでエラーになる。
opDoctrineQuery::andWhereIn() は、パフォーマンスチューニングのため filed IN ?
の形式の DQL を生成するが (#991)、親クラスの Doctrine_Query_Abstract::andWhereIn() をそのまま使う場合は DQL 時点でパラメーターの展開が行われるためこの問題が発生しない。
Way to fix (修正内容)¶
例1 だけを修正するのであれば、以下のようにサブクエリ利用前提の場合はパフォーマンスチューニングのためのコードを利用しないという修正案が考えられます。
--- 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)
ただし、この修正では例2は動作しないため修正としては不十分かもしれない。
Yuya Watanabe さんが11年以上前に更新
- 3.8 で発生するか を Unknown (未調査) にセット
原因¶
test/unit/util/opActivityQueryBuilderTest.php のテストを動かしたときに下記のようなクエリが MySQL で受け取られている.ここで WHERE NOT IN の部分を見ると 'Array' という値が入っていることがわかる.
SELECT a.id AS a__id, a.member_id AS a__member_id, a.in_reply_to_activity_id AS a__in_reply_to_activity_id, a .body AS a__body, a.uri AS a__uri, a.public_flag AS a__public_flag, a.is_pc AS a__is_pc, a.is_mobile AS a__is_mobile, a.source AS a__source, a.source_uri AS a__source_uri, a.foreign_table AS a__foreign_table, a.foreign_id AS a__foreign_id, a.template AS a__template, a.template_para m AS a__template_param, a.created_at AS a__created_at, a.updated_at AS a__updated_at, m.id AS m__id, m.name AS m__name, m.invite_member_id AS m__invite_member_id, m.is_login_rejected AS m__is_login_rejected, m.created_at AS m__created_at, m.updated_at AS m__updated_at, m.is_active AS m__is_active FROM activity_data a LEFT JOIN member m ON a.member_id = m.id WHERE (a.member_id IN (SELECT m2.member_id_to AS m2__member_id_ to FROM member_relationship m2 WHERE (m2.member_id_from = '1' AND m2.is_friend = 1 AND m2.member_id_to NOT IN ('Array'))) AND a.public_flag $ = '2') AND (a.foreign_table IS NULL OR a.foreign_table <> "community") AND (m.is_active = '1' OR m.is_active IS NULL) ORDER BY a.id DESC
該当箇所をみてみると,配列かどうかについては特に確認していない.
178 public function andWhereIn($expr, $params = array(), $not = false) 179 { 180 if (isset($params) && (count($params) == 0)) 181 { 182 if (!$not) 183 { 184 return $this->andWhere('0 = 1'); 185 } 186 else 187 { 188 return parent::andWhereIn($expr, $params, $not); 189 } 190 } 192 $this->addWhereInCount(count($params)); 193 194 if ($not) 195 { 196 $this->andWhere($expr.' NOT IN ?', array($params)); 197 } 198 else 199 { 200 $this->andWhere($expr.' IN ?', array($params)); 201 } 202 203 return $this; 204 }
原因としては,おそらく Doctrine_Query の andWhereIn() に渡される際にネストされない配列であるべきものがネストされて渡されていたためとおもわれる.
実際に下記のような修正を適用し, f9eaaaf97258be8b38d4b6e9e36d79195481d5fc の修正のみを revert したコードでテストを実行した場合にすべてのテストケースをパスした.
diff --git a/lib/util/opDoctrineQuery.class.php b/lib/util/opDoctrineQuery.class.php index 7e5db73..fc9757e 100644 --- a/lib/util/opDoctrineQuery.class.php +++ b/lib/util/opDoctrineQuery.class.php @@ -191,13 +191,18 @@ class opDoctrineQuery extends Doctrine_Query $this->addWhereInCount(count($params)); + if (!is_array($params)) + { + $params = array($params); + } + if ($not) { - $this->andWhere($expr.' NOT IN ?', array($params)); + $this->andWhere($expr.' NOT IN ?', $params); } else { - $this->andWhere($expr.' IN ?', array($params)); + $this->andWhere($expr.' IN ?', $params); } return $this;
追記)
これでもいいらしい
diff --git a/lib/util/opDoctrineQuery.class.php b/lib/util/opDoctrineQuery.class.php index 7e5db73..106c051 100644 --- a/lib/util/opDoctrineQuery.class.php +++ b/lib/util/opDoctrineQuery.class.php @@ -193,11 +193,11 @@ class opDoctrineQuery extends Doctrine_Query if ($not) { - $this->andWhere($expr.' NOT IN ?', array($params)); + $this->andWhere($expr.' NOT IN ?', (array)$params); } else { - $this->andWhere($expr.' IN ?', array($params)); + $this->andWhere($expr.' IN ?', (array)$params); } return $this;
Yuya Watanabe さんが11年以上前に更新
調査中に別のバグっぽい挙動を見つけたので報告しました.
Bug(バグ) #3338: Doctrine のサブクエリの中でエイリアスを指定した場合に正しくないSQLが発行される場合がある
https://redmine.openpne.jp/issues/3338
Yuya Watanabe さんが11年以上前に更新
調査中に別のバグっぽい挙動を見つけたので報告しました.
Bug(バグ) #3339: Doctrine で WHERE IN を用いたサブクエリの場合に配列が正しく展開されない
https://redmine.openpne.jp/issues/3339
Youichi Kimura さんが約11年前に更新
- 説明 を更新 (差分)
Doctrineに対して下記の修正を施すことで、概要に書かれているコードは動作するようになりました。他のSNS機能に影響がないかどうかは未確認です。
https://github.com/upsilon/doctrine1/compare/op3135_subquery-where-in-bug
Youichi Kimura さんが約11年前に更新
OpenPNE で独自に note-5 のような修正を施すことは問題ありませんが、Doctrine1 は LGPL なのでプロプライエタリな利用で先行して note-5 の修正を取り込むことは避けた方が良いかもしれません。
Yuya Watanabe さんがほぼ11年前に更新
いろいろなところに影響が出ているので #991 の修正として追加されている opDoctrineQuqyer の andWhereIn() 自体を一旦削除してしまうというのも手かもしれない.