1package sqlite
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7 "fmt"
8
9 "github.com/jmoiron/sqlx"
10 "modernc.org/sqlite"
11 sqlite3 "modernc.org/sqlite/lib"
12)
13
14// WrapDbErr wraps database errors.
15func WrapDbErr(err error) error {
16 if err != nil {
17 if errors.Is(err, sql.ErrNoRows) {
18 return ErrNoRecord
19 }
20 if liteErr, ok := err.(*sqlite.Error); ok {
21 code := liteErr.Code()
22 if code == sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY ||
23 code == sqlite3.SQLITE_CONSTRAINT_UNIQUE {
24 return ErrDuplicateKey
25 }
26 }
27 }
28 return err
29}
30
31// WrapTx wraps database transactions.
32func WrapTx(db *sqlx.DB, ctx context.Context, fn func(tx *sqlx.Tx) error) error {
33 tx, err := db.BeginTxx(ctx, nil)
34 if err != nil {
35 return fmt.Errorf("failed to begin transaction: %w", err)
36 }
37
38 if err := fn(tx); err != nil {
39 return Rollback(tx, err)
40 }
41
42 if err := tx.Commit(); err != nil {
43 if errors.Is(err, sql.ErrTxDone) {
44 // this is ok because whoever did finish the tx should have also written the error already.
45 return nil
46 }
47 return fmt.Errorf("failed to commit transaction: %w", err)
48 }
49
50 return nil
51}
52
53// Rollback rolls back database transactions.
54func Rollback(tx *sqlx.Tx, err error) error {
55 if rerr := tx.Rollback(); rerr != nil {
56 if errors.Is(rerr, sql.ErrTxDone) {
57 return err
58 }
59 return fmt.Errorf("failed to rollback: %s: %w", err.Error(), rerr)
60 }
61
62 return err
63}