Update
db.update(table) starts an UPDATE, and set adds a SET clause. The builder carries a
SAFE type parameter: until a WHERE clause exists, or until you say all_rows(), the
statement cannot be awaited.
A filter is part of the type
Database::update returns Update<'a, E, T, false>. The IntoFuture implementation — the
thing that makes .await work — exists only for SAFE = true:
So this does not compile:
There are exactly two ways to flip the flag:
all_rows() is not a hidden bypass: it is a method whose name spells out the consequence.
filter and all_rows are the only methods that exist solely on the unsafe state, so a SET
with no WHERE cannot be built by accident.
to_sql() is available before either call. Rendering an unsafe statement is fine; only
execution needs the flag, which makes it possible to assert the SQL in a unit test.
set versus set_expr
set binds a Rust value, checked against the column's Rust type including nullability:
A nullable column takes an Option<T>, because that is the column's Rust type. A bare
String for users.bio does not compile; pass Some(value) or None::<String>.
set_expr sends an expression for the database to evaluate against the row it is updating,
which is what counters and timestamps need:
The difference matters: set reads the old value in Rust and writes a constant, while
set_expr computes the new value in the database. The expression must have the column's SQL
type; .as_expr().cast::<..>() is there when it does not. The two methods mix freely in one
statement, and set_expr may be called more than once.
Writing a whole row
set_model sets every column from a model, skipping the primary key and any auto-incrementing
column:
The filter is still yours, because set_model has no opinion about which rows you meant — and
the type-state is what keeps that honest.
returning
returning exists only once the statement is safe, so an unsafe update cannot sneak a result
type in:
It takes the same selections as pick: a column, an expression, a tuple
or the table handle.
RETURNING is dialect-dependent — PostgreSQL and SQLite support it, MySQL does not, and the
MySQL failure is a build error (unsupported: `mysql` does not support `RETURNING` ) rather
than a compile error.
What is checked
Gotchas
filtercan only be called once. The first call returnsSAFE = true, and the method exists only on the unsafe type. Combine conditions withand(..)/or(..)instead of chaining two filters.- There are no joins in an update. The row set is
(T,), so there is noUPDATE ... FROMand no joined column in the filter. Userawfor a subquery, or restructure the statement. all_rows()removes the compiler's objection, not the risk. It is the only way to write a full-table update, and it is exactly what you should read twice in a diff.returningcannot be rendered. Once it is called the builder changes type and has noto_sql(), so assert the SQL before adding the clause.- A nullable column needs an
Option.None::<String>is usually needed to spell the type; a bareNoneoften cannot be inferred. - MySQL cannot return the modified rows. Select them before or after the write, or read
rows_affected()from the driver output.
