diff options
author | Ian Lance Taylor <iant@golang.org> | 2019-01-18 19:04:36 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2019-01-18 19:04:36 +0000 |
commit | 4f4a855d82a889cebcfca150a7a43909bcb6a346 (patch) | |
tree | f12bae0781920fa34669fe30b6f4615a86d9fb80 /libgo/go/database | |
parent | 225220d668dafb8262db7012bced688acbe63b33 (diff) | |
download | gcc-4f4a855d82a889cebcfca150a7a43909bcb6a346.zip gcc-4f4a855d82a889cebcfca150a7a43909bcb6a346.tar.gz gcc-4f4a855d82a889cebcfca150a7a43909bcb6a346.tar.bz2 |
libgo: update to Go1.12beta2
Reviewed-on: https://go-review.googlesource.com/c/158019
gotools/:
* Makefile.am (go_cmd_vet_files): Update for Go1.12beta2 release.
(GOTOOLS_TEST_TIMEOUT): Increase to 600.
(check-runtime): Export LD_LIBRARY_PATH before computing GOARCH
and GOOS.
(check-vet): Copy golang.org/x/tools into check-vet-dir.
* Makefile.in: Regenerate.
gcc/testsuite/:
* go.go-torture/execute/names-1.go: Stop using debug/xcoff, which
is no longer externally visible.
From-SVN: r268084
Diffstat (limited to 'libgo/go/database')
-rw-r--r-- | libgo/go/database/sql/convert.go | 45 | ||||
-rw-r--r-- | libgo/go/database/sql/driver/driver.go | 7 | ||||
-rw-r--r-- | libgo/go/database/sql/example_cli_test.go | 88 | ||||
-rw-r--r-- | libgo/go/database/sql/example_service_test.go | 160 | ||||
-rw-r--r-- | libgo/go/database/sql/fakedb_test.go | 20 | ||||
-rw-r--r-- | libgo/go/database/sql/sql.go | 56 | ||||
-rw-r--r-- | libgo/go/database/sql/sql_test.go | 102 |
7 files changed, 448 insertions, 30 deletions
diff --git a/libgo/go/database/sql/convert.go b/libgo/go/database/sql/convert.go index 92a2ebe..c450d98 100644 --- a/libgo/go/database/sql/convert.go +++ b/libgo/go/database/sql/convert.go @@ -203,10 +203,18 @@ func driverArgsConnLocked(ci driver.Conn, ds *driverStmt, args []interface{}) ([ } -// convertAssign copies to dest the value in src, converting it if possible. -// An error is returned if the copy would result in loss of information. -// dest should be a pointer type. +// convertAssign is the same as convertAssignRows, but without the optional +// rows argument. func convertAssign(dest, src interface{}) error { + return convertAssignRows(dest, src, nil) +} + +// convertAssignRows copies to dest the value in src, converting it if possible. +// An error is returned if the copy would result in loss of information. +// dest should be a pointer type. If rows is passed in, the rows will +// be used as the parent for any cursor values converted from a +// driver.Rows to a *Rows. +func convertAssignRows(dest, src interface{}, rows *Rows) error { // Common cases, without reflect. switch s := src.(type) { case string: @@ -299,6 +307,35 @@ func convertAssign(dest, src interface{}) error { *d = nil return nil } + // The driver is returning a cursor the client may iterate over. + case driver.Rows: + switch d := dest.(type) { + case *Rows: + if d == nil { + return errNilPtr + } + if rows == nil { + return errors.New("invalid context to convert cursor rows, missing parent *Rows") + } + rows.closemu.Lock() + *d = Rows{ + dc: rows.dc, + releaseConn: func(error) {}, + rowsi: s, + } + // Chain the cancel function. + parentCancel := rows.cancel + rows.cancel = func() { + // When Rows.cancel is called, the closemu will be locked as well. + // So we can access rs.lasterr. + d.close(rows.lasterr) + if parentCancel != nil { + parentCancel() + } + } + rows.closemu.Unlock() + return nil + } } var sv reflect.Value @@ -381,7 +418,7 @@ func convertAssign(dest, src interface{}) error { return nil } dv.Set(reflect.New(dv.Type().Elem())) - return convertAssign(dv.Interface(), src) + return convertAssignRows(dv.Interface(), src, rows) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: s := asString(src) i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) diff --git a/libgo/go/database/sql/driver/driver.go b/libgo/go/database/sql/driver/driver.go index 1e54b4c..5ff2bc9 100644 --- a/libgo/go/database/sql/driver/driver.go +++ b/libgo/go/database/sql/driver/driver.go @@ -24,6 +24,11 @@ import ( // []byte // string // time.Time +// +// If the driver supports cursors, a returned Value may also implement the Rows interface +// in this package. This is used when, for example, when a user selects a cursor +// such as "select cursor(select * from my_table) from dual". If the Rows +// from the select is closed, the cursor Rows will also be closed. type Value interface{} // NamedValue holds both the value name and value. @@ -469,7 +474,7 @@ type RowsAffected int64 var _ Result = RowsAffected(0) func (RowsAffected) LastInsertId() (int64, error) { - return 0, errors.New("no LastInsertId available") + return 0, errors.New("LastInsertId is not supported by this driver") } func (v RowsAffected) RowsAffected() (int64, error) { diff --git a/libgo/go/database/sql/example_cli_test.go b/libgo/go/database/sql/example_cli_test.go new file mode 100644 index 0000000..c87c508 --- /dev/null +++ b/libgo/go/database/sql/example_cli_test.go @@ -0,0 +1,88 @@ +// 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. + +// +build ignore + +package sql_test + +import ( + "context" + "database/sql" + "flag" + "log" + "os" + "os/signal" + "time" +) + +var pool *sql.DB // Database connection pool. + +func Example_openDBCLI() { + id := flag.Int64("id", 0, "person ID to find") + dsn := flag.String("dsn", os.Getenv("DSN"), "connection data source name") + flag.Parse() + + if len(*dsn) == 0 { + log.Fatal("missing dsn flag") + } + if *id == 0 { + log.Fatal("missing person ID") + } + var err error + + // Opening a driver typically will not attempt to connect to the database. + pool, err = sql.Open("driver-name", *dsn) + if err != nil { + // This will not be a connection error, but a DSN parse error or + // another initialization error. + log.Fatal("unable to use data source name", err) + } + defer pool.Close() + + pool.SetConnMaxLifetime(0) + pool.SetMaxIdleConns(3) + pool.SetMaxOpenConns(3) + + ctx, stop := context.WithCancel(context.Background()) + defer stop() + + appSignal := make(chan os.Signal, 3) + signal.Notify(appSignal, os.Interrupt) + + go func() { + select { + case <-appSignal: + stop() + } + }() + + Ping(ctx) + + Query(ctx, *id) +} + +// Ping the database to verify DSN provided by the user is valid and the +// server accessible. If the ping fails exit the program with an error. +func Ping(ctx context.Context) { + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + + if err := pool.PingContext(ctx); err != nil { + log.Fatalf("unable to connect to database: %v", err) + } +} + +// Query the database for the information requested and prints the results. +// If the query fails exit the program with an error. +func Query(ctx context.Context, id int64) { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + var name string + err := pool.QueryRowContext(ctx, "select p.name from people as p where p.id = :id;", sql.Named("id", id)).Scan(&name) + if err != nil { + log.Fatal("unable to execute search query", err) + } + log.Println("name=", name) +} diff --git a/libgo/go/database/sql/example_service_test.go b/libgo/go/database/sql/example_service_test.go new file mode 100644 index 0000000..7605e2f --- /dev/null +++ b/libgo/go/database/sql/example_service_test.go @@ -0,0 +1,160 @@ +// 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. + +// +build ignore + +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}) + _, 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 + } +} diff --git a/libgo/go/database/sql/fakedb_test.go b/libgo/go/database/sql/fakedb_test.go index a21bae6..dcdd264 100644 --- a/libgo/go/database/sql/fakedb_test.go +++ b/libgo/go/database/sql/fakedb_test.go @@ -539,7 +539,7 @@ func (c *fakeConn) prepareCreate(stmt *fakeStmt, parts []string) (*fakeStmt, err } // parts are table|col=?,col2=val -func (c *fakeConn) prepareInsert(stmt *fakeStmt, parts []string) (*fakeStmt, error) { +func (c *fakeConn) prepareInsert(ctx context.Context, stmt *fakeStmt, parts []string) (*fakeStmt, error) { if len(parts) != 2 { stmt.Close() return nil, errf("invalid INSERT syntax with %d parts; want 2", len(parts)) @@ -574,6 +574,20 @@ func (c *fakeConn) prepareInsert(stmt *fakeStmt, parts []string) (*fakeStmt, err return nil, errf("invalid conversion to int32 from %q", value) } subsetVal = int64(i) // int64 is a subset type, but not int32 + case "table": // For testing cursor reads. + c.skipDirtySession = true + vparts := strings.Split(value, "!") + + substmt, err := c.PrepareContext(ctx, fmt.Sprintf("SELECT|%s|%s|", vparts[0], strings.Join(vparts[1:], ","))) + if err != nil { + return nil, err + } + cursor, err := (substmt.(driver.StmtQueryContext)).QueryContext(ctx, []driver.NamedValue{}) + substmt.Close() + if err != nil { + return nil, err + } + subsetVal = cursor default: stmt.Close() return nil, errf("unsupported conversion for pre-bound parameter %q to type %q", value, ctype) @@ -658,11 +672,11 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm case "CREATE": stmt, err = c.prepareCreate(stmt, parts) case "INSERT": - stmt, err = c.prepareInsert(stmt, parts) + stmt, err = c.prepareInsert(ctx, stmt, parts) case "NOSERT": // Do all the prep-work like for an INSERT but don't actually insert the row. // Used for some of the concurrent tests. - stmt, err = c.prepareInsert(stmt, parts) + stmt, err = c.prepareInsert(ctx, stmt, parts) default: stmt.Close() return nil, errf("unsupported command type %q", cmd) diff --git a/libgo/go/database/sql/sql.go b/libgo/go/database/sql/sql.go index 3617985..b0353ab 100644 --- a/libgo/go/database/sql/sql.go +++ b/libgo/go/database/sql/sql.go @@ -133,6 +133,7 @@ const ( LevelLinearizable ) +// String returns the name of the transaction isolation level. func (i IsolationLevel) String() string { switch i { case LevelDefault: @@ -567,7 +568,6 @@ type finalCloser interface { // addDep notes that x now depends on dep, and x's finalClose won't be // called until all of x's dependencies are removed with removeDep. func (db *DB) addDep(x finalCloser, dep interface{}) { - //println(fmt.Sprintf("addDep(%T %p, %T %p)", x, x, dep, dep)) db.mu.Lock() defer db.mu.Unlock() db.addDepLocked(x, dep) @@ -597,7 +597,6 @@ func (db *DB) removeDep(x finalCloser, dep interface{}) error { } func (db *DB) removeDepLocked(x finalCloser, dep interface{}) func() error { - //println(fmt.Sprintf("removeDep(%T %p, %T %p)", x, x, dep, dep)) xdep, ok := db.dep[x] if !ok { @@ -1322,11 +1321,13 @@ func (db *DB) putConnDBLocked(dc *driverConn, err error) bool { err: err, } return true - } else if err == nil && !db.closed && db.maxIdleConnsLocked() > len(db.freeConn) { - db.freeConn = append(db.freeConn, dc) + } else if err == nil && !db.closed { + if db.maxIdleConnsLocked() > len(db.freeConn) { + db.freeConn = append(db.freeConn, dc) + db.startCleanerLocked() + return true + } db.maxIdleClosed++ - db.startCleanerLocked() - return true } return false } @@ -2605,6 +2606,15 @@ type Rows struct { lastcols []driver.Value } +// lasterrOrErrLocked returns either lasterr or the provided err. +// rs.closemu must be read-locked. +func (rs *Rows) lasterrOrErrLocked(err error) error { + if rs.lasterr != nil && rs.lasterr != io.EOF { + return rs.lasterr + } + return err +} + func (rs *Rows) initContextClose(ctx, txctx context.Context) { if ctx.Done() == nil && (txctx == nil || txctx.Done() == nil) { return @@ -2681,7 +2691,7 @@ func (rs *Rows) nextLocked() (doClose, ok bool) { return false, true } -// NextResultSet prepares the next result set for reading. It returns true if +// NextResultSet prepares the next result set for reading. It reports whether // there is further result sets, or false if there is no further result set // or if there is an error advancing to it. The Err method should be consulted // to distinguish between the two cases. @@ -2728,23 +2738,22 @@ func (rs *Rows) NextResultSet() bool { func (rs *Rows) Err() error { rs.closemu.RLock() defer rs.closemu.RUnlock() - if rs.lasterr == io.EOF { - return nil - } - return rs.lasterr + return rs.lasterrOrErrLocked(nil) } +var errRowsClosed = errors.New("sql: Rows are closed") +var errNoRows = errors.New("sql: no Rows available") + // Columns returns the column names. -// Columns returns an error if the rows are closed, or if the rows -// are from QueryRow and there was a deferred error. +// Columns returns an error if the rows are closed. func (rs *Rows) Columns() ([]string, error) { rs.closemu.RLock() defer rs.closemu.RUnlock() if rs.closed { - return nil, errors.New("sql: Rows are closed") + return nil, rs.lasterrOrErrLocked(errRowsClosed) } if rs.rowsi == nil { - return nil, errors.New("sql: no Rows available") + return nil, rs.lasterrOrErrLocked(errNoRows) } rs.dc.Lock() defer rs.dc.Unlock() @@ -2758,10 +2767,10 @@ func (rs *Rows) ColumnTypes() ([]*ColumnType, error) { rs.closemu.RLock() defer rs.closemu.RUnlock() if rs.closed { - return nil, errors.New("sql: Rows are closed") + return nil, rs.lasterrOrErrLocked(errRowsClosed) } if rs.rowsi == nil { - return nil, errors.New("sql: no Rows available") + return nil, rs.lasterrOrErrLocked(errNoRows) } rs.dc.Lock() defer rs.dc.Unlock() @@ -2812,7 +2821,7 @@ func (ci *ColumnType) ScanType() reflect.Type { return ci.scanType } -// Nullable returns whether the column may be null. +// Nullable reports whether the column may be null. // If a driver does not support this property ok will be false. func (ci *ColumnType) Nullable() (nullable, ok bool) { return ci.nullable, ci.hasNullable @@ -2873,6 +2882,7 @@ func rowsColumnInfoSetupConnLocked(rowsi driver.Rows) []*ColumnType { // *float32, *float64 // *interface{} // *RawBytes +// *Rows (cursor value) // any type implementing Scanner (see Scanner docs) // // In the most simple case, if the type of the value from the source @@ -2909,6 +2919,11 @@ func rowsColumnInfoSetupConnLocked(rowsi driver.Rows) []*ColumnType { // // For scanning into *bool, the source may be true, false, 1, 0, or // string inputs parseable by strconv.ParseBool. +// +// Scan can also convert a cursor returned from a query, such as +// "select cursor(select * from my_table) from dual", into a +// *Rows value that can itself be scanned from. The parent +// select query will close any cursor *Rows if the parent *Rows is closed. func (rs *Rows) Scan(dest ...interface{}) error { rs.closemu.RLock() @@ -2917,8 +2932,9 @@ func (rs *Rows) Scan(dest ...interface{}) error { return rs.lasterr } if rs.closed { + err := rs.lasterrOrErrLocked(errRowsClosed) rs.closemu.RUnlock() - return errors.New("sql: Rows are closed") + return err } rs.closemu.RUnlock() @@ -2929,7 +2945,7 @@ func (rs *Rows) Scan(dest ...interface{}) error { return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest)) } for i, sv := range rs.lastcols { - err := convertAssign(dest[i], sv) + err := convertAssignRows(dest[i], sv, rs) if err != nil { return fmt.Errorf(`sql: Scan error on column index %d, name %q: %v`, i, rs.rowsi.Columns()[i], err) } diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go index f194744..64b9dfe 100644 --- a/libgo/go/database/sql/sql_test.go +++ b/libgo/go/database/sql/sql_test.go @@ -397,7 +397,7 @@ func TestQueryContextWait(t *testing.T) { prepares0 := numPrepares(t, db) // TODO(kardianos): convert this from using a timeout to using an explicit - // cancel when the query signals that is is "executing" the query. + // cancel when the query signals that it is "executing" the query. ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond) defer cancel() @@ -597,7 +597,7 @@ func TestPoolExhaustOnCancel(t *testing.T) { state := 0 // waiter will be called for all queries, including - // initial setup queries. The state is only assigned when no + // initial setup queries. The state is only assigned when // no queries are made. // // Only allow the first batch of queries to finish once the @@ -1338,6 +1338,52 @@ func TestConnQuery(t *testing.T) { } } +func TestCursorFake(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + exec(t, db, "CREATE|peoplecursor|list=table") + exec(t, db, "INSERT|peoplecursor|list=people!name!age") + + rows, err := db.QueryContext(ctx, `SELECT|peoplecursor|list|`) + if err != nil { + t.Fatal(err) + } + defer rows.Close() + + if !rows.Next() { + t.Fatal("no rows") + } + var cursor = &Rows{} + err = rows.Scan(cursor) + if err != nil { + t.Fatal(err) + } + defer cursor.Close() + + const expectedRows = 3 + var currentRow int64 + + var n int64 + var s string + for cursor.Next() { + currentRow++ + err = cursor.Scan(&s, &n) + if err != nil { + t.Fatal(err) + } + if n != currentRow { + t.Errorf("expected number(Age)=%d, got %d", currentRow, n) + } + } + if currentRow != expectedRows { + t.Errorf("expected %d rows, got %d rows", expectedRows, currentRow) + } +} + func TestInvalidNilValues(t *testing.T) { var date1 time.Time var date2 int @@ -3415,6 +3461,58 @@ func TestConnectionLeak(t *testing.T) { wg.Wait() } +func TestStatsMaxIdleClosedZero(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(1) + db.SetConnMaxLifetime(0) + + preMaxIdleClosed := db.Stats().MaxIdleClosed + + for i := 0; i < 10; i++ { + rows, err := db.Query("SELECT|people|name|") + if err != nil { + t.Fatal(err) + } + rows.Close() + } + + st := db.Stats() + maxIdleClosed := st.MaxIdleClosed - preMaxIdleClosed + t.Logf("MaxIdleClosed: %d", maxIdleClosed) + if maxIdleClosed != 0 { + t.Fatal("expected 0 max idle closed conns, got: ", maxIdleClosed) + } +} + +func TestStatsMaxIdleClosedTen(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(0) + db.SetConnMaxLifetime(0) + + preMaxIdleClosed := db.Stats().MaxIdleClosed + + for i := 0; i < 10; i++ { + rows, err := db.Query("SELECT|people|name|") + if err != nil { + t.Fatal(err) + } + rows.Close() + } + + st := db.Stats() + maxIdleClosed := st.MaxIdleClosed - preMaxIdleClosed + t.Logf("MaxIdleClosed: %d", maxIdleClosed) + if maxIdleClosed != 10 { + t.Fatal("expected 0 max idle closed conns, got: ", maxIdleClosed) + } +} + type nvcDriver struct { fakeDriver skipNamedValueCheck bool |