Optimistic concurrency
Two people load the same row, and both save. By default the second save wins for the columns it changed. A version field makes it fail instead, so nobody overwrites work they never saw.
A version field
ant:"version" starts at 1 and goes up with every save. A save only goes through if the row still has the version you loaded.
type Page struct {
ID int64
Title string
Version int64 `ant:"version"`
}
When writers collide
one, two := ant.Session(db), ant.Session(db)
a, err := one.Pages.Find(ctx, id)
b, err := two.Pages.Find(ctx, id)
a.Title = "first"
err = one.SaveChanges(ctx) // fine
b.Title = "second"
err = two.SaveChanges(ctx) // ant.ErrConcurrency
Bulk updates and deletes count as writes, and a delete checks the version too.
Handling a conflict
ant.ConcurrencyError tells you which row it was. Reload it to see what's there now, and try again.
err := db.SaveChanges(ctx)
var conflict *ant.ConcurrencyError
if errors.As(err, &conflict) {
fmt.Println(conflict.Model, conflict.Key)
return db.Pages.Reload(ctx, page)
}
A token you manage
ant:"token" works the same way with a value your application sets, such as an ETag. A NULL token compares correctly too.
type Stock struct {
ID int64
Quantity int
ETag *string `ant:"token"`
}
See it tested: tests/features/lifecycle/concurrency_test.go