Files
oslstats/internal/db/txhelpers.go
2026-02-14 19:48:59 +11:00

114 lines
2.7 KiB
Go

package db
import (
"context"
"net/http"
"time"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/oslstats/internal/notify"
"git.haelnorr.com/h/oslstats/internal/throw"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
// TxFunc is a function that runs within a database transaction
type (
TxFunc func(ctx context.Context, tx bun.Tx) (bool, error)
TxFuncSilent func(ctx context.Context, tx bun.Tx) error
)
var timeout = 15 * time.Second
// WithReadTx executes a read-only transaction with automatic rollback
// Returns true if successful, false if error was thrown to client
func (db *DB) WithReadTx(
s *hws.Server,
w http.ResponseWriter,
r *http.Request,
fn TxFunc,
) bool {
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
ok, err := db.withTx(ctx, fn, false)
if err != nil {
throw.InternalServiceError(s, w, r, "Database error", err)
}
return ok
}
// WithTxFailSilently executes a transaction with automatic rollback
// Returns true if successful, false if error occured.
// Does not throw any errors to the client.
func (db *DB) WithTxFailSilently(
ctx context.Context,
fn TxFuncSilent,
) error {
fnc := func(ctx context.Context, tx bun.Tx) (bool, error) {
err := fn(ctx, tx)
return err == nil, err
}
_, err := db.withTx(ctx, fnc, true)
return err
}
// WithWriteTx executes a write transaction with automatic rollback on error
// Commits only if fn returns nil. Returns true if successful.
func (db *DB) WithWriteTx(
s *hws.Server,
w http.ResponseWriter,
r *http.Request,
fn TxFunc,
) bool {
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
ok, err := db.withTx(ctx, fn, true)
if err != nil {
throw.InternalServiceError(s, w, r, "Database error", err)
}
return ok
}
// WithNotifyTx executes a transaction with notification-based error handling
// Uses notifyInternalServiceError instead of throwInternalServiceError
func (db *DB) WithNotifyTx(
s *hws.Server,
w http.ResponseWriter,
r *http.Request,
fn TxFunc,
) bool {
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
ok, err := db.withTx(ctx, fn, true)
if err != nil {
notify.InternalServiceError(s, w, r, "Database error", err)
}
return ok
}
// withTx executes a transaction with automatic rollback on error
func (db *DB) withTx(
ctx context.Context,
fn TxFunc,
write bool,
) (bool, error) {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return false, errors.Wrap(err, "conn.BeginTx")
}
defer func() { _ = tx.Rollback() }()
ok, err := fn(ctx, tx)
if err != nil || !ok {
return false, err
}
if write {
err = tx.Commit()
if err != nil {
return false, errors.Wrap(err, "tx.Commit")
}
} else {
_ = tx.Commit()
}
return true, nil
}