exec

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

README

exec — running a statement

DBTX, One and Each. It takes an interface that both *sql.DB and *sql.Tx satisfy, so it has no driver and no opinion about one.

A Query is not safe to share between goroutines, and this is the layer that says so. It holds pointers into the caller's variables, so two goroutines executing one query interleave each other's rows silently. One and Each refuse an overlapping execution with ErrQueryInUse; the guard is inherited by every statement derived from the builder. It is detection, not prevention.

Full documentation

The page for this package is docs/exec/README.md, with runnable snippets — every one of them copied from a test whose expected output go test checks. The documentation index is ../docs/README.md and the repository README is ../README.md.

Documentation

Overview

Package exec runs a built statement against a database and iterates its rows.

It is a layer because execution has nothing to do with how results come back into Go. Taking a DBTX, carrying the context, looping the rows and telling "no rows" from "more than one row" are the same work whichever result strategy sits above, so doing them once is what keeps a second strategy from re-implementing all of it.

It takes a DBTX rather than a concrete handle so a caller can run a library's query inside their own transaction, alongside their own writes. That is the single capability an embedded library has and a remote service can never offer, which is why the interface is the narrowest thing providing it.

Index

Constants

This section is empty.

Variables

View Source
var ErrMultipleRows = errors.New("sqlb: query matched multiple rows but expected one")

ErrMultipleRows is returned by One when a statement expected to be unique matched more than one row.

Worth its own error rather than silently taking the first: every One caller in this library is resolving an identity, so a second row means a uniqueness index is missing or a join fans out. Taking the first row would pick an arbitrary user, which is the worst possible failure mode for a login lookup.

View Source
var ErrNoRows = sql.ErrNoRows

ErrNoRows is returned by One when the statement matched nothing.

It aliases sql.ErrNoRows so existing errors.Is checks keep working, and so a caller is never forced to learn a second spelling of the same condition.

Functions

func Each

func Each(ctx context.Context, db DBTX, q stmt.Query, fn func() error) error

Each executes q and scans every row into the bound destinations, calling fn once after each row is scanned.

Invariant, and the one thing that will bite a caller who skips this comment: every row scans into the SAME destinations, because the bindings hold fixed pointers. The struct is overwritten on each iteration, so fn must copy what it needs — appending a pointer to the destination yields a slice of N identical values, all equal to the last row.

var ident auth.Identity
q, _ := a.IdentitiesOfUserQuery(userID, &ident)
var all []auth.Identity
err := sqlb.Each(ctx, db, q, func() error {
    all = append(all, ident) // copies the value; &ident would not
    return nil
})

Returning an error from fn stops iteration and returns that error, so a caller can abort a large result set without draining it.

Returns stmt.ErrNoTargets when the query bound no destinations, and stmt.ErrQueryInUse when the same statement is already executing, including from inside fn. q is not safe for concurrent use; running one from two goroutines is a data race. See One.

func One

func One(ctx context.Context, db DBTX, q stmt.Query) error

One executes q and scans exactly one row into the destinations bound when the query was built.

Nothing is passed for the destination because the query already carries it: Bind captured a pointer to each field, Build collected them in SELECT order, and Scan writes straight through those pointers. That is why the builder is the only place column order is decided — there is no second list to keep in step with it.

Returns ErrNoRows when nothing matched, ErrMultipleRows when more than one row did, stmt.ErrNoTargets when the query bound no destinations, and stmt.ErrQueryInUse when the same statement is already executing.

That last one is the concurrency rule: q is not safe for concurrent use, and executing it from two goroutines is a data race on its scan targets, which are pointers into the caller's variables. Build the query where its destination lives, per call. Reusing one sequentially is fine.

Types

type DBTX

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

DBTX is the subset of database/sql that both *sql.DB and *sql.Tx satisfy.

Taking this rather than a concrete type is what lets a caller run an auth query inside their own transaction, alongside their own writes. That is the single capability an embedded library has and a remote identity service can never offer, so the interface is deliberately the narrowest thing that provides it.

Jump to

Keyboard shortcuts

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