| // Copyright 2018 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package sql_test | |
| import ( | |
| "context" | |
| "database/sql" | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "log" | |
| "net/http" | |
| "time" | |
| ) | |
| func Example_openDBService() { | |
| // Opening a driver typically will not attempt to connect to the database. | |
| db, err := sql.Open("driver-name", "database=test1") | |
| if err != nil { | |
| // This will not be a connection error, but a DSN parse error or | |
| // another initialization error. | |
| log.Fatal(err) | |
| } | |
| db.SetConnMaxLifetime(0) | |
| db.SetMaxIdleConns(50) | |
| db.SetMaxOpenConns(50) | |
| s := &Service{db: db} | |
| http.ListenAndServe(":8080", s) | |
| } | |
| type Service struct { | |
| db *sql.DB | |
| } | |
| func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
| db := s.db | |
| switch r.URL.Path { | |
| default: | |
| http.Error(w, "not found", http.StatusNotFound) | |
| return | |
| case "/healthz": | |
| ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second) | |
| defer cancel() | |
| err := s.db.PingContext(ctx) | |
| if err != nil { | |
| http.Error(w, fmt.Sprintf("db down: %v", err), http.StatusFailedDependency) | |
| return | |
| } | |
| w.WriteHeader(http.StatusOK) | |
| return | |
| case "/quick-action": | |
| // This is a short SELECT. Use the request context as the base of | |
| // the context timeout. | |
| ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) | |
| defer cancel() | |
| id := 5 | |
| org := 10 | |
| var name string | |
| err := db.QueryRowContext(ctx, ` | |
| select | |
| p.name | |
| from | |
| people as p | |
| join organization as o on p.organization = o.id | |
| where | |
| p.id = :id | |
| and o.id = :org | |
| ;`, | |
| sql.Named("id", id), | |
| sql.Named("org", org), | |
| ).Scan(&name) | |
| if err != nil { | |
| if err == sql.ErrNoRows { | |
| http.Error(w, "not found", http.StatusNotFound) | |
| return | |
| } | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| io.WriteString(w, name) | |
| return | |
| case "/long-action": | |
| // This is a long SELECT. Use the request context as the base of | |
| // the context timeout, but give it some time to finish. If | |
| // the client cancels before the query is done the query will also | |
| // be canceled. | |
| ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second) | |
| defer cancel() | |
| var names []string | |
| rows, err := db.QueryContext(ctx, "select p.name from people as p where p.active = true;") | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| for rows.Next() { | |
| var name string | |
| err = rows.Scan(&name) | |
| if err != nil { | |
| break | |
| } | |
| names = append(names, name) | |
| } | |
| // Check for errors during rows "Close". | |
| // This may be more important if multiple statements are executed | |
| // in a single batch and rows were written as well as read. | |
| if closeErr := rows.Close(); closeErr != nil { | |
| http.Error(w, closeErr.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| // Check for row scan error. | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| // Check for errors during row iteration. | |
| if err = rows.Err(); err != nil { | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| json.NewEncoder(w).Encode(names) | |
| return | |
| case "/async-action": | |
| // This action has side effects that we want to preserve | |
| // even if the client cancels the HTTP request part way through. | |
| // For this we do not use the http request context as a base for | |
| // the timeout. | |
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | |
| defer cancel() | |
| var orderRef = "ABC123" | |
| tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable}) | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| _, err = tx.ExecContext(ctx, "stored_proc_name", orderRef) | |
| if err != nil { | |
| tx.Rollback() | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| err = tx.Commit() | |
| if err != nil { | |
| http.Error(w, "action in unknown state, check state before attempting again", http.StatusInternalServerError) | |
| return | |
| } | |
| w.WriteHeader(http.StatusOK) | |
| return | |
| } | |
| } |