Sobes.tech
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:

  1. Issue with defer and err variable: In defer, the variable err is used, which may change after the defer is declared, but in the closure, its value at the time of declaration will be used. This can lead to tx.Rollback() not being called in case of errors.

  2. Implicit retrieval of user ID: After inserting a user, it's better to use LastInsertId() or RETURNING id (depending on the DBMS), rather than making a separate query.

  3. 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.