のんびり精進

調べた情報などをおすそ分けできれば。

【Go】gorp でプレースホルダを使う

テーブルは前回の記事のものを引き続き使用することにします。 kabochapo.hateblo.jp

値を SQL 文中に直書き

rows, err := dbmap.Select(User{}, `SELECT id, name FROM user WHERE id IN (1, 3)`)

プレースホルダ

rows, err := dbmap.Select(User{}, `SELECT id, name FROM user WHERE id IN (?, ?)`, 1, 3)

このように値の代わりに ? をあてがっておき、そこに値をバインドできます(database/sql でも同様)。

では、もしバインドしたい値がスライスに入っていたらどうすればいいのでしょうか。 Select() の第3引数は args ...interface{} となっているので、スライスをそのまま渡しても当然ダメなんですが、とりあえず試してみます。

bind := []uint32{1, 3}
rows, err := dbmap.Select(User{}, `SELECT id, name FROM user WHERE id IN (?, ?)`, bind...)
cannot use bind (type []uint32) as type []interface {} in argument to dbmap.Select

やはりダメでした。

では素直に []interface{} に変換してみます。

s := []uint32{1, 3}
bind := make([]interface{}, len(s))
for i, v := range s {
    bind[i] = v
}
rows, err := dbmap.Select(User{}, `SELECT id, name FROM user WHERE id IN (?, ?)`, bind...)

今度は OK でした。 これはちょっとだけ手間ですね。 何か良い方法があるのかもしれません。

では、エスケープのほうはどうなのでしょうか。 次のように変えて動作を確認してみます。

bind := []interface{}{1, "3abc"}

id が 1 と 3 のレコードが得られました。 "3abc" という文字列であっても数値の 3 として扱ってくれたようです(数値でないのにエラーにならないのが良いかどうかは置いておいて)。

名前付きプレースホルダ

普段、PHP の PDO でも名前付きのプレースホルダを好んで使っています。 幸い gorp でも同じことができます。便利ですね。

これを使って、名前が「郎」で終わる30代の人だけを取得してみます。

bind := map[string]interface{}{
    "name":    "%郎",
    "age_min": 30,
    "age_max": 39,
}
rows, err := dbmap.Select(User{}, `
    SELECT id, name FROM user
    WHERE name LIKE :name
        AND age BETWEEN :age_min AND :age_max
`, bind)

名前付きだと、当然ですが

bind := map[string]interface{}{
    "age_max": 39,
    "age_min": 30,
    "name":    "%郎",
}

のように SQL 内での指定順と異なっていても問題ありません。

まとめ

プレースホルダの使用は SQL インジェクション対策として必須です。 gorp では今回見たとおり簡単にプレースホルダを利用できて安心ですね。

今後 gorp を実際に使ってみて、気づいたことや気をつける点などがあれば、また書くかもしれません。