pgfx

package module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 20 Imported by: 0

README

pgfx

CI Go Reference

A thin, Fx-first layer over pgx for Postgres: a connection built from a plain config, with generic query methods, tracing and lifecycle.

  • Config is data, runtime is options - what survives a round trip through text is Config; a tracer, a metrics callback, an in-memory *tls.Config are Option values. Nothing is configured twice.
  • It reads no config source - the application supplies the Config, so pgfx is tied to no particular loader.
  • A bad endpoint fails the start, not the first query.
  • No second query API - DB and Tx add typed fetches while preserving pgx for execution, transactions and advanced operations.
go get github.com/uchaloop/pgfx

Quick start

fx.New(
	confx.Module(),
	confx.Provide[pgfx.Config]("postgres"),

	pgfx.Module,          // untagged *pgfx.DB, verified at start, closed at stop
)

A replica is another named connection - the name gives both the Fx tag and the environment prefix:

confx.ProvideNamed[pgfx.Config]("replica"),   // REPLICA_HOST, REPLICA_DATABASE, ...
pgfx.ModuleFor("replica"),

Without Fx: db, err := pgfx.Make(ctx, cfg).

Fx provides only *pgfx.DB; the embedded pool is available as db.Pool and is not registered separately.

Queries

order, err := db.FetchRow[Order](
	ctx,
	`SELECT id, amount FROM orders WHERE id = $1`, id,
)

total, err := db.FetchValue[int64](ctx, `SELECT count(*) FROM orders`)
tag, err := db.Exec(ctx, `UPDATE orders SET status = $1 WHERE id = $2`, status, id)

FetchRows and FetchRow decode struct rows by the db:"..." tag; FetchValues and FetchValue decode a single column into a scalar. The type to decode into is an explicit type argument, because nothing in the arguments implies it.

Pagination

FetchPage returns one page and the number of rows the filter matches. The query stays a plain SELECT - no ORDER BY, no LIMIT, no row numbers, no count - and the page is wrapped around it, so the filter is written once and the rows decode by the db tag like any other:

var warehouses = page.Must(fetchWarehousesSQL,
	page.Head(page.Desc("is_active")),   // pinned, before the client's sort
	page.Tie(page.Asc("id")),            // required: a page needs a total order
	page.SortKeyTag("json"),             // the client sorts by json names
)

rows, total, err := db.FetchPage[Warehouse](ctx, warehouses,
	page.Request{Number: 2, Size: 20, Sort: []string{"cityEng:desc"}},
	params.Country,
)

Sortable fields are derived from the model, so they cannot drift away from the columns that are actually there, and an unknown one is an error rather than a silent skip. The total takes a second statement, skipped whenever the rows already imply it - a page shorter than it asked for is the last one.

Page number and size arrive as a page.Request, apart from the filter: a default for a page a client did not fully specify belongs at the edge that parsed the request, and a zero here is a bug rather than a request for page one.

Transactions

Transaction commits on nil and rolls back on an error or panic. Its Tx has the same typed fetches as DB and embeds the native pgx.Tx API:

err := db.Transaction(ctx, pgx.TxOptions{}, func(tx *pgfx.Tx) error {
	order, err := tx.FetchRow[Order](ctx, selectOrder, id)
	if err != nil {
		return err
	}

	_, err = tx.Exec(ctx, updateOrder, order.ID)
	return err
})

Use tx, not db, inside the callback: a call through db uses the pool and does not participate in the transaction.

For manual ownership use the embedded native db.Begin or db.BeginTx; they return pgx.Tx. tx.BeginNested and tx.Transaction create typed pgx savepoints. The embedded pool also makes COPY, LISTEN, acquired connections and the rest of the native API available directly on DB; the pool itself is db.Pool.

What a deployment sets

POSTGRES_HOST=localhost:5432
POSTGRES_DATABASE=orders
POSTGRES_USER=orders
POSTGRES_PASSWORD=...

POSTGRES_TLS_MODE=verify-full
POSTGRES_TLS_ROOT_CERT=/run/secrets/ca.crt

POSTGRES_POOL_MAX_CONNS=20
POSTGRES_TIMEOUTS_CONNECT=5s

Host and database are required; everything else keeps the pgx default until it is set. confx.Manifest[pgfx.Config]("postgres") lists the whole set with types and defaults.

Documentation

The options, the config fields and the reasons behind them are in the package documentation: pkg.go.dev/github.com/uchaloop/pgfx.

Acknowledgements

I am grateful to the authors of pgx, Uber Fx, and otelpgx. Their work made this library possible.

License

MIT

Documentation

Overview

Package pgfx is a thin, Fx-first layer over pgx/pgxpool for Postgres. A pgfx connection is a DB: a pool built from a plain, serializable Config, carrying generic methods for the two things every query does - read rows, read one value.

Config and Option

The split between the two is the design. Config is data: it survives a round trip through text, so a deployment fills it and a manifest lists it. Everything that cannot survive that trip - a tracer, a metrics callback, a pool hook, an in-memory *tls.Config - is an Option instead. Nothing is configured twice, and nothing has to be expressed as a string that was never a string.

pgfx reads no configuration source of itself, which is what keeps it decoupled from any particular loader. An application supplies the Config, typically through github.com/uchaloop/confmaker, whose Manifest lists every variable a connection reads:

confx.Manifest[pgfx.Config]("postgres")

Host and Database have to be supplied. An empty User or Password falls through to pgx's libpq-compatible defaults, so peer authentication and a .pgpass keep working. Every pool and timeout field left at its zero value keeps the pgxpool default, so a deployment sets what it means to change and nothing else.

Wiring

Module provides an untagged *DB - the single default connection. It verifies the connection while the application starts, so a bad endpoint fails the start rather than the first query, and closes the pool on shutdown. Fx receives and provides only *DB; the embedded *pgxpool.Pool is not registered separately.

ModuleFor adds a replica or a shard under a name, which gives both the Fx tag and the environment prefix:

fx.New(
	confx.Module(),
	confx.Provide[pgfx.Config]("postgres"),
	confx.ProvideNamed[pgfx.Config]("replica"),

	pgfx.Module,
	pgfx.ModuleFor("replica"),
)

A named connection inherits nothing from the default one. Its database, user and TLS settings are given again under its own prefix - a replica that silently borrowed the primary's credentials would be the kind of thing noticed in production.

Several databases are several named connections; there is no other mechanism. Without Fx, Make builds a DB from a Config filled by hand.

Queries

FetchRow and FetchRows decode structs by `db` tag or field name:

order, err := db.FetchRow[Order](
	ctx,
	`SELECT id, amount FROM orders WHERE id = $1`, id,
)

orders, err := db.FetchRows[Order](
	ctx,
	`SELECT id, amount FROM orders ORDER BY id`,
)

FetchValue and FetchValues decode one column:

total, err := db.FetchValue[int64](ctx, `SELECT count(*) FROM orders`)

ids, err := db.FetchValues[int64](
	ctx,
	`SELECT id FROM orders ORDER BY id`,
)

The singular methods require exactly one row and report pgx.ErrNoRows or pgx.ErrTooManyRows otherwise. pgx.ErrNoRows wraps sql.ErrNoRows, so errors.Is matches either sentinel.

DB embeds *pgxpool.Pool. Native methods such as Exec, Query, CopyFrom, Acquire, and Close are therefore available directly:

tag, err := db.Exec(
	ctx,
	`UPDATE orders SET status = $1 WHERE id = $2`,
	"paid", id,
)

Use FetchValue rather than Exec for INSERT ... RETURNING.

Pagination

FetchPage returns one page of rows and how many rows the filter matches in total. The query stays a plain SELECT - no ORDER BY, no LIMIT, no row numbers, no count - and the page is applied around it, which is what keeps the filter written once and the rows decodable by the `db` tag like any other:

SELECT * FROM ( <query> ) AS pgfx_page ORDER BY ... LIMIT $n+1 OFFSET $n+2
SELECT count(*) FROM ( <query> ) AS pgfx_page

The sort policy is bound once, next to the SQL; the page number, its size and the client's sort arrive per call, apart from the filter arguments:

query, err := page.Make(fetchWarehousesSQL,
	page.Head(page.Desc("is_active")),
	page.Tie(page.Asc("id")),
	page.SortKeyTag("json"),
)

warehouses, total, err := db.FetchPage[Warehouse](ctx, query,
	page.Request{Number: 2, Size: 20, Sort: []string{"cityEng:desc"}},
	params.Country,
)

The sortable fields are derived from the model, so they cannot drift away from the columns that are there; an unknown one is an error rather than a silent skip. page.Tie is required, because a page without a total order repeats rows on one page and loses them from another. The details - narrowing the whitelist, counting over a cheaper statement, NULL placement - are in the github.com/uchaloop/pgfx/page documentation.

The second statement is skipped whenever the rows already imply the total: a page shorter than it asked for is the last one. When it does run, it is reported to the metrics under the caller's query name with a ".count" suffix - it is a different statement with a cost of its own, and it does not run every time.

An empty page is a valid result, not an error. Tx.FetchPage runs both statements inside a transaction, where a repeatable read isolation level makes the total agree with the page it describes.

Transactions

DB.Transaction is the typed transaction entry point. It commits when its callback returns nil and rolls back on an error or panic. The callback receives a Tx with the same Fetch methods as DB and the embedded native pgx.Tx API:

err := db.Transaction(ctx, pgx.TxOptions{}, func(tx *pgfx.Tx) error {
	order, err := tx.FetchRow[Order](ctx, selectOrder, id)
	if err != nil {
		return err
	}

	_, err = tx.Exec(ctx, updateOrder, order.ID)
	return err
})

Inside the callback, use tx rather than db. A call through db uses the pool and does not participate in the transaction.

The embedded pool's Begin and BeginTx remain the native manual API and return pgx.Tx:

tx, err := db.BeginTx(ctx, pgx.TxOptions{
	IsoLevel: pgx.Serializable,
})
if err != nil {
	return err
}
defer tx.Rollback(ctx)

if _, err := tx.Exec(ctx, insertOrder); err != nil {
	return err
}
return tx.Commit(ctx)

Tx.Transaction creates a typed savepoint:

err := db.Transaction(ctx, pgx.TxOptions{}, func(tx *pgfx.Tx) error {
	return tx.Transaction(ctx, func(nested *pgfx.Tx) error {
		_, err := nested.Exec(ctx, insertOptionalData)
		return err
	})
})

DB embeds *pgxpool.Pool, so COPY, LISTEN, acquired connections and the rest of the native pool API are available directly. The pool itself is the DB.Pool field; no accessor is needed.

TLS

TLS_MODE follows libpq, and verify-full with TLS_ROOT_CERT is the setting that actually verifies the server. WithTLS takes an in-memory *tls.Config instead, for an application that already assembled one - from a secret store, or from a certificate that never touched the filesystem.

Tracing and metrics

Under Fx, Module enables OpenTelemetry query spans by itself when a trace.TracerProvider is in the graph. WithTracing configures otelpgx directly, and WithTracer installs any pgx.QueryTracer.

Query, batch and COPY metrics are reported through a callback rather than to a metrics library, so pgfx holds no opinion about where they go:

fx.Supply(pgfx.QueryMetricFunc(func(metric pgfx.QueryMetric) {
	// Use metric.Name and metric.Kind.String() as bounded labels.
	// Record metric.Duration, metric.RowsAffected and metric.Err.
	// This callback must be concurrency-safe, cheap and non-blocking.
}))

WithQueryName attaches a stable name to a query through the context, because the SQL itself is not a metric label - it varies, and it is unbounded.

ctx = pgfx.WithQueryName(ctx, "orders.get_by_id")

Outside Fx, install the callback with WithQueryMetrics. A batch produces one observation, including results drained by Close:

batch := &pgx.Batch{}
batch.Queue("insert into orders (id) values ($1)", 1)
batch.Queue("insert into orders (id) values ($1)", 2)
results := db.SendBatch(pgfx.WithQueryName(ctx, "orders.sync"), batch)
if err := results.Close(); err != nil {
	return err
}

COPY is observed through the same callback with KindCopyFrom:

_, err := db.CopyFrom(pgfx.WithQueryName(ctx, "orders.load"),
	pgx.Identifier{"orders"}, []string{"id"},
	pgx.CopyFromRows([][]any{{int64(3)}, {int64(4)}}))

Duration measures the pgx operation lifecycle, not server time or a pure round trip: it includes preparation, result consumption, delays before batch Close, and COPY source production, but excludes pool acquisition. Batch row counts sum observed command tags; they can be partial on error and do not imply a commit.

Coverage follows pgx hooks. Empty batches and pool acquisition failures produce no metric. In pgx v5.10, early COPY statement-description errors also omit the end hook and are not reported. Always close batch results.

SQL is omitted by default. WithSQLInQueryMetrics opts in to raw query text; batch statements are joined with "; " before rewriting, without interpolating arguments, and may include statements that never execute. COPY leaves SQL empty. Never use SQL as a label; scrub it before recording.

Index

Constants

This section is empty.

Variables

View Source
var Module = fx.Module("pgfx", connectionProvider(``))

Module is an Fx module for the default Postgres connection: it consumes an untagged pgfx.Config from the container and provides only an untagged *DB, so a repository just depends on *DB - no tags, no wrappers. The pool embedded in DB is not provided as a separate Fx value.

The application supplies the Config explicitly - pgfx does not read any config source itself - typically with confmaker/confx:

fx.New(
	confx.Module(),
	confx.Provide[pgfx.Config]("postgres"),
	pgfx.Module,
)

The connection is pinged on start (fail-fast on a dead database) and closed on stop, and takes an optional trace.TracerProvider and QueryMetricFunc from the container.

Functions

func ModuleFor

func ModuleFor(name string) fx.Option

ModuleFor is an Fx module for a named connection - a replica or another shard. It consumes a pgfx.Config tagged name:"<name>" and provides only a *DB tagged the same; a consumer selects it with fx.ParamTags. The application supplies the tagged Config explicitly (typically confmaker/confx's Provide).

func QueryName

func QueryName(ctx context.Context) string

QueryName returns the name set by WithQueryName, or "" if none.

func WithQueryName

func WithQueryName(ctx context.Context, name string) context.Context

WithQueryName tags ctx with a bounded query name used as QueryMetric.Name. Set it per call site (e.g. "orders.get_by_id"); keep the set of names small.

Types

type Config

type Config struct {
	// Host is the endpoint as "host" or "host:port". A port in the string wins;
	// when omitted, 5432 applies. IPv6 with a port must be bracketed
	// ("[::1]:5433").
	Host string `env:"HOST,notEmpty"`
	// Database is the PostgreSQL database name.
	Database string `env:"DATABASE,notEmpty"`
	// User is the PostgreSQL role. When empty, libpq selects its default.
	User string `env:"USER"`
	// Password is read from the environment only. When empty, libpq may use
	// PGPASSWORD or .pgpass.
	Password secret.Secret `env:"PASSWORD"`
	// AppName is reported as application_name in pg_stat_activity.
	AppName string `env:"APP_NAME"`

	// TLS configures libpq transport security.
	TLS TLSConfig `envPrefix:"TLS_"`
	// Pool configures pgxpool sizing and connection lifetimes.
	Pool PoolConfig `envPrefix:"POOL_"`
	// Timeouts configures connection timeouts.
	Timeouts TimeoutConfig `envPrefix:"TIMEOUTS_"`
}

Config is the serializable configuration of a single Postgres connection. It carries only plain, serializable values - no tracer, hooks, or other runtime dependencies (those are passed to Make as Option values).

The `env` tags let a loader fill it from the environment (see confmaker/confx). They are inert strings, so the Config type itself depends only on the standard-library-only github.com/uchaloop/secret/v2 module.

Host and Database must be supplied by the deployment. User and Password are optional: when empty they fall back to libpq's defaults (PGUSER / the OS user, and PGPASSWORD / .pgpass).

func (Config) Validate

func (cfg Config) Validate() error

Validate checks what the values mean, once a loader has supplied them: every problem at once, not just the first, so a misconfigured deployment takes one rollout to fix rather than one per mistake.

type DB added in v0.5.0

type DB struct {
	*pgxpool.Pool
}

DB is a Postgres connection: a pool, and the queries bound to it. It is what Make returns and what the Fx modules provide, so a repository depends on a DB rather than on a pool plus a set of free functions.

A DB is safe for concurrent use, as the pool behind it is. Its zero value is not usable.

func Make

func Make(ctx context.Context, cfg Config, opts ...Option) (*DB, error)

Make validates cfg and opens a connection for it, applying the runtime opts. The caller owns the returned DB and must call Close; the Fx module does that through the lifecycle.

func (*DB) FetchPage added in v0.6.0

func (d *DB) FetchPage[T any](
	ctx context.Context,
	query page.Query,
	req page.Request,
	args ...any,
) ([]T, uint, error)

FetchPage runs a paginated query and decodes one page of struct rows into T, with the number of rows the filter matches in total. The page request carries the page number, its size and the client's sort; the rest of the arguments are the query's own, numbered from $1.

The page is applied around the query - ORDER BY, LIMIT and OFFSET are wrapped on the outside - so the query stays a plain SELECT and its columns decode by the `db:"..."` tag as everywhere else. The total takes a second statement, which is skipped whenever the returned rows already imply it. An empty page is a valid result, not an error.

func (*DB) FetchRow added in v0.5.0

func (d *DB) FetchRow[T any](ctx context.Context, sql string, args ...any) (T, error)

FetchRow runs the query and decodes exactly one struct row into T. It reports pgx.ErrNoRows when the query yields no rows and pgx.ErrTooManyRows when it yields more than one. For a scalar result use FetchValue.

func (*DB) FetchRows added in v0.5.0

func (d *DB) FetchRows[T any](ctx context.Context, sql string, args ...any) ([]T, error)

FetchRows runs the query and decodes every row into a struct T. Columns map to struct fields by the `db:"..."` tag, falling back to the field name. For a single-column result use FetchValues.

func (*DB) FetchValue added in v0.5.0

func (d *DB) FetchValue[T any](ctx context.Context, sql string, args ...any) (T, error)

FetchValue runs the query and decodes exactly one single-column row into a scalar T. It reports pgx.ErrNoRows when the query yields no rows and pgx.ErrTooManyRows when it yields more than one. For a struct use FetchRow.

func (*DB) FetchValues added in v0.5.0

func (d *DB) FetchValues[T any](ctx context.Context, sql string, args ...any) ([]T, error)

FetchValues runs the query and decodes a single-column result into a slice of scalar T (int64, string, uuid.UUID, ...). For struct rows use FetchRows.

func (*DB) Transaction added in v0.5.0

func (d *DB) Transaction(
	ctx context.Context,
	opts pgx.TxOptions,
	fn func(*Tx) error,
) error

Transaction runs fn in a transaction. A nil error commits; a non-nil error rolls back and is returned. A panic rolls back and continues unwinding. Use the callback's Tx for every transactional operation; calls through d use the pool and are outside this transaction.

type Option

type Option func(*options)

Option configures the runtime behaviour of a connection built by Make.

func WithAfterConnect

func WithAfterConnect(fn func(context.Context, *pgx.Conn) error) Option

WithAfterConnect installs pgxpool.Config.AfterConnect - the usual place for custom pgx type registration.

func WithBeforeConnect

func WithBeforeConnect(fn func(context.Context, *pgx.ConnConfig) error) Option

WithBeforeConnect installs pgxpool.Config.BeforeConnect - invoked before establishing each new connection (e.g. to rotate a password).

func WithDefaultQueryExecMode

func WithDefaultQueryExecMode(mode pgx.QueryExecMode) Option

WithDefaultQueryExecMode sets the default pgx.QueryExecMode.

func WithQueryMetrics

func WithQueryMetrics(fn QueryMetricFunc) Option

WithQueryMetrics reports query, batch and COPY metrics alongside any other tracers. See QueryMetricFunc for callback requirements and coverage limits. nil disables it.

func WithRuntimeParam

func WithRuntimeParam(key, value string) Option

WithRuntimeParam adds a single pgx.ConnConfig.RuntimeParams value (SET key = value on connect).

func WithSQLInQueryMetrics

func WithSQLInQueryMetrics(include bool) Option

WithSQLInQueryMetrics includes the raw SQL in QueryMetric.SQL (default off). Enable only when statements are safe to expose.

func WithStatementCacheCapacity

func WithStatementCacheCapacity(capacity int) Option

WithStatementCacheCapacity sets the statement-cache size per connection.

func WithTLS

func WithTLS(tlsConfig *tls.Config) Option

WithTLS sets the connection's *tls.Config directly, overriding whatever the file-based Config.TLS produced (sslmode and the cert/key/root-cert paths). Use it when certificates are not files on disk - loaded from a secret manager, held in memory, or rotated at runtime: the application builds the *tls.Config and passes it here. It also removes libpq TLS/plaintext fallbacks so every connection attempt uses exactly this configuration. A nil value disables TLS.

func WithTracer

func WithTracer(tracer pgx.QueryTracer) Option

WithTracer adds a custom pgx.QueryTracer (for example an AWS X-Ray tracer). It composes with WithTracing and WithQueryMetrics rather than replacing them.

func WithTracing

func WithTracing(opts ...otelpgx.Option) Option

WithTracing enables OpenTelemetry query spans via otelpgx. Tracing is off by default; this turns it on. Configure it with otelpgx's own options, e.g. WithTracing(otelpgx.WithTracerProvider(tp), otelpgx.WithTrimSQLInSpanName()).

type PoolConfig

type PoolConfig struct {
	// MaxConns is the maximum pool size.
	MaxConns int32 `env:"MAX_CONNS"`
	// MinConns is the minimum number of connections maintained by the pool.
	MinConns int32 `env:"MIN_CONNS"`
	// MinIdleConns is the minimum number of idle connections maintained by the
	// pool.
	MinIdleConns int32 `env:"MIN_IDLE_CONNS"`
	// MaxConnLifetime is the maximum lifetime of a connection.
	MaxConnLifetime time.Duration `env:"MAX_CONN_LIFETIME"`
	// MaxConnLifetimeJitter randomizes connection expiry to avoid synchronized
	// reconnects.
	MaxConnLifetimeJitter time.Duration `env:"MAX_CONN_LIFETIME_JITTER"`
	// MaxConnIdleTime is the maximum time a connection may remain idle.
	MaxConnIdleTime time.Duration `env:"MAX_CONN_IDLE_TIME"`
	// HealthPeriod controls how often pgxpool checks idle connections.
	HealthPeriod time.Duration `env:"HEALTH_PERIOD"`
}

PoolConfig holds pgxpool sizing and connection-lifecycle settings. Zero values leave the pgxpool defaults in place.

type QueryKind added in v0.7.0

type QueryKind uint8

QueryKind is the operation a QueryMetric describes.

Use it as a bounded metric label to separate queries, batches and bulk loads.

const (
	// KindQuery is a single query - Query, QueryRow or Exec. It is the zero
	// value, so a metric that says nothing about its kind is a query.
	KindQuery QueryKind = iota
	// KindBatch is one SendBatch operation, including reading its results.
	KindBatch
	// KindCopyFrom is one CopyFrom.
	KindCopyFrom
)

func (QueryKind) String added in v0.7.0

func (k QueryKind) String() string

String returns the kind as a metric label value.

type QueryMetric

type QueryMetric struct {
	// Kind is what was observed: a query, a batch or a copy. Use it as a metric
	// label - it is bounded - to keep the three apart.
	Kind QueryKind
	// Name is the bounded label from WithQueryName ("" if unset) - use it as the
	// metric label, never SQL (raw statements have unbounded cardinality).
	Name string
	// SQL is the raw statement when explicitly enabled with WithSQLInQueryMetrics;
	// otherwise it is empty. For a batch it is every queued statement joined with
	// "; " before query rewriting, including statements that may not execute.
	// For a copy it is empty. Arguments are not interpolated. Do NOT use it as a
	// metric label, and scrub it before recording; batches can produce long text.
	SQL string
	// Duration is elapsed time between pgx start/end hooks, not server execution
	// or network latency. It excludes pool acquisition and includes preparation,
	// result consumption (including delays before BatchResults.Close), and COPY
	// source production as applicable. Always close batch results.
	Duration time.Duration
	// RowsAffected is the number of rows affected or returned as reported by pgx,
	// summed over observed batch command tags. It can be partial on error and
	// does not indicate committed changes: the transaction may roll back.
	RowsAffected int64
	// Err is non-nil when the operation failed. For a batch it is the error the
	// batch ended with, or the first statement error when the batch itself
	// reported none.
	Err error
}

QueryMetric is one observed operation, passed to a QueryMetricFunc.

type QueryMetricFunc

type QueryMetricFunc func(QueryMetric)

QueryMetricFunc receives one QueryMetric per completed pgx trace lifecycle. A batch produces at most one metric, not one per statement. The callback must be concurrency-safe, cheap and non-blocking; different connections call it concurrently. The package ships no metrics dependency.

Coverage follows pgx hooks: pool acquisition failures and empty batches are not observed. In pgx v5.10, some early CopyFrom failures (such as statement description errors) omit the end hook and therefore produce no metric.

type TLSConfig

type TLSConfig struct {
	// Mode is the libpq sslmode (disable/allow/prefer/require/verify-ca/
	// verify-full). Empty leaves the libpq default ("prefer").
	//
	// SECURITY: "prefer"/"allow" fall back to unencrypted and never verify the
	// server certificate; "require" encrypts but still does not verify it. Use
	// "verify-full" with RootCert for MITM protection.
	Mode string `env:"MODE"`
	// Cert is the path to the client certificate.
	Cert string `env:"CERT"`
	// Key is the path to the client private key.
	Key string `env:"KEY"`
	// RootCert is the path to the trusted root certificate.
	RootCert string `env:"ROOT_CERT"`
	// ServerName overrides the TLS server name used for verification.
	ServerName string `env:"SERVER_NAME"`
}

TLSConfig holds the libpq TLS settings.

type TimeoutConfig

type TimeoutConfig struct {
	// Connect bounds establishing a single connection. Zero leaves the pgx
	// default.
	Connect time.Duration `env:"CONNECT"`
}

TimeoutConfig holds connection timeouts.

type Tx

type Tx struct {
	pgx.Tx
}

Tx is a Postgres transaction with the same generic fetch methods as DB. It embeds the native pgx transaction API and adds typed fetch helpers.

A Tx is not safe for concurrent use. Its zero value is not usable.

func (*Tx) BeginNested added in v0.5.0

func (t *Tx) BeginNested(ctx context.Context) (*Tx, error)

BeginNested starts a pseudo-nested transaction implemented by pgx with a savepoint. The caller must call Commit or Rollback. Transaction is the safer callback-based alternative.

func (*Tx) FetchPage added in v0.6.0

func (t *Tx) FetchPage[T any](
	ctx context.Context,
	query page.Query,
	req page.Request,
	args ...any,
) ([]T, uint, error)

FetchPage runs a paginated query and decodes one page of struct rows into T, with the number of rows the filter matches in total. It is DB.FetchPage inside a transaction, where the rows and the count see one snapshot - under a repeatable read isolation level, a total that cannot disagree with the page it describes.

func (*Tx) FetchRow added in v0.5.0

func (t *Tx) FetchRow[T any](ctx context.Context, sql string, args ...any) (T, error)

FetchRow runs the query and decodes exactly one struct row into T. It reports pgx.ErrNoRows when the query yields no rows and pgx.ErrTooManyRows when it yields more than one. For a scalar result use FetchValue.

func (*Tx) FetchRows added in v0.5.0

func (t *Tx) FetchRows[T any](ctx context.Context, sql string, args ...any) ([]T, error)

FetchRows runs the query and decodes every row into a struct T. Columns map to struct fields by the `db:"..."` tag, falling back to the field name. For a single-column result use FetchValues.

func (*Tx) FetchValue added in v0.5.0

func (t *Tx) FetchValue[T any](ctx context.Context, sql string, args ...any) (T, error)

FetchValue runs the query and decodes exactly one single-column row into a scalar T. It reports pgx.ErrNoRows when the query yields no rows and pgx.ErrTooManyRows when it yields more than one. For a struct use FetchRow.

func (*Tx) FetchValues added in v0.5.0

func (t *Tx) FetchValues[T any](ctx context.Context, sql string, args ...any) ([]T, error)

FetchValues runs the query and decodes a single-column result into a slice of scalar T (int64, string, uuid.UUID, ...). For struct rows use FetchRows.

func (*Tx) Transaction added in v0.5.0

func (t *Tx) Transaction(ctx context.Context, fn func(*Tx) error) error

Transaction runs fn in a pseudo-nested transaction implemented by pgx with a savepoint. A nil error releases the savepoint; a non-nil error rolls back to it. A panic rolls back to it and continues unwinding.

Directories

Path Synopsis
Package page is the vocabulary of a paginated query: what a client asked for, and what a query is allowed to order by.
Package page is the vocabulary of a paginated query: what a client asked for, and what a query is allowed to order by.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL