7.9.20. SQL-19 — UPDATE

Five flavours, mirroring the SELECT side:

Form

When to use

db |> update(row)

by-PK whole-row replace from a loaded struct

db |> _sql_update(type<T>, where, set)

bulk: predicate + named-tuple set clause

db |> _sql_update_returning(...)

bulk + capture rows AFTER the update

db |> exec(sql)

raw escape hatch for migrations / dynamic SQL

db |> try_update(...) / _sql_try_*

non-panic Result<int, string> variants

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