Transactions
Phi provides closure-based transactions with automatic rollback and panic recovery, as well as manual transaction handles.
1. Closure-Based Transactions (`db.Transaction`)
db.Transaction(ctx, fn) manages the entire lifecycle of a transaction automatically:
err := db.Transaction(ctx, func(tx *phi.Tx) error {
// 1. Create a user within the transaction
u, err := tx.User.Create().
SetEmail("tx-user@example.com").
SetPhoneNum("+1999").
Exec(ctx)
if err != nil {
return err // Triggers automatic tx.Rollback()
}
// 2. Create a post associated with the new user
_, err = tx.Post.Create().
SetTitle("First Post").
SetContent("Hello World").
SetAuthorId(u.Id).
Exec(ctx)
if err != nil {
return err // Triggers automatic tx.Rollback()
}
return nil // Triggers automatic tx.Commit()
})2. Lifecycle & Execution Rules
Automatic Begin: Executes
db.BeginTx(ctx, nil)to start the transaction.Commit on Success: If
fn(tx)returnsnil,tx.Commit()is called automatically.Rollback on Error: If
fn(tx)returns a non-nilerror,tx.Rollback()is automatically executed, and the original error is returned.Panic Protection & Recovery: If a panic occurs anywhere inside
fn(tx), Phi's internaldeferblock catches the panic, rolls back the transaction to prevent database lockups, and then re-throws (repanic) the original panic:// Internal Phi Panic-Safety Guard: defer func() { if p := recover(); p != nil { _ = tx.Rollback() // Guarantees database rollback before repanicking panic(p) // Re-throws original panic } }()
3. Manual Transactions (`db.BeginTx`)
For workflows requiring manual control across function boundaries:
// 1. Begin manual transaction
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
// 2. Execute operations on tx
u, err := tx.User.Create().
SetEmail("manual-tx@example.com").
SetPhoneNum("+2000").
Exec(ctx)
if err != nil {
_ = tx.Rollback()
return err
}
// 3. Commit manually
if err := tx.Commit(); err != nil {
return err
}