go.transaction_boundary
Correctness
High
Detects database operations spanning multiple queries without transaction boundaries.
Why It Matters
Section titled “Why It Matters”Without transactions:
- Partial updates — Some changes commit, others fail
- Data inconsistency — Concurrent reads see intermediate states
- Lost writes — Overwrites happen between read and update
- Recovery issues — Can’t rollback partial operations
Example
Section titled “Example”// ❌ Before (no transaction)func transfer(db *sql.DB, from, to int, amount float64) error { _, err := db.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from) if err != nil { return err } // If this fails, money is lost! _, err = db.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to) return err}// ✅ After (with transaction)func transfer(db *sql.DB, from, to int, amount float64) error { tx, err := db.Begin() if err != nil { return err } defer tx.Rollback()
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from) if err != nil { return err } _, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to) if err != nil { return err }
return tx.Commit()}What Unfault Detects
Section titled “What Unfault Detects”- Multiple
db.Exec()calls withoutdb.Begin() - Missing
tx.Commit()ortx.Rollback() - Related writes without transaction wrapper
Auto-Fix
Section titled “Auto-Fix”Unfault wraps operations in transactions:
tx, err := db.BeginTx(ctx, nil)if err != nil { return err}defer tx.Rollback()// ... operationsreturn tx.Commit()