2
votes

It's a bit hard to write a title that match with my current problem.. I have a main() function which uses a function in another package (database_sql). This function initializes a global variable sql.DB*. After the initialization, the variable isn't nil, but for the others functions, this variable is still nil.. Let see the code below !

main.go

package main

import (
    "net/http"
    db "./database_sql"
    router "./router"
)

func main() {

    db.Init_SQL();

    router.Init_routes()

    http.ListenAndServe(":8080", router.GetRouter())

}

db.go

package database_sql

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

var DB *sql.DB // global variable to share it between main and the HTTP handler                                                                                                                              
//var dbi *databaseinfos                                                                                                                                                                                     

func Init_SQL() {
    dbi := databaseinfos{
            user: "something",
            password: "something",
            name: "something",
            address: "something",
            port: "2323",
            url: ""}
    dbi.url = (dbi.user + ":" + dbi.password + "@tcp(" + dbi.address + ":" + dbi.port + ")/" + dbi.name)

    db_init_var, err := sql.Open("mysql", dbi.url)
    if err != nil {
            fmt.Println("Error on initializing database connection: %s", err.Error())
    }

    err = db_init_var.Ping()
    if err != nil {
            fmt.Println("Error on opening database connection: %s", err.Error())
    }

    // Here, as you can test, my variable is initialized
    if err == nil {
            DB = db_init_var
            if DB == nil {
                    fmt.Println("NIL DB !!")
            }
            if db_init_var == nil {
                    fmt.Println("NIL db_init_var !!")
            }
            fmt.Println(dbi.url)
    }
}

Now, everything is ok ! I will now test http://xxxxxxx/login with an username/password. Everything's working fine and now, it's the time to make a request in the database with my DB variable declared above.

request_user.go

package database_sql

import (
    "encoding/json"
    "fmt"
    "net/http"
    m "models"
)

func CanLogin(user m.User) (bool, m.User, m.StatusBack) {

    // Prepare statement for reading data                                                                                                                                                                
    stmtOut, err := DB.Prepare("SELECT username, password, token FROM users WHERE username = ? AND password = ?")
    if err != nil {
            return false, user, m.StatusBack{ToString: err.Error(), IdStatus: http.StatusInternalServerError}
    }
defer stmtOut.Close()

    // Query the user                                                                                                                                                                                    
err = stmtOut.QueryRow(user.Username, user.Password).Scan(&user.Username, &user.Password, &user.UUID) // WHERE user is registered                                                                    
    if err != nil {
            return false, user, m.StatusBack{ToString: "User not found.", IdStatus: http.StatusNotFound}
    } else {

            j, err := json.Marshal(user)
            if err == nil {
                    return true, user, m.StatusBack{ToString: string(j), IdStatus: http.StatusOK}
            } else {
                    return false, user, m.StatusBack{ToString: err.Error(), IdStatus: http.StatusInternalServerError}
    }
    }

}

However, when I'm making my sql request, the following line says that DB is nil. stmtOut, err := DB.Prepare("SELECT username, password, token FROM users WHERE username = ? AND password = ?")

I made lot of test such as trying to initialize it with '=' and no ':='. I tried 'if DB == nil' statement and DB is always nil.. Why? I initialize my database var before initialize my router, so technically, when my router give a redirection to a function, this function wouldn't use a database var (DB) nil, isn't?

Thank for your answers !

EDIT 2 I red your answers and then, I edited my code like you asked with some adds. After init_SQL, DB isn't nil !

main.go

package main

import (
    "fmt"
    "net/http"
    db "./database_sql"
    router "./router"
)

func main() {

    if db.Init_SQL() == true {

            if db.DB == nil {
                    fmt.Println("db.DB is nil !")
            }

            if router.Init_routes() == true {

                    http.ListenAndServe(":8080", router.GetRouter())

            }
    }
}

So, now my 2 init functions return true on success and false on failure. I thought it could be an asynchronous problem, I made it like that to wait the end of each functions to continue my 'step by step'

1
I assume that "error opening database" does NOT print, correct? There's no reason why you would get a failure on the DB.Prepare line other than Init_SQL failed to Ping() the dbDavid Budworth
Can you check if the global DB variable is set after db.Init_SQL();?Melvin
I suggest you printing out DB value after db.Init_SQL() and after router.Init_routes(), also before/after every function call you made in your calling stack, in this way you might have an idea when your DB get reset, or haven't been set at all.nevets
Thank for your answers, I added "EDIT 2"Emixam23

1 Answers

1
votes

First, it is not idomatic to export a global var like that. Complex types should be initialized by the runtime (which you are doing with main()), but retained globally from the runtime to control the disposal.

You are also missing a db.Close().

I believe I ran I to the same issue with your pattern a few years ago when first using MySQL. There is an oddness with the way one creates a local scoped pointer, and then assign it to a global var. It is usually better to assign it directly to global var. But I think the core issue is you need to assign the pointer to the pointer.

The pattern I use is to keep the testable database/sql *DB in the global state where I initialize it. Why reinvent the wheel when the wheel works:

package main

import ( 
  ...
  "database/sql"

  "mypackage-that-inits-db/database"
)

var db *sql.DB

find main() {

  var err error
  db, err = database.Init_SQL(...params)
  if err != nil {
    ...
  }
  defer db.Close()

  InitUsers(db)
  InitStats(db)
  ...

  http.ListenAndServe(...)

  // now inject db global dependency in other code
  //

  globalStats := NewStatsEngine(db)
  globalStats.RecordUpdateToSomething(...)

  users := getUsers(db)

   ... etc

}

That's typically the pattern you see in other code.

Note the control over defer and Close, where it belongs here in caller.

Can't Test that easily

Also note that your code in other packages now becomes easily testable by creating a mock sql.DB object and injecting it into NewStatsEngine, getUsers, etc instead of having to mock a global var, test, tear down and setup again for test test, test, teardown, etc. Also since Go 1.5, tests could be run concurrently so you'll even need to put a mutex.Lock around your global DB var if you leave your package like that. Switching to a IoC pattern, like Dependency Injection which I demo'd in the code above, makes so much easier to test (and debug) your code.