2012年11月28日

最初の方に追記しておくと生DBI限定の話です。僕のしらない wrapper モジュールだと動くのかもしれません。
けど、こういうエントリを僕が書いてる時点で、そういうモジュールは筋悪いんだとおもいます。

人のプロジェクトのコードを見てたら

my $dbh = DBI->connect('dbi:mysql:dbname=geekdb;sql-mode=STRICT_TRANS_TABLES', 'yappo', 'moneyfriend', %attr);
みたいな感じの DSN があったんですよね。
他にも attribute てんこもりだったんで、それコミットした人に聞いて見たら「秘伝のタレです」的なのが帰ってきたので、僕も何も考えないで「これでSTRICT_TRANS_TABLES効いたら、まぁ捗るから付けとこう」って感じでコピペして付けてたんだけど、さいきん鉄板 DBI 選手権あったのでコメント書いたら「有効になるわけねーだろ!」的な椅子が飛んできて、調べたら確かに意味ない設定でした。

エントリ書いてからもっかい確かめようと思って実装みてたんですが、延々と XS コード出てきて説明面倒になってきたので禿げてないでコード書いてみた。
DBI 1.622
DBD::mysql 4.022

だめなばあい

MySQL のドキュメントを見ると「STRICT_TRANS_TABLES は厳格なんで ENUM に定義してない値とかいれてもエラーだよ」って書いてあるので、分かりやすく ENUM で定義されてないデータを突っ込んでみる。

use strict;
use warnings;
use 5.012;
use DBI;

my $dbh = DBI->connect(
    'dbi:mysql:dbname=geekdb;sql-mode=STRICT_TRANS_TABLES', 'yappo', 'moneyfriend',
    +{
        PrintError => 0,
        RaiseError => 1,
        AutoCommit => 1,
    }
);

$dbh->do(q{DROP TABLE IF EXISTS geeks});
$dbh->do(q{CREATE TABLE geeks (                                                                             
    geek ENUM('matz', 'dan') NOT NULL                                                                          
)});


$dbh->do(q{INSERT INTO geeks (geek) VALUES('dan')});
my($geek1) = $dbh->selectrow_array('SELECT geek FROM geeks LIMIT 1');
say $geek1;
$dbh->do(q{DELETE FROM geeks});

$dbh->do(q{INSERT INTO geeks (geek) VALUES('hyoshiok')});
my($geek2) = $dbh->selectrow_array('SELECT geek FROM geeks LIMIT 1');
say $geek2;

これを実行すると、普通に実行されてるが $geek1 で dan を表示してるのに $geek2 では何も表示しないで終わってる。
これは hyoshiok という文字列が geek の ENUM に定義されてないから、なにも入れられてないって事で STRICT_TRANS_TABLES が有効になってなさそう。。。。!

大丈夫な例

椅子とともに SET SESSION sql_mode=... しろと言われたので、 connect 時に SQL を打ってみる。
ここで何も考えないで $dbh->do つかってやっちゃうと reconnect した時とかに無効になるので、筋良く Callbacks オプションを使う。
説明は椅子プロさんの解説が Perl Advent Calendar に乗ってた!

use strict;
use warnings;
use 5.012;
use DBI;

my $dbh = DBI->connect(
    'dbi:mysql:dbname=geekdb;sql-mode=STRICT_TRANS_TABLES', 'yappo', 'moneyfriend',
    +{
        PrintError => 0,
        RaiseError => 1,
        AutoCommit => 1,
        Callbacks  => +{
            connected => sub {
                my $dbh = shift;
                $dbh->do(q{SET SESSION sql_mode=STRICT_TRANS_TABLES});
                return;
            },
	},
    }
);

$dbh->do(q{DROP TABLE IF EXISTS geeks});
$dbh->do(q{CREATE TABLE geeks (                                                                             
    geek ENUM('matz', 'dan') NOT NULL                                                                          
)});

$dbh->do(q{INSERT INTO geeks (geek) VALUES('dan')});
my($geek1) = $dbh->selectrow_array('SELECT geek FROM geeks LIMIT 1');
say $geek1;
$dbh->do(q{DELETE FROM geeks});

$dbh->do(q{INSERT INTO geeks (geek) VALUES('hyoshiok')});
my($geek2) = $dbh->selectrow_array('SELECT geek FROM geeks LIMIT 1');
say $geek2;

今度のケースを実行すると。。。

dan
DBD::mysql::db do failed: Data truncated for column 'geek' at row 1 at ./hoge.pl line 32.
ちゃんとエラーが出てくれてて STRICT_TRANS_TABLES が有効になってるっぽい!111

まとめ

DSN を良きにしてくれる O/R Mapper とかあるのかなとおもって DBIC みてもそういう実装じゃないし、そもそも DSN 変な拡張入れる筋悪なの使う人は迷惑なので、実際なくて良かった。

ということで、接続してから

SET SESSION sql_mode=...;
すべし、と椅子プロさんに指摘頂きました。

本日の教訓

コピペでプロジェクト始めるやつは空気椅子で仕事してろや!

Posted by Yappo at 2012年11月28日 13:37 | TrackBack | Perl
Comments
Post a comment









Remember personal info?






コメントを投稿する前に↓の場所にnospamと入力してください。