I'm learning Go at the moment and trying to make a little SQL-toolset:
type DBUtils struct {
User string
Password string
Host string
Database string
Handle *sql.DB
}
func (dbUtil DBUtils) Connect() {
var err error
dbUtil.Handle, err = sql.Open("mysql", dbUtil.User + ":" + dbUtil.Password + "@tcp(" + dbUtil.Host + ")/" + dbUtil.Database)
if err != nil {
panic(err.Error())
}
err = dbUtil.Handle.Ping()
if err != nil {
panic(err.Error())
}
fmt.Printf("%v", dbUtil)
}
func (dbUtil DBUtils) Close() {
dbUtil.Handle.Close()
}
func (dbUtil DBUtils) GetString(what string, from string, where string, wherevalue string) string {
var username string
fmt.Printf("%v", dbUtil)
stmtOut, err := dbUtil.Handle.Prepare("SELECT " + what + " FROM " + from + " WHERE " + where + " = " + wherevalue)
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
err = stmtOut.QueryRow(1).Scan(&username)
return username
}
So when using this with the following code:
db := databaseutils.DBUtils{"root", "root", "127.0.0.1:3306", "gotest", nil}
db.Connect() // I get: {root root 127.0.0.1:3306 gotest 0xc42019d600}
fmt.Printf("%v", db) // I get {root root 127.0.0.1:3306 gotest <nil>}
x := db.GetString("username", "users", "id", "1") // Doesn't work: panic: runtime error: invalid memory address or nil pointer dereference
fmt.Println(x)
For me it seems like my DB handle isn't saved properly? Does anyone have an idea - I'm pretty new to go and there are many things looking different to PHP, JS, C++ etc.
Thanks in advance!
Your Connect
method is not changing the state of the object you're calling the method on. You're working on a copy of the type. If you want a method to change the object itself, you'll have to define it on a pointer:
func (dbUtil *DBUtils) Connect() {
//instead of
func (dbUtil DBUtils) Connect() {
If you're familiar with C or C++, your current method works similarly to something like:
void connect_db(struct db_utils db_util)
{}
When you call a function like that, you're creating a copy of the argument, and push that on to the stack. The connect_db
function will work with that, and after it returns, the copy is deallocated.
compare it to this C code:
struct foo {
int bar;
};
static
void change_copy(struct foo bar)
{
bar.bar *= 2;
}
static
void change_ptr(struct foo *bar)
{
bar->bar *= 2;
}
int main ( void )
{
struct foo bar = {10};
printf("%d
", bar.bar);//prints 10
change_copy(bar);//pass by value
printf("%d
", bar.bar);//still prints 10
change_ptr(&bar);
printf("%d
", bar.bar);//prints 20
return 0;
}
The same thing happens in go. The object on which you define the method can only change state of the instance if it has access to the instance. If not, it can't update that part of the memory.
In case you're wondering, this method doesn't need to be defined on the pointer type:
func (dbUtil DBUtils) Close() {
dbUtil.Handle.Close()
}
The reason for this being that DBUtils.Handle
is a pointer type. A copy of that pointer will always point to the same resource.
I will say this, though: given that you're essentially wrapping the handle, and you're exposing the Connect
and Close
methods, the member itself really shouldn't be exported. I'd change it to lower-case handle
You are right about your DB handle not being saved. You are defining your methods on a value receiver. Therefore you receive a copy inside the Connect
method and modify said copy. This copy is then dropped at the end of the Connect
method.
You have to define your method on a pointer to your structure:
func (dbUtil *DBUtils) Connect() {
var err error
dbUtil.Handle, err = sql.Open("mysql", dbUtil.User + ":" + dbUtil.Password + "@tcp(" + dbUtil.Host + ")/" + dbUtil.Database)
if err != nil {
panic(err.Error())
}
err = dbUtil.Handle.Ping()
if err != nil {
panic(err.Error())
}
fmt.Printf("%v", dbUtil)
}
For further information see https://golang.org/doc/faq#methods_on_values_or_pointers.
Note: I noticed your usage of QueryRow
. It accepts arguments for parameters in your prepare statement. You have no parameters there, so I think you should not pass any parameters. You should also check the Scan
result for errors (see https://golang.org/pkg/database/sql/#Stmt).