69 lines
1.7 KiB
Go
69 lines
1.7 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"go.ntppool.org/common/logger"
|
|
)
|
|
|
|
// DB interface for database operations that can begin transactions
|
|
type DB[Q any] interface {
|
|
Begin(ctx context.Context) (Q, error)
|
|
}
|
|
|
|
// TX interface for transaction operations
|
|
type TX interface {
|
|
Commit(ctx context.Context) error
|
|
Rollback(ctx context.Context) error
|
|
}
|
|
|
|
// WithTransaction executes a function within a database transaction
|
|
// Handles proper rollback on error and commit on success
|
|
func WithTransaction[Q TX](ctx context.Context, db DB[Q], fn func(ctx context.Context, q Q) error) error {
|
|
tx, err := db.Begin(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
|
|
var committed bool
|
|
defer func() {
|
|
if !committed {
|
|
if rbErr := tx.Rollback(ctx); rbErr != nil {
|
|
// Log rollback error but don't override original error
|
|
log := logger.FromContext(ctx)
|
|
log.ErrorContext(ctx, "failed to rollback transaction", "error", rbErr)
|
|
}
|
|
}
|
|
}()
|
|
|
|
if err := fn(ctx, tx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
}
|
|
|
|
committed = true
|
|
return nil
|
|
}
|
|
|
|
// WithReadOnlyTransaction executes a read-only function within a transaction
|
|
// Always rolls back at the end (for consistent read isolation)
|
|
func WithReadOnlyTransaction[Q TX](ctx context.Context, db DB[Q], fn func(ctx context.Context, q Q) error) error {
|
|
tx, err := db.Begin(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin read-only transaction: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
if rbErr := tx.Rollback(ctx); rbErr != nil {
|
|
log := logger.FromContext(ctx)
|
|
log.ErrorContext(ctx, "failed to rollback read-only transaction", "error", rbErr)
|
|
}
|
|
}()
|
|
|
|
return fn(ctx, tx)
|
|
}
|