Files
postgres/docs/adr/ADR-001-pgx-native-types.md
Rene Nochebuena 2baafa6a0c feat(postgres): initial stable release v0.9.0
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/
2026-03-19 13:18:07 +00:00

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.CommandTag carries richer information than sql.Result (includes the command string, not just rows affected).
  • Positive: pgx.Rows and pgx.Row support pgx scan helpers and named arguments.
  • Negative: Repository code cannot be trivially swapped to use the mysql module or any other database/sql driver — 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 on Executor.
  • Negative: pgx.Rows must be closed after iteration (defer rows.Close()). Forgetting this leaks connections. This is the same discipline as database/sql but worth noting.