Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RowToStructByName Snake Case Collision #2085

Merged
merged 4 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
35 changes: 35 additions & 0 deletions rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down