はじめに
golang公式チュートリアルのAccessing a relational databaseをやっていたところ、DB接続は行えているにも関わらず、SQLクエリーを叩く行でエラーが出ました。
備忘録として、そのエラーに対する解決策を残したいと思います。
発生したエラー
albumsByArtist関数でSQLクエリーを実行する行で以下のエラーが出ました。
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
// => panic: runtime error: invalid memory address or nil pointer dereference
このエラーをググったところ、いわゆるぬるぽと呼ばれるエラーでした。
ぬるぽとは、Javaで発生するエラーメッセージ「NullPointerException」の略称です。
nil値の参照型変数を参照しようとした時に発生するエラーメッセージです。
ということで、変数dbがnilであることを確かめ、その原因を探れば解消できるだろうという算段を立てました。
コードの全体像
説明のために今回作成したコードの全体像を簡略化させました。
var db *sql.DB
func main() {
var err error
// Get a database handle.
db, err := sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connected!")
// => Connected!
albums, err := albumsByArtist("John Coltrane")
}
func albumsByArtist(name string) ([]Album, error) {
if name == "" {
return nil, errors.New("name is empty")
}
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
// => panic: runtime error: invalid memory address or nil pointer dereference
}
解決までのプロセス
エラー箇所のrows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
の一行前に変数dbがnilであることを確かめるために、この変数を標準出力してみました。
fmt.Println(db)
// => nil
確かに変数dbはnilでした。
この変数はグローバルに定義されています。
また、main関数内でalbumsByArtist関数を呼び出す前に、SQL操作するため以下のように値が代入されています。
db, err := sql.Open("mysql", cfg.FormatDSN())
さらに、Connected!
と出力されているため、db.Ping()
も上手くいっているようです。
そこで、変数dbのアドレスを調べることにしました。
それというのも、main関数で使用される場合とalubmsByArtist関数で使用される場合とで参照しているアドレスが異なっている可能性を考えたからです。
func main() {
// 省略
db, err := sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
fmt.Println(*db)
// => {{{} {} 0} 0x1400000e068 {{} {} 0} {0 0} [] map[] 0 0 0x14000022180 false map[] map[] 0 0 0 0 <nil> 0 0 0 0 0x104c25e00}
// 省略
albums, err := albumsByArtist("John Coltrane")
// 省略
}
func albumsByArtist(name string) ([]Albums, error) {
// 省略
fmt.Println(*db)
// => [signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x104d5f854]
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
// 省略
}
main関数では{{{} {} 0} 0x1400000e068 {{} {} 0} {0 0} [] map[] 0 0 0x14000022180 false map[] map[] 0 0 0 0 <nil> 0 0 0 0 0x104c25e00}
で、albumsByArtisit関数では[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x104d5f854]
と全く内容が違いました。
以上から、代入処理に原因があるのだろうと思いました。
解決
main関数内での代入処理が間違っていました。
- db, err := sql.Open("mysql", cfg.FormatDSN())
+ db, err = sql.Open("mysql", cfg.FormatDSN())
:=
キーワードは初期化と代入処理を行います。
main関数内で変数dbが初期化されたため、グローバルに定義したdbとは別物の変数としてローカルに定義する処理となっていました。
そのため、main関数内ではローカル変数としてのdbを使用し、albumsByArtist関数内ではグローバルで定義されたdbを使用していたため、ぬるぽが発生してしまいました。
おわりに
結局のところ、公式チュートリアルに書いてあるコードの写し間違いによるエラーでした。
しかし、写し間違えたからこそ:=
表記の意味やグローバル変数を使用するデメリットを感じることができました。
日々精進です。