|
|
|
@ -0,0 +1,114 @@
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"database/sql"
|
|
|
|
|
"flag"
|
|
|
|
|
"log"
|
|
|
|
|
|
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
|
|
|
|
log.Println("Hello, world!")
|
|
|
|
|
db_path := flag.String("database", "./data/trysqlite.db", "Path to a Sqlite3 database")
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
|
|
// TODO figure out what query string options to be using
|
|
|
|
|
full_database_path := "file:" + *db_path + "?cache=shared"
|
|
|
|
|
|
|
|
|
|
log.Printf("Using Database: %s", full_database_path)
|
|
|
|
|
// db is a Handle backed by a go database/sql connection pool. It is not a connection in of itself, nor is it the actual connection pool
|
|
|
|
|
// The first connection is made lazily by the handle, and this doesn't validate any of the connection parameters either at this point.
|
|
|
|
|
// It simply prepares the abstraction for use.
|
|
|
|
|
|
|
|
|
|
db, err := sql.Open("sqlite3", full_database_path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("Failed to connect to sqlite database: %s", full_database_path)
|
|
|
|
|
}
|
|
|
|
|
// It is idiomatic to defer db.Close() if the sql.DB should not have a lifetime beyond the scope of the function.
|
|
|
|
|
//Although it’s idiomatic to Close() the database when you’re finished with it, the sql.DB object is designed to be long-lived.
|
|
|
|
|
//Don’t Open() and Close() databases frequently. Instead, create one sql.DB object for each distinct datastore you need to access,
|
|
|
|
|
// and keep it until the program is done accessing that datastore.
|
|
|
|
|
|
|
|
|
|
// Pass it around as needed, or make it available somehow globally, but keep it open.
|
|
|
|
|
// And don’t Open() and Close() from a short-lived function. Instead, pass the sql.DB
|
|
|
|
|
// into that short-lived function as an argument.
|
|
|
|
|
|
|
|
|
|
//If you don’t treat the sql.DB as a long-lived object, you could experience problems such as poor reuse and sharing of connections,
|
|
|
|
|
// running out of available network resources, or sporadic failures due to a lot of TCP connections remaining in TIME_WAIT status.
|
|
|
|
|
// Such problems are signs that you’re not using database/sql as it was designed.
|
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
|
|
// If you want to check right away that the db is available and accessible you can do this and check for errs
|
|
|
|
|
err = db.Ping()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal("DB Ping failed. Check database and database connection parameters")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
id int
|
|
|
|
|
subject string
|
|
|
|
|
todo string
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// db.Query vs db.Exec. If a function name includes Query, it is designed to ask a question of the database,
|
|
|
|
|
// and will return a set of rows, even if it’s empty. Statements that don’t return rows should not use Query functions;
|
|
|
|
|
// they should use Exec()
|
|
|
|
|
|
|
|
|
|
rows, err := db.Query("SELECT subject FROM todos")
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
// It is important to defer closing a row object also. Why? It will release the memory and the network connection. So it
|
|
|
|
|
// also prevents resource leaks. So you are a good steward, you clean up after yourself.
|
|
|
|
|
// You don't wait for the garbage collector to do it.
|
|
|
|
|
|
|
|
|
|
// as long as there’s an open result set (represented by rows), the underlying connection is busy and can’t be used for
|
|
|
|
|
//any other query.
|
|
|
|
|
|
|
|
|
|
// Also never defer in a loop. Defer only runs on function exit.
|
|
|
|
|
defer rows.Close()
|
|
|
|
|
// Iterate through each. Internally rows.Next will hit an EOF error it will call rows.close() for you freeing up the connection resources.
|
|
|
|
|
// but you shouldn't rely on that alone. rows.Close() will run on function exit, but this example is a Main func, so it's not realistic
|
|
|
|
|
// still get in the habit
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
err = rows.Scan(&subject)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
log.Printf("Subject is %s", subject)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don’t just assume that the loop iterates until you’ve processed all the rows. Always check.
|
|
|
|
|
err = rows.Err()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// You could also clean up at this point. The call is supposed to be idempotent, but defer is more idiomatic.
|
|
|
|
|
// You don't want to call close if there was an Error because of that could cause a Panic.
|
|
|
|
|
rows.Close()
|
|
|
|
|
|
|
|
|
|
// If never want to do string concat from user input into a sql statement you run unless you like sql injection attacks
|
|
|
|
|
// expects to return one row. If no row it will delay throwing error until Scan is called
|
|
|
|
|
row := db.QueryRow("SELECT id, subject, todo FROM todos WHERE id = ?", 1)
|
|
|
|
|
err = row.Scan(&id, &subject, &todo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Printf("id: %d, subject: %s, todo: %s", id, subject, todo)
|
|
|
|
|
|
|
|
|
|
// This should error on scan
|
|
|
|
|
|
|
|
|
|
row = db.QueryRow("SELECT * FROM todos WHERE id = ?", 100_000)
|
|
|
|
|
err = row.Scan(&id, &subject, &todo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Print(err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// There is no row.Close(). Rows objects have a Close() method. No explicit closing required.
|
|
|
|
|
|
|
|
|
|
}
|