diff --git a/rows.go b/rows.go index d4f7a901..f23625d4 100644 --- a/rows.go +++ b/rows.go @@ -797,7 +797,7 @@ func computeNamedStructFields( if !dbTagPresent { colName = sf.Name } - fpos := fieldPosByName(fldDescs, colName) + fpos := fieldPosByName(fldDescs, colName, !dbTagPresent) if fpos == -1 { if missingField == "" { missingField = colName @@ -816,16 +816,21 @@ func computeNamedStructFields( const structTagKey = "db" -func fieldPosByName(fldDescs []pgconn.FieldDescription, field string) (i int) { +func fieldPosByName(fldDescs []pgconn.FieldDescription, field string, normalize bool) (i int) { i = -1 - for i, desc := range fldDescs { - // Snake case support. + if normalize { field = strings.ReplaceAll(field, "_", "") - descName := strings.ReplaceAll(desc.Name, "_", "") - - if strings.EqualFold(descName, field) { - return i + } + for i, desc := range fldDescs { + if normalize { + if strings.EqualFold(strings.ReplaceAll(desc.Name, "_", ""), field) { + return i + } + } else { + if desc.Name == field { + return i + } } } return diff --git a/rows_test.go b/rows_test.go index bb9d5015..4cda957f 100644 --- a/rows_test.go +++ b/rows_test.go @@ -667,6 +667,41 @@ func TestRowToStructByName(t *testing.T) { }) } +func TestRowToStructByNameDbTags(t *testing.T) { + type person struct { + Last string `db:"last_name"` + First string `db:"first_name"` + Age int32 `db:"age"` + AccountID string `db:"account_id"` + AnotherAccountID string `db:"account__id"` + } + + defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { + rows, _ := conn.Query(ctx, `select 'John' as first_name, 'Smith' as last_name, n as age, 'd5e49d3f' as account_id, '5e49d321' as account__id from generate_series(0, 9) n`) + slice, err := pgx.CollectRows(rows, pgx.RowToStructByName[person]) + assert.NoError(t, err) + + assert.Len(t, slice, 10) + for i := range slice { + assert.Equal(t, "Smith", slice[i].Last) + assert.Equal(t, "John", slice[i].First) + assert.EqualValues(t, i, slice[i].Age) + assert.Equal(t, "d5e49d3f", slice[i].AccountID) + assert.Equal(t, "5e49d321", slice[i].AnotherAccountID) + } + + // check missing fields in a returned row + rows, _ = conn.Query(ctx, `select 'Smith' as last_name, n as age from generate_series(0, 9) n`) + _, err = pgx.CollectRows(rows, pgx.RowToStructByName[person]) + assert.ErrorContains(t, err, "cannot find field first_name in returned row") + + // check missing field in a destination struct + rows, _ = conn.Query(ctx, `select 'John' as first_name, 'Smith' as last_name, n as age, 'd5e49d3f' as account_id, '5e49d321' as account__id, null as ignore from generate_series(0, 9) n`) + _, err = pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[person]) + assert.ErrorContains(t, err, "struct doesn't have corresponding row field ignore") + }) +} + func TestRowToStructByNameEmbeddedStruct(t *testing.T) { type Name struct { Last string `db:"last_name"`