dbTransaction calls fn within a read-write transaction in db.
(db *sql.DB, fn func(tx *sql.Tx) (T, error))
| 183 | |
| 184 | // dbTransaction calls fn within a read-write transaction in db. |
| 185 | func dbTransaction[T any](db *sql.DB, fn func(tx *sql.Tx) (T, error)) (T, error) { |
| 186 | // Ideally we should be able to distinguish between read-only and read-write transactions, see the _txlock=exclusive discussion. |
| 187 | |
| 188 | var zeroRes T // A zero value of T |
| 189 | |
| 190 | tx, err := db.Begin() |
| 191 | if err != nil { |
| 192 | return zeroRes, fmt.Errorf("beginning transaction: %w", err) |
| 193 | } |
| 194 | succeeded := false |
| 195 | defer func() { |
| 196 | if !succeeded { |
| 197 | if err := tx.Rollback(); err != nil { |
| 198 | logrus.Errorf("Rolling back transaction: %v", err) |
| 199 | } |
| 200 | } |
| 201 | }() |
| 202 | |
| 203 | res, err := fn(tx) |
| 204 | if err != nil { |
| 205 | return zeroRes, err |
| 206 | } |
| 207 | if err := tx.Commit(); err != nil { |
| 208 | return zeroRes, fmt.Errorf("committing transaction: %w", err) |
| 209 | } |
| 210 | |
| 211 | succeeded = true |
| 212 | return res, nil |
| 213 | } |
| 214 | |
| 215 | // querySingleValue executes a SELECT which is expected to return at most one row with a single column. |
| 216 | // It returns (value, true, nil) on success, or (value, false, nil) if no row was returned. |
no test coverage detected
searching dependent graphs…