pgx v5-native PostgreSQL client with launcher lifecycle, health check, unit-of-work via context injection, and structured error mapping. What's included: - Executor / Tx / Client / Component interfaces using pgx native types (pgconn.CommandTag, pgx.Rows, pgx.Row) - New(logger, cfg) constructor; pgxpool initialised in OnInit - Config struct with env-tag support for all pool tuning parameters - UnitOfWork via context injection; GetExecutor(ctx) returns active Tx or pool - HandleError mapping pgerrcode constants to xerrors codes (AlreadyExists, InvalidInput, NotFound, Internal) - health.Checkable at LevelCritical; HealthCheck delegates to pgxpool.Ping Tested-via: todo-api POC integration Reviewed-against: docs/adr/
2.5 KiB
ADR-001: pgx Native Types
Status: Accepted Date: 2026-03-18
Context
Go's standard database/sql package provides a database-agnostic interface. Using it with PostgreSQL requires a database/sql-compatible driver and means working with sql.Result, *sql.Rows, and *sql.Row — types that were designed for the lowest common denominator across all SQL databases.
github.com/jackc/pgx/v5 is a PostgreSQL-specific driver and toolkit that exposes its own richer type system: pgx.Rows, pgx.Row, and pgconn.CommandTag. It provides better performance, native support for PostgreSQL-specific types (arrays, hstore, composite types, etc.), and a more accurate representation of PostgreSQL semantics (e.g., CommandTag carries RowsAffected as well as the SQL command string).
The tradeoff is that choosing pgx means explicitly not supporting other databases through the same client type.
Decision
The postgres module uses pgx native types throughout its public API. The Executor interface uses:
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
The connection pool is *pgxpool.Pool (from pgx/v5/pgxpool). The transaction type wraps pgx.Tx. There is no database/sql adapter layer.
Repository code in application layers imports pgx types directly when scanning rows or reading CommandTag. This is an explicit, honest API: it says "this is PostgreSQL via pgx" rather than pretending to be database-agnostic.
Consequences
- Positive: Full access to PostgreSQL-specific capabilities (binary encoding, COPY protocol, listen/notify, array types, etc.) without impedance mismatch.
- Positive:
pgconn.CommandTagcarries richer information thansql.Result(includes the command string, not just rows affected). - Positive:
pgx.Rowsandpgx.Rowsupport pgx scan helpers and named arguments. - Negative: Repository code cannot be trivially swapped to use the
mysqlmodule or any otherdatabase/sqldriver — it imports pgx types. This is acceptable because the tier system isolates database clients at Tier 3; application logic in higher tiers operates through domain interfaces, not directly onExecutor. - Negative:
pgx.Rowsmust be closed after iteration (defer rows.Close()). Forgetting this leaks connections. This is the same discipline asdatabase/sqlbut worth noting.