feat(tx): add WithTxValue for transactional return values
WithTxValue runs a callback inside a transaction and returns its result, with rollback on error. Includes tests and documentation updates. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
- `StringKSUID.Equal` / `IsZero`, `BinaryKSUID.Equal` / `IsZero`, and nil sentinel vars for [ApplyInterfacePtr].
|
||||
- `Query`, `MustQuery`, and `UpdateOne` helpers (+ Context variants).
|
||||
- `WithTxValue` for transactional functions that return a value.
|
||||
- Context-aware variants for all query and mutation helpers.
|
||||
- `Delete`, `DeleteAffected`, `WithTx`, `ContainsCol`, and `CurrentDialect()`.
|
||||
- Package documentation (`doc.go`), expanded README, and subpackage docs for `dbxm` / `dbxp`.
|
||||
|
||||
@@ -44,7 +44,7 @@ For Postgres, use `dbx.DialectPostgres` and blank-import `dbxp` instead of `dbxm
|
||||
|------|-----------|
|
||||
| Query | `Fetch`, `MustFetch`, `FetchOne`, `MustFetchOne`, `Query`, `MustQuery` (+ `*Context` variants) |
|
||||
| Mutations | `Insert`, `InsertReturning`, `Update`, `UpdateAffected`, `UpdateOne`, `UpdateReturning`, `Delete`, `DeleteAffected` (+ `*Context` variants) |
|
||||
| Transactions | `WithTx` |
|
||||
| Transactions | `WithTx`, `WithTxValue` |
|
||||
| Columns | `NormalCols`, `ContainsCol`, `ExprValues`, `ExprStringers` |
|
||||
| Partial update | `ApplyPtr`, `ApplyComplexPtr`, `ApplyInterfacePtr`, `ApplyVal` |
|
||||
| Pointers | `Ptr`, `Val`, `NowPtr`, `TrimPtr`, `TrimPtrToNil`, `IsZero` |
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// [Fetch], [FetchOne], [Query], [Insert], [Update], [Delete], and their Must*
|
||||
// and Context variants wrap Jet [Statement] execution with consistent error
|
||||
// semantics. [UpdateOne] requires at least one row affected.
|
||||
// [WithTx] runs a function inside a SQL transaction.
|
||||
// [WithTx] and [WithTxValue] run functions inside SQL transactions.
|
||||
//
|
||||
// # Jet column utilities
|
||||
//
|
||||
|
||||
@@ -24,3 +24,30 @@ func WithTx(ctx context.Context, db QueryExecTx, fn func(tx *sql.Tx) error) erro
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// WithTxValue begins a transaction on db, runs fn, and commits on success.
|
||||
// Returns the value from fn or zero and an error if begin, fn, or commit fails.
|
||||
// The transaction is rolled back if fn returns an error.
|
||||
func WithTxValue[T any](ctx context.Context, db QueryExecTx, fn func(tx QueryExec) (T, error)) (T, error) {
|
||||
var zero T
|
||||
if err := ctx.Err(); err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
result, err := fn(tx)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return zero, err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWithTxValue_ReturnsResult(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
|
||||
result, err := WithTxValue(context.Background(), db, func(tx QueryExec) (int, error) {
|
||||
return 42, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 42, result)
|
||||
}
|
||||
|
||||
func TestWithTxValue_RollbackOnError(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
fail := errors.New("fail")
|
||||
|
||||
_, err := WithTxValue(context.Background(), db, func(tx QueryExec) (int, error) {
|
||||
return 0, fail
|
||||
})
|
||||
assert.ErrorIs(t, err, fail)
|
||||
}
|
||||
|
||||
func TestWithTxValue_CancelledContext(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
_, err := WithTxValue(ctx, openTestDB(t), func(tx QueryExec) (int, error) {
|
||||
return 1, nil
|
||||
})
|
||||
assert.ErrorIs(t, err, context.Canceled)
|
||||
}
|
||||
Reference in New Issue
Block a user