Skip to content

Instantly share code, notes, and snippets.

@benhoyt
Created December 20, 2023 08:34
Show Gist options
  • Save benhoyt/e7c362014f8c369db96f5a9f8281ced1 to your computer and use it in GitHub Desktop.
Save benhoyt/e7c362014f8c369db96f5a9f8281ced1 to your computer and use it in GitHub Desktop.
Proposed Rows.ScanRow implementation (cut-down version)
package model
import (
"database/sql"
"errors"
"fmt"
"reflect"
"sync"
)
// ScanRow is a cut-down version of the proposed Rows.ScanRow method. It
// currently only handles dest being a (pointer to) struct, and does not
// handle embedded fields. See https://github.com/golang/go/issues/61637
func ScanRow(rows *sql.Rows, dest any) error {
rv := reflect.ValueOf(dest)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return errors.New("dest must be a non-nil pointer")
}
elem := rv.Elem()
if elem.Kind() != reflect.Struct {
return errors.New("dest must point to a struct")
}
indexes := cachedFieldIndexes(reflect.TypeOf(dest).Elem())
columns, err := rows.Columns()
if err != nil {
return fmt.Errorf("cannot fetch columns: %w", err)
}
var scanArgs []any
for _, column := range columns {
index, ok := indexes[column]
if ok {
// We have a column to field mapping, scan the value.
field := elem.Field(index)
scanArgs = append(scanArgs, field.Addr().Interface())
} else {
// Unassigned column, throw away the scanned value.
var throwAway any
scanArgs = append(scanArgs, &throwAway)
}
}
return rows.Scan(scanArgs...)
}
// fieldIndexes returns a map of database column name to struct field index.
func fieldIndexes(structType reflect.Type) map[string]int {
indexes := make(map[string]int)
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
tag := field.Tag.Get("sql")
if tag != "" {
// Use "sql" tag if set
indexes[tag] = i
} else {
// Otherwise use field name (with exact case)
indexes[field.Name] = i
}
}
return indexes
}
var fieldIndexesCache sync.Map // map[reflect.Type]map[string]int
// cachedFieldIndexes is like fieldIndexes, but cached per struct type.
func cachedFieldIndexes(structType reflect.Type) map[string]int {
if f, ok := fieldIndexesCache.Load(structType); ok {
return f.(map[string]int)
}
indexes := fieldIndexes(structType)
fieldIndexesCache.Store(structType, indexes)
return indexes
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment