package dbi import ( "database/sql" "reflect" "strings" ) // scanRow scans a single row into val, using reflection to map columns to struct fields. // Supports sql.Scanner, structs (column names mapped by lowercasing and stripping underscores), // and single-value types. func scanRow(rows *sql.Rows, cols []string, val any) error { rv := reflect.ValueOf(val).Elem() rt := rv.Type() if _, ok := val.(sql.Scanner); ok { return rows.Scan(val) } if rt.Kind() == reflect.Struct { fieldMap := make(map[string]int, rt.NumField()) for i := 0; i < rt.NumField(); i++ { f := rt.Field(i) if f.IsExported() { fieldMap[strings.ToLower(f.Name)] = i } } dest := make([]any, len(cols)) for i, col := range cols { // TODO get rid of this subtlety. just require exact name/case match if fi, ok := fieldMap[strings.ReplaceAll(col, "_", "")]; ok { dest[i] = rv.Field(fi).Addr().Interface() } else { dest[i] = new(any) } } return rows.Scan(dest...) } return rows.Scan(val) }