Table of contents
Part of nurago, a collection of independent Go packages for backend services.
import "github.com/tecnickcom/nurago/pkg/sqlxtransaction"
The sqltransaction pattern against jmoiron/sqlx, so the callback receives a *sqlx.Tx.
err := sqlxtransaction.Exec(ctx, db, func(ctx context.Context, tx *sqlx.Tx) error {
return tx.SelectContext(ctx, &users, query, arg)
})
Why a separate package
sqlx.Tx embeds sql.Tx but is a distinct type, and its extra methods are the reason to be using sqlx at all. A callback typed func(context.Context, *sql.Tx) error cannot call SelectContext, GetContext, NamedExecContext or StructScan, which is most of what sqlx is for.
Keeping them separate also keeps the dependency separate. sqltransaction reaches no external module, so a service not using sqlx does not pull it in. Go resolves dependencies per package, so importing one of these two does not bring the other. See /docs/dependency-footprint/.
Identical semantics
The transaction handling matches the database/sql version exactly:
- Begin through
DB.BeginTxx. - Run the function with the
*sqlx.Tx. - Commit on success.
- Roll back if the function failed or commit was not reached.
And the same three rollback guards: skipped after a successful commit, sql.ErrTxDone ignored during the deferred rollback, and a genuine rollback failure joined with the current error rather than replacing it.
Matching behaviour matters for a codebase using both, which is common when one part uses struct scanning and another does raw work. The two behave the same, so the transaction semantics do not depend on which package a given repository method happens to use.
Isolation levels
ExecWithOptions takes *sql.TxOptions, as the other variant does. Exec uses the driver default, which differs between MySQL and PostgreSQL, so be explicit where it matters.
What sqlx gives you inside the callback
The methods that make the wrapper worth having:
err := sqlxtransaction.Exec(ctx, db, func(ctx context.Context, tx *sqlx.Tx) error {
if err := tx.SelectContext(ctx, &rows, "SELECT ... WHERE id = ?", id); err != nil {
return err
}
_, err := tx.NamedExecContext(ctx, "UPDATE t SET name = :name WHERE id = :id", rec)
return err
})
SelectContext scans into a slice of structs, GetContext into one, and NamedExecContext binds :name parameters from a struct or map. All of it participates in the transaction, and all of it rolls back together.
Dependencies
Reaches github.com/jmoiron/sqlx.
Links
- Reference documentation
- Source code
- The standard-library variant: sqltransaction
- Connection lifecycle: sqlconn
- All nurago packages