Extract common database functionality from api/ntpdb and monitor/ntpdb into shared common/database package: - Dynamic connector pattern with configuration loading - Configurable connection pool management (API: 25/10, Monitor: 10/5) - Optional Prometheus metrics integration - Generic transaction helpers with proper error handling - Unified interfaces compatible with SQLC-generated code Foundation for migration to eliminate ~200 lines of duplicate code.
70 lines
1.8 KiB
Go
70 lines
1.8 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
|
|
}
|
|
|
|
err = tx.Commit(ctx)
|
|
committed = true // Mark as committed regardless of commit success/failure
|
|
if err != nil {
|
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
}
|
|
|
|
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)
|
|
}
|