DBD::mysql で mysql_server_prepare=1 のとき TEXT 型の欄が自動 utf8::decode されなくなる

次の環境で表題のような挙動をしており、はまってしまったので備忘録を残します。

DBD::mysql ドライバで $dbh->{mysql_enable_utf8} = 1 にすると、MYSQL_TYPE_STRING なカラムを自動的に utf8::decode() してくれます。

DBD-mysql-4.0.19/dbdimp.c 3827行目より

        case MYSQL_TYPE_STRING:
          if (DBIc_TRACE_LEVEL(imp_xxh) >= 2)
            PerlIO_printf(DBILOGFP, "\t\tst_fetch string data %s\n", fbh->data);
          sv_setpvn(sv, fbh->data, fbh->length);
          /*HELMUT*/
#ifdef sv_utf8_decode
          if(imp_dbh->enable_utf8)
              sv_utf8_decode(sv);
#endif
          break;

ところが、dsn に mysql_server_prepare=1 オプションを指定すると、TEXT 型の欄の utf8::decode() 処理がスキップされてしまうようです。このオプション指定がないときは、utf8 になってくれます。
(当初 decode_utf8() と記述してましたが、sv_utf8_decode()、すなわち sv.c の Perl_sv_utf8_decode() は、utf8::decode() の実体なので修正しておきました。)

末尾のスクリプトは、オプション -s をつけると mysql_server_prepare=1 を dsn に追加します。

$ perl exam-utf8.pl -s
DBI:mysql:database=test;host=localhost;mysql_server_prepare=1
- '1'
- utf8! 'テスト'
- 'こんにちわ! こんにちわ!'
$ perl exam-utf8.pl
DBI:mysql:database=test;host=localhost
- '1'
- utf8! 'テスト'
- utf8! 'こんにちわ! こんにちわ!'

この実行結果のように、2番目のカラムは VARCHAR(255) 型で、mysql_server_prepare=1 の有無にかかわらず、utf8::decode() されています。3番目の TEXT 型のカラムは、mysql_server_prepare=1 のときに utf8::decode() がスキップされています。

use strict;
use warnings;
use utf8;
use Getopt::Long;
use Carp;
use DBI;
use Encode;

my $server_prepare;
GetOptions('server|s' => \$server_prepare);

my $dsn = 'DBI:mysql:database=test;host=localhost';
if ($server_prepare) {
    $dsn .= ';mysql_server_prepare=1';
}
print "$dsn\n";
my $dbh = DBI->connect(
    $dsn, 'xxx', 'xxx', {
        AutoCommit => 0, RaiseError => 1, PrintError => 0,
        mysql_enable_utf8 => 1,
        on_connect_do => [
            q{SET NAMES 'utf8'},
            q{SET CHARACTER SET 'utf8'},
        ],
    },
);

{
    $dbh->do(q{DROP TABLE entries;});
    $dbh->do(q{
        CREATE TABLE IF NOT EXISTS entries (
             id INTEGER PRIMARY KEY
            ,title VARCHAR(255) CHARACTER SET utf8 NOT NULL
            ,body TEXT CHARACTER SET utf8  NOT NULL
        ) ENGINE=InnoDB CHARACTER SET utf8;
    });
    my $sth = $dbh->prepare(q{
        INSERT INTO entries VALUES (?, ?, ?);
    });
    $sth->execute(1, 'テスト', 'こんにちわ! こんにちわ!');
    $dbh->commit;
}

{
    my $sth = $dbh->prepare(q{
        SELECT * FROM entries;
    });
    $sth->execute;
    my(@row) = $sth->fetchrow_array;
    $sth->finish;
    $dbh->commit;
    for my $col (@row) {
        if (utf8::is_utf8($col)) {
            print "- utf8! '", Encode::encode('UTF-8', $col), "'\n";
        }
        else {
            print "- '$col'\n";
        }
    }
}

$dbh->disconnect;