sqldb

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jun 23, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package sqldb 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 := sqldb.Config{
    User: "postgres", Password: "postgres",
    Host: "localhost:5432", Name: "app",
    Schema: "public", MaxIdleConns: 2, MaxOpenConns: 5,
    DisableTLS: true,
}
db, err := sqldb.Open(cfg)
if err != nil {
    return err
}
if err := sqldb.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 := sqldb.NamedQuerySlice(ctx, log, db, q, arg, &widgets); err != nil {
    return err
}

// Write inside a transaction.
err = sqldb.WithinTran(ctx, log, db, func(tx *sqlx.Tx) error {
    const ins = `INSERT INTO widgets (id, name) VALUES (:id, :name)`
    return sqldb.NamedExecContext(ctx, log, tx, ins, w)
})
if errors.Is(err, sqldb.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.

The plain (non-Named) variants take no parameters; the Named variants bind from a struct via its `db` tags.

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

Constants

This section is empty.

Variables

View Source
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

func ExecContext(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string) error

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 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 Open

func Open(cfg Config) (*sqlx.DB, error)

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

func StatusCheck(ctx context.Context, db *sqlx.DB) error

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.

func WithinTran

func WithinTran(ctx context.Context, log *logger.Logger, db *sqlx.DB, fn func(tx *sqlx.Tx) error) error

WithinTran runs fn inside a transaction, committing on success and rolling back on error or panic.

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

type CommitRollbacker interface {
	Commit() error
	Rollback() error
}

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.

Jump to

Keyboard shortcuts

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