Skip to main content

Update and delete

Update and Delete write straight to the database, without loading anything, and tell you how many rows they touched. To change things you've loaded, use SaveChanges.

Update​

Every assignment is explicit, so zero values and nil are written like any other.

n, err := db.Users.
Where(ant.Eq(&U.ID, id)).
Update(ctx, ant.Set(&U.Age, 0), ant.Set(&U.Bio, nil))
UPDATE "users" SET "age" = $1, "bio" = $2, "updated_at" = $3 WHERE "users"."id" = $4

UpdatedAt moves by itself. An explicit ant.Set(&U.UpdatedAt, t) wins.

Values from the row​

Inc and Dec run in the database, so two writers can't lose each other's update. SetExpr assigns any expression, a subquery included.

_, err := db.Users.Where(ant.Eq(&U.ID, id)).Update(ctx, ant.Inc(&U.Age, 1))

_, err = db.Users.Where(ant.Eq(&U.ID, id)).Update(ctx, ant.SetExpr(&U.Email, ant.Lower(&U.Name)))

oldest := ant.SubExpr(db.Users.Where(), ant.Max(&U.Age)).Expr()

_, err = db.Users.Where(ant.Eq(&U.ID, id)).Update(ctx, ant.SetExpr(&U.Age, oldest))

Delete​

n, err := db.Users.Where(ant.Lt(&U.Age, 18)).Delete(ctx)

On a model with soft deletes this marks rows instead of removing them. Conditions can use relations, which is how to delete with a join.

n, err := db.Users.Where(ant.HasNone(&U.Posts)).Delete(ctx)

One row by key is DeleteByID, which returns ant.ErrNotFound if there's no such row.

err := db.Users.DeleteByID(ctx, id)

The no-condition guard​

A bulk write with no condition is refused with ant.ErrNoCondition, because a forgotten filter would otherwise change the whole table. Unfiltered says you really mean it.

_, err := db.Users.Query().Delete(ctx) // ant.ErrNoCondition

n, err := db.Users.Unfiltered().Delete(ctx) // every row, on purpose

The guard also sees through conditions that can't exclude a row. Those are the dangerous ones, because the code looks filtered.

var none []string

_, err := db.Users.Where(ant.And()).Delete(ctx) // refused
_, err = db.Users.Where(ant.NotIn(&U.Name, none...)).Delete(ctx) // refused

n, err := db.Users.Where(ant.In(&U.Name, none...)).Delete(ctx) // fine: deletes nothing

A When that was skipped leaves the guard on too.

Limiting a write​

Limit and Offset bound a bulk write, oldest first with an ordering.

n, err := db.Users.Where(ant.Lt(&U.Age, 18)).OrderBy(&U.CreatedAt).Limit(100).Delete(ctx)

Getting the rows back​

UpdateReturning and DeleteReturning give back what they touched. Support varies: see databases.

changed, err := db.Users.Where(ant.Gt(&U.Age, 18)).UpdateReturning(ctx, ant.Inc(&U.Age, 1))

removed, err := db.Users.Where(ant.Eq(&U.Name, "Bob")).DeleteReturning(ctx)

Reloading an entity​

Reload replaces an entity's columns with what is stored now, discarding unsaved changes. Relations are left alone.

_, err := db.Users.Where(ant.Eq(&U.ID, user.ID)).Update(ctx, ant.Set(&U.Age, 41))

err = db.Users.Reload(ctx, user)
Bulk writes skip the model layer

Update and Delete run no hooks and no SaveInterceptor, because they load no entities, and a bulk update doesn't touch tracked structs. They do apply global filters and timestamps.