exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
// Copyright 2011 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
|
|
|
|
|
|
|
|
|
|
import (
|
2016-09-19 11:19:32 -07:00
|
|
|
"context"
|
2013-08-13 14:56:40 -07:00
|
|
|
"database/sql/driver"
|
2013-08-16 11:23:35 +10:00
|
|
|
"errors"
|
2012-01-25 17:47:32 -08:00
|
|
|
"fmt"
|
2013-08-30 09:27:33 -07:00
|
|
|
"math/rand"
|
2011-11-20 14:56:49 -05:00
|
|
|
"reflect"
|
2013-04-03 11:13:40 -07:00
|
|
|
"runtime"
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
"strings"
|
2013-04-03 11:13:40 -07:00
|
|
|
"sync"
|
2017-01-20 17:12:50 -08:00
|
|
|
"sync/atomic"
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
"testing"
|
2012-01-13 15:45:05 -08:00
|
|
|
"time"
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
)
|
|
|
|
|
|
2012-03-10 10:00:02 -08:00
|
|
|
func init() {
|
|
|
|
|
type dbConn struct {
|
|
|
|
|
db *DB
|
2013-03-14 15:01:45 -07:00
|
|
|
c *driverConn
|
2012-03-10 10:00:02 -08:00
|
|
|
}
|
|
|
|
|
freedFrom := make(map[dbConn]string)
|
2016-11-30 09:30:31 -08:00
|
|
|
var mu sync.Mutex
|
|
|
|
|
getFreedFrom := func(c dbConn) string {
|
|
|
|
|
mu.Lock()
|
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
return freedFrom[c]
|
|
|
|
|
}
|
|
|
|
|
setFreedFrom := func(c dbConn, s string) {
|
|
|
|
|
mu.Lock()
|
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
freedFrom[c] = s
|
|
|
|
|
}
|
2013-03-14 15:01:45 -07:00
|
|
|
putConnHook = func(db *DB, c *driverConn) {
|
2014-08-28 08:49:56 -07:00
|
|
|
idx := -1
|
|
|
|
|
for i, v := range db.freeConn {
|
|
|
|
|
if v == c {
|
|
|
|
|
idx = i
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if idx >= 0 {
|
2013-08-30 09:27:33 -07:00
|
|
|
// print before panic, as panic may get lost due to conflicting panic
|
|
|
|
|
// (all goroutines asleep) elsewhere, since we might not unlock
|
|
|
|
|
// the mutex in freeConn here.
|
2016-11-30 09:30:31 -08:00
|
|
|
println("double free of conn. conflicts are:\nA) " + getFreedFrom(dbConn{db, c}) + "\n\nand\nB) " + stack())
|
2013-08-30 09:27:33 -07:00
|
|
|
panic("double free of conn.")
|
2012-03-10 10:00:02 -08:00
|
|
|
}
|
2016-11-30 09:30:31 -08:00
|
|
|
setFreedFrom(dbConn{db, c}, stack())
|
2012-03-10 10:00:02 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-10 12:51:27 -08:00
|
|
|
const fakeDBName = "foo"
|
|
|
|
|
|
2012-01-13 15:45:05 -08:00
|
|
|
var chrisBirthday = time.Unix(123456789, 0)
|
|
|
|
|
|
2013-08-14 23:21:32 -07:00
|
|
|
func newTestDB(t testing.TB, name string) *DB {
|
2017-10-02 11:16:53 -07:00
|
|
|
return newTestDBConnector(t, &fakeConnector{name: fakeDBName}, name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newTestDBConnector(t testing.TB, fc *fakeConnector, name string) *DB {
|
|
|
|
|
fc.name = fakeDBName
|
|
|
|
|
db := OpenDB(fc)
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
if _, err := db.Exec("WIPE"); err != nil {
|
|
|
|
|
t.Fatalf("exec wipe: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if name == "people" {
|
2012-01-13 15:45:05 -08:00
|
|
|
exec(t, db, "CREATE|people|name=string,age=int32,photo=blob,dead=bool,bdate=datetime")
|
2012-01-12 11:23:33 -08:00
|
|
|
exec(t, db, "INSERT|people|name=Alice,age=?,photo=APHOTO", 1)
|
|
|
|
|
exec(t, db, "INSERT|people|name=Bob,age=?,photo=BPHOTO", 2)
|
2012-01-13 15:45:05 -08:00
|
|
|
exec(t, db, "INSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?", 3, chrisBirthday)
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
}
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
if name == "magicquery" {
|
|
|
|
|
// Magic table name and column, known by fakedb_test.go.
|
|
|
|
|
exec(t, db, "CREATE|magicquery|op=string,millis=int32")
|
|
|
|
|
exec(t, db, "INSERT|magicquery|op=sleep,millis=10")
|
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
return db
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-05 06:04:19 -04:00
|
|
|
func TestOpenDB(t *testing.T) {
|
|
|
|
|
db := OpenDB(dsnConnector{dsn: fakeDBName, driver: fdriver})
|
|
|
|
|
if db.Driver() != fdriver {
|
|
|
|
|
t.Fatalf("OpenDB should return the driver of the Connector")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-18 12:16:05 -05:00
|
|
|
func TestDriverPanic(t *testing.T) {
|
|
|
|
|
// Test that if driver panics, database/sql does not deadlock.
|
|
|
|
|
db, err := Open("test", fakeDBName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Open: %v", err)
|
|
|
|
|
}
|
|
|
|
|
expectPanic := func(name string, f func()) {
|
|
|
|
|
defer func() {
|
|
|
|
|
err := recover()
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatalf("%s did not panic", name)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
f()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expectPanic("Exec Exec", func() { db.Exec("PANIC|Exec|WIPE") })
|
|
|
|
|
exec(t, db, "WIPE") // check not deadlocked
|
|
|
|
|
expectPanic("Exec NumInput", func() { db.Exec("PANIC|NumInput|WIPE") })
|
|
|
|
|
exec(t, db, "WIPE") // check not deadlocked
|
|
|
|
|
expectPanic("Exec Close", func() { db.Exec("PANIC|Close|WIPE") })
|
|
|
|
|
exec(t, db, "WIPE") // check not deadlocked
|
|
|
|
|
exec(t, db, "PANIC|Query|WIPE") // should run successfully: Exec does not call Query
|
|
|
|
|
exec(t, db, "WIPE") // check not deadlocked
|
|
|
|
|
|
|
|
|
|
exec(t, db, "CREATE|people|name=string,age=int32,photo=blob,dead=bool,bdate=datetime")
|
|
|
|
|
|
|
|
|
|
expectPanic("Query Query", func() { db.Query("PANIC|Query|SELECT|people|age,name|") })
|
|
|
|
|
expectPanic("Query NumInput", func() { db.Query("PANIC|NumInput|SELECT|people|age,name|") })
|
|
|
|
|
expectPanic("Query Close", func() {
|
|
|
|
|
rows, err := db.Query("PANIC|Close|SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
rows.Close()
|
|
|
|
|
})
|
|
|
|
|
db.Query("PANIC|Exec|SELECT|people|age,name|") // should run successfully: Query does not call Exec
|
|
|
|
|
exec(t, db, "WIPE") // check not deadlocked
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-14 23:21:32 -07:00
|
|
|
func exec(t testing.TB, db *DB, query string, args ...interface{}) {
|
2019-04-26 11:46:26 -07:00
|
|
|
t.Helper()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
_, err := db.Exec(query, args...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Exec of %q: %v", query, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-14 23:21:32 -07:00
|
|
|
func closeDB(t testing.TB, db *DB) {
|
2013-02-20 15:35:27 -08:00
|
|
|
if e := recover(); e != nil {
|
|
|
|
|
fmt.Printf("Panic: %v\n", e)
|
|
|
|
|
panic(e)
|
|
|
|
|
}
|
2013-03-25 16:50:27 -07:00
|
|
|
defer setHookpostCloseConn(nil)
|
|
|
|
|
setHookpostCloseConn(func(_ *fakeConn, err error) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Error closing fakeConn: %v", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
database/sql: allow using a single connection from the database
Databases have the following concepts: Statement, Batch, and Session.
A statement is often a single line like:
SELECT Amount from Account where ID = 50;
A batch is one or more statements submitted together for the query
to process. It may be a DELETE, INSERT, two UPDATES and a SELECT in
a single query text.
A session is usually represented by a single database connection.
This often is an issue when dealing with scopes in databases.
Temporary tables and variables can have batch, session, or global
scope depending on the syntax, database, and use.
Furthermore, some databases (sybase and derivatives in perticular)
that prevent certain statements from being in the same batch
and may necessitate being in the same session.
By allowing users to extract a Conn from the database they can manage
session on their own without hacking around it by making connection
pools of single connections (a real workaround presented in issue).
It is tempting to just use a transaction, but this isn't always
desirable or an option if running an interactive session or
alter script set that itself starts transactions.
Fixes #18081
Change-Id: I9bdf0796632c48d4bcaef3624c629641984ffaf2
Reviewed-on: https://go-review.googlesource.com/40694
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-04-13 16:04:40 -07:00
|
|
|
db.mu.Lock()
|
2014-08-28 08:49:56 -07:00
|
|
|
for i, dc := range db.freeConn {
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
if n := len(dc.openStmt); n > 0 {
|
|
|
|
|
// Just a sanity check. This is legal in
|
|
|
|
|
// general, but if we make the tests clean up
|
|
|
|
|
// their statements first, then we can safely
|
|
|
|
|
// verify this is always zero here, and any
|
|
|
|
|
// other value is a leak.
|
2014-08-28 08:49:56 -07:00
|
|
|
t.Errorf("while closing db, freeConn %d/%d had %d open stmts; want 0", i, len(db.freeConn), n)
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
}
|
|
|
|
|
}
|
database/sql: allow using a single connection from the database
Databases have the following concepts: Statement, Batch, and Session.
A statement is often a single line like:
SELECT Amount from Account where ID = 50;
A batch is one or more statements submitted together for the query
to process. It may be a DELETE, INSERT, two UPDATES and a SELECT in
a single query text.
A session is usually represented by a single database connection.
This often is an issue when dealing with scopes in databases.
Temporary tables and variables can have batch, session, or global
scope depending on the syntax, database, and use.
Furthermore, some databases (sybase and derivatives in perticular)
that prevent certain statements from being in the same batch
and may necessitate being in the same session.
By allowing users to extract a Conn from the database they can manage
session on their own without hacking around it by making connection
pools of single connections (a real workaround presented in issue).
It is tempting to just use a transaction, but this isn't always
desirable or an option if running an interactive session or
alter script set that itself starts transactions.
Fixes #18081
Change-Id: I9bdf0796632c48d4bcaef3624c629641984ffaf2
Reviewed-on: https://go-review.googlesource.com/40694
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-04-13 16:04:40 -07:00
|
|
|
db.mu.Unlock()
|
|
|
|
|
|
2011-11-14 10:48:26 -08:00
|
|
|
err := db.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("error closing DB: %v", err)
|
|
|
|
|
}
|
2017-02-12 15:12:52 -08:00
|
|
|
|
|
|
|
|
var numOpen int
|
|
|
|
|
if !waitCondition(5*time.Second, 5*time.Millisecond, func() bool {
|
|
|
|
|
numOpen = db.numOpenConns()
|
|
|
|
|
return numOpen == 0
|
|
|
|
|
}) {
|
|
|
|
|
t.Fatalf("%d connections still open after closing DB", numOpen)
|
2013-10-16 09:17:25 -07:00
|
|
|
}
|
2011-11-14 10:48:26 -08:00
|
|
|
}
|
|
|
|
|
|
2012-03-06 14:10:58 -08:00
|
|
|
// numPrepares assumes that db has exactly 1 idle conn and returns
|
|
|
|
|
// its count of calls to Prepare
|
|
|
|
|
func numPrepares(t *testing.T, db *DB) int {
|
2014-08-28 08:49:56 -07:00
|
|
|
if n := len(db.freeConn); n != 1 {
|
2012-03-06 14:10:58 -08:00
|
|
|
t.Fatalf("free conns = %d; want 1", n)
|
|
|
|
|
}
|
2014-08-28 08:49:56 -07:00
|
|
|
return db.freeConn[0].ci.(*fakeConn).numPrepare
|
2012-03-06 14:10:58 -08:00
|
|
|
}
|
|
|
|
|
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
func (db *DB) numDeps() int {
|
|
|
|
|
db.mu.Lock()
|
|
|
|
|
defer db.mu.Unlock()
|
|
|
|
|
return len(db.dep)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dependencies are closed via a goroutine, so this polls waiting for
|
|
|
|
|
// numDeps to fall to want, waiting up to d.
|
|
|
|
|
func (db *DB) numDepsPollUntil(want int, d time.Duration) int {
|
|
|
|
|
deadline := time.Now().Add(d)
|
|
|
|
|
for {
|
|
|
|
|
n := db.numDeps()
|
|
|
|
|
if n <= want || time.Now().After(deadline) {
|
|
|
|
|
return n
|
|
|
|
|
}
|
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (db *DB) numFreeConns() int {
|
|
|
|
|
db.mu.Lock()
|
|
|
|
|
defer db.mu.Unlock()
|
2014-08-28 08:49:56 -07:00
|
|
|
return len(db.freeConn)
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
}
|
|
|
|
|
|
2016-10-28 10:10:46 -07:00
|
|
|
func (db *DB) numOpenConns() int {
|
|
|
|
|
db.mu.Lock()
|
|
|
|
|
defer db.mu.Unlock()
|
|
|
|
|
return db.numOpen
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-03 21:27:07 +09:00
|
|
|
// clearAllConns closes all connections in db.
|
|
|
|
|
func (db *DB) clearAllConns(t *testing.T) {
|
|
|
|
|
db.SetMaxIdleConns(0)
|
|
|
|
|
|
|
|
|
|
if g, w := db.numFreeConns(), 0; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n := db.numDepsPollUntil(0, time.Second); n > 0 {
|
|
|
|
|
t.Errorf("number of dependencies = %d; expected 0", n)
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
func (db *DB) dumpDeps(t *testing.T) {
|
|
|
|
|
for fc := range db.dep {
|
|
|
|
|
db.dumpDep(t, 0, fc, map[finalCloser]bool{})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (db *DB) dumpDep(t *testing.T, depth int, dep finalCloser, seen map[finalCloser]bool) {
|
|
|
|
|
seen[dep] = true
|
|
|
|
|
indent := strings.Repeat(" ", depth)
|
|
|
|
|
ds := db.dep[dep]
|
|
|
|
|
for k := range ds {
|
|
|
|
|
t.Logf("%s%T (%p) waiting for -> %T (%p)", indent, dep, dep, k, k)
|
|
|
|
|
if fc, ok := k.(finalCloser); ok {
|
|
|
|
|
if !seen[fc] {
|
|
|
|
|
db.dumpDep(t, depth+1, fc, seen)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
func TestQuery(t *testing.T) {
|
2011-11-20 14:56:49 -05:00
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
2012-03-06 14:10:58 -08:00
|
|
|
prepares0 := numPrepares(t, db)
|
2011-11-20 14:56:49 -05:00
|
|
|
rows, err := db.Query("SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Query: %v", err)
|
|
|
|
|
}
|
|
|
|
|
type row struct {
|
|
|
|
|
age int
|
|
|
|
|
name string
|
|
|
|
|
}
|
|
|
|
|
got := []row{}
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var r row
|
|
|
|
|
err = rows.Scan(&r.age, &r.name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Scan: %v", err)
|
|
|
|
|
}
|
|
|
|
|
got = append(got, r)
|
|
|
|
|
}
|
|
|
|
|
err = rows.Err()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
want := []row{
|
|
|
|
|
{age: 1, name: "Alice"},
|
|
|
|
|
{age: 2, name: "Bob"},
|
|
|
|
|
{age: 3, name: "Chris"},
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
2012-01-17 10:44:35 -08:00
|
|
|
t.Errorf("mismatch.\n got: %#v\nwant: %#v", got, want)
|
2011-11-20 14:56:49 -05:00
|
|
|
}
|
2012-01-10 12:51:27 -08:00
|
|
|
|
|
|
|
|
// And verify that the final rows.Next() call, which hit EOF,
|
|
|
|
|
// also closed the rows connection.
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
if n := db.numFreeConns(); n != 1 {
|
2012-03-06 14:10:58 -08:00
|
|
|
t.Fatalf("free conns after query hitting EOF = %d; want 1", n)
|
|
|
|
|
}
|
|
|
|
|
if prepares := numPrepares(t, db) - prepares0; prepares != 1 {
|
|
|
|
|
t.Errorf("executed %d Prepare statements; want 1", prepares)
|
2012-01-10 12:51:27 -08:00
|
|
|
}
|
2011-11-20 14:56:49 -05:00
|
|
|
}
|
|
|
|
|
|
2017-02-12 15:12:52 -08:00
|
|
|
// TestQueryContext tests canceling the context while scanning the rows.
|
2016-09-28 12:51:39 -07:00
|
|
|
func TestQueryContext(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
prepares0 := numPrepares(t, db)
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2016-10-23 23:13:59 +02:00
|
|
|
defer cancel()
|
2016-09-28 12:51:39 -07:00
|
|
|
|
|
|
|
|
rows, err := db.QueryContext(ctx, "SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Query: %v", err)
|
|
|
|
|
}
|
|
|
|
|
type row struct {
|
|
|
|
|
age int
|
|
|
|
|
name string
|
|
|
|
|
}
|
|
|
|
|
got := []row{}
|
|
|
|
|
index := 0
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
if index == 2 {
|
|
|
|
|
cancel()
|
2017-02-12 15:12:52 -08:00
|
|
|
waitForRowsClose(t, rows, 5*time.Second)
|
2016-09-28 12:51:39 -07:00
|
|
|
}
|
|
|
|
|
var r row
|
|
|
|
|
err = rows.Scan(&r.age, &r.name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if index == 2 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
t.Fatalf("Scan: %v", err)
|
|
|
|
|
}
|
2018-04-02 22:15:59 -07:00
|
|
|
if index == 2 && err != context.Canceled {
|
|
|
|
|
t.Fatalf("Scan: %v; want context.Canceled", err)
|
2016-09-28 12:51:39 -07:00
|
|
|
}
|
|
|
|
|
got = append(got, r)
|
|
|
|
|
index++
|
|
|
|
|
}
|
2017-02-07 10:19:02 -08:00
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
if err := ctx.Err(); err != context.Canceled {
|
2017-02-09 12:28:51 +09:00
|
|
|
t.Fatalf("context err = %v; want context.Canceled", err)
|
2017-02-07 10:19:02 -08:00
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
t.Fatalf("context err = nil; want context.Canceled")
|
2016-09-28 12:51:39 -07:00
|
|
|
}
|
|
|
|
|
want := []row{
|
|
|
|
|
{age: 1, name: "Alice"},
|
|
|
|
|
{age: 2, name: "Bob"},
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
|
|
|
t.Errorf("mismatch.\n got: %#v\nwant: %#v", got, want)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And verify that the final rows.Next() call, which hit EOF,
|
|
|
|
|
// also closed the rows connection.
|
2017-02-12 15:12:52 -08:00
|
|
|
waitForRowsClose(t, rows, 5*time.Second)
|
2017-01-25 08:27:45 -08:00
|
|
|
waitForFree(t, db, 5*time.Second, 1)
|
2016-09-28 12:51:39 -07:00
|
|
|
if prepares := numPrepares(t, db) - prepares0; prepares != 1 {
|
|
|
|
|
t.Errorf("executed %d Prepare statements; want 1", prepares)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-28 10:10:46 -07:00
|
|
|
func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool {
|
|
|
|
|
deadline := time.Now().Add(waitFor)
|
|
|
|
|
for time.Now().Before(deadline) {
|
|
|
|
|
if fn() {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
time.Sleep(checkEvery)
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-25 08:27:45 -08:00
|
|
|
// waitForFree checks db.numFreeConns until either it equals want or
|
|
|
|
|
// the maxWait time elapses.
|
|
|
|
|
func waitForFree(t *testing.T, db *DB, maxWait time.Duration, want int) {
|
|
|
|
|
var numFree int
|
|
|
|
|
if !waitCondition(maxWait, 5*time.Millisecond, func() bool {
|
|
|
|
|
numFree = db.numFreeConns()
|
|
|
|
|
return numFree == want
|
|
|
|
|
}) {
|
|
|
|
|
t.Fatalf("free conns after hitting EOF = %d; want %d", numFree, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-12 15:12:52 -08:00
|
|
|
func waitForRowsClose(t *testing.T, rows *Rows, maxWait time.Duration) {
|
|
|
|
|
if !waitCondition(maxWait, 5*time.Millisecond, func() bool {
|
|
|
|
|
rows.closemu.RLock()
|
|
|
|
|
defer rows.closemu.RUnlock()
|
|
|
|
|
return rows.closed
|
|
|
|
|
}) {
|
|
|
|
|
t.Fatal("failed to close rows")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestQueryContextWait ensures that rows and all internal statements are closed when
|
|
|
|
|
// a query context is closed during execution.
|
2016-10-28 10:10:46 -07:00
|
|
|
func TestQueryContextWait(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
prepares0 := numPrepares(t, db)
|
|
|
|
|
|
2017-02-12 15:12:52 -08:00
|
|
|
// TODO(kardianos): convert this from using a timeout to using an explicit
|
2018-10-06 06:10:25 +00:00
|
|
|
// cancel when the query signals that it is "executing" the query.
|
2017-02-12 15:12:52 -08:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
|
2017-02-09 12:28:51 +09:00
|
|
|
defer cancel()
|
2016-10-28 10:10:46 -07:00
|
|
|
|
|
|
|
|
// This will trigger the *fakeConn.Prepare method which will take time
|
|
|
|
|
// performing the query. The ctxDriverPrepare func will check the context
|
|
|
|
|
// after this and close the rows and return an error.
|
2016-11-30 09:30:31 -08:00
|
|
|
_, err := db.QueryContext(ctx, "WAIT|1s|SELECT|people|age,name|")
|
2016-10-28 10:10:46 -07:00
|
|
|
if err != context.DeadlineExceeded {
|
|
|
|
|
t.Fatalf("expected QueryContext to error with context deadline exceeded but returned %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify closed rows connection after error condition.
|
2017-01-25 08:27:45 -08:00
|
|
|
waitForFree(t, db, 5*time.Second, 1)
|
2016-10-28 10:10:46 -07:00
|
|
|
if prepares := numPrepares(t, db) - prepares0; prepares != 1 {
|
2017-02-12 15:12:52 -08:00
|
|
|
// TODO(kardianos): if the context timeouts before the db.QueryContext
|
|
|
|
|
// executes this check may fail. After adjusting how the context
|
|
|
|
|
// is canceled above revert this back to a Fatal error.
|
|
|
|
|
t.Logf("executed %d Prepare statements; want 1", prepares)
|
2016-10-28 10:10:46 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-12 15:12:52 -08:00
|
|
|
// TestTxContextWait tests the transaction behavior when the tx context is canceled
|
|
|
|
|
// during execution of the query.
|
2016-10-28 10:10:46 -07:00
|
|
|
func TestTxContextWait(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
2017-02-09 12:32:33 +09:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Millisecond)
|
2017-02-09 12:28:51 +09:00
|
|
|
defer cancel()
|
2016-10-28 10:10:46 -07:00
|
|
|
|
2016-12-13 07:55:12 -08:00
|
|
|
tx, err := db.BeginTx(ctx, nil)
|
2016-10-28 10:10:46 -07:00
|
|
|
if err != nil {
|
2017-02-12 15:12:52 -08:00
|
|
|
// Guard against the context being canceled before BeginTx completes.
|
|
|
|
|
if err == context.DeadlineExceeded {
|
|
|
|
|
t.Skip("tx context canceled prior to first use")
|
|
|
|
|
}
|
2016-10-28 10:10:46 -07:00
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This will trigger the *fakeConn.Prepare method which will take time
|
|
|
|
|
// performing the query. The ctxDriverPrepare func will check the context
|
|
|
|
|
// after this and close the rows and return an error.
|
2016-11-30 09:30:31 -08:00
|
|
|
_, err = tx.QueryContext(ctx, "WAIT|1s|SELECT|people|age,name|")
|
2016-10-28 10:10:46 -07:00
|
|
|
if err != context.DeadlineExceeded {
|
|
|
|
|
t.Fatalf("expected QueryContext to error with context deadline exceeded but returned %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-25 08:27:45 -08:00
|
|
|
waitForFree(t, db, 5*time.Second, 0)
|
2016-10-28 10:10:46 -07:00
|
|
|
}
|
|
|
|
|
|
2017-08-08 15:58:27 -04:00
|
|
|
// TestUnsupportedOptions checks that the database fails when a driver that
|
|
|
|
|
// doesn't implement ConnBeginTx is used with non-default options and an
|
|
|
|
|
// un-cancellable context.
|
|
|
|
|
func TestUnsupportedOptions(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
_, err := db.BeginTx(context.Background(), &TxOptions{
|
|
|
|
|
Isolation: LevelSerializable, ReadOnly: true,
|
|
|
|
|
})
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error when using unsupported options, got nil")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-06 11:06:21 -07:00
|
|
|
func TestMultiResultSetQuery(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
prepares0 := numPrepares(t, db)
|
|
|
|
|
rows, err := db.Query("SELECT|people|age,name|;SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Query: %v", err)
|
|
|
|
|
}
|
|
|
|
|
type row1 struct {
|
|
|
|
|
age int
|
|
|
|
|
name string
|
|
|
|
|
}
|
|
|
|
|
type row2 struct {
|
|
|
|
|
name string
|
|
|
|
|
}
|
|
|
|
|
got1 := []row1{}
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var r row1
|
|
|
|
|
err = rows.Scan(&r.age, &r.name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Scan: %v", err)
|
|
|
|
|
}
|
|
|
|
|
got1 = append(got1, r)
|
|
|
|
|
}
|
|
|
|
|
err = rows.Err()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
want1 := []row1{
|
|
|
|
|
{age: 1, name: "Alice"},
|
|
|
|
|
{age: 2, name: "Bob"},
|
|
|
|
|
{age: 3, name: "Chris"},
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(got1, want1) {
|
|
|
|
|
t.Errorf("mismatch.\n got1: %#v\nwant: %#v", got1, want1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !rows.NextResultSet() {
|
|
|
|
|
t.Errorf("expected another result set")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got2 := []row2{}
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var r row2
|
|
|
|
|
err = rows.Scan(&r.name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Scan: %v", err)
|
|
|
|
|
}
|
|
|
|
|
got2 = append(got2, r)
|
|
|
|
|
}
|
|
|
|
|
err = rows.Err()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
want2 := []row2{
|
|
|
|
|
{name: "Alice"},
|
|
|
|
|
{name: "Bob"},
|
|
|
|
|
{name: "Chris"},
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(got2, want2) {
|
|
|
|
|
t.Errorf("mismatch.\n got: %#v\nwant: %#v", got2, want2)
|
|
|
|
|
}
|
|
|
|
|
if rows.NextResultSet() {
|
|
|
|
|
t.Errorf("expected no more result sets")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And verify that the final rows.Next() call, which hit EOF,
|
|
|
|
|
// also closed the rows connection.
|
2017-01-25 08:27:45 -08:00
|
|
|
waitForFree(t, db, 5*time.Second, 1)
|
2016-10-06 11:06:21 -07:00
|
|
|
if prepares := numPrepares(t, db) - prepares0; prepares != 1 {
|
|
|
|
|
t.Errorf("executed %d Prepare statements; want 1", prepares)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-29 09:57:17 -08:00
|
|
|
func TestQueryNamedArg(t *testing.T) {
|
2016-10-03 09:49:25 -07:00
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
prepares0 := numPrepares(t, db)
|
|
|
|
|
rows, err := db.Query(
|
|
|
|
|
// Ensure the name and age parameters only match on placeholder name, not position.
|
|
|
|
|
"SELECT|people|age,name|name=?name,age=?age",
|
2016-11-23 09:10:30 -08:00
|
|
|
Named("age", 2),
|
|
|
|
|
Named("name", "Bob"),
|
2016-10-03 09:49:25 -07:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Query: %v", err)
|
|
|
|
|
}
|
|
|
|
|
type row struct {
|
|
|
|
|
age int
|
|
|
|
|
name string
|
|
|
|
|
}
|
|
|
|
|
got := []row{}
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var r row
|
|
|
|
|
err = rows.Scan(&r.age, &r.name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Scan: %v", err)
|
|
|
|
|
}
|
|
|
|
|
got = append(got, r)
|
|
|
|
|
}
|
|
|
|
|
err = rows.Err()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
want := []row{
|
|
|
|
|
{age: 2, name: "Bob"},
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
|
|
|
t.Errorf("mismatch.\n got: %#v\nwant: %#v", got, want)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And verify that the final rows.Next() call, which hit EOF,
|
|
|
|
|
// also closed the rows connection.
|
|
|
|
|
if n := db.numFreeConns(); n != 1 {
|
|
|
|
|
t.Fatalf("free conns after query hitting EOF = %d; want 1", n)
|
|
|
|
|
}
|
|
|
|
|
if prepares := numPrepares(t, db) - prepares0; prepares != 1 {
|
|
|
|
|
t.Errorf("executed %d Prepare statements; want 1", prepares)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-08 10:32:22 -08:00
|
|
|
func TestPoolExhaustOnCancel(t *testing.T) {
|
|
|
|
|
if testing.Short() {
|
|
|
|
|
t.Skip("long test")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
max := 3
|
2017-10-02 11:16:53 -07:00
|
|
|
var saturate, saturateDone sync.WaitGroup
|
|
|
|
|
saturate.Add(max)
|
|
|
|
|
saturateDone.Add(max)
|
|
|
|
|
|
|
|
|
|
donePing := make(chan bool)
|
|
|
|
|
state := 0
|
|
|
|
|
|
|
|
|
|
// waiter will be called for all queries, including
|
2018-10-08 01:19:51 +00:00
|
|
|
// initial setup queries. The state is only assigned when
|
2017-10-02 11:16:53 -07:00
|
|
|
// no queries are made.
|
|
|
|
|
//
|
|
|
|
|
// Only allow the first batch of queries to finish once the
|
|
|
|
|
// second batch of Ping queries have finished.
|
|
|
|
|
waiter := func(ctx context.Context) {
|
|
|
|
|
switch state {
|
|
|
|
|
case 0:
|
|
|
|
|
// Nothing. Initial database setup.
|
|
|
|
|
case 1:
|
|
|
|
|
saturate.Done()
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
case <-donePing:
|
|
|
|
|
}
|
|
|
|
|
case 2:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
db := newTestDBConnector(t, &fakeConnector{waiter: waiter}, "people")
|
|
|
|
|
defer closeDB(t, db)
|
2017-02-08 10:32:22 -08:00
|
|
|
|
|
|
|
|
db.SetMaxOpenConns(max)
|
|
|
|
|
|
|
|
|
|
// First saturate the connection pool.
|
|
|
|
|
// Then start new requests for a connection that is cancelled after it is requested.
|
|
|
|
|
|
2017-10-02 11:16:53 -07:00
|
|
|
state = 1
|
2017-02-08 10:32:22 -08:00
|
|
|
for i := 0; i < max; i++ {
|
|
|
|
|
go func() {
|
2017-10-02 11:16:53 -07:00
|
|
|
rows, err := db.Query("SELECT|people|name,photo|")
|
2017-02-08 10:32:22 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Query: %v", err)
|
|
|
|
|
}
|
|
|
|
|
rows.Close()
|
|
|
|
|
saturateDone.Done()
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saturate.Wait()
|
2017-10-02 11:16:53 -07:00
|
|
|
state = 2
|
2017-02-08 10:32:22 -08:00
|
|
|
|
|
|
|
|
// Now cancel the request while it is waiting.
|
2017-02-09 12:32:33 +09:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
2017-02-08 10:32:22 -08:00
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
for i := 0; i < max; i++ {
|
|
|
|
|
ctxReq, cancelReq := context.WithCancel(ctx)
|
|
|
|
|
go func() {
|
2017-02-09 12:32:33 +09:00
|
|
|
time.Sleep(100 * time.Millisecond)
|
2017-02-08 10:32:22 -08:00
|
|
|
cancelReq()
|
|
|
|
|
}()
|
|
|
|
|
err := db.PingContext(ctxReq)
|
|
|
|
|
if err != context.Canceled {
|
|
|
|
|
t.Fatalf("PingContext (Exhaust): %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-10-02 11:16:53 -07:00
|
|
|
close(donePing)
|
2017-02-08 10:32:22 -08:00
|
|
|
saturateDone.Wait()
|
|
|
|
|
|
|
|
|
|
// Now try to open a normal connection.
|
|
|
|
|
err := db.PingContext(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("PingContext (Normal): %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-15 10:14:57 -08:00
|
|
|
func TestRowsColumns(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
rows, err := db.Query("SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Query: %v", err)
|
|
|
|
|
}
|
|
|
|
|
cols, err := rows.Columns()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Columns: %v", err)
|
|
|
|
|
}
|
|
|
|
|
want := []string{"age", "name"}
|
|
|
|
|
if !reflect.DeepEqual(cols, want) {
|
|
|
|
|
t.Errorf("got %#v; want %#v", cols, want)
|
|
|
|
|
}
|
2013-10-16 09:17:25 -07:00
|
|
|
if err := rows.Close(); err != nil {
|
|
|
|
|
t.Errorf("error closing rows: %s", err)
|
|
|
|
|
}
|
2011-12-15 10:14:57 -08:00
|
|
|
}
|
|
|
|
|
|
2016-09-27 13:27:02 -07:00
|
|
|
func TestRowsColumnTypes(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
rows, err := db.Query("SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Query: %v", err)
|
|
|
|
|
}
|
|
|
|
|
tt, err := rows.ColumnTypes()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ColumnTypes: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
types := make([]reflect.Type, len(tt))
|
|
|
|
|
for i, tp := range tt {
|
|
|
|
|
st := tp.ScanType()
|
|
|
|
|
if st == nil {
|
|
|
|
|
t.Errorf("scantype is null for column %q", tp.Name())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
types[i] = st
|
|
|
|
|
}
|
|
|
|
|
values := make([]interface{}, len(tt))
|
|
|
|
|
for i := range values {
|
|
|
|
|
values[i] = reflect.New(types[i]).Interface()
|
|
|
|
|
}
|
|
|
|
|
ct := 0
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
err = rows.Scan(values...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to scan values in %v", err)
|
|
|
|
|
}
|
2017-10-04 17:26:14 +02:00
|
|
|
if ct == 1 {
|
|
|
|
|
if age := *values[0].(*int32); age != 2 {
|
|
|
|
|
t.Errorf("Expected 2, got %v", age)
|
2016-09-27 13:27:02 -07:00
|
|
|
}
|
2017-10-04 17:26:14 +02:00
|
|
|
if name := *values[1].(*string); name != "Bob" {
|
|
|
|
|
t.Errorf("Expected Bob, got %v", name)
|
2016-09-27 13:27:02 -07:00
|
|
|
}
|
|
|
|
|
}
|
2017-10-04 17:26:14 +02:00
|
|
|
ct++
|
2016-09-27 13:27:02 -07:00
|
|
|
}
|
|
|
|
|
if ct != 3 {
|
|
|
|
|
t.Errorf("expected 3 rows, got %d", ct)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := rows.Close(); err != nil {
|
|
|
|
|
t.Errorf("error closing rows: %s", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-20 14:56:49 -05:00
|
|
|
func TestQueryRow(t *testing.T) {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
db := newTestDB(t, "people")
|
2011-11-14 10:48:26 -08:00
|
|
|
defer closeDB(t, db)
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
var name string
|
|
|
|
|
var age int
|
2012-01-13 15:45:05 -08:00
|
|
|
var birthday time.Time
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
|
|
|
|
|
err := db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&age)
|
2011-11-01 22:04:37 -04:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "expected 2 destination arguments") {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
t.Errorf("expected error from wrong number of arguments; actually got: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-13 15:45:05 -08:00
|
|
|
err = db.QueryRow("SELECT|people|bdate|age=?", 3).Scan(&birthday)
|
|
|
|
|
if err != nil || !birthday.Equal(chrisBirthday) {
|
|
|
|
|
t.Errorf("chris birthday = %v, err = %v; want %v", birthday, err, chrisBirthday)
|
|
|
|
|
}
|
|
|
|
|
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
err = db.QueryRow("SELECT|people|age,name|age=?", 2).Scan(&age, &name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("age QueryRow+Scan: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if name != "Bob" {
|
|
|
|
|
t.Errorf("expected name Bob, got %q", name)
|
|
|
|
|
}
|
|
|
|
|
if age != 2 {
|
|
|
|
|
t.Errorf("expected age 2, got %d", age)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = db.QueryRow("SELECT|people|age,name|name=?", "Alice").Scan(&age, &name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("name QueryRow+Scan: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if name != "Alice" {
|
|
|
|
|
t.Errorf("expected name Alice, got %q", name)
|
|
|
|
|
}
|
|
|
|
|
if age != 1 {
|
|
|
|
|
t.Errorf("expected age 1, got %d", age)
|
|
|
|
|
}
|
2012-01-12 11:23:33 -08:00
|
|
|
|
|
|
|
|
var photo []byte
|
|
|
|
|
err = db.QueryRow("SELECT|people|photo|name=?", "Alice").Scan(&photo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("photo QueryRow+Scan: %v", err)
|
|
|
|
|
}
|
|
|
|
|
want := []byte("APHOTO")
|
|
|
|
|
if !reflect.DeepEqual(photo, want) {
|
|
|
|
|
t.Errorf("photo = %q; want %q", photo, want)
|
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
}
|
|
|
|
|
|
2016-12-01 11:07:11 -08:00
|
|
|
func TestTxRollbackCommitErr(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
err = tx.Rollback()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("expected nil error from Rollback; got %v", err)
|
|
|
|
|
}
|
|
|
|
|
err = tx.Commit()
|
|
|
|
|
if err != ErrTxDone {
|
|
|
|
|
t.Errorf("expected %q from Commit; got %q", ErrTxDone, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx, err = db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
err = tx.Commit()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("expected nil error from Commit; got %v", err)
|
|
|
|
|
}
|
|
|
|
|
err = tx.Rollback()
|
|
|
|
|
if err != ErrTxDone {
|
|
|
|
|
t.Errorf("expected %q from Rollback; got %q", ErrTxDone, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-20 14:56:49 -05:00
|
|
|
func TestStatementErrorAfterClose(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
stmt, err := db.Prepare("SELECT|people|age|name=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Prepare: %v", err)
|
|
|
|
|
}
|
|
|
|
|
err = stmt.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Close: %v", err)
|
|
|
|
|
}
|
|
|
|
|
var name string
|
|
|
|
|
err = stmt.QueryRow("foo").Scan(&name)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Errorf("expected error from QueryRow.Scan after Stmt.Close")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
func TestStatementQueryRow(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
2011-11-14 10:48:26 -08:00
|
|
|
defer closeDB(t, db)
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
stmt, err := db.Prepare("SELECT|people|age|name=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Prepare: %v", err)
|
|
|
|
|
}
|
2012-03-10 15:21:44 -08:00
|
|
|
defer stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
var age int
|
|
|
|
|
for n, tt := range []struct {
|
|
|
|
|
name string
|
|
|
|
|
want int
|
|
|
|
|
}{
|
|
|
|
|
{"Alice", 1},
|
|
|
|
|
{"Bob", 2},
|
|
|
|
|
{"Chris", 3},
|
|
|
|
|
} {
|
|
|
|
|
if err := stmt.QueryRow(tt.name).Scan(&age); err != nil {
|
|
|
|
|
t.Errorf("%d: on %q, QueryRow/Scan: %v", n, tt.name, err)
|
|
|
|
|
} else if age != tt.want {
|
2011-10-13 13:34:01 +11:00
|
|
|
t.Errorf("%d: age=%d, want %d", n, age, tt.want)
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-01 03:29:27 -07:00
|
|
|
type stubDriverStmt struct {
|
|
|
|
|
err error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s stubDriverStmt) Close() error {
|
|
|
|
|
return s.err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s stubDriverStmt) NumInput() int {
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s stubDriverStmt) Exec(args []driver.Value) (driver.Result, error) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s stubDriverStmt) Query(args []driver.Value) (driver.Rows, error) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// golang.org/issue/12798
|
|
|
|
|
func TestStatementClose(t *testing.T) {
|
|
|
|
|
want := errors.New("STMT ERROR")
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
stmt *Stmt
|
|
|
|
|
msg string
|
|
|
|
|
}{
|
|
|
|
|
{&Stmt{stickyErr: want}, "stickyErr not propagated"},
|
2017-06-12 10:46:15 -07:00
|
|
|
{&Stmt{cg: &Tx{}, cgds: &driverStmt{Locker: &sync.Mutex{}, si: stubDriverStmt{want}}}, "driverStmt.Close() error not propagated"},
|
2015-10-01 03:29:27 -07:00
|
|
|
}
|
|
|
|
|
for _, test := range tests {
|
|
|
|
|
if err := test.stmt.Close(); err != want {
|
|
|
|
|
t.Errorf("%s. Got stmt.Close() = %v, want = %v", test.msg, err, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-20 22:15:36 -08:00
|
|
|
// golang.org/issue/3734
|
|
|
|
|
func TestStatementQueryRowConcurrent(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
stmt, err := db.Prepare("SELECT|people|age|name=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Prepare: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer stmt.Close()
|
|
|
|
|
|
|
|
|
|
const n = 10
|
|
|
|
|
ch := make(chan error, n)
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
go func() {
|
|
|
|
|
var age int
|
|
|
|
|
err := stmt.QueryRow("Alice").Scan(&age)
|
|
|
|
|
if err == nil && age != 1 {
|
|
|
|
|
err = fmt.Errorf("unexpected age %d", age)
|
|
|
|
|
}
|
|
|
|
|
ch <- err
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
if err := <-ch; err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
// just a test of fakedb itself
|
|
|
|
|
func TestBogusPreboundParameters(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "foo")
|
2011-11-14 10:48:26 -08:00
|
|
|
defer closeDB(t, db)
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
|
|
|
|
|
_, err := db.Prepare("INSERT|t1|name=?,age=bogusconversion")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatalf("expected error")
|
|
|
|
|
}
|
2011-11-01 22:04:37 -04:00
|
|
|
if err.Error() != `fakedb: invalid conversion to int32 from "bogusconversion"` {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
t.Errorf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-28 11:00:32 -05:00
|
|
|
func TestExec(t *testing.T) {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
db := newTestDB(t, "foo")
|
2011-11-14 10:48:26 -08:00
|
|
|
defer closeDB(t, db)
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
|
|
|
|
|
stmt, err := db.Prepare("INSERT|t1|name=?,age=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Stmt, err = %v, %v", stmt, err)
|
|
|
|
|
}
|
2012-03-10 15:21:44 -08:00
|
|
|
defer stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
|
|
|
|
|
type execTest struct {
|
|
|
|
|
args []interface{}
|
|
|
|
|
wantErr string
|
|
|
|
|
}
|
|
|
|
|
execTests := []execTest{
|
|
|
|
|
// Okay:
|
|
|
|
|
{[]interface{}{"Brad", 31}, ""},
|
|
|
|
|
{[]interface{}{"Brad", int64(31)}, ""},
|
|
|
|
|
{[]interface{}{"Bob", "32"}, ""},
|
|
|
|
|
{[]interface{}{7, 9}, ""},
|
|
|
|
|
|
|
|
|
|
// Invalid conversions:
|
2016-10-17 09:28:06 -07:00
|
|
|
{[]interface{}{"Brad", int64(0xFFFFFFFF)}, "sql: converting argument $2 type: sql/driver: value 4294967295 overflows int32"},
|
|
|
|
|
{[]interface{}{"Brad", "strconv fail"}, `sql: converting argument $2 type: sql/driver: value "strconv fail" can't be converted to int32`},
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
|
|
|
|
|
// Wrong number of args:
|
2011-12-15 10:14:57 -08:00
|
|
|
{[]interface{}{}, "sql: expected 2 arguments, got 0"},
|
|
|
|
|
{[]interface{}{1, 2, 3}, "sql: expected 2 arguments, got 3"},
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
}
|
|
|
|
|
for n, et := range execTests {
|
|
|
|
|
_, err := stmt.Exec(et.args...)
|
|
|
|
|
errStr := ""
|
|
|
|
|
if err != nil {
|
2011-11-01 22:04:37 -04:00
|
|
|
errStr = err.Error()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 16:12:21 -07:00
|
|
|
}
|
|
|
|
|
if errStr != et.wantErr {
|
|
|
|
|
t.Errorf("stmt.Execute #%d: for %v, got error %q, want error %q",
|
|
|
|
|
n, et.args, errStr, et.wantErr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-11-28 11:00:32 -05:00
|
|
|
|
2014-09-22 09:19:27 -04:00
|
|
|
func TestTxPrepare(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Begin = %v", err)
|
|
|
|
|
}
|
|
|
|
|
stmt, err := tx.Prepare("INSERT|t1|name=?,age=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Stmt, err = %v, %v", stmt, err)
|
|
|
|
|
}
|
|
|
|
|
defer stmt.Close()
|
|
|
|
|
_, err = stmt.Exec("Bobby", 7)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Exec = %v", err)
|
|
|
|
|
}
|
|
|
|
|
err = tx.Commit()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Commit = %v", err)
|
|
|
|
|
}
|
|
|
|
|
// Commit() should have closed the statement
|
|
|
|
|
if !stmt.closed {
|
|
|
|
|
t.Fatal("Stmt not closed after Commit")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-28 11:00:32 -05:00
|
|
|
func TestTxStmt(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
|
|
|
|
|
stmt, err := db.Prepare("INSERT|t1|name=?,age=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Stmt, err = %v, %v", stmt, err)
|
|
|
|
|
}
|
2012-03-10 15:21:44 -08:00
|
|
|
defer stmt.Close()
|
2011-11-28 11:00:32 -05:00
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Begin = %v", err)
|
|
|
|
|
}
|
2012-03-10 15:21:44 -08:00
|
|
|
txs := tx.Stmt(stmt)
|
|
|
|
|
defer txs.Close()
|
|
|
|
|
_, err = txs.Exec("Bobby", 7)
|
2011-11-28 11:00:32 -05:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Exec = %v", err)
|
|
|
|
|
}
|
|
|
|
|
err = tx.Commit()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Commit = %v", err)
|
|
|
|
|
}
|
2014-09-22 09:19:27 -04:00
|
|
|
// Commit() should have closed the statement
|
|
|
|
|
if !txs.closed {
|
|
|
|
|
t.Fatal("Stmt not closed after Commit")
|
|
|
|
|
}
|
2011-11-28 11:00:32 -05:00
|
|
|
}
|
2011-12-12 13:56:56 -08:00
|
|
|
|
2017-01-19 14:17:10 -08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-08 10:22:00 -08:00
|
|
|
// Test that tx.Stmt called with a statement already
|
2017-01-19 14:17:10 -08:00
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-10 17:17:11 -06:00
|
|
|
// Issue: https://golang.org/issue/2784
|
2014-04-29 12:44:40 -04:00
|
|
|
// This test didn't fail before because we got lucky with the fakedb driver.
|
2012-01-25 17:49:30 -08:00
|
|
|
// It was failing, and now not, in github.com/bradfitz/go-sql-test
|
|
|
|
|
func TestTxQuery(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
|
|
|
|
|
exec(t, db, "INSERT|t1|name=Alice")
|
|
|
|
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
|
|
r, err := tx.Query("SELECT|t1|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2012-03-10 15:21:44 -08:00
|
|
|
defer r.Close()
|
2012-01-25 17:49:30 -08:00
|
|
|
|
|
|
|
|
if !r.Next() {
|
|
|
|
|
if r.Err() != nil {
|
|
|
|
|
t.Fatal(r.Err())
|
|
|
|
|
}
|
|
|
|
|
t.Fatal("expected one row")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var x string
|
|
|
|
|
err = r.Scan(&x)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-10 10:00:02 -08:00
|
|
|
func TestTxQueryInvalid(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
|
|
_, err = tx.Query("SELECT|t1|name|")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("Error expected")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-14 09:00:33 -08:00
|
|
|
// Tests fix for issue 4433, that retries in Begin happen when
|
|
|
|
|
// conn.Begin() returns ErrBadConn
|
|
|
|
|
func TestTxErrBadConn(t *testing.T) {
|
|
|
|
|
db, err := Open("test", fakeDBName+";badConn")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Open: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := db.Exec("WIPE"); err != nil {
|
|
|
|
|
t.Fatalf("exec wipe: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
|
|
|
|
|
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)
|
|
|
|
|
defer txs.Close()
|
|
|
|
|
_, err = txs.Exec("Bobby", 7)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Exec = %v", err)
|
|
|
|
|
}
|
|
|
|
|
err = tx.Commit()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Commit = %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
database/sql: allow using a single connection from the database
Databases have the following concepts: Statement, Batch, and Session.
A statement is often a single line like:
SELECT Amount from Account where ID = 50;
A batch is one or more statements submitted together for the query
to process. It may be a DELETE, INSERT, two UPDATES and a SELECT in
a single query text.
A session is usually represented by a single database connection.
This often is an issue when dealing with scopes in databases.
Temporary tables and variables can have batch, session, or global
scope depending on the syntax, database, and use.
Furthermore, some databases (sybase and derivatives in perticular)
that prevent certain statements from being in the same batch
and may necessitate being in the same session.
By allowing users to extract a Conn from the database they can manage
session on their own without hacking around it by making connection
pools of single connections (a real workaround presented in issue).
It is tempting to just use a transaction, but this isn't always
desirable or an option if running an interactive session or
alter script set that itself starts transactions.
Fixes #18081
Change-Id: I9bdf0796632c48d4bcaef3624c629641984ffaf2
Reviewed-on: https://go-review.googlesource.com/40694
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-04-13 16:04:40 -07:00
|
|
|
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)
|
|
|
|
|
}
|
2017-10-02 11:16:53 -07:00
|
|
|
conn.dc.ci.(*fakeConn).skipDirtySession = true
|
database/sql: allow using a single connection from the database
Databases have the following concepts: Statement, Batch, and Session.
A statement is often a single line like:
SELECT Amount from Account where ID = 50;
A batch is one or more statements submitted together for the query
to process. It may be a DELETE, INSERT, two UPDATES and a SELECT in
a single query text.
A session is usually represented by a single database connection.
This often is an issue when dealing with scopes in databases.
Temporary tables and variables can have batch, session, or global
scope depending on the syntax, database, and use.
Furthermore, some databases (sybase and derivatives in perticular)
that prevent certain statements from being in the same batch
and may necessitate being in the same session.
By allowing users to extract a Conn from the database they can manage
session on their own without hacking around it by making connection
pools of single connections (a real workaround presented in issue).
It is tempting to just use a transaction, but this isn't always
desirable or an option if running an interactive session or
alter script set that itself starts transactions.
Fixes #18081
Change-Id: I9bdf0796632c48d4bcaef3624c629641984ffaf2
Reviewed-on: https://go-review.googlesource.com/40694
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-04-13 16:04:40 -07:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-26 14:09:07 -07:00
|
|
|
func TestConnRaw(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)
|
|
|
|
|
}
|
|
|
|
|
conn.dc.ci.(*fakeConn).skipDirtySession = true
|
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
|
|
sawFunc := false
|
|
|
|
|
err = conn.Raw(func(dc interface{}) error {
|
|
|
|
|
sawFunc = true
|
|
|
|
|
if _, ok := dc.(*fakeConn); !ok {
|
|
|
|
|
return fmt.Errorf("got %T want *fakeConn", dc)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if !sawFunc {
|
|
|
|
|
t.Fatal("Raw func not called")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
x := recover()
|
|
|
|
|
if x == nil {
|
|
|
|
|
t.Fatal("expected panic")
|
|
|
|
|
}
|
|
|
|
|
conn.closemu.Lock()
|
|
|
|
|
closed := conn.dc == nil
|
|
|
|
|
conn.closemu.Unlock()
|
|
|
|
|
if !closed {
|
|
|
|
|
t.Fatal("expected connection to be closed after panic")
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
err = conn.Raw(func(dc interface{}) error {
|
|
|
|
|
panic("Conn.Raw panic should return an error")
|
|
|
|
|
})
|
|
|
|
|
t.Fatal("expected panic from Raw func")
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-29 16:22:37 -07:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-07 15:08:59 +01:00
|
|
|
func TestInvalidNilValues(t *testing.T) {
|
|
|
|
|
var date1 time.Time
|
|
|
|
|
var date2 int
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
input interface{}
|
|
|
|
|
expectedError string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "time.Time",
|
|
|
|
|
input: &date1,
|
|
|
|
|
expectedError: `sql: Scan error on column index 0, name "bdate": unsupported Scan, storing driver.Value type <nil> into type *time.Time`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "int",
|
|
|
|
|
input: &date2,
|
2019-04-26 11:09:51 -07:00
|
|
|
expectedError: `sql: Scan error on column index 0, name "bdate": converting NULL to int is unsupported`,
|
2018-01-07 15:08:59 +01:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(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)
|
|
|
|
|
}
|
|
|
|
|
conn.dc.ci.(*fakeConn).skipDirtySession = true
|
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
|
|
err = conn.QueryRowContext(ctx, "SELECT|people|bdate|age=?", 1).Scan(tt.input)
|
|
|
|
|
if err == nil {
|
2018-02-20 20:50:20 +00:00
|
|
|
t.Fatal("expected error when querying nil column, but succeeded")
|
2018-01-07 15:08:59 +01:00
|
|
|
}
|
|
|
|
|
if err.Error() != tt.expectedError {
|
|
|
|
|
t.Fatalf("Expected error: %s\nReceived: %s", tt.expectedError, err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = conn.PingContext(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
database/sql: allow using a single connection from the database
Databases have the following concepts: Statement, Batch, and Session.
A statement is often a single line like:
SELECT Amount from Account where ID = 50;
A batch is one or more statements submitted together for the query
to process. It may be a DELETE, INSERT, two UPDATES and a SELECT in
a single query text.
A session is usually represented by a single database connection.
This often is an issue when dealing with scopes in databases.
Temporary tables and variables can have batch, session, or global
scope depending on the syntax, database, and use.
Furthermore, some databases (sybase and derivatives in perticular)
that prevent certain statements from being in the same batch
and may necessitate being in the same session.
By allowing users to extract a Conn from the database they can manage
session on their own without hacking around it by making connection
pools of single connections (a real workaround presented in issue).
It is tempting to just use a transaction, but this isn't always
desirable or an option if running an interactive session or
alter script set that itself starts transactions.
Fixes #18081
Change-Id: I9bdf0796632c48d4bcaef3624c629641984ffaf2
Reviewed-on: https://go-review.googlesource.com/40694
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-04-13 16:04:40 -07:00
|
|
|
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)
|
|
|
|
|
}
|
2017-10-02 11:16:53 -07:00
|
|
|
conn.dc.ci.(*fakeConn).skipDirtySession = true
|
database/sql: allow using a single connection from the database
Databases have the following concepts: Statement, Batch, and Session.
A statement is often a single line like:
SELECT Amount from Account where ID = 50;
A batch is one or more statements submitted together for the query
to process. It may be a DELETE, INSERT, two UPDATES and a SELECT in
a single query text.
A session is usually represented by a single database connection.
This often is an issue when dealing with scopes in databases.
Temporary tables and variables can have batch, session, or global
scope depending on the syntax, database, and use.
Furthermore, some databases (sybase and derivatives in perticular)
that prevent certain statements from being in the same batch
and may necessitate being in the same session.
By allowing users to extract a Conn from the database they can manage
session on their own without hacking around it by making connection
pools of single connections (a real workaround presented in issue).
It is tempting to just use a transaction, but this isn't always
desirable or an option if running an interactive session or
alter script set that itself starts transactions.
Fixes #18081
Change-Id: I9bdf0796632c48d4bcaef3624c629641984ffaf2
Reviewed-on: https://go-review.googlesource.com/40694
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-04-13 16:04:40 -07:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-12 13:56:56 -08:00
|
|
|
// Tests fix for issue 2542, that we release a lock when querying on
|
|
|
|
|
// a closed connection.
|
|
|
|
|
func TestIssue2542Deadlock(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
closeDB(t, db)
|
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
|
_, err := db.Query("SELECT|people|age,name|")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatalf("expected error")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-01-13 15:25:07 -08:00
|
|
|
|
2013-02-20 15:35:27 -08:00
|
|
|
// From golang.org/issue/3865
|
2013-02-13 12:00:03 -08:00
|
|
|
func TestCloseStmtBeforeRows(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
s, err := db.Prepare("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r, err := s.Query()
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.Close()
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = s.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.Close()
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-26 15:12:48 -08:00
|
|
|
// Tests fix for issue 2788, that we bind nil to a []byte if the
|
|
|
|
|
// value in the column is sql null
|
|
|
|
|
func TestNullByteSlice(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
exec(t, db, "CREATE|t|id=int32,name=nullstring")
|
|
|
|
|
exec(t, db, "INSERT|t|id=10,name=?", nil)
|
|
|
|
|
|
|
|
|
|
var name []byte
|
|
|
|
|
|
|
|
|
|
err := db.QueryRow("SELECT|t|name|id=?", 10).Scan(&name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if name != nil {
|
|
|
|
|
t.Fatalf("name []byte should be nil for null column value, got: %#v", name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exec(t, db, "INSERT|t|id=11,name=?", "bob")
|
|
|
|
|
err = db.QueryRow("SELECT|t|name|id=?", 11).Scan(&name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if string(name) != "bob" {
|
|
|
|
|
t.Fatalf("name []byte should be bob, got: %q", string(name))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-02-09 15:01:29 +11:00
|
|
|
func TestPointerParamsAndScans(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
exec(t, db, "CREATE|t|id=int32,name=nullstring")
|
|
|
|
|
|
|
|
|
|
bob := "bob"
|
|
|
|
|
var name *string
|
|
|
|
|
|
|
|
|
|
name = &bob
|
|
|
|
|
exec(t, db, "INSERT|t|id=10,name=?", name)
|
|
|
|
|
name = nil
|
|
|
|
|
exec(t, db, "INSERT|t|id=20,name=?", name)
|
|
|
|
|
|
|
|
|
|
err := db.QueryRow("SELECT|t|name|id=?", 10).Scan(&name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("querying id 10: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if name == nil {
|
|
|
|
|
t.Errorf("id 10's name = nil; want bob")
|
|
|
|
|
} else if *name != "bob" {
|
|
|
|
|
t.Errorf("id 10's name = %q; want bob", *name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = db.QueryRow("SELECT|t|name|id=?", 20).Scan(&name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("querying id 20: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if name != nil {
|
|
|
|
|
t.Errorf("id 20 = %q; want nil", *name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-13 15:25:07 -08:00
|
|
|
func TestQueryRowClosingStmt(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
var name string
|
|
|
|
|
var age int
|
|
|
|
|
err := db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&age, &name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2014-08-28 08:49:56 -07:00
|
|
|
if len(db.freeConn) != 1 {
|
2012-01-13 15:25:07 -08:00
|
|
|
t.Fatalf("expected 1 free conn")
|
|
|
|
|
}
|
2014-08-28 08:49:56 -07:00
|
|
|
fakeConn := db.freeConn[0].ci.(*fakeConn)
|
2012-01-13 15:25:07 -08:00
|
|
|
if made, closed := fakeConn.stmtsMade, fakeConn.stmtsClosed; made != closed {
|
2012-01-17 10:44:35 -08:00
|
|
|
t.Errorf("statement close mismatch: made %d, closed %d", made, closed)
|
2012-01-13 15:25:07 -08:00
|
|
|
}
|
|
|
|
|
}
|
2012-01-19 09:27:45 -08:00
|
|
|
|
2017-01-20 17:12:50 -08:00
|
|
|
var atomicRowsCloseHook atomic.Value // of func(*Rows, *error)
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
rowsCloseHook = func() func(*Rows, *error) {
|
|
|
|
|
fn, _ := atomicRowsCloseHook.Load().(func(*Rows, *error))
|
|
|
|
|
return fn
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setRowsCloseHook(fn func(*Rows, *error)) {
|
|
|
|
|
if fn == nil {
|
|
|
|
|
// Can't change an atomic.Value back to nil, so set it to this
|
|
|
|
|
// no-op func instead.
|
|
|
|
|
fn = func(*Rows, *error) {}
|
|
|
|
|
}
|
|
|
|
|
atomicRowsCloseHook.Store(fn)
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-16 12:48:35 -08:00
|
|
|
// Test issue 6651
|
|
|
|
|
func TestIssue6651(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
var v string
|
|
|
|
|
|
|
|
|
|
want := "error in rows.Next"
|
|
|
|
|
rowsCursorNextHook = func(dest []driver.Value) error {
|
|
|
|
|
return fmt.Errorf(want)
|
|
|
|
|
}
|
|
|
|
|
defer func() { rowsCursorNextHook = nil }()
|
2017-01-20 17:12:50 -08:00
|
|
|
|
2013-12-16 12:48:35 -08:00
|
|
|
err := db.QueryRow("SELECT|people|name|").Scan(&v)
|
|
|
|
|
if err == nil || err.Error() != want {
|
|
|
|
|
t.Errorf("error = %q; want %q", err, want)
|
|
|
|
|
}
|
|
|
|
|
rowsCursorNextHook = nil
|
|
|
|
|
|
|
|
|
|
want = "error in rows.Close"
|
2017-01-20 17:12:50 -08:00
|
|
|
setRowsCloseHook(func(rows *Rows, err *error) {
|
2013-12-16 12:48:35 -08:00
|
|
|
*err = fmt.Errorf(want)
|
2017-01-20 17:12:50 -08:00
|
|
|
})
|
|
|
|
|
defer setRowsCloseHook(nil)
|
2013-12-16 12:48:35 -08:00
|
|
|
err = db.QueryRow("SELECT|people|name|").Scan(&v)
|
|
|
|
|
if err == nil || err.Error() != want {
|
|
|
|
|
t.Errorf("error = %q; want %q", err, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-25 17:47:32 -08:00
|
|
|
type nullTestRow struct {
|
|
|
|
|
nullParam interface{}
|
|
|
|
|
notNullParam interface{}
|
|
|
|
|
scanNullVal interface{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type nullTestSpec struct {
|
|
|
|
|
nullType string
|
|
|
|
|
notNullType string
|
|
|
|
|
rows [6]nullTestRow
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-19 09:27:45 -08:00
|
|
|
func TestNullStringParam(t *testing.T) {
|
2012-01-25 17:47:32 -08:00
|
|
|
spec := nullTestSpec{"nullstring", "string", [6]nullTestRow{
|
2012-03-08 10:48:51 -08:00
|
|
|
{NullString{"aqua", true}, "", NullString{"aqua", true}},
|
|
|
|
|
{NullString{"brown", false}, "", NullString{"", false}},
|
|
|
|
|
{"chartreuse", "", NullString{"chartreuse", true}},
|
|
|
|
|
{NullString{"darkred", true}, "", NullString{"darkred", true}},
|
|
|
|
|
{NullString{"eel", false}, "", NullString{"", false}},
|
|
|
|
|
{"foo", NullString{"black", false}, nil},
|
2012-01-25 17:47:32 -08:00
|
|
|
}}
|
|
|
|
|
nullTestRun(t, spec)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNullInt64Param(t *testing.T) {
|
|
|
|
|
spec := nullTestSpec{"nullint64", "int64", [6]nullTestRow{
|
2012-03-08 10:48:51 -08:00
|
|
|
{NullInt64{31, true}, 1, NullInt64{31, true}},
|
|
|
|
|
{NullInt64{-22, false}, 1, NullInt64{0, false}},
|
|
|
|
|
{22, 1, NullInt64{22, true}},
|
|
|
|
|
{NullInt64{33, true}, 1, NullInt64{33, true}},
|
|
|
|
|
{NullInt64{222, false}, 1, NullInt64{0, false}},
|
|
|
|
|
{0, NullInt64{31, false}, nil},
|
2019-04-26 11:46:26 -07:00
|
|
|
}}
|
|
|
|
|
nullTestRun(t, spec)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNullInt32Param(t *testing.T) {
|
|
|
|
|
spec := nullTestSpec{"nullint32", "int32", [6]nullTestRow{
|
|
|
|
|
{NullInt32{31, true}, 1, NullInt32{31, true}},
|
|
|
|
|
{NullInt32{-22, false}, 1, NullInt32{0, false}},
|
|
|
|
|
{22, 1, NullInt32{22, true}},
|
|
|
|
|
{NullInt32{33, true}, 1, NullInt32{33, true}},
|
|
|
|
|
{NullInt32{222, false}, 1, NullInt32{0, false}},
|
|
|
|
|
{0, NullInt32{31, false}, nil},
|
2012-01-25 17:47:32 -08:00
|
|
|
}}
|
|
|
|
|
nullTestRun(t, spec)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNullFloat64Param(t *testing.T) {
|
|
|
|
|
spec := nullTestSpec{"nullfloat64", "float64", [6]nullTestRow{
|
2012-03-08 10:48:51 -08:00
|
|
|
{NullFloat64{31.2, true}, 1, NullFloat64{31.2, true}},
|
|
|
|
|
{NullFloat64{13.1, false}, 1, NullFloat64{0, false}},
|
|
|
|
|
{-22.9, 1, NullFloat64{-22.9, true}},
|
|
|
|
|
{NullFloat64{33.81, true}, 1, NullFloat64{33.81, true}},
|
|
|
|
|
{NullFloat64{222, false}, 1, NullFloat64{0, false}},
|
|
|
|
|
{10, NullFloat64{31.2, false}, nil},
|
2012-01-25 17:47:32 -08:00
|
|
|
}}
|
|
|
|
|
nullTestRun(t, spec)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNullBoolParam(t *testing.T) {
|
|
|
|
|
spec := nullTestSpec{"nullbool", "bool", [6]nullTestRow{
|
2012-03-08 10:48:51 -08:00
|
|
|
{NullBool{false, true}, true, NullBool{false, true}},
|
|
|
|
|
{NullBool{true, false}, false, NullBool{false, false}},
|
|
|
|
|
{true, true, NullBool{true, true}},
|
|
|
|
|
{NullBool{true, true}, false, NullBool{true, true}},
|
|
|
|
|
{NullBool{true, false}, true, NullBool{false, false}},
|
|
|
|
|
{true, NullBool{true, false}, nil},
|
2012-01-25 17:47:32 -08:00
|
|
|
}}
|
|
|
|
|
nullTestRun(t, spec)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-05 10:23:15 -07:00
|
|
|
func TestNullTimeParam(t *testing.T) {
|
|
|
|
|
t0 := time.Time{}
|
|
|
|
|
t1 := time.Date(2000, 1, 1, 8, 9, 10, 11, time.UTC)
|
|
|
|
|
t2 := time.Date(2010, 1, 1, 8, 9, 10, 11, time.UTC)
|
|
|
|
|
spec := nullTestSpec{"nulldatetime", "datetime", [6]nullTestRow{
|
|
|
|
|
{NullTime{t1, true}, t2, NullTime{t1, true}},
|
|
|
|
|
{NullTime{t1, false}, t2, NullTime{t0, false}},
|
|
|
|
|
{t1, t2, NullTime{t1, true}},
|
|
|
|
|
{NullTime{t1, true}, t2, NullTime{t1, true}},
|
|
|
|
|
{NullTime{t1, false}, t2, NullTime{t0, false}},
|
|
|
|
|
{t2, NullTime{t1, false}, nil},
|
|
|
|
|
}}
|
|
|
|
|
nullTestRun(t, spec)
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-25 17:47:32 -08:00
|
|
|
func nullTestRun(t *testing.T, spec nullTestSpec) {
|
2012-01-19 09:27:45 -08:00
|
|
|
db := newTestDB(t, "")
|
|
|
|
|
defer closeDB(t, db)
|
2012-01-25 17:47:32 -08:00
|
|
|
exec(t, db, fmt.Sprintf("CREATE|t|id=int32,name=string,nullf=%s,notnullf=%s", spec.nullType, spec.notNullType))
|
2012-01-19 09:27:45 -08:00
|
|
|
|
|
|
|
|
// Inserts with db.Exec:
|
2012-01-25 17:47:32 -08:00
|
|
|
exec(t, db, "INSERT|t|id=?,name=?,nullf=?,notnullf=?", 1, "alice", spec.rows[0].nullParam, spec.rows[0].notNullParam)
|
|
|
|
|
exec(t, db, "INSERT|t|id=?,name=?,nullf=?,notnullf=?", 2, "bob", spec.rows[1].nullParam, spec.rows[1].notNullParam)
|
2012-01-19 09:27:45 -08:00
|
|
|
|
|
|
|
|
// Inserts with a prepared statement:
|
2012-01-25 17:47:32 -08:00
|
|
|
stmt, err := db.Prepare("INSERT|t|id=?,name=?,nullf=?,notnullf=?")
|
2012-01-19 09:27:45 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("prepare: %v", err)
|
|
|
|
|
}
|
2012-03-10 15:21:44 -08:00
|
|
|
defer stmt.Close()
|
2012-01-25 17:47:32 -08:00
|
|
|
if _, err := stmt.Exec(3, "chris", spec.rows[2].nullParam, spec.rows[2].notNullParam); err != nil {
|
2012-01-19 09:27:45 -08:00
|
|
|
t.Errorf("exec insert chris: %v", err)
|
|
|
|
|
}
|
2012-01-25 17:47:32 -08:00
|
|
|
if _, err := stmt.Exec(4, "dave", spec.rows[3].nullParam, spec.rows[3].notNullParam); err != nil {
|
2012-01-19 09:27:45 -08:00
|
|
|
t.Errorf("exec insert dave: %v", err)
|
|
|
|
|
}
|
2012-01-25 17:47:32 -08:00
|
|
|
if _, err := stmt.Exec(5, "eleanor", spec.rows[4].nullParam, spec.rows[4].notNullParam); err != nil {
|
|
|
|
|
t.Errorf("exec insert eleanor: %v", err)
|
2012-01-19 09:27:45 -08:00
|
|
|
}
|
|
|
|
|
|
2012-01-25 17:47:32 -08:00
|
|
|
// Can't put null val into non-null col
|
|
|
|
|
if _, err := stmt.Exec(6, "bob", spec.rows[5].nullParam, spec.rows[5].notNullParam); err == nil {
|
|
|
|
|
t.Errorf("expected error inserting nil val with prepared statement Exec")
|
2012-01-19 09:27:45 -08:00
|
|
|
}
|
|
|
|
|
|
2012-01-25 17:47:32 -08:00
|
|
|
_, err = db.Exec("INSERT|t|id=?,name=?,nullf=?", 999, nil, nil)
|
|
|
|
|
if err == nil {
|
|
|
|
|
// TODO: this test fails, but it's just because
|
|
|
|
|
// fakeConn implements the optional Execer interface,
|
2016-03-01 23:21:55 +00:00
|
|
|
// so arguably this is the correct behavior. But
|
2012-01-25 17:47:32 -08:00
|
|
|
// maybe I should flesh out the fakeConn.Exec
|
|
|
|
|
// implementation so this properly fails.
|
|
|
|
|
// t.Errorf("expected error inserting nil name with Exec")
|
2012-01-19 09:27:45 -08:00
|
|
|
}
|
|
|
|
|
|
2012-01-25 17:47:32 -08:00
|
|
|
paramtype := reflect.TypeOf(spec.rows[0].nullParam)
|
|
|
|
|
bindVal := reflect.New(paramtype).Interface()
|
|
|
|
|
|
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
|
id := i + 1
|
|
|
|
|
if err := db.QueryRow("SELECT|t|nullf|id=?", id).Scan(bindVal); err != nil {
|
2012-01-19 09:27:45 -08:00
|
|
|
t.Errorf("id=%d Scan: %v", id, err)
|
|
|
|
|
}
|
2012-01-25 17:47:32 -08:00
|
|
|
bindValDeref := reflect.ValueOf(bindVal).Elem().Interface()
|
|
|
|
|
if !reflect.DeepEqual(bindValDeref, spec.rows[i].scanNullVal) {
|
|
|
|
|
t.Errorf("id=%d got %#v, want %#v", id, bindValDeref, spec.rows[i].scanNullVal)
|
2012-01-19 09:27:45 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-02-21 10:43:00 -08:00
|
|
|
|
|
|
|
|
// golang.org/issue/4859
|
|
|
|
|
func TestQueryRowNilScanDest(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
var name *string // nil pointer
|
|
|
|
|
err := db.QueryRow("SELECT|people|name|").Scan(name)
|
2018-01-07 15:08:59 +01:00
|
|
|
want := `sql: Scan error on column index 0, name "name": destination pointer is nil`
|
2013-02-21 10:43:00 -08:00
|
|
|
if err == nil || err.Error() != want {
|
|
|
|
|
t.Errorf("error = %q; want %q", err.Error(), want)
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-03-08 10:04:17 -08:00
|
|
|
|
|
|
|
|
func TestIssue4902(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
2017-08-05 06:04:19 -04:00
|
|
|
driver := db.Driver().(*fakeDriver)
|
2013-03-08 10:04:17 -08:00
|
|
|
opens0 := driver.openCount
|
|
|
|
|
|
|
|
|
|
var stmt *Stmt
|
|
|
|
|
var err error
|
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
|
stmt, err = db.Prepare("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
err = stmt.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
opens := driver.openCount - opens0
|
|
|
|
|
if opens > 1 {
|
|
|
|
|
t.Errorf("opens = %d; want <= 1", opens)
|
|
|
|
|
t.Logf("db = %#v", db)
|
|
|
|
|
t.Logf("driver = %#v", driver)
|
|
|
|
|
t.Logf("stmt = %#v", stmt)
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-03-18 11:39:00 -07:00
|
|
|
|
|
|
|
|
// Issue 3857
|
|
|
|
|
// This used to deadlock.
|
|
|
|
|
func TestSimultaneousQueries(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
|
|
r1, err := tx.Query("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer r1.Close()
|
|
|
|
|
|
|
|
|
|
r2, err := tx.Query("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer r2.Close()
|
|
|
|
|
}
|
2013-03-18 15:33:04 -07:00
|
|
|
|
|
|
|
|
func TestMaxIdleConns(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
tx.Commit()
|
2014-08-28 08:49:56 -07:00
|
|
|
if got := len(db.freeConn); got != 1 {
|
2013-03-18 15:33:04 -07:00
|
|
|
t.Errorf("freeConns = %d; want 1", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
db.SetMaxIdleConns(0)
|
|
|
|
|
|
2014-08-28 08:49:56 -07:00
|
|
|
if got := len(db.freeConn); got != 0 {
|
2013-03-18 15:33:04 -07:00
|
|
|
t.Errorf("freeConns after set to zero = %d; want 0", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx, err = db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
tx.Commit()
|
2014-08-28 08:49:56 -07:00
|
|
|
if got := len(db.freeConn); got != 0 {
|
2013-03-18 15:33:04 -07:00
|
|
|
t.Errorf("freeConns = %d; want 0", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-03-25 16:50:27 -07:00
|
|
|
|
2013-08-30 09:27:33 -07:00
|
|
|
func TestMaxOpenConns(t *testing.T) {
|
|
|
|
|
if testing.Short() {
|
|
|
|
|
t.Skip("skipping in short mode")
|
|
|
|
|
}
|
|
|
|
|
defer setHookpostCloseConn(nil)
|
|
|
|
|
setHookpostCloseConn(func(_ *fakeConn, err error) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Error closing fakeConn: %v", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t, "magicquery")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
2017-08-05 06:04:19 -04:00
|
|
|
driver := db.Driver().(*fakeDriver)
|
2013-08-30 09:27:33 -07:00
|
|
|
|
|
|
|
|
// Force the number of open connections to 0 so we can get an accurate
|
|
|
|
|
// count for the test
|
2015-03-03 21:27:07 +09:00
|
|
|
db.clearAllConns(t)
|
2013-08-30 09:27:33 -07:00
|
|
|
|
|
|
|
|
driver.mu.Lock()
|
|
|
|
|
opens0 := driver.openCount
|
|
|
|
|
closes0 := driver.closeCount
|
|
|
|
|
driver.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
db.SetMaxIdleConns(10)
|
|
|
|
|
db.SetMaxOpenConns(10)
|
|
|
|
|
|
|
|
|
|
stmt, err := db.Prepare("SELECT|magicquery|op|op=?,millis=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start 50 parallel slow queries.
|
|
|
|
|
const (
|
|
|
|
|
nquery = 50
|
|
|
|
|
sleepMillis = 25
|
|
|
|
|
nbatch = 2
|
|
|
|
|
)
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
for batch := 0; batch < nbatch; batch++ {
|
|
|
|
|
for i := 0; i < nquery; i++ {
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
var op string
|
|
|
|
|
if err := stmt.QueryRow("sleep", sleepMillis).Scan(&op); err != nil && err != ErrNoRows {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
// Sleep for twice the expected length of time for the
|
|
|
|
|
// batch of 50 queries above to finish before starting
|
|
|
|
|
// the next round.
|
|
|
|
|
time.Sleep(2 * sleepMillis * time.Millisecond)
|
|
|
|
|
}
|
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
|
|
if g, w := db.numFreeConns(), 10; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n := db.numDepsPollUntil(20, time.Second); n > 20 {
|
|
|
|
|
t.Errorf("number of dependencies = %d; expected <= 20", n)
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
driver.mu.Lock()
|
|
|
|
|
opens := driver.openCount - opens0
|
|
|
|
|
closes := driver.closeCount - closes0
|
|
|
|
|
driver.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if opens > 10 {
|
|
|
|
|
t.Logf("open calls = %d", opens)
|
|
|
|
|
t.Logf("close calls = %d", closes)
|
|
|
|
|
t.Errorf("db connections opened = %d; want <= 10", opens)
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := stmt.Close(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if g, w := db.numFreeConns(), 10; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n := db.numDepsPollUntil(10, time.Second); n > 10 {
|
|
|
|
|
t.Errorf("number of dependencies = %d; expected <= 10", n)
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
db.SetMaxOpenConns(5)
|
|
|
|
|
|
|
|
|
|
if g, w := db.numFreeConns(), 5; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n := db.numDepsPollUntil(5, time.Second); n > 5 {
|
|
|
|
|
t.Errorf("number of dependencies = %d; expected 0", n)
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
db.SetMaxOpenConns(0)
|
|
|
|
|
|
|
|
|
|
if g, w := db.numFreeConns(), 5; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n := db.numDepsPollUntil(5, time.Second); n > 5 {
|
|
|
|
|
t.Errorf("number of dependencies = %d; expected 0", n)
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-03 21:27:07 +09:00
|
|
|
db.clearAllConns(t)
|
2013-08-30 09:27:33 -07:00
|
|
|
}
|
|
|
|
|
|
2014-12-30 16:12:50 +08:00
|
|
|
// Issue 9453: tests that SetMaxOpenConns can be lowered at runtime
|
|
|
|
|
// and affects the subsequent release of connections.
|
|
|
|
|
func TestMaxOpenConnsOnBusy(t *testing.T) {
|
|
|
|
|
defer setHookpostCloseConn(nil)
|
|
|
|
|
setHookpostCloseConn(func(_ *fakeConn, err error) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Error closing fakeConn: %v", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t, "magicquery")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
db.SetMaxOpenConns(3)
|
|
|
|
|
|
2016-09-19 11:19:32 -07:00
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
conn0, err := db.conn(ctx, cachedOrNewConn)
|
2014-12-30 16:12:50 +08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("db open conn fail: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-19 11:19:32 -07:00
|
|
|
conn1, err := db.conn(ctx, cachedOrNewConn)
|
2014-12-30 16:12:50 +08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("db open conn fail: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-19 11:19:32 -07:00
|
|
|
conn2, err := db.conn(ctx, cachedOrNewConn)
|
2014-12-30 16:12:50 +08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("db open conn fail: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if g, w := db.numOpen, 3; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
db.SetMaxOpenConns(2)
|
|
|
|
|
if g, w := db.numOpen, 3; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
conn0.releaseConn(nil)
|
|
|
|
|
conn1.releaseConn(nil)
|
|
|
|
|
if g, w := db.numOpen, 2; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
conn2.releaseConn(nil)
|
|
|
|
|
if g, w := db.numOpen, 2; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-14 03:44:56 -04:00
|
|
|
// Issue 10886: tests that all connection attempts return when more than
|
|
|
|
|
// DB.maxOpen connections are in flight and the first DB.maxOpen fail.
|
|
|
|
|
func TestPendingConnsAfterErr(t *testing.T) {
|
|
|
|
|
const (
|
|
|
|
|
maxOpen = 2
|
|
|
|
|
tryOpen = maxOpen*2 + 2
|
|
|
|
|
)
|
|
|
|
|
|
2016-10-31 07:58:41 -07:00
|
|
|
// No queries will be run.
|
|
|
|
|
db, err := Open("test", fakeDBName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Open: %v", err)
|
|
|
|
|
}
|
2015-09-14 03:44:56 -04:00
|
|
|
defer closeDB(t, db)
|
|
|
|
|
defer func() {
|
|
|
|
|
for k, v := range db.lastPut {
|
|
|
|
|
t.Logf("%p: %v", k, v)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
db.SetMaxOpenConns(maxOpen)
|
|
|
|
|
db.SetMaxIdleConns(0)
|
|
|
|
|
|
|
|
|
|
errOffline := errors.New("db offline")
|
2016-10-31 07:58:41 -07:00
|
|
|
|
2015-09-14 03:44:56 -04:00
|
|
|
defer func() { setHookOpenErr(nil) }()
|
|
|
|
|
|
|
|
|
|
errs := make(chan error, tryOpen)
|
|
|
|
|
|
2016-10-31 07:58:41 -07:00
|
|
|
var opening sync.WaitGroup
|
|
|
|
|
opening.Add(tryOpen)
|
|
|
|
|
|
2015-09-14 03:44:56 -04:00
|
|
|
setHookOpenErr(func() error {
|
2016-10-31 07:58:41 -07:00
|
|
|
// Wait for all connections to enqueue.
|
|
|
|
|
opening.Wait()
|
2015-09-14 03:44:56 -04:00
|
|
|
return errOffline
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
for i := 0; i < tryOpen; i++ {
|
|
|
|
|
go func() {
|
|
|
|
|
opening.Done() // signal one connection is in flight
|
2016-10-31 07:58:41 -07:00
|
|
|
_, err := db.Exec("will never run")
|
2015-09-14 03:44:56 -04:00
|
|
|
errs <- err
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-31 07:58:41 -07:00
|
|
|
opening.Wait() // wait for all workers to begin running
|
2015-09-14 03:44:56 -04:00
|
|
|
|
2016-06-28 12:06:08 -07:00
|
|
|
const timeout = 5 * time.Second
|
2015-09-14 03:44:56 -04:00
|
|
|
to := time.NewTimer(timeout)
|
|
|
|
|
defer to.Stop()
|
|
|
|
|
|
|
|
|
|
// check that all connections fail without deadlock
|
|
|
|
|
for i := 0; i < tryOpen; i++ {
|
|
|
|
|
select {
|
|
|
|
|
case err := <-errs:
|
|
|
|
|
if got, want := err, errOffline; got != want {
|
|
|
|
|
t.Errorf("unexpected err: got %v, want %v", got, want)
|
|
|
|
|
}
|
|
|
|
|
case <-to.C:
|
|
|
|
|
t.Fatalf("orphaned connection request(s), still waiting after %v", timeout)
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-31 07:58:41 -07:00
|
|
|
|
|
|
|
|
// Wait a reasonable time for the database to close all connections.
|
|
|
|
|
tick := time.NewTicker(3 * time.Millisecond)
|
|
|
|
|
defer tick.Stop()
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-tick.C:
|
|
|
|
|
db.mu.Lock()
|
|
|
|
|
if db.numOpen == 0 {
|
|
|
|
|
db.mu.Unlock()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
db.mu.Unlock()
|
|
|
|
|
case <-to.C:
|
|
|
|
|
// Closing the database will check for numOpen and fail the test.
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-09-14 03:44:56 -04:00
|
|
|
}
|
|
|
|
|
|
2013-12-26 11:27:18 -08:00
|
|
|
func TestSingleOpenConn(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
db.SetMaxOpenConns(1)
|
|
|
|
|
|
|
|
|
|
rows, err := db.Query("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err = rows.Close(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
// shouldn't deadlock
|
|
|
|
|
rows, err = db.Query("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err = rows.Close(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-23 18:23:53 +03:00
|
|
|
func TestStats(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
stats := db.Stats()
|
|
|
|
|
if got := stats.OpenConnections; got != 1 {
|
|
|
|
|
t.Errorf("stats.OpenConnections = %d; want 1", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
|
|
closeDB(t, db)
|
|
|
|
|
stats = db.Stats()
|
|
|
|
|
if got := stats.OpenConnections; got != 0 {
|
|
|
|
|
t.Errorf("stats.OpenConnections = %d; want 0", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-03 21:27:07 +09:00
|
|
|
func TestConnMaxLifetime(t *testing.T) {
|
|
|
|
|
t0 := time.Unix(1000000, 0)
|
|
|
|
|
offset := time.Duration(0)
|
|
|
|
|
|
|
|
|
|
nowFunc = func() time.Time { return t0.Add(offset) }
|
|
|
|
|
defer func() { nowFunc = time.Now }()
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t, "magicquery")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
2017-08-05 06:04:19 -04:00
|
|
|
driver := db.Driver().(*fakeDriver)
|
2015-03-03 21:27:07 +09:00
|
|
|
|
|
|
|
|
// Force the number of open connections to 0 so we can get an accurate
|
|
|
|
|
// count for the test
|
|
|
|
|
db.clearAllConns(t)
|
|
|
|
|
|
|
|
|
|
driver.mu.Lock()
|
|
|
|
|
opens0 := driver.openCount
|
|
|
|
|
closes0 := driver.closeCount
|
|
|
|
|
driver.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
db.SetMaxIdleConns(10)
|
|
|
|
|
db.SetMaxOpenConns(10)
|
|
|
|
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
offset = time.Second
|
|
|
|
|
tx2, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
|
tx2.Commit()
|
|
|
|
|
|
|
|
|
|
driver.mu.Lock()
|
|
|
|
|
opens := driver.openCount - opens0
|
|
|
|
|
closes := driver.closeCount - closes0
|
|
|
|
|
driver.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if opens != 2 {
|
|
|
|
|
t.Errorf("opens = %d; want 2", opens)
|
|
|
|
|
}
|
|
|
|
|
if closes != 0 {
|
|
|
|
|
t.Errorf("closes = %d; want 0", closes)
|
|
|
|
|
}
|
|
|
|
|
if g, w := db.numFreeConns(), 2; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expire first conn
|
2017-02-09 12:32:33 +09:00
|
|
|
offset = 11 * time.Second
|
|
|
|
|
db.SetConnMaxLifetime(10 * time.Second)
|
2015-03-03 21:27:07 +09:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx, err = db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
tx2, err = db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
tx.Commit()
|
|
|
|
|
tx2.Commit()
|
|
|
|
|
|
|
|
|
|
driver.mu.Lock()
|
|
|
|
|
opens = driver.openCount - opens0
|
|
|
|
|
closes = driver.closeCount - closes0
|
|
|
|
|
driver.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if opens != 3 {
|
|
|
|
|
t.Errorf("opens = %d; want 3", opens)
|
|
|
|
|
}
|
|
|
|
|
if closes != 1 {
|
|
|
|
|
t.Errorf("closes = %d; want 1", closes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
// golang.org/issue/5323
|
|
|
|
|
func TestStmtCloseDeps(t *testing.T) {
|
|
|
|
|
if testing.Short() {
|
|
|
|
|
t.Skip("skipping in short mode")
|
|
|
|
|
}
|
2013-03-25 16:50:27 -07:00
|
|
|
defer setHookpostCloseConn(nil)
|
|
|
|
|
setHookpostCloseConn(func(_ *fakeConn, err error) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Error closing fakeConn: %v", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
db := newTestDB(t, "magicquery")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
2017-08-05 06:04:19 -04:00
|
|
|
driver := db.Driver().(*fakeDriver)
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
|
|
|
|
|
driver.mu.Lock()
|
|
|
|
|
opens0 := driver.openCount
|
|
|
|
|
closes0 := driver.closeCount
|
|
|
|
|
driver.mu.Unlock()
|
|
|
|
|
openDelta0 := opens0 - closes0
|
|
|
|
|
|
|
|
|
|
stmt, err := db.Prepare("SELECT|magicquery|op|op=?,millis=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start 50 parallel slow queries.
|
|
|
|
|
const (
|
|
|
|
|
nquery = 50
|
|
|
|
|
sleepMillis = 25
|
|
|
|
|
nbatch = 2
|
|
|
|
|
)
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
for batch := 0; batch < nbatch; batch++ {
|
|
|
|
|
for i := 0; i < nquery; i++ {
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
var op string
|
|
|
|
|
if err := stmt.QueryRow("sleep", sleepMillis).Scan(&op); err != nil && err != ErrNoRows {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
// Sleep for twice the expected length of time for the
|
|
|
|
|
// batch of 50 queries above to finish before starting
|
|
|
|
|
// the next round.
|
|
|
|
|
time.Sleep(2 * sleepMillis * time.Millisecond)
|
|
|
|
|
}
|
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
|
|
if g, w := db.numFreeConns(), 2; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n := db.numDepsPollUntil(4, time.Second); n > 4 {
|
|
|
|
|
t.Errorf("number of dependencies = %d; expected <= 4", n)
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
driver.mu.Lock()
|
|
|
|
|
opens := driver.openCount - opens0
|
|
|
|
|
closes := driver.closeCount - closes0
|
2013-08-29 17:26:00 -07:00
|
|
|
openDelta := (driver.openCount - driver.closeCount) - openDelta0
|
2013-08-30 09:27:33 -07:00
|
|
|
driver.mu.Unlock()
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
|
|
|
|
|
if openDelta > 2 {
|
|
|
|
|
t.Logf("open calls = %d", opens)
|
|
|
|
|
t.Logf("close calls = %d", closes)
|
|
|
|
|
t.Logf("open delta = %d", openDelta)
|
|
|
|
|
t.Errorf("db connections opened = %d; want <= 2", openDelta)
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 17:12:50 -08:00
|
|
|
if !waitCondition(5*time.Second, 5*time.Millisecond, func() bool {
|
|
|
|
|
return len(stmt.css) <= nquery
|
|
|
|
|
}) {
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
t.Errorf("len(stmt.css) = %d; want <= %d", len(stmt.css), nquery)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := stmt.Close(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if g, w := db.numFreeConns(), 2; g != w {
|
|
|
|
|
t.Errorf("free conns = %d; want %d", g, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n := db.numDepsPollUntil(2, time.Second); n > 2 {
|
|
|
|
|
t.Errorf("number of dependencies = %d; expected <= 2", n)
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-03 21:27:07 +09:00
|
|
|
db.clearAllConns(t)
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// golang.org/issue/5046
|
|
|
|
|
func TestCloseConnBeforeStmts(t *testing.T) {
|
2013-03-25 16:50:27 -07:00
|
|
|
db := newTestDB(t, "people")
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
defer setHookpostCloseConn(nil)
|
|
|
|
|
setHookpostCloseConn(func(_ *fakeConn, err error) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Error closing fakeConn: %v; from %s", err, stack())
|
|
|
|
|
db.dumpDeps(t)
|
|
|
|
|
t.Errorf("DB = %#v", db)
|
|
|
|
|
}
|
|
|
|
|
})
|
2013-03-25 16:50:27 -07:00
|
|
|
|
|
|
|
|
stmt, err := db.Prepare("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-28 08:49:56 -07:00
|
|
|
if len(db.freeConn) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 freeConn; got %d", len(db.freeConn))
|
2013-03-25 16:50:27 -07:00
|
|
|
}
|
2014-08-28 08:49:56 -07:00
|
|
|
dc := db.freeConn[0]
|
2013-03-25 16:50:27 -07:00
|
|
|
if dc.closed {
|
|
|
|
|
t.Errorf("conn shouldn't be closed")
|
|
|
|
|
}
|
|
|
|
|
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
if n := len(dc.openStmt); n != 1 {
|
|
|
|
|
t.Errorf("driverConn num openStmt = %d; want 1", n)
|
|
|
|
|
}
|
2013-03-25 16:50:27 -07:00
|
|
|
err = db.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("db Close = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !dc.closed {
|
|
|
|
|
t.Errorf("after db.Close, driverConn should be closed")
|
|
|
|
|
}
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 14:45:56 -07:00
|
|
|
if n := len(dc.openStmt); n != 0 {
|
|
|
|
|
t.Errorf("driverConn num openStmt = %d; want 0", n)
|
2013-03-25 16:50:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = stmt.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Stmt close = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !dc.closed {
|
|
|
|
|
t.Errorf("conn should be closed")
|
|
|
|
|
}
|
|
|
|
|
if dc.ci != nil {
|
|
|
|
|
t.Errorf("after Stmt Close, driverConn's Conn interface should be nil")
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-04-03 11:13:40 -07:00
|
|
|
|
2013-04-15 14:06:41 -07:00
|
|
|
// golang.org/issue/5283: don't release the Rows' connection in Close
|
|
|
|
|
// before calling Stmt.Close.
|
|
|
|
|
func TestRowsCloseOrder(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
db.SetMaxIdleConns(0)
|
|
|
|
|
setStrictFakeConnClose(t)
|
|
|
|
|
defer setStrictFakeConnClose(nil)
|
|
|
|
|
|
|
|
|
|
rows, err := db.Query("SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
err = rows.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-16 11:23:35 +10:00
|
|
|
func TestRowsImplicitClose(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
rows, err := db.Query("SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
want, fail := 2, errors.New("fail")
|
|
|
|
|
r := rows.rowsi.(*rowsCursor)
|
|
|
|
|
r.errPos, r.err = want, fail
|
|
|
|
|
|
|
|
|
|
got := 0
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
got++
|
|
|
|
|
}
|
|
|
|
|
if got != want {
|
|
|
|
|
t.Errorf("got %d rows, want %d", got, want)
|
|
|
|
|
}
|
|
|
|
|
if err := rows.Err(); err != fail {
|
|
|
|
|
t.Errorf("got error %v, want %v", err, fail)
|
|
|
|
|
}
|
|
|
|
|
if !r.closed {
|
|
|
|
|
t.Errorf("r.closed is false, want true")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-23 14:09:53 +10:00
|
|
|
func TestStmtCloseOrder(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
db.SetMaxIdleConns(0)
|
|
|
|
|
setStrictFakeConnClose(t)
|
|
|
|
|
defer setStrictFakeConnClose(nil)
|
|
|
|
|
|
|
|
|
|
_, err := db.Query("SELECT|non_existent|name|")
|
|
|
|
|
if err == nil {
|
2016-02-24 11:55:20 +01:00
|
|
|
t.Fatal("Querying non-existent table should fail")
|
2013-07-23 14:09:53 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-27 19:45:12 +01:00
|
|
|
// Test cases where there's more than maxBadConnRetries bad connections in the
|
|
|
|
|
// pool (issue 8834)
|
|
|
|
|
func TestManyErrBadConn(t *testing.T) {
|
2017-05-21 19:51:46 -07:00
|
|
|
manyErrBadConnSetup := func(first ...func(db *DB)) *DB {
|
2015-03-27 19:45:12 +01:00
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
|
2017-05-21 19:51:46 -07:00
|
|
|
for _, f := range first {
|
|
|
|
|
f(db)
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-27 19:45:12 +01:00
|
|
|
nconn := maxBadConnRetries + 1
|
|
|
|
|
db.SetMaxIdleConns(nconn)
|
|
|
|
|
db.SetMaxOpenConns(nconn)
|
|
|
|
|
// open enough connections
|
|
|
|
|
func() {
|
|
|
|
|
for i := 0; i < nconn; i++ {
|
|
|
|
|
rows, err := db.Query("SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer rows.Close()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2016-06-28 12:06:08 -07:00
|
|
|
db.mu.Lock()
|
|
|
|
|
defer db.mu.Unlock()
|
2015-03-27 19:45:12 +01:00
|
|
|
if db.numOpen != nconn {
|
|
|
|
|
t.Fatalf("unexpected numOpen %d (was expecting %d)", db.numOpen, nconn)
|
|
|
|
|
} else if len(db.freeConn) != nconn {
|
|
|
|
|
t.Fatalf("unexpected len(db.freeConn) %d (was expecting %d)", len(db.freeConn), nconn)
|
|
|
|
|
}
|
|
|
|
|
for _, conn := range db.freeConn {
|
2017-10-02 11:16:53 -07:00
|
|
|
conn.Lock()
|
2015-03-27 19:45:12 +01:00
|
|
|
conn.ci.(*fakeConn).stickyBad = true
|
2017-10-02 11:16:53 -07:00
|
|
|
conn.Unlock()
|
2015-03-27 19:45:12 +01:00
|
|
|
}
|
|
|
|
|
return db
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Query
|
|
|
|
|
db := manyErrBadConnSetup()
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
rows, err := db.Query("SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err = rows.Close(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Exec
|
|
|
|
|
db = manyErrBadConnSetup()
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
_, err = db.Exec("INSERT|people|name=Julia,age=19")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Begin
|
|
|
|
|
db = manyErrBadConnSetup()
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err = tx.Rollback(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepare
|
|
|
|
|
db = manyErrBadConnSetup()
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
stmt, err := db.Prepare("SELECT|people|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err = stmt.Close(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
database/sql: allow using a single connection from the database
Databases have the following concepts: Statement, Batch, and Session.
A statement is often a single line like:
SELECT Amount from Account where ID = 50;
A batch is one or more statements submitted together for the query
to process. It may be a DELETE, INSERT, two UPDATES and a SELECT in
a single query text.
A session is usually represented by a single database connection.
This often is an issue when dealing with scopes in databases.
Temporary tables and variables can have batch, session, or global
scope depending on the syntax, database, and use.
Furthermore, some databases (sybase and derivatives in perticular)
that prevent certain statements from being in the same batch
and may necessitate being in the same session.
By allowing users to extract a Conn from the database they can manage
session on their own without hacking around it by making connection
pools of single connections (a real workaround presented in issue).
It is tempting to just use a transaction, but this isn't always
desirable or an option if running an interactive session or
alter script set that itself starts transactions.
Fixes #18081
Change-Id: I9bdf0796632c48d4bcaef3624c629641984ffaf2
Reviewed-on: https://go-review.googlesource.com/40694
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-04-13 16:04:40 -07:00
|
|
|
|
2017-05-21 19:51:46 -07:00
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
database/sql: allow using a single connection from the database
Databases have the following concepts: Statement, Batch, and Session.
A statement is often a single line like:
SELECT Amount from Account where ID = 50;
A batch is one or more statements submitted together for the query
to process. It may be a DELETE, INSERT, two UPDATES and a SELECT in
a single query text.
A session is usually represented by a single database connection.
This often is an issue when dealing with scopes in databases.
Temporary tables and variables can have batch, session, or global
scope depending on the syntax, database, and use.
Furthermore, some databases (sybase and derivatives in perticular)
that prevent certain statements from being in the same batch
and may necessitate being in the same session.
By allowing users to extract a Conn from the database they can manage
session on their own without hacking around it by making connection
pools of single connections (a real workaround presented in issue).
It is tempting to just use a transaction, but this isn't always
desirable or an option if running an interactive session or
alter script set that itself starts transactions.
Fixes #18081
Change-Id: I9bdf0796632c48d4bcaef3624c629641984ffaf2
Reviewed-on: https://go-review.googlesource.com/40694
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-04-13 16:04:40 -07:00
|
|
|
// 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)
|
|
|
|
|
}
|
2017-10-02 11:16:53 -07:00
|
|
|
conn.dc.ci.(*fakeConn).skipDirtySession = true
|
database/sql: allow using a single connection from the database
Databases have the following concepts: Statement, Batch, and Session.
A statement is often a single line like:
SELECT Amount from Account where ID = 50;
A batch is one or more statements submitted together for the query
to process. It may be a DELETE, INSERT, two UPDATES and a SELECT in
a single query text.
A session is usually represented by a single database connection.
This often is an issue when dealing with scopes in databases.
Temporary tables and variables can have batch, session, or global
scope depending on the syntax, database, and use.
Furthermore, some databases (sybase and derivatives in perticular)
that prevent certain statements from being in the same batch
and may necessitate being in the same session.
By allowing users to extract a Conn from the database they can manage
session on their own without hacking around it by making connection
pools of single connections (a real workaround presented in issue).
It is tempting to just use a transaction, but this isn't always
desirable or an option if running an interactive session or
alter script set that itself starts transactions.
Fixes #18081
Change-Id: I9bdf0796632c48d4bcaef3624c629641984ffaf2
Reviewed-on: https://go-review.googlesource.com/40694
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-04-13 16:04:40 -07:00
|
|
|
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)
|
|
|
|
|
}
|
2015-03-27 19:45:12 +01:00
|
|
|
}
|
|
|
|
|
|
2017-06-05 09:04:05 -07:00
|
|
|
// 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")
|
2017-06-10 22:02:53 -07:00
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
2017-06-05 09:04:05 -07:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-10 22:02:53 -07:00
|
|
|
// 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()
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-05 17:02:09 +10:00
|
|
|
// golang.org/issue/5718
|
2013-12-17 11:57:30 -08:00
|
|
|
func TestErrBadConnReconnect(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "foo")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
|
|
|
|
|
|
|
|
|
|
simulateBadConn := func(name string, hook *func() bool, op func() error) {
|
|
|
|
|
broken, retried := false, false
|
|
|
|
|
numOpen := db.numOpen
|
|
|
|
|
|
|
|
|
|
// simulate a broken connection on the first try
|
|
|
|
|
*hook = func() bool {
|
|
|
|
|
if !broken {
|
|
|
|
|
broken = true
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
retried = true
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := op(); err != nil {
|
|
|
|
|
t.Errorf(name+": %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !broken || !retried {
|
|
|
|
|
t.Error(name + ": Failed to simulate broken connection")
|
|
|
|
|
}
|
|
|
|
|
*hook = nil
|
|
|
|
|
|
|
|
|
|
if numOpen != db.numOpen {
|
|
|
|
|
t.Errorf(name+": leaked %d connection(s)!", db.numOpen-numOpen)
|
|
|
|
|
numOpen = db.numOpen
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// db.Exec
|
|
|
|
|
dbExec := func() error {
|
|
|
|
|
_, err := db.Exec("INSERT|t1|name=?,age=?,dead=?", "Gordon", 3, true)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
simulateBadConn("db.Exec prepare", &hookPrepareBadConn, dbExec)
|
|
|
|
|
simulateBadConn("db.Exec exec", &hookExecBadConn, dbExec)
|
|
|
|
|
|
|
|
|
|
// db.Query
|
|
|
|
|
dbQuery := func() error {
|
|
|
|
|
rows, err := db.Query("SELECT|t1|age,name|")
|
|
|
|
|
if err == nil {
|
|
|
|
|
err = rows.Close()
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
simulateBadConn("db.Query prepare", &hookPrepareBadConn, dbQuery)
|
|
|
|
|
simulateBadConn("db.Query query", &hookQueryBadConn, dbQuery)
|
|
|
|
|
|
|
|
|
|
// db.Prepare
|
|
|
|
|
simulateBadConn("db.Prepare", &hookPrepareBadConn, func() error {
|
|
|
|
|
stmt, err := db.Prepare("INSERT|t1|name=?,age=?,dead=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
stmt.Close()
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
|
2014-09-02 09:08:41 -07:00
|
|
|
// Provide a way to force a re-prepare of a statement on next execution
|
|
|
|
|
forcePrepare := func(stmt *Stmt) {
|
|
|
|
|
stmt.css = nil
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-17 11:57:30 -08:00
|
|
|
// stmt.Exec
|
|
|
|
|
stmt1, err := db.Prepare("INSERT|t1|name=?,age=?,dead=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("prepare: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer stmt1.Close()
|
|
|
|
|
// make sure we must prepare the stmt first
|
2014-09-02 09:08:41 -07:00
|
|
|
forcePrepare(stmt1)
|
2013-12-17 11:57:30 -08:00
|
|
|
|
|
|
|
|
stmtExec := func() error {
|
|
|
|
|
_, err := stmt1.Exec("Gopher", 3, false)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
simulateBadConn("stmt.Exec prepare", &hookPrepareBadConn, stmtExec)
|
|
|
|
|
simulateBadConn("stmt.Exec exec", &hookExecBadConn, stmtExec)
|
|
|
|
|
|
|
|
|
|
// stmt.Query
|
|
|
|
|
stmt2, err := db.Prepare("SELECT|t1|age,name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("prepare: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer stmt2.Close()
|
|
|
|
|
// make sure we must prepare the stmt first
|
2014-09-02 09:08:41 -07:00
|
|
|
forcePrepare(stmt2)
|
2013-12-17 11:57:30 -08:00
|
|
|
|
|
|
|
|
stmtQuery := func() error {
|
|
|
|
|
rows, err := stmt2.Query()
|
|
|
|
|
if err == nil {
|
|
|
|
|
err = rows.Close()
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
simulateBadConn("stmt.Query prepare", &hookPrepareBadConn, stmtQuery)
|
|
|
|
|
simulateBadConn("stmt.Query exec", &hookQueryBadConn, stmtQuery)
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-24 21:48:39 -04:00
|
|
|
// golang.org/issue/11264
|
|
|
|
|
func TestTxEndBadConn(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "foo")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
db.SetMaxIdleConns(0)
|
|
|
|
|
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
|
|
|
|
|
db.SetMaxIdleConns(1)
|
|
|
|
|
|
|
|
|
|
simulateBadConn := func(name string, hook *func() bool, op func() error) {
|
|
|
|
|
broken := false
|
|
|
|
|
numOpen := db.numOpen
|
|
|
|
|
|
|
|
|
|
*hook = func() bool {
|
|
|
|
|
if !broken {
|
|
|
|
|
broken = true
|
|
|
|
|
}
|
|
|
|
|
return broken
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := op(); err != driver.ErrBadConn {
|
|
|
|
|
t.Errorf(name+": %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !broken {
|
|
|
|
|
t.Error(name + ": Failed to simulate broken connection")
|
|
|
|
|
}
|
|
|
|
|
*hook = nil
|
|
|
|
|
|
|
|
|
|
if numOpen != db.numOpen {
|
|
|
|
|
t.Errorf(name+": leaked %d connection(s)!", db.numOpen-numOpen)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// db.Exec
|
|
|
|
|
dbExec := func(endTx func(tx *Tx) error) func() error {
|
|
|
|
|
return func() error {
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
_, err = tx.Exec("INSERT|t1|name=?,age=?,dead=?", "Gordon", 3, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return endTx(tx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
simulateBadConn("db.Tx.Exec commit", &hookCommitBadConn, dbExec((*Tx).Commit))
|
|
|
|
|
simulateBadConn("db.Tx.Exec rollback", &hookRollbackBadConn, dbExec((*Tx).Rollback))
|
|
|
|
|
|
|
|
|
|
// db.Query
|
|
|
|
|
dbQuery := func(endTx func(tx *Tx) error) func() error {
|
|
|
|
|
return func() error {
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
rows, err := tx.Query("SELECT|t1|age,name|")
|
|
|
|
|
if err == nil {
|
|
|
|
|
err = rows.Close()
|
|
|
|
|
} else {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return endTx(tx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
simulateBadConn("db.Tx.Query commit", &hookCommitBadConn, dbQuery((*Tx).Commit))
|
|
|
|
|
simulateBadConn("db.Tx.Query rollback", &hookRollbackBadConn, dbQuery((*Tx).Rollback))
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-30 09:27:33 -07:00
|
|
|
type concurrentTest interface {
|
|
|
|
|
init(t testing.TB, db *DB)
|
|
|
|
|
finish(t testing.TB)
|
|
|
|
|
test(t testing.TB) error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type concurrentDBQueryTest struct {
|
|
|
|
|
db *DB
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentDBQueryTest) init(t testing.TB, db *DB) {
|
|
|
|
|
c.db = db
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentDBQueryTest) finish(t testing.TB) {
|
|
|
|
|
c.db = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentDBQueryTest) test(t testing.TB) error {
|
|
|
|
|
rows, err := c.db.Query("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var name string
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
rows.Scan(&name)
|
|
|
|
|
}
|
|
|
|
|
rows.Close()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type concurrentDBExecTest struct {
|
|
|
|
|
db *DB
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentDBExecTest) init(t testing.TB, db *DB) {
|
|
|
|
|
c.db = db
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentDBExecTest) finish(t testing.TB) {
|
|
|
|
|
c.db = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentDBExecTest) test(t testing.TB) error {
|
|
|
|
|
_, err := c.db.Exec("NOSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?", 3, chrisBirthday)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type concurrentStmtQueryTest struct {
|
|
|
|
|
db *DB
|
|
|
|
|
stmt *Stmt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentStmtQueryTest) init(t testing.TB, db *DB) {
|
|
|
|
|
c.db = db
|
|
|
|
|
var err error
|
|
|
|
|
c.stmt, err = db.Prepare("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentStmtQueryTest) finish(t testing.TB) {
|
|
|
|
|
if c.stmt != nil {
|
|
|
|
|
c.stmt.Close()
|
|
|
|
|
c.stmt = nil
|
|
|
|
|
}
|
|
|
|
|
c.db = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentStmtQueryTest) test(t testing.TB) error {
|
|
|
|
|
rows, err := c.stmt.Query()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("error on query: %v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var name string
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
rows.Scan(&name)
|
|
|
|
|
}
|
|
|
|
|
rows.Close()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type concurrentStmtExecTest struct {
|
|
|
|
|
db *DB
|
|
|
|
|
stmt *Stmt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentStmtExecTest) init(t testing.TB, db *DB) {
|
|
|
|
|
c.db = db
|
|
|
|
|
var err error
|
|
|
|
|
c.stmt, err = db.Prepare("NOSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentStmtExecTest) finish(t testing.TB) {
|
|
|
|
|
if c.stmt != nil {
|
|
|
|
|
c.stmt.Close()
|
|
|
|
|
c.stmt = nil
|
|
|
|
|
}
|
|
|
|
|
c.db = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentStmtExecTest) test(t testing.TB) error {
|
|
|
|
|
_, err := c.stmt.Exec(3, chrisBirthday)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("error on exec: %v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type concurrentTxQueryTest struct {
|
|
|
|
|
db *DB
|
|
|
|
|
tx *Tx
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxQueryTest) init(t testing.TB, db *DB) {
|
|
|
|
|
c.db = db
|
|
|
|
|
var err error
|
|
|
|
|
c.tx, err = c.db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxQueryTest) finish(t testing.TB) {
|
|
|
|
|
if c.tx != nil {
|
|
|
|
|
c.tx.Rollback()
|
|
|
|
|
c.tx = nil
|
|
|
|
|
}
|
|
|
|
|
c.db = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxQueryTest) test(t testing.TB) error {
|
|
|
|
|
rows, err := c.db.Query("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var name string
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
rows.Scan(&name)
|
|
|
|
|
}
|
|
|
|
|
rows.Close()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type concurrentTxExecTest struct {
|
|
|
|
|
db *DB
|
|
|
|
|
tx *Tx
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxExecTest) init(t testing.TB, db *DB) {
|
|
|
|
|
c.db = db
|
|
|
|
|
var err error
|
|
|
|
|
c.tx, err = c.db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxExecTest) finish(t testing.TB) {
|
|
|
|
|
if c.tx != nil {
|
|
|
|
|
c.tx.Rollback()
|
|
|
|
|
c.tx = nil
|
|
|
|
|
}
|
|
|
|
|
c.db = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxExecTest) test(t testing.TB) error {
|
|
|
|
|
_, err := c.tx.Exec("NOSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?", 3, chrisBirthday)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type concurrentTxStmtQueryTest struct {
|
|
|
|
|
db *DB
|
|
|
|
|
tx *Tx
|
|
|
|
|
stmt *Stmt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxStmtQueryTest) init(t testing.TB, db *DB) {
|
|
|
|
|
c.db = db
|
|
|
|
|
var err error
|
|
|
|
|
c.tx, err = c.db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
c.stmt, err = c.tx.Prepare("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxStmtQueryTest) finish(t testing.TB) {
|
|
|
|
|
if c.stmt != nil {
|
|
|
|
|
c.stmt.Close()
|
|
|
|
|
c.stmt = nil
|
|
|
|
|
}
|
|
|
|
|
if c.tx != nil {
|
|
|
|
|
c.tx.Rollback()
|
|
|
|
|
c.tx = nil
|
|
|
|
|
}
|
|
|
|
|
c.db = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxStmtQueryTest) test(t testing.TB) error {
|
|
|
|
|
rows, err := c.stmt.Query()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("error on query: %v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var name string
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
rows.Scan(&name)
|
|
|
|
|
}
|
|
|
|
|
rows.Close()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type concurrentTxStmtExecTest struct {
|
|
|
|
|
db *DB
|
|
|
|
|
tx *Tx
|
|
|
|
|
stmt *Stmt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxStmtExecTest) init(t testing.TB, db *DB) {
|
|
|
|
|
c.db = db
|
|
|
|
|
var err error
|
|
|
|
|
c.tx, err = c.db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
c.stmt, err = c.tx.Prepare("NOSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxStmtExecTest) finish(t testing.TB) {
|
|
|
|
|
if c.stmt != nil {
|
|
|
|
|
c.stmt.Close()
|
|
|
|
|
c.stmt = nil
|
|
|
|
|
}
|
|
|
|
|
if c.tx != nil {
|
|
|
|
|
c.tx.Rollback()
|
|
|
|
|
c.tx = nil
|
|
|
|
|
}
|
|
|
|
|
c.db = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentTxStmtExecTest) test(t testing.TB) error {
|
|
|
|
|
_, err := c.stmt.Exec(3, chrisBirthday)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("error on exec: %v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type concurrentRandomTest struct {
|
|
|
|
|
tests []concurrentTest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentRandomTest) init(t testing.TB, db *DB) {
|
|
|
|
|
c.tests = []concurrentTest{
|
|
|
|
|
new(concurrentDBQueryTest),
|
|
|
|
|
new(concurrentDBExecTest),
|
|
|
|
|
new(concurrentStmtQueryTest),
|
|
|
|
|
new(concurrentStmtExecTest),
|
|
|
|
|
new(concurrentTxQueryTest),
|
|
|
|
|
new(concurrentTxExecTest),
|
2017-06-12 11:28:18 -07:00
|
|
|
new(concurrentTxStmtQueryTest),
|
|
|
|
|
new(concurrentTxStmtExecTest),
|
2013-08-30 09:27:33 -07:00
|
|
|
}
|
|
|
|
|
for _, ct := range c.tests {
|
|
|
|
|
ct.init(t, db)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentRandomTest) finish(t testing.TB) {
|
|
|
|
|
for _, ct := range c.tests {
|
|
|
|
|
ct.finish(t)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *concurrentRandomTest) test(t testing.TB) error {
|
|
|
|
|
ct := c.tests[rand.Intn(len(c.tests))]
|
|
|
|
|
return ct.test(t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func doConcurrentTest(t testing.TB, ct concurrentTest) {
|
|
|
|
|
maxProcs, numReqs := 1, 500
|
|
|
|
|
if testing.Short() {
|
|
|
|
|
maxProcs, numReqs = 4, 50
|
|
|
|
|
}
|
|
|
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs))
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
|
|
|
|
ct.init(t, db)
|
|
|
|
|
defer ct.finish(t)
|
|
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
wg.Add(numReqs)
|
|
|
|
|
|
|
|
|
|
reqs := make(chan bool)
|
|
|
|
|
defer close(reqs)
|
|
|
|
|
|
|
|
|
|
for i := 0; i < maxProcs*2; i++ {
|
|
|
|
|
go func() {
|
2014-07-16 16:29:51 -07:00
|
|
|
for range reqs {
|
2013-08-30 09:27:33 -07:00
|
|
|
err := ct.test(t)
|
|
|
|
|
if err != nil {
|
|
|
|
|
wg.Done()
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
wg.Done()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 0; i < numReqs; i++ {
|
|
|
|
|
reqs <- true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-13 14:56:40 -07:00
|
|
|
func TestIssue6081(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
|
2017-08-05 06:04:19 -04:00
|
|
|
drv := db.Driver().(*fakeDriver)
|
2013-08-13 14:56:40 -07:00
|
|
|
drv.mu.Lock()
|
|
|
|
|
opens0 := drv.openCount
|
|
|
|
|
closes0 := drv.closeCount
|
|
|
|
|
drv.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
stmt, err := db.Prepare("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2017-01-20 17:12:50 -08:00
|
|
|
setRowsCloseHook(func(rows *Rows, err *error) {
|
2013-08-13 14:56:40 -07:00
|
|
|
*err = driver.ErrBadConn
|
2017-01-20 17:12:50 -08:00
|
|
|
})
|
|
|
|
|
defer setRowsCloseHook(nil)
|
2013-08-13 14:56:40 -07:00
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
|
rows, err := stmt.Query()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
rows.Close()
|
|
|
|
|
}
|
|
|
|
|
if n := len(stmt.css); n > 1 {
|
|
|
|
|
t.Errorf("len(css slice) = %d; want <= 1", n)
|
|
|
|
|
}
|
|
|
|
|
stmt.Close()
|
|
|
|
|
if n := len(stmt.css); n != 0 {
|
|
|
|
|
t.Errorf("len(css slice) after Close = %d; want 0", n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drv.mu.Lock()
|
|
|
|
|
opens := drv.openCount - opens0
|
|
|
|
|
closes := drv.closeCount - closes0
|
|
|
|
|
drv.mu.Unlock()
|
|
|
|
|
if opens < 9 {
|
|
|
|
|
t.Errorf("opens = %d; want >= 9", opens)
|
|
|
|
|
}
|
|
|
|
|
if closes < 9 {
|
|
|
|
|
t.Errorf("closes = %d; want >= 9", closes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-02 13:19:31 -08:00
|
|
|
// TestIssue18429 attempts to stress rolling back the transaction from a
|
|
|
|
|
// context cancel while simultaneously calling Tx.Rollback. Rolling back from a
|
|
|
|
|
// context happens concurrently so tx.rollback and tx.Commit must guard against
|
|
|
|
|
// double entry.
|
2016-12-26 11:33:46 -08:00
|
|
|
//
|
2017-01-02 13:19:31 -08:00
|
|
|
// In the test, a context is canceled while the query is in process so
|
|
|
|
|
// the internal rollback will run concurrently with the explicitly called
|
2016-12-26 11:33:46 -08:00
|
|
|
// Tx.Rollback.
|
2017-10-17 15:59:56 -07:00
|
|
|
//
|
|
|
|
|
// The addition of calling rows.Next also tests
|
|
|
|
|
// Issue 21117.
|
2016-12-26 11:33:46 -08:00
|
|
|
func TestIssue18429(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
|
|
|
|
|
|
2017-10-17 22:04:17 +00:00
|
|
|
for i := 0; i < 100; i++ {
|
2016-12-26 11:33:46 -08:00
|
|
|
sem <- true
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
<-sem
|
|
|
|
|
wg.Done()
|
|
|
|
|
}()
|
|
|
|
|
qwait := (time.Duration(rand.Intn(milliWait)) * time.Millisecond).String()
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, time.Duration(rand.Intn(milliWait))*time.Millisecond)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
tx, err := db.BeginTx(ctx, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-01-26 13:41:56 -08:00
|
|
|
// This is expected to give a cancel error most, but not all the time.
|
2017-01-20 17:12:50 -08:00
|
|
|
// Test failure will happen with a panic or other race condition being
|
|
|
|
|
// reported.
|
|
|
|
|
rows, _ := tx.QueryContext(ctx, "WAIT|"+qwait+"|SELECT|people|name|")
|
2016-12-26 11:33:46 -08:00
|
|
|
if rows != nil {
|
2018-01-25 10:50:02 -08:00
|
|
|
var name string
|
2017-10-17 15:59:56 -07:00
|
|
|
// Call Next to test Issue 21117 and check for races.
|
|
|
|
|
for rows.Next() {
|
2018-01-25 10:50:02 -08:00
|
|
|
// Scan the buffer so it is read and checked for races.
|
|
|
|
|
rows.Scan(&name)
|
2017-10-17 15:59:56 -07:00
|
|
|
}
|
2016-12-26 11:33:46 -08:00
|
|
|
rows.Close()
|
|
|
|
|
}
|
|
|
|
|
// This call will race with the context cancel rollback to complete
|
|
|
|
|
// if the rollback itself isn't guarded.
|
|
|
|
|
tx.Rollback()
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
wg.Wait()
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-28 14:24:31 -07:00
|
|
|
// 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()
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 17:12:50 -08:00
|
|
|
// 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.
|
|
|
|
|
//
|
|
|
|
|
// See https://golang.org/cl/35550 .
|
|
|
|
|
func TestIssue18719(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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hookTxGrabConn = func() {
|
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
|
|
// Wait for the context to cancel and tx to rollback.
|
|
|
|
|
for tx.isDone() == false {
|
2017-02-09 12:32:33 +09:00
|
|
|
time.Sleep(3 * time.Millisecond)
|
2017-01-20 17:12:50 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
defer func() { hookTxGrabConn = nil }()
|
|
|
|
|
|
|
|
|
|
// This call will grab the connection and cancel the context
|
|
|
|
|
// after it has done so. Code after must deal with the canceled state.
|
2017-10-02 11:16:53 -07:00
|
|
|
_, err = tx.QueryContext(ctx, "SELECT|people|name|")
|
2017-01-20 17:12:50 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("expected error %v but got %v", nil, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rows may be ignored because it will be closed when the context is canceled.
|
|
|
|
|
|
|
|
|
|
// Do not explicitly rollback. The rollback will happen from the
|
|
|
|
|
// canceled context.
|
|
|
|
|
|
2017-02-12 15:12:52 -08:00
|
|
|
cancel()
|
2017-01-20 17:12:50 -08:00
|
|
|
}
|
|
|
|
|
|
2017-06-12 10:46:15 -07:00
|
|
|
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)
|
|
|
|
|
}
|
2017-10-02 11:16:53 -07:00
|
|
|
conn.dc.ci.(*fakeConn).skipDirtySession = true
|
2017-06-12 10:46:15 -07:00
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-03 11:13:40 -07:00
|
|
|
func TestConcurrency(t *testing.T) {
|
2017-06-10 22:02:53 -07:00
|
|
|
list := []struct {
|
|
|
|
|
name string
|
|
|
|
|
ct concurrentTest
|
|
|
|
|
}{
|
|
|
|
|
{"Query", new(concurrentDBQueryTest)},
|
|
|
|
|
{"Exec", new(concurrentDBExecTest)},
|
|
|
|
|
{"StmtQuery", new(concurrentStmtQueryTest)},
|
|
|
|
|
{"StmtExec", new(concurrentStmtExecTest)},
|
|
|
|
|
{"TxQuery", new(concurrentTxQueryTest)},
|
|
|
|
|
{"TxExec", new(concurrentTxExecTest)},
|
2017-06-12 11:28:18 -07:00
|
|
|
{"TxStmtQuery", new(concurrentTxStmtQueryTest)},
|
|
|
|
|
{"TxStmtExec", new(concurrentTxStmtExecTest)},
|
2017-06-10 22:02:53 -07:00
|
|
|
{"Random", new(concurrentRandomTest)},
|
|
|
|
|
}
|
|
|
|
|
for _, item := range list {
|
|
|
|
|
t.Run(item.name, func(t *testing.T) {
|
|
|
|
|
doConcurrentTest(t, item.ct)
|
|
|
|
|
})
|
|
|
|
|
}
|
2013-08-30 09:27:33 -07:00
|
|
|
}
|
|
|
|
|
|
2013-10-16 09:22:57 -07:00
|
|
|
func TestConnectionLeak(t *testing.T) {
|
|
|
|
|
db := newTestDB(t, "people")
|
|
|
|
|
defer closeDB(t, db)
|
|
|
|
|
// Start by opening defaultMaxIdleConns
|
|
|
|
|
rows := make([]*Rows, defaultMaxIdleConns)
|
|
|
|
|
// We need to SetMaxOpenConns > MaxIdleConns, so the DB can open
|
|
|
|
|
// a new connection and we can fill the idle queue with the released
|
|
|
|
|
// connections.
|
|
|
|
|
db.SetMaxOpenConns(len(rows) + 1)
|
|
|
|
|
for ii := range rows {
|
|
|
|
|
r, err := db.Query("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
r.Next()
|
|
|
|
|
if err := r.Err(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
rows[ii] = r
|
|
|
|
|
}
|
|
|
|
|
// Now we have defaultMaxIdleConns busy connections. Open
|
|
|
|
|
// a new one, but wait until the busy connections are released
|
|
|
|
|
// before returning control to DB.
|
2017-08-05 06:04:19 -04:00
|
|
|
drv := db.Driver().(*fakeDriver)
|
2013-10-16 09:22:57 -07:00
|
|
|
drv.waitCh = make(chan struct{}, 1)
|
|
|
|
|
drv.waitingCh = make(chan struct{}, 1)
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
r, err := db.Query("SELECT|people|name|")
|
|
|
|
|
if err != nil {
|
2016-11-14 21:34:58 -08:00
|
|
|
t.Error(err)
|
|
|
|
|
return
|
2013-10-16 09:22:57 -07:00
|
|
|
}
|
|
|
|
|
r.Close()
|
|
|
|
|
wg.Done()
|
|
|
|
|
}()
|
|
|
|
|
// Wait until the goroutine we've just created has started waiting.
|
|
|
|
|
<-drv.waitingCh
|
|
|
|
|
// Now close the busy connections. This provides a connection for
|
|
|
|
|
// the blocked goroutine and then fills up the idle queue.
|
|
|
|
|
for _, v := range rows {
|
|
|
|
|
v.Close()
|
|
|
|
|
}
|
|
|
|
|
// At this point we give the new connection to DB. This connection is
|
|
|
|
|
// now useless, since the idle queue is full and there are no pending
|
|
|
|
|
// requests. DB should deal with this situation without leaking the
|
|
|
|
|
// connection.
|
|
|
|
|
drv.waitCh <- struct{}{}
|
|
|
|
|
wg.Wait()
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-29 22:10:43 -07:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-23 13:17:59 -07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-26 12:49:23 -07:00
|
|
|
type decimalInt struct {
|
2017-03-23 13:17:59 -07:00
|
|
|
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:
|
2017-08-22 15:41:50 +05:30
|
|
|
return errors.New("unknown NameValueCheck OUTPUT type")
|
2017-03-23 13:17:59 -07:00
|
|
|
case *string:
|
|
|
|
|
*ov = "from-server"
|
|
|
|
|
nv.Value = "OUT:*string"
|
|
|
|
|
}
|
|
|
|
|
return nil
|
2019-04-26 12:49:23 -07:00
|
|
|
case decimalInt, []int64:
|
2017-03-23 13:17:59 -07:00
|
|
|
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 := ""
|
2019-04-26 12:49:23 -07:00
|
|
|
_, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A,str1=?,out1=?O1,array1=?", Named("A", decimalInt{123}), "hello", Named("O1", Out{Dest: &o1}), []int64{42, 128, 707}, doNotInclude{})
|
2017-03-23 13:17:59 -07:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal("exec insert", err)
|
|
|
|
|
}
|
|
|
|
|
var (
|
|
|
|
|
str1 string
|
2019-04-26 12:49:23 -07:00
|
|
|
dec1 decimalInt
|
2017-03-23 13:17:59 -07:00
|
|
|
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"},
|
2019-04-26 12:49:23 -07:00
|
|
|
{dec1, decimalInt{123}},
|
2017-03-23 13:17:59 -07:00
|
|
|
{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)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-26 12:49:23 -07:00
|
|
|
_, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A", Named("A", decimalInt{123}))
|
2017-03-23 13:17:59 -07:00
|
|
|
if err == nil {
|
|
|
|
|
t.Fatalf("expected error with bad argument, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-14 08:53:56 -08:00
|
|
|
func TestOpenConnector(t *testing.T) {
|
|
|
|
|
Register("testctx", &fakeDriverCtx{})
|
|
|
|
|
db, err := Open("testctx", "people")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
|
|
if _, is := db.connector.(*fakeConnector); !is {
|
|
|
|
|
t.Fatal("not using *fakeConnector")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-23 19:38:32 -07:00
|
|
|
type ctxOnlyDriver struct {
|
|
|
|
|
fakeDriver
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *ctxOnlyDriver) Open(dsn string) (driver.Conn, error) {
|
|
|
|
|
conn, err := d.fakeDriver.Open(dsn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &ctxOnlyConn{fc: conn.(*fakeConn)}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
_ driver.Conn = &ctxOnlyConn{}
|
|
|
|
|
_ driver.QueryerContext = &ctxOnlyConn{}
|
|
|
|
|
_ driver.ExecerContext = &ctxOnlyConn{}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type ctxOnlyConn struct {
|
|
|
|
|
fc *fakeConn
|
|
|
|
|
|
|
|
|
|
queryCtxCalled bool
|
|
|
|
|
execCtxCalled bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ctxOnlyConn) Begin() (driver.Tx, error) {
|
|
|
|
|
return c.fc.Begin()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ctxOnlyConn) Close() error {
|
|
|
|
|
return c.fc.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepare is still part of the Conn interface, so while it isn't used
|
|
|
|
|
// must be defined for compatibility.
|
|
|
|
|
func (c *ctxOnlyConn) Prepare(q string) (driver.Stmt, error) {
|
|
|
|
|
panic("not used")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ctxOnlyConn) PrepareContext(ctx context.Context, q string) (driver.Stmt, error) {
|
|
|
|
|
return c.fc.PrepareContext(ctx, q)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ctxOnlyConn) QueryContext(ctx context.Context, q string, args []driver.NamedValue) (driver.Rows, error) {
|
|
|
|
|
c.queryCtxCalled = true
|
|
|
|
|
return c.fc.QueryContext(ctx, q, args)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ctxOnlyConn) ExecContext(ctx context.Context, q string, args []driver.NamedValue) (driver.Result, error) {
|
|
|
|
|
c.execCtxCalled = true
|
|
|
|
|
return c.fc.ExecContext(ctx, q, args)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestQueryExecContextOnly ensures drivers only need to implement QueryContext
|
|
|
|
|
// and ExecContext methods.
|
|
|
|
|
func TestQueryExecContextOnly(t *testing.T) {
|
|
|
|
|
// Ensure connection does not implment non-context interfaces.
|
|
|
|
|
var connType driver.Conn = &ctxOnlyConn{}
|
|
|
|
|
if _, ok := connType.(driver.Execer); ok {
|
|
|
|
|
t.Fatalf("%T must not implement driver.Execer", connType)
|
|
|
|
|
}
|
|
|
|
|
if _, ok := connType.(driver.Queryer); ok {
|
|
|
|
|
t.Fatalf("%T must not implement driver.Queryer", connType)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Register("ContextOnly", &ctxOnlyDriver{})
|
|
|
|
|
db, err := Open("ContextOnly", "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
conn, err := db.Conn(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal("db.Conn", err)
|
|
|
|
|
}
|
|
|
|
|
defer conn.Close()
|
2017-10-02 11:16:53 -07:00
|
|
|
coc := conn.dc.ci.(*ctxOnlyConn)
|
|
|
|
|
coc.fc.skipDirtySession = true
|
2017-09-23 19:38:32 -07:00
|
|
|
|
|
|
|
|
_, err = conn.ExecContext(ctx, "WIPE")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal("exec wipe", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = conn.ExecContext(ctx, "CREATE|keys|v1=string")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal("exec create", err)
|
|
|
|
|
}
|
|
|
|
|
expectedValue := "value1"
|
|
|
|
|
_, err = conn.ExecContext(ctx, "INSERT|keys|v1=?", expectedValue)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal("exec insert", err)
|
|
|
|
|
}
|
|
|
|
|
rows, err := conn.QueryContext(ctx, "SELECT|keys|v1|")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal("query select", err)
|
|
|
|
|
}
|
|
|
|
|
v1 := ""
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
err = rows.Scan(&v1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal("rows scan", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
rows.Close()
|
|
|
|
|
|
|
|
|
|
if v1 != expectedValue {
|
|
|
|
|
t.Fatalf("expected %q, got %q", expectedValue, v1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !coc.execCtxCalled {
|
|
|
|
|
t.Error("ExecContext not called")
|
|
|
|
|
}
|
|
|
|
|
if !coc.queryCtxCalled {
|
|
|
|
|
t.Error("QueryContext not called")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-31 06:58:33 -07:00
|
|
|
// badConn implements a bad driver.Conn, for TestBadDriver.
|
|
|
|
|
// The Exec method panics.
|
|
|
|
|
type badConn struct{}
|
|
|
|
|
|
|
|
|
|
func (bc badConn) Prepare(query string) (driver.Stmt, error) {
|
|
|
|
|
return nil, errors.New("badConn Prepare")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (bc badConn) Close() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (bc badConn) Begin() (driver.Tx, error) {
|
|
|
|
|
return nil, errors.New("badConn Begin")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (bc badConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
|
|
|
|
panic("badConn.Exec")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// badDriver is a driver.Driver that uses badConn.
|
|
|
|
|
type badDriver struct{}
|
|
|
|
|
|
|
|
|
|
func (bd badDriver) Open(name string) (driver.Conn, error) {
|
|
|
|
|
return badConn{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Issue 15901.
|
|
|
|
|
func TestBadDriver(t *testing.T) {
|
|
|
|
|
Register("bad", badDriver{})
|
|
|
|
|
db, err := Open("bad", "ignored")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r == nil {
|
|
|
|
|
t.Error("expected panic")
|
|
|
|
|
} else {
|
|
|
|
|
if want := "badConn.Exec"; r.(string) != want {
|
|
|
|
|
t.Errorf("panic was %v, expected %v", r, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
defer db.Close()
|
|
|
|
|
db.Exec("ignored")
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 17:11:13 +09:00
|
|
|
type pingDriver struct {
|
|
|
|
|
fails bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type pingConn struct {
|
|
|
|
|
badConn
|
|
|
|
|
driver *pingDriver
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var pingError = errors.New("Ping failed")
|
|
|
|
|
|
|
|
|
|
func (pc pingConn) Ping(ctx context.Context) error {
|
|
|
|
|
if pc.driver.fails {
|
|
|
|
|
return pingError
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ driver.Pinger = pingConn{}
|
|
|
|
|
|
|
|
|
|
func (pd *pingDriver) Open(name string) (driver.Conn, error) {
|
|
|
|
|
return pingConn{driver: pd}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPing(t *testing.T) {
|
|
|
|
|
driver := &pingDriver{}
|
|
|
|
|
Register("ping", driver)
|
|
|
|
|
|
|
|
|
|
db, err := Open("ping", "ignored")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := db.Ping(); err != nil {
|
|
|
|
|
t.Errorf("err was %#v, expected nil", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
driver.fails = true
|
|
|
|
|
if err := db.Ping(); err != pingError {
|
|
|
|
|
t.Errorf("err was %#v, expected pingError", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-30 16:03:03 -07:00
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-30 09:27:33 -07:00
|
|
|
func BenchmarkConcurrentDBExec(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
ct := new(concurrentDBExecTest)
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
doConcurrentTest(b, ct)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkConcurrentStmtQuery(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
ct := new(concurrentStmtQueryTest)
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
doConcurrentTest(b, ct)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkConcurrentStmtExec(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
ct := new(concurrentStmtExecTest)
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
doConcurrentTest(b, ct)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkConcurrentTxQuery(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
ct := new(concurrentTxQueryTest)
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
doConcurrentTest(b, ct)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkConcurrentTxExec(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
ct := new(concurrentTxExecTest)
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
doConcurrentTest(b, ct)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkConcurrentTxStmtQuery(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
ct := new(concurrentTxStmtQueryTest)
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
doConcurrentTest(b, ct)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkConcurrentTxStmtExec(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
ct := new(concurrentTxStmtExecTest)
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
doConcurrentTest(b, ct)
|
|
|
|
|
}
|
2013-04-03 11:13:40 -07:00
|
|
|
}
|
|
|
|
|
|
2013-08-30 09:27:33 -07:00
|
|
|
func BenchmarkConcurrentRandom(b *testing.B) {
|
2013-04-03 11:13:40 -07:00
|
|
|
b.ReportAllocs()
|
2013-08-30 09:27:33 -07:00
|
|
|
ct := new(concurrentRandomTest)
|
2013-04-03 11:13:40 -07:00
|
|
|
for i := 0; i < b.N; i++ {
|
2013-08-30 09:27:33 -07:00
|
|
|
doConcurrentTest(b, ct)
|
2013-04-03 11:13:40 -07:00
|
|
|
}
|
|
|
|
|
}
|
2015-01-23 20:02:37 +09:00
|
|
|
|
|
|
|
|
func BenchmarkManyConcurrentQueries(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
// To see lock contention in Go 1.4, 16~ cores and 128~ goroutines are required.
|
|
|
|
|
const parallelism = 16
|
|
|
|
|
|
|
|
|
|
db := newTestDB(b, "magicquery")
|
|
|
|
|
defer closeDB(b, db)
|
|
|
|
|
db.SetMaxIdleConns(runtime.GOMAXPROCS(0) * parallelism)
|
|
|
|
|
|
|
|
|
|
stmt, err := db.Prepare("SELECT|magicquery|op|op=?,millis=?")
|
|
|
|
|
if err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer stmt.Close()
|
|
|
|
|
|
|
|
|
|
b.SetParallelism(parallelism)
|
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
|
|
|
for pb.Next() {
|
|
|
|
|
rows, err := stmt.Query("sleep", 1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
b.Error(err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
rows.Close()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|