From edfdaf15c673e383f5892ab484e0d4201c26adc6 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Fri, 20 Nov 2015 14:02:49 -0600 Subject: [PATCH] Rows.Scan errors now include which argument caused error --- CHANGELOG.md | 4 ++++ query.go | 19 ++++++++++++++----- query_test.go | 4 ++++ values_test.go | 3 +-- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a15fa2..14393272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# Tip + +* Rows.Scan errors now include which argument caused error + # 2.7.1 (October 26, 2015) * Disable SSL renegotiation diff --git a/query.go b/query.go index 5871d628..8c0e9d07 100644 --- a/query.go +++ b/query.go @@ -214,6 +214,15 @@ func (rows *Rows) nextColumn() (*ValueReader, bool) { return &rows.vr, true } +type scanArgError struct { + col int + err error +} + +func (e scanArgError) Error() string { + return fmt.Sprintf("can't scan into dest[%d]: %v", e.col, e.err) +} + // Scan reads the values from the current row into dest values positionally. // dest can include pointers to core types, values implementing the Scanner // interface, and []byte. []byte will skip the decoding process and directly @@ -225,7 +234,7 @@ func (rows *Rows) Scan(dest ...interface{}) (err error) { return err } - for _, d := range dest { + for i, d := range dest { vr, _ := rows.nextColumn() // Check for []byte first as we allow sidestepping the decoding process and retrieving the raw bytes @@ -244,7 +253,7 @@ func (rows *Rows) Scan(dest ...interface{}) (err error) { } else if s, ok := d.(Scanner); ok { err = s.Scan(vr) if err != nil { - rows.Fatal(err) + rows.Fatal(scanArgError{col: i, err: err}) } } else if vr.Type().DataType == JsonOid || vr.Type().DataType == JsonbOid { decodeJson(vr, &d) @@ -292,7 +301,7 @@ func (rows *Rows) Scan(dest ...interface{}) (err error) { case TimestampOid: *v = decodeTimestamp(vr) default: - rows.Fatal(fmt.Errorf("Can't convert OID %v to time.Time", vr.Type().DataType)) + rows.Fatal(scanArgError{col: i, err: fmt.Errorf("Can't convert OID %v to time.Time", vr.Type().DataType)}) } case *net.IPNet: *v = decodeInet(vr) @@ -319,12 +328,12 @@ func (rows *Rows) Scan(dest ...interface{}) (err error) { } } } - rows.Fatal(fmt.Errorf("Scan cannot decode into %T", d)) + rows.Fatal(scanArgError{col: i, err: fmt.Errorf("Scan cannot decode into %T", d)}) } } if vr.Err() != nil { - rows.Fatal(vr.Err()) + rows.Fatal(scanArgError{col: i, err: vr.Err()}) } if rows.Err() != nil { diff --git a/query_test.go b/query_test.go index f6d969d5..2f9e7ee9 100644 --- a/query_test.go +++ b/query_test.go @@ -161,6 +161,10 @@ func TestConnQueryReadWrongTypeError(t *testing.T) { t.Fatal("Expected Rows to have an error after an improper read but it didn't") } + if rows.Err().Error() != "can't scan into dest[0]: Can't convert OID 23 to time.Time" { + t.Fatalf("Expected different Rows.Err(): %v", rows.Err()) + } + ensureConnValid(t, conn) } diff --git a/values_test.go b/values_test.go index a63a8cc9..8a54421e 100644 --- a/values_test.go +++ b/values_test.go @@ -1,7 +1,6 @@ package pgx_test import ( - "encoding/json" "github.com/jackc/pgx" "net" "reflect" @@ -196,7 +195,7 @@ func testJsonInt16ArrayFailureDueToOverflow(t *testing.T, conn *pgx.Conn, typena input := []int{1, 2, 234432} var output []int16 err := conn.QueryRow("select $1::"+typename, input).Scan(&output) - if _, ok := err.(*json.UnmarshalTypeError); !ok { + if err.Error() != "can't scan into dest[0]: json: cannot unmarshal number 234432 into Go value of type int16" { t.Errorf("%s: Expected *json.UnmarkalTypeError, but got %v", typename, err) } }