db.go

 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}