-3

Below is the whole code of the program. It is a service that forwards requests. Currently working. What I am trying to do is get rid of the yml file that is currently storing all configs and move them to db. I don't want to mess with the code much, so my idea was to simply store the db data in the same structs.

    // Config contains configuration for this service
type Instance struct {
    User         string `json:"user"`
    Password     string `json:"password"`
    InstanceId   string `json:"instance_id"`
    InstanceType string `json:"instance_type"`
    InstanceMode string `json:"instance_mode"`
    ClientId     string `json:"client_id"`
    ClientSecret string `json:"client_secret"`
    PublicKey    string `json:"pubkey"`
    Apis         Api    `json:"apis"`
}

// API struct
type Api struct {
    Name    string `json:"name"`
    Url     string `json:"url"`
    Version string `json:"version"`
    IsBhdGw bool   `json:"isBhdGw"`
    Key     string `json:"key"`
}


// API struc
type InfoResponse struct {
    InstanceId   string `json:"instance_id"`
    InstanceType string `json:"instance_type"`
    InstanceMode string `json:"instance_mode"`
    ClientId     string `json:"client_id"`
    ClientSecret string `json:"client_secret"`
    PublicKey    string `json:"pubkey"`
    Apis         Api    `json:"apis"`
}

type Settings struct {
    Port   int    `json:"port"`
    User   string `json:"user"`
    Secret string `json:"secret"`
    Mode   string `json:"mode"`
}

type Instances struct {
    Instances []Instance
}

var payloadLength int = 3
var instances Instances
var settings Settings
var db *sql.DB



func fetchInstances() []Instance {

    rows, err := db.Query("SELECT Instances.InstanceId, Instances.User, Instances.Password, Instances.InstanceType, Instances.InstanceMode, Instances.ClientId,  Instances.ClientSecret, Instances.PublicKey, Api.Name, Api.Url, Api.Version, Api.IsBhdGw, Api.Key FROM Instances, Api")

    if err != nil {
        panic(err.Error())
    }

    defer rows.Close()

    instances := []Instance{}


    for rows.Next() {
        var instance Instance
        var apis Api

        err := rows.Scan(&instance.InstanceId, &instance.User, &instance.Password,
            &instance.InstanceType, &instance.InstanceMode, &instance.ClientId, &instance.ClientSecret, &instance.PublicKey,
            &apis.Name, &apis.Url, &apis.Version, &apis.IsBhdGw, &apis.Key)

        if err != nil {
            panic(err.Error())
        }

        instance.Apis = apis

        instances = append(instances, instance)
    }

    return instances
}

When I run this I get a "panic: runtime error: invalid memory address or nil pointer dereference"

1 Answers1

3

You are attempting to connect to an uninitiated database connection. This is due to variable shadowing.

Although you've defined a global db variable:

var db *sql.DB

Your main() method creates a new one when it connects:

db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@tcp(db:3306)/"+dbName)

So then when you call fetchInstances, which uses the global variable, the global one is still unset.

The easiest solution is to change your code in main() as follows:

var err error
db, err = sql.Open(dbDriver, dbUser+":"+dbPass+"@tcp(db:3306)/"+dbName)

But the better solution is to never use a global variable. Use the locally-defined variable in main, then pass that to fetchInstances.


As a side note: ALWAYS, ALWAYS check your errors. Twice in your code, you fail to check db connection errors, as in:

db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@tcp(db:3306)/"+dbName)
err = db.Ping()

if err != nil {
    fmt.Println(err)
    os.Exit(1)
}

If the connection fails, your db.Ping() call will likely panic. Instead, you should do:

db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@tcp(db:3306)/"+dbName)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}

err = db.Ping()

if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189