How to work with context global variables in Golang? - go

I am trying to get all documents from a Firestore database and things were working fine.
But then I decided to make the context and client variable global, so that I won't have to deal with passing them as parameters everytime.
Things broke after that.
The error I get is:
panic: runtime error: invalid memory address or nil pointer dereference
and according to the stack trace, it occurs when I try to:
client.Collection("dummy").Documents(ctx)
What can I do to resolve this?
And how can I efficiently work with global variables in my case?
My code for reference:
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/firestore"
firebase "firebase.google.com/go"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
)
var (
ctx context.Context
client *firestore.Client
)
func init() {
ctx := context.Background()
keyFile := option.WithCredentialsFile("serviceAccountKey.json")
app, err := firebase.NewApp(ctx, nil, keyFile)
if err != nil {
log.Fatalln(err)
}
client, err = app.Firestore(ctx)
if err != nil {
log.Fatalln(err)
}
fmt.Println("Connection to Firebase Established!")
}
func getDocuments(collectionName string) {
iter := client.Collection("dummy").Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
log.Fatalf("Failed to iterate: %v", err)
}
fmt.Println(doc.Data()["question"])
}
}
func main() {
getDocuments("dummy")
defer client.Close()
}

You get that error because you never assign anything to the package level ctx variable, so it remains nil.
Inside init() you use short variable declaration which creates a local variable:
ctx := context.Background()
If you change to to simple assignment, it will assign a value to the existing, package level ctx variable:
ctx = context.Background()
Although using "global" variables to store something that's not global is bad practice. You should just pass ctx where it's needed.

Related

How to define a connect to DB function in golang using gorm

Good morning,
I am new to golang. I am creating following connect to DB function:
func ConnectDatabase(db gorm.Dialector, gorm.Config gorm.Option) *gorm.DB{
db, err := gorm.Open(db, gormConfig)
if err != nil {
log.Fatal(err)
}
return db
}
It gives me an error on Open: cannot use gorm.Open(db, gormConfig)(value of type *gorm.DB) as gorm.Dialector value in assignment: *gorm.DB does not implement gorm.Dialector
The reason I create the function with two parameteres is because i want to wright the test on it, and need to pass test parameters to it
My test code looks like it:
func TestDbConnect(t *testing.T){
dbFile, err := os.CreateTemp("", "sample_db"
if err != nil{
t.Fatal(err)
}
sqlOpen := sqlite.Open(dbFile.Name())
gormConfig := &gorm.Config{}
conn, err := sqlOpen, gormConfig
}
Would really appreciate an advice on how to define my function correctly and if my test function makes sense
You have some improper function signature ,in parameters .I have fixed your code .Now you can use it
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func ConnectDatabase(db gorm.Dialector, config *gorm.Config, option gorm.Option) (*gorm.DB,err) {
db1, err := gorm.Open(db, config)
if err != nil {
return nil,err // return nil and error here
}
return db1,nil
}
func main() {
sql := sqlite.Open("test.db")
db := ConnectDatabase(sql, &gorm.Config{}, nil)
fmt.Println("Hello, World!", db)
}
Points -
1- Dialector is an interface of gorm
2- sqlite.Open("test.db") returns Dialector interface
Your test would look something like this
func TestDbConnect(t *testing.T){
dbFile, err := os.CreateTemp("", "sample_db"
if err != nil{
t.Fatal(err)
}
sqlOpen := sqlite.Open(dbFile.Name())
db,err := ConnectDatabase(sqlOpen, &gorm.Config{}, nil)
// handle error and test pass case
}
Also don't make function local variable name as parameter name,this may cause variable shadowing see here as you are having err variable as new one in this line
db, err := gorm.Open(db, gormConfig)
so go complier was not giving error.

GCP - Remove Obsolete Buckets

GCP - Remove Obsolete Buckets
Remove Obsolete Buckets in GCP in golang
How can I implement this thing in golang ?
You may try to delete the bucket through BucketHandle.Delete
import (
"context"
"cloud.google.com/go/storage"
)
func main() {
ctx := context.Background()
client, err := storage.NewClient(ctx)
if err != nil {
// TODO: handle error.
}
if err := client.Bucket("my-bucket").Delete(ctx); err != nil {
// TODO: handle error.
}
}

Panic: runtime error: invalid memory address or nil pointer dereference only on the GAE

I am working on the golang app using gin framework. Basically it just fetch the data from firestore as JSON.
Localy it works perfectly but when I deploy it to the GAE (gcloud app deploy) there is no error during deployment but when access the page it does not work and in the logs provide an error: "panic: runtime error: invalid memory address or nil pointer dereference"
package listcollections
import (
"fmt"
"log"
"net/http"
"cloud.google.com/go/firestore"
"github.com/gin-gonic/gin"
"google.golang.org/api/iterator"
"google.golang.org/appengine"
)
func main() {
}
//GetListCollections function
func GetListCollections(c *gin.Context) {
var coll []string
ctx := appengine.NewContext(c.Request)
projectID := "XXX"
client, err := firestore.NewClient(ctx, projectID)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
defer client.Close()
iter := client.Collection("collection").Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
fmt.Println("ERROR")
}
coll = append(coll, doc.Data()["Title"].(string))
}
c.JSON(http.StatusOK, gin.H{
"collections": coll,
})
}
As no one has any clue where it did happen?
From Analysing your code, the only possibility which I can think off is that your
itr variable is empty.
You might need to change the check for error part and add Panic instead of just printing the error and keep runing
if err != nil {
panic("ERROR")
}

How to separate DB connection initialization as a package in Go?

I have two packages, main and db.
However, I get "DB declared and not used" error.
db.go
package db
import (
"database/sql"
)
var DB *sql.DB
func Connect() {
DB, err := sql.Open("mysql", "root:xxxx#/xxxx")
if err != nil {
panic(err.Error())
}
}
func Close() {
DB.Close()
}
main.go
package main
import (
"database/sql"
// "fmt"
_ "github.com/go-sql-driver/mysql"
"html/template"
"net/http"
"github.com/****/****/config"
"github.com/****/****/db"
)
var tpl *template.Template
func init() {
tpl = template.Must(template.ParseGlob("templates/*.gohtml"))
}
func main() {
Connect()
defer Close()
loadRoutes()
http.ListenAndServe(":8080", nil)
}
Golang is strict about variable declaration, it is also mentioned in the Golang FAQs:
The presence of an unused variable may indicate a bug, while unused
imports just slow down compilation, an effect that can become
substantial as a program accumulates code and programmers over time.
For these reasons, Go refuses to compile programs with unused
variables or imports, trading short-term convenience for long-term
build speed and program clarity.
It's easy to address the situation, though. Use the blank identifier to let unused things persist while you're developing.
_, err := sql.Open("mysql", "root:Berlin2018#/jplatform")
But since you want db instance by creating a connection. I suggest to use it either by returning from the function OR You can check for the connection if it is working or not by sending ping to the database server as:
var DB *sql.DB
func Connect() {
DB, err := sql.Open("mysql", "root:Berlin2018#/jplatform")
if err = DB.Ping(); err != nil {
log.Panic(err)
}
}
Or you can create a struct which you can use anywhere you want including use of method receiver for every function which require db connection for querying the database as
type Env struct {
db *sql.DB
}
func Connect() {
db, err := sql.Open("mysql", "root:Berlin2018#/jplatform")
_ = &Env{db: db}
}
func(env *Env) getDataFromDatabase(){}
You are not using your DB variable on db.go:
package db
import (
"database/sql"
)
var DB *sql.DB
func Connect() {
con, err := sql.Open("mysql", "root:Berlin2018#/jplatform")
if err != nil {
panic(err.Error())
}
DB = con
}
func Close() {
DB.Close()
}

Connection DB in golang

Where i can put initialize files like languages, connection db etc. in mvc structur golang (beego,revel)?
I tried to use in controller but it isn't good.
Is a good solution would be create base controller and put here all init connection, languages etc? or is there some other way (better)?
You can use global variables, but I don't suggest to do it. What happens in more complicated applications where database logic is spread over multiple packages? It's better to use dependency injection:
File: main.go
package main
import (
"bookstore/models"
"database/sql"
"fmt"
"log"
"net/http"
)
type Env struct {
db *sql.DB
}
func main() {
db, err := models.NewDB("postgres://user:pass#localhost/bookstore")
if err != nil {
log.Panic(err)
}
env := &Env{db: db}
http.HandleFunc("/books", env.booksIndex)
http.ListenAndServe(":3000", nil)
}
func (env *Env) booksIndex(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, http.StatusText(405), 405)
return
}
bks, err := models.AllBooks(env.db)
if err != nil {
http.Error(w, http.StatusText(500), 500)
return
}
for _, bk := range bks {
fmt.Fprintf(w, "%s, %s, %s, £%.2f\n", bk.Isbn, bk.Title, bk.Author, bk.Price)
}
}
File: models/db.go
package models
import (
"database/sql"
_ "github.com/lib/pq"
)
func NewDB(dataSourceName string) (*sql.DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return db, nil
}
I always do some packega where i keep my enviroment variables.
For example main.go
package main
import (
"net/http"
env "github.com/vardius/example/enviroment"
)
func main() {
//some extra code here, http srever or something
defer env.DB.Close()
}
end inside enviroment dir env.go
package env
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
var (
DB *sql.DB
)
func connectToDB(dbURL string) *sql.DB {
conn, err := sql.Open("mysql", dbURL)
//check for err
return conn
}
func init() {
DB = connectToDB("root:password#tcp(127.0.0.1:3306)/test")
}
this way you initialize once your DB and can use it in all parts of your app by injecting env
Ofcourse this solution has some downsides. First, code is harder to
ponder because the dependencies of a component are unclear. Second,
testing these components is made more difficult, and running tests in
parallel is near impossible. With global connections, tests that hit
the same data in a backend service could not be run in parallel.
There is a great article about a Dependency Injection with Go
I hope you will find this helpfull

Resources