やろうとしたこと
GO言語の公式チュートリアルのコードを基に(ほぼコピペ)ローカルのmysqlサーバに接続して、DBのデータを参照したり、書き込んだりするプログラムを書こうとしました。
書いたコード(エラー解消前)
以下、エラーが発生するコードです。
package main
import (
"database/sql"
"fmt"
"log"
"github.com/go-sql-driver/mysql"
)
var db *sql.DB
type Album struct {
ID int64
Title string
Artist string
Price float32
}
func albumByArtist(name string) ([]Album, error) {
var albums []Album
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
if err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
defer rows.Close()
for rows.Next() {
var alb Album
err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price)
if err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
albums = append(albums, alb)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
return albums, nil
}
func main() {
//connect to DB
cfg := mysql.Config{
User: "user",
Passwd: "pass",
Net: "tcp",
Addr: "localhost:3306",
DBName: "recording",
AllowNativePasswords: true,
}
//get a database handle
var err error
db, err := sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("connect to DB!")
var albums []Album
albums, err = albumByArtist("John Coltrane")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)
}
これを実行すると以下のエラーが発生します。
panic: runtime error: invalid memory address or nil pointer dereference
エラーの原因
かなり初歩的なミスでしたが、main()
内のsql.Open()
の書き方にミスがありました。
- 正:
db, err = sql.Open("mysql", cfg.FormatDSN())
- 誤:
db, err := sql.Open("mysql", cfg.FormatDSN())
=
とすべきところ、うっかり:=
と書いてしまったことが原因でした。
複数の関数内で共通のDBのコネクションを使うためグローバル変数として定義する必要があります。
ところが、main()内のsql.Open()を実行する際に:=
としてしまうと、
左辺のdbはmain()内のローカル変数として定義されてしまいます。
そして、albumByArtist()内でDBにクエリを投げようと、グローバル変数のDBのコネクションを利用しようとしても、グローバル変数のコネクションがnilなので上記のエラーが発生したというわけです。
(当然main()内のコネクションにはデータが格納されています。)
最後に
GO言語を勉強したてで、意識して=
と:=
の使い分けができていなかったため発生したエラーでした。
気をつけていきたいですね。