@@ -10,12 +10,40 @@ import (
1010 "time"
1111)
1212
13- type queryer interface {
13+ // Queryer is an interface implemented by [sql.DB] and [sql.Tx].
14+ type Queryer interface {
1415 QueryContext (ctx context.Context , query string , args ... any ) (* sql.Rows , error )
1516}
1617
17- // TODO: document me.
18- func Query [T any ](ctx context.Context , q queryer , query string , args ... any ) iter.Seq2 [T , error ] {
18+ // Query executes a query that returns rows, scans each row into a T, and returns an iterator over the Ts.
19+ // If an error occurs, the iterator yields it as the second value, and the caller should then stop the iteration.
20+ // [Queryer] can be either [sql.DB] or [sql.Tx], the rest of the arguments are passed directly to [Queryer.QueryContext].
21+ // Query fully manages the lifecycle of the [sql.Rows] returned by [Queryer.QueryContext], so the caller does not have to.
22+ //
23+ // The following Ts are supported:
24+ // - int (any kind)
25+ // - uint (any kind)
26+ // - float (any kind)
27+ // - bool
28+ // - string
29+ // - time.Time
30+ // - [sql.Scanner] (implemented by [sql.Null] types)
31+ // - any struct
32+ //
33+ // See the [sql.Rows.Scan] documentation for the scanning rules.
34+ // If the query has multiple columns, T must be a struct, other types can only be used for single-column queries.
35+ // The fields of a struct T must have the `sql:"COLUMN"` tag, where COLUMN is the name of the corresponding column in the query.
36+ // Unexported and untagged fields are ignored.
37+ //
38+ // Query panics if:
39+ // - The query has no columns.
40+ // - A non-struct T is specified with a multi-column query.
41+ // - The specified struct T has no field for one of the query columns.
42+ // - An unsupported T is specified.
43+ // - One of the fields in a struct T has an empty `sql` tag.
44+ //
45+ // If the caller prefers the result to be a slice rather than an iterator, Query can be combined with [Collect].
46+ func Query [T any ](ctx context.Context , q Queryer , query string , args ... any ) iter.Seq2 [T , error ] {
1947 return func (yield func (T , error ) bool ) {
2048 rows , err := q .QueryContext (ctx , query , args ... )
2149 if err != nil {
@@ -47,8 +75,12 @@ func Query[T any](ctx context.Context, q queryer, query string, args ...any) ite
4775 }
4876}
4977
50- // TODO: document me.
51- func QueryRow [T any ](ctx context.Context , q queryer , query string , args ... any ) (T , error ) {
78+ // QueryRow is a [Query] variant for queries that are expected to return at most one row,
79+ // so instead of an iterator, it returns a single T.
80+ // Like [sql.DB.QueryRow], QueryRow returns [sql.ErrNoRows] if the query selects no rows,
81+ // otherwise it scans the first row and discards the rest.
82+ // See the [Query] documentation for details on supported Ts.
83+ func QueryRow [T any ](ctx context.Context , q Queryer , query string , args ... any ) (T , error ) {
5284 rows , err := q .QueryContext (ctx , query , args ... )
5385 if err != nil {
5486 return zero [T ](), err
@@ -78,6 +110,19 @@ func QueryRow[T any](ctx context.Context, q queryer, query string, args ...any)
78110 return t , nil
79111}
80112
113+ // Collect is a [slices.Collect] variant that collects values from an iter.Seq2[T, error].
114+ // If an error occurs during the collection, Collect stops the iteration and returns the error.
115+ func Collect [T any ](seq iter.Seq2 [T , error ]) ([]T , error ) {
116+ var ts []T
117+ for t , err := range seq {
118+ if err != nil {
119+ return nil , err
120+ }
121+ ts = append (ts , t )
122+ }
123+ return ts , nil
124+ }
125+
81126func zero [T any ]() (t T ) { return t }
82127
83128type scanner interface {
0 commit comments