0

I have two .go files: a.go and b.go

I'm declaring a global db *sql.DB for my mysql database connection.

My goal is to declare db once and use it in all my package files, in this case b.go.

Everything builds fine, but I get an error when hitting my API endpoint /users

22:48:52 app         | 2015/05/18 22:48:52 http: panic serving 127.0.0.1:55742: runtime error: invalid memory address or nil pointer dereference
goroutine 6 [running]:
net/http.func·011()
    /usr/local/go/src/net/http/server.go:1130 +0xbb
database/sql.(*DB).conn(0x0, 0x4da104, 0x0, 0x0)
    /usr/local/go/src/database/sql/sql.go:634 +0x7ae
database/sql.(*DB).query(0x0, 0x809e70, 0x16, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0)
    /usr/local/go/src/database/sql/sql.go:933 +0x43
database/sql.(*DB).Query(0x0, 0x809e70, 0x16, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
    /usr/local/go/src/database/sql/sql.go:924 +0xa6
main.GetUsers(0x0, 0x0, 0x0)
    /var/www/zazok.com/api/src/app/user.go:15 +0xc0
main.func·001(0xc2080424d0)
    /var/www/zazok.com/api/src/app/app.go:35 +0x1f
github.com/gin-gonic/gin.(*Context).Next(0xc2080424d0)
    /var/www/zazok.com/api/src/github.com/gin-gonic/gin/context.go:114 +0x95
github.com/gin-gonic/gin.func·006(0xc2080424d0)
    /var/www/zazok.com/api/src/github.com/gin-gonic/gin/logger.go:49 +0x68
github.com/gin-gonic/gin.(*Context).Next(0xc2080424d0)
    /var/www/zazok.com/api/src/github.com/gin-gonic/gin/context.go:114 +0x95
github.com/gin-gonic/gin.func·009(0x7f1123351408, 0xc208036280, 0xc2080328f0, 0x0, 0x0, 0x0)
    /var/www/zazok.com/api/src/github.com/gin-gonic/gin/routergroup.go:57 +0xa3
github.com/julienschmidt/httprouter.(*Router).ServeHTTP(0xc20803af60, 0x7f1123351408, 0xc208036280, 0xc2080328f0)
    /var/www/zazok.com/api/src/github.com/julienschmidt/httprouter/router.go:299 +0x18e
github.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc208042000, 0x7f1123351408, 0xc208036280, 0xc2080328f0)
    /var/www/zazok.com/api/src/github.com/gin-gonic/gin/gin.go:156 +0x4d
net/http.serverHandler.ServeHTTP(0xc208030120, 0x7f1123351408, 0xc208036280, 0xc2080328f0)
    /usr/local/go/src/net/http/server.go:1703 +0x19a
net/http.(*conn).serve(0xc2080361e0)
    /usr/local/go/src/net/http/server.go:1204 +0xb57
created by net/http.(*Server).Serve
    /usr/local/go/src/net/http/server.go:1751 +0x35e

a.go

package main

import (
    "database/sql"
    "github.com/gin-gonic/gin"
    _ "github.com/go-sql-driver/mysql"
)

var (
    prefix string = "/api/v1" // API prefix
    db     *sql.DB
)

// Boots up this whole thing
func main() {
    // Setting up DB
    db, err := sql.Open("mysql", "root:password@unix(/var/run/mysqld/mysqld.sock)/test.com?collation=utf8_general_ci")
    if err != nil {
        panic(err)
    }

    defer db.Close()

    err = db.Ping()
    if err != nil {
        panic(err)
    }

    r := gin.New()

    r.Use(gin.Logger())

    r.GET(prefix+"/users", func(c *gin.Context) {

        t := GetUsers()

        c.JSON(200, t)
    })

    r.Run(":3000")
}

b.go

package main

import (
    "log"
)

type User struct {
    Id          int    `json:"id"`
    Name        string `json:"name"`
}

func GetUsers() []User {
    a := []User{}

    _, err := db.Query("SELECT name FROM users")
    if err != nil {
        log.Fatal(err)
    }

    return a
}
1

2 Answers 2

3

EDIT: As pointed out by DaveC, the problem is that using := initiates a variable in the local scope only. Declaring err beforehand will cause the sql.Open to save the connection in the global instead of creating a new local, as follows:

func main() {
    var err error // <- Declare err

    // Use = instead of :=
    db, err = sql.Open("mysql", "root:password@unix(/var/run/mysqld/mysqld.sock)/test.com?collation=utf8_general_ci")
    ...

This edit influenced by DaveC, tomasz, and Ben Darnell (Ben Darnell's answer: Go global variable and short variable definition)

/EDIT

Personally, I prefer to avoid globals where possible. To do this, change GetUsers to take an argument *sql.DB. Then change your a.go to have a custom gin.HandlerFunc that is a database receiver. In the example I've created a tools struct that you could use to pass in many "global" things.

package main

import (
    "database/sql"

    "github.com/gin-gonic/gin"
    _ "github.com/go-sql-driver/mysql"
)

var (
    prefix = "/api/v1" // API prefix
)

type tools struct {
    db *sql.DB
}

func (t tools) dispatch() gin.HandlerFunc {
    return func(c *gin.Context) {
        GetUsers(t.db)
    }
}

// Boots up this whole thing
func main() {
    // Setting up DB
    db, err := sql.Open("mysql", "root:password@unix(/var/run/mysqld/mysqld.sock)/test.com?collation=utf8_general_ci")

    if err != nil {
        panic("DB connection failed")
    }
    defer db.Close()

    t := tools{db}

    r := gin.New()

    r.Use(gin.Logger())

    r.GET(prefix+"/users", t.dispatch())

    r.Run(":3000")
}
Sign up to request clarification or add additional context in comments.

4 Comments

and completely ignores the underlying cause…
@DaveC can you clarify? I personally find it favorable to use a struct to pass the data I need, rather than declaring globals. It is also very acceptable to create a custom handler func for http stuff. What is wrong with my answer?
It ignores the fact that the OP's problem is due entirely to variable shadowing. Saying "it's better to avoid globals and pass values instead" is fine per-se but without even mentioning the shadowing problem it's more of a coment or workaround than an answer. At best it just defers the underlying issue until later when the OP really does want/need to use a global (or other case where shadowing often happens).
@DaveC This is a valid point. I'll edit my answer for posterity.
3

I think this is a classic Go bug and it has nothing to do with using multiple files.

This statement:

db, err := sql.Open("mysql", ...)

declares new variable db in a main scope (note :=) and your global variable is untouched (nil).

You can easily fix it:

var err error
db, err = sql.Open("mysql", ...)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.