| package main |
| |
| import ( |
| "database/sql" |
| "fmt" |
| "log" |
| |
| "github.com/kisielk/sqlstruct" |
| ) |
| |
| const ORDER_PENDING = 0 |
| const ORDER_CANCELLED = 1 |
| |
| type User struct { |
| Id int `sql:"id"` |
| Username string `sql:"username"` |
| Balance float64 `sql:"balance"` |
| } |
| |
| type Order struct { |
| Id int `sql:"id"` |
| Value float64 `sql:"value"` |
| ReservedFee float64 `sql:"reserved_fee"` |
| Status int `sql:"status"` |
| } |
| |
| func cancelOrder(id int, db *sql.DB) (err error) { |
| tx, err := db.Begin() |
| if err != nil { |
| return |
| } |
| |
| var order Order |
| var user User |
| sql := fmt.Sprintf(` |
| SELECT %s, %s |
| FROM orders AS o |
| INNER JOIN users AS u ON o.buyer_id = u.id |
| WHERE o.id = ? |
| FOR UPDATE`, |
| sqlstruct.ColumnsAliased(order, "o"), |
| sqlstruct.ColumnsAliased(user, "u")) |
| |
| // fetch order to cancel |
| rows, err := tx.Query(sql, id) |
| if err != nil { |
| tx.Rollback() |
| return |
| } |
| |
| defer rows.Close() |
| // no rows, nothing to do |
| if !rows.Next() { |
| tx.Rollback() |
| return |
| } |
| |
| // read order |
| err = sqlstruct.ScanAliased(&order, rows, "o") |
| if err != nil { |
| tx.Rollback() |
| return |
| } |
| |
| // ensure order status |
| if order.Status != ORDER_PENDING { |
| tx.Rollback() |
| return |
| } |
| |
| // read user |
| err = sqlstruct.ScanAliased(&user, rows, "u") |
| if err != nil { |
| tx.Rollback() |
| return |
| } |
| rows.Close() // manually close before other prepared statements |
| |
| // refund order value |
| sql = "UPDATE users SET balance = balance + ? WHERE id = ?" |
| refundStmt, err := tx.Prepare(sql) |
| if err != nil { |
| tx.Rollback() |
| return |
| } |
| defer refundStmt.Close() |
| _, err = refundStmt.Exec(order.Value+order.ReservedFee, user.Id) |
| if err != nil { |
| tx.Rollback() |
| return |
| } |
| |
| // update order status |
| order.Status = ORDER_CANCELLED |
| sql = "UPDATE orders SET status = ?, updated = NOW() WHERE id = ?" |
| orderUpdStmt, err := tx.Prepare(sql) |
| if err != nil { |
| tx.Rollback() |
| return |
| } |
| defer orderUpdStmt.Close() |
| _, err = orderUpdStmt.Exec(order.Status, order.Id) |
| if err != nil { |
| tx.Rollback() |
| return |
| } |
| return tx.Commit() |
| } |
| |
| func main() { |
| // @NOTE: the real connection is not required for tests |
| db, err := sql.Open("mysql", "root:@/orders") |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer db.Close() |
| err = cancelOrder(1, db) |
| if err != nil { |
| log.Fatal(err) |
| } |
| } |