package main
import (
"database/sql"
"flag"
"log"
_ "github.com/mattn/go-sqlite3"
)
func showTransactionStmtClose ( p * sql . Stmt ) {
log . Println ( "Closing statement!" )
p . Close ( )
}
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. It's busy because sql.Rows objects don't retrieve all results a query returns. It does it lazily to prevent over runing memory.
// it depends on the driver but there is usually and internal buffer that stores results.
// 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 )
// Scan does the heavily lifting for you when casting from db type to go type. It will cast based on the variable type.
// failures of course will be returned as part of the err
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
// We can also use this short hand!
err = db . QueryRow ( "SELECT * FROM todos WHERE id = ?" , 100_000 ) . 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.
//Prepared Queries
// If you will be using the same query over and over you should just prepare the query.
// $N (e.g. $1, $2, etc...) is the postgres query param which SQLite can use too ? is mysql's
//Under the hood, db.Query() actually prepares, executes, and closes a prepared statement
// That’ s three round-trips to the database.
stmt , err := db . Prepare ( "SELECT todo as other_todo FROM todos WHERE id = $1" )
if err != nil {
log . Fatal ( err )
}
//This prepared statement *sql.Stmt is tied to the database connection from which it was created, and it holds onto that connection until you call stmt.Close()
defer stmt . Close ( )
// You can also call with just params the .QueryRow method since the statement is already prepared
var other_todo string
err = stmt . QueryRow ( 1 ) . Scan ( & other_todo )
if err != nil {
log . Fatal ( err )
}
log . Printf ( "Other Todo: %s" , other_todo )
stmt . Close ( )
// MODIFYING DATA
// Use db.Exec() with a prepared statement when INSERTING, UPDATING, or DELETING
// Or another statement that doesn't return rows.
stmt , err = db . Prepare ( "INSERT INTO todos (subject, todo) VALUES ($1, $2)" )
if err != nil {
log . Fatal ( err )
}
defer stmt . Close ( )
// stmt.Exec returns a sql.Result. That also has access to the statement metadata
// use _ if you don't care about the result and only want to check the err.
// Exec will NOT reserve a connection. unlike db.Query which returns a sql.Rows that
// will hold on to a connection until .Close() is called.
res , err := stmt . Exec ( "programming" , "learn golang database queries!" )
if err != nil {
log . Print ( "Dirp..." )
log . Fatal ( err )
}
lastId , err := res . LastInsertId ( )
if err != nil {
log . Fatal ( err )
}
log . Printf ( "Last inserted id: %d" , lastId )
rowCnt , err := res . RowsAffected ( )
if err != nil {
log . Fatal ( err )
}
log . Printf ( "The number of rows affected: %d" , rowCnt )
// close insert
stmt . Close ( )
stmt , err = db . Prepare ( "DELETE FROM todos WHERE subject = 'programming'" )
if err != nil {
log . Fatal ( err )
}
res , err = stmt . Exec ( )
if err != nil {
log . Fatal ( err )
}
rowCnt , err = res . RowsAffected ( )
if err != nil {
log . Fatal ( err )
}
log . Printf ( "The number of rows affected: %d" , rowCnt )
// TRANSACTIONS
//In Go, a transaction is essentially an object that reserves a connection to the datastore.
//It lets you do all of the operations we’ ve seen thus far, but guarantees that they’ ll be
//executed on the same connection.
// Starts with db.Begin() returning a sql.Tx and ends with a .Commit() or .Rollback() on the results sql.Tx
tx , err := db . Begin ( )
if err != nil {
log . Fatal ( err )
}
// Prepared statement in a transaction are bound to that transaction exclusively.
// DO NOT use sql BEGIN or COMMIT in a sql.Tx object. It could leave the DB and client in different states.
// or mess havoc with the connection pool and connection lifetime
insert_stmt , err := tx . Prepare ( "INSERT INTO todos (subject, todo) VALUES ($1, $2)" )
if err != nil {
log . Fatal ( err )
}
defer insert_stmt . Close ( ) // This closes at the end of the transaction, not the end of the function.
_ , _ = insert_stmt . Exec ( "dirp" , "just a place holder" )
// There is no Close method on a transaction. It is closed by using .Commit or .Rollback
tx . Rollback ( )
// PREPARED STATEMENTS IN MORE DEPTH
//At the database level, a prepared statement is bound to a single database connection.
//The typical flow is that the client sends a SQL statement with placeholders to the server
//for preparation, the server responds with a statement ID, and then the client executes
//the statement by sending its ID and parameters.
// THE ABOVE DOES NOT HAPPEN IN database/sql. Instead the statement is managed at the driver level
// Go doesn't expose connections directly to users. The sql.Stmt object remembers which connection
// from the pool it is associated with.
//Know that if a connection is busy it may automatically get a new connection and re-prepare the
// statement on a new connection. It could cause more connections than you think, more statement
// recreation, or even hitting a server limit on prepared statements.
// NOTE db.Query(sql, param1, param2) will create a prepared statement under the hood.
// If you don’ t want to use a prepared statement, you need to use fmt.Sprint() or similar to
//assemble the SQL, and pass this as the only argument to db.Query() or db.QueryRow() And your driver
//needs to support plaintext query execution,
// NOTE prepared statements on Tx are bound exclusively to the Tx. You cannot use a prepared statement
// created by db.Prepare nor can you use a Tx.Prepare statement with a db.Query. Tx.Stmt() can be used
// to copy a db.Prepared statement into a transaction, but don't do this.
// HANDLING ERRORS
// You should always explicitly close a sql.Rows result set. If rows.Close() returns an error, it’ s
// unclear what you should do, so logging the error message and ignoring or panic-ing might be the only sensible
// thing to do.
// Use sql.ErrNoRows in the event that no data is returned.
// rememeber db.QueryRow defers errors till Scan is called.
err = db . QueryRow ( "select subject from todos where id = $1" , 100_000 ) . Scan ( & subject )
if err != nil {
if err == sql . ErrNoRows {
// there were no rows, but otherwise no error occurred
log . Println ( "No rows were returned! But that is okay. We expected that could happen" )
} else {
log . Fatal ( err )
}
}
}