私が歌川です

@utgwkk が書いている

go-sql-driver/mysqlのプレースホルダ置換を有効にしつつJSONカラムを使うときは[]byte型を経由しない

tl;dr

  • JSONカラムをマッピングするときは []byte 型を使わない
  • string 型や json.RawMessage 型を使う

起こっていたこと

Goのwebアプリケーションで、go-sql-driver/mysqlの interpolateParams=true オプションを有効にしてテストを回したら、以下のようなエラーでテストが落ちた。 interpolateParams=true はプレースホルダ置換を有効にするオプションである*1

Cannot create a JSON value from a string with CHARACTER SET 'binary'.

アプリケーションのコードは interpolateParams=true を指定しただけで他の箇所は書き換えていない。MySQLと通信するときのcharacter setに binary を指定した覚えは全くない。何が起こっていたのか?

原因と解決策

DBの行をマップしているstructの構造をかいつまんで書くと以下の通り。JSON型のカラムを書き込むところでエラーになっていた。

type User struct {
  Id   string     `db:"id"`
  Data JsonColumn `db:"data"` // JSON NOT NULL
}

// JsonColumn は driver.Valuer, sql.Scanner を実装している
// 内部的に json.Marshal, json.Unmarshal を呼んでいる
type JsonColumn []byte

出てきたエラーメッセージで検索してみると以下のissueがヒットした。 JSON型のカラムをやり取りするときは []byte 型ではなく string 型や json.RawMessage 型を使うようにするといいらしい。

github.com

ということで、筆者が関わっているプロダクトではstructのフィールドの型を自由に調整できるのでこれで解決しました。

何が起こっていたのか

これだけで記事を終わっても備忘録*2としてはいいだろうけど、ついでなのでプレースホルダ置換を有効にしてエラーになったとき何が起こっていたのか見てみる。

mysqlConn structの interpolateParams メソッドの []byte 型に対する処理を読んでみると、文字列リテラルの前に _binary というのを書いていて、いかにもバイナリにしていそう。

github.com

これはイントロデューサというもので、文字列リテラルのcharacter setを指示しているらしい。謎が解けましたね。

dev.mysql.com