node-mysqlを利用してEUC-JPなテーブルを読む


## はじめに

Node.jsのMySQL接続用モジュールである「[node-mysql](https://github.com/felixge/node-mysql)」はUTF-8以外の文字コードをうまく扱うことが出来ない。
[このIssue](https://github.com/felixge/node-mysql/issues/804)で問題にしてあるが、まだ未対応のようだ。
この投稿ではnode-mysqlモジュールに手を加えて、せめてデータを読むだけでもやれるようにする。

タイトルではEUC-JPと銘打っているが、恐らくShift JISでも同様にすれば動くと思われる。

## patch

問題点等はさておき、とりあえずはこのパッチを当てればUTF-8以外の文字コードが格納されているフィールドも読むことが出来る。

```diff
diff --git a/lib/ConnectionConfig.js b/lib/ConnectionConfig.js
index c7232e7..5448304 100644
--- a/lib/ConnectionConfig.js
+++ b/lib/ConnectionConfig.js
@@ -54,6 +54,7 @@ function ConnectionConfig(options) {
   this.charsetNumber = (options.charset)
     ? ConnectionConfig.getCharsetNumber(options.charset)
     : options.charsetNumber||Charsets.UTF8_GENERAL_CI;
+  this.encoding = options.encoding || 'utf-8'

   // Set the client flags
   var defaultFlags = ConnectionConfig.getDefaultFlags(options);
diff --git a/lib/protocol/Parser.js b/lib/protocol/Parser.js
index 82815bd..3c6aec9 100644
--- a/lib/protocol/Parser.js
+++ b/lib/protocol/Parser.js
@@ -17,7 +17,7 @@ function Parser(options) {
   this._onError           = options.onError || function(err) { throw err; };
   this._onPacket          = options.onPacket || function() {};
   this._nextPacketNumber  = 0;
-  this._encoding          = 'utf-8';
+  this._encoding          = options.config.encoding || 'utf-8';
   this._paused            = false;
 }

@@ -296,7 +296,7 @@ Parser.prototype.parseBuffer = function(length) {
 Parser.prototype.parseString = function(length) {
   var offset = this._offset;
   var end = offset + length;
-  var value = this._buffer.toString(this._encoding, offset, end);
+  var value = this._buffer.slice(offset, end).toString(this._encoding);

   this._offset = end;
   return value;
```

Bufferから読み出す際にUTF-8決めうちで読んでいたため、接続する際のオプションに文字コードを渡せるようにした。
とはいえ、Bufferの引数で指定する文字コードにEUC-JPは指定できないため、下記使用例のように一旦「binary」を指定して[jconv](https://github.com/narirou/jconv)などを用いてUTF-8に変換してやる必要がある。
新たにオプションを増やすのではなく、charsetオプションが「UTF-8」や「UTF-16」関係以外はデフォルトでbinaryとするようにしてもよいのではないかと思ったが、デフォルト動作を変えるのもよくないと思いひとまずこのような形に落ち着いた。

また、MySQLからのレスポンスを格納しているBufferから該当フィールドのデータを取得する際、一度`slice`で切り取ってから`toString`するようにした。
理由はよく分からないが、`toString`で始点と終点を指定する方法だと想定外の位置を切り出してしまっていた。

今のところこちらの環境では正常に動いているように見えるが、まだ検証不足であるためもうしばらく使ってみてからPRするか考えようと思っている。

### 使用例

```coffeescript
mysql = require 'mysql'
jconv = require 'jconv'

conn = mysql.createConnection {
  host: "xxx.xxx.xxx.xxx",
  user: "user",
  password: "password",
  database: "database",
  charset: "UJIS",
  encoding: "binary"
}

query = conn.query "SELECT non_utf8_field FROM table"

query.on('error', (error) =>
  console.log error
).on('result', (row) =>
  buff = new Buffer row.non_utf8_field, 'binary'
  utfbuff = jconv.convert buff, "EUCJP", "UTF-8"
  console.log utfbuff.toString()
).on('end', =>
  conn.destroy()
)
```

## 問題点

### 書き込みの対応

このパッチで対応できるのはあくまでデータを読むだけ(SELECTだけ)であり、INSERT, REPLACE, UPDATE等書き込みには対応していない。
`lib/protocol/PacketWriter.js`あたりにUTF-8決めうちで書かれているため、恐らくこのあたりを修正すればよいはずだが、現状データを読めさえすればよかったためまだ修正は行っていない。

### binaryの使用

現時点で最新版である Node.js v4.2.1のマニュアルには下記のように書かれている。

[Buffer Node.js v4.2.1 Manual &amp; Documentation](https://nodejs.org/api/buffer.html#buffer_buffer)
> This encoding method is deprecated and should be avoided in favor of Buffer objects where possible. This encoding will be removed in future versions of Node.js.

いつかは分からないが、将来的にbinaryは削除されるらしい。
代替案がないか探してみたが今のところ見つけることが出来なかった。