CakePHP2.3のfind()に、行ロック機能を追加してみた

こんにちは、株式会社CFlatです。

CakePHPのコアライブラリを弄ります。CakePHPのアップデート時などには注意して下さい。また、修正は自己責任でお願いします。

 PHP用のフレームワークは数多かれど、中でもそこそこ名前の挙がるCakePHP[1]。単純なWebアプリケーション(とも言えない程度のHP)を作るなら素早く実装できるのですが、ちょこっと真面目にSQLを弄り出すと、途端に不満が続出してきます[2]。
 行ロックくらいは寄越せや、ってことで、微妙に古めですが手元のCakePHP2.3.1をいろいろ弄ってみることにしました[3]。DBにはMySQLを使います。

方針

・$this->Model->find()の第二引数に 'lock' => ... という項目を追加すれば、ロックができるようにする
・'lock' => 'read' では、読み取ったデータの一貫性を保証する行ロック(SELECT ... LOCK IN SHARE MODE)を行う
・'lock' => 'write' では、これから更新する可能性のあるデータの一貫性を保証する行ロック(SELECT ... FOR UPDATE)を行う

修正するファイル

・lib/Cake/Model/Datasource/DboSource.php
・lib/Cake/Model/Datasource/Database/Mysql.php

DboSource.phpの修正点

・protected $_queryDefaults = array(...) ;
 最後にでも 'lock' => null の項目を追加しておきます。
・public function generateAssociationQuery(Model $model, $linkModel, $type, $association, $assocData, &$queryData, $external, &$resultSet) {...}
 とりあえず 'group' という文字列を探して、何やら連想配列作ってるところに 'lock' => $queryData['lock'] あたりを追加しておきます。
・public function buildStatement($query, $model) { ... }
 こちらも同じです。renderStatement() の引数に 'lock' => $query['lock'] を追加しておきましょう。
・protected function _scrubQueryData($data) { ... }
 array_fill_keys() の配列に、'lock' を追加しておきます。

 以上で、オプションとして与えられた 'lock' が途中で削除されることなくMySQLのレイヤーまで渡されることになります。

Mysql.phpの修正点

・public function renderStatement($type, $data) { ... } の追加
 SELECT ... LOCK IN SHARE MODE / SELECT ... FOR UPDATE の構文はMySQL独自のものなので、これらのSQL文を構築するコードはDboSource.phpではなくMysql.phpの中で行うこととします。

public function renderStatement($type, $data)
{
$statement = parent::renderStatement($type, $data) ;
$type = strtolower($type) ;
if ($type === 'select') {
if (is_string($data['lock'])) {
$lock = strtolower($data['lock']) ;
if ($lock === 'read') {
return $statement . ' LOCK IN SHARE MODE' ;
}
elseif ($lock === 'write') {
return $statement . ' FOR UPDATE' ;
}
}
}
return $statement ;
}

結果

 ちゃんとこんな感じのSQLが発行されました。

 SELECT `Hoge`.`id` FROM `database`.`hoges` AS `Hoge` WHERE `id` = 1 LIMIT 1 LOCK IN SHARE MODE
 SELECT `Hoge`.`id` FROM `database`.`hoges` AS `Hoge` WHERE `id` = 1 LIMIT 1 FOR UPDATE


[1] 正直、あまりよくできたフレームワークだとは思わないのですが……特にModel周り。メリットといえば、PHP4でも動くとか、日本語の資料がよく見つかる(ただし糞ったれなことに1.x系に限る)とかくらい?
[2] ホントよりにもよって、どうしてこれ選んじゃったんだろう……? という後の祭り。
[3] 行ロックの前に、まずはトランザクション機能を実装してやらねばならないのですが、そちらは調べればそこそこ出てくるので割愛します。