Documentation
¶
Overview ¶
Package dbx provides opinionated database utilities built on top of sqlx. It covers three areas: opening databases with migrations, type-safe prepared statement wrappers using generics, and cursor-based pagination.
Opening a database ¶
Open and OpenSQLX open a database connection and optionally apply SQLite pragmas and schema migrations. Migrations are read from an fs.FS (typically an embedded filesystem) and applied via golang-migrate. Configuration is done through functional options:
db, err := dbx.OpenSQLX(
dbx.WithDSN(":memory:"),
dbx.WithDriver("sqlite"),
dbx.WithPragmas(
"PRAGMA foreign_keys = ON",
"PRAGMA synchronous = NORMAL",
),
dbx.WithMigrations(migrationsFS, "migrations"),
)
Prepared statements ¶
The package provides generic function types that wrap prepared statements. Constructors panic on invalid SQL so that wiring errors are caught at init time rather than at runtime.
The underlying prepared statements are captured in closures and cannot be closed individually — they are released when the database connection is closed. This is by design for the recommended init-time wiring pattern:
- NewExecFunc — simple operations (DELETE, etc.) with positional args
- NewNamedExecFunc — INSERT/UPDATE with a struct, returns Result
- NewQueryRowxFunc — single-row query with positional args
- NewEntityQueryRowxFunc — struct in, struct out (e.g. RETURNING *)
- NewSelectFunc — bounded result sets
- NewQueryxIteratorFunc — streaming large result sets via range iterator
- NewPaginatedSelectFunc — cursor-based paginated SELECT (see Pagination)
Transactions ¶
The Tx* function types mirror the prepared-statement types above but execute against a *sqlx.Tx instead of *sqlx.DB. They are prepared on the *sqlx.DB at construction time and bound to the transaction on each call, so the SQL is parsed once. Construct them outside any transaction (typically in a newXStmts constructor): on a database with a single connection, preparing while a transaction holds it would wait forever.
RunTx handles the begin/commit/rollback boilerplate:
addTx := dbx.NewTxNamedExecFunc[Device](db, "INSERT INTO devices ... VALUES ...")
pskTx := dbx.NewTxNamedExecFunc[PSK](db, "INSERT INTO psk ... VALUES ...")
err := dbx.RunTx(ctx, db, func(tx *sqlx.Tx) error {
if _, err := addTx(ctx, tx, device); err != nil {
return err
}
_, err := pskTx(ctx, tx, psk)
return err
})
Available Tx function types:
- NewTxExecFunc — positional-arg exec inside a transaction
- NewTxNamedExecFunc — named exec (struct) inside a transaction
- NewTxQueryRowxFunc — single-row query inside a transaction
- NewTxEntityQueryRowxFunc — struct in, struct out inside a transaction
- NewTxBatchExecFunc — multi-row statement for a slice of structs
Batch inserts ¶
NewTxBatchExecFunc inserts many rows with few statements. It takes the statement head and a named template for one row, prepares statements for 1 to maxRows rows, and binds struct fields by their db tags:
insert := dbx.NewTxBatchExecFunc[Event](db,
"INSERT INTO events (time, path) VALUES ", "(:time, :path)", 8)
err := dbx.RunTx(ctx, db, func(tx *sqlx.Tx) error {
_, err := insert(ctx, tx, events)
return err
})
Each statement costs the driver a fixed amount of work (with a cancellable context, modernc.org/sqlite starts a goroutine per statement), so batching is much faster than one statement per row. Keep maxRows small: some drivers bind parameters in time quadratic in their number.
Pagination ¶
NewPaginatedSelectFunc builds cursor-based paginated SELECT queries. Table and column names are validated as safe SQL identifiers at construction time. Queries are rebound via sqlx.Rebind for cross-database placeholder compatibility.
Breaking changes in v1.0.0 ¶
WithPragmas now accepts variadic string arguments instead of a string slice. Change WithPragmas([]string{...}) to WithPragmas(...).
ValidSQLIdentifier now rejects identifiers that start with a digit, aligning with standard SQL identifier rules.
Index ¶
- Constants
- Variables
- func Open(opts ...Option) (*sql.DB, error)
- func OpenSQLX(opts ...Option) (*sqlx.DB, error)
- func RunTx(ctx context.Context, db *sqlx.DB, fn func(tx *sqlx.Tx) error) error
- func ValidSQLIdentifier(s string) bool
- type DriverForFunc
- type EntityQueryRowxFunc
- type ExecFunc
- type NamedExecFunc
- type Option
- type PageRequest
- type PageResponse
- type PaginatedSelectFunc
- type QueryRowxFunc
- type QueryxIteratorFunc
- type Result
- type SelectFunc
- type TxBatchExecFunc
- type TxEntityQueryRowxFunc
- type TxExecFunc
- type TxNamedExecFunc
- type TxQueryRowxFunc
Constants ¶
const MaxPageSize = 1000
MaxPageSize is the upper bound on PageRequest.PageSize. Requests exceeding this value are silently clamped.
Variables ¶
var ( ErrDatabaseTooNew = errors.New("database may be newer than migrations available in this binary") ErrNoDSN = errors.New("no data source name given") )
errors
var ErrMutuallyExclusive = errors.New("after and before are mutually exclusive")
ErrMutuallyExclusive is returned when both After and Before are set in a PageRequest.
Functions ¶
func RunTx ¶
RunTx executes fn inside a transaction. If fn returns an error or panics the transaction is rolled back; otherwise it is committed.
func ValidSQLIdentifier ¶
ValidSQLIdentifier reports whether s is safe to interpolate as a SQL identifier. Only ASCII letters, digits, and underscores are allowed, and the identifier must not start with a digit.
Types ¶
type DriverForFunc ¶
DriverForFunc returns (driver, migrateDBName, error).
type EntityQueryRowxFunc ¶
EntityQueryRowxFunc is useful for queries that take some entity and return an entity of the same type. For instance when using RETURNING in SQL statement.
func NewEntityQueryRowxFunc ¶
func NewEntityQueryRowxFunc[T any](db *sqlx.DB, stmt string) EntityQueryRowxFunc[T]
NewEntityQueryRowxFunc creates a new EntityQueryRowxFunc
type ExecFunc ¶
ExecFunc is useful for very simple operations like DELETE with positional arguments.
type NamedExecFunc ¶
NamedExecFunc is useful for create, update etc where you send an entity in and just want a Result and error back.
func NewNamedExecFunc ¶
func NewNamedExecFunc[T any](db *sqlx.DB, stmt string) NamedExecFunc[T]
NewNamedExecFunc creates a new NamedExecFunc
type Option ¶
type Option func(*config) error
Option is a configuration option callback type
func WithMigrationDriver ¶
func WithMigrationDriver(sqlDriverName string, migrateName string, create func(*sql.DB) (database.Driver, error)) Option
WithMigrationDriver is provided in case you want to use SQL databases beyond those provided in the default config (sqlite, postgres).
func WithMigrations ¶
WithMigrations sets the migrations filesystem and path within that filesystem. You can either pass an embed.FS or a fs.FS for a OS filesystem path using os.DirFS(path).
func WithPragmas ¶
WithPragmas appends pragmas to the config. Pragmas are executed as raw SQL statements during database opening, so they must be trusted inputs — never pass user-controlled strings.
type PageRequest ¶
type PageRequest struct {
// PageSize is the maximum number of rows to return. Zero means unlimited.
PageSize int
// After is the cursor for forward pagination (exclusive lower bound).
// Mutually exclusive with Before. Only applied when PageSize > 0.
After string
// Before is the cursor for backward pagination (exclusive upper bound).
// Mutually exclusive with After. Only applied when PageSize > 0.
Before string
}
PageRequest carries cursor-based pagination parameters for list operations. A zero PageSize means "return all results" (backward compatible).
After and Before are mutually exclusive: setting both is an error. After requests results after the given cursor (forward pagination), while Before requests results before the given cursor (backward pagination).
Note: After and Before are only applied when PageSize > 0 (i.e. when IsPaginated returns true). Setting a cursor without a page size will return all rows and silently ignore the cursor value.
func (PageRequest) IsPaginated ¶
func (p PageRequest) IsPaginated() bool
IsPaginated returns true when the caller has requested a bounded page.
type PageResponse ¶
type PageResponse struct {
// HasMore is true when additional pages exist beyond this result set.
HasMore bool
// FirstCursor is the cursor of the first item in the returned page.
FirstCursor string
// LastCursor is the cursor of the last item in the returned page.
LastCursor string
}
PageResponse carries pagination metadata returned from list operations.
type PaginatedSelectFunc ¶
type PaginatedSelectFunc[T any] func(ctx context.Context, page PageRequest, args ...any) ([]T, PageResponse, error)
PaginatedSelectFunc performs a cursor-based paginated SELECT query. The args are positional parameters for the base WHERE clause provided at construction time.
func NewPaginatedSelectFunc ¶
func NewPaginatedSelectFunc[T any](db *sqlx.DB, table, orderCol, baseWhere string, cursorFn func(T) string) PaginatedSelectFunc[T]
NewPaginatedSelectFunc creates a PaginatedSelectFunc.
table and orderCol must be valid SQL identifiers (the constructor panics otherwise, consistent with other dbx constructors that panic on invalid input detected at init time).
baseWhere is an optional WHERE clause including the WHERE keyword (e.g. "WHERE tenant_id = ?"). Pass "" to select all rows. baseWhere is interpolated directly into the query; it must be a trusted string — never pass user-controlled input.
cursorFn extracts a cursor string from a result row for populating PageResponse.FirstCursor and LastCursor.
type QueryRowxFunc ¶
QueryRowxFunc is useful for queries that return one row and takes positional arguments. For instance get operations on a single row.
func NewQueryRowxFunc ¶
func NewQueryRowxFunc[T any](db *sqlx.DB, stmt string) QueryRowxFunc[T]
NewQueryRowxFunc creates a new QueryRowxFunc
type QueryxIteratorFunc ¶
QueryxIteratorFunc is useful for queries that return (potentially) large result sets and you want to be able to stream the result.
func NewQueryxIteratorFunc ¶
func NewQueryxIteratorFunc[T any](db *sqlx.DB, stmt string) QueryxIteratorFunc[T]
NewQueryxIteratorFunc creates a new QueryxIteratorFunc
type Result ¶
type Result struct {
// contains filtered or unexported fields
}
Result holds the outcome of a mutating operation
func NewResult ¶
NewResult wraps a sql.Result into a dbx.Result, extracting LastInsertId and RowsAffected if available.
func (Result) LastInsertID ¶
LastInsertID returns the last insert ID if available
func (Result) RowsAffected ¶
RowsAffected returns the number of rows affected if available
type SelectFunc ¶
SelectFunc is useful for when you perform selects and you know the result set will be small or at least bounded to acceptable size.
func NewSelectFunc ¶
func NewSelectFunc[T any](db *sqlx.DB, stmt string) SelectFunc[T]
NewSelectFunc creates a new SelectFunc
type TxBatchExecFunc ¶
TxBatchExecFunc executes a multi-row statement for a slice of entities inside a transaction, one statement per chunk of up to maxRows entities. It returns one Result per statement, in order.
func NewTxBatchExecFunc ¶
NewTxBatchExecFunc creates a TxBatchExecFunc. head is the statement up to the rows, e.g. "INSERT INTO foo (name, ts) VALUES ", and row is the named template for one row, e.g. "(:name, :ts)". Names map to T's db tags as in NewNamedExecFunc; a literal colon is written "::".
Statements for 1 to maxRows rows are prepared on db at construction time. Keep maxRows small: some drivers bind parameters in time quadratic in their number, and SQLite allows at most 32766 per statement.
type TxEntityQueryRowxFunc ¶
TxEntityQueryRowxFunc executes a named statement inside a transaction that takes an entity and returns an entity of the same type. It mirrors EntityQueryRowxFunc but operates on a *sqlx.Tx.
func NewTxEntityQueryRowxFunc ¶
func NewTxEntityQueryRowxFunc[T any](db *sqlx.DB, stmt string) TxEntityQueryRowxFunc[T]
NewTxEntityQueryRowxFunc creates a TxEntityQueryRowxFunc. The statement is prepared on db at construction time, see NewTxExecFunc. A query that yields no rows returns an error wrapping sql.ErrNoRows.
type TxExecFunc ¶
TxExecFunc executes a statement with positional arguments inside a transaction. It mirrors ExecFunc but operates on a *sqlx.Tx.
func NewTxExecFunc ¶
func NewTxExecFunc(db *sqlx.DB, stmt string) TxExecFunc
NewTxExecFunc creates a TxExecFunc. The statement is prepared on db at construction time and bound to the transaction on each call, so it is parsed once. Construct it outside any transaction: on a database with a single connection, preparing inside one would wait forever.
type TxNamedExecFunc ¶
TxNamedExecFunc executes a named statement inside a transaction. It mirrors NamedExecFunc but operates on a *sqlx.Tx.
func NewTxNamedExecFunc ¶
func NewTxNamedExecFunc[T any](db *sqlx.DB, stmt string) TxNamedExecFunc[T]
NewTxNamedExecFunc creates a TxNamedExecFunc. The statement is prepared on db at construction time, see NewTxExecFunc.
type TxQueryRowxFunc ¶
TxQueryRowxFunc queries a single row inside a transaction with positional arguments. It mirrors QueryRowxFunc but operates on a *sqlx.Tx.
func NewTxQueryRowxFunc ¶
func NewTxQueryRowxFunc[T any](db *sqlx.DB, stmt string) TxQueryRowxFunc[T]
NewTxQueryRowxFunc creates a TxQueryRowxFunc. The statement is prepared on db at construction time, see NewTxExecFunc.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
open
command
Example: opening a database with migrations and pragmas.
|
Example: opening a database with migrations and pragmas. |
|
paginate
command
Example: cursor-based pagination.
|
Example: cursor-based pagination. |
|
prepared
command
Example: prepared statement functions for CRUD operations.
|
Example: prepared statement functions for CRUD operations. |