文字コード変換

少々理解があやふやだったので整理しました.

INSERT時の変換する/しないは,

・Connector/J側のcharacterEncodingの設定内容(サーバ変数character_set_client)
・テーブル側の文字コード設定内容

SELECT時の変換する/しないは,

・テーブル側の文字コード設定内容
・Connector/J側のcharacterSetResultsの設定内容(サーバ変数character_set_results)

それぞれ異なる場合にはUnicodeを通して変換するが、同じ場合には変換しない.

(character_set_resultsに関してはNULLであれば同じであるかどうかに関わらず変換されない)

Connector/JにいろいろDebugログを出させるコードを突っ込んでいろんな組み合わせで動かして見てわかったのですが,

以下の状況で"㈱日本"(かっこ株日本)という文字を格納(INSERT)し,

[Connector/J]  characterEncoding=WINDOWS-31J
[MySQL Server] character_set_client=cp932
[MySQL Server] テーブルcreate時のdefault charset=cp932

新しい以下の状況の接続でそのデータを読んだ(SELECT)場合、

[Connector/J]  characterEncoding=ASCII
[MySQL Server] character_set_client=ascii
[Connector/J]  characterSetResults設定せず
[MySQL Server] character_set_results=NULL

ResultSet.getString()で"㈱日本"(かっこ株日本)がJava側で文字化けせずに取得できました.読む際に変換をしていないので,格納する際に文字化けしなければ上手くいくわけです.ちなみにこの設定で取ってきたデータのFieldヘッダーに書いてある文字コードは"cp932(WINDOWS-31J)"です.

しかし似たようなケースで以下の状況で読んだ場合には、

[Connector/J]  characterEncoding=ASCII
[MySQL Server] character_set_client=ascii
[Connector/J]  characterSetResults=ASCII
[MySQL Server] character_set_results=ascii

サーバ側でasciiに変換されて"???"という状態で帰ってきました.実際に帰ってきたバイト列を見ると[63,63,63]になっていたのでConnector/J側の変換による文字化けではないことが分かります.またこのときのFieldに書いてある文字コードはASCIIです.

サーバ側でデータの文字コードそのものを変換し(このとき???になる),さらにFieldのヘッダーを元のテーブルのcp932からasciiに付け替えた上で結果としてデータを送り返してきたわけです.

ここ数日,自分的に話題となっているcharacter_set_resultsですが,Connector/Jでは接続時にNULLを設定します.つまりデフォルトではSELECT時のデータ変換は行わない設定になっています.特殊なケースを除いて(特に日本人は)変換して欲しくない人が多いでしょうから,このデフォルトの振る舞いで良く,従ってまたcharacterSetResultsを使うケースも少ないでしょう.しかもバグってるのでなおさらです.

いろいろ寄り道しましたが,そろそろパッチ作りたいと思います.INDEX_TO_CHARSETの動的拡張の仕組み自体に手を入れるほどの理由が見当たらないのと,SHOW VARIABLESやSHOW COLLATIONで帰ってくるデータはメタデータなのでutf8ときまっていることから,単にSET NAMESおよびSET character_set_resultsを発行するタイミングを後ろに変えれば良いと今のところ考えています.

追記:
テーブル側をわざとasciiで定義し,character_set_client=cp932で"㈱日本"をINSERTしようとするとエラー"Data Truncation"になります.同じ設定で"hogehoge"をINSERTしようとすると成功し,SELECTもできます.