package dbi import ( "context" "database/sql" "reflect" "strings" "atlas9.dev/c/core" ) func Get(ctx context.Context, db DBI, val any, query string, vals ...any) error { rows, err := db.Query(ctx, query, vals...) if err != nil { return err } defer rows.Close() if !rows.Next() { if err := rows.Err(); err != nil { return err } return core.ErrNotFound } cols, err := rows.Columns() if err != nil { return err } rv := reflect.ValueOf(val).Elem() rt := rv.Type() if _, ok := val.(sql.Scanner); ok { err = rows.Scan(val) } else if rt.Kind() == reflect.Struct { // Build a map of lowercase field name -> field index. // Column names are matched by lowercasing and stripping underscores, // so SQL snake_case columns map to Go PascalCase fields naturally: // picture_url -> pictureurl -> PictureURL // user_id -> userid -> UserID 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 { if fi, ok := fieldMap[strings.ReplaceAll(col, "_", "")]; ok { dest[i] = rv.Field(fi).Addr().Interface() } else { dest[i] = new(any) } } err = rows.Scan(dest...) } else { err = rows.Scan(val) } if err != nil { return err } return nil }