Summary
DB.Select (ctx.SQL.Select) has two problems with struct destinations:
- A
[]*T destination comes back with N nil pointers. The rows are counted but never scanned into the elements, and no error is reported.
- A struct with an unexported field that matches a column panics:
reflect.Value.Interface: cannot return value obtained from unexported field or method.
Reproduction
In-process sqlite, table users(id, name) with rows (1,'a') and (2,'b'):
type User struct {
ID int
Name string
}
var us []*User
db.Select(ctx, &us, "SELECT id, name FROM users")
// us[0] = <nil>, us[1] = <nil>
type Row struct {
id int // unexported
Name string
}
var rs []Row
db.Select(ctx, &rs, "SELECT id, name FROM users")
// panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
// sql.(*DB).rowsToStruct db.go:345
Root cause
pkg/gofr/datasource/sql/db.go:
- Pointer elements:
selectSlice (line 266) only maps rows into structs when the element kind is Struct. For *T it falls through to rows.Scan with a **T. That fails, and the error is discarded, so a nil element is appended per row.
- Unexported fields:
rowsToStruct builds scan targets with v.Field(i).Addr().Interface() (line 345) for every field whose name matches a column. It never checks IsExported(), so the call panics on an unexported field.
Impact
[]*Model is a very common Go idiom. With it, handlers silently get nil entries (and nil-pointer panics later) instead of data.
- Models with private helper fields that happen to share a column name panic the request.
Suggested fix
- In
selectSlice, handle Elem().Kind() == Ptr && Elem().Elem().Kind() == Struct: allocate with reflect.New(elem.Elem()), fill it via rowsToStruct and append the pointer.
- Skip fields that are unexported or tagged
db:"-" in rowsToStruct, as encoding/json and sqlx do.
- Log (or return) scan errors rather than dropping them.
Summary
DB.Select(ctx.SQL.Select) has two problems with struct destinations:[]*Tdestination comes back with N nil pointers. The rows are counted but never scanned into the elements, and no error is reported.reflect.Value.Interface: cannot return value obtained from unexported field or method.Reproduction
In-process sqlite, table
users(id, name)with rows(1,'a')and(2,'b'):Root cause
pkg/gofr/datasource/sql/db.go:selectSlice(line 266) only maps rows into structs when the element kind isStruct. For*Tit falls through torows.Scanwith a**T. That fails, and the error is discarded, so a nil element is appended per row.rowsToStructbuilds scan targets withv.Field(i).Addr().Interface()(line 345) for every field whose name matches a column. It never checksIsExported(), so the call panics on an unexported field.Impact
[]*Modelis a very common Go idiom. With it, handlers silently get nil entries (and nil-pointer panics later) instead of data.Suggested fix
selectSlice, handleElem().Kind() == Ptr && Elem().Elem().Kind() == Struct: allocate withreflect.New(elem.Elem()), fill it viarowsToStructand append the pointer.db:"-"inrowsToStruct, asencoding/jsonand sqlx do.