sqlitex

package
v0.7.1 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package sqlitex provides ergonomic helpers over a database/sql SQLite database: deferred savepoints, transaction wrappers, multi-statement scripts, a row-callback query runner, single-row and multi-row scalar reads, and an embed.FS schema-migration runner.

The helpers operate on the standard *sql.DB / *sql.Conn / *sql.Tx types and work with any database/sql driver, but assume SQLite semantics (SAVEPOINT, BEGIN IMMEDIATE, multi-statement Exec, PRAGMA user_version). They fill the quality-of-life gap that users of zombiezen.com/go/sqlite and crawshaw.io/sqlite miss when moving to a database/sql driver — this package is named and shaped after their sqlitex helpers.

Deferred savepoints

release, err := sqlitex.Save(ctx, conn)
if err != nil { return err }
defer release(&err) // rolls back on a non-nil *err or panic, else releases

Transactions

err := sqlitex.Transaction(ctx, db, func(tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, "INSERT INTO t VALUES (?)", 1)
    return err // non-nil rolls back; nil commits
})

Row-callback queries

Execute runs a query and invokes a callback per row, handling the Query/Next/Scan/Err/Close lifecycle. ResultStrings / ResultInts collect a single column across rows:

err := sqlitex.Execute(ctx, db, "SELECT name FROM users WHERE age > :min",
    &sqlitex.ExecOptions{
        Named:      map[string]any{"min": 18},
        ResultFunc: func(rows *sql.Rows) error { /* rows.Scan(...) */ return nil },
    })

Migrations

Embed numbered .sql files and apply the pending ones, tracked via PRAGMA user_version:

//go:embed migrations/*.sql
var migrations embed.FS

sub, _ := fs.Sub(migrations, "migrations")
n, err := sqlitex.Migrate(ctx, db, sub) // n = migrations applied

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExecScript

func ExecScript(ctx context.Context, e Execer, script string) error

ExecScript runs a multi-statement SQL script (statements separated by ';') in a single ExecContext on e.

func Execute

func Execute(ctx context.Context, eq ExecQuerier, query string, opts *ExecOptions) error

Execute runs query against eq with opts, handling the row lifecycle so callers don't repeat the Query/Next/Scan/Err/Close dance. It is the ergonomic row-callback helper the crawshaw/zombiezen sqlitex lineage is known for, expressed over database/sql:

err := sqlitex.Execute(ctx, db, "SELECT id, name FROM users WHERE age > ?",
    &sqlitex.ExecOptions{
        Args: []any{18},
        ResultFunc: func(rows *sql.Rows) error {
            var id int64
            var name string
            if err := rows.Scan(&id, &name); err != nil {
                return err
            }
            // … use id, name …
            return nil
        },
    })

With a non-nil ResultFunc it runs as a query; with a nil ResultFunc (or nil opts) it runs as a statement via ExecContext, suitable for DDL/DML.

func ImmediateTransaction

func ImmediateTransaction(ctx context.Context, conn *sql.Conn, fn func(conn *sql.Conn) error) (err error)

ImmediateTransaction runs fn inside a BEGIN IMMEDIATE transaction on conn — SQLite acquires the write lock up front, avoiding a deadlock-prone upgrade-from-read under concurrency. It commits when fn returns nil and rolls back on error or panic. fn must run its statements on the same conn.

func Migrate

func Migrate(ctx context.Context, db *sql.DB, fsys fs.FS) (applied int, err error)

Migrate applies every migration in fsys whose version exceeds the database's current PRAGMA user_version, in ascending order, each in its own transaction, bumping user_version as it goes. It is idempotent: re-running applies nothing once the database is up to date. Returns the number of migrations applied.

Migration files are named "NNNN_description.sql" (see LoadMigrations).

func ResultBool

func ResultBool(ctx context.Context, q Querier, query string, args ...any) (bool, error)

ResultBool runs a single-row, single-column query and returns the value as a bool (non-zero / non-empty is true).

func ResultFloat

func ResultFloat(ctx context.Context, q Querier, query string, args ...any) (float64, error)

ResultFloat runs a single-row, single-column query and returns the value as a float64.

func ResultInt

func ResultInt(ctx context.Context, q Querier, query string, args ...any) (int64, error)

ResultInt runs query (which must return a single row, single column) and returns the value as an int64. sql.ErrNoRows is returned for no rows.

func ResultInts

func ResultInts(ctx context.Context, eq ExecQuerier, query string, args ...any) ([]int64, error)

ResultInts runs query and collects the first column of every row as int64s — the multi-row companion to ResultInt.

func ResultStrings

func ResultStrings(ctx context.Context, eq ExecQuerier, query string, args ...any) ([]string, error)

ResultStrings runs query and collects the first column of every row as strings — the multi-row companion to ResultText.

func ResultText

func ResultText(ctx context.Context, q Querier, query string, args ...any) (string, error)

ResultText runs a single-row, single-column query and returns the value as a string.

func Save

func Save(ctx context.Context, conn *sql.Conn) (release func(errp *error), err error)

Save opens a SAVEPOINT on conn and returns a release function for the deferred-cleanup idiom:

release, err := sqlitex.Save(ctx, conn)
if err != nil { return err }
defer release(&err)

release rolls back to the savepoint when *errp is non-nil or a panic is in flight, and always releases it. A nil errp always releases (commit). Nesting is safe: each call uses a distinct savepoint name. Use a *sql.Conn (not a pooled *sql.DB) so every statement lands on the same connection.

func Transaction

func Transaction(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) (err error)

Transaction runs fn inside a transaction: it commits when fn returns nil and rolls back on a non-nil error or a panic (which is re-raised).

Types

type ExecOptions

type ExecOptions struct {
	// Args are positional bind parameters for ? placeholders.
	Args []any

	// Named are named bind parameters (e.g. :id / @id / $id). Each is passed as
	// sql.Named and appended after Args. Bound by name, so map order is
	// irrelevant.
	Named map[string]any

	// ResultFunc, if non-nil, is invoked once per result row with the *sql.Rows
	// positioned on that row — call rows.Scan to read it. Returning an error
	// stops iteration and is returned by Execute. When nil, Execute runs the
	// statement without expecting rows (DDL/DML).
	ResultFunc func(rows *sql.Rows) error
}

ExecOptions configures Execute.

type ExecQuerier

type ExecQuerier interface {
	Execer
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
}

ExecQuerier is implemented by *sql.DB, *sql.Conn, and *sql.Tx — the two methods Execute needs (the row-returning and no-result paths).

type Execer

type Execer interface {
	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}

Execer is implemented by *sql.DB, *sql.Conn, and *sql.Tx.

type Migration

type Migration struct {
	Version int    // numeric prefix of the filename (e.g. 0003_… → 3)
	Name    string // the description after the prefix (e.g. "add_index")
	SQL     string // file contents (may contain multiple statements)
}

Migration is one schema-migration step parsed from a `.sql` file.

func LoadMigrations

func LoadMigrations(fsys fs.FS) ([]Migration, error)

LoadMigrations reads every `*.sql` file in fsys (recursively), parsing each filename of the form "NNNN_description.sql" into a Migration. Results are sorted ascending by version. It errors on a non-numeric prefix or a duplicate version.

type Querier

type Querier interface {
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}

Querier is implemented by *sql.DB, *sql.Conn, and *sql.Tx.

Jump to

Keyboard shortcuts

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