MySQLでUnicode Escape SequenceをDecodeするストアドファンクションを実装してみた。

2016.05.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

好物はインフラとフロントエンドのかじわらゆたかです。
Unicode Escape Sequenceされた文字列を含むCSVファイル等をMySQL/AuroraのLOADコマンドのみで取り込もうとすると、
ストアドファンクションを実装する必要が出てきます。
今回はそんなストアドファンクションを実装してみたといった内容です。

環境構築

今回用いた環境は下記となります。
* MySQL : 5.7.11
* Aurora : 5.6.10a

実装内容

DELIMITER //
CREATE FUNCTION STRINGDECODE(str TEXT CHARSET utf8)
RETURNS text CHARSET utf8 DETERMINISTIC
BEGIN
declare pos         int;
declare escape      char(6) charset utf8;
declare unescape    char(3) charset utf8;
set pos = locate('\u', str);
while pos > 0 do
    set escape =  substring(str, pos, 5);
    set unescape = char(conv(substring(escape,2),16,10) using utf16);
    set str = replace(str, escape, unescape);
    set pos = locate('\u', str, pos+1);
end while;
return str;
END;
//

検索していたところ、\u で始まる文字列をDecodeする実装例はあったのですが、
\uで始まる文字列をDecodeする実装例はなかったため、その点に対して手を入れてます。
参考にした\uで始まる文字列をDecodeするのは下記になります。
STRINGDECODE: MySQL function to decode unicode escapes to utf8

実際にやっていることとしては単純です。 まず、\uのがどこにあるかその場所を探します。

次に、見つけたその場所から5文字を抽出し、escapeという変数に格納します。
なお、下記の例にある\u30d5 の場合escapeに格納されるのは、u30d5となります。

変数に格納したescapeからuを取り除いた値(30d5)を16進数から10進数に変換します。
変換すると12501となります。
その後文字列関数charを用いて、12501をutf16として変換した文字列を変数unescapeに格納します。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.5 文字列関数

文字列全体から、変数escapeを変数unescapeに置き換えます。

その後、\uの検索からの流れを全体に実施し、実施後の文字列を返すといった流れになります。

確認

以下の様なテキストファイルをLOAD DATA INFILEを用いて取り込みたいと思います。
なお、内容は弊社横田のPythonでUnicodeエスケープシーケンスをUnicodeキャラクタに変換する | Developers.IOの記事から用いました。

UnicodeEscapeSequence.txt

\u30d5\u30a1\u30a4\u30eb\u30b5\u30a4\u30ba\u304c\u5927\u304d\u3044

上記がデコードされれば、ファイルサイズが大きいといった文字列となります。

MySQLで普通には取り込めないことを確認します。

drop table if exists LoadUnicodeEscapeSequence cascade;
create table LoadUnicodeEscapeSequence (
    message  character varying(1000)
);
LOAD DATA LOCAL INFILE '/tmp/UnicodeEscapeSequence.txt' INTO TABLE LoadUnicodeEscapeSequence;
SELECT * FROM LoadUnicodeEscapeSequence;

結果

message
u30d5u30a1u30a4u30ebu30b5u30a4u30bau304cu5927u304du3044

Decodeされていないそのままの文字列で取り込めてしまっています。

最後に、作成したストアドファンクションを用いて取り込みたいと思います。

drop table if exists LoadUnicodeEscapeSequence cascade;
create table LoadUnicodeEscapeSequence (
    message  character varying(1000)
);
LOAD DATA LOCAL INFILE '/tmp/UnicodeEscapeSequence.txt' INTO TABLE LoadUnicodeEscapeSequence 
FIELDS TERMINATED BY ',' (@message) SET message = STRINGDECODE(@message);
SELECT * FROM LoadUnicodeEscapeSequence;

結果

message
ファイルサイズが大きい

Decodeされて取り込まれていることが確認できました。

もちろんAuroraでも動きます。
なお、Auroraでの結果は上記のMySQLと同一なので省略します。

まとめ

VPC内に配置したLambdaでS3に配置したCSVを取り込ませようとした時に、この問題に遭遇しました。
同じ問題に悩んでいる人の一助になれば幸いです。