Go の MySQL ドライバーとして go-sql-driver/mysql を使用した際の挙動にハマったので、解決策を記しておきます。
クエリを発行し、取得した整数型の値をany型の値で受け取る際に予期しない結果となることがあります。
package main import ( "database/sql" "fmt" "os" "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", (&mysql.Config{ User: "root", Passwd: "password", DBName: "rdb", Addr: "localhost:3000", AllowNativePasswords: true, }).FormatDSN()) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } var val any if err := db.QueryRow("SELECT 1").Scan(&val); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Printf("type is %T\n", val) var val2 any if err := db.QueryRow("SELECT ?", 1).Scan(&val2); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Printf("type is %T\n", val2) }
go run main.go type is []uint8 type is int64
2つのSELECT文は同じ値を返しているはずですが、プレースホルダを用いているか否かで、値の型が異なってしまいます。
期待するのは、どちらもint64になることです。
解決策
プレースホルダへのバインドが不要であってもプリペアドステートメントを使うよう実装することができます。
https://github.com/go-sql-driver/mysql/issues/407#issuecomment-172583652
下記が実装例です。
package main import ( "database/sql" "fmt" "os" "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", (&mysql.Config{ User: "root", Passwd: "password", DBName: "rdb", Addr: "localhost:3000", AllowNativePasswords: true, }).FormatDSN()) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } var val any stmt, err := db.Prepare("SELECT 1") if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer stmt.Close() if err := stmt.QueryRow().Scan(&val); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Printf("type is %T\n", val) var val2 any if err := db.QueryRow("SELECT ?", 1).Scan(&val2); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Printf("type is %T\n", val2) }
go run main.go type is int64 type is int64
解説
MySQL にはバイナリプロトコル、テキストプロトコルという2つのプロトコルがあります。
go-sql-driver/mysql は、プリペアドステートメントを使用する際にはバイナリプロトコルを使用し、そうでない場合はテキストプロトコルを使用します。
テキストプロトコルでデータを受信した場合には[]uint8
で結果を取得し、バイナリプロトコルの場合にはカラムの型に応じた Go の型で結果を取得します。
上記を踏まえると、テキストプロトコルが使用されるケースでScan()
にany
型の値を渡した際に[]uint8
型の値がアサインされるのは自然です。
また、テキストプロトコルが使用されるケースであってもint64
型の値を渡せばint64
型の値がアサインされるので、型を強制したいのであればany
型を渡すなよということです。
こちらの記事が詳しいです。
go-sql-driver/mysql の次期バージョンを待ちましょう
今回、解決策を示していますが、実は go-sql-driver/mysql 次期バージョンにおいて上記の問題は解決されます。
テキストプロトコルが使用されるケースであっても、整数、浮動小数点をint64
やfloat64
にパースするPRがマージされています。
Parse numbers on text protocol too by methane · Pull Request #1452 · go-sql-driver/mysql · GitHub
v1.8 のリリースを待ちましょう。