postgres

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 8, 2026 License: MIT Imports: 15 Imported by: 0

README

postgres

Doc Go Release Test License

PostgreSQL driver module for rio, built on pgx. It supports database/sql, database/sql over pgxpool, and direct pgx execution.

Getting started

go get github.com/go-rio/postgres

The default path uses database/sql:

db, err := postgres.Open("postgres://user:pass@localhost:5432/app")
if err != nil {
	log.Fatal(err)
}
defer db.Close()

if err := db.Unwrap().PingContext(ctx); err != nil {
	log.Fatal(err)
}

if err := rio.Insert(ctx, db, &user); err != nil {
	log.Fatal(err)
}

Open validates the DSN but does not connect. It accepts pgx URL and keyword/value DSNs. Use New to wrap an existing *sql.DB; the caller remains responsible for its session configuration.

Error translation
SQLSTATE rio sentinel
23505 rio.ErrDuplicateKey
23503 rio.ErrForeignKeyViolated

The original *pgconn.PgError remains available through errors.As, including its constraint, table, and detail fields.

Choosing an execution path

rio queries use the same API on all three paths. Choose based on connection ownership and access to pgx APIs.

Path Constructors Use when
database/sql Open, New You want *sql.DB pooling, sqlmock, or database/sql instrumentation.
database/sql over pgxpool OpenPool, NewFromPool You want pgxpool configuration and PoolOf, while keeping the database/sql query path.
native pgx OpenNative, NewNativeFromPool You want lower scan allocations and can use pgx execution-mode and transaction semantics.

standard_conforming_strings

rio's PostgreSQL placeholder lexer requires standard_conforming_strings=on, the PostgreSQL default since 9.1. Open, OpenPool, and OpenNative reject an explicit false value supplied directly, through the options startup parameter, or through PGOPTIONS.

If omitted, rio does not inject the setting. Servers that disable it globally must enable it for rio connections:

postgres://user:pass@localhost:5432/app?options=-c%20standard_conforming_strings%3Don

Keyword/value DSNs can use options='-c standard_conforming_strings=on' instead.

The bring-your-own constructors cannot validate an existing pool's session settings; callers of New, NewFromPool, and NewNativeFromPool must enforce this requirement.

pgxpool with database/sql

db, err := postgres.OpenPool(ctx, "postgres://user:pass@localhost:5432/app?pool_max_conns=10")
if err != nil {
	log.Fatal(err)
}
defer db.Close()

pool := postgres.PoolOf(db)
if err := pool.Ping(ctx); err != nil {
	log.Fatal(err)
}
  • OpenPool accepts pgxpool pool_* DSN parameters. Use NewFromPool for a caller-built pgxpool.Config.
  • PoolOf exposes the pool for Ping, Stat, AcquireFunc, CopyFrom, and LISTEN/NOTIFY.
  • OpenPool and NewFromPool transfer pool ownership to the rio DB. Closing the DB closes the pool and may block until acquired connections return. Do not transfer a pool that must outlive the DB.
  • Configure connection counts on pgxpool, not on db.Unwrap(). The database/sql view intentionally keeps no idle connections.

Native pgx execution

OpenNative executes rio queries directly through pgx. db.Unwrap() remains available as a database/sql view for helpers such as migrations and pings, but must not be used to configure pooling.

db, err := postgres.OpenNative(ctx, "postgres://user:pass@localhost:5432/app")
if err != nil {
	log.Fatal(err)
}
defer db.Close()

pool := postgres.PoolOf(db)
if err := pool.Ping(ctx); err != nil {
	log.Fatal(err)
}
err = db.Tx(ctx, func(tx *rio.Tx) error {
	ptx := postgres.TxOf(tx)
	_, err := ptx.Exec(ctx, "SET LOCAL lock_timeout = '1s'")
	return err
})

NewNativeFromPool accepts a caller-built pool and transfers ownership to the rio DB. Closing the DB closes its database/sql view first and then the pool, and may block until acquired connections return.

The native path preserves rio's SQL rendering, scanning rules, sentinel errors, hooks, and savepoint behavior. Its public differences are:

Difference Detail
tx.Unwrap() returns nil Use postgres.TxOf(tx) for the pgx.Tx. Nested rio savepoints expose the root pgx transaction.
rio.WithStmtCache panics Native statement caching belongs to pgx's query execution mode.
Error text may carry pgx prefixes Use errors.Is and errors.As for stable checks.

The native path removes the database/sql conversion layer. Use rio's PostgreSQL benchmarks to compare both paths against the application workload.

Query exec mode and PgBouncer

The native path uses pgx's default cache_statement mode. Change it with the default_query_exec_mode DSN parameter or on a caller-built pool config.

Setup Action
Direct connection Keep the pgx default.
PgBouncer ≥ 1.21 with max_prepared_statements > 0 None; PgBouncer tracks prepared statements across the multiplexer.
Older PgBouncer in transaction/statement pooling Add default_query_exec_mode=exec (or simple_protocol) to the DSN. Symptom otherwise: prepared statement "stmtcache_..." does not exist.

On database/sql paths, rio.WithStmtCache adds a DB-level cache and a cache local to each transaction. It is off by default and is unsuitable for transaction/statement-mode poolers. pgx already caches statements per connection, so enable the additional rio cache only after measuring it.

Arrays and JSONB

Tag JSON fields with rio:",json":

type Account struct {
	ID    int64
	Prefs map[string]any `rio:",json"` // jsonb column "prefs"
}

A set-based rio.Set{"prefs": v} uses the same JSON mapping.

For PostgreSQL arrays, define a driver.Valuer and sql.Scanner wrapper around pgtype:

var pgMap = pgtype.NewMap()

type Tags []string

func (t Tags) Value() (driver.Value, error) {
	b, err := pgMap.Encode(
		pgtype.TextArrayOID,
		pgtype.TextFormatCode,
		pgtype.FlatArray[string](t),
		nil,
	)
	if err != nil || b == nil {
		return nil, err
	}
	return string(b), nil
}

func (t *Tags) Scan(src any) error {
	return pgMap.SQLScanner((*pgtype.FlatArray[string])(t)).Scan(src)
}

The wrapper works on all three execution paths.

JSONB existence operators collide with rio's ? placeholder. Escape each literal question mark as ??:

rio.From[Account]().Where("prefs ?? ?", "beta").All(ctx, db)

This renders prefs ? $1; write ?| and ?& as ??| and ??&.

For UpdateAll, wrap an array slice in the Valuer type rather than passing a bare slice, or use rio.Expr for a database-side expression.

Contributing

Use Go 1.27 or newer, then run go test ./..., go test -race ./..., and go vet ./... before opening a pull request.

License

MIT

Documentation

Overview

Package postgres connects rio to PostgreSQL through pgx's database/sql adapter or rio's native execution path.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(db *sql.DB, opts ...rio.Option) *rio.DB

New wraps an existing *sql.DB with the Postgres dialect and error translator. The caller must ensure standard_conforming_strings is on. Options are applied last, so a supplied translator replaces the default.

func NewFromPool added in v0.3.0

func NewFromPool(pool *pgxpool.Pool, opts ...rio.Option) *rio.DB

NewFromPool wraps a caller-built pgxpool.Pool and takes ownership of it. The caller must ensure standard_conforming_strings is on. Closing the rio DB closes the pool; do not use this function if the pool must outlive it.

func NewNativeFromPool added in v0.3.0

func NewNativeFromPool(pool *pgxpool.Pool, opts ...rio.Option) *rio.DB

NewNativeFromPool wraps a caller-built pgxpool.Pool for native execution and takes ownership of it. The caller must ensure standard_conforming_strings is on. Closing the rio DB closes the pool.

func Open

func Open(dsn string, opts ...rio.Option) (*rio.DB, error)

Open validates a pgx DSN and returns a rio DB using pgx's database/sql adapter. It does not connect; use db.Unwrap().PingContext to verify connectivity and db.Unwrap() to configure the pool.

Open rejects standard_conforming_strings=off, including settings supplied through PGOPTIONS, because rio's placeholder lexer assumes the PostgreSQL default. URL and keyword/value DSNs are both accepted.

func OpenNative added in v0.3.0

func OpenNative(ctx context.Context, dsn string, opts ...rio.Option) (*rio.DB, error)

OpenNative validates a pgxpool DSN and returns a rio DB that executes queries directly through pgx. It connects lazily; use PoolOf(db).Ping(ctx) to verify connectivity. Like Open, it rejects standard_conforming_strings=off.

Statement caching is controlled by pgx's default_query_exec_mode; rio.WithStmtCache is unsupported. Tx.Unwrap returns nil, so use TxOf for the pgx transaction. db.Unwrap returns a database/sql view for compatible helpers, but pooling must be configured through pgxpool.

func OpenPool added in v0.3.0

func OpenPool(ctx context.Context, dsn string, opts ...rio.Option) (*rio.DB, error)

OpenPool validates a pgxpool DSN and returns a rio DB using pgx's database/sql adapter over that pool. It connects lazily; use PoolOf(db).Ping(ctx) to verify connectivity. Like Open, it rejects standard_conforming_strings=off.

Configure pooling through the DSN, not db.Unwrap(). Closing the rio DB also closes the pool and may block until acquired connections are returned.

func PoolOf added in v0.3.0

func PoolOf(db *rio.DB) *pgxpool.Pool

PoolOf returns the pool managed by a pool-backed or native rio DB. It returns nil for other constructions and for a nil DB.

func TxOf added in v0.3.0

func TxOf(tx *rio.Tx) pgx.Tx

TxOf returns the pgx transaction behind a native rio transaction. It returns nil for other constructions and for a nil transaction. Nested savepoints share the root pgx transaction.

Types

This section is empty.

Jump to

Keyboard shortcuts

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