diff --git a/go-sql-database/try-sqlite/data/trysqlite.db b/go-sql-database/try-sqlite/data/trysqlite.db index 51730b8..d1cb1d4 100644 Binary files a/go-sql-database/try-sqlite/data/trysqlite.db and b/go-sql-database/try-sqlite/data/trysqlite.db differ diff --git a/go-sql-database/try-sqlite/data/trysqlite.db-shm b/go-sql-database/try-sqlite/data/trysqlite.db-shm new file mode 100644 index 0000000..cbb8862 Binary files /dev/null and b/go-sql-database/try-sqlite/data/trysqlite.db-shm differ diff --git a/go-sql-database/try-sqlite/data/trysqlite.db-wal b/go-sql-database/try-sqlite/data/trysqlite.db-wal new file mode 100644 index 0000000..1da2d5d Binary files /dev/null and b/go-sql-database/try-sqlite/data/trysqlite.db-wal differ diff --git a/go-sql-database/try-sqlite/main.go b/go-sql-database/try-sqlite/main.go index 934bd9f..ed781e9 100644 --- a/go-sql-database/try-sqlite/main.go +++ b/go-sql-database/try-sqlite/main.go @@ -8,6 +8,11 @@ import ( _ "github.com/mattn/go-sqlite3" ) +func showTransactionStmtClose(p *sql.Stmt) { + log.Println("Closing statement!") + p.Close() +} + func main() { log.Println("Hello, world!") @@ -66,7 +71,8 @@ func main() { // 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. + //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() @@ -114,11 +120,11 @@ func main() { //Prepared Queries // If you will be using the same query over and over you should just prepare the query. - // $N is the postgres query param which SQLite can use too ? is mysql's + // $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 * FROM todos WHERE id = $N") + stmt, err := db.Prepare("SELECT todo as other_todo FROM todos WHERE id = $1") if err != nil { log.Fatal(err) } @@ -126,4 +132,138 @@ func main() { //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) + } + } }