diff --git a/conn.go b/conn.go index c47a8b3a..1f7ebc65 100644 --- a/conn.go +++ b/conn.go @@ -2081,3 +2081,21 @@ func alnumLowerASCII(ch rune) rune { } return -1 // discard } + +// The database/sql/driver package says: +// All Conn implementations should implement the following interfaces: Pinger, SessionResetter, and Validator. +var _ driver.Pinger = &conn{} +var _ driver.SessionResetter = &conn{} +var _ driver.Validator = &conn{} + +func (cn *conn) ResetSession(ctx context.Context) error { + // Ensure bad connections are reported: From database/sql/driver: + // If a connection is never returned to the connection pool but immediately reused, then + // ResetSession is called prior to reuse but IsValid is not called. + return cn.err.get() +} + +func (cn *conn) IsValid() bool { + // panic("TODO IsValid") + return cn.err.get() == nil +} diff --git a/issues_test.go b/issues_test.go index 26a70282..e2c93925 100644 --- a/issues_test.go +++ b/issues_test.go @@ -2,6 +2,7 @@ package pq import ( "context" + "database/sql" "errors" "testing" "time" @@ -79,3 +80,79 @@ func TestIssue1062(t *testing.T) { } } } + +func connIsValid(t *testing.T, db *sql.DB) { + t.Helper() + + ctx := context.Background() + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + // the connection must be valid + err = conn.PingContext(ctx) + if err != nil { + t.Errorf("PingContext err=%#v", err) + } + // close must not return an error + err = conn.Close() + if err != nil { + t.Errorf("Close err=%#v", err) + } +} + +func TestQueryCancelRace(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + // cancel a query while executing on Postgres: must return the cancelled error code + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(10 * time.Millisecond) + cancel() + }() + row := db.QueryRowContext(ctx, "select pg_sleep(0.5)") + var pgSleepVoid string + err := row.Scan(&pgSleepVoid) + if pgErr := (*Error)(nil); !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode) { + t.Fatalf("expected cancelled error; err=%#v", err) + } + + // get a connection: it must be a valid + connIsValid(t, db) +} + +// Test cancelling a scan after it is started. This broke with 1.10.4. +func TestQueryCancelledReused(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + ctx, cancel := context.WithCancel(context.Background()) + // run a query that returns a lot of data + rows, err := db.QueryContext(ctx, "select generate_series(1, 10000)") + if err != nil { + t.Fatal(err) + } + + // scan the first value + if !rows.Next() { + t.Error("expected rows.Next() to return true") + } + var i int + err = rows.Scan(&i) + if err != nil { + t.Fatal(err) + } + if i != 1 { + t.Error(i) + } + + // cancel the context and close rows, ignoring errors + cancel() + rows.Close() + + // get a connection: it must be valid + connIsValid(t, db) +}