Skip to content

Commit

Permalink
Include the field name in error messages when scanning structs
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason Turim committed Jan 11, 2025
1 parent c2175fe commit 9c0ad69
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 10 deletions.
9 changes: 7 additions & 2 deletions pgtype/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"reflect"
"testing"

Expand Down Expand Up @@ -191,11 +192,15 @@ func TestJSONCodecUnmarshalSQLNull(t *testing.T) {
// A string cannot scan a NULL.
str := "foobar"
err = conn.QueryRow(ctx, "select null::json").Scan(&str)
require.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *string")
fieldName := "json"
if conn.PgConn().ParameterStatus("crdb_version") != "" {
fieldName = "jsonb" // Seems like CockroachDB treats json as jsonb.
}
require.EqualError(t, err, fmt.Sprintf("can't scan into dest[0] (col: %s): cannot scan NULL into *string", fieldName))

// A non-string cannot scan a NULL.
err = conn.QueryRow(ctx, "select null::json").Scan(&n)
require.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *int")
require.EqualError(t, err, fmt.Sprintf("can't scan into dest[0] (col: %s): cannot scan NULL into *int", fieldName))
})
}

Expand Down
4 changes: 2 additions & 2 deletions pgtype/jsonb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ func TestJSONBCodecUnmarshalSQLNull(t *testing.T) {
// A string cannot scan a NULL.
str := "foobar"
err = conn.QueryRow(ctx, "select null::jsonb").Scan(&str)
require.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *string")
require.EqualError(t, err, "can't scan into dest[0] (col: jsonb): cannot scan NULL into *string")

// A non-string cannot scan a NULL.
err = conn.QueryRow(ctx, "select null::jsonb").Scan(&n)
require.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *int")
require.EqualError(t, err, "can't scan into dest[0] (col: jsonb): cannot scan NULL into *int")
})
}

Expand Down
2 changes: 1 addition & 1 deletion pgtype/xml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestXMLCodecUnmarshalSQLNull(t *testing.T) {
// A string cannot scan a NULL.
str := "foobar"
err = conn.QueryRow(ctx, "select null::xml").Scan(&str)
assert.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *string")
assert.EqualError(t, err, "can't scan into dest[0] (col: xml): cannot scan NULL into *string")
})
}

Expand Down
2 changes: 1 addition & 1 deletion query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ 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]: cannot scan int4 (OID 23) in binary format into *time.Time" {
if rows.Err().Error() != "can't scan into dest[0] (col: n): cannot scan int4 (OID 23) in binary format into *time.Time" {
t.Fatalf("Expected different Rows.Err(): %v", rows.Err())
}

Expand Down
11 changes: 8 additions & 3 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ func (rows *baseRows) Scan(dest ...any) error {

err := rows.scanPlans[i].Scan(values[i], dst)
if err != nil {
err = ScanArgError{ColumnIndex: i, Err: err}
err = ScanArgError{ColumnIndex: i, FieldName: fieldDescriptions[i].Name, Err: err}
rows.fatal(err)
return err
}
Expand Down Expand Up @@ -334,11 +334,16 @@ func (rows *baseRows) Conn() *Conn {

type ScanArgError struct {
ColumnIndex int
FieldName string
Err error
}

func (e ScanArgError) Error() string {
return fmt.Sprintf("can't scan into dest[%d]: %v", e.ColumnIndex, e.Err)
if e.FieldName == "?column?" { // Don't include the fieldname if it's unknown
return fmt.Sprintf("can't scan into dest[%d]: %v", e.ColumnIndex, e.Err)
}

return fmt.Sprintf("can't scan into dest[%d] (col: %s): %v", e.ColumnIndex, e.FieldName, e.Err)
}

func (e ScanArgError) Unwrap() error {
Expand Down Expand Up @@ -366,7 +371,7 @@ func ScanRow(typeMap *pgtype.Map, fieldDescriptions []pgconn.FieldDescription, v

err := typeMap.Scan(fieldDescriptions[i].DataTypeOID, fieldDescriptions[i].Format, values[i], d)
if err != nil {
return ScanArgError{ColumnIndex: i, Err: err}
return ScanArgError{ColumnIndex: i, FieldName: fieldDescriptions[i].Name, Err: err}
}
}

Expand Down
8 changes: 7 additions & 1 deletion values_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pgx_test
import (
"bytes"
"context"
"fmt"
"net"
"os"
"reflect"
Expand Down Expand Up @@ -215,7 +216,12 @@ func testJSONInt16ArrayFailureDueToOverflow(t *testing.T, conn *pgx.Conn, typena
input := []int{1, 2, 234432}
var output []int16
err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
if err == nil || err.Error() != "can't scan into dest[0]: json: cannot unmarshal number 234432 into Go value of type int16" {
fieldName := typename
if conn.PgConn().ParameterStatus("crdb_version") != "" && typename == "json" {
fieldName = "jsonb" // Seems like CockroachDB treats json as jsonb.
}
expectedMessage := fmt.Sprintf("can't scan into dest[0] (col: %s): json: cannot unmarshal number 234432 into Go value of type int16", fieldName)
if err == nil || err.Error() != expectedMessage {
t.Errorf("%s: Expected *json.UnmarshalTypeError, but got %v", typename, err)
}
}
Expand Down

0 comments on commit 9c0ad69

Please sign in to comment.