Commit 1f20ab11 authored by Marko Tiikkaja's avatar Marko Tiikkaja Committed by Brad Fitzpatrick

database/sql: Check errors in QueryRow.Scan

The previous coding did not correctly check for errors from the driver's
Next() or Close(), which could mask genuine errors from the database, as
witnessed in issue #6651.

Even after this change errors from Close() will be ignored if the query
returned no rows (as Rows.Next will have closed the handle already), but it
is a lot easier for the drivers to guard against that.

Fixes #6651.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/41590043
parent 54f39c99
...@@ -686,7 +686,13 @@ func (rc *rowsCursor) Columns() []string { ...@@ -686,7 +686,13 @@ func (rc *rowsCursor) Columns() []string {
return rc.cols return rc.cols
} }
var rowsCursorNextHook func(dest []driver.Value) error
func (rc *rowsCursor) Next(dest []driver.Value) error { func (rc *rowsCursor) Next(dest []driver.Value) error {
if rowsCursorNextHook != nil {
return rowsCursorNextHook(dest)
}
if rc.closed { if rc.closed {
return errors.New("fakedb: cursor is closed") return errors.New("fakedb: cursor is closed")
} }
......
...@@ -1495,10 +1495,12 @@ type Rows struct { ...@@ -1495,10 +1495,12 @@ type Rows struct {
closeStmt driver.Stmt // if non-nil, statement to Close on close closeStmt driver.Stmt // if non-nil, statement to Close on close
} }
// Next prepares the next result row for reading with the Scan method. // Next prepares the next result row for reading with the Scan method. It
// It returns true on success, false if there is no next result row. // returns true on success, or false if there is no next result row or an error
// Every call to Scan, even the first one, must be preceded by a call // happened while preparing it. Err should be consulted to distinguish between
// to Next. // the two cases.
//
// Every call to Scan, even the first one, must be preceded by a call to Next.
func (rs *Rows) Next() bool { func (rs *Rows) Next() bool {
if rs.closed { if rs.closed {
return false return false
...@@ -1625,12 +1627,19 @@ func (r *Row) Scan(dest ...interface{}) error { ...@@ -1625,12 +1627,19 @@ func (r *Row) Scan(dest ...interface{}) error {
} }
if !r.rows.Next() { if !r.rows.Next() {
if err := r.rows.Err(); err != nil {
return err
}
return ErrNoRows return ErrNoRows
} }
err := r.rows.Scan(dest...) err := r.rows.Scan(dest...)
if err != nil { if err != nil {
return err return err
} }
// Make sure the query can be processed to completion with no errors.
if err := r.rows.Close(); err != nil {
return err
}
return nil return nil
} }
......
...@@ -660,6 +660,35 @@ func TestQueryRowClosingStmt(t *testing.T) { ...@@ -660,6 +660,35 @@ func TestQueryRowClosingStmt(t *testing.T) {
} }
} }
// 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 }()
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"
rowsCloseHook = func(rows *Rows, err *error) {
*err = fmt.Errorf(want)
}
defer func() { rowsCloseHook = nil }()
err = db.QueryRow("SELECT|people|name|").Scan(&v)
if err == nil || err.Error() != want {
t.Errorf("error = %q; want %q", err, want)
}
}
type nullTestRow struct { type nullTestRow struct {
nullParam interface{} nullParam interface{}
notNullParam interface{} notNullParam interface{}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment