7.9.20. SQL-19 — UPDATE
Five flavours, mirroring the SELECT side:
Form |
When to use |
|---|---|
|
by-PK whole-row replace from a loaded struct |
|
bulk: predicate + named-tuple set clause |
|
bulk + capture rows AFTER the update |
|
raw escape hatch for migrations / dynamic SQL |
|
non-panic |
All forms return int rows-affected (or array<T> for
RETURNING). 0 rows-affected is not an error — the row simply
wasn’t there. Err from the try_ variants is reserved for
genuine SQL failures (constraint violation, IO, BUSY).
7.9.20.1. Naming
The function form is plain update / try_update. The macro
form carries the _sql_ prefix — _sql_update /
_sql_try_update / _sql_update_returning /
_sql_try_update_returning — to match the flagship
_sql(...) macro and keep the SQL provenance visible at the call
site. RETURNING is macro-only; there is no plain
update_returning function.
7.9.20.2. By-PK whole-row replace
let n1 = db |> update(User(Id = 1, Name = "alice", Email = "alice@new.com",
Active = true, LastSeen = 100l))
// UPDATE "Users" SET "Name"=?, "Email"=?, "Active"=?, "LastSeen"=?
// WHERE "Id" = ?
A non-matching PK returns 0 rows-affected. Not an error.
7.9.20.3. Bulk update via the macro
_sql_update(type<T>, where_expr, set_expr) translates the
predicate to SQL and binds captured locals automatically. The set
clause is a named-tuple literal (Col=expr, ...) whose RHS may
reference column refs (_.Other) and outer-scope locals.
let cutoff : int64 = 250l
let stale = db |> _sql_update(type<User>, _.LastSeen < cutoff, (Active = false))
// UPDATE "Users" SET "Active" = ? WHERE "LastSeen" < ?
7.9.20.4. Bulk RETURNING — capture rows AFTER the update
let touch_at : int64 = 1000l
let revived <- db |> _sql_update_returning(type<User>, _.Id == 4,
(Active = true, LastSeen = touch_at))
// UPDATE "Users" SET "Active" = ?, "LastSeen" = ? WHERE "Id" = ?
// RETURNING "Id", "Name", "Email", "Active", "LastSeen"
Useful for audit logs, “update-and-publish” pipelines, or any caller that needs the post-update row state in one round-trip.
7.9.20.5. Raw-SQL escape hatch
exec runs arbitrary SQL with no parameter binding — useful
for migrations or queries the macro can’t (or shouldn’t) translate.
For dynamic values in raw SQL, format them into the string yourself
(taking care to escape) or stick with the macro forms.
db |> exec("UPDATE Users SET Name = 'admin' WHERE Id = 1")
// db |> changes() reports rows-affected from the most recent
// INSERT / UPDATE / DELETE on this connection.
7.9.20.6. Non-panic try_ variants
Return Result<int, string>. 0 rows-affected is NOT an Err
(reserved for constraint violation / IO / BUSY).
let attempt = db |> try_update(User(Id = 999, Name = "phantom",
Email = "", Active = false, LastSeen = 0l))
if (attempt |> is_err) {
// "constraint failed: ..." or similar
} else {
let n = attempt |> unwrap // 0 if PK didn't match
}
See also
Full source: tutorials/sql/19-update.das
Previous tutorial: SQL-18 — NULL Handling: Option<T> Everywhere
Next tutorial: SQL-20 — DELETE