Go Learn - Database IO

2019-01-30
by
Learn the basics of database input and output in Go. Full example main_sql.go

Import driver

Database interactions are defined by package database/sql. It inturn relies on you importing a driver package for the database you will be working with. import ( "database/sql" _ "github.com/mattn/go-sqlite3" ) Note that the driver is imported into '_' since we won't be using it directly. Should you forget to import a driver, your application will panic. $ go run main_sql.go panic: sql: unknown driver "sqlite3" (forgotten import?)

Construct DB handle

Having a loaded driver we are set to open a database handle *sql.DB. The handle is a pool of connections so you don't have to worry about reusing database connections. var db *sql.DB if db, err = sql.Open("sqlite3", "/tmp/test.db"); err != nil { panic(err) } Reuse the db handle throughout your application, only close it when you exit. It's meant to live long and prosper.

Define your queries

In this example we'll be working with account data. Making it slightly more readable lets define them in a struct. var accounts = struct { CREATE, INSERT, SELECT string }{ `CREATE TABLE accounts ( username VARCHAR(64) NULL, email VARCHAR(256) NULL )`, "INSERT INTO accounts(username, email) values(?,?)", "SELECT * FROM accounts", }

Create table

Creating tables involves calling the Exec method on our connection. We'll do just a tiny bit of error checking and accept errors telling us the table already exists. if _, err = db.Exec(accounts.CREATE); err != nil { if !strings.Contains(err.Error(), "already exists") { panic(err) } } Since we haven't written our query in a way that works even if the table already exists the err is checked for acceptable content. Note that error messages could differ between drivers so it's not bullet proof. You would probably want to rewrite your query to contain a IF NOT EXISTS.

Insert row

We'll secure our query by using a prepared statement, minimizing SQL injection problems. The placeholders are driver specific, and in our case of sqlite3 we're using the '?' character. var stmt *sql.Stmt if stmt, err = db.Prepare(accounts.INSERT); err != nil { panic(err) } defer stmt.Close() if _, err = stmt.Exec("Ben kenobi", "ben@council.com"); err != nil { panic(err) } Make sure you close the prepared statement After you inserted all the rows. Do not prepare it for each row you want to insert.

Select rows

var rows *sql.Rows if rows, err = db.Query(accounts.SELECT); err != nil { panic(err) } defer rows.Close() Once you have rows, scan column values into their corresponding types, the number of values must be the same as the number of columns in your SELECT query. var username, email string for rows.Next() { if err = rows.Scan(&username, &email); err != nil { panic(err) } } Note how we use pointers to the variables in the call to rows.Scan.

And those are the basics of database input and output operations in Go.