先日、Catalystアプリを作っていたとき、データの新規作成と編集を同じメソッドで処理していて、このようなコードを書きました。

my $rs = $c->model('DBIC::Hoge');
$rs->update_or_create(
    {   
        id   => $id, # primary key
        name => $name,
    },
    {},
);

hogeテーブルはこんな感じ。
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(255) | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+

update_or_create メソッドを使っています。これは、primary key である id に対して、id = $id の条件を満たすデータが存在するかを検索します。そしてデータがあれば update 、データが無ければ、insert するという便利メソッドです。
※ちなみに、通常は primary key でデータが存在するかどうが検索しますが、add_unique_constraint制約を付ければ、任意のキーでデータを検索することが出来ます。
これについては、こことかこことかに詳しく書かれているので参考にどーぞ。

今回の場合、$id は数字が入っているときと undef のときがあります。数字が入っている場合(データ編集時)には select でデータが存在するので、そのデータの name カラムが update されます。

問題は $id が undef のとき(新規作成時)です。このときにはまず、以下のような select が走ります。

SELECT `me`.`id`, `me`.`name` FROM `hoge` `me` WHERE ( ( `me`.`id` IS NULL ) AND ( `me`.`delete_flag` = ? ) ): '0'

実はこれが曲者です!! id が NULL のデータなんて無いから必ず insert するだろうと思っていました。思っていましたが実際に試してみると、、、

U字工事 なんかときどき update してるんですけどー。

すいません、脱線しました。まぁ、、こういう謎な問題があってとても困っていたわけです。「なんか昔こんなの見たことあるなぁ...orz 」と思ってブックマークを漁った結果見つけました。こちらのサイトです。えらい、えらいぞ自分。よく頑張った(´Д⊂)

ちなみに、リファレンスにも載っていました。。
一番最近の AUTO_INCREMENT 値を含む行を、その値を生成した直後に、次のフォームのステートメントを発行することによって検索することができます :

SELECT * FROM tbl_name WHERE auto_col IS NULL

この動作は、SQL_AUTO_IS_NULL=0 を設定すると無効になります。項12.5.3. 「SET 構文」 を参照してください。

なんですかこの謎の仕様は!?こんなの知りませんよー。・゚・(ノД`) なぜデフォルトで 0 じゃないのかと小一時間(ry とりあえず yaml に指示通りに設定してみますた。

---
Model::DBIC:
  schema_class: MyApp::Schema
  connect_info:
    - dbi:mysql:db_test
    - user
    - pass
    - quote_char: '`' # テーブル名をバッククォートで囲む(予約語回避のため)
      name_sep: '.'
      on_connect_do:
        - 'SET SQL_AUTO_IS_NULL = 0'

とりあえず解決しました!!ヾ(o゚ω゚o)ノ゙

ちなみに on_connect_do には問題があるようですね。。詳しくはこちらのサイトに書かれています。
DBD::mysqlのmysql_auto_reconnectが真だとDB再接続時にDBICのon_connect_doが実行されない件

この設定もしといた方がいいのかなー。とか考えていたんですが、ふとこんなアイディアが・・・

my $rs = $c->model('DBIC::Hoge');

if ( $id ) {
    $rs->find( $id )->update(
        {   
            name => $name,
        },
        {},
    );
}
else {
    $rs->create(
        {   
            id   => $id,
            name => $name,
        },
        {},
    );
}

そもそも $id の値が真かどうかで、update するか create するかは決まりますからね。ちょっとコードは長くなりますけど安全な気がします。じゃあここまでの話はなんだったのかと。(^^; にしても、この仕様は危険すぎるので、MySQLを使う人だったら知らないとはまる可能性が高いんじゃないでしょうか?

ちなみに以下は検証してみた結果です。

mysql> insert into hoge values ( null, "name_1" );
Query OK, 1 row affected (0.06 sec)


-- 初回のみデータが返ってきます
mysql> select * from hoge where id is NULL;
+----+--------+
| id | name   |
+----+--------+
|  1 | name_1 |
+----+--------+
1 row in set (0.05 sec)


-- 2 回目はデータが返ってきません
mysql> select * from hoge where id is NULL;
Empty set (0.01 sec)


mysql> SET SQL_AUTO_IS_NULL = 0;
Query OK, 0 rows affected (0.00 sec)


mysql> insert into hoge values ( null, "name_2" );
Query OK, 1 row affected (0.02 sec)


-- SQL_AUTO_IS_NULLを 0 に設定したので、データが返ってきません
mysql> select * from hoge where id is NULL;
Empty set (0.00 sec)
このエントリーをはてなブックマークに追加