# postgres pgx-backed PostgreSQL client with launcher lifecycle, health check integration, and unit-of-work transaction management. ## Purpose Provides a `Component` that manages a `pgxpool` connection pool, satisfies the `launcher.Component` lifecycle hooks (`OnInit`, `OnStart`, `OnStop`), and implements `health.Checkable` (priority: critical). Also provides `NewUnitOfWork` for wrapping multiple repository operations in a single transaction via context injection. ## Tier & Dependencies **Tier 3** (infrastructure) — depends on: - `code.nochebuena.dev/go/health` (Tier 1) - `code.nochebuena.dev/go/launcher` (Tier 1) - `code.nochebuena.dev/go/logz` (Tier 0) - `code.nochebuena.dev/go/xerrors` (Tier 0) - `github.com/jackc/pgx/v5` and `pgxpool`, `pgconn` sub-packages - `github.com/jackc/pgerrcode` ## Key Design Decisions - **pgx native types** (ADR-001): `Executor` uses `pgconn.CommandTag`, `pgx.Rows`, and `pgx.Row`. There is no `database/sql` adapter. Repository code imports pgx types directly. - **Local Executor interface** (ADR-002): `dbutil` was eliminated. `Executor`, `Tx`, `Client`, and `Component` are all defined in this package using pgx types. The `mysql` module has its own independent `Executor`. - **UnitOfWork via context injection** (ADR-003): The active `pgx.Tx` is stored in the context under `ctxTxKey{}` (unexported). `GetExecutor(ctx)` returns the transaction if present, otherwise the pool. `Tx.Commit` and `Tx.Rollback` both accept `ctx`. - **Error mapping via pgerrcode constants**: `HandleError` uses `pgerrcode.UniqueViolation`, `pgerrcode.ForeignKeyViolation`, and `pgerrcode.CheckViolation` (not string-matched error messages) to map `*pgconn.PgError` to xerrors codes. - **`health.Checkable` embedded in Component**: `pgComponent.HealthCheck` delegates to `Ping`. Priority is `health.LevelCritical` — a PostgreSQL outage brings down the service. ## Patterns Lifecycle registration: ```go db := postgres.New(logger, cfg) lc.Append(db) r.Get("/health", health.NewHandler(logger, db)) ``` Unit of Work across multiple repositories: ```go 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 ...") return err }) ``` Error handling in repository code: ```go row := db.GetExecutor(ctx).QueryRow(ctx, "SELECT ...") if err := row.Scan(&out); err != nil { return db.HandleError(err) // maps pgx.ErrNoRows → ErrNotFound, etc. } ``` ## What to Avoid - Do not use `database/sql` types (`sql.Rows`, `sql.Result`, etc.) alongside this module. The module is pgx-native; mixing the two type systems requires explicit adaptation. - Do not store `Executor` references across goroutine boundaries during a `UnitOfWork.Do`. The transaction is tied to the context passed into `Do`; goroutines that outlive `Do` will use a committed or rolled-back transaction. - Do not match PostgreSQL error codes by parsing error message strings. Use `pgerrcode` constants and `errors.As(err, &pgErr)` as `HandleError` does. - Do not add `sync.Once` or package-level pool variables. `pgComponent` is the unit of construction; create one per database and pass it as a dependency. - Do not call `db.GetExecutor(ctx)` from outside the `UnitOfWork.Do` callback if you need transactional semantics — it will return the pool. ## Testing Notes - `postgres_test.go` tests compile-time interface satisfaction. Integration tests (pool, real queries) require a live PostgreSQL instance and are typically run in CI with a service container. - `compliance_test.go` asserts at compile time that `postgres.New(...)` satisfies `postgres.Component`. - `HandleError` can be unit-tested by constructing `*pgconn.PgError` with known `Code` values — no database connection needed. - Pool initialization happens in `OnInit`, not `New`. Tests that mock the `Client` interface bypass pool setup entirely.