ForQueryRows encapsulates a lot of boilerplate when making db queries. Call it like this: err = ForQueryRows(ctx, db, query, queryArg1, queryArg2, ..., func(scanVar1 type1, scanVar2 type2, ...) { ...process a row from the result... }) This is equivalent to: rows, err = db.Query(ctx, query, queryA
(ctx context.Context, db DB, query string, args ...interface{})
| 48 | // single error-type value. If any invocation yields a non-nil |
| 49 | // result, ForQueryRows will abort and return it. |
| 50 | func ForQueryRows(ctx context.Context, db DB, query string, args ...interface{}) error { |
| 51 | if len(args) == 0 { |
| 52 | return errors.Wrap(ErrBadRequest, "too few arguments") |
| 53 | } |
| 54 | |
| 55 | fnArg := args[len(args)-1] |
| 56 | queryArgs := args[:len(args)-1] |
| 57 | |
| 58 | fnType := reflect.TypeOf(fnArg) |
| 59 | if fnType.Kind() != reflect.Func { |
| 60 | return errors.Wrap(ErrBadRequest, "fn arg not a function") |
| 61 | } |
| 62 | if fnType.NumOut() > 1 { |
| 63 | return errors.Wrap(ErrBadRequest, "fn arg must return 0 values or 1") |
| 64 | } |
| 65 | if fnType.NumOut() == 1 && !fnType.Out(0).Implements(errorInterface) { |
| 66 | return errors.Wrap(ErrBadRequest, "fn arg return type must be error") |
| 67 | } |
| 68 | |
| 69 | rows, err := db.Query(ctx, query, queryArgs...) |
| 70 | if err != nil { |
| 71 | return errors.Wrap(err, "query") |
| 72 | } |
| 73 | defer rows.Close() |
| 74 | |
| 75 | fnVal := reflect.ValueOf(fnArg) |
| 76 | |
| 77 | argPtrVals := make([]reflect.Value, 0, fnType.NumIn()) |
| 78 | scanArgs := make([]interface{}, 0, fnType.NumIn()) |
| 79 | fnArgs := make([]reflect.Value, 0, fnType.NumIn()) |
| 80 | |
| 81 | for rows.Next() { |
| 82 | argPtrVals = argPtrVals[:0] |
| 83 | scanArgs = scanArgs[:0] |
| 84 | fnArgs = fnArgs[:0] |
| 85 | for i := 0; i < fnType.NumIn(); i++ { |
| 86 | argType := fnType.In(i) |
| 87 | argPtrVal := reflect.New(argType) |
| 88 | argPtrVals = append(argPtrVals, argPtrVal) |
| 89 | scanArgs = append(scanArgs, argPtrVal.Interface()) |
| 90 | } |
| 91 | err = rows.Scan(scanArgs...) |
| 92 | if err != nil { |
| 93 | return errors.Wrap(err, "scan") |
| 94 | } |
| 95 | for _, argPtrVal := range argPtrVals { |
| 96 | fnArgs = append(fnArgs, argPtrVal.Elem()) |
| 97 | } |
| 98 | res := fnVal.Call(fnArgs) |
| 99 | if fnType.NumOut() == 1 && !res[0].IsNil() { |
| 100 | return errors.Wrap(res[0].Interface().(error), "callback") |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | return errors.Wrap(rows.Err(), "end scan") |
| 105 | } |