]> Cypherpunks repositories - gostls13.git/commitdiff
database/sql: make Rows.Scan properly wrap underlying errors
authorTim Möhlmann <muhlemmer@gmail.com>
Thu, 13 Aug 2020 08:56:31 +0000 (11:56 +0300)
committerDaniel Theophanes <kardianos@gmail.com>
Fri, 14 Aug 2020 17:45:39 +0000 (17:45 +0000)
The prior implementation used the format verb %v which unfortunately
improperly wrapped any underlying scanner errors, and we couldn't use
errors.Is nor errors.As. This change fixes that by using the %w verb.

Added a unit to ensure that both error sub string matching works, but
also that errors.Is works as expected.

Fixes #38099

Change-Id: Iea667041dd8081d961246f77f2542330417292dc
Reviewed-on: https://go-review.googlesource.com/c/go/+/248337
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
Reviewed-by: Daniel Theophanes <kardianos@gmail.com>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/database/sql/sql.go
src/database/sql/sql_test.go

index b3d0653f5c6c6e52f2bee2d4b49c76579d9dd521..0b85db66b9a2dc88d339cf05d1157bde0dc055c7 100644 (file)
@@ -3110,6 +3110,9 @@ func rowsColumnInfoSetupConnLocked(rowsi driver.Rows) []*ColumnType {
 // "select cursor(select * from my_table) from dual", into a
 // *Rows value that can itself be scanned from. The parent
 // select query will close any cursor *Rows if the parent *Rows is closed.
+//
+// If any of the first arguments implementing Scanner returns an error,
+// that error will be wrapped in the returned error
 func (rs *Rows) Scan(dest ...interface{}) error {
        rs.closemu.RLock()
 
@@ -3133,7 +3136,7 @@ func (rs *Rows) Scan(dest ...interface{}) error {
        for i, sv := range rs.lastcols {
                err := convertAssignRows(dest[i], sv, rs)
                if err != nil {
-                       return fmt.Errorf(`sql: Scan error on column index %d, name %q: %v`, i, rs.rowsi.Columns()[i], err)
+                       return fmt.Errorf(`sql: Scan error on column index %d, name %q: %w`, i, rs.rowsi.Columns()[i], err)
                }
        }
        return nil
index 5727f0d8aa64183423e7d203e3470f5fb1345419..762d42f54b4e06d3469f9ddbdf340eb8018671c8 100644 (file)
@@ -4149,6 +4149,41 @@ func TestQueryExecContextOnly(t *testing.T) {
        }
 }
 
+type alwaysErrScanner struct{}
+
+var errTestScanWrap = errors.New("errTestScanWrap")
+
+func (alwaysErrScanner) Scan(interface{}) error {
+       return errTestScanWrap
+}
+
+// Issue 38099: Ensure that Rows.Scan properly wraps underlying errors.
+func TestRowsScanProperlyWrapsErrors(t *testing.T) {
+       db := newTestDB(t, "people")
+       defer closeDB(t, db)
+
+       rows, err := db.Query("SELECT|people|age|")
+       if err != nil {
+               t.Fatalf("Query: %v", err)
+       }
+
+       var res alwaysErrScanner
+
+       for rows.Next() {
+               err = rows.Scan(&res)
+               if err == nil {
+                       t.Fatal("expecting back an error")
+               }
+               if !errors.Is(err, errTestScanWrap) {
+                       t.Fatalf("errors.Is mismatch\n%v\nWant: %v", err, errTestScanWrap)
+               }
+               // Ensure that error substring matching still correctly works.
+               if !strings.Contains(err.Error(), errTestScanWrap.Error()) {
+                       t.Fatalf("Error %v does not contain %v", err, errTestScanWrap)
+               }
+       }
+}
+
 // badConn implements a bad driver.Conn, for TestBadDriver.
 // The Exec method panics.
 type badConn struct{}