Documentation
¶
Overview ¶
Package transaction provides a context-bound unit of work: the Go analog of a Spring @Transactional method over a single datasource. A Manager owns the database transaction lifecycle, and stores resolve the querier they execute against from the context instead of receiving a *sql.Tx through their signatures. Nested Run calls join the transaction already bound to the context, so a use case that spans several stores runs them in one transaction without threading it by hand.
Run accepts options that mirror the attributes of @Transactional: propagation (Required, Supports, Mandatory, Never, Nested), isolation level, read-only, timeout, and rollback rules. Work may also register callbacks for the before-commit, before-completion, after-commit, and after-completion synchronization phases.
A Manager may also be constructed with ExecutionListeners that observe the begin, commit, and rollback steps of each new transaction, the seam for logging, metrics, and tracing.
The synchronization and savepoint callbacks require an active unit of work: the Register functions report false on a non-transactional path (Supports or Never without an active transaction), so callbacks are not maintained there, and registration is multiplicity-preserving, so the same callback registered twice runs twice.
The package depends only on database/sql; it knows nothing of the query builder the stores use.
Index ¶
- Variables
- func CurrentTransactionName(ctx context.Context) (string, bool)
- func MarkRollbackOnly(ctx context.Context) bool
- func RegisterAfterCommit(ctx context.Context, callback func(ctx context.Context), ...) bool
- func RegisterAfterCompletion(ctx context.Context, callback func(ctx context.Context, status Status), ...) bool
- func RegisterBeforeCommit(ctx context.Context, callback func(ctx context.Context) error, ...) bool
- func RegisterBeforeCompletion(ctx context.Context, callback func(ctx context.Context), ...) bool
- func RegisterSavepoint(ctx context.Context, callback func(ctx context.Context, savepoint string), ...) bool
- func RegisterSavepointRollback(ctx context.Context, callback func(ctx context.Context, savepoint string), ...) bool
- func RunResult[T any](ctx context.Context, transactor Transactor, ...) (T, error)
- type ExecutionListener
- type Manager
- type Option
- func NoRollbackForError(targets ...error) Option
- func NoRollbackForFunc(predicate func(error) bool) Option
- func ReadOnly() Option
- func RollbackForError(targets ...error) Option
- func RollbackForFunc(predicate func(error) bool) Option
- func WithIsolation(level sql.IsolationLevel) Option
- func WithName(name string) Option
- func WithPropagation(propagation Propagation) Option
- func WithTimeout(timeout time.Duration) Option
- type Propagation
- type Querier
- type RegisterOption
- type Status
- type TransactionStatus
- func (s TransactionStatus) HasSavepoint() bool
- func (s TransactionStatus) IsCompleted() bool
- func (s TransactionStatus) IsNewTransaction() bool
- func (s TransactionStatus) IsReadOnly() bool
- func (s TransactionStatus) IsRollbackOnly() bool
- func (s TransactionStatus) Name() string
- func (s TransactionStatus) SetRollbackOnly()
- type Transactor
Constants ¶
This section is empty.
Variables ¶
var ( // ErrRollbackOnly is returned by the outermost Run when a joining call // failed and marked the unit rollback only, even though that call's error // was handled by its own caller. It is the analog of Spring's // globalRollbackOnly. ErrRollbackOnly = errors.New("unit of work marked rollback only") // ErrTransactionRequired is returned by Run with Mandatory propagation when // no transaction is active. ErrTransactionRequired = errors.New("mandatory propagation requires an active transaction") // ErrTransactionNotAllowed is returned by Run with Never propagation when a // transaction is already active. ErrTransactionNotAllowed = errors.New("never propagation forbids an active transaction") // ErrIncompatibleJoin is returned when a call joins an active transaction // with options the active transaction cannot honor, such as a stricter // isolation level or a read-only guarantee over a read-write transaction. ErrIncompatibleJoin = errors.New("requested options are incompatible with the active transaction") // ErrBeginFailed wraps the driver error when a transaction cannot be begun. // It is the analog of Spring's CannotCreateTransactionException; the wrapped // driver error remains reachable through errors.Is and errors.As. ErrBeginFailed = errors.New("could not begin the transaction") // ErrTransactionSystem wraps the driver error when an infrastructure step of // the transaction fails: a commit, a rollback, or a savepoint operation. It // is the analog of Spring's TransactionSystemException and lets a caller tell // an infrastructure failure apart from the business error work returned. The // wrapped driver error remains reachable through errors.Is and errors.As. ErrTransactionSystem = errors.New("transaction system failure") // ErrInvalidTimeout is returned by Run when a negative timeout is configured // through WithTimeout. The default, a zero duration, means no timeout. It is // the analog of Spring's InvalidTimeoutException. ErrInvalidTimeout = errors.New("timeout must not be negative") // ErrTransactionTimedOut wraps the work error when a transaction's own // timeout, configured through WithTimeout, expires before work completes. It // is the analog of Spring's TransactionTimedOutException and gives callers one // stable errors.Is target regardless of the driver's context-cancellation // error. A cancellation of the caller's own context is not reported as a // timeout. ErrTransactionTimedOut = errors.New("transaction timed out") )
Functions ¶
func CurrentTransactionName ¶
CurrentTransactionName returns the name of the active transaction and true, or the empty string and false when no transaction is active. It is the analog of Spring's getCurrentTransactionName, letting logging or monitoring code read the name without holding a TransactionStatus.
func MarkRollbackOnly ¶
MarkRollbackOnly marks the active transaction so the outermost Run rolls it back even when work returns nil. It reports false when no transaction is active. It is the free-function form of TransactionStatus.SetRollbackOnly.
func RegisterAfterCommit ¶
func RegisterAfterCommit( ctx context.Context, callback func(ctx context.Context), opts ...RegisterOption, ) bool
RegisterAfterCommit schedules callback to run after the outermost transaction commits successfully. It reports false when no unit of work is active, so the caller can fall back to immediate execution on the auto-commit path. The callback receives a context whose transaction has been detached, so any database work it performs runs on the database, never on the closed transaction.
A panic from the callback is recovered and logged, not propagated: the transaction is already durably committed, so an observation failure must not surface to the caller as a failed transaction, which could trigger a retry of committed work, and must not abort the remaining after-commit callbacks. This diverges from Spring, where an afterCommit exception propagates out of the commit to the caller and aborts the remaining callbacks.
func RegisterAfterCompletion ¶
func RegisterAfterCompletion( ctx context.Context, callback func(ctx context.Context, status Status), opts ...RegisterOption, ) bool
RegisterAfterCompletion schedules callback to run after the outermost transaction commits or rolls back, receiving the final Status. It reports false when no unit of work is active.
func RegisterBeforeCommit ¶
func RegisterBeforeCommit( ctx context.Context, callback func(ctx context.Context) error, opts ...RegisterOption, ) bool
RegisterBeforeCommit schedules callback to run inside the transaction, just before the outermost commit. Returning an error vetoes the commit and forces a rollback. It reports false when no unit of work is active. The callback receives the in-transaction context, so its querier resolves to the transaction.
func RegisterBeforeCompletion ¶
func RegisterBeforeCompletion( ctx context.Context, callback func(ctx context.Context), opts ...RegisterOption, ) bool
RegisterBeforeCompletion schedules callback to run just before the outermost transaction commits or rolls back, while the transaction is still bound. It reports false when no unit of work is active.
The callback cannot veto by returning, having no error result, but a panic forces a rollback and is re-raised to the caller once the transaction has been settled. This diverges from Spring, where a beforeCompletion exception is logged and the transaction still commits.
func RegisterSavepoint ¶
func RegisterSavepoint( ctx context.Context, callback func(ctx context.Context, savepoint string), opts ...RegisterOption, ) bool
RegisterSavepoint schedules callback to run just after a savepoint is created, that is when a Nested Run takes a savepoint of the active transaction. The savepoint argument identifies the savepoint. It reports false when no unit of work is active. A panic from the callback rolls the whole transaction back.
func RegisterSavepointRollback ¶
func RegisterSavepointRollback( ctx context.Context, callback func(ctx context.Context, savepoint string), opts ...RegisterOption, ) bool
RegisterSavepointRollback schedules callback to run just before a rollback to a savepoint, that is when a Nested Run rolls its savepoint back. The savepoint argument identifies the savepoint. It reports false when no unit of work is active. A panic from the callback rolls the whole transaction back.
func RunResult ¶
func RunResult[T any]( ctx context.Context, transactor Transactor, work func(ctx context.Context) (T, error), opts ...Option, ) (T, error)
RunResult runs work as a unit of work like Transactor.Run, but lets work return a value alongside its error. The value work produced is returned with the error Run reports; on a failed or rolled-back transaction the caller should consult the error rather than the value. It is the generic counterpart of Spring's TransactionTemplate.execute, expressed as a free function because Go methods cannot be generic.
Types ¶
type ExecutionListener ¶
type ExecutionListener struct {
// BeforeBegin runs before the transaction is begun.
BeforeBegin func(ctx context.Context, status TransactionStatus)
// AfterBegin runs after the begin step, with the error it produced or nil on
// success. On failure no transaction is active and the Run returns that same
// error.
AfterBegin func(ctx context.Context, status TransactionStatus, beginErr error)
// BeforeCommit runs inside the transaction, just before it is committed.
BeforeCommit func(ctx context.Context, status TransactionStatus)
// AfterCommit runs after the commit step, with the error it produced or nil
// on success. It runs after the after-commit and after-completion
// synchronizations.
AfterCommit func(ctx context.Context, status TransactionStatus, commitErr error)
// BeforeRollback runs just before the transaction is rolled back.
BeforeRollback func(ctx context.Context, status TransactionStatus)
// AfterRollback runs after the rollback step, with the error it produced or
// nil on success. It runs after the after-completion synchronizations.
AfterRollback func(ctx context.Context, status TransactionStatus, rollbackErr error)
}
ExecutionListener observes the lifecycle of the physical database transaction a Manager drives: the begin, commit, and rollback of a newly begun transaction. It is the analog of Spring's TransactionExecutionListener and is meant for stateless observation — logging, metrics, tracing — not for taking part in the transaction; use the synchronization phases (RegisterBeforeCommit and its companions) for that.
Every hook is optional: a nil field is skipped. The before hooks run just before their step, the after hooks just after it, receiving the error the step produced or nil on success. Hooks fire only around the physical begin, commit, and rollback of a new transaction; they do not fire for a Run that joins an active transaction (which performs no physical begin or commit) nor for the savepoint operations of Nested propagation. The commit and rollback hooks run after the corresponding synchronization phases, mirroring Spring's ordering.
A panic raised by a hook is recovered and logged, never propagated, so an observation callback can never disturb the transaction lifecycle. This diverges from Spring, where a listener exception propagates to the caller.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager begins, commits, and rolls back the database transactions its units of work run in. It is immutable after construction and safe for concurrent use; concurrency is bounded by the single SQLite writer.
func NewManager ¶
func NewManager(database *sql.DB, listeners ...ExecutionListener) *Manager
NewManager constructs a Manager over an open database. The optional listeners observe the begin, commit, and rollback steps of every new transaction the Manager drives; see ExecutionListener.
func (*Manager) Querier ¶
Querier resolves the executor for the current context: the active transaction when a unit of work is in progress, otherwise the database for an auto-commit statement. Stores pass the result as the final querier argument of their terminal calls, mirroring DataSourceUtils.getConnection.
func (*Manager) Run ¶
func (manager *Manager) Run( ctx context.Context, work func(ctx context.Context) error, opts ...Option, ) error
Run executes work as a unit of work configured by opts. With no options it uses Required propagation at the database default isolation: it joins an active transaction or begins one, commits when work returns nil, and rolls back on error or panic. The propagation option selects a different relation to an active transaction; see Propagation.
type Option ¶
type Option func(*definition)
Option configures a single Run. Options are the programmatic equivalent of the attributes of Spring's @Transactional.
func NoRollbackForError ¶
NoRollbackForError keeps the transaction committable when work returns an error matching, through errors.Is, any of the given sentinels, unless a RollbackForError or RollbackForFunc rule also matches. The error is still returned to the caller.
func NoRollbackForFunc ¶
NoRollbackForFunc keeps the transaction committable when predicate reports true for the error work returned. The error is still returned to the caller.
func ReadOnly ¶
func ReadOnly() Option
ReadOnly marks a newly begun transaction read only, a hint the driver may use to optimize or to refuse writes. It has no effect when the call joins an existing transaction.
func RollbackForError ¶
RollbackForError forces a rollback when work returns an error matching, through errors.Is, any of the given sentinels, overriding any no-rollback rule that would otherwise excuse it. It is redundant with the default, which rolls back on every error, and is only useful to re-include an error a broader NoRollbackForError or NoRollbackForFunc rule would have committed.
func RollbackForFunc ¶
RollbackForFunc forces a rollback when predicate reports true for the error work returned, overriding any no-rollback rule that would otherwise excuse it.
func WithIsolation ¶
func WithIsolation(level sql.IsolationLevel) Option
WithIsolation sets the isolation level a newly begun transaction requests of the driver. It has no effect when the call joins an existing transaction.
func WithName ¶
WithName labels the unit of work, surfaced through TransactionStatus.Name for logging and monitoring. It has no effect on the transaction itself.
func WithPropagation ¶
func WithPropagation(propagation Propagation) Option
WithPropagation selects the propagation behavior. The default is Required.
func WithTimeout ¶
WithTimeout bounds the duration of a newly begun transaction: its context is cancelled once the timeout elapses, so a statement that overruns fails and the transaction rolls back. It has no effect when the call joins an existing transaction. A zero duration, the default, means no timeout; a negative duration is invalid and makes Run fail with ErrInvalidTimeout. When the timeout elapses, the error Run returns wraps ErrTransactionTimedOut.
type Propagation ¶
type Propagation int
Propagation selects how Run relates to a transaction that may already be bound to the context, mirroring Spring's propagation behaviors. The two behaviors that suspend the active transaction or open a second concurrent one (REQUIRES_NEW and NOT_SUPPORTED) are intentionally omitted: on a single-writer SQLite database a second concurrent transaction would deadlock against the write lock the first one already holds.
const ( // Required joins an active transaction or begins a new one. It is the // default and the propagation most callers need. Required Propagation = iota // Supports joins an active transaction when one exists, and otherwise runs // without a transaction. Supports // Mandatory joins an active transaction and fails with // ErrTransactionRequired when none exists. Mandatory // Never runs without a transaction and fails with ErrTransactionNotAllowed // when one is already active. Never // Nested runs within a savepoint of the active transaction, so its work can // roll back to the savepoint without aborting the outer transaction. It // begins a new transaction when none is active. Nested )
func (Propagation) String ¶
func (p Propagation) String() string
String renders the propagation for logs and test failures.
type Querier ¶
type Querier interface {
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}
Querier is the execution surface the stores run their statements against. It is satisfied by *sql.DB, *sql.Tx, and *sql.Conn, and its method set matches the minimal querier a query builder accepts, so the resolved value can be passed straight to one without this package importing the builder.
type RegisterOption ¶
type RegisterOption func(*registration)
RegisterOption configures a synchronization registration.
func WithOrder ¶
func WithOrder(order int) RegisterOption
WithOrder sets the order of a synchronization callback within its phase. Lower orders run first; callbacks with equal orders run in registration order. The default order is zero, so an unordered callback runs before any positive-order callback and after any negative-order one. This differs from Spring, whose default order is the lowest precedence, running unordered synchronizations last.
type Status ¶
type Status int
Status reports how a unit of work completed, mirroring the completion statuses a Spring transaction synchronization receives.
type TransactionStatus ¶
type TransactionStatus struct {
// contains filtered or unexported fields
}
TransactionStatus exposes the live state of the unit of work bound to the current context, the introspection analog of Spring's TransactionStatus. It is obtained through StatusFromContext.
func StatusFromContext ¶
func StatusFromContext(ctx context.Context) (TransactionStatus, bool)
StatusFromContext returns the status of the active unit of work and true, or a zero status and false when no transaction is active.
func (TransactionStatus) HasSavepoint ¶
func (s TransactionStatus) HasSavepoint() bool
HasSavepoint reports whether the current Run runs inside a savepoint, that is under Nested propagation within an existing transaction.
func (TransactionStatus) IsCompleted ¶
func (s TransactionStatus) IsCompleted() bool
IsCompleted reports whether the transaction has settled, that is committed or rolled back. Once completed, SetRollbackOnly has no effect.
func (TransactionStatus) IsNewTransaction ¶
func (s TransactionStatus) IsNewTransaction() bool
IsNewTransaction reports whether the current Run began the transaction rather than joining or nesting within an existing one.
func (TransactionStatus) IsReadOnly ¶
func (s TransactionStatus) IsReadOnly() bool
IsReadOnly reports whether the transaction was begun read only.
func (TransactionStatus) IsRollbackOnly ¶
func (s TransactionStatus) IsRollbackOnly() bool
IsRollbackOnly reports whether the transaction has been marked rollback only.
func (TransactionStatus) Name ¶
func (s TransactionStatus) Name() string
Name returns the label given to the transaction through WithName, or the empty string when none was set.
func (TransactionStatus) SetRollbackOnly ¶
func (s TransactionStatus) SetRollbackOnly()
SetRollbackOnly marks the transaction so the outermost Run rolls it back even when work returns nil. It has no effect once the transaction has completed.
type Transactor ¶
type Transactor interface {
Run(ctx context.Context, work func(ctx context.Context) error, opts ...Option) error
Querier(ctx context.Context) Querier
}
Transactor is the slice of the Manager that stores depend on: it runs a unit of work and resolves the querier the store executes against. It is satisfied by *Manager.