Documentation
¶
Overview ¶
Package postgres provides a pgx-backed PostgreSQL client with lifecycle management, health checks, and unit-of-work transaction support for Einherjar applications.
Overview ¶
New returns a Component that manages a pgxpool.Pool connection pool, satisfies the lifecycle.Component lifecycle hooks (OnInit, OnStart, OnStop), and implements observability.Checkable with critical priority.
NewUnitOfWork wraps multiple repository operations in a single transaction via context injection — no transaction object is passed between functions.
Lifecycle Registration ¶
db := postgres.New(logger, cfg) launcher.Register(db) health.Register(db)
Querying ¶
Repository code receives a Provider or Executor and calls [Provider.GetExecutor] to obtain the active transaction (if inside a UnitOfWork) or the pool:
exec := db.GetExecutor(ctx)
row := exec.QueryRow(ctx, "SELECT id FROM users WHERE email = $1", email)
if err := row.Scan(&id); err != nil {
return db.HandleError(err) // maps pgx.ErrNoRows → ErrNotFound, etc.
}
Unit of Work ¶
NewUnitOfWork wraps operations in a single transaction. The transaction is injected into the context; [Provider.GetExecutor] returns it automatically.
uow := postgres.NewUnitOfWork(logger, db)
err := uow.Do(ctx, func(ctx context.Context) error {
exec := db.GetExecutor(ctx) // returns active Tx
_, err := exec.Exec(ctx, "INSERT INTO orders ...")
return err
})
Error Handling ¶
HandleError translates pgx and PostgreSQL error codes into typed xerrors values. Call it at every point where a pgx error is first observed:
if err := row.Scan(&out); err != nil {
return db.HandleError(err)
}
Mapped codes:
- UniqueViolation → ErrAlreadyExists
- ForeignKeyViolation → ErrInvalidInput
- CheckViolation → ErrInvalidInput
- pgx.ErrNoRows → ErrNotFound
- all others → ErrInternal
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func HandleError ¶
HandleError maps pgx and PostgreSQL errors to typed xerrors values. Returns nil when err is nil. Also available as [Provider.HandleError].
Mapped codes:
- UniqueViolation → ErrAlreadyExists
- ForeignKeyViolation → ErrInvalidInput
- CheckViolation → ErrInvalidInput
- pgx.ErrNoRows → ErrNotFound
- all others → ErrInternal
Types ¶
type Component ¶
type Component interface {
lifecycle.Component
observability.Checkable
observability.Identifiable
Provider
// Stats returns connection pool statistics. Returns a zero-value stat
// when the pool has not been initialized yet.
Stats() *pgxpool.Stat
}
Component bundles the full postgres capability: lifecycle management, health reporting, and the database Provider interface. Register with launcher and health before starting.
type Config ¶
type Config struct {
Host string `env:"EINHERJAR_PG_HOST,required"`
Port int `env:"EINHERJAR_PG_PORT" envDefault:"5432"`
User string `env:"EINHERJAR_PG_USER,required"`
Password string `env:"EINHERJAR_PG_PASSWORD,required"`
Name string `env:"EINHERJAR_PG_NAME,required"`
SSLMode string `env:"EINHERJAR_PG_SSL_MODE" envDefault:"disable"`
Timezone string `env:"EINHERJAR_PG_TIMEZONE" envDefault:"UTC"`
MaxConns int `env:"EINHERJAR_PG_MAX_CONNS" envDefault:"5"`
MinConns int `env:"EINHERJAR_PG_MIN_CONNS" envDefault:"2"`
MaxConnLifetime string `env:"EINHERJAR_PG_MAX_CONN_LIFETIME" envDefault:"1h"`
MaxConnIdleTime string `env:"EINHERJAR_PG_MAX_CONN_IDLE_TIME" envDefault:"30m"`
HealthCheckPeriod string `env:"EINHERJAR_PG_HEALTH_CHECK_PERIOD" envDefault:"1m"`
}
Config holds PostgreSQL connection settings. Required fields must be supplied by the caller; optional fields have production-safe defaults via DefaultConfig.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with all optional fields set to production-safe defaults. Callers must supply Host, Port, User, Password, and Name.
type Executor ¶
type Executor interface {
// Exec executes a query that returns no rows.
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
// Query executes a query that returns rows.
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
// QueryRow executes a query that returns at most one row.
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}
Executor is the shared query interface for both the connection pool and an active transaction. Repository code accepts Executor so it works identically inside and outside a UnitOfWork.
type Provider ¶
type Provider interface {
// GetExecutor returns the active transaction injected by [UnitOfWork] if one
// is present in ctx, otherwise returns the connection pool.
GetExecutor(ctx context.Context) Executor
// Begin starts a new transaction with default options.
Begin(ctx context.Context) (Tx, error)
// BeginTx starts a new transaction with the given options.
BeginTx(ctx context.Context, opts pgx.TxOptions) (Tx, error)
// Ping verifies that the database connection is alive.
Ping(ctx context.Context) error
// HandleError maps a pgx or PostgreSQL error to a typed [xerrors] value.
// Call this at every point where a pgx error is first observed.
HandleError(err error) error
}
Provider is the database access interface consumed by repositories and services. All methods are safe for concurrent use.
type Tx ¶
type Tx interface {
Executor
// Commit commits the transaction.
Commit(ctx context.Context) error
// Rollback rolls back the transaction. Safe to call after Commit.
Rollback(ctx context.Context) error
}
Tx extends Executor with commit and rollback. Obtained via [Provider.Begin] or [Provider.BeginTx] when manual transaction control is needed. Prefer UnitOfWork for the common case of a single transactional unit.
type UnitOfWork ¶
type UnitOfWork interface {
// Do begins a transaction, calls fn with the enriched context, and commits
// on success or rolls back on error. The original fn error is always returned
// when fn fails, regardless of the rollback outcome.
Do(ctx context.Context, fn func(ctx context.Context) error) error
}
UnitOfWork wraps a set of repository operations in a single database transaction. The transaction is injected into the context; [Provider.GetExecutor] returns it automatically so repository code requires no changes to participate.
func NewUnitOfWork ¶
func NewUnitOfWork(logger logging.Logger, client Provider) UnitOfWork
NewUnitOfWork returns a UnitOfWork backed by the given client.