sqltransaction

sqltransaction executes business logic inside a transaction with begin/commit/rollback control flow and consistent error handling.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/sqltransaction"

Package sqltransaction executes business logic inside a transaction with begin/commit/rollback control flow and consistent error handling.

How It Works

Exec and ExecWithOptions execute a caller-provided ExecFunc inside a transaction:

  1. Start transaction via DB.BeginTx.
  2. Run the provided function with *sql.Tx.
  3. Commit on success.
  4. Roll back automatically if the function fails or commit is not reached.

Rollback behavior is guarded to avoid noisy false failures:

  • rollback is skipped after successful commit,
  • sql.ErrTxDone during rollback is ignored,
  • rollback failures are joined with the current error for full diagnostics.

Usage

err := sqltransaction.Exec(ctx, db, func(ctx context.Context, tx *sql.Tx) error {
    // Execute all related SQL operations using tx.
    // Return an error to trigger rollback.
    return nil
})
if err != nil {
    return err
}

For a similar helper using github.com/jmoiron/sqlx instead of database/sql, see: github.com/tecnickcom/nurago/pkg/sqlxtransaction

When To Use

  • Several statements must succeed or fail together.
  • You want rollback guaranteed on error and on panic, without repeating the boilerplate.

Example

// A real program would use a *sql.DB from sql.Open. The mock keeps the
// example self-contained and its output deterministic.
db, mock, err := sqlmock.New()
if err != nil {
	fmt.Println(err)

	return
}

defer func() { _ = db.Close() }()

mock.ExpectBegin()
mock.ExpectExec("UPDATE balance").WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectCommit()

// The transaction is committed when run returns nil, and rolled back
// when it returns an error or panics.
err = sqltransaction.Exec(
	context.TODO(),
	db,
	func(ctx context.Context, tx *sql.Tx) error {
		_, err := tx.ExecContext(ctx, "UPDATE balance SET amount = amount - 10 WHERE id = 1")
		if err != nil {
			return fmt.Errorf("debiting account: %w", err)
		}

		return nil
	},
)

fmt.Println("committed:", err)

// Output:
// committed: <nil>

Full source is in example_sqltransaction_test.go. More runnable examples are on pkg.go.dev.

Dependencies

This package reaches no external module: it uses only the Go standard library.