]> Cypherpunks repositories - gostls13.git/commitdiff
database/sql: add more examples
authorKevin Burke <kev@inburke.com>
Wed, 31 Jan 2018 03:25:37 +0000 (19:25 -0800)
committerDaniel Theophanes <kardianos@gmail.com>
Sun, 25 Mar 2018 05:09:57 +0000 (05:09 +0000)
This aims to expand the coverage of examples showing how the sql
package works, as well as to address a number of issues I've observed
while explaining how the database package works:

- The best way to issue UPDATE or INSERT queries, that don't need
to scan anything in return. (Previously, we had no examples for any
Execute statement).

- How to use prepared statements and transactions.

- How to aggregate arguments from a Query/QueryContext query into
a slice.

Furthermore just having examples in more places should help, as users
click on e.g. the "Rows" return parameter and are treated with the
lack of any example about how Rows is used.

Switch package examples to use QueryContext/QueryRowContext; I think
it is a good practice to prepare users to issue queries with a timeout
attached, even if they are not using it immediately.

Change-Id: I4e63af91c7e4fff88b25f820906104ecefde4cc3
Reviewed-on: https://go-review.googlesource.com/91015
Reviewed-by: Daniel Theophanes <kardianos@gmail.com>
Run-TryBot: Daniel Theophanes <kardianos@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/database/sql/example_test.go
src/database/sql/sql.go

index ce56ca4cb038718aeb0b2533860ff3dac5b77db8..da938b071a121df8771cb2dddc9e8c706b12dbda 100644 (file)
@@ -5,43 +5,65 @@
 package sql_test
 
 import (
+       "context"
        "database/sql"
        "fmt"
        "log"
+       "strings"
+       "time"
 )
 
+var ctx = context.Background()
 var db *sql.DB
 
-func ExampleDB_Query() {
+func ExampleDB_QueryContext() {
        age := 27
-       rows, err := db.Query("SELECT name FROM users WHERE age=?", age)
+       rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
        if err != nil {
                log.Fatal(err)
        }
        defer rows.Close()
+       names := make([]string, 0)
        for rows.Next() {
                var name string
                if err := rows.Scan(&name); err != nil {
                        log.Fatal(err)
                }
-               fmt.Printf("%s is %d\n", name, age)
+               names = append(names, name)
        }
        if err := rows.Err(); err != nil {
                log.Fatal(err)
        }
+       fmt.Printf("%s are %d years old", strings.Join(names, ", "), age)
 }
 
-func ExampleDB_QueryRow() {
+func ExampleDB_QueryRowContext() {
        id := 123
        var username string
-       err := db.QueryRow("SELECT username FROM users WHERE id=?", id).Scan(&username)
+       var created time.Time
+       err := db.QueryRowContext(ctx, "SELECT username, created_at FROM users WHERE id=?", id).Scan(&username, &created)
        switch {
        case err == sql.ErrNoRows:
-               log.Printf("No user with that ID.")
+               log.Printf("No user with id %d", id)
        case err != nil:
                log.Fatal(err)
        default:
-               fmt.Printf("Username is %s\n", username)
+               fmt.Printf("Username is %s, account created on %s\n", username, created)
+       }
+}
+
+func ExampleDB_ExecContext() {
+       id := 47
+       result, err := db.ExecContext(ctx, "UPDATE balances SET balance = balance + 10 WHERE user_id = ?", id)
+       if err != nil {
+               log.Fatal(err)
+       }
+       rows, err := result.RowsAffected()
+       if err != nil {
+               log.Fatal(err)
+       }
+       if rows != 1 {
+               panic(err)
        }
 }
 
@@ -106,3 +128,154 @@ from
                log.Fatal(err)
        }
 }
+
+func ExampleDB_PingContext() {
+       ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+       defer cancel()
+       if err := db.PingContext(ctx); err != nil {
+               log.Fatal(err)
+       }
+}
+
+func ExampleConn_BeginTx() {
+       tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
+       if err != nil {
+               log.Fatal(err)
+       }
+       id := 37
+       _, execErr := tx.Exec(`UPDATE users SET status = ? WHERE id = ?`, "paid", id)
+       if execErr != nil {
+               _ = tx.Rollback()
+               log.Fatal(execErr)
+       }
+       if err := tx.Commit(); err != nil {
+               log.Fatal(err)
+       }
+}
+
+func ExampleConn_ExecContext() {
+       // A *DB is a pool of connections. Call Conn to reserve a connection for
+       // exclusive use.
+       conn, err := db.Conn(ctx)
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer conn.Close() // Return the connection to the pool.
+       id := 41
+       result, err := conn.ExecContext(ctx, `UPDATE balances SET balance = balance + 10 WHERE user_id = ?`, id)
+       if err != nil {
+               log.Fatal(err)
+       }
+       rows, err := result.RowsAffected()
+       if err != nil {
+               log.Fatal(err)
+       }
+       if rows != 1 {
+               panic(err)
+       }
+}
+
+func ExampleTx_ExecContext() {
+       tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
+       if err != nil {
+               log.Fatal(err)
+       }
+       id := 37
+       _, execErr := tx.ExecContext(ctx, "UPDATE users SET status = ? WHERE id = ?", "paid", id)
+       if execErr != nil {
+               if rollbackErr := tx.Rollback(); rollbackErr != nil {
+                       log.Printf("Could not roll back: %v\n", rollbackErr)
+               }
+               log.Fatal(execErr)
+       }
+       if err := tx.Commit(); err != nil {
+               log.Fatal(err)
+       }
+}
+
+func ExampleTx_Rollback() {
+       tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
+       if err != nil {
+               log.Fatal(err)
+       }
+       id := 53
+       _, err = tx.ExecContext(ctx, "UPDATE drivers SET status = ? WHERE id = ?", "assigned", id)
+       if err != nil {
+               if rollbackErr := tx.Rollback(); rollbackErr != nil {
+                       log.Printf("Could not roll back: %v\n", rollbackErr)
+               }
+               log.Fatal(err)
+       }
+       _, err = tx.ExecContext(ctx, "UPDATE pickups SET driver_id = $1", id)
+       if err != nil {
+               if rollbackErr := tx.Rollback(); rollbackErr != nil {
+                       log.Printf("Could not roll back: %v\n", rollbackErr)
+               }
+               log.Fatal(err)
+       }
+       if err := tx.Commit(); err != nil {
+               log.Fatal(err)
+       }
+}
+
+func ExampleStmt() {
+       // In normal use, create one Stmt when your process starts.
+       stmt, err := db.PrepareContext(ctx, "SELECT username FROM users WHERE id = ?")
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer stmt.Close()
+       // Then reuse it each time you need to issue the query.
+       id := 43
+       var username string
+       err = stmt.QueryRowContext(ctx, id).Scan(&username)
+       switch {
+       case err == sql.ErrNoRows:
+               log.Printf("No user with that ID.")
+       case err != nil:
+               log.Fatal(err)
+       default:
+               fmt.Printf("Username is %s\n", username)
+       }
+}
+
+func ExampleStmt_QueryRowContext() {
+       // In normal use, create one Stmt when your process starts.
+       stmt, err := db.PrepareContext(ctx, "SELECT username FROM users WHERE id = ?")
+       if err != nil {
+               log.Fatal(err)
+       }
+       // Then reuse it each time you need to issue the query.
+       id := 43
+       var username string
+       err = stmt.QueryRowContext(ctx, id).Scan(&username)
+       switch {
+       case err == sql.ErrNoRows:
+               log.Printf("No user with that ID.")
+       case err != nil:
+               log.Fatal(err)
+       default:
+               fmt.Printf("Username is %s\n", username)
+       }
+}
+
+func ExampleRows() {
+       age := 27
+       rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer rows.Close()
+       names := make([]string, 0)
+       for rows.Next() {
+               var name string
+               if err := rows.Scan(&name); err != nil {
+                       log.Fatal(err)
+               }
+               names = append(names, name)
+       }
+       if err := rows.Err(); err != nil {
+               log.Fatal(err)
+       }
+       fmt.Printf("%s are %d years old", strings.Join(names, ", "), age)
+}
index 05d15455c03bc81e0780c2349cc724db1c40b498..784ffac26d9e4ac226598ce3552610189a6ba21d 100644 (file)
@@ -2472,11 +2472,6 @@ func rowsiFromStatement(ctx context.Context, ci driver.Conn, ds *driverStmt, arg
 // If the query selects no rows, the *Row's Scan will return ErrNoRows.
 // Otherwise, the *Row's Scan scans the first selected row and discards
 // the rest.
-//
-// Example usage:
-//
-//  var name string
-//  err := nameByUseridStmt.QueryRowContext(ctx, id).Scan(&name)
 func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *Row {
        rows, err := s.QueryContext(ctx, args...)
        if err != nil {
@@ -2545,19 +2540,7 @@ func (s *Stmt) finalClose() error {
 }
 
 // Rows is the result of a query. Its cursor starts before the first row
-// of the result set. Use Next to advance through the rows:
-//
-//     rows, err := db.Query("SELECT ...")
-//     ...
-//     defer rows.Close()
-//     for rows.Next() {
-//         var id int
-//         var name string
-//         err = rows.Scan(&id, &name)
-//         ...
-//     }
-//     err = rows.Err() // get any error encountered during iteration
-//     ...
+// of the result set. Use Next to advance from row to row.
 type Rows struct {
        dc          *driverConn // owned; must call releaseConn when closed to release
        releaseConn func(error)