5.3 SQLite

SQLite is an open source, embedded relational database. It has a self-contained, zero-configuration and transaction-supported database engine. Its characteristics are highly portable, easy to use, compact, efficient and reliable. In most of cases, you only need a binary file of SQLite to create, connect and operate a database. If you are looking for an embedded database solution, SQLite is worth considering. You can say SQLite is the open source version of Access.

SQLite drivers

There are many database drivers for SQLite in Go, but many of them do not support the database/sql interface standards.

The first driver is the only one that supports the database/sql interface standard in its SQLite driver, so I use this in my projects -it will make it easy to migrate my code in the future if I need to.

Samples

We create the following SQL:

  1. CREATE TABLE `userinfo` (
  2. `uid` INTEGER PRIMARY KEY AUTOINCREMENT,
  3. `username` VARCHAR(64) NULL,
  4. `department` VARCHAR(64) NULL,
  5. `created` DATE NULL
  6. );

An example:

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "time"
  6. _ "github.com/mattn/go-sqlite3"
  7. )
  8. func main() {
  9. db, err := sql.Open("sqlite3", "./foo.db")
  10. checkErr(err)
  11. // insert
  12. stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)")
  13. checkErr(err)
  14. res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
  15. checkErr(err)
  16. id, err := res.LastInsertId()
  17. checkErr(err)
  18. fmt.Println(id)
  19. // update
  20. stmt, err = db.Prepare("update userinfo set username=? where uid=?")
  21. checkErr(err)
  22. res, err = stmt.Exec("astaxieupdate", id)
  23. checkErr(err)
  24. affect, err := res.RowsAffected()
  25. checkErr(err)
  26. fmt.Println(affect)
  27. // query
  28. rows, err := db.Query("SELECT * FROM userinfo")
  29. checkErr(err)
  30. var uid int
  31. var username string
  32. var department string
  33. var created time.Time
  34. for rows.Next() {
  35. err = rows.Scan(&uid, &username, &department, &created)
  36. checkErr(err)
  37. fmt.Println(uid)
  38. fmt.Println(username)
  39. fmt.Println(department)
  40. fmt.Println(created)
  41. }
  42. rows.Close() //good habit to close
  43. // delete
  44. stmt, err = db.Prepare("delete from userinfo where uid=?")
  45. checkErr(err)
  46. res, err = stmt.Exec(id)
  47. checkErr(err)
  48. affect, err = res.RowsAffected()
  49. checkErr(err)
  50. fmt.Println(affect)
  51. db.Close()
  52. }
  53. func checkErr(err error) {
  54. if err != nil {
  55. panic(err)
  56. }
  57. }

You may have noticed that the code is almost the same as in the previous section, and that we only changed the name of the registered driver and called sql.Open to connect to SQLite in a different way.

Note that sometimes you can’t use the for statement because you don’t have more than one row, then you can use the if statement

  1. if rows.Next() {
  2. err = rows.Scan(&uid, &username, &department, &created)
  3. checkErr(err)
  4. fmt.Println(uid)
  5. fmt.Println(username)
  6. fmt.Println(department)
  7. fmt.Println(created)
  8. }

Also you have to do a rows.Next(), without using that you can’t fetch data in the Scan function.

Transactions

The above example shows how you fetch data from the database, but when you want to write a web application then it will not only be necessary to fetch data from the db but it will also be required to write data into it. For that purpose, you should use transactions because for various reasons, such as having multiple go routines which access the database, the database might get locked. This is undesirable in your web application and the use of transactions is effective in ensuring your database activities either pass or fail completely depending on circumstances. It is clear that using transactions can prevent a lot of things from going wrong with the web app.

  1. trashSQL, err := database.Prepare("update task set is_deleted='Y',last_modified_at=datetime() where id=?")
  2. if err != nil {
  3. fmt.Println(err)
  4. }
  5. tx, err := database.Begin()
  6. if err != nil {
  7. fmt.Println(err)
  8. }
  9. _, err = tx.Stmt(trashSQL).Exec(id)
  10. if err != nil {
  11. fmt.Println("doing rollback")
  12. tx.Rollback()
  13. } else {
  14. tx.Commit()
  15. }

As it is clear from the above block of code, you first prepare a statement, after which you execute it, depending on the output of that execution then you either roll it back or commit it.

As a final note on this section, there is a useful SQLite management tool available: http://sqlitebrowser.org