Skip to content

Transactions

To declare a transaction, use the Transaction method which can be called recursively to define nested transactions.

Rel accepts a function with context.Context argument that is used to determine the transaction scope. Context makes it easier to call any function that involves db operation inside a transaction, because the scope of transaction is automatically passed by context.

If any error occured within transaction, the transaction will be rolled back, and returns the error. If the error is a runtime error or panic with string argument, it'll panic after rollback.

err := repo.Transaction(ctx, func(ctx context.Context) error {
    repo.Update(ctx, &book, rel.Dec("stock"))

    // Any database calls inside other function will be using the same transaction as long as it share the same context.
    Process(ctx, transaction)

    // Nested transaction
    repo.Transaction(ctx, func(ctx context.Context) error {
        repo.UpdateAny(ctx, rel.From("authors").Where(where.Eq("id", book.AuthorID)), rel.Inc("popularity"))
        repo.UpdateAny(ctx, rel.From("publishers").Where(where.Eq("name", book.Publisher)), rel.Inc("popularity"))
        return nil
    })

    return repo.Update(ctx, &transaction, rel.Set("status", "paid"))
})
repo.ExpectTransaction(func(repo *reltest.Repository) {
    repo.ExpectUpdate(rel.Dec("stock")).ForType("main.Book")

    // mock process

    repo.ExpectTransaction(func(r *reltest.Repository) {
        repo.ExpectUpdateAny(rel.From("authors").Where(where.Eq("id", 0)), rel.Inc("popularity"))
        repo.ExpectUpdateAny(rel.From("publishers").Where(where.Eq("name", "")), rel.Inc("popularity"))
    })

    repo.ExpectUpdate(rel.Set("status", "paid")).ForType("main.Transaction")
})

Last update: 2024-03-28