diff options
Diffstat (limited to 'libgo/go/database/sql/sql_test.go')
-rw-r--r-- | libgo/go/database/sql/sql_test.go | 650 |
1 files changed, 630 insertions, 20 deletions
diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go index 381aafc86b7..c935eb43480 100644 --- a/libgo/go/database/sql/sql_test.go +++ b/libgo/go/database/sql/sql_test.go @@ -139,6 +139,7 @@ func closeDB(t testing.TB, db *DB) { t.Errorf("Error closing fakeConn: %v", err) } }) + db.mu.Lock() for i, dc := range db.freeConn { if n := len(dc.openStmt); n > 0 { // Just a sanity check. This is legal in @@ -149,6 +150,8 @@ func closeDB(t testing.TB, db *DB) { t.Errorf("while closing db, freeConn %d/%d had %d open stmts; want 0", i, len(db.freeConn), n) } } + db.mu.Unlock() + err := db.Close() if err != nil { t.Fatalf("error closing DB: %v", err) @@ -322,7 +325,7 @@ func TestQueryContext(t *testing.T) { select { case <-ctx.Done(): if err := ctx.Err(); err != context.Canceled { - t.Fatalf("context err = %v; want context.Canceled", ctx.Err()) + t.Fatalf("context err = %v; want context.Canceled", err) } default: t.Fatalf("context err = nil; want context.Canceled") @@ -413,7 +416,7 @@ func TestTxContextWait(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*15) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Millisecond) defer cancel() tx, err := db.BeginTx(ctx, nil) @@ -590,13 +593,13 @@ func TestPoolExhaustOnCancel(t *testing.T) { saturate.Wait() // Now cancel the request while it is waiting. - ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() for i := 0; i < max; i++ { ctxReq, cancelReq := context.WithCancel(ctx) go func() { - time.Sleep(time.Millisecond * 100) + time.Sleep(100 * time.Millisecond) cancelReq() }() err := db.PingContext(ctxReq) @@ -874,7 +877,7 @@ func TestStatementClose(t *testing.T) { msg string }{ {&Stmt{stickyErr: want}, "stickyErr not propagated"}, - {&Stmt{tx: &Tx{}, txds: &driverStmt{Locker: &sync.Mutex{}, si: stubDriverStmt{want}}}, "driverStmt.Close() error not propagated"}, + {&Stmt{cg: &Tx{}, cgds: &driverStmt{Locker: &sync.Mutex{}, si: stubDriverStmt{want}}}, "driverStmt.Close() error not propagated"}, } for _, test := range tests { if err := test.stmt.Close(); err != want { @@ -1024,6 +1027,196 @@ func TestTxStmt(t *testing.T) { } } +func TestTxStmtPreparedOnce(t *testing.T) { + db := newTestDB(t, "") + defer closeDB(t, db) + exec(t, db, "CREATE|t1|name=string,age=int32") + + prepares0 := numPrepares(t, db) + + // db.Prepare increments numPrepares. + stmt, err := db.Prepare("INSERT|t1|name=?,age=?") + if err != nil { + t.Fatalf("Stmt, err = %v, %v", stmt, err) + } + defer stmt.Close() + + tx, err := db.Begin() + if err != nil { + t.Fatalf("Begin = %v", err) + } + + txs1 := tx.Stmt(stmt) + txs2 := tx.Stmt(stmt) + + _, err = txs1.Exec("Go", 7) + if err != nil { + t.Fatalf("Exec = %v", err) + } + txs1.Close() + + _, err = txs2.Exec("Gopher", 8) + if err != nil { + t.Fatalf("Exec = %v", err) + } + txs2.Close() + + err = tx.Commit() + if err != nil { + t.Fatalf("Commit = %v", err) + } + + if prepares := numPrepares(t, db) - prepares0; prepares != 1 { + t.Errorf("executed %d Prepare statements; want 1", prepares) + } +} + +func TestTxStmtClosedRePrepares(t *testing.T) { + db := newTestDB(t, "") + defer closeDB(t, db) + exec(t, db, "CREATE|t1|name=string,age=int32") + + prepares0 := numPrepares(t, db) + + // db.Prepare increments numPrepares. + stmt, err := db.Prepare("INSERT|t1|name=?,age=?") + if err != nil { + t.Fatalf("Stmt, err = %v, %v", stmt, err) + } + tx, err := db.Begin() + if err != nil { + t.Fatalf("Begin = %v", err) + } + err = stmt.Close() + if err != nil { + t.Fatalf("stmt.Close() = %v", err) + } + // tx.Stmt increments numPrepares because stmt is closed. + txs := tx.Stmt(stmt) + if txs.stickyErr != nil { + t.Fatal(txs.stickyErr) + } + if txs.parentStmt != nil { + t.Fatal("expected nil parentStmt") + } + _, err = txs.Exec(`Eric`, 82) + if err != nil { + t.Fatalf("txs.Exec = %v", err) + } + + err = txs.Close() + if err != nil { + t.Fatalf("txs.Close = %v", err) + } + + tx.Rollback() + + if prepares := numPrepares(t, db) - prepares0; prepares != 2 { + t.Errorf("executed %d Prepare statements; want 2", prepares) + } +} + +func TestParentStmtOutlivesTxStmt(t *testing.T) { + db := newTestDB(t, "") + defer closeDB(t, db) + exec(t, db, "CREATE|t1|name=string,age=int32") + + // Make sure everything happens on the same connection. + db.SetMaxOpenConns(1) + + prepares0 := numPrepares(t, db) + + // db.Prepare increments numPrepares. + stmt, err := db.Prepare("INSERT|t1|name=?,age=?") + if err != nil { + t.Fatalf("Stmt, err = %v, %v", stmt, err) + } + defer stmt.Close() + tx, err := db.Begin() + if err != nil { + t.Fatalf("Begin = %v", err) + } + txs := tx.Stmt(stmt) + if len(stmt.css) != 1 { + t.Fatalf("len(stmt.css) = %v; want 1", len(stmt.css)) + } + err = txs.Close() + if err != nil { + t.Fatalf("txs.Close() = %v", err) + } + err = tx.Rollback() + if err != nil { + t.Fatalf("tx.Rollback() = %v", err) + } + // txs must not be valid. + _, err = txs.Exec("Suzan", 30) + if err == nil { + t.Fatalf("txs.Exec(), expected err") + } + // Stmt must still be valid. + _, err = stmt.Exec("Janina", 25) + if err != nil { + t.Fatalf("stmt.Exec() = %v", err) + } + + if prepares := numPrepares(t, db) - prepares0; prepares != 1 { + t.Errorf("executed %d Prepare statements; want 1", prepares) + } +} + +// Test that tx.Stmt called with a statement already +// associated with tx as argument re-prepares the same +// statement again. +func TestTxStmtFromTxStmtRePrepares(t *testing.T) { + db := newTestDB(t, "") + defer closeDB(t, db) + exec(t, db, "CREATE|t1|name=string,age=int32") + prepares0 := numPrepares(t, db) + // db.Prepare increments numPrepares. + stmt, err := db.Prepare("INSERT|t1|name=?,age=?") + if err != nil { + t.Fatalf("Stmt, err = %v, %v", stmt, err) + } + defer stmt.Close() + + tx, err := db.Begin() + if err != nil { + t.Fatalf("Begin = %v", err) + } + txs1 := tx.Stmt(stmt) + + // tx.Stmt(txs1) increments numPrepares because txs1 already + // belongs to a transaction (albeit the same transaction). + txs2 := tx.Stmt(txs1) + if txs2.stickyErr != nil { + t.Fatal(txs2.stickyErr) + } + if txs2.parentStmt != nil { + t.Fatal("expected nil parentStmt") + } + _, err = txs2.Exec(`Eric`, 82) + if err != nil { + t.Fatal(err) + } + + err = txs1.Close() + if err != nil { + t.Fatalf("txs1.Close = %v", err) + } + err = txs2.Close() + if err != nil { + t.Fatalf("txs1.Close = %v", err) + } + err = tx.Rollback() + if err != nil { + t.Fatalf("tx.Rollback = %v", err) + } + + if prepares := numPrepares(t, db) - prepares0; prepares != 2 { + t.Errorf("executed %d Prepare statements; want 2", prepares) + } +} + // Issue: https://golang.org/issue/2784 // This test didn't fail before because we got lucky with the fakedb driver. // It was failing, and now not, in github.com/bradfitz/go-sql-test @@ -1108,6 +1301,69 @@ func TestTxErrBadConn(t *testing.T) { } } +func TestConnQuery(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + var name string + err = conn.QueryRowContext(ctx, "SELECT|people|name|age=?", 3).Scan(&name) + if err != nil { + t.Fatal(err) + } + if name != "Chris" { + t.Fatalf("unexpected result, got %q want Chris", name) + } + + err = conn.PingContext(ctx) + if err != nil { + t.Fatal(err) + } +} + +func TestConnTx(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + t.Fatal(err) + } + insertName, insertAge := "Nancy", 33 + _, err = tx.ExecContext(ctx, "INSERT|people|name=?,age=?,photo=APHOTO", insertName, insertAge) + if err != nil { + t.Fatal(err) + } + err = tx.Commit() + if err != nil { + t.Fatal(err) + } + + var selectName string + err = conn.QueryRowContext(ctx, "SELECT|people|name|age=?", insertAge).Scan(&selectName) + if err != nil { + t.Fatal(err) + } + if selectName != insertName { + t.Fatalf("got %q want %q", selectName, insertName) + } +} + // Tests fix for issue 2542, that we release a lock when querying on // a closed connection. func TestIssue2542Deadlock(t *testing.T) { @@ -1831,8 +2087,8 @@ func TestConnMaxLifetime(t *testing.T) { } // Expire first conn - offset = time.Second * 11 - db.SetConnMaxLifetime(time.Second * 10) + offset = 11 * time.Second + db.SetConnMaxLifetime(10 * time.Second) if err != nil { t.Fatal(err) } @@ -2078,9 +2334,13 @@ func TestStmtCloseOrder(t *testing.T) { // Test cases where there's more than maxBadConnRetries bad connections in the // pool (issue 8834) func TestManyErrBadConn(t *testing.T) { - manyErrBadConnSetup := func() *DB { + manyErrBadConnSetup := func(first ...func(db *DB)) *DB { db := newTestDB(t, "people") + for _, f := range first { + f(db) + } + nconn := maxBadConnRetries + 1 db.SetMaxIdleConns(nconn) db.SetMaxOpenConns(nconn) @@ -2148,6 +2408,128 @@ func TestManyErrBadConn(t *testing.T) { if err = stmt.Close(); err != nil { t.Fatal(err) } + + // Stmt.Exec + db = manyErrBadConnSetup(func(db *DB) { + stmt, err = db.Prepare("INSERT|people|name=Julia,age=19") + if err != nil { + t.Fatal(err) + } + }) + defer closeDB(t, db) + _, err = stmt.Exec() + if err != nil { + t.Fatal(err) + } + if err = stmt.Close(); err != nil { + t.Fatal(err) + } + + // Stmt.Query + db = manyErrBadConnSetup(func(db *DB) { + stmt, err = db.Prepare("SELECT|people|age,name|") + if err != nil { + t.Fatal(err) + } + }) + defer closeDB(t, db) + rows, err = stmt.Query() + if err != nil { + t.Fatal(err) + } + if err = rows.Close(); err != nil { + t.Fatal(err) + } + if err = stmt.Close(); err != nil { + t.Fatal(err) + } + + // Conn + db = manyErrBadConnSetup() + defer closeDB(t, db) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + err = conn.Close() + if err != nil { + t.Fatal(err) + } + + // Ping + db = manyErrBadConnSetup() + defer closeDB(t, db) + err = db.PingContext(ctx) + if err != nil { + t.Fatal(err) + } +} + +// TestIssue20575 ensures the Rows from query does not block +// closing a transaction. Ensure Rows is closed while closing a trasaction. +func TestIssue20575(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + tx, err := db.Begin() + if err != nil { + t.Fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + _, err = tx.QueryContext(ctx, "SELECT|people|age,name|") + if err != nil { + t.Fatal(err) + } + // Do not close Rows from QueryContext. + err = tx.Rollback() + if err != nil { + t.Fatal(err) + } + select { + default: + case <-ctx.Done(): + t.Fatal("timeout: failed to rollback query without closing rows:", ctx.Err()) + } +} + +// TestIssue20622 tests closing the transaction before rows is closed, requires +// the race detector to fail. +func TestIssue20622(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + tx, err := db.BeginTx(ctx, nil) + if err != nil { + t.Fatal(err) + } + + rows, err := tx.Query("SELECT|people|age,name|") + if err != nil { + t.Fatal(err) + } + + count := 0 + for rows.Next() { + count++ + var age int + var name string + if err := rows.Scan(&age, &name); err != nil { + t.Fatal("scan failed", err) + } + + if count == 1 { + cancel() + } + time.Sleep(100 * time.Millisecond) + } + rows.Close() + tx.Commit() } // golang.org/issue/5718 @@ -2751,7 +3133,7 @@ func TestIssue18429(t *testing.T) { if err != nil { return } - // This is expected to give a cancel error many, but not all the time. + // This is expected to give a cancel error most, but not all the time. // Test failure will happen with a panic or other race condition being // reported. rows, _ := tx.QueryContext(ctx, "WAIT|"+qwait+"|SELECT|people|name|") @@ -2766,6 +3148,46 @@ func TestIssue18429(t *testing.T) { wg.Wait() } +// TestIssue20160 attempts to test a short context life on a stmt Query. +func TestIssue20160(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + ctx := context.Background() + sem := make(chan bool, 20) + var wg sync.WaitGroup + + const milliWait = 30 + + stmt, err := db.PrepareContext(ctx, "SELECT|people|name|") + if err != nil { + t.Fatal(err) + } + defer stmt.Close() + + for i := 0; i < 100; i++ { + sem <- true + wg.Add(1) + go func() { + defer func() { + <-sem + wg.Done() + }() + ctx, cancel := context.WithTimeout(ctx, time.Duration(rand.Intn(milliWait))*time.Millisecond) + defer cancel() + + // This is expected to give a cancel error most, but not all the time. + // Test failure will happen with a panic or other race condition being + // reported. + rows, _ := stmt.QueryContext(ctx) + if rows != nil { + rows.Close() + } + }() + } + wg.Wait() +} + // TestIssue18719 closes the context right before use. The sql.driverConn // will nil out the ci on close in a lock, but if another process uses it right after // it will panic with on the nil ref. @@ -2788,7 +3210,7 @@ func TestIssue18719(t *testing.T) { // Wait for the context to cancel and tx to rollback. for tx.isDone() == false { - time.Sleep(time.Millisecond * 3) + time.Sleep(3 * time.Millisecond) } } defer func() { hookTxGrabConn = nil }() @@ -2807,19 +3229,64 @@ func TestIssue18719(t *testing.T) { // canceled context. cancel() - waitForRowsClose(t, rows, 5*time.Second) +} + +func TestIssue20647(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + stmt, err := conn.PrepareContext(ctx, "SELECT|people|name|") + if err != nil { + t.Fatal(err) + } + defer stmt.Close() + + rows1, err := stmt.QueryContext(ctx) + if err != nil { + t.Fatal("rows1", err) + } + defer rows1.Close() + + rows2, err := stmt.QueryContext(ctx) + if err != nil { + t.Fatal("rows2", err) + } + defer rows2.Close() + + if rows1.dc != rows2.dc { + t.Fatal("stmt prepared on Conn does not use same connection") + } } func TestConcurrency(t *testing.T) { - doConcurrentTest(t, new(concurrentDBQueryTest)) - doConcurrentTest(t, new(concurrentDBExecTest)) - doConcurrentTest(t, new(concurrentStmtQueryTest)) - doConcurrentTest(t, new(concurrentStmtExecTest)) - doConcurrentTest(t, new(concurrentTxQueryTest)) - doConcurrentTest(t, new(concurrentTxExecTest)) - doConcurrentTest(t, new(concurrentTxStmtQueryTest)) - doConcurrentTest(t, new(concurrentTxStmtExecTest)) - doConcurrentTest(t, new(concurrentRandomTest)) + list := []struct { + name string + ct concurrentTest + }{ + {"Query", new(concurrentDBQueryTest)}, + {"Exec", new(concurrentDBExecTest)}, + {"StmtQuery", new(concurrentStmtQueryTest)}, + {"StmtExec", new(concurrentStmtExecTest)}, + {"TxQuery", new(concurrentTxQueryTest)}, + {"TxExec", new(concurrentTxExecTest)}, + {"TxStmtQuery", new(concurrentTxStmtQueryTest)}, + {"TxStmtExec", new(concurrentTxStmtExecTest)}, + {"Random", new(concurrentRandomTest)}, + } + for _, item := range list { + t.Run(item.name, func(t *testing.T) { + doConcurrentTest(t, item.ct) + }) + } } func TestConnectionLeak(t *testing.T) { @@ -2874,6 +3341,131 @@ func TestConnectionLeak(t *testing.T) { wg.Wait() } +type nvcDriver struct { + fakeDriver + skipNamedValueCheck bool +} + +func (d *nvcDriver) Open(dsn string) (driver.Conn, error) { + c, err := d.fakeDriver.Open(dsn) + fc := c.(*fakeConn) + fc.db.allowAny = true + return &nvcConn{fc, d.skipNamedValueCheck}, err +} + +type nvcConn struct { + *fakeConn + skipNamedValueCheck bool +} + +type decimal struct { + value int +} + +type doNotInclude struct{} + +var _ driver.NamedValueChecker = &nvcConn{} + +func (c *nvcConn) CheckNamedValue(nv *driver.NamedValue) error { + if c.skipNamedValueCheck { + return driver.ErrSkip + } + switch v := nv.Value.(type) { + default: + return driver.ErrSkip + case Out: + switch ov := v.Dest.(type) { + default: + return errors.New("unkown NameValueCheck OUTPUT type") + case *string: + *ov = "from-server" + nv.Value = "OUT:*string" + } + return nil + case decimal, []int64: + return nil + case doNotInclude: + return driver.ErrRemoveArgument + } +} + +func TestNamedValueChecker(t *testing.T) { + Register("NamedValueCheck", &nvcDriver{}) + db, err := Open("NamedValueCheck", "") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, err = db.ExecContext(ctx, "WIPE") + if err != nil { + t.Fatal("exec wipe", err) + } + + _, err = db.ExecContext(ctx, "CREATE|keys|dec1=any,str1=string,out1=string,array1=any") + if err != nil { + t.Fatal("exec create", err) + } + + o1 := "" + _, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A,str1=?,out1=?O1,array1=?", Named("A", decimal{123}), "hello", Named("O1", Out{Dest: &o1}), []int64{42, 128, 707}, doNotInclude{}) + if err != nil { + t.Fatal("exec insert", err) + } + var ( + str1 string + dec1 decimal + arr1 []int64 + ) + err = db.QueryRowContext(ctx, "SELECT|keys|dec1,str1,array1|").Scan(&dec1, &str1, &arr1) + if err != nil { + t.Fatal("select", err) + } + + list := []struct{ got, want interface{} }{ + {o1, "from-server"}, + {dec1, decimal{123}}, + {str1, "hello"}, + {arr1, []int64{42, 128, 707}}, + } + + for index, item := range list { + if !reflect.DeepEqual(item.got, item.want) { + t.Errorf("got %#v wanted %#v for index %d", item.got, item.want, index) + } + } +} + +func TestNamedValueCheckerSkip(t *testing.T) { + Register("NamedValueCheckSkip", &nvcDriver{skipNamedValueCheck: true}) + db, err := Open("NamedValueCheckSkip", "") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, err = db.ExecContext(ctx, "WIPE") + if err != nil { + t.Fatal("exec wipe", err) + } + + _, err = db.ExecContext(ctx, "CREATE|keys|dec1=any") + if err != nil { + t.Fatal("exec create", err) + } + + _, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A", Named("A", decimal{123})) + if err == nil { + t.Fatalf("expected error with bad argument, got %v", err) + } +} + // badConn implements a bad driver.Conn, for TestBadDriver. // The Exec method panics. type badConn struct{} @@ -2965,6 +3557,24 @@ func TestPing(t *testing.T) { } } +// Issue 18101. +func TestTypedString(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + type Str string + var scanned Str + + err := db.QueryRow("SELECT|people|name|name=?", "Alice").Scan(&scanned) + if err != nil { + t.Fatal(err) + } + expected := Str("Alice") + if scanned != expected { + t.Errorf("expected %+v, got %+v", expected, scanned) + } +} + func BenchmarkConcurrentDBExec(b *testing.B) { b.ReportAllocs() ct := new(concurrentDBExecTest) |