Documentation
¶
Overview ¶
Package dbx provides Postgres helpers on top of jmoiron/sqlx and the pgx driver: connection setup, named query/exec wrappers with query logging, transactions, and bulk insert/upsert.
Open builds a *sqlx.DB from a Config; StatusCheck waits for it to become reachable. The Named* helpers run named queries/execs, log the expanded SQL at debug level, and translate well-known Postgres errors into the package sentinels (ErrDBNotFound, ErrDBDuplicatedEntry, ErrUndefinedTable) so callers match them with errors.Is instead of inspecting driver codes. WithinTran runs a function inside a transaction, committing on success and rolling back on error or panic. Bulk* batch large multi-row writes within Postgres's parameter limit.
Usage ¶
cfg := dbx.Config{
User: "postgres", Password: "postgres",
Host: "localhost:5432", Name: "app",
Schema: "public", MaxIdleConns: 2, MaxOpenConns: 5,
DisableTLS: true,
}
db, err := dbx.Open(cfg)
if err != nil {
return err
}
if err := dbx.StatusCheck(ctx, db); err != nil {
return err
}
// Named query into a slice.
var widgets []Widget
const q = `SELECT id, name FROM widgets WHERE name = :name`
arg := struct {
Name string `db:"name"`
}{Name: "gadget"}
if err := dbx.NamedQuerySlice(ctx, log, db, q, arg, &widgets); err != nil {
return err
}
// Pass a slice as a single array parameter: pgx binds it as a Postgres
// array, so ANY(:ids) filters without expanding placeholders.
var out []Widget
err := dbx.NamedQuerySlice(ctx, log, db,
`SELECT * FROM widgets WHERE id = ANY(:ids)`,
map[string]any{"ids": []int64{235, 401, 512}}, // slice -> one $1 -> int8[]
&out,
)
// Write inside a transaction.
err = dbx.WithinTran(ctx, log, db, func(tx *sqlx.Tx) error {
const ins = `INSERT INTO widgets (id, name) VALUES (:id, :name)`
return dbx.NamedExecContext(ctx, log, tx, ins, w)
})
if errors.Is(err, dbx.ErrDBDuplicatedEntry) {
// unique violation
}
Querying ¶
- ExecContext / NamedExecContext run statements; the RowsAffected variant returns the affected-row count.
- QueryStruct / NamedQueryStruct scan exactly one row (ErrDBNotFound when none).
- QuerySlice / NamedQuerySlice scan every row into a *[]T.
- NamedQuerySliceUsingIn / NamedQueryStructUsingIn handle a query with an IN clause: they expand a slice parameter (sqlx.In) and rebind before running.
For an IN-style filter, prefer binding the slice as a single array parameter and matching with ANY(:ids) (see Usage): pgx encodes []int64/[]string as a Postgres array, so the query text stays constant regardless of list length. Reach for the …UsingIn helpers only when you need driver-agnostic placeholder expansion instead.
The plain (non-Named) variants take no parameters; the Named variants bind from a struct via its `db` tags.
Dialects ¶
Subpackage dbx/dialect captures the few SQL fragments that differ between engines (currently pagination) behind a small Dialect interface, with Postgres and SQLite implementations, so a store can compose portable SQL.
Bulk writes ¶
BulkInsert writes len(values)/len(columns) rows, splitting into batches that stay under Postgres's bind-parameter cap, with values laid out row-major. An optional conflictAction (e.g. "ON CONFLICT DO NOTHING") is appended verbatim. BulkUpsert builds the ON CONFLICT … DO UPDATE clause for you from the conflict columns.
Transactions ¶
WithinTran is the common path. For stores that must run against either a pool or an outer transaction, depend on the Beginner / CommitRollbacker seam: NewBeginner adapts a *sqlx.DB, and ExtContext extracts the query surface from a started transaction.
Config ¶
Config fields: User, Password, Host, Name, Schema (sets search_path), MaxIdleConns, MaxOpenConns, DisableTLS (DisableTLS=true selects sslmode=disable, otherwise sslmode=require). Open always sets timezone=utc.
Index ¶
- Variables
- func BulkInsert(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, table string, ...) error
- func BulkUpsert(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, table string, ...) error
- func ExecContext(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string) error
- func ExtContext(tx CommitRollbacker) (sqlx.ExtContext, error)
- func NamedExecContext(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, ...) error
- func NamedExecContextRowsAffected(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, ...) (int64, error)
- func NamedQuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, ...) error
- func NamedQuerySliceUsingIn[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, ...) error
- func NamedQueryStruct(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, ...) error
- func NamedQueryStructUsingIn(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, ...) error
- func Open(cfg Config) (*sqlx.DB, error)
- func QuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, ...) error
- func QueryStruct(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, ...) error
- func StatusCheck(ctx context.Context, db *sqlx.DB) error
- func WithinTran(ctx context.Context, log *logger.Logger, db *sqlx.DB, ...) error
- type Beginner
- type CommitRollbacker
- type Config
- type DBBeginner
Constants ¶
This section is empty.
Variables ¶
var ( ErrDBNotFound = sql.ErrNoRows ErrDBDuplicatedEntry = errors.New("duplicated entry") ErrUndefinedTable = errors.New("undefined table") )
Sentinel errors returned by this package; match them with errors.Is.
Functions ¶
func BulkInsert ¶
func BulkInsert(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, table string, columns []string, values []any, conflictAction string) error
BulkInsert inserts len(values)/len(columns) rows into table in batches, each batch a single multi-row INSERT. conflictAction, if non-empty, is appended verbatim (e.g. "ON CONFLICT DO NOTHING"). values are laid out row-major: [r0c0, r0c1, r1c0, r1c1, ...].
For upserts prefer BulkUpsert, which builds the conflict clause for you.
func BulkUpsert ¶
func BulkUpsert(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, table string, columns []string, values []any, conflictColumns []string) error
BulkUpsert inserts rows and, on conflict over conflictColumns, updates every non-conflict column from the proposed row.
func ExecContext ¶
ExecContext runs a parameterless statement.
func ExtContext ¶
func ExtContext(tx CommitRollbacker) (sqlx.ExtContext, error)
ExtContext extracts the sqlx.ExtContext (the query surface) from a transaction returned by Begin.
func NamedExecContext ¶
func NamedExecContext(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any) error
NamedExecContext runs an INSERT/UPDATE/DELETE with named parameters bound from data. It logs the expanded query and translates well-known Postgres errors.
func NamedExecContextRowsAffected ¶
func NamedExecContextRowsAffected(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any) (int64, error)
NamedExecContextRowsAffected is like NamedExecContext but returns the number of affected rows.
func NamedQuerySlice ¶
func NamedQuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest *[]T) error
NamedQuerySlice runs a named query and scans all rows into *[]T.
func NamedQuerySliceUsingIn ¶
func NamedQuerySliceUsingIn[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest *[]T) error
NamedQuerySliceUsingIn runs a named query whose data contains a slice bound to an IN clause, scanning all rows into *[]T. It expands the slice parameters (sqlx.In) and rebinds the query for the driver before executing. Use it instead of NamedQuerySlice when the query has an `IN (:ids)` form.
func NamedQueryStruct ¶
func NamedQueryStruct(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data, dest any) error
NamedQueryStruct runs a named query expected to return exactly one row.
func NamedQueryStructUsingIn ¶
func NamedQueryStructUsingIn(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data, dest any) error
NamedQueryStructUsingIn runs a named query with an IN clause expected to return exactly one row, scanning it into dest. Returns ErrDBNotFound when no row matches.
func Open ¶
Open opens a sqlx.DB using the pgx driver. It does not verify connectivity; call StatusCheck for that.
func QuerySlice ¶
func QuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, dest *[]T) error
QuerySlice runs a parameterless query and scans all rows into *[]T.
func QueryStruct ¶
func QueryStruct(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, dest any) error
QueryStruct runs a parameterless query expected to return exactly one row and scans it into dest (a pointer to a struct).
func StatusCheck ¶
StatusCheck pings the database, retrying until it is reachable or ctx is done.
On timeout it returns errors.Join(ctx.Err(), lastPingErr) rather than a bare "context deadline exceeded", so the real cause — connection refused, TLS failure, password authentication failed — is visible at the call site instead of being masked by the deadline.
Types ¶
type Beginner ¶
type Beginner interface {
Begin() (CommitRollbacker, error)
}
Beginner starts a transaction. It is the seam stores depend on so they can be driven either by a pool or by an outer transaction (see middleware that opens a transaction per request).
type CommitRollbacker ¶
CommitRollbacker is a transaction that can be committed or rolled back.
type Config ¶
type Config struct {
User string
Password string
Host string
Name string
Schema string
MaxIdleConns int
MaxOpenConns int
DisableTLS bool
}
Config holds connection settings.
type DBBeginner ¶
type DBBeginner struct {
// contains filtered or unexported fields
}
DBBeginner adapts a *sqlx.DB to the Beginner interface.
func NewBeginner ¶
func NewBeginner(db *sqlx.DB) *DBBeginner
NewBeginner returns a Beginner backed by db.
func (*DBBeginner) Begin ¶
func (b *DBBeginner) Begin() (CommitRollbacker, error)
Begin starts a new transaction.