Documentation
¶
Overview ¶
Package libdb provides an interface for interacting with a SQL database, currently with a specific implementation for PostgreSQL using lib/pq.
Key Features:
Abstraction: Defines interfaces (`DBManager`, `Exec`, `QueryRower`) to decouple application code from specific database driver details.
Simplified Transaction Management: The `DBManager.WithTransaction` method provides a clear pattern for handling database transactions, returning separate functions for committing (`CommitTx`) and releasing/rolling back (`ReleaseTx`). The `ReleaseTx` function is designed for use with `defer` to ensure transactions are always finalized and connections are released, even in cases of errors or panics.
Centralized Error Translation: Maps common low-level database errors (like sql.ErrNoRows or PostgreSQL-specific pq.Error codes) to a consistent set of exported package errors (e.g., ErrNotFound, ErrUniqueViolation, ErrDeadlockDetected). This simplifies error handling in application code.
Usage Example (Transaction):
func handleRequest(ctx context.Context, mgr libdb.DBManager) error {
// Start transaction, get executor and commit/release functions
exec, commit, release, err := mgr.WithTransaction(ctx)
if err != nil {
return fmt.Errorf("failed to start transaction: %w", err)
}
// Always defer release() to ensure cleanup (rollback on error/panic, no-op after commit)
defer release()
// --- Do work using exec ---
_, err = exec.ExecContext(ctx, "UPDATE settings SET value = $1 WHERE key = $2", "new_value", "setting_key")
if err != nil {
// Error occurred - no need to call release explicitly, defer handles it.
return fmt.Errorf("failed to update setting: %w", err)
}
// --- Success ---
// Attempt to commit; if it fails, the deferred release() still runs.
if err = commit(ctx); err != nil {
return fmt.Errorf("transaction commit failed: %w", err)
}
// Commit successful. The deferred release() will run but do nothing (idempotent).
return nil
}
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound is returned by Scan when sql.ErrNoRows is encountered. ErrNotFound = errors.New("libdb: not found") // ErrTxFailed indicates a failure during transaction finalization (Commit or Rollback). ErrTxFailed = errors.New("libdb: transaction failed") // ErrMaxRowsReached indicates that the maximum number of rows on a given table has been reached. // This error should be thrown when attempting to create a new entry would lead to exceeding the maximum capacity. // It implies that enforcing a reasonable maximum row count is necessary to prevent operations like bulk update from failing. ErrMaxRowsReached = errors.New("max row count reached") // ErrUniqueViolation corresponds to unique key constraint errors (e.g., PostgreSQL code 23505). ErrUniqueViolation = errors.New("libdb: unique constraint violation") // ErrForeignKeyViolation corresponds to foreign key constraint errors (e.g., PostgreSQL code 23503). ErrForeignKeyViolation = errors.New("libdb: foreign key violation") // ErrNotNullViolation corresponds to not-null constraint errors (e.g., PostgreSQL code 23502). ErrNotNullViolation = errors.New("libdb: not null constraint violation") // ErrCheckViolation corresponds to check constraint errors (e.g., PostgreSQL code 23514). ErrCheckViolation = errors.New("libdb: check constraint violation") // ErrConstraintViolation is a generic error for constraint violations not specifically mapped. ErrConstraintViolation = errors.New("libdb: constraint violation") // ErrDeadlockDetected corresponds to deadlock errors (e.g., PostgreSQL code 40P01). ErrDeadlockDetected = errors.New("libdb: deadlock detected") // ErrSerializationFailure corresponds to serialization failures (e.g., PostgreSQL code 40001). ErrSerializationFailure = errors.New("libdb: serialization failure") // ErrLockNotAvailable corresponds to lock acquisition failures (e.g., PostgreSQL code 55P03). ErrLockNotAvailable = errors.New("libdb: lock not available") // ErrQueryCanceled corresponds to query cancellation (e.g., PostgreSQL code 57014 or context cancellation). ErrQueryCanceled = errors.New("libdb: query canceled") // ErrDataTruncation corresponds to data truncation errors (e.g., PostgreSQL code 22001). ErrDataTruncation = errors.New("libdb: data truncation error") // ErrNumericOutOfRange corresponds to numeric overflow errors (e.g., PostgreSQL code 22003). ErrNumericOutOfRange = errors.New("libdb: numeric value out of range") // ErrInvalidInputSyntax corresponds to syntax errors in data representation (e.g., PostgreSQL code 22P02). ErrInvalidInputSyntax = errors.New("libdb: invalid input syntax") // ErrUndefinedColumn corresponds to referencing an unknown column (e.g., PostgreSQL code 42703). ErrUndefinedColumn = errors.New("libdb: undefined column") // ErrUndefinedTable corresponds to referencing an unknown table (e.g., PostgreSQL code 42P01). ErrUndefinedTable = errors.New("libdb: undefined table") )
Predefined errors for common database interaction scenarios. Using these allows application code to check for specific conditions using errors.Is without relying on driver-specific error types or codes.
Functions ¶
func SetupLocalInstance ¶
Types ¶
type CommitTx ¶
CommitTx is a function type responsible for attempting to commit a transaction. It should typically only be called on the success path of a transactional operation. It may check the context before committing and returns ErrTxFailed or a translated database error if the commit fails.
type DBManager ¶
type DBManager interface {
// WithoutTransaction returns an executor that operates directly on the underlying
// database connection pool (i.e., outside of an explicit transaction).
// Each operation may run on a different connection.
WithoutTransaction() Exec
// WithTransaction starts a new database transaction and returns an executor
// bound to that transaction, a function to commit the transaction, and a function
// to release (roll back) the transaction.
//
// The returned ReleaseTx function is designed to be deferred to ensure the transaction
// is always finalized (rolled back on error/panic, or a no-op after successful commit)
// and the connection is released. See package documentation for usage patterns.
WithTransaction(ctx context.Context) (Exec, CommitTx, ReleaseTx, error)
// Close terminates the underlying database connection pool.
// It should be called when the application is shutting down.
Close() error
}
DBManager defines the interface for obtaining database executors and managing the database connection lifecycle. It serves as the main entry point for database interactions.
func NewPostgresDBManager ¶
NewPostgresDBManager creates a new DBManager for PostgreSQL. It opens a connection pool using the provided DSN, pings the database to verify connectivity, and optionally executes an initial schema setup query. Note: For production schema management, using dedicated migration tools is recommended over passing a simple schema string here.
type Exec ¶
type Exec interface {
// ExecContext executes a query without returning any rows.
// The args are for any placeholder parameters in the query.
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
// QueryContext executes a query that returns rows, typically a SELECT.
// The args are for any placeholder parameters in the query.
// Callers should check rows.Err() after iterating.
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
// QueryRowContext executes a query that is expected to return at most one row.
// QueryRowContext always returns a non-nil value. Errors are deferred until
// QueryRower's Scan method is called.
QueryRowContext(ctx context.Context, query string, args ...any) QueryRower
}
Exec defines the common interface for executing database operations, whether within a transaction or directly on the connection pool. Errors returned by methods implementing this interface should be translated into the package's predefined Err* variables where applicable.
type QueryRower ¶
type QueryRower interface {
// Scan copies the columns from the matched row into the values pointed at by dest.
// If no rows were found, it returns ErrNotFound. Other scan errors are translated.
Scan(dest ...any) error
}
QueryRower provides the Scan method, typically implemented by wrapping *sql.Row. Using this interface allows Scan errors (like sql.ErrNoRows) to be translated consistently by the library.
type ReleaseTx ¶
type ReleaseTx func() error
ReleaseTx is a function type responsible for rolling back a transaction, ensuring its resources are released. It is designed to be idempotent (safe to call multiple times or after a commit) and is ideal for use with `defer` to guarantee cleanup. It returns ErrTxFailed or a translated database error if the rollback fails (and the transaction wasn't already finalized).