// Package sqlr ("squealer") provides some convenience wrappers around // database/sql in the spirit of encoding/json. package sqlr import ( "context" "database/sql" "fmt" "iter" "reflect" ) // Scan calls rows.Scan to unpack a single row into the fields of v. func Scan(rows *sql.Rows, v any) error { rv := reflect.ValueOf(v) if rv.Kind() != reflect.Pointer || rv.Elem().Kind() != reflect.Struct { return fmt.Errorf("Scan needs a pointer to a struct") } inner := rv.Elem() fields := reflect.VisibleFields(inner.Type()) cols, err := rows.Columns() if err != nil { return err } dest := make([]any, len(cols)) Cols: for i, c := range cols { for _, f := range fields { if !f.IsExported() { continue } tag := f.Tag.Get("sql") if tag == "" { tag = f.Name } if tag == c { dest[i] = inner.FieldByIndex(f.Index).Addr().Interface() continue Cols } } return fmt.Errorf("no field with tag %q", c) } return rows.Scan(dest...) } // Querier is the underlying type used to query the database, // usually *sql.DB or *sql.Tx. type Querier interface { QueryContext(context.Context, string, ...any) (*sql.Rows, error) } // Query returns an iterator over the rows matched by a query. func Query[Row any](ctx context.Context, querier Querier, query string, args ...any) iter.Seq2[Row, error] { return func(yield func(Row, error) bool) { rows, err := querier.QueryContext(ctx, query, args...) if err != nil { var zero Row yield(zero, err) return } defer rows.Close() for rows.Next() { var row Row if err := Scan(rows, &row); err != nil { yield(row, err) return } if !yield(row, nil) { return } } if err := rows.Err(); err != nil { var zero Row yield(zero, err) } } }