Senior
```go var defaultTxOptions = &sql.TxOptions{} const defaultCurrency = "RUB" func CreateUserWithDefaultCurrency(ctx context.Context, db *sql.DB, email, passwordHash string) error { tx, err := db.BeginTx(ctx, defaultTxOptions) if err != nil { return err } defer func() { if err != nil{ tx.Rollback() } }() _, err = tx.Exec("INSERT INTO users (email, password) VALUES (?, ?)", email, passwordHash) if err != nil { return err } rows, err := tx.Query("SELECT id FROM users WHERE email = ?", email) if err != nil { return err } defer rows.Close() id := 0 for rows.Next() { err = rows.Scan(&id) if err != nil { return err } } _, err = tx.Exec("INSERT INTO balances (user_id, currency, current_balance) VALUES (?, ?, 0)", id, defaultCurrency) if err != nil { return err } return tx.Commit() } ```
sobes.tech AI
Answer from AI
The provided code has several issues related to transaction management and error handling:
-
Issue with
deferanderrvariable: Indefer, the variableerris used, which may change after thedeferis declared, but in the closure, its value at the time of declaration will be used. This can lead totx.Rollback()not being called in case of errors. -
Implicit retrieval of user ID: After inserting a user, it's better to use
LastInsertId()orRETURNING id(depending on the DBMS), rather than making a separate query. -
Lack of error handling for
tx.Commit().
Corrected example:
func CreateUserWithDefaultCurrency(ctx context.Context, db *sql.DB, email, passwordHash string) error {
tx, err := db.BeginTx(ctx, defaultTxOptions)
if err != nil {
return err
}
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p)
} else if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
res, err := tx.ExecContext(ctx, "INSERT INTO users (email, password) VALUES (?, ?)", email, passwordHash)
if err != nil {
return err
}
id64, err := res.LastInsertId()
if err != nil {
return err
}
id := int(id64)
_, err = tx.ExecContext(ctx, "INSERT INTO balances (user_id, currency, current_balance) VALUES (?, ?, 0)", id, defaultCurrency)
if err != nil {
return err
}
return err
}
Thus, the transaction will correctly rollback on errors, and the user ID is obtained directly after insertion, which is more efficient and safer.